diff --git a/messages/en.json b/messages/en.json index bd12883..74a3cdc 100644 --- a/messages/en.json +++ b/messages/en.json @@ -158,6 +158,7 @@ "kv_play_judging": "Judging", "kv_play_enter_wager": "Enter wager", "kv_play_judged": "Judged", + "kv_play_wager_range": "Min: {min}€ — Max: {max}€", "kv_play_finish": "Finish", "kv_color_picker": "Color Picker", "kv_done": "Done", diff --git a/messages/et.json b/messages/et.json index 76a086f..ba7c1fe 100644 --- a/messages/et.json +++ b/messages/et.json @@ -106,7 +106,7 @@ "kv_play_confirm": "Kinnita", "kv_play_question_number": "Küsimus {current}/{total}", "kv_play_showing_answer": "Näitan vastust...", - "kv_play_question_short": "Küsimus", + "kv_play_question_short": "K", "kv_play_answer_short": "Vastus", "kv_play_answering": "Vastab", "kv_play_correct": "Õige", @@ -158,6 +158,7 @@ "kv_play_judging": "Hindamine", "kv_play_enter_wager": "Sisesta panus", "kv_play_judged": "Hinnatud", + "kv_play_wager_range": "Min: {min}€ — Max: {max}€", "kv_play_finish": "Lõpeta", "kv_color_picker": "Värvivalija", "kv_done": "Valmis", diff --git a/package-lock.json b/package-lock.json index ea2c3be..bed45b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ultimate-gaming", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package.json b/package.json index 2bb8cab..627f02b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ultimate-gaming", "private": true, - "version": "0.1.0", + "version": "0.1.1", "type": "module", "scripts": { "dev": "vite dev", diff --git a/src/app.html b/src/app.html index 50bd0b5..d54b047 100644 --- a/src/app.html +++ b/src/app.html @@ -1,11 +1,18 @@ + + + + + %sveltekit.head% +
%sveltekit.body%
- + + \ No newline at end of file diff --git a/src/lib/assets/kuldvillak_favicon.svg b/src/lib/assets/kuldvillak_favicon.svg index fd3cb2b..6ae9759 100644 --- a/src/lib/assets/kuldvillak_favicon.svg +++ b/src/lib/assets/kuldvillak_favicon.svg @@ -1,3 +1,3 @@ - + diff --git a/src/lib/components/kuldvillak/ui/KvEditCard.svelte b/src/lib/components/kuldvillak/ui/KvEditCard.svelte index 750f894..373f196 100644 --- a/src/lib/components/kuldvillak/ui/KvEditCard.svelte +++ b/src/lib/components/kuldvillak/ui/KvEditCard.svelte @@ -43,6 +43,7 @@ let finalWagerInput = $state(0); +
{#if selectable && !finalJudged} @@ -78,7 +79,10 @@ : ''}">{score}€ {#if finalJudged} - ✓ {m.kv_play_judged()} + ✓ {m.kv_play_judged()} {/if}
diff --git a/src/lib/components/kuldvillak/ui/KvGameLogo.svelte b/src/lib/components/kuldvillak/ui/KvGameLogo.svelte index a44655c..45e8848 100644 --- a/src/lib/components/kuldvillak/ui/KvGameLogo.svelte +++ b/src/lib/components/kuldvillak/ui/KvGameLogo.svelte @@ -21,7 +21,7 @@ {#if variant === "kuldvillak"} - + -{:else} - +{:else if variant === "villak"} + + + + + + +{:else if variant === "topeltvillak"} + + + + + + + + + + + + + + +{:else if variant === "hobevillak"} + + + + + + + + + + {/if} diff --git a/src/lib/components/kuldvillak/ui/KvSpinner.svelte b/src/lib/components/kuldvillak/ui/KvSpinner.svelte index ca8a013..6ce1bb7 100644 --- a/src/lib/components/kuldvillak/ui/KvSpinner.svelte +++ b/src/lib/components/kuldvillak/ui/KvSpinner.svelte @@ -13,9 +13,7 @@ xmlns="http://www.w3.org/2000/svg" > diff --git a/src/lib/example_testing_game.json b/src/lib/example_testing_game.json index 9f490a2..51bc06c 100644 --- a/src/lib/example_testing_game.json +++ b/src/lib/example_testing_game.json @@ -10,7 +10,6 @@ 40, 50 ], - "basePointValue": 10, "categoriesPerRound": 6, "questionsPerCategory": 5, "dailyDoublesPerRound": [ @@ -22,7 +21,8 @@ "allowNegativeScores": true, "maxTeams": 6, "defaultTimerSeconds": 5, - "answerRevealSeconds": 5 + "answerRevealSeconds": 5, + "basePointValue": 10 }, "teams": [ { diff --git a/src/lib/stores/gameSession.svelte.ts b/src/lib/stores/gameSession.svelte.ts index 6d1f442..a4b8ed3 100644 --- a/src/lib/stores/gameSession.svelte.ts +++ b/src/lib/stores/gameSession.svelte.ts @@ -28,8 +28,9 @@ export interface GameSessionState { } | null; showAnswer: boolean; wrongTeamIds: string[]; // Teams that answered wrong for current question - lastAnsweredTeamId: string | null; // Track who answered last - lastAnswerCorrect: boolean | null; // Was it correct or wrong + lastAnsweredTeamId: string | null; // Track who answered last (for current question feedback) + lastAnswerCorrect: boolean | null; // Was it correct or wrong (for current question feedback) + lastCorrectTeamId: string | null; // Track who answered correctly last (persists across questions for clue selection) // Daily Double dailyDoubleWager: number | null; @@ -37,7 +38,6 @@ export interface GameSessionState { // Final Round finalCategoryRevealed: boolean; // Has the final category been revealed finalWagers: Record; - finalAnswers: Record; finalRevealed: string[]; // Team IDs that have been revealed // Timer @@ -146,10 +146,10 @@ class GameSessionStore { wrongTeamIds: [], lastAnsweredTeamId: null, lastAnswerCorrect: null, + lastCorrectTeamId: null, dailyDoubleWager: null, finalCategoryRevealed: false, finalWagers: {}, - finalAnswers: {}, finalRevealed: [], timerRunning: false, timerSeconds: 0, @@ -274,12 +274,6 @@ class GameSessionStore { this.persist(); } - toggleAnswer() { - if (!this.state) return; - this.state.showAnswer = !this.state.showAnswer; - this.persist(); - } - revealAnswer() { if (!this.state) return; this.state.showAnswer = true; @@ -300,9 +294,11 @@ class GameSessionStore { team.score += points; } - // Track last answer + // Track last answer (current question feedback) this.state.lastAnsweredTeamId = teamId; this.state.lastAnswerCorrect = true; + // Track last correct answer (persists for clue selection) + this.state.lastCorrectTeamId = teamId; // Show answer and start reveal countdown this.state.showAnswer = true; @@ -387,22 +383,12 @@ class GameSessionStore { wager: this.state.dailyDoubleWager ?? undefined, }); - // Mark as revealed + // Mark as revealed and increment counter question.isRevealed = true; - - // Increment questions answered counter this.state.questionsAnswered++; - // Reset state - this.state.currentQuestion = null; - this.state.showAnswer = false; - this.state.wrongTeamIds = []; - this.state.dailyDoubleWager = null; - this.state.activeTeamId = null; - this.state.timeoutCountdown = null; - this.state.revealCountdown = null; - this.state.skippingQuestion = false; - this.state.phase = "board"; + // Reset question state using consolidated helper + this.resetQuestionState(); // Check if round is complete this.checkRoundComplete(); @@ -420,7 +406,7 @@ class GameSessionStore { if (allRevealed) { // Move to next round or final if (this.state.currentRoundIndex < this.state.rounds.length - 1) { - this.state.currentRoundIndex++; + this.transitionToNextRound(); } else if (this.state.settings.enableFinalRound && this.state.finalRound) { this.state.phase = "final-category"; } else { @@ -454,15 +440,6 @@ class GameSessionStore { } } - setScore(teamId: string, score: number) { - if (!this.state) return; - const team = this.state.teams.find(t => t.id === teamId); - if (team) { - team.score = score; - this.persist(); - } - } - // ============================================ // Round Management // ============================================ @@ -470,12 +447,7 @@ class GameSessionStore { nextRound() { if (!this.state) return; if (this.state.currentRoundIndex < this.state.rounds.length - 1) { - this.state.currentRoundIndex++; - this.state.phase = "intro"; - this.state.introCategoryIndex = -1; - this.state.categoriesIntroduced = false; // Reset for new round - this.state.boardRevealed = false; // Reset for new round - this.state.questionResults = []; + this.transitionToNextRound(); this.persist(); } } @@ -521,13 +493,6 @@ class GameSessionStore { this.persist(); } - // Reveal the final answer (after all teams judged) - revealFinalAnswer() { - if (!this.state) return; - this.state.showAnswer = true; - this.persist(); - } - // Select a team to judge their final answer selectFinalTeam(teamId: string) { if (!this.state) return; @@ -555,6 +520,11 @@ class GameSessionStore { this.state.finalRevealed.push(teamId); this.state.activeTeamId = null; + // Auto-reveal answer when all teams are judged + if (this.state.finalRevealed.length === this.state.teams.length) { + this.state.showAnswer = true; + } + this.persist(); } @@ -590,7 +560,7 @@ class GameSessionStore { this.persist(); if (this.state.revealCountdown === 0) { - // Return to board + // Mark question as revealed if (this.state.currentQuestion) { const { roundIndex, categoryIndex, questionIndex } = this.state.currentQuestion; const question = this.state.rounds[roundIndex]?.categories[categoryIndex]?.questions[questionIndex]; @@ -600,16 +570,8 @@ class GameSessionStore { } } - // Reset state and return to board - this.state.currentQuestion = null; - this.state.showAnswer = false; - this.state.wrongTeamIds = []; - this.state.dailyDoubleWager = null; - this.state.activeTeamId = null; - this.state.timeoutCountdown = null; - this.state.revealCountdown = null; - this.state.skippingQuestion = false; - this.state.phase = "board"; + // Reset state using consolidated helper + this.resetQuestionState(); this.checkRoundComplete(); this.persist(); } @@ -645,12 +607,6 @@ class GameSessionStore { } } - setTimerMax(seconds: number) { - if (!this.state) return; - this.state.timerMax = seconds; - this.persist(); - } - // Call this from moderator view only enableTimerControl() { this.startInternalTimer(); @@ -680,7 +636,36 @@ class GameSessionStore { } // ============================================ - // Helpers + // Private Helpers + // ============================================ + + // Consolidated state reset after question completion + private resetQuestionState() { + if (!this.state) return; + this.state.currentQuestion = null; + this.state.showAnswer = false; + this.state.wrongTeamIds = []; + this.state.dailyDoubleWager = null; + this.state.activeTeamId = null; + this.state.timeoutCountdown = null; + this.state.revealCountdown = null; + this.state.skippingQuestion = false; + this.state.phase = "board"; + } + + // Consolidated round transition logic + private transitionToNextRound() { + if (!this.state) return; + this.state.currentRoundIndex++; + this.state.phase = "intro"; + this.state.introCategoryIndex = -1; + this.state.categoriesIntroduced = false; + this.state.boardRevealed = false; + this.state.questionResults = []; + } + + // ============================================ + // Public Helpers // ============================================ get currentRound(): Round | null { diff --git a/src/lib/stores/persistence.test.ts b/src/lib/stores/persistence.test.ts index 3f652a8..d1f8026 100644 --- a/src/lib/stores/persistence.test.ts +++ b/src/lib/stores/persistence.test.ts @@ -15,7 +15,7 @@ const localStorageMock = (() => { }; })(); -Object.defineProperty(global, 'localStorage', { value: localStorageMock }); +Object.defineProperty(globalThis, 'localStorage', { value: localStorageMock }); // Import after mocking localStorage import { diff --git a/src/lib/stores/persistence.ts b/src/lib/stores/persistence.ts index 0f86e31..fee2f24 100644 --- a/src/lib/stores/persistence.ts +++ b/src/lib/stores/persistence.ts @@ -122,14 +122,8 @@ export function duplicateKuldvillakGame(gameId: string): KuldvillakGame | null { // Reset game state duplicate.state = { - phase: 'lobby', - currentRoundIndex: 0, - currentQuestionId: null, - currentCategoryId: null, - activeTeamId: null, - dailyDoubleWager: null, - finalWagers: {}, - finalAnswers: {} + phase: 'intro', + currentRoundIndex: 0 }; // Reset revealed questions and scores diff --git a/src/lib/types/kuldvillak.test.ts b/src/lib/types/kuldvillak.test.ts index caae25e..dec6905 100644 --- a/src/lib/types/kuldvillak.test.ts +++ b/src/lib/types/kuldvillak.test.ts @@ -47,22 +47,13 @@ describe('Kuldvillak Types', () => { }); describe('DEFAULT_STATE', () => { - it('should start in lobby phase', () => { - expect(DEFAULT_STATE.phase).toBe('lobby'); + it('should start in intro phase', () => { + expect(DEFAULT_STATE.phase).toBe('intro'); }); it('should start at round index 0', () => { expect(DEFAULT_STATE.currentRoundIndex).toBe(0); }); - - it('should have no active team', () => { - expect(DEFAULT_STATE.activeTeamId).toBeNull(); - }); - - it('should have empty final round data', () => { - expect(DEFAULT_STATE.finalWagers).toEqual({}); - expect(DEFAULT_STATE.finalAnswers).toEqual({}); - }); }); describe('Type Validation Helpers', () => { @@ -124,7 +115,6 @@ describe('Kuldvillak Types', () => { it('should validate game phases', () => { const validPhases = [ - 'lobby', 'intro', 'intro-categories', 'board', @@ -132,9 +122,7 @@ describe('Kuldvillak Types', () => { 'daily-double', 'final-intro', 'final-category', - 'final-wager', 'final-question', - 'final-reveal', 'final-scores', 'finished', ]; diff --git a/src/lib/types/kuldvillak.ts b/src/lib/types/kuldvillak.ts index 60a18a8..affdae5 100644 --- a/src/lib/types/kuldvillak.ts +++ b/src/lib/types/kuldvillak.ts @@ -44,20 +44,16 @@ export interface Team { /** Current game phase */ export type GamePhase = - | 'intro' // Show Kuldvillak home screen (round start) + | 'intro' // Show round intro screen (Villak/Topeltvillak) | 'intro-categories' // Animating category introductions - | 'lobby' - | 'board' - | 'question' - | 'answer' - | 'daily-double' + | 'board' // Main game board with questions + | 'question' // Displaying a question + | 'daily-double' // Daily double wager selection | 'final-intro' // Final round intro (Kuldvillak screen) | 'final-category' // Reveal final round category - | 'final-wagers' // Collect wagers from each team - | 'final-question' - | 'final-reveal' - | 'final-scores' - | 'finished'; + | 'final-question' // Final round question display + | 'final-scores' // Final scores display + | 'finished'; // Game complete /** Result of a question for tracking */ export interface QuestionResult { @@ -70,27 +66,17 @@ export interface QuestionResult { wager?: number; // DD wager if applicable } -/** Current state during gameplay */ -export interface GameState { - phase: GamePhase; - currentRoundIndex: number; - currentQuestionId: string | null; - currentCategoryId: string | null; - activeTeamId: string | null; - dailyDoubleWager: number | null; - finalWagers: Record; - finalAnswers: Record; -} - -/** Point value preset types */ -export type PointValuePreset = 'round1' | 'round2' | 'custom' | 'multiplier'; +/** Point value preset types + * - round1: Base points (10-50) multiplied by round number + * - custom: User-defined point values + */ +export type PointValuePreset = 'round1' | 'custom'; /** Configurable game settings */ export interface GameSettings { numberOfRounds: 1 | 2; pointValuePreset: PointValuePreset; pointValues: number[]; - basePointValue: number; // For multiplier preset (e.g., 100 → 100,200,300,400,500) categoriesPerRound: number; questionsPerCategory: number; dailyDoublesPerRound: number[]; @@ -102,11 +88,11 @@ export interface GameSettings { answerRevealSeconds: number; } -/** Point value presets */ -export const POINT_PRESETS = { - round1: [100, 200, 300, 400, 500], - round2: [200, 400, 600, 800, 1000] -} as const; +/** Minimal state stored with saved games (not used during gameplay) */ +export interface SavedGameState { + phase: GamePhase; + currentRoundIndex: number; +} /** Complete game configuration (saveable/loadable) */ export interface KuldvillakGame { @@ -118,7 +104,7 @@ export interface KuldvillakGame { teams: Team[]; rounds: Round[]; finalRound: FinalRound | null; - state: GameState; + state: SavedGameState; } /** Default settings for new games */ @@ -126,7 +112,6 @@ export const DEFAULT_SETTINGS: GameSettings = { numberOfRounds: 2, pointValuePreset: 'round1', pointValues: [10, 20, 30, 40, 50], - basePointValue: 10, categoriesPerRound: 6, questionsPerCategory: 5, dailyDoublesPerRound: [1, 2], @@ -138,16 +123,10 @@ export const DEFAULT_SETTINGS: GameSettings = { answerRevealSeconds: 5 }; -/** Default initial game state */ -export const DEFAULT_STATE: GameState = { - phase: 'lobby', - currentRoundIndex: 0, - currentQuestionId: null, - currentCategoryId: null, - activeTeamId: null, - dailyDoubleWager: null, - finalWagers: {}, - finalAnswers: {} +/** Default initial game state for saved games */ +export const DEFAULT_STATE: SavedGameState = { + phase: 'intro', + currentRoundIndex: 0 }; // ============================================ @@ -162,7 +141,4 @@ export interface GameMetadata { updatedAt: string; teamCount: number; roundCount: number; -} - -/** View mode for dual-screen setup */ -export type ViewMode = 'projector' | 'moderator'; \ No newline at end of file +} \ No newline at end of file diff --git a/src/routes/kuldvillak/+page.svelte b/src/routes/kuldvillak/+page.svelte index 5843cf2..85ec34b 100644 --- a/src/routes/kuldvillak/+page.svelte +++ b/src/routes/kuldvillak/+page.svelte @@ -32,7 +32,7 @@ - Kuldvillak - Ultimate Gaming + {m.game_kuldvillak()} - {m.app_title()} diff --git a/src/routes/kuldvillak/edit/+page.svelte b/src/routes/kuldvillak/edit/+page.svelte index 744c262..c2b7fb7 100644 --- a/src/routes/kuldvillak/edit/+page.svelte +++ b/src/routes/kuldvillak/edit/+page.svelte @@ -217,14 +217,8 @@ const base = [10, 20, 30, 40, 50]; const multiplier = roundIndex + 1; if (preset === "round1") return base.map((v) => v * multiplier); - if (preset === "round2") return base.map((v) => v * 2 * multiplier); - if (preset === "multiplier") - return [1, 2, 3, 4, 5].map( - (i) => settings.basePointValue * i * multiplier, - ); - if (preset === "custom") - return settings.pointValues.map((v) => v * multiplier); - return settings.pointValues; + // Custom preset uses user-defined point values + return settings.pointValues.map((v) => v * multiplier); } function updatePreset(preset: PointValuePreset) { @@ -475,7 +469,7 @@ - {m.kv_edit_title()} - Kuldvillak + {m.kv_edit_title()} - {m.game_kuldvillak()} @@ -790,7 +784,7 @@ onclick={() => (settings.allowNegativeScores = !settings.allowNegativeScores)} - class="w-8 h-8 border-none cursor-pointer p-0 rounded-sm flex items-center justify-center {settings.allowNegativeScores + class="w-8 h-8 cursor-pointer p-0 rounded-sm flex items-center justify-center border-4 border-black {settings.allowNegativeScores ? 'bg-kv-yellow' : 'bg-white'}" > @@ -798,6 +792,10 @@ + {:else} + {/if} @@ -900,10 +898,10 @@ onclick={() => openQuestion(ri, ci, qi)} class="bg-kv-blue flex items-center justify-center cursor-pointer border-none transition-opacity relative {q.question.trim() ? 'opacity-100' : 'opacity-50'} - {q.isDailyDouble ? 'ring-2 ring-inset ring-kv-yellow' : ''}" + {q.isDailyDouble ? 'ring-2 ring-inset ring-kv-yellow' : ''} kv-shadow-text" > {q.points}€ @@ -990,12 +988,14 @@ (settings.enableFinalRound = !settings.enableFinalRound)} - class="w-8 h-8 rounded-sm cursor-pointer border-none p-0 flex items-center justify-center {settings.enableFinalRound + class="w-8 h-8 rounded-sm cursor-pointer p-0 flex items-center justify-center border-4 border-black {settings.enableFinalRound ? 'bg-kv-yellow' : 'bg-white'}" > {#if settings.enableFinalRound} + {:else} + {/if} - {gameSession.state?.name ?? "Play"} - Kuldvillak + {gameSession.state?.name ?? m.kv_play_round()} - {m.game_kuldvillak()} {#if !gameSession.state} {#if view === "projector"} -
+
@@ -46,24 +48,25 @@
{:else} -
-
- +
+
+

{m.kv_play_loading()}

-

+

{m.kv_play_loading_hint()}

- - {m.kv_play_go_to_editor()} + + + {m.kv_play_go_to_editor()} +
diff --git a/src/routes/kuldvillak/play/ModeratorView.svelte b/src/routes/kuldvillak/play/ModeratorView.svelte index bdcd96f..ca0fcbe 100644 --- a/src/routes/kuldvillak/play/ModeratorView.svelte +++ b/src/routes/kuldvillak/play/ModeratorView.svelte @@ -76,19 +76,6 @@ let currentRound = $derived(gameSession.currentRound); let questionData = $derived(gameSession.currentQuestionData); - // Calculate total questions in all rounds - let totalQuestions = $derived(() => { - if (!session) return 30; - return session.rounds.reduce((total, round) => { - return ( - total + - round.categories.reduce((catTotal, cat) => { - return catTotal + cat.questions.length; - }, 0) - ); - }, 0); - }); - function selectQuestion(catIndex: number, qIndex: number) { if (!session) return; gameSession.selectQuestion(session.currentRoundIndex, catIndex, qIndex); @@ -121,17 +108,7 @@ gameSession.adjustScore(teamId, amount); } - function handleTeamClick(teamId: string) { - if ( - session?.phase === "question" && - !session.showAnswer && - !isTeamWrong(teamId) - ) { - gameSession.setActiveTeam(teamId); - } - } - - // Count daily doubles in a round + // Count daily doubles in a round (total) function countDailyDoubles(roundIndex: number) { if (!session) return 0; const round = session.rounds[roundIndex]; @@ -143,6 +120,39 @@ }, 0) ?? 0 ); } + + // Count remaining (unrevealed) daily doubles in a round + function countRemainingDailyDoubles(roundIndex: number) { + if (!session) return 0; + const round = session.rounds[roundIndex]; + return ( + round?.categories.reduce((count, cat) => { + return ( + count + + cat.questions.filter( + (q) => q.isDailyDouble && !q.isRevealed, + ).length + ); + }, 0) ?? 0 + ); + } + + // Calculate total questions in current round + let totalQuestionsInRound = $derived( + currentRound?.categories.reduce( + (total, cat) => total + cat.questions.length, + 0, + ) ?? 0, + ); + + // Count answered questions in current round + let answeredInRound = $derived( + currentRound?.categories.reduce( + (total, cat) => + total + cat.questions.filter((q) => q.isRevealed).length, + 0, + ) ?? 0, + ); {#if session} @@ -166,16 +176,15 @@ {:else} {session.currentRoundIndex === 0 - ? "Villak" - : "Topeltvillak"} + ? m.kv_edit_r1() + : m.kv_edit_r2()} ({m.kv_edit_dd_short()} - {countDailyDoubles( + {countRemainingDailyDoubles( session.currentRoundIndex, - )}/{session.settings.dailyDoublesPerRound[ - session.currentRoundIndex - ] ?? 1}) + )}/{countDailyDoubles(session.currentRoundIndex)}, + {m.kv_play_question_short()}: {answeredInRound}/{totalQuestionsInRound}) {/if}
@@ -213,23 +222,17 @@
-
- {#if session.lastAnsweredTeamId} - {@const lastTeam = session.teams.find( - (t) => t.id === session.lastAnsweredTeamId, +
+ {#if session.lastCorrectTeamId} + {@const lastCorrectTeam = session.teams.find( + (t) => t.id === session.lastCorrectTeamId, )} {m.kv_play_last_answer()}: - - {lastTeam?.name} + + {lastCorrectTeam?.name} {:else} @@ -251,7 +254,7 @@
- + {#if session.finalRevealed.length === session.teams.length} - {#if session.showAnswer} - gameSession.showFinalScores()} - > - {m.kv_play_show_scores()} - - {:else} - gameSession.revealFinalAnswer()} - > - {m.kv_play_reveal_answer()} - - {/if} + gameSession.showFinalScores()} + > + {m.kv_play_show_scores()} + {/if}
{:else if session.phase === "final-scores"} @@ -800,10 +802,10 @@ {@const bottomRowCount = count > 3 ? count - 3 : 0} {@const topRow = sorted.slice(0, topRowCount)} {@const bottomRow = sorted.slice(topRowCount)} -
+
{#each topRow as team, i} @@ -834,7 +836,7 @@ {#if bottomRowCount > 0}
{#each bottomRow as team, i} @@ -863,8 +865,10 @@ {/each}
{/if} -
- gameSession.endGame()}> +
+ (showEndGameConfirm = true)} + > {m.kv_play_end_game()}
diff --git a/src/routes/kuldvillak/play/ProjectorView.svelte b/src/routes/kuldvillak/play/ProjectorView.svelte index 4cb5bcb..d6b5200 100644 --- a/src/routes/kuldvillak/play/ProjectorView.svelte +++ b/src/routes/kuldvillak/play/ProjectorView.svelte @@ -193,66 +193,71 @@ prevBoardPhase !== "board" && !alreadyRevealed ) { - // First time entering board - do the reveal animation + // First time entering board - wait 3 seconds showing logos, then reveal prices boardRevealPhase = "revealing"; revealedPrices = new Set(); - // Custom reveal order: [ci, qi, order] - order determines when cell appears - // Grid: 6 columns (C1-C6) × 5 rows (R1-R5) - const revealOrder: [number, number, number][] = [ - // Row 1 (qi=0): 01 02 15 11 13 08 - [0, 0, 1], - [1, 0, 2], - [2, 0, 15], - [3, 0, 11], - [4, 0, 13], - [5, 0, 8], - // Row 2 (qi=1): 25 04 28 24 05 07 - [0, 1, 25], - [1, 1, 4], - [2, 1, 28], - [3, 1, 24], - [4, 1, 5], - [5, 1, 7], - // Row 3 (qi=2): 20 16 09 10 18 26 - [0, 2, 20], - [1, 2, 16], - [2, 2, 9], - [3, 2, 10], - [4, 2, 18], - [5, 2, 26], - // Row 4 (qi=3): 12 27 06 23 21 30 - [0, 3, 12], - [1, 3, 27], - [2, 3, 6], - [3, 3, 23], - [4, 3, 21], - [5, 3, 30], - // Row 5 (qi=4): 19 22 03 14 17 29 - [0, 4, 19], - [1, 4, 22], - [2, 4, 3], - [3, 4, 14], - [4, 4, 17], - [5, 4, 29], - ]; + // 3 second delay before starting the price reveal animation + const BOARD_REVEAL_DELAY = 1000; - // Sort by order and schedule reveals - const sorted = [...revealOrder].sort((a, b) => a[2] - b[2]); - sorted.forEach(([ci, qi, _order], idx) => { - const key = `${ci}-${qi}`; - setTimeout(() => { - revealedPrices = new Set([...revealedPrices, key]); - }, idx * 50); // 50ms between each cell - }); + setTimeout(() => { + // Custom reveal order: [ci, qi, order] - order determines when cell appears + // Grid: 6 columns (C1-C6) × 5 rows (R1-R5) + const revealOrder: [number, number, number][] = [ + // Row 1 (qi=0): 01 02 15 11 13 08 + [0, 0, 1], + [1, 0, 2], + [2, 0, 15], + [3, 0, 11], + [4, 0, 13], + [5, 0, 8], + // Row 2 (qi=1): 25 04 28 24 05 07 + [0, 1, 25], + [1, 1, 4], + [2, 1, 28], + [3, 1, 24], + [4, 1, 5], + [5, 1, 7], + // Row 3 (qi=2): 20 16 09 10 18 26 + [0, 2, 20], + [1, 2, 16], + [2, 2, 9], + [3, 2, 10], + [4, 2, 18], + [5, 2, 26], + // Row 4 (qi=3): 12 27 06 23 21 30 + [0, 3, 12], + [1, 3, 27], + [2, 3, 6], + [3, 3, 23], + [4, 3, 21], + [5, 3, 30], + // Row 5 (qi=4): 19 22 03 14 17 29 + [0, 4, 19], + [1, 4, 22], + [2, 4, 3], + [3, 4, 14], + [4, 4, 17], + [5, 4, 29], + ]; - setTimeout( - () => { - boardRevealPhase = "revealed"; - gameSession.markBoardRevealed(); // Mark as revealed so it won't animate again - }, - sorted.length * 50 + 100, - ); + // Sort by order and schedule reveals + const sorted = [...revealOrder].sort((a, b) => a[2] - b[2]); + sorted.forEach(([ci, qi, _order], idx) => { + const key = `${ci}-${qi}`; + setTimeout(() => { + revealedPrices = new Set([...revealedPrices, key]); + }, idx * 50); // 50ms between each cell + }); + + setTimeout( + () => { + boardRevealPhase = "revealed"; + gameSession.markBoardRevealed(); // Mark as revealed so it won't animate again + }, + sorted.length * 50 + 100, + ); + }, BOARD_REVEAL_DELAY); } else if (currentPhase === "board" && alreadyRevealed) { // Already revealed - show all prices immediately boardRevealPhase = "revealed"; @@ -305,7 +310,7 @@ {#if session.categoriesIntroduced} {:else}
{#if currentCat}
{:else if session.phase === "board" || session.phase === "question"} -
+
-
+
{cat.name || "???"}
@@ -484,7 +489,9 @@ style="transform: scaleX(0.9225);" > {#if session.showAnswer} -
+
{questionData.question.answer}
{:else} @@ -505,7 +512,7 @@ >
@@ -564,7 +571,7 @@ > {#if session.showAnswer}
{questionData.question.answer}
@@ -584,7 +591,7 @@ {:else if session.phase === "daily-double"} -
+
{#if session.dailyDoubleWager}
{#if session.showAnswer} -
+
{session.finalRound?.answer}
{:else} @@ -968,7 +977,6 @@ } /* Scale all elements proportionally to container */ .expand-overlay { - padding: clamp(4px, 3cqh, 32px); gap: clamp(4px, 1.5cqh, 16px); } .expand-overlay .font-kv-question { @@ -1035,7 +1043,6 @@ } /* Scale elements for center expand */ .expand-overlay-center { - padding: clamp(4px, 3cqh, 32px); gap: clamp(4px, 1.5cqh, 16px); } .expand-overlay-center .font-kv-question { @@ -1143,6 +1150,12 @@ } .category-logo { height: 1.2em; + max-width: 100%; + } + .category-logo :global(svg) { + max-width: 100%; + height: 100%; + width: auto; } .category-content.show-logo .category-logo { opacity: 1; diff --git a/static/kuldvillak_favicon.svg b/static/kuldvillak_favicon.svg index fd3cb2b..6ae9759 100644 --- a/static/kuldvillak_favicon.svg +++ b/static/kuldvillak_favicon.svg @@ -1,3 +1,3 @@ - +