798 lines
30 KiB
JavaScript
798 lines
30 KiB
JavaScript
const translations = {
|
||
en: {
|
||
"lang.label": "Language",
|
||
"lang.en": "English",
|
||
"lang.de": "Deutsch",
|
||
|
||
"auth.loginTab": "Log in",
|
||
"auth.registerTab": "Register",
|
||
"auth.username": "Username",
|
||
"auth.password": "Password",
|
||
"auth.displayName": "Display name (shows to group)",
|
||
"auth.adminKey": "Admin key (optional)",
|
||
"auth.loginSubmit": "Log in",
|
||
"auth.registerSubmit": "Create account",
|
||
"auth.loginHeading": "Log in",
|
||
"auth.registerHeading": "Create account",
|
||
"auth.switchToRegister": "Need an account? Register",
|
||
"auth.switchToLogin": "Have an account? Log in",
|
||
"auth.logout": "Logout",
|
||
"auth.welcome": "Welcome, {name}!",
|
||
"auth.defaultName": "Player",
|
||
"auth.loading": "Loading…",
|
||
"auth.needCredentials": "Username and password required",
|
||
"auth.invalidCredentials": "Invalid username or password",
|
||
|
||
"phase.suggest": "Suggest",
|
||
"phase.reveal": "Reveal",
|
||
"phase.vote": "Vote",
|
||
"phase.results": "Results",
|
||
"phase.loading": "Loading…",
|
||
|
||
"counts.format": "Players: {players} • Suggestions: {suggestions} • Votes: {votes}",
|
||
|
||
"nav.prev": "Back",
|
||
"nav.next": "Next",
|
||
"nav.waitingForResults": "Waiting…",
|
||
"nav.freezeTitle": "Ready to reveal?",
|
||
"nav.freezeHint": "Moving forward will freeze your suggestions. Game names become locked; only extra details stay editable.",
|
||
"nav.freezeModalTitle": "Freeze suggestions?",
|
||
"nav.freezeModalBody": "Once you leave Suggest, your games are locked: game names cannot be changed or deleted. Only optional details (description, links, players, artwork) remain editable. Continue?",
|
||
"nav.voteHint": "Cast votes for every game to unlock results.",
|
||
"nav.voteFinalized": "✅ You finalized your votes. Sit back and relax while the other players finalize their votes.",
|
||
|
||
"suggest.title": "Suggest games (up to 5)",
|
||
"suggest.new": "Add new suggestion",
|
||
"suggest.addButton": "Suggest a game",
|
||
"suggest.maxReached": "max limit reached",
|
||
"suggest.jokerAddButton": "🃏 Joker: add another game",
|
||
"suggest.hint": "Only you can see your suggestions until voting starts.",
|
||
"form.gameName": "Game name *",
|
||
"form.genre": "Genre",
|
||
"form.description": "Description",
|
||
"form.players": "Players",
|
||
"form.min": "Min",
|
||
"form.max": "Max",
|
||
"form.screenshot": "Screenshot URL",
|
||
"form.youtube": "YouTube URL",
|
||
"form.gameUrl": "Game website URL",
|
||
"form.submit": "Submit",
|
||
"form.placeholder.description": "Short description",
|
||
"form.placeholder.gameName": "Game name *",
|
||
"form.placeholder.genre": "Genre",
|
||
"form.placeholder.screenshot": "Screenshot URL",
|
||
"form.placeholder.youtube": "YouTube URL",
|
||
"form.placeholder.gameUrl": "Game website URL",
|
||
"form.playersInvalid": "Players must be between 1 and 32, and min cannot exceed max.",
|
||
"form.screenshotHint": "Use a public direct image link (http/https), max 5 MB. Avoid shortlinks/redirects.",
|
||
"form.screenshotInvalid": "Screenshot must be a direct http/https image URL (png, jpg, jpeg, gif, webp, avif) under 5 MB and not a redirect/shortlink.",
|
||
|
||
"section.mySuggestions": "Your suggestions",
|
||
"section.allSuggestions": "All suggestions",
|
||
"section.allSuggestions.count": "All {count} suggestions",
|
||
"section.vote": "Vote 0-10",
|
||
"section.vote.count": "Vote for all {count} games",
|
||
"section.results": "Results",
|
||
|
||
"card.edit": "Edit",
|
||
"card.delete": "Delete",
|
||
"card.players": "Players: {min}–{max}",
|
||
"card.site": "Site ↗",
|
||
"card.youtube": "YouTube ↗",
|
||
"card.openScreenshot": "Open screenshot",
|
||
"card.linked": "Votes linked",
|
||
"card.linkedWith": "Linked with: {names}",
|
||
|
||
"vote.saved": "Saved vote",
|
||
"vote.missing": "Missing",
|
||
"vote.missingWarn": "You haven’t voted yet.",
|
||
"vote.missingFinalWarn": "You didn't vote for this game.",
|
||
"vote.missingFooter": "At least one game is missing a score. Check before finalizing.",
|
||
"vote.finalize": "Finalize votes",
|
||
"vote.unfinalize": "Edit votes",
|
||
"vote.finalHint": "Finalize when you’re done. You can unfinalize to change scores.",
|
||
"vote.waitAdmin": "Waiting for admin to unlock results.",
|
||
"vote.finalizeMissingTitle": "Finalize with missing votes?",
|
||
"vote.finalizeMissingBody": "You still have {count} game(s) without a score. Finalizing will mark you as done while those stay unrated.",
|
||
"vote.finalizeMissingConfirm": "Finalize anyway",
|
||
|
||
"results.rank": "Rank",
|
||
"results.game": "Game",
|
||
"results.author": "Author",
|
||
"results.average": "Ø",
|
||
"results.votesList": "All votes",
|
||
"results.myVote": "Your vote",
|
||
"results.links": "Links",
|
||
"results.link.site": "Site ↗",
|
||
"results.link.youtube": "YouTube ↗",
|
||
"results.relockedTitle": "Results closed",
|
||
"results.relockedBody": "Results have been locked again. You’re back in the voting phase and your finalized status was cleared. Adjust scores and re-finalize when ready.",
|
||
"results.relockedConfirm": "Got it",
|
||
|
||
"admin.title": "Admin",
|
||
"admin.tools": "Admin tools",
|
||
"admin.resultsOpenToggle": "Allow results phase",
|
||
"admin.resultsLocked": "Results locked by admin",
|
||
"admin.resultsUpdated": "Results availability updated",
|
||
"admin.reset": "Reset (keep players)",
|
||
"admin.factoryReset": "Factory reset",
|
||
"admin.resetDone": "Reset complete",
|
||
"admin.factoryResetDone": "Factory reset complete",
|
||
"admin.readyForResults": "Ready for results",
|
||
"admin.waitingForPlayers": "Waiting for players: {names}",
|
||
"admin.playerName": "Name",
|
||
"admin.playerUsername": "Username",
|
||
"admin.playerStatus": "Status",
|
||
"admin.playerGames": "Games",
|
||
"admin.playerJoker": "Joker",
|
||
"admin.playerDelete": "Delete",
|
||
"admin.grantJokerChip": "Grant",
|
||
"admin.statusSuggesting": "Suggesting",
|
||
"admin.statusVoting": "Voting",
|
||
"admin.statusFinished": "Finished",
|
||
"admin.deleteTitle": "Delete account?",
|
||
"admin.deleteBody": "Delete player \"{name}\" and all their games and votes? This cannot be undone.",
|
||
"admin.deleteConfirm": "Delete",
|
||
"admin.deleteDone": "Player deleted",
|
||
"admin.jokerGranted": "Joker granted",
|
||
"admin.linkTitle": "Link games",
|
||
"admin.linkSource": "Game to link",
|
||
"admin.linkTarget": "Link to (parent)",
|
||
"admin.linkAction": "Link & clear votes",
|
||
"admin.linkSourcePlaceholder": "Select source",
|
||
"admin.linkTargetPlaceholder": "Select target",
|
||
"admin.linkValidation": "Choose two different games to link.",
|
||
"admin.linkDone": "Games linked. Votes cleared.",
|
||
"admin.unlinkTitle": "Remove links?",
|
||
"admin.unlinkBody": "Remove all links involving \"{name}\"? This clears votes and unfinalizes voters in this group: {peers}.",
|
||
"admin.unlinkConfirm": "Remove links",
|
||
"admin.unlinkDone": "Links removed. Votes cleared.",
|
||
"admin.unlinkUnknownPeers": "linked games",
|
||
|
||
"toast.unexpected": "Unexpected error",
|
||
"toast.registered": "Registered",
|
||
"toast.loggedIn": "Logged in",
|
||
"toast.suggestionAdded": "Suggestion added",
|
||
"toast.suggestionDeleted": "Suggestion deleted",
|
||
"toast.savedChanges": "Saved changes",
|
||
"toast.nameRequired": "Name required",
|
||
"toast.displayNameRequired": "Display name is required",
|
||
"toast.invalidImageUrl": "Screenshot URL must be http(s) and end with an image file.",
|
||
|
||
"modal.editTitle": "Edit game",
|
||
"modal.addTitle": "Suggest a game",
|
||
"modal.confirmDeleteTitle": "Are you sure?",
|
||
"modal.confirmDelete": "Confirm delete",
|
||
"modal.save": "Save changes",
|
||
"modal.cancel": "Cancel",
|
||
"modal.close": "Close",
|
||
|
||
"lightbox.close": "Close",
|
||
"help.label": "Help",
|
||
"help.title": "FAQ & tips",
|
||
},
|
||
de: {
|
||
"lang.label": "Sprache",
|
||
"lang.en": "Englisch",
|
||
"lang.de": "Deutsch",
|
||
|
||
"auth.loginTab": "Anmelden",
|
||
"auth.registerTab": "Registrieren",
|
||
"auth.username": "Benutzername",
|
||
"auth.password": "Passwort",
|
||
"auth.displayName": "Anzeigename (für die Gruppe sichtbar)",
|
||
"auth.adminKey": "Admin-Schlüssel (optional)",
|
||
"auth.loginSubmit": "Anmelden",
|
||
"auth.registerSubmit": "Konto erstellen",
|
||
"auth.loginHeading": "Anmelden",
|
||
"auth.registerHeading": "Konto erstellen",
|
||
"auth.switchToRegister": "Noch kein Konto? Registrieren",
|
||
"auth.switchToLogin": "Schon ein Konto? Anmelden",
|
||
"auth.logout": "Abmelden",
|
||
"auth.welcome": "Willkommen, {name}!",
|
||
"auth.defaultName": "Spieler",
|
||
"auth.loading": "Lädt…",
|
||
"auth.needCredentials": "Benutzername und Passwort erforderlich",
|
||
"auth.invalidCredentials": "Ungültiger Benutzername oder Passwort",
|
||
|
||
"phase.suggest": "Vorschlagen",
|
||
"phase.reveal": "Enthüllen",
|
||
"phase.vote": "Bewerten",
|
||
"phase.results": "Ergebnisse",
|
||
"phase.loading": "Lädt…",
|
||
|
||
"counts.format": "Spieler: {players} • Vorschläge: {suggestions} • Stimmen: {votes}",
|
||
|
||
"nav.prev": "Zurück",
|
||
"nav.next": "Weiter",
|
||
"nav.waitingForResults": "Warten…",
|
||
"nav.freezeTitle": "Bereit zum Aufdecken?",
|
||
"nav.freezeHint": "Beim Weitergehen werden deine Vorschläge eingefroren. Spielnamen werden gesperrt; nur Zusatzinfos bleiben bearbeitbar.",
|
||
"nav.freezeModalTitle": "Vorschläge einfrieren?",
|
||
"nav.freezeModalBody": "Sobald du die Vorschlagsphase verlässt, sind deine Spiele gesperrt: Die Namen von deinen Spielen können nicht mehr geändert oder gelöscht werden. Nur optionale Angaben (Beschreibung, Links, Spielerzahlen, Bilder) bleiben bearbeitbar. Fortfahren?",
|
||
"nav.voteHint": "Bewerte alle Spiele, um die Ergebnisse freizuschalten.",
|
||
"nav.voteFinalized": "✅ Du hast deine Abstimmung abgeschlossen. Lehn dich zurück, bis die anderen fertig sind.",
|
||
|
||
"suggest.title": "Schlage Spiele vor (bis zu 5)",
|
||
"suggest.new": "Neuen Vorschlag hinzufügen",
|
||
"suggest.addButton": "Spiel vorschlagen",
|
||
"suggest.maxReached": "Limit erreicht",
|
||
"suggest.jokerAddButton": "🃏 Joker: Weiteres Spiel hinzufügen",
|
||
"suggest.hint": "Nur du siehst deine Vorschläge bis zum Start der Abstimmung.",
|
||
"form.gameName": "Spielname *",
|
||
"form.genre": "Genre",
|
||
"form.description": "Beschreibung",
|
||
"form.players": "Spieler",
|
||
"form.min": "Min",
|
||
"form.max": "Max",
|
||
"form.screenshot": "Screenshot-URL",
|
||
"form.youtube": "YouTube-URL",
|
||
"form.gameUrl": "Spiel-Webseite",
|
||
"form.submit": "Absenden",
|
||
"form.placeholder.description": "Kurze Beschreibung",
|
||
"form.placeholder.gameName": "Spielname *",
|
||
"form.placeholder.genre": "Genre",
|
||
"form.placeholder.screenshot": "Screenshot-URL",
|
||
"form.placeholder.youtube": "YouTube-URL",
|
||
"form.placeholder.gameUrl": "Spiel-Webseite",
|
||
"form.playersInvalid": "Spielerzahl muss zwischen 1 und 32 liegen, und Min darf Max nicht überschreiten.",
|
||
"form.screenshotHint": "Nutze einen öffentlichen Bildlink (http/https), max. 5 MB. Keine Kurzlinks/Weiterleitungen.",
|
||
"form.screenshotInvalid": "Screenshot muss eine direkte http/https-Bild-URL sein (png, jpg, jpeg, gif, webp, avif), unter 5 MB und ohne Weiterleitung/Kurzlink.",
|
||
|
||
"section.mySuggestions": "Deine Vorschläge",
|
||
"section.allSuggestions": "Alle Vorschläge",
|
||
"section.allSuggestions.count": "Alle {count} Vorschläge",
|
||
"section.vote": "Bewerten 0-10",
|
||
"section.vote.count": "Bewerte alle {count} Spiele",
|
||
"section.results": "Ergebnisse",
|
||
|
||
"card.edit": "Bearbeiten",
|
||
"card.delete": "Löschen",
|
||
"card.players": "Spieler: {min}–{max}",
|
||
"card.site": "Webseite ↗",
|
||
"card.youtube": "YouTube ↗",
|
||
"card.openScreenshot": "Screenshot öffnen",
|
||
"card.linked": "Verknüpfte Stimmen",
|
||
"card.linkedWith": "Verknüpft mit: {names}",
|
||
|
||
"vote.saved": "Stimme gespeichert",
|
||
"vote.missing": "Fehlt",
|
||
"vote.missingWarn": "Du hast hier noch nicht abgestimmt.",
|
||
"vote.missingFinalWarn": "Du hast für dieses Spiel nicht abgestimmt.",
|
||
"vote.missingFooter": "Für mindestens einen Spiel fehlt noch eine Wertung. Prüfe vor dem Abschließen.",
|
||
"vote.finalize": "Abstimmung abschließen",
|
||
"vote.unfinalize": "Abstimmung bearbeiten",
|
||
"vote.finalHint": "Schließe ab, wenn du fertig bist. Zum Ändern wieder öffnen.",
|
||
"vote.waitAdmin": "Warten, bis der Admin die Ergebnisse freigibt.",
|
||
"vote.finalizeMissingTitle": "Mit fehlenden Stimmen abschließen?",
|
||
"vote.finalizeMissingBody": "Für {count} Spiel(e) fehlt noch eine Wertung. Beim Abschließen gilt deine Abstimmung als fertig, obwohl diese Spiele unbewertet bleiben.",
|
||
"vote.finalizeMissingConfirm": "Trotzdem abschließen",
|
||
|
||
"results.rank": "Rang",
|
||
"results.game": "Spiel",
|
||
"results.author": "Autor",
|
||
"results.average": "Ø",
|
||
"results.votesList": "Alle Stimmen",
|
||
"results.myVote": "Deine Stimme",
|
||
"results.links": "Links",
|
||
"results.link.site": "Webseite ↗",
|
||
"results.link.youtube": "YouTube ↗",
|
||
"results.relockedTitle": "Ergebnisse geschlossen",
|
||
"results.relockedBody": "Die Ergebnisse wurden wieder gesperrt. Du bist zurück in der Bewertungsphase und deine Finalisierung wurde zurückgesetzt. Passe deine Bewertungen an und schließe erneut ab, wenn du bereit bist.",
|
||
"results.relockedConfirm": "Verstanden",
|
||
|
||
"admin.title": "Admin",
|
||
"admin.tools": "Admin-Werkzeuge",
|
||
"admin.resultsOpenToggle": "Ergebnisse freigeben",
|
||
"admin.resultsLocked": "Ergebnisse vom Admin gesperrt",
|
||
"admin.resultsUpdated": "Ergebnisfreigabe aktualisiert",
|
||
"admin.reset": "Zurücksetzen (Spieler behalten)",
|
||
"admin.factoryReset": "Werkseinstellung",
|
||
"admin.resetDone": "Zurücksetzen abgeschlossen",
|
||
"admin.factoryResetDone": "Werkseinstellung abgeschlossen",
|
||
"admin.readyForResults": "Bereit für Ergebnisse",
|
||
"admin.waitingForPlayers": "Warten auf: {names}",
|
||
"admin.playerName": "Name",
|
||
"admin.playerUsername": "Benutzername",
|
||
"admin.playerStatus": "Status",
|
||
"admin.playerGames": "Spiele",
|
||
"admin.playerJoker": "Joker",
|
||
"admin.playerDelete": "Löschen",
|
||
"admin.grantJokerChip": "Joker",
|
||
"admin.statusSuggesting": "Vorschlagen",
|
||
"admin.statusVoting": "Bewerten",
|
||
"admin.statusFinished": "Fertig",
|
||
"admin.deleteTitle": "Konto löschen?",
|
||
"admin.deleteBody": "Spieler \"{name}\" samt Spielen und Stimmen löschen? Dies kann nicht rückgängig gemacht werden.",
|
||
"admin.deleteConfirm": "Löschen",
|
||
"admin.deleteDone": "Spieler gelöscht",
|
||
"admin.jokerGranted": "Joker vergeben",
|
||
"admin.linkTitle": "Spiele verknüpfen",
|
||
"admin.linkSource": "Spiel verknüpfen",
|
||
"admin.linkTarget": "Verknüpfen mit",
|
||
"admin.linkAction": "Verknüpfen & Stimmen löschen",
|
||
"admin.linkSourcePlaceholder": "Quelle wählen",
|
||
"admin.linkTargetPlaceholder": "Ziel wählen",
|
||
"admin.linkValidation": "Wähle zwei verschiedene Spiele aus.",
|
||
"admin.linkDone": "Spiele verknüpft. Stimmen gelöscht.",
|
||
"admin.unlinkTitle": "Links entfernen?",
|
||
"admin.unlinkBody": "Alle Links zu \"{name}\" entfernen? Dadurch werden Stimmen gelöscht und Finalisierungen aufgehoben für: {peers}.",
|
||
"admin.unlinkConfirm": "Links entfernen",
|
||
"admin.unlinkDone": "Links entfernt. Stimmen gelöscht.",
|
||
"admin.unlinkUnknownPeers": "verknüpfte Spiele",
|
||
|
||
"toast.unexpected": "Unerwarteter Fehler",
|
||
"toast.registered": "Registriert",
|
||
"toast.loggedIn": "Angemeldet",
|
||
"toast.suggestionAdded": "Vorschlag hinzugefügt",
|
||
"toast.suggestionDeleted": "Vorschlag gelöscht",
|
||
"toast.savedChanges": "Änderungen gespeichert",
|
||
"toast.nameRequired": "Name erforderlich",
|
||
"toast.displayNameRequired": "Anzeigename ist erforderlich",
|
||
"toast.invalidImageUrl": "Screenshot-URL muss mit http(s) beginnen und auf eine Bilddatei enden.",
|
||
|
||
"modal.editTitle": "Spiel bearbeiten",
|
||
"modal.addTitle": "Spiel vorschlagen",
|
||
"modal.confirmDeleteTitle": "Bist du sicher?",
|
||
"modal.confirmDelete": "Löschen bestätigen",
|
||
"modal.save": "Änderungen speichern",
|
||
"modal.cancel": "Abbrechen",
|
||
"modal.close": "Schließen",
|
||
|
||
"lightbox.close": "Schließen",
|
||
"help.label": "Hilfe",
|
||
"help.title": "FAQ & Tipps",
|
||
}
|
||
};
|
||
|
||
const faqMarkdown = {
|
||
en: `
|
||
Pick'n'play helps groups fairly and transparently choose what game to play next. Players can suggest options, score them independently, and move through structured phases that keep the process organized and anonymous. It solves the classic “what should we play?” chaos by turning group decision-making into a clear, balanced, and drama-free flow.
|
||
|
||
## Accounts & Login
|
||
|
||
### How do I create an account?
|
||
|
||
Register with:
|
||
- A **unique username** (max 24 characters)
|
||
- A **password**
|
||
- A **display name** (max 16 characters)
|
||
|
||
Your display name is required ‒ it appears next to all of your suggestions and scores.
|
||
|
||
### Do I need admin privileges?
|
||
|
||
If you've been given an **admin key**, enter it during registration. If the key is invalid, the request is rejected.
|
||
Admin access cannot be added later. To become an admin, you must re-register with the correct key.
|
||
|
||
## Phases at a Glance
|
||
|
||
### Personal phases
|
||
|
||
Each player progresses independently through the phases:
|
||
|
||
**Suggest → Vote → Results**
|
||
|
||
Click **"Next"** to move forward. Admins can move themselves backward if needed.
|
||
|
||
## Suggesting Games
|
||
|
||
### How many games can I suggest?
|
||
|
||
Up to **5 suggestions per player**.
|
||
|
||
### Required fields and limits
|
||
|
||
- **Name** ‒ required (max 100 characters)
|
||
- **Genre** ‒ optional (max 50 characters)
|
||
- **Description** ‒ optional (max 500 characters)
|
||
- **Links** ‒ optional (URLs up to 2048 characters)
|
||
|
||
### Min/Max players
|
||
|
||
- Must be filled together (or both left empty)
|
||
- Values must be between **1 and 32**
|
||
- Minimum must be ≤ maximum
|
||
|
||
### Screenshot rules
|
||
|
||
If you include a screenshot URL, it must:
|
||
- Use **http or https**
|
||
- End with a valid image extension (\`png\`, \`jpg\`, \`jpeg\`, \`gif\`, \`webp\`, \`avif\`)
|
||
- Be directly accessible (no redirects)
|
||
- Load within ~3 seconds
|
||
- Be under **5 MB**
|
||
- Not point to local or private hosts
|
||
|
||
Screenshots are optional.
|
||
|
||
### Other links
|
||
|
||
Game links and YouTube links must use **http or https**. Other URL schemes are rejected.
|
||
|
||
### Editing a suggestion
|
||
|
||
Click the **edit (pencil) icon** on a game card to update any field at any time.
|
||
|
||
### Deleting a suggestion
|
||
|
||
Click the **delete (cross) icon** on a game card to remove it ‒ unless you're already in the Vote phase (see below).
|
||
|
||
### Why was my suggestion blocked?
|
||
|
||
Common reasons:
|
||
- Missing display name
|
||
- Already reached the 5-suggestion limit
|
||
- Name exceeds character limit
|
||
- Screenshot URL is invalid, unreachable, or too large
|
||
- Min/max players missing or invalid
|
||
- Attempting to add a suggestion in the wrong phase
|
||
|
||
Check the bottom-right corner of the screen for error messages.
|
||
|
||
## Jokers (Late Additions)
|
||
|
||
### What is a joker?
|
||
|
||
A **joker** is a one-time extra suggestion slot available only during the **Vote phase**. An admin must grant it to you.
|
||
|
||
### How it works
|
||
|
||
If you receive a joker:
|
||
- A button appears in the top bar allowing you to add one more game.
|
||
- Once used, the joker is consumed immediately.
|
||
- Your ballot becomes unfinalized.
|
||
- All players are unfinalized so the new game can be scored.
|
||
|
||
Admins may grant additional jokers if necessary.
|
||
|
||
## Voting
|
||
|
||
### Who can vote?
|
||
|
||
Authenticated players during the **Vote phase**.
|
||
|
||
### How do I score games?
|
||
|
||
Use the slider to assign a whole number from **0 to 10**.
|
||
|
||
### Editing during Vote
|
||
|
||
- You can still edit most game details.
|
||
- The **game name becomes locked** during the Vote phase.
|
||
- You can no longer delete your own suggestions.
|
||
- Admins may delete suggestions if necessary.
|
||
|
||
### Linked duplicates
|
||
|
||
If an admin links duplicate games:
|
||
- Changing the score for one updates all linked entries.
|
||
- Scores are stored per group, not per individual entry.
|
||
|
||
### Finalizing your ballot
|
||
|
||
Toggling **"Finalize"** locks your scores. Toggle it off to edit again.
|
||
|
||
Finalize is only available during the Vote phase and will automatically reset if:
|
||
- A joker adds a new game
|
||
- An admin links or unlinks games
|
||
|
||
### Voting after changes
|
||
|
||
If new games are added or links are modified:
|
||
- Affected votes are cleared
|
||
- You are automatically unfinalized
|
||
|
||
Review your list and rescore before finalizing again.
|
||
|
||
## Results
|
||
|
||
### When are results visible?
|
||
|
||
Results are hidden until an admin opens them. When opened, all players are automatically moved to the **Results phase**.
|
||
If needed, an admin can close the Results: everyone returns to the Vote phase, and all ballots are unfinalized for adjustments.
|
||
|
||
### Can I edit anything in Results?
|
||
|
||
No. Suggestions and votes are read-only. Contact an admin for assistance.
|
||
|
||
## Admin Tools (For Hosts)
|
||
|
||
### What can admin accounts do?
|
||
|
||
- Grant jokers during Vote
|
||
- Link or unlink duplicate suggestions
|
||
- View vote readiness (who has finalized)
|
||
- Delete a player (removes their suggestions and votes)
|
||
- Reset the database to factory defaults
|
||
- Move backward to previous phases
|
||
|
||
### What can't admin accounts do?
|
||
|
||
- View individual player votes
|
||
|
||
Voting remains anonymous and fair.
|
||
|
||
## Common Errors & Fixes
|
||
|
||
### "Screenshot URL must be http(s) and end with an image file extension."
|
||
|
||
Make sure:
|
||
- The link is direct (not a page or html content)
|
||
- It ends with a valid image extension
|
||
- The file is under 5 MB
|
||
- There are no redirects
|
||
|
||
### "You have reached the 5 suggestion limit."
|
||
|
||
Wait for the Vote phase and request a joker if needed.
|
||
|
||
### "Invalid admin key."
|
||
|
||
Register again using the correct key from the host ‒ or leave it blank to create a regular account.
|
||
|
||
## Data & Privacy
|
||
|
||
- Suggestions, votes, and phase states are stored in a shared **SQLite database**.
|
||
- Logging out clears your authentication cookie.
|
||
- If an admin deletes your player account, your suggestions and votes are removed as well.
|
||
`,
|
||
de: `
|
||
Pick'n'play hilft Gruppen dabei, fair und transparent zu entscheiden, welches Spiel als Nächstes gespielt wird. Spieler können Vorschläge einreichen, diese unabhängig bewerten und durch strukturierte Phasen gehen, die den Prozess organisiert und anonym halten. Es löst das klassische „Was sollen wir spielen?"-Chaos, indem es Gruppenentscheidungen in einen klaren, ausgewogenen und stressfreien Ablauf verwandelt.
|
||
|
||
## Konten & Anmeldung
|
||
|
||
### Wie erstelle ich ein Konto?
|
||
|
||
Registriere dich mit:
|
||
- Einem **eindeutigen Benutzernamen** (max. 24 Zeichen)
|
||
- Einem **Passwort**
|
||
- Einem **Anzeigenamen** (max. 16 Zeichen)
|
||
|
||
Dein Anzeigename ist erforderlich ‒ er erscheint neben all deinen Vorschlägen und Bewertungen.
|
||
|
||
### Brauche ich Admin-Rechte?
|
||
|
||
Wenn du einen **Admin-Schlüssel** erhalten hast, gib ihn bei der Registrierung ein. Ist der Schlüssel ungültig, wird die Anfrage abgelehnt. Admin-Rechte können später nicht hinzugefügt werden. Um Admin zu werden, musst du dich mit dem korrekten Schlüssel neu registrieren.
|
||
|
||
## Phasen im Überblick
|
||
|
||
### Persönliche Phasen
|
||
|
||
Jeder Spieler durchläuft die Phasen unabhängig voneinander:
|
||
|
||
**Vorschlagen → Abstimmen → Ergebnisse**
|
||
|
||
Klicke auf **„Weiter"**, um fortzufahren. Admins können sich bei Bedarf auch wieder zurücksetzen.
|
||
|
||
## Spiele vorschlagen
|
||
|
||
### Wie viele Spiele kann ich vorschlagen?
|
||
|
||
Bis zu **5 Vorschläge pro Spieler**.
|
||
|
||
### Pflichtfelder und Grenzen
|
||
|
||
- **Name** ‒ erforderlich (max. 100 Zeichen)
|
||
- **Genre** ‒ optional (max. 50 Zeichen)
|
||
- **Beschreibung** ‒ optional (max. 500 Zeichen)
|
||
- **Links** ‒ optional (URLs bis zu 2048 Zeichen)
|
||
|
||
### Min./Max. Spieleranzahl
|
||
- Müssen gemeinsam ausgefüllt werden (oder beide leer bleiben)
|
||
- Werte müssen zwischen **1 und 32** liegen
|
||
- Minimum muss ≤ Maximum sein
|
||
|
||
### Screenshot-Regeln
|
||
|
||
Wenn du eine Screenshot-URL angibst, muss sie:
|
||
- **http oder https** verwenden
|
||
- Mit einer gültigen Bilddateiendung enden (\`png\`, \`jpg\`, \`jpeg\`, \`gif\`, \`webp\`, \`avif\`)
|
||
- Direkt erreichbar sein (keine Weiterleitungen)
|
||
- Innerhalb von ~3 Sekunden laden
|
||
- Unter **5 MB**groß sein
|
||
- Nicht auf lokale oder private Hosts verweisen
|
||
|
||
Screenshots sind optional.
|
||
|
||
### Weitere Links
|
||
|
||
Spiel-Links und YouTube-Links müssen **http oder https** verwenden. Andere URL-Schemata werden abgelehnt.
|
||
|
||
### Vorschlag bearbeiten
|
||
|
||
Klicke auf das **Bearbeiten-Symbol (Stift)** auf einer Spielkarte, um Felder jederzeit zu aktualisieren.
|
||
|
||
### Vorschlag löschen
|
||
|
||
Klicke auf das **Löschen-Symbol (Kreuz)** auf einer Spielkarte, um sie zu entfernen ‒ außer du befindest dich bereits in der Abstimmungsphase (siehe unten).
|
||
|
||
### Warum wurde mein Vorschlag blockiert?
|
||
|
||
Häufige Gründe:
|
||
- Fehlender Anzeigename
|
||
- Das Limit von 5 Vorschlägen wurde erreicht
|
||
- Name überschreitet das Zeichenlimit
|
||
- Screenshot-URL ist ungültig, nicht erreichbar oder zu groß
|
||
- Min./Max.-Spieler fehlen oder sind ungültig
|
||
- Versuch, im falschen Phase einen Vorschlag hinzuzufügen
|
||
|
||
Überprüfe Fehlermeldungen unten rechts auf dem Bildschirm.
|
||
|
||
## Abstimmung
|
||
|
||
### Wer darf abstimmen?
|
||
|
||
Authentifizierte Spieler während der **Abstimmungsphase**.
|
||
|
||
### Wie vergebe ich Punkte?
|
||
|
||
Nutze den Schieberegler, um eine ganze Zahl von **0 bis 10** zu vergeben.
|
||
|
||
### Bearbeiten während der Abstimmung
|
||
|
||
- Die meisten Spieldetails können weiterhin bearbeitet werden
|
||
- Der **Spielname ist während der Abstimmungsphase gesperrt**
|
||
- Eigene Vorschläge können nicht mehr gelöscht werden
|
||
- Admins können Vorschläge bei Bedarf löschen
|
||
|
||
### Verknüpfte Duplikate
|
||
|
||
Wenn ein Admin doppelte Spiele verknüpft:
|
||
- Eine Punkteänderung wirkt sich auf alle verknüpften Einträge aus
|
||
- Punkte werden pro Gruppe gespeichert, nicht pro Einzeleintrag
|
||
|
||
### Abstimmung finalisieren
|
||
|
||
Mit **„Finalisieren"** werden deine Bewertungen gesperrt. Deaktiviere es, um erneut zu bearbeiten.
|
||
|
||
„Finalisieren" ist nur während der Abstimmungsphase verfügbar und wird automatisch zurückgesetzt, wenn:
|
||
- Ein Joker ein neues Spiel hinzufügt
|
||
- Ein Admin Spiele verknüpft oder trennt
|
||
|
||
### Abstimmen nach Änderungen
|
||
|
||
Wenn neue Spiele hinzugefügt oder Verknüpfungen geändert werden:
|
||
- Betroffene Stimmen werden gelöscht
|
||
- Deine Abstimmung wird automatisch zurückgesetzt
|
||
|
||
Überprüfe deine Liste und bewerte erneut, bevor du wieder finalisierst.
|
||
|
||
## Joker (Späte Ergänzungen)
|
||
|
||
### Was ist ein Joker?
|
||
|
||
Ein **Joker** ist ein einmaliger zusätzlicher Vorschlags-Slot, der nur während der **Abstimmungsphase** verfügbar ist. Ein Admin muss ihn dir gewähren.
|
||
|
||
### So funktioniert es
|
||
|
||
Wenn du einen Joker erhältst:
|
||
- Erscheint ein Button in der oberen Leiste, mit dem du ein weiteres Spiel hinzufügen kannst
|
||
- Nach der Nutzung wird der Joker sofort verbraucht
|
||
- Die Finalisierung aller Abstimmungen werden automatisch zurückgesetzt, damit das neue Spiel bewertet werden kann
|
||
|
||
Admins können bei Bedarf zusätzliche Joker vergeben.
|
||
|
||
## Ergebnisse
|
||
|
||
### Wann sind die Ergebnisse sichtbar?
|
||
|
||
Die Ergebnisse bleiben verborgen, bis ein Admin sie freigibt. Danach werden alle Spieler automatisch in die **Ergebnisphase** verschoben. Falls nötig, kann ein Admin die Ergebnisse wieder schließen: Alle kehren in die Abstimmungsphase zurück und alle Abstimmungen werden zur Anpassung zurückgesetzt.
|
||
|
||
### Kann ich in der Ergebnisphase etwas bearbeiten?
|
||
|
||
Nein. Vorschläge und Bewertungen sind schreibgeschützt. Wende dich bei Bedarf an einen Admin.
|
||
|
||
## Admin-Tools (Für Hosts)
|
||
|
||
### Was können Admin-Konten tun?
|
||
|
||
- Joker während der Abstimmung vergeben
|
||
- Doppelte Vorschläge verknüpfen oder trennen
|
||
- Abstimmungsstatus einsehen (wer finalisiert hat)
|
||
- Einen Spieler löschen (inklusive dessen Vorschläge und Stimmen)
|
||
- Die Datenbank auf Werkseinstellungen zurücksetzen
|
||
- Zu vorherigen Phasen zurückkehren
|
||
|
||
### Was können Admin-Konten nicht tun?
|
||
|
||
- Einzelne Spielerbewertungen einsehen
|
||
|
||
Die Abstimmung bleibt anonym und fair.
|
||
|
||
## Häufige Fehler & Lösungen
|
||
|
||
### „Screenshot-URL muss http(s) verwenden und mit einer Bilddateiendung enden."
|
||
|
||
Stelle sicher:
|
||
- Der Link ist direkt (keine HTML-Seite)
|
||
- Er endet mit einer gültigen Bilddateiendung
|
||
- Die Datei ist unter 5 MB groß
|
||
- Es gibt keine Weiterleitungen
|
||
|
||
### „Du hast das Limit von 5 Vorschlägen erreicht."
|
||
|
||
Warte auf die Abstimmungsphase und bitte bei Bedarf um einen Joker.
|
||
|
||
### „Ungültiger Admin-Schlüssel."
|
||
|
||
Registriere dich erneut mit dem korrekten Schlüssel vom Host ‒ oder lasse das Feld leer, um ein normales Konto zu erstellen.
|
||
|
||
## Daten & Datenschutz
|
||
|
||
- Vorschläge, Stimmen und Phasenstatus werden in einer gemeinsamen **SQLite-Datenbank** gespeichert.
|
||
- Beim Abmelden wird dein Authentifizierungs-Cookie gelöscht.
|
||
- Wenn ein Admin dein Spielerkonto löscht, werden auch deine Vorschläge und Stimmen entfernt.
|
||
`,
|
||
};
|
||
|
||
const storageKey = "app_lang";
|
||
const defaultLang = "en";
|
||
let currentLang = defaultLang;
|
||
const listeners = [];
|
||
|
||
function interpolate(template, params = {}) {
|
||
return template.replace(/\{(\w+)\}/g, (_, key) => (params[key] ?? `{${key}}`));
|
||
}
|
||
|
||
function t(key, params) {
|
||
const fallback = translations[defaultLang][key] ?? key;
|
||
const phrase = translations[currentLang]?.[key] ?? fallback;
|
||
return interpolate(phrase, params);
|
||
}
|
||
|
||
function detectLanguage() {
|
||
const stored = localStorage.getItem(storageKey);
|
||
if (stored && translations[stored]) return stored;
|
||
const nav = navigator.language?.slice(0, 2);
|
||
if (nav && translations[nav]) return nav;
|
||
return defaultLang;
|
||
}
|
||
|
||
function applyTranslations(root = document) {
|
||
root.querySelectorAll("[data-i18n]").forEach((el) => {
|
||
const key = el.dataset.i18n;
|
||
const attrs = (el.dataset.i18nAttr || "")
|
||
.split(",")
|
||
.map((a) => a.trim())
|
||
.filter(Boolean);
|
||
const text = t(key);
|
||
if (attrs.length === 0) {
|
||
el.textContent = text;
|
||
} else {
|
||
attrs.forEach((attr) => el.setAttribute(attr, text));
|
||
}
|
||
});
|
||
}
|
||
|
||
function notify() {
|
||
listeners.forEach((fn) => fn(currentLang));
|
||
}
|
||
|
||
function setLanguage(lang) {
|
||
if (!translations[lang]) lang = defaultLang;
|
||
currentLang = lang;
|
||
localStorage.setItem(storageKey, lang);
|
||
document.documentElement.lang = lang;
|
||
applyTranslations();
|
||
notify();
|
||
}
|
||
|
||
function getLanguage() {
|
||
return currentLang;
|
||
}
|
||
|
||
function initI18n() {
|
||
currentLang = detectLanguage();
|
||
document.documentElement.lang = currentLang;
|
||
applyTranslations();
|
||
notify();
|
||
return currentLang;
|
||
}
|
||
|
||
function onLanguageChange(fn) {
|
||
listeners.push(fn);
|
||
}
|
||
|
||
export { t, setLanguage, getLanguage, initI18n, applyTranslations, onLanguageChange, translations, faqMarkdown };
|