Bug fixes, QoL, added new logos, codebase improvements

master
AlacrisDevs 2 weeks ago
parent 9dc3e7006e
commit 8ec3a2b10c
  1. 1
      messages/en.json
  2. 3
      messages/et.json
  3. 2
      package-lock.json
  4. 2
      package.json
  5. 7
      src/app.html
  6. 2
      src/lib/assets/kuldvillak_favicon.svg
  7. 10
      src/lib/components/kuldvillak/ui/KvEditCard.svelte
  8. 184
      src/lib/components/kuldvillak/ui/KvGameLogo.svelte
  9. 4
      src/lib/components/kuldvillak/ui/KvSpinner.svelte
  10. 4
      src/lib/example_testing_game.json
  11. 115
      src/lib/stores/gameSession.svelte.ts
  12. 2
      src/lib/stores/persistence.test.ts
  13. 10
      src/lib/stores/persistence.ts
  14. 16
      src/lib/types/kuldvillak.test.ts
  15. 68
      src/lib/types/kuldvillak.ts
  16. 2
      src/routes/kuldvillak/+page.svelte
  17. 30
      src/routes/kuldvillak/edit/+page.svelte
  18. 31
      src/routes/kuldvillak/play/+page.svelte
  19. 150
      src/routes/kuldvillak/play/ModeratorView.svelte
  20. 153
      src/routes/kuldvillak/play/ProjectorView.svelte
  21. 2
      static/kuldvillak_favicon.svg

@ -158,6 +158,7 @@
"kv_play_judging": "Judging", "kv_play_judging": "Judging",
"kv_play_enter_wager": "Enter wager", "kv_play_enter_wager": "Enter wager",
"kv_play_judged": "Judged", "kv_play_judged": "Judged",
"kv_play_wager_range": "Min: {min}€ — Max: {max}€",
"kv_play_finish": "Finish", "kv_play_finish": "Finish",
"kv_color_picker": "Color Picker", "kv_color_picker": "Color Picker",
"kv_done": "Done", "kv_done": "Done",

@ -106,7 +106,7 @@
"kv_play_confirm": "Kinnita", "kv_play_confirm": "Kinnita",
"kv_play_question_number": "Küsimus {current}/{total}", "kv_play_question_number": "Küsimus {current}/{total}",
"kv_play_showing_answer": "Näitan vastust...", "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_answer_short": "Vastus",
"kv_play_answering": "Vastab", "kv_play_answering": "Vastab",
"kv_play_correct": "Õige", "kv_play_correct": "Õige",
@ -158,6 +158,7 @@
"kv_play_judging": "Hindamine", "kv_play_judging": "Hindamine",
"kv_play_enter_wager": "Sisesta panus", "kv_play_enter_wager": "Sisesta panus",
"kv_play_judged": "Hinnatud", "kv_play_judged": "Hinnatud",
"kv_play_wager_range": "Min: {min}€ — Max: {max}€",
"kv_play_finish": "Lõpeta", "kv_play_finish": "Lõpeta",
"kv_color_picker": "Värvivalija", "kv_color_picker": "Värvivalija",
"kv_done": "Valmis", "kv_done": "Valmis",

2
package-lock.json generated

@ -1,6 +1,6 @@
{ {
"name": "ultimate-gaming", "name": "ultimate-gaming",
"version": "0.1.0", "version": "0.1.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {

@ -1,7 +1,7 @@
{ {
"name": "ultimate-gaming", "name": "ultimate-gaming",
"private": true, "private": true,
"version": "0.1.0", "version": "0.1.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",

@ -1,11 +1,18 @@
<!doctype html> <!doctype html>
<html lang="%paraglide.lang%"> <html lang="%paraglide.lang%">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Preload fonts to prevent FOUT (Flash of Unstyled Text) -->
<link rel="preload" href="/fonts/BebasNeue-Regular.ttf" as="font" type="font/ttf" crossorigin />
<link rel="preload" href="/fonts/Swiss 921 Regular.otf" as="font" type="font/opentype" crossorigin />
<link rel="preload" href="/fonts/ITC Korinna Regular.otf" as="font" type="font/opentype" crossorigin />
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>
</body> </body>
</html> </html>

@ -1,3 +1,3 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.05957 1.5L11.3781 12.5V115.5L8.05957 126.5H45.9855L42.667 115.5V73.6719L82.0151 126.5H119.941L67.3188 59L115.674 1.50006H77.7485L42.667 50.5001V12.5L45.9855 1.50006L8.05957 1.5Z" fill="#FFAB00"/> <path d="M41.1692 53.4247L69.1227 18.3469L69.6477 17.6762C72.1759 14.3139 73.9344 10.0017 74.8782 4.68266L75.3597 0.603516H120.912L112.11 5.67812C108.463 8.11385 104.833 11.5122 101.228 15.9036L66.1687 58.5571L100.949 109.253C104.122 113.783 107.03 117.183 109.669 119.496C112.414 121.688 115.611 123.454 119.273 124.785L118.812 127.396H73.6992L74.2347 125.654C74.6513 124.301 74.865 122.827 74.865 121.224C74.865 119.433 74.5497 117.765 73.9268 116.208L73.9124 116.171L73.8992 116.133C73.381 114.58 72.4248 112.807 70.9807 110.809L70.9702 110.794L70.9609 110.78L41.1692 67.1546V108.469C41.1693 115.27 42.7295 120.85 45.7641 125.291L47.2023 127.396H7.22473L8.70898 125.278C10.2229 123.116 11.3047 120.739 11.9551 118.139L11.9591 118.122L11.9644 118.103C12.7285 115.376 13.121 112.17 13.121 108.469V18.4968C13.121 12.4177 11.5774 7.17358 8.5274 2.71015L7.08789 0.603516H47.6141L46.0917 2.73251C42.8134 7.31921 41.1692 12.5587 41.1692 18.4968V53.4247Z" fill="#FFAB00"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 355 B

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -43,6 +43,7 @@
let finalWagerInput = $state(0); let finalWagerInput = $state(0);
</script> </script>
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
<div <div
class="group relative bg-kv-blue flex flex-col gap-4 items-center justify-center p-4 flex-1 min-w-0 box-border class="group relative bg-kv-blue flex flex-col gap-4 items-center justify-center p-4 flex-1 min-w-0 box-border
{answering || finalActive {answering || finalActive
@ -54,8 +55,8 @@
onkeydown={selectable && !finalJudged onkeydown={selectable && !finalJudged
? (e) => e.key === "Enter" && onSelect?.() ? (e) => e.key === "Enter" && onSelect?.()
: undefined} : undefined}
role={selectable && !finalJudged ? "button" : undefined} role={selectable && !finalJudged ? "button" : "presentation"}
tabindex={selectable && !finalJudged ? 0 : undefined} tabindex={selectable && !finalJudged ? 0 : -1}
> >
<!-- Hover overlay - darkens background only, extends to cover border --> <!-- Hover overlay - darkens background only, extends to cover border -->
{#if selectable && !finalJudged} {#if selectable && !finalJudged}
@ -78,7 +79,10 @@
: ''}">{score}</span : ''}">{score}</span
> >
{#if finalJudged} {#if finalJudged}
<span class="text-sm text-kv-green">{m.kv_play_judged()}</span> <span
class="text-lg md:text-xl text-kv-green uppercase kv-shadow-text"
>✓ {m.kv_play_judged()}</span
>
{/if} {/if}
</div> </div>

@ -21,7 +21,7 @@
</script> </script>
{#if variant === "kuldvillak"} {#if variant === "kuldvillak"}
<!-- KULDVILLAK - Full logo with all letters --> <!-- KULDVILLAK -->
<svg <svg
class="text-kv-yellow {sizeClasses[size]} {className}" class="text-kv-yellow {sizeClasses[size]} {className}"
viewBox="0 0 1024 128" viewBox="0 0 1024 128"
@ -31,105 +31,203 @@
aria-label="Kuldvillak" aria-label="Kuldvillak"
> >
<path <path
fill-rule="evenodd" d="M34.0813 52.8211L62.0335 17.7434C64.8427 14.288 66.7869 9.74444 67.7929 4.06205L68.2705 0H113.825L105.028 5.06935C101.379 7.50538 97.7476 10.9058 94.1397 15.3001L59.0795 57.9536L93.8568 108.644L94.4503 109.485C97.3983 113.605 100.113 116.731 102.589 118.9C105.332 121.089 108.527 122.852 112.185 124.181L111.724 126.793H66.6099L67.1468 125.05C67.5634 123.697 67.7771 122.223 67.7771 120.62C67.7771 118.829 67.4618 117.161 66.8389 115.605L66.8244 115.568L66.8113 115.53C66.2931 113.977 65.3369 112.204 63.8928 110.205L63.8717 110.176L34.0813 66.5524V107.866C34.0814 114.667 35.6403 120.247 38.6749 124.688L40.1144 126.793H0.13553L1.61979 124.674C3.13375 122.513 4.21547 120.135 4.86594 117.535L4.8712 117.518L4.87647 117.5C5.64064 114.772 6.03305 111.566 6.03308 107.866V17.8933C6.03307 11.814 4.48836 6.57015 1.4382 2.10664L0 0H40.5262L39.0038 2.12899C35.7256 6.7157 34.0813 11.9552 34.0813 17.8933V52.8211Z"
clip-rule="evenodd"
d="M912.118 0L915.437 11V114L912.118 125H950.044L946.726 114V72.1719L986.074 125H1024L971.377 57.5L1019.73 0H981.807L946.726 49V11L950.044 0H912.118Z"
fill="currentColor" fill="currentColor"
/> />
<path <path
d="M830.841 0L787.603 114L779.26 125L813.772 125L823.706 94.086H861.678L871.611 125L906.124 125L897.78 114L854.544 0H830.841ZM842.693 35L854.93 73.086H830.454L842.693 35Z" d="M172.012 108.76C166.234 108.76 161.789 106.922 158.519 103.338C155.24 99.7435 153.524 94.7574 153.524 88.2158V17.8933C153.524 14.5799 153.965 11.6363 154.823 9.04724C155.833 6.3578 156.946 4.02801 158.156 2.04878L159.409 0H119.117L120.534 2.10006C123.715 6.81333 125.305 12.0655 125.305 17.8933V83.9079C125.305 86.1133 125.363 88.1037 125.48 89.8635C125.598 91.513 126.006 95.1691 126.695 100.79L126.696 100.806L126.699 100.82C127.573 106.813 131.775 112.829 138.886 118.887L138.904 118.903C146.289 124.973 155.048 128 165.113 128C171.622 128 177.843 126.695 183.764 124.092C188.82 121.911 193.191 119.256 196.864 116.12C196.871 116.255 196.878 116.375 196.878 116.483C196.878 119.501 196.234 122.281 194.952 124.844L193.977 126.793H229.418L228.182 124.749C225.338 120.049 223.892 114.496 223.892 108.038V18.0655C223.892 13.4601 224.874 9.07965 226.84 4.91023L229.729 0H189.661L192.548 4.90629C194.64 9.32422 195.672 13.7065 195.672 18.0655V92.0083C195.672 95.4846 193.855 98.809 189.807 101.98C185.592 105.282 182.407 107.069 180.188 107.581C177.828 108.03 176.069 108.369 174.915 108.6C173.826 108.707 172.86 108.76 172.012 108.76Z"
fill="currentColor" fill="currentColor"
/> />
<path <path
fill-rule="evenodd" d="M281.903 107.202C288.893 107.144 295.261 106.634 301.011 105.678C307.095 104.551 313.074 102.581 318.949 99.7576L321.76 98.4071L312.76 126.793H247.133L248.998 124.578C250.715 122.541 251.883 120.307 252.52 117.867L252.526 117.842L252.534 117.815C253.285 115.349 253.683 112.048 253.683 107.866V18.0655C253.683 11.5107 252.074 6.24437 248.958 2.16318L247.306 0H287.804L285.386 4.52625C283.033 9.68785 281.903 14.1923 281.903 18.0655V107.202Z"
clip-rule="evenodd"
d="M707.371 0L710.69 11V114L707.371 125H770.423L780.853 95C780.853 95 766.952 103 741.978 103V11L745.297 0H707.371Z"
fill="currentColor" fill="currentColor"
/> />
<path <path
fill-rule="evenodd" fill-rule="evenodd"
clip-rule="evenodd" clip-rule="evenodd"
d="M623.724 0L627.042 11V114L623.724 125H686.775L697.205 95C697.205 95 683.305 103 658.331 103V11L661.65 0H623.724Z" d="M336.418 17.8933C336.418 11.9552 334.773 6.7157 331.494 2.12899L329.973 0H360.745L369.067 0.519427L369.11 0.522057L369.153 0.528632C388.231 2.97001 403.149 9.67534 413.763 20.7534C424.381 31.8366 429.659 45.9744 429.659 63.0519C429.659 80.3573 424.716 94.7017 414.756 105.976L413.777 107.057C403.27 118.384 388.673 124.812 370.135 126.443L370.105 126.446L361.969 126.792L361.94 126.793H329.855L331.523 124.626C334.749 120.435 336.418 114.944 336.418 108.038V17.8933ZM400.232 63.7409C400.232 50.4806 397.339 40.1691 391.705 32.6621C386.064 25.0343 378.09 20.0206 367.698 17.6658L364.465 17.2042V109.602L368.076 109.12C378.33 107.221 386.165 102.399 391.687 94.6725C397.344 86.8019 400.232 76.5259 400.232 63.7409Z"
fill="currentColor" fill="currentColor"
/> />
<path <path
fill-rule="evenodd" d="M470.004 7.89661C470.004 10.4585 470.651 13.9296 472.015 18.3588L495.078 87.8371L519.637 17.8117C520.997 13.7357 521.637 10.5034 521.637 8.06887C521.637 7.25857 521.536 6.31434 521.319 5.22978C521.096 4.11466 520.815 2.93875 520.478 1.70162L520.013 0H558.629L555.349 2.42882C552.376 4.62932 549.839 7.32693 547.739 10.5306L547.727 10.549C545.761 13.4445 543.963 17.1576 542.346 21.7147L542.025 22.6365L506.53 126.793H479.575L479.275 125.859L445.816 22.0987C443.995 16.411 442.147 12.2468 440.303 9.53116C438.355 6.83186 435.743 4.48456 432.436 2.50114L428.265 0H471.186L470.837 1.62929C470.492 3.23721 470.267 4.35949 470.158 5.01543L470.155 5.03647L470.151 5.05751C470.066 5.48536 470.004 6.39658 470.004 7.89661Z"
clip-rule="evenodd"
d="M568.046 0L571.364 11V114L568.046 125H605.972L602.653 114V11L605.972 0H568.046Z"
fill="currentColor" fill="currentColor"
/> />
<path <path
d="M560.535 0L552.191 11L508.956 125H485.252L442.017 11L433.673 0H468.185L497.104 90L526.023 0H560.535Z" d="M569.285 108.038V18.0655C569.285 11.7749 567.578 6.50168 564.22 2.17107L562.537 0H603.687L600.794 4.91681C598.47 9.45783 597.333 13.8936 597.333 18.2378V108.038C597.333 114.337 598.987 119.871 602.273 124.688L603.708 126.793H562.653L564.192 124.659C567.576 119.969 569.285 114.448 569.285 108.038Z"
fill="currentColor" fill="currentColor"
/> />
<path <path
fill-rule="evenodd" d="M655.81 107.202C662.797 107.144 669.162 106.635 674.91 105.679C680.996 104.553 686.98 102.581 692.857 99.7576L695.668 98.4071L686.666 126.793H621.04L622.905 124.578C624.621 122.541 625.789 120.307 626.426 117.867L626.432 117.842L626.44 117.815C627.192 115.349 627.59 112.048 627.59 107.866V18.0655C627.59 11.5107 625.982 6.24437 622.865 2.16318L621.213 0H661.71L659.293 4.52625C656.939 9.68777 655.81 14.1923 655.81 18.0655V107.202Z"
clip-rule="evenodd"
d="M336.276 0L339.595 11V114L336.276 125H374.202C377.363 125 380.52 124.787 383.649 124.363C386.776 123.94 389.868 123.307 392.9 122.469C395.933 121.63 398.899 120.588 401.774 119.352C404.649 118.115 407.427 116.687 410.086 115.078C412.744 113.469 415.277 111.683 417.665 109.734C420.054 107.785 422.292 105.677 424.362 103.428C426.432 101.178 428.328 98.7928 430.037 96.2891C431.746 93.7854 433.262 91.17 434.575 88.4629C435.887 85.7555 436.993 82.9632 437.884 80.1074C438.774 77.252 439.447 74.3402 439.897 71.3945C440.347 68.4486 440.573 65.4761 440.573 62.5C440.573 61.508 440.548 60.5162 440.498 59.5254C440.448 58.5349 440.373 57.5457 440.272 56.5586C440.172 55.5715 440.047 54.5869 439.896 53.6054C439.747 52.6238 439.572 51.6457 439.372 50.6719C439.173 49.6983 438.95 48.7292 438.702 47.7656C438.453 46.8013 438.181 45.8427 437.883 44.8906C437.587 43.9396 437.267 42.9953 436.922 42.0586C436.578 41.1214 436.21 40.1921 435.819 39.2715C435.427 38.3509 435.012 37.4391 434.574 36.5371C434.137 35.635 433.677 34.7428 433.195 33.8613C432.712 32.9797 432.208 32.109 431.682 31.25C431.155 30.3908 430.607 29.5435 430.037 28.7089C429.468 27.8749 428.878 27.0537 428.267 26.2461C427.655 25.4384 427.024 24.6446 426.372 23.8652C425.721 23.0853 425.051 22.3201 424.361 21.5703C423.672 20.8208 422.964 20.0869 422.237 19.3691C421.511 18.6514 420.766 17.95 420.004 17.2656C419.241 16.5816 418.461 15.9147 417.665 15.2656C416.869 14.616 416.057 13.9843 415.23 13.3711C414.402 12.7579 413.558 12.1633 412.7 11.5879C411.843 11.0131 410.971 10.4575 410.085 9.92184C409.199 9.38548 408.299 8.86906 407.387 8.373C406.475 7.87712 405.551 7.4017 404.615 6.94725C403.679 6.49314 402.732 6.06008 401.774 5.64841C400.816 5.23614 399.848 4.84542 398.87 4.47654C397.892 4.10804 396.905 3.76165 395.909 3.43748C394.914 3.11282 393.911 2.81078 392.9 2.53121C391.89 2.2519 390.873 1.99539 389.85 1.76171C388.827 1.52775 387.797 1.31669 386.763 1.12888C385.729 0.941039 384.691 0.776271 383.648 0.634727C382.606 0.494129 381.56 0.376955 380.511 0.283222C379.463 0.188734 378.413 0.117689 377.361 0.0704451C376.309 0.023201 375.255 -0.000233775 374.202 0.000144178L336.276 0ZM370.884 20.25C371.649 20.2502 372.413 20.2764 373.176 20.332C373.939 20.3857 374.701 20.4665 375.46 20.5742C376.218 20.6827 376.974 20.8184 377.724 20.9805C378.475 21.1423 379.221 21.3305 379.962 21.545C380.702 21.7604 381.436 22.0021 382.163 22.2696C382.891 22.5364 383.611 22.8287 384.323 23.1465C385.033 23.4648 385.735 23.8079 386.426 24.1758C387.118 24.5436 387.799 24.9356 388.469 25.3516C389.139 25.7682 389.797 26.2086 390.443 26.6719C391.089 27.1343 391.722 27.6196 392.341 28.127C392.959 28.6354 393.563 29.1656 394.152 29.7169C394.741 30.2673 395.315 30.8386 395.873 31.4298C396.43 32.0211 396.971 32.632 397.495 33.2618C398.018 33.8914 398.524 34.5396 399.012 35.2051C399.498 35.8713 399.966 36.5546 400.415 37.2539C400.865 37.9527 401.296 38.6672 401.706 39.3965C402.115 40.126 402.505 40.8699 402.873 41.627C403.241 42.384 403.588 43.154 403.913 43.9356C404.239 44.7176 404.543 45.5109 404.825 46.3145C405.106 47.1176 405.364 47.9305 405.6 48.752C405.837 49.5743 406.051 50.4048 406.241 51.2422C406.431 52.0786 406.598 52.9214 406.741 53.7696C406.884 54.6186 407.005 55.4726 407.1 56.3301C407.196 57.1874 407.267 58.0479 407.315 58.9102C407.363 59.7724 407.387 60.6361 407.387 61.5001C407.388 63.9011 407.203 66.2976 406.834 68.6622C406.464 71.027 405.913 73.351 405.186 75.6075C404.459 77.8643 403.559 80.0451 402.497 82.1251C401.434 84.2049 400.213 86.176 398.847 88.0157C397.481 89.855 395.976 91.5562 394.349 93.0996C392.72 94.6428 390.976 96.0224 389.136 97.2227C387.295 98.4234 385.366 99.4403 383.369 100.262C381.372 101.083 379.315 101.706 377.223 102.123C375.13 102.54 373.009 102.75 370.884 102.75V20.25Z"
fill="currentColor" fill="currentColor"
/> />
<path <path
fill-rule="evenodd" d="M738.373 107.202C745.362 107.144 751.73 106.634 757.48 105.678C763.564 104.551 769.543 102.581 775.419 99.7576L778.229 98.4071L769.229 126.793H703.602L705.468 124.578C707.184 122.541 708.352 120.307 708.989 117.867L708.996 117.842L709.003 117.815C709.755 115.349 710.152 112.048 710.152 107.866V18.0655C710.152 11.5107 708.544 6.24437 705.427 2.16318L703.776 0H744.273L741.856 4.52625C739.502 9.68786 738.373 14.1923 738.373 18.0655V107.202Z"
clip-rule="evenodd"
d="M252.629 0L255.947 11V114L252.629 125H315.681L326.11 95C326.11 95 312.21 103 287.236 103V11L290.555 0H252.629Z"
fill="currentColor" fill="currentColor"
/> />
<path <path
fill-rule="evenodd" d="M855.575 3.66755L854.841 0H829.866L829.139 3.63073L828.461 6.17001L828.456 6.19105C828.005 7.9925 827.441 9.97008 826.761 12.1244C826.077 14.1765 825.507 15.8233 825.054 17.0688L791.09 100.981C787.349 110.157 782.486 118.003 776.511 124.538L774.449 126.793H815.022L814.382 119.523C814.388 114.755 815.168 110.341 816.708 106.272L823.441 89.0456H855.708L862.466 108.648L862.474 108.671C863.368 111.127 863.843 113.178 863.948 114.843L863.95 114.885L863.954 114.928C864.178 116.715 864.29 118.44 864.29 120.103V122.516C864.29 123.45 864.185 124.317 863.985 125.12L863.566 126.793H903.727L900.379 121.926C895.382 114.662 891.41 107.006 888.46 98.9568L859.155 17.1253L856.084 6.38041L855.579 3.68465L855.575 3.66755ZM851.404 69.4612H827.676L840.21 32.4741L851.404 69.4612Z"
clip-rule="evenodd"
d="M122.048 0.00245102L125.366 11.0022V84.2509C125.366 86.7978 125.577 89.34 125.996 91.8484C126.415 94.3561 127.041 96.8205 127.866 99.2135C128.693 101.607 129.716 103.92 130.924 106.125C132.131 108.331 133.518 110.421 135.07 112.371C136.623 114.322 138.333 116.127 140.183 117.764C142.034 119.401 144.016 120.865 146.108 122.139C147.973 123.25 149.918 124.206 151.924 125C152.168 125.123 152.413 125.243 152.659 125.361C154.929 126.233 157.266 126.893 159.645 127.336C162.023 127.778 164.434 128 166.849 128C169.263 128 171.673 127.778 174.051 127.336C176.429 126.893 178.766 126.233 181.036 125.361C183.305 124.49 185.498 123.412 187.59 122.139C189.681 120.865 191.662 119.401 193.512 117.764C194.447 116.864 195.342 115.92 196.195 114.934L193.158 125H231.085L227.766 114V11.0022L231.085 0.00245102H193.158L196.477 11.0022V82.0009V82.0772H196.469C196.467 83.6241 196.302 85.166 195.979 86.6748C195.647 88.2074 195.153 89.6959 194.506 91.1122C193.86 92.5292 193.064 93.865 192.134 95.0946C191.204 96.3237 190.146 97.4387 188.98 98.4187C187.815 99.399 186.549 100.238 185.206 100.921C183.863 101.603 182.451 102.124 180.997 102.473C179.543 102.824 178.057 103 176.565 103.001C175.075 103 173.589 102.823 172.136 102.473C170.682 102.124 169.27 101.603 167.926 100.921C166.583 100.238 165.317 99.399 164.152 98.4187C162.986 97.4387 161.928 96.3237 160.998 95.0946C160.069 93.865 159.273 92.5292 158.626 91.1122C157.979 89.6959 157.486 88.2074 157.154 86.6748C156.83 85.1661 156.664 83.6242 156.661 82.0772H156.656V82.0187L156.654 81.9998L156.656 81.9809V10.9998L159.974 0L122.048 0.00245102Z"
fill="currentColor" fill="currentColor"
/> />
<path <path
fill-rule="evenodd" d="M944.257 52.8211L972.21 17.7434L972.735 17.0727C975.263 13.7104 977.022 9.39822 977.966 4.07915L978.447 0H1024L1015.2 5.07461C1011.55 7.51034 1007.92 10.9087 1004.32 15.3001L969.256 57.9536L1004.04 108.65C1007.21 113.18 1010.12 116.579 1012.76 118.892C1015.5 121.084 1018.7 122.85 1022.36 124.181L1021.9 126.793H976.787L977.322 125.05C977.739 123.697 977.953 122.223 977.953 120.62C977.953 118.829 977.637 117.161 977.014 115.605L977 115.568L976.987 115.53C976.469 113.977 975.512 112.204 974.068 110.205L974.058 110.191L974.048 110.176L944.257 66.5511V107.866C944.257 114.667 945.817 120.247 948.852 124.688L950.29 126.793H910.312L911.797 124.674C913.31 122.513 914.392 120.135 915.043 117.535L915.047 117.518L915.052 117.5C915.816 114.772 916.209 111.566 916.209 107.866V17.8933C916.209 11.8142 914.665 6.57006 911.615 2.10664L910.175 0H950.702L949.179 2.12899C945.901 6.7157 944.257 11.9552 944.257 17.8933V52.8211Z"
clip-rule="evenodd"
d="M0 0L3.31851 11V114L0 125H37.9259L34.6074 114V72.1719L73.9556 125H111.882L59.2593 57.5L107.615 5.7671e-05H69.6889L34.6074 49.0001V11L37.9259 5.7671e-05L0 0Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
{:else} {:else if variant === "villak"}
<!-- VILLAK / TOPELTVILLAK / HÕBEVILLAK - Generic logo --> <!-- VILLAK -->
<svg <svg
class="{variant === 'hobevillak' class="text-kv-yellow {sizeClasses[size]} {className}"
? 'text-[#c0c0c0]' viewBox="0 0 601 128"
: 'text-kv-yellow'} {sizeClasses[size]} {className}"
viewBox="0 0 591 128"
fill="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
role="img" role="img"
aria-label={variant === "hobevillak" ? "Hõbevillak" : "Villak"} aria-label="Villak"
> >
<path <path
fill-rule="evenodd" d="M42.1095 7.97179C42.1096 10.5581 42.7621 14.0621 44.1379 18.5336L67.4055 88.6734L92.1798 17.9813C93.5514 13.8663 94.1989 10.6035 94.1989 8.1457C94.1989 7.32768 94.0953 6.37445 93.8763 5.27957C93.6511 4.15382 93.3687 2.96675 93.0281 1.71782L92.5595 0H131.518L128.207 2.45194C125.208 4.67336 122.65 7.39679 120.532 10.6308L120.52 10.6494C118.536 13.5726 116.721 17.3207 115.089 21.9214L114.765 22.852L78.957 128H51.7654L17.7069 22.3091C15.8698 16.5678 14.0077 12.3634 12.1475 9.6219C10.1823 6.89648 7.54542 4.52749 4.20803 2.52495L0 0H43.3016L42.9498 1.6448C42.602 3.26809 42.3752 4.40099 42.2648 5.06318L42.2569 5.10566C42.1705 5.53758 42.1095 6.45748 42.1095 7.97179Z"
clip-rule="evenodd" fill="currentColor"
d="M478.446 0L481.765 11V114L478.446 125H516.372L513.054 114V72.1719L552.402 125H590.328L537.706 57.5L586.061 0H548.135L513.054 49V11L516.372 0H478.446Z" />
<path
d="M142.267 109.067V18.2375C142.267 11.887 140.546 6.56358 137.158 2.19174L135.459 0H176.974L174.053 4.96362C171.71 9.54776 170.562 14.026 170.562 18.4114V109.067C170.562 115.426 172.232 121.013 175.547 125.875L176.995 128H135.577L137.13 125.845C140.543 121.111 142.267 115.538 142.267 109.067Z"
fill="currentColor"
/>
<path
d="M229.557 108.223C236.608 108.164 243.032 107.65 248.833 106.684C254.971 105.547 261.003 103.557 266.93 100.707L269.765 99.344L260.686 128H194.479L196.361 125.764C198.093 123.708 199.271 121.452 199.913 118.989L199.92 118.964L199.928 118.937C200.686 116.447 201.087 113.115 201.087 108.893V18.2375C201.087 11.6203 199.464 6.30382 196.32 2.18378L194.654 0H235.509L233.071 4.56934C230.696 9.78009 229.557 14.3274 229.557 18.2375V108.223Z"
fill="currentColor" fill="currentColor"
/> />
<path <path
d="M397.169 0L353.932 114L345.588 125L380.1 125L390.034 94.086H428.006L437.939 125L472.452 125L464.108 114L420.873 0H397.169ZM409.021 35L421.258 73.086H396.782L409.021 35Z" d="M312.848 108.223C319.9 108.164 326.324 107.65 332.125 106.684C338.263 105.547 344.295 103.557 350.222 100.707L353.058 99.344L343.977 128H277.771L279.652 125.764C281.384 123.708 282.562 121.452 283.205 118.989L283.213 118.964L283.221 118.937C283.978 116.447 284.379 113.115 284.379 108.893V18.2375C284.379 11.6203 282.757 6.30382 279.613 2.18378L277.945 0H318.8L316.363 4.56934C313.989 9.78017 312.848 14.3273 312.848 18.2375V108.223Z"
fill="currentColor"
/>
<path
d="M430.564 1.09255L430.346 0H405.15L404.421 3.64671L403.734 6.22875L403.728 6.24999C403.274 8.06869 402.704 10.0649 402.017 12.2398C401.327 14.3114 400.754 15.9739 400.297 17.2313L366.031 101.942C362.257 111.206 357.352 119.127 351.324 125.723L349.244 128H390.176L389.528 120.661C389.535 115.848 390.321 111.391 391.875 107.284L398.668 89.8934H431.22L438.038 109.683L438.046 109.705C438.947 112.186 439.427 114.255 439.532 115.937L439.535 115.979L439.54 116.022C439.766 117.826 439.877 119.567 439.877 121.247V123.683C439.877 124.626 439.774 125.501 439.571 126.311L439.149 128H479.664L476.286 123.087C471.245 115.754 467.237 108.025 464.262 99.8989L434.704 17.3069L431.599 6.44115L431.09 3.71973L431.087 3.70247L430.564 1.09255ZM426.879 70.1225H402.941L415.586 32.7832L426.879 70.1225Z"
fill="currentColor"
/>
<path
d="M520.552 53.324L548.751 17.9123C551.585 14.4241 553.547 9.83722 554.562 4.10073L555.044 0H601L592.126 5.11761C588.444 7.57683 584.781 11.0097 581.141 15.4458L545.771 58.5054L580.856 109.679L581.455 110.527C584.429 114.687 587.167 117.842 589.665 120.032C592.433 122.241 595.656 124.021 599.346 125.364L598.881 128H553.368L553.91 126.241C554.33 124.875 554.546 123.387 554.546 121.769C554.546 119.96 554.228 118.277 553.599 116.705L553.585 116.668L553.571 116.63C553.049 115.062 552.084 113.272 550.627 111.255L550.606 111.225L520.552 67.1861V108.893C520.552 115.758 522.125 121.392 525.186 125.875L526.639 128H486.307L487.804 125.861C489.331 123.679 490.423 121.279 491.079 118.654L491.084 118.637L491.089 118.618C491.86 115.865 492.256 112.629 492.256 108.893V18.0636C492.256 11.9265 490.698 6.6327 487.621 2.1267L486.17 0H527.054L525.518 2.14926C522.211 6.77964 520.552 12.069 520.552 18.0636V53.324Z"
fill="currentColor"
/>
</svg>
{:else if variant === "topeltvillak"}
<!-- TOPELTVILLAK -->
<svg
class="text-kv-yellow {sizeClasses[size]} {className}"
viewBox="0 0 1196 128"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
role="img"
aria-label="Topeltvillak"
>
<path
d="M63.1692 107.82C63.1692 114.753 64.7637 120.225 67.8327 124.352L69.4083 126.471H29.2587L32.5253 121.718C33.242 120.48 33.8766 118.956 34.4146 117.127L34.421 117.103L34.4288 117.081C35.0375 115.255 35.3799 112.263 35.3799 107.99V19.646C26.4731 19.7414 19.5731 20.8053 14.606 22.7715C9.18451 24.9176 5.30141 26.6987 2.90384 28.1154L0 29.8311L8.37072 1.52777H98.0826L91.053 25.9385L89.6497 25.2906C86.7841 23.9679 83.5152 22.8029 79.8367 21.7996C76.2825 20.8 72.8948 20.0759 69.6934 19.6317L63.1692 19.2144V107.82Z"
fill="currentColor"
/>
<path
d="M124.483 18.2257C112.55 30.2745 106.605 45.5686 106.605 63.9994C106.605 69.2447 107.708 76.7229 109.865 86.3717C112.105 96.2759 118.858 105.69 129.86 114.63C140.977 123.547 154.012 128 168.905 128C175.065 128 183.113 126.725 193.007 124.223C203.233 121.637 212.58 114.729 221.076 103.706C229.75 92.4875 234.091 79.6892 234.091 65.3587C234.091 58.6247 232.757 50.7925 230.13 41.8836C227.452 32.6894 222.165 24.4012 214.327 17.0193C206.852 9.76717 199.035 5.09393 190.873 3.1022L190.083 2.91819C181.885 0.983524 174.987 4.17148e-06 169.414 0C151.529 0 136.521 6.07184 124.483 18.2257ZM179.604 109.89C175.9 109.89 172.188 109.291 168.464 108.086C164.779 106.894 160.367 104.589 155.212 101.116C150.311 97.7367 146.083 93.0321 142.546 86.9574C139.142 80.8048 136.918 73.2783 135.914 64.3544L135.769 62.9912C135.645 61.6857 135.584 60.5509 135.584 59.5832V54.9973C135.584 50.0558 135.88 45.8404 136.461 42.3384L136.581 41.6477C137.262 38.4458 138.156 35.2414 139.261 32.034L139.75 30.654C141.087 27.1711 143.772 24.2832 147.964 22.0264C152.158 19.7675 157.032 18.6184 162.62 18.6184C167.31 18.6184 172.083 19.5483 176.927 21.4199C181.834 23.2738 186.499 26.2842 190.919 30.4829C195.279 34.625 198.749 40.0619 201.295 46.8155L201.3 46.8284C203.943 53.4349 205.282 61.1317 205.282 69.9446C205.282 76.0945 204.723 81.5391 203.618 86.2888L203.613 86.3108C202.607 91.007 201.383 95.058 199.95 98.4747C198.605 101.683 196.155 104.426 192.498 106.686C188.888 108.805 184.606 109.89 179.604 109.89Z"
fill="currentColor" fill="currentColor"
/> />
<path <path
fill-rule="evenodd" fill-rule="evenodd"
clip-rule="evenodd" clip-rule="evenodd"
d="M273.699 0L277.018 11V114L273.699 125H336.751L347.181 95C347.181 95 333.281 103 308.307 103V11L311.625 0H273.699Z" d="M257.058 19.16V107.99C257.058 111.814 256.83 114.577 256.414 116.348C256.003 118.093 255.227 119.867 254.063 121.673L250.466 126.471H290.603L289.207 124.402C286.333 120.143 284.848 114.702 284.848 107.99V73.5586L286.331 73.6467L286.369 73.6493H286.408C292.712 73.6493 298.257 73.0766 303.028 71.9129L303.056 71.9052C307.825 70.6258 312.413 68.6493 316.818 65.9833C321.353 63.2379 325.019 59.1972 327.832 53.923C330.653 48.6319 332.064 42.9256 332.064 36.8247C332.064 28.863 329.831 22.1468 325.296 16.7757C320.964 11.3982 315.623 7.52325 309.288 5.17681C302.981 2.72798 295.341 1.52777 286.408 1.52777H250.594L252.237 3.66458C255.428 7.81296 257.058 12.9503 257.058 19.16ZM303.424 40.3908C303.424 30.2218 300.79 24.2606 296.127 21.6804L296.107 21.67C291.476 19.0093 287.511 17.7509 284.169 17.7217V64.8261C286.115 64.678 288.421 64.2167 291.102 63.4136C294.21 62.409 297.042 59.9661 299.54 55.8396L299.546 55.8279C302.098 51.7873 303.424 46.6608 303.424 40.3908Z"
fill="currentColor"
/>
<path
d="M416.719 1.52777L405.87 25.3592L404.754 25.0133C400.054 23.5584 395.356 22.4408 390.661 21.6584L390.647 21.6558L390.632 21.6519C386.375 20.8411 381.975 20.3994 377.43 20.3276V53.1611H411.851L398.79 75.9715L392.356 73.7595C387.669 72.4368 382.694 71.4857 377.43 70.9061V107.845C392.444 107.714 406.921 105.36 420.867 100.784L423.793 99.8249L411.254 126.471H343.464L344.962 124.373C348.176 119.874 349.811 114.435 349.811 107.99V19.4995C349.811 13.5197 348.239 8.23322 345.112 3.5972L343.717 1.52777H416.719Z"
fill="currentColor"
/>
<path
d="M468.023 107.166C474.906 107.109 481.177 106.606 486.839 105.664C492.831 104.554 498.72 102.612 504.506 99.8301L507.272 98.4993L498.409 126.471H433.782L435.62 124.289C437.31 122.282 438.46 120.079 439.087 117.675L439.094 117.65L439.101 117.624C439.841 115.194 440.233 111.941 440.233 107.82V19.3298C440.233 12.8705 438.649 7.68105 435.579 3.6594L433.953 1.52777H473.833L471.453 5.98799C469.135 11.0743 468.023 15.513 468.023 19.3298V107.166Z"
fill="currentColor" fill="currentColor"
/> />
<path
d="M569.538 107.82C569.538 114.753 571.133 120.225 574.202 124.352L575.777 126.471H535.628L538.894 121.718C539.611 120.48 540.246 118.956 540.784 117.127L540.79 117.103L540.798 117.081C541.407 115.255 541.749 112.263 541.749 107.99V19.646C532.842 19.7414 525.942 20.8053 520.975 22.7715C515.554 24.9176 511.671 26.6987 509.273 28.1154L506.369 29.8311L514.74 1.52777H604.452L597.422 25.9385L596.019 25.2906C593.153 23.9679 589.884 22.8029 586.206 21.7996C582.652 20.8 579.264 20.0759 576.063 19.6317L569.538 19.2144V107.82Z"
fill="currentColor"
/>
<path
d="M650.447 9.30919C650.447 11.8307 651.082 15.246 652.421 19.6032L675.139 88.0835L699.323 19.0758C700.661 15.0609 701.293 11.8772 701.293 9.47894C701.293 8.68045 701.192 7.75 700.978 6.68126C700.758 5.5824 700.482 4.42367 700.15 3.20457L699.693 1.52777H737.721L734.489 3.92116C731.562 6.08952 729.065 8.74792 726.997 11.9047L726.986 11.9229C725.049 14.7762 723.277 18.4348 721.685 22.9257L721.368 23.8341L686.415 126.471H659.872L626.626 23.3041C624.833 17.6995 623.014 13.5959 621.198 10.9199C619.28 8.25981 616.707 5.94699 613.45 3.99243L609.342 1.52777H651.61L651.267 3.1333C650.927 4.71781 650.706 5.82366 650.598 6.47004L650.59 6.51151C650.506 6.93311 650.447 7.83104 650.447 9.30919Z"
fill="currentColor"
/>
<path
d="M748.214 107.99V19.3298C748.214 13.1309 746.534 7.93461 743.227 3.66718L741.568 1.52777H782.092L779.242 6.37285C776.954 10.8475 775.834 15.2188 775.834 19.4995V107.99C775.834 114.197 777.464 119.65 780.699 124.396L782.113 126.471H741.684L743.2 124.368C746.531 119.746 748.214 114.306 748.214 107.99Z"
fill="currentColor"
/>
<path
d="M833.421 107.166C840.304 107.109 846.574 106.606 852.237 105.664C858.228 104.554 864.116 102.612 869.902 99.8301L872.67 98.4993L863.807 126.471H799.18L801.017 124.289C802.707 122.282 803.858 120.079 804.485 117.675L804.491 117.65L804.499 117.624C805.239 115.194 805.63 111.941 805.63 107.82V19.3298C805.63 12.8705 804.046 7.68105 800.977 3.6594L799.351 1.52777H839.231L836.851 5.98799C834.533 11.0743 833.421 15.513 833.421 19.3298V107.166Z"
fill="currentColor"
/>
<path
d="M914.726 107.166C921.609 107.109 927.879 106.606 933.542 105.664C939.533 104.554 945.421 102.612 951.207 99.8301L953.975 98.4993L945.11 126.471H880.485L882.321 124.289C884.011 122.281 885.162 120.079 885.79 117.675L885.796 117.65L885.804 117.624C886.544 115.194 886.935 111.941 886.935 107.82V19.3298C886.935 12.8705 885.351 7.68105 882.282 3.6594L880.654 1.52777H920.535L918.156 5.98799C915.838 11.0744 914.726 15.513 914.726 19.3298V107.166Z"
fill="currentColor"
/>
<path
d="M1029.63 2.59424L1029.42 1.52777H1004.82L1004.61 2.59424L1004.11 5.10554L1003.44 7.60777L1003.44 7.6285C1002.99 9.40377 1002.44 11.3523 1001.77 13.4753C1001.09 15.4974 1000.53 17.1202 1000.09 18.3475L966.638 101.035C962.954 110.078 958.166 117.81 952.282 124.249L950.252 126.471H990.207L989.575 119.308C989.582 114.609 990.349 110.259 991.866 106.25L998.496 89.2744H1030.27L1036.93 108.591L1036.93 108.613C1037.81 111.034 1038.28 113.054 1038.39 114.696L1038.39 114.737L1038.39 114.779C1038.61 116.54 1038.72 118.24 1038.72 119.879V122.257C1038.72 123.177 1038.62 124.031 1038.42 124.823L1038.01 126.471H1077.56L1074.26 121.675C1069.34 114.518 1065.43 106.973 1062.52 99.041L1033.67 18.4201L1030.64 7.8151L1030.14 5.15867L1030.14 5.14182L1029.63 2.59424ZM1026.03 69.9757H1002.67L1015.01 33.5281L1026.03 69.9757Z"
fill="currentColor"
/>
<path
d="M1117.47 53.5784L1145 19.0123C1147.77 15.6063 1149.68 11.1273 1150.67 5.52538L1151.14 1.52777H1196L1187.34 6.52317C1183.74 8.92366 1180.17 12.2745 1176.62 16.6047L1142.09 58.6359L1176.34 108.587L1176.92 109.415C1179.82 113.474 1182.5 116.553 1184.93 118.691C1187.64 120.848 1190.78 122.587 1194.39 123.897L1193.93 126.471H1149.5L1150.03 124.754C1150.44 123.421 1150.65 121.968 1150.65 120.388C1150.65 118.623 1150.34 116.98 1149.73 115.446L1149.72 115.41L1149.7 115.372C1149.19 113.842 1148.25 112.095 1146.83 110.125L1146.81 110.097L1117.47 67.1093V107.82C1117.47 114.522 1119.01 120.02 1122 124.396L1123.41 126.471H1084.04L1085.51 124.383C1087 122.253 1088.06 119.911 1088.7 117.348L1088.71 117.331L1088.71 117.313C1089.46 114.626 1089.85 111.467 1089.85 107.82V19.16C1089.85 13.1694 1088.33 8.00208 1085.33 3.60368L1083.91 1.52777H1123.82L1122.32 3.62571C1119.09 8.1455 1117.47 13.3086 1117.47 19.16V53.5784Z"
fill="currentColor"
/>
</svg>
{:else if variant === "hobevillak"}
<!-- HÕBEVILLAK -->
<svg
class="text-kv-yellow {sizeClasses[size]} {className}"
viewBox="0 0 1036 154"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
role="img"
aria-label="Hõbevillak"
>
<path
d="M101.925 133.966C101.925 140.779 103.568 146.197 106.747 150.331L108.391 152.469H68.3699L69.5344 150.471C70.3226 149.119 70.7717 148.2 70.9517 147.66L70.9815 147.57L71.0243 147.486C71.3341 146.866 71.7677 145.839 72.3315 144.372C72.8568 143.006 73.2899 141.343 73.6191 139.37C73.9519 137.261 74.1132 135.463 74.1132 133.966V98.5635H34.1071V133.966C34.1072 138.873 35.1412 143.435 37.2025 147.668L39.7441 152.469H0L3.25225 147.736C4.20292 145.927 4.94893 143.952 5.48526 141.806C6.02048 139.551 6.2957 136.942 6.29572 133.966V45.2003C6.29569 38.8539 4.71337 33.6485 1.62872 29.4982L0.0531668 27.377H40.0761L38.8416 29.3983C37.6292 31.3831 36.7058 33.2343 36.0613 34.9536C35.4104 36.6903 34.9219 38.4823 34.596 40.3299C34.2666 42.1976 34.1072 43.876 34.1071 45.3702V79.0719H74.1132V45.3702C74.1131 40.8291 73.0339 36.4452 70.8596 32.2032L68.3063 27.377H108.091L106.61 29.4723C103.5 33.8703 101.925 39.0936 101.925 45.2003V133.966Z"
fill="#C0C0C0"
/>
<path <path
fill-rule="evenodd" fill-rule="evenodd"
clip-rule="evenodd" clip-rule="evenodd"
d="M190.051 0L193.369 11V114L190.051 125H253.103L263.532 95C263.532 95 249.632 103 224.658 103V11L227.977 0H190.051Z" d="M143.242 44.0949C131.3 56.158 125.35 71.4703 125.35 89.9231C125.35 95.1747 126.455 102.662 128.613 112.322C130.855 122.238 137.612 131.663 148.623 140.614C159.749 149.542 172.793 154 187.697 154C193.862 154 201.916 152.723 211.818 150.218C222.051 147.629 231.405 140.713 239.908 129.677C248.588 118.445 252.932 105.632 252.932 91.284C252.932 84.542 251.597 76.7005 248.968 67.7809C246.289 58.5758 240.998 50.2778 233.153 42.887C225.673 35.6262 217.85 30.9474 209.682 28.9533L208.891 28.7691C200.687 26.8321 193.784 25.8474 188.206 25.8474C170.308 25.8474 155.289 31.9265 143.242 44.0949ZM198.404 135.868C203.41 135.868 207.695 134.782 211.308 132.661C214.968 130.398 217.419 127.652 218.766 124.44C220.2 121.019 221.425 116.963 222.432 112.261L222.437 112.239C223.542 107.484 224.102 102.033 224.102 95.8754C224.102 87.052 222.761 79.346 220.117 72.7317L220.112 72.7187C217.563 65.9571 214.091 60.5137 209.727 56.3667C205.305 52.163 200.636 49.149 195.725 47.2929C190.877 45.419 186.101 44.488 181.408 44.488C175.815 44.488 170.938 45.6385 166.74 47.9001C162.546 50.1596 159.859 53.0509 158.52 56.5379L158.031 57.9196C156.925 61.1308 156.031 64.339 155.349 67.5448L155.229 68.2363C154.647 71.7424 154.351 75.9628 154.351 80.9103V85.5016C154.351 86.4705 154.412 87.6067 154.536 88.9137L154.682 90.2786C155.686 99.2131 157.912 106.749 161.318 112.909C164.858 118.99 169.089 123.701 173.994 127.084C179.153 130.561 183.568 132.868 187.256 134.062C190.983 135.268 194.698 135.868 198.404 135.868Z"
fill="currentColor" fill="#C0C0C0"
/> />
<path <path
fill-rule="evenodd" fill-rule="evenodd"
clip-rule="evenodd" clip-rule="evenodd"
d="M134.374 0L137.693 11V114L134.374 125H172.3L168.981 114V11L172.3 0H134.374Z" d="M276.256 45.5402C276.256 39.8682 274.896 34.4702 272.163 29.3296L271.125 27.377H309.065L309.103 27.3796L315.221 27.7195L315.27 27.7221L315.318 27.7286C321.559 28.538 327.301 30.1593 332.535 32.6028C337.836 34.9645 341.944 38.4663 344.81 43.1102C347.79 47.6423 349.278 52.7161 349.278 58.2946C349.278 65.8538 347.476 71.7895 343.649 75.8661C340.249 79.4951 337.627 82.0159 335.818 83.3428L335.805 83.3519L335.791 83.3623L331.722 86.188L332.456 86.4747L332.5 86.4928C337.703 88.7408 342.615 92.5021 347.244 97.7125C352.024 102.979 354.377 109.658 354.377 117.641C354.377 123.798 352.658 129.65 349.251 135.174C345.981 140.547 342.019 144.48 337.339 146.881C332.977 149.178 329.066 150.704 325.622 151.418C322.325 152.125 318.202 152.469 313.278 152.469H270.782L273.153 148.018C275.239 142.947 276.256 138.325 276.256 134.136V45.5402ZM317.717 49.4764C320.66 52.8412 322.148 56.9435 322.148 61.8649C322.148 62.8826 321.993 64.321 321.66 66.2111C321.341 67.9156 320.692 70.0977 319.689 72.7745C318.807 75.0311 316.784 77.3341 313.391 79.635C310.577 81.5119 307.477 82.5557 304.067 82.7694V44.6164C304.771 44.5347 305.682 44.488 306.819 44.488C311.19 44.4881 314.793 46.133 317.717 49.4764ZM323.229 101.546C325.259 107.975 326.227 112.805 326.227 116.11C326.227 121.963 324.555 126.548 321.317 129.996C318.193 133.33 313.773 135.322 307.91 135.869C307.401 135.876 306.726 135.935 305.91 136.035C305.482 136.031 304.818 135.977 303.897 135.863V90.6327L306.195 90.7378C316.013 91.9621 321.417 95.7045 323.229 101.546Z"
fill="currentColor" fill="#C0C0C0"
/> />
<path <path
d="M126.862 0L118.519 11L75.283 125H51.5793L8.3437 11L0 0H34.5126L63.4311 90L92.3497 0H126.862Z" d="M444.495 27.377L433.637 51.2369L432.521 50.8905C427.817 49.4339 423.116 48.315 418.417 47.5316L418.403 47.529L418.388 47.5251C414.128 46.7135 409.725 46.2712 405.178 46.1992V79.0719H439.623L426.552 101.909L420.113 99.6948C415.423 98.3707 410.445 97.4183 405.178 96.838V133.821C420.202 133.69 434.69 131.333 448.646 126.751L451.574 125.791L439.025 152.469H371.184L372.685 150.369C375.901 145.864 377.536 140.419 377.536 133.966V45.3702C377.536 39.3832 375.963 34.0905 372.834 29.4489L371.437 27.377H444.495Z"
fill="currentColor" fill="#C0C0C0"
/>
<path
d="M490.038 35.1677C490.038 37.69 490.672 41.1055 492.011 45.4623L514.749 114.035L538.952 44.946C540.291 40.9263 540.921 37.7388 540.921 35.3377C540.921 34.5382 540.821 33.6067 540.608 32.5367C540.388 31.4365 540.112 30.2764 539.779 29.0558L539.321 27.377H577.377L574.144 29.7733C571.215 31.9442 568.716 34.6057 566.646 37.7663L566.635 37.7845C564.697 40.6413 562.924 44.3042 561.33 48.8004L561.013 49.7099L526.033 152.469H499.471L466.2 49.1793C464.408 43.5779 462.591 39.4741 460.777 36.7946L460.41 36.2977C458.532 33.8364 456.075 31.6824 453.013 29.8446L448.903 27.377H491.203L490.858 28.9845C490.518 30.5708 490.298 31.6781 490.19 32.3252L490.182 32.3667C490.098 32.7888 490.038 33.6878 490.038 35.1677Z"
fill="#C0C0C0"
/>
<path
d="M587.879 133.966V45.2003C587.879 38.994 586.198 33.7915 582.888 29.519L581.228 27.377H621.783L618.933 32.224C616.642 36.7055 615.52 41.083 615.52 45.3702V133.966C615.52 140.18 617.149 145.64 620.388 150.392L621.804 152.469H581.344L582.861 150.363C586.195 145.737 587.879 140.29 587.879 133.966Z"
fill="#C0C0C0"
/>
<path
d="M673.15 133.141C680.038 133.084 686.313 132.581 691.98 131.637C697.976 130.526 703.868 128.582 709.658 125.797L712.428 124.464L703.557 152.469H638.883L640.722 150.284C642.413 148.275 643.564 146.07 644.192 143.663L644.198 143.638L644.206 143.612C644.947 141.178 645.338 137.922 645.338 133.796V45.2003C645.338 38.7333 643.753 33.5376 640.682 29.5112L639.053 27.377H678.963L676.582 31.8426C674.263 36.935 673.15 41.3789 673.15 45.2003V133.141Z"
fill="#C0C0C0"
/>
<path
d="M754.514 133.141C761.403 133.084 767.679 132.581 773.346 131.637C779.342 130.526 785.234 128.582 791.024 125.797L793.794 124.464L784.923 152.469H720.249L722.086 150.284C723.778 148.275 724.929 146.07 725.557 143.663L725.564 143.638L725.572 143.612C726.312 141.178 726.704 137.922 726.704 133.796V45.2003C726.704 38.7333 725.119 33.5376 722.048 29.5112L720.419 27.377H760.329L757.947 31.8426C755.627 36.9348 754.514 41.379 754.514 45.2003V133.141Z"
fill="#C0C0C0"
/>
<path
d="M870.017 30.9954L869.295 27.377H844.681L844.469 28.4448L843.964 30.9591L843.298 33.4643L843.292 33.485C842.848 35.2624 842.291 37.2133 841.621 39.3388C840.946 41.3635 840.385 42.988 839.939 44.2168L806.467 127.003C802.781 136.057 797.988 143.797 792.099 150.244L790.067 152.469H830.054L829.421 145.297C829.428 140.593 830.196 136.238 831.714 132.224L838.349 115.228H870.148L876.808 134.568L876.816 134.59C877.697 137.014 878.166 139.037 878.268 140.68L878.271 140.721L878.276 140.763C878.496 142.526 878.606 144.228 878.606 145.869V148.25C878.606 149.171 878.504 150.027 878.306 150.819L877.892 152.469H917.472L914.172 147.668C909.247 140.501 905.332 132.948 902.426 125.006L873.546 44.2726L870.519 33.6719L870.021 31.0122L870.017 30.9954ZM865.906 95.9065H842.522L854.875 59.4155L865.906 95.9065Z"
fill="#C0C0C0"
/>
<path
d="M957.414 79.4884L984.961 44.8824C987.731 41.4723 989.647 36.9881 990.638 31.3794L991.108 27.377H1036L1027.32 32.3836C1023.73 34.7866 1020.15 38.1394 1016.6 42.4719L982.05 84.5533L1016.33 134.569C1019.46 139.042 1022.32 142.397 1024.93 144.68C1027.63 146.84 1030.78 148.581 1034.38 149.893L1033.93 152.469H989.471L990 150.75C990.411 149.415 990.621 147.961 990.621 146.379C990.621 144.612 990.311 142.967 989.697 141.431L989.681 141.395L989.67 141.357C989.159 139.825 988.217 138.076 986.794 136.104L986.773 136.076L957.414 93.0367V133.796C957.414 140.506 958.951 146.011 961.941 150.392L963.36 152.469H923.961L925.423 150.379C926.915 148.247 927.982 145.901 928.623 143.336L928.626 143.319L928.632 143.301C929.385 140.61 929.773 137.447 929.773 133.796V45.0303C929.773 39.0326 928.25 33.859 925.245 29.4554L923.827 27.377H963.764L962.265 29.4775C959.035 34.0026 957.414 39.1719 957.414 45.0303V79.4884Z"
fill="#C0C0C0"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M179.198 0C172.667 4.46269e-05 167.752 2.15746 164.831 6.72036C162.089 10.8949 160.85 15.2103 161.219 19.633L161.32 20.8513H177.297V19.5228C177.297 18.3576 177.634 17.9146 177.954 17.7116C178.368 17.4498 179.227 17.2784 180.815 17.5962L186.066 18.6133L193.038 20.3155L193.087 20.3232C193.713 20.4275 194.69 20.644 196.043 20.9823L196.201 21.0212H198.915C204.167 21.0211 208.58 19.5205 212.037 16.4324C215.504 13.3354 217.239 9.34163 217.239 4.55895C217.239 3.3923 217.182 2.40252 217.051 1.61911L216.866 0.509865H201.331L201.505 1.99276C201.586 2.68795 201.413 2.91647 201.301 3.01638C201.122 3.17695 200.655 3.4004 199.595 3.4004C198.256 3.4004 196.668 3.19427 194.817 2.7595L192.778 2.24963L192.711 2.23277L192.644 2.22369L191.504 2.06022L185.448 0.713552C183.662 0.267473 182.271 0 181.408 0H179.198Z"
fill="#C0C0C0"
/> />
</svg> </svg>
{/if} {/if}

@ -13,9 +13,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
fill-rule="evenodd" d="M41.1692 53.4247L69.1227 18.3469L69.6477 17.6762C72.1759 14.3139 73.9344 10.0017 74.8782 4.68266L75.3597 0.603516H120.912L112.11 5.67812C108.463 8.11385 104.833 11.5122 101.228 15.9036L66.1687 58.5571L100.949 109.253C104.122 113.783 107.03 117.183 109.669 119.496C112.414 121.688 115.611 123.454 119.273 124.785L118.812 127.396H73.6992L74.2347 125.654C74.6513 124.301 74.865 122.827 74.865 121.224C74.865 119.433 74.5497 117.765 73.9268 116.208L73.9124 116.171L73.8992 116.133C73.381 114.58 72.4248 112.807 70.9807 110.809L70.9702 110.794L70.9609 110.78L41.1692 67.1546V108.469C41.1693 115.27 42.7295 120.85 45.7641 125.291L47.2023 127.396H7.22473L8.70898 125.278C10.2229 123.116 11.3047 120.739 11.9551 118.139L11.9591 118.122L11.9644 118.103C12.7285 115.376 13.121 112.17 13.121 108.469V18.4968C13.121 12.4177 11.5774 7.17358 8.5274 2.71015L7.08789 0.603516H47.6141L46.0917 2.73251C42.8134 7.31921 41.1692 12.5587 41.1692 18.4968V53.4247Z"
clip-rule="evenodd"
d="M8.05957 1.5L11.3781 12.5V115.5L8.05957 126.5H45.9855L42.667 115.5V73.6719L82.0151 126.5H119.941L67.3188 59L115.674 1.50006H77.7485L42.667 50.5001V12.5L45.9855 1.50006L8.05957 1.5Z"
/> />
</svg> </svg>

@ -10,7 +10,6 @@
40, 40,
50 50
], ],
"basePointValue": 10,
"categoriesPerRound": 6, "categoriesPerRound": 6,
"questionsPerCategory": 5, "questionsPerCategory": 5,
"dailyDoublesPerRound": [ "dailyDoublesPerRound": [
@ -22,7 +21,8 @@
"allowNegativeScores": true, "allowNegativeScores": true,
"maxTeams": 6, "maxTeams": 6,
"defaultTimerSeconds": 5, "defaultTimerSeconds": 5,
"answerRevealSeconds": 5 "answerRevealSeconds": 5,
"basePointValue": 10
}, },
"teams": [ "teams": [
{ {

@ -28,8 +28,9 @@ export interface GameSessionState {
} | null; } | null;
showAnswer: boolean; showAnswer: boolean;
wrongTeamIds: string[]; // Teams that answered wrong for current question wrongTeamIds: string[]; // Teams that answered wrong for current question
lastAnsweredTeamId: string | null; // Track who answered last lastAnsweredTeamId: string | null; // Track who answered last (for current question feedback)
lastAnswerCorrect: boolean | null; // Was it correct or wrong 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 // Daily Double
dailyDoubleWager: number | null; dailyDoubleWager: number | null;
@ -37,7 +38,6 @@ export interface GameSessionState {
// Final Round // Final Round
finalCategoryRevealed: boolean; // Has the final category been revealed finalCategoryRevealed: boolean; // Has the final category been revealed
finalWagers: Record<string, number>; finalWagers: Record<string, number>;
finalAnswers: Record<string, string>;
finalRevealed: string[]; // Team IDs that have been revealed finalRevealed: string[]; // Team IDs that have been revealed
// Timer // Timer
@ -146,10 +146,10 @@ class GameSessionStore {
wrongTeamIds: [], wrongTeamIds: [],
lastAnsweredTeamId: null, lastAnsweredTeamId: null,
lastAnswerCorrect: null, lastAnswerCorrect: null,
lastCorrectTeamId: null,
dailyDoubleWager: null, dailyDoubleWager: null,
finalCategoryRevealed: false, finalCategoryRevealed: false,
finalWagers: {}, finalWagers: {},
finalAnswers: {},
finalRevealed: [], finalRevealed: [],
timerRunning: false, timerRunning: false,
timerSeconds: 0, timerSeconds: 0,
@ -274,12 +274,6 @@ class GameSessionStore {
this.persist(); this.persist();
} }
toggleAnswer() {
if (!this.state) return;
this.state.showAnswer = !this.state.showAnswer;
this.persist();
}
revealAnswer() { revealAnswer() {
if (!this.state) return; if (!this.state) return;
this.state.showAnswer = true; this.state.showAnswer = true;
@ -300,9 +294,11 @@ class GameSessionStore {
team.score += points; team.score += points;
} }
// Track last answer // Track last answer (current question feedback)
this.state.lastAnsweredTeamId = teamId; this.state.lastAnsweredTeamId = teamId;
this.state.lastAnswerCorrect = true; this.state.lastAnswerCorrect = true;
// Track last correct answer (persists for clue selection)
this.state.lastCorrectTeamId = teamId;
// Show answer and start reveal countdown // Show answer and start reveal countdown
this.state.showAnswer = true; this.state.showAnswer = true;
@ -387,22 +383,12 @@ class GameSessionStore {
wager: this.state.dailyDoubleWager ?? undefined, wager: this.state.dailyDoubleWager ?? undefined,
}); });
// Mark as revealed // Mark as revealed and increment counter
question.isRevealed = true; question.isRevealed = true;
// Increment questions answered counter
this.state.questionsAnswered++; this.state.questionsAnswered++;
// Reset state // Reset question state using consolidated helper
this.state.currentQuestion = null; this.resetQuestionState();
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";
// Check if round is complete // Check if round is complete
this.checkRoundComplete(); this.checkRoundComplete();
@ -420,7 +406,7 @@ class GameSessionStore {
if (allRevealed) { if (allRevealed) {
// Move to next round or final // Move to next round or final
if (this.state.currentRoundIndex < this.state.rounds.length - 1) { if (this.state.currentRoundIndex < this.state.rounds.length - 1) {
this.state.currentRoundIndex++; this.transitionToNextRound();
} else if (this.state.settings.enableFinalRound && this.state.finalRound) { } else if (this.state.settings.enableFinalRound && this.state.finalRound) {
this.state.phase = "final-category"; this.state.phase = "final-category";
} else { } 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 // Round Management
// ============================================ // ============================================
@ -470,12 +447,7 @@ class GameSessionStore {
nextRound() { nextRound() {
if (!this.state) return; if (!this.state) return;
if (this.state.currentRoundIndex < this.state.rounds.length - 1) { if (this.state.currentRoundIndex < this.state.rounds.length - 1) {
this.state.currentRoundIndex++; this.transitionToNextRound();
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.persist(); this.persist();
} }
} }
@ -521,13 +493,6 @@ class GameSessionStore {
this.persist(); 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 // Select a team to judge their final answer
selectFinalTeam(teamId: string) { selectFinalTeam(teamId: string) {
if (!this.state) return; if (!this.state) return;
@ -555,6 +520,11 @@ class GameSessionStore {
this.state.finalRevealed.push(teamId); this.state.finalRevealed.push(teamId);
this.state.activeTeamId = null; 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(); this.persist();
} }
@ -590,7 +560,7 @@ class GameSessionStore {
this.persist(); this.persist();
if (this.state.revealCountdown === 0) { if (this.state.revealCountdown === 0) {
// Return to board // Mark question as revealed
if (this.state.currentQuestion) { if (this.state.currentQuestion) {
const { roundIndex, categoryIndex, questionIndex } = this.state.currentQuestion; const { roundIndex, categoryIndex, questionIndex } = this.state.currentQuestion;
const question = this.state.rounds[roundIndex]?.categories[categoryIndex]?.questions[questionIndex]; const question = this.state.rounds[roundIndex]?.categories[categoryIndex]?.questions[questionIndex];
@ -600,16 +570,8 @@ class GameSessionStore {
} }
} }
// Reset state and return to board // Reset state using consolidated helper
this.state.currentQuestion = null; this.resetQuestionState();
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";
this.checkRoundComplete(); this.checkRoundComplete();
this.persist(); 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 // Call this from moderator view only
enableTimerControl() { enableTimerControl() {
this.startInternalTimer(); 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 { get currentRound(): Round | null {

@ -15,7 +15,7 @@ const localStorageMock = (() => {
}; };
})(); })();
Object.defineProperty(global, 'localStorage', { value: localStorageMock }); Object.defineProperty(globalThis, 'localStorage', { value: localStorageMock });
// Import after mocking localStorage // Import after mocking localStorage
import { import {

@ -122,14 +122,8 @@ export function duplicateKuldvillakGame(gameId: string): KuldvillakGame | null {
// Reset game state // Reset game state
duplicate.state = { duplicate.state = {
phase: 'lobby', phase: 'intro',
currentRoundIndex: 0, currentRoundIndex: 0
currentQuestionId: null,
currentCategoryId: null,
activeTeamId: null,
dailyDoubleWager: null,
finalWagers: {},
finalAnswers: {}
}; };
// Reset revealed questions and scores // Reset revealed questions and scores

@ -47,22 +47,13 @@ describe('Kuldvillak Types', () => {
}); });
describe('DEFAULT_STATE', () => { describe('DEFAULT_STATE', () => {
it('should start in lobby phase', () => { it('should start in intro phase', () => {
expect(DEFAULT_STATE.phase).toBe('lobby'); expect(DEFAULT_STATE.phase).toBe('intro');
}); });
it('should start at round index 0', () => { it('should start at round index 0', () => {
expect(DEFAULT_STATE.currentRoundIndex).toBe(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', () => { describe('Type Validation Helpers', () => {
@ -124,7 +115,6 @@ describe('Kuldvillak Types', () => {
it('should validate game phases', () => { it('should validate game phases', () => {
const validPhases = [ const validPhases = [
'lobby',
'intro', 'intro',
'intro-categories', 'intro-categories',
'board', 'board',
@ -132,9 +122,7 @@ describe('Kuldvillak Types', () => {
'daily-double', 'daily-double',
'final-intro', 'final-intro',
'final-category', 'final-category',
'final-wager',
'final-question', 'final-question',
'final-reveal',
'final-scores', 'final-scores',
'finished', 'finished',
]; ];

@ -44,20 +44,16 @@ export interface Team {
/** Current game phase */ /** Current game phase */
export type GamePhase = export type GamePhase =
| 'intro' // Show Kuldvillak home screen (round start) | 'intro' // Show round intro screen (Villak/Topeltvillak)
| 'intro-categories' // Animating category introductions | 'intro-categories' // Animating category introductions
| 'lobby' | 'board' // Main game board with questions
| 'board' | 'question' // Displaying a question
| 'question' | 'daily-double' // Daily double wager selection
| 'answer'
| 'daily-double'
| 'final-intro' // Final round intro (Kuldvillak screen) | 'final-intro' // Final round intro (Kuldvillak screen)
| 'final-category' // Reveal final round category | 'final-category' // Reveal final round category
| 'final-wagers' // Collect wagers from each team | 'final-question' // Final round question display
| 'final-question' | 'final-scores' // Final scores display
| 'final-reveal' | 'finished'; // Game complete
| 'final-scores'
| 'finished';
/** Result of a question for tracking */ /** Result of a question for tracking */
export interface QuestionResult { export interface QuestionResult {
@ -70,27 +66,17 @@ export interface QuestionResult {
wager?: number; // DD wager if applicable wager?: number; // DD wager if applicable
} }
/** Current state during gameplay */ /** Point value preset types
export interface GameState { * - round1: Base points (10-50) multiplied by round number
phase: GamePhase; * - custom: User-defined point values
currentRoundIndex: number; */
currentQuestionId: string | null; export type PointValuePreset = 'round1' | 'custom';
currentCategoryId: string | null;
activeTeamId: string | null;
dailyDoubleWager: number | null;
finalWagers: Record<string, number>;
finalAnswers: Record<string, string>;
}
/** Point value preset types */
export type PointValuePreset = 'round1' | 'round2' | 'custom' | 'multiplier';
/** Configurable game settings */ /** Configurable game settings */
export interface GameSettings { export interface GameSettings {
numberOfRounds: 1 | 2; numberOfRounds: 1 | 2;
pointValuePreset: PointValuePreset; pointValuePreset: PointValuePreset;
pointValues: number[]; pointValues: number[];
basePointValue: number; // For multiplier preset (e.g., 100 → 100,200,300,400,500)
categoriesPerRound: number; categoriesPerRound: number;
questionsPerCategory: number; questionsPerCategory: number;
dailyDoublesPerRound: number[]; dailyDoublesPerRound: number[];
@ -102,11 +88,11 @@ export interface GameSettings {
answerRevealSeconds: number; answerRevealSeconds: number;
} }
/** Point value presets */ /** Minimal state stored with saved games (not used during gameplay) */
export const POINT_PRESETS = { export interface SavedGameState {
round1: [100, 200, 300, 400, 500], phase: GamePhase;
round2: [200, 400, 600, 800, 1000] currentRoundIndex: number;
} as const; }
/** Complete game configuration (saveable/loadable) */ /** Complete game configuration (saveable/loadable) */
export interface KuldvillakGame { export interface KuldvillakGame {
@ -118,7 +104,7 @@ export interface KuldvillakGame {
teams: Team[]; teams: Team[];
rounds: Round[]; rounds: Round[];
finalRound: FinalRound | null; finalRound: FinalRound | null;
state: GameState; state: SavedGameState;
} }
/** Default settings for new games */ /** Default settings for new games */
@ -126,7 +112,6 @@ export const DEFAULT_SETTINGS: GameSettings = {
numberOfRounds: 2, numberOfRounds: 2,
pointValuePreset: 'round1', pointValuePreset: 'round1',
pointValues: [10, 20, 30, 40, 50], pointValues: [10, 20, 30, 40, 50],
basePointValue: 10,
categoriesPerRound: 6, categoriesPerRound: 6,
questionsPerCategory: 5, questionsPerCategory: 5,
dailyDoublesPerRound: [1, 2], dailyDoublesPerRound: [1, 2],
@ -138,16 +123,10 @@ export const DEFAULT_SETTINGS: GameSettings = {
answerRevealSeconds: 5 answerRevealSeconds: 5
}; };
/** Default initial game state */ /** Default initial game state for saved games */
export const DEFAULT_STATE: GameState = { export const DEFAULT_STATE: SavedGameState = {
phase: 'lobby', phase: 'intro',
currentRoundIndex: 0, currentRoundIndex: 0
currentQuestionId: null,
currentCategoryId: null,
activeTeamId: null,
dailyDoubleWager: null,
finalWagers: {},
finalAnswers: {}
}; };
// ============================================ // ============================================
@ -163,6 +142,3 @@ export interface GameMetadata {
teamCount: number; teamCount: number;
roundCount: number; roundCount: number;
} }
/** View mode for dual-screen setup */
export type ViewMode = 'projector' | 'moderator';

@ -32,7 +32,7 @@
</script> </script>
<svelte:head> <svelte:head>
<title>Kuldvillak - Ultimate Gaming</title> <title>{m.game_kuldvillak()} - {m.app_title()}</title>
<link rel="icon" href="/kuldvillak_favicon.svg" type="image/svg+xml" /> <link rel="icon" href="/kuldvillak_favicon.svg" type="image/svg+xml" />
</svelte:head> </svelte:head>

@ -217,14 +217,8 @@
const base = [10, 20, 30, 40, 50]; const base = [10, 20, 30, 40, 50];
const multiplier = roundIndex + 1; const multiplier = roundIndex + 1;
if (preset === "round1") return base.map((v) => v * multiplier); if (preset === "round1") return base.map((v) => v * multiplier);
if (preset === "round2") return base.map((v) => v * 2 * multiplier); // Custom preset uses user-defined point values
if (preset === "multiplier") return settings.pointValues.map((v) => v * 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;
} }
function updatePreset(preset: PointValuePreset) { function updatePreset(preset: PointValuePreset) {
@ -475,7 +469,7 @@
</script> </script>
<svelte:head> <svelte:head>
<title>{m.kv_edit_title()} - Kuldvillak</title> <title>{m.kv_edit_title()} - {m.game_kuldvillak()}</title>
<link rel="icon" href="/kuldvillak_favicon.svg" type="image/svg+xml" /> <link rel="icon" href="/kuldvillak_favicon.svg" type="image/svg+xml" />
</svelte:head> </svelte:head>
@ -790,7 +784,7 @@
onclick={() => onclick={() =>
(settings.allowNegativeScores = (settings.allowNegativeScores =
!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-kv-yellow'
: 'bg-white'}" : 'bg-white'}"
> >
@ -798,6 +792,10 @@
<span class="text-black text-lg font-bold" <span class="text-black text-lg font-bold"
>✓</span >✓</span
> >
{:else}
<span class="text-black text-lg font-bold"
>✗</span
>
{/if} {/if}
</button> </button>
</div> </div>
@ -900,10 +898,10 @@
onclick={() => openQuestion(ri, ci, qi)} onclick={() => openQuestion(ri, ci, qi)}
class="bg-kv-blue flex items-center justify-center cursor-pointer border-none transition-opacity relative class="bg-kv-blue flex items-center justify-center cursor-pointer border-none transition-opacity relative
{q.question.trim() ? 'opacity-100' : 'opacity-50'} {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"
> >
<span <span
class="font-kv-price text-kv-yellow text-4xl kv-shadow-price" class="font-kv-price text-kv-yellow text-4xl kv-shadow-price kv-shadow-text"
>{q.points}</span >{q.points}</span
> >
</button> </button>
@ -990,12 +988,14 @@
<button <button
onclick={toggleDailyDouble} onclick={toggleDailyDouble}
disabled={!q.isDailyDouble && currentDD >= maxDD} disabled={!q.isDailyDouble && currentDD >= maxDD}
class="w-8 h-8 rounded-sm cursor-pointer border-none p-0 disabled:opacity-50 flex items-center justify-center {q.isDailyDouble class="w-8 h-8 rounded-sm cursor-pointer p-0 disabled:opacity-50 flex items-center justify-center border-4 border-black {q.isDailyDouble
? 'bg-kv-yellow' ? 'bg-kv-yellow'
: 'bg-white'}" : 'bg-white'}"
> >
{#if q.isDailyDouble} {#if q.isDailyDouble}
<span class="text-black text-lg font-bold"></span> <span class="text-black text-lg font-bold"></span>
{:else}
<span class="text-black text-lg font-bold"></span>
{/if} {/if}
</button> </button>
<span <span
@ -1096,12 +1096,14 @@
onclick={() => onclick={() =>
(settings.enableFinalRound = (settings.enableFinalRound =
!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-kv-yellow'
: 'bg-white'}" : 'bg-white'}"
> >
{#if settings.enableFinalRound} {#if settings.enableFinalRound}
<span class="text-black text-lg font-bold"></span> <span class="text-black text-lg font-bold"></span>
{:else}
<span class="text-black text-lg font-bold"></span>
{/if} {/if}
</button> </button>
<span <span

@ -16,13 +16,15 @@
<svelte:head> <svelte:head>
<link rel="icon" href={faviconKuldvillak} /> <link rel="icon" href={faviconKuldvillak} />
<title>{gameSession.state?.name ?? "Play"} - Kuldvillak</title> <title
>{gameSession.state?.name ?? m.kv_play_round()} - {m.game_kuldvillak()}</title
>
</svelte:head> </svelte:head>
{#if !gameSession.state} {#if !gameSession.state}
{#if view === "projector"} {#if view === "projector"}
<!-- Projector Loading Screen --> <!-- Projector Loading Screen -->
<div class="h-screen w-screen bg-kv-black p-4 md:p-8"> <div class="h-screen w-screen bg-kv-black">
<div <div
class="w-full h-full bg-kv-blue flex flex-col items-center justify-center gap-4 md:gap-8 overflow-hidden px-4" class="w-full h-full bg-kv-blue flex flex-col items-center justify-center gap-4 md:gap-8 overflow-hidden px-4"
> >
@ -46,24 +48,25 @@
</div> </div>
{:else} {:else}
<!-- Moderator Loading Screen --> <!-- Moderator Loading Screen -->
<div <div class="h-screen w-screen bg-kv-black">
class="h-screen w-screen flex items-center justify-center bg-kv-black" <div
> class="w-full h-full bg-kv-blue flex flex-col items-center justify-center gap-4 md:gap-8 overflow-hidden px-4"
<div class="text-center"> >
<KvSpinner class="w-16 h-16 mx-auto mb-4" /> <KvSpinner class="w-20 h-20 md:w-32 md:h-32" />
<p <p
class="font-kv-body text-kv-white text-2xl mb-4 uppercase kv-shadow-text" class="font-kv-body text-2xl md:text-5xl text-kv-white uppercase kv-shadow-text text-center"
> >
{m.kv_play_loading()} {m.kv_play_loading()}
</p> </p>
<p class="font-kv-body text-gray-400 text-sm mb-4 uppercase"> <p
class="font-kv-body text-sm md:text-xl text-kv-white uppercase kv-shadow-text text-center"
>
{m.kv_play_loading_hint()} {m.kv_play_loading_hint()}
</p> </p>
<a <a href="/kuldvillak/edit">
href="/kuldvillak/edit" <KvButtonSecondary>
class="text-kv-yellow underline uppercase font-kv-body" {m.kv_play_go_to_editor()}
> </KvButtonSecondary>
{m.kv_play_go_to_editor()}
</a> </a>
</div> </div>
</div> </div>

@ -76,19 +76,6 @@
let currentRound = $derived(gameSession.currentRound); let currentRound = $derived(gameSession.currentRound);
let questionData = $derived(gameSession.currentQuestionData); 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) { function selectQuestion(catIndex: number, qIndex: number) {
if (!session) return; if (!session) return;
gameSession.selectQuestion(session.currentRoundIndex, catIndex, qIndex); gameSession.selectQuestion(session.currentRoundIndex, catIndex, qIndex);
@ -121,17 +108,7 @@
gameSession.adjustScore(teamId, amount); gameSession.adjustScore(teamId, amount);
} }
function handleTeamClick(teamId: string) { // Count daily doubles in a round (total)
if (
session?.phase === "question" &&
!session.showAnswer &&
!isTeamWrong(teamId)
) {
gameSession.setActiveTeam(teamId);
}
}
// Count daily doubles in a round
function countDailyDoubles(roundIndex: number) { function countDailyDoubles(roundIndex: number) {
if (!session) return 0; if (!session) return 0;
const round = session.rounds[roundIndex]; const round = session.rounds[roundIndex];
@ -143,6 +120,39 @@
}, 0) ?? 0 }, 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,
);
</script> </script>
{#if session} {#if session}
@ -166,16 +176,15 @@
{:else} {:else}
<span class="text-xl md:text-[28px] kv-shadow-text"> <span class="text-xl md:text-[28px] kv-shadow-text">
{session.currentRoundIndex === 0 {session.currentRoundIndex === 0
? "Villak" ? m.kv_edit_r1()
: "Topeltvillak"} : m.kv_edit_r2()}
</span> </span>
<span class="text-base md:text-xl text-kv-yellow"> <span class="text-base md:text-xl text-kv-yellow">
({m.kv_edit_dd_short()} ({m.kv_edit_dd_short()}
{countDailyDoubles( {countRemainingDailyDoubles(
session.currentRoundIndex, session.currentRoundIndex,
)}/{session.settings.dailyDoublesPerRound[ )}/{countDailyDoubles(session.currentRoundIndex)},
session.currentRoundIndex {m.kv_play_question_short()}: {answeredInRound}/{totalQuestionsInRound})
] ?? 1})
</span> </span>
{/if} {/if}
</div> </div>
@ -213,23 +222,17 @@
</div> </div>
<!-- Last Answer & Score Adjustment --> <!-- Last Answer & Score Adjustment -->
<div <div class="flex flex-wrap items-center gap-16">
class="flex flex-wrap items-center justify-between gap-8 w-full" {#if session.lastCorrectTeamId}
> {@const lastCorrectTeam = session.teams.find(
{#if session.lastAnsweredTeamId} (t) => t.id === session.lastCorrectTeamId,
{@const lastTeam = session.teams.find(
(t) => t.id === session.lastAnsweredTeamId,
)} )}
<span <span
class="font-kv-body text-lg md:text-xl text-kv-white uppercase kv-shadow-text" class="font-kv-body text-lg md:text-xl text-kv-white uppercase kv-shadow-text"
> >
{m.kv_play_last_answer()}: {m.kv_play_last_answer()}:
<span <span class="text-kv-yellow">
class={session.lastAnswerCorrect {lastCorrectTeam?.name}
? "text-kv-yellow"
: "text-kv-red"}
>
{lastTeam?.name}
</span> </span>
</span> </span>
{:else} {:else}
@ -251,7 +254,7 @@
<button <button
onclick={openSettings} onclick={openSettings}
class="text-kv-yellow hover:opacity-80 transition-opacity cursor-pointer bg-transparent border-none p-0" class="text-kv-yellow hover:opacity-80 transition-opacity cursor-pointer bg-transparent border-none p-0"
aria-label="Settings" aria-label={m.kv_settings()}
> >
<svg <svg
viewBox="0 0 48 48" viewBox="0 0 48 48"
@ -323,29 +326,36 @@
<span <span
class="font-kv-body text-4xl md:text-6xl text-kv-yellow uppercase kv-shadow-text" class="font-kv-body text-4xl md:text-6xl text-kv-yellow uppercase kv-shadow-text"
> >
Kuldvillak {session.currentRoundIndex === 0
? m.kv_edit_r1()
: m.kv_edit_r2()}
</span> </span>
<div class="flex gap-4"> <div class="flex gap-4">
<KvButtonPrimary {#if !session.categoriesIntroduced}
onclick={() => gameSession.startCategoryIntro()} <KvButtonPrimary
> onclick={() =>
{m.kv_play_introduce_categories()} gameSession.startCategoryIntro()}
</KvButtonPrimary> >
{m.kv_play_introduce_categories()}
</KvButtonPrimary>
{/if}
<KvButtonSecondary <KvButtonSecondary
onclick={() => gameSession.startBoard()} onclick={() => gameSession.startBoard()}
> >
{m.kv_play_skip_to_game()} {session.categoriesIntroduced
? m.kv_play_start_game()
: m.kv_play_skip_to_game()}
</KvButtonSecondary> </KvButtonSecondary>
</div> </div>
{:else if session.phase === "intro-categories"} {:else if session.phase === "intro-categories"}
{#if !introDelayComplete && session.introCategoryIndex === 0} {#if !introDelayComplete && session.introCategoryIndex === 0}
<!-- Initial 3s delay - show Villak/Topeltvillak with countdown --> <!-- Initial 3s delay - show round name with countdown -->
<span <span
class="font-kv-body text-4xl md:text-6xl text-kv-yellow uppercase kv-shadow-text" class="font-kv-body text-4xl md:text-6xl text-kv-yellow uppercase kv-shadow-text"
> >
{session.currentRoundIndex === 0 {session.currentRoundIndex === 0
? "Villak" ? m.kv_edit_r1()
: "Topeltvillak"} : m.kv_edit_r2()}
</span> </span>
<span <span
class="font-kv-body text-lg md:text-xl text-kv-white uppercase kv-shadow-text" class="font-kv-body text-lg md:text-xl text-kv-white uppercase kv-shadow-text"
@ -464,7 +474,7 @@
<div <div
class="font-kv-body text-lg text-kv-yellow uppercase kv-shadow-text" class="font-kv-body text-lg text-kv-yellow uppercase kv-shadow-text"
> >
Min: 5€ — Max: {maxWager} {m.kv_play_wager_range({ min: 5, max: maxWager })}
</div> </div>
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<span <span
@ -775,21 +785,13 @@
</div> </div>
</div> </div>
<!-- After all judged: Reveal Answer, then Show Scores --> <!-- After all judged: Show Scores button (answer auto-reveals) -->
{#if session.finalRevealed.length === session.teams.length} {#if session.finalRevealed.length === session.teams.length}
{#if session.showAnswer} <KvButtonPrimary
<KvButtonPrimary onclick={() => gameSession.showFinalScores()}
onclick={() => gameSession.showFinalScores()} >
> {m.kv_play_show_scores()}
{m.kv_play_show_scores()} </KvButtonPrimary>
</KvButtonPrimary>
{:else}
<KvButtonSecondary
onclick={() => gameSession.revealFinalAnswer()}
>
{m.kv_play_reveal_answer()}
</KvButtonSecondary>
{/if}
{/if} {/if}
</div> </div>
{:else if session.phase === "final-scores"} {:else if session.phase === "final-scores"}
@ -800,10 +802,10 @@
{@const bottomRowCount = count > 3 ? count - 3 : 0} {@const bottomRowCount = count > 3 ? count - 3 : 0}
{@const topRow = sorted.slice(0, topRowCount)} {@const topRow = sorted.slice(0, topRowCount)}
{@const bottomRow = sorted.slice(topRowCount)} {@const bottomRow = sorted.slice(topRowCount)}
<div class="flex-1 flex flex-col bg-kv-black p-4 gap-4"> <div class="flex-1 flex flex-col bg-kv-black gap-4">
<!-- Top row --> <!-- Top row -->
<div <div
class="flex-1 grid gap-2" class="flex-1 grid gap-2 bg-kv-black"
style="grid-template-columns: repeat({topRowCount}, 1fr);" style="grid-template-columns: repeat({topRowCount}, 1fr);"
> >
{#each topRow as team, i} {#each topRow as team, i}
@ -834,7 +836,7 @@
<!-- Bottom row (if more than 3 players) --> <!-- Bottom row (if more than 3 players) -->
{#if bottomRowCount > 0} {#if bottomRowCount > 0}
<div <div
class="flex-1 grid gap-2" class="flex-1 grid gap-2 bg-kv-black"
style="grid-template-columns: repeat({bottomRowCount}, 1fr);" style="grid-template-columns: repeat({bottomRowCount}, 1fr);"
> >
{#each bottomRow as team, i} {#each bottomRow as team, i}
@ -863,8 +865,10 @@
{/each} {/each}
</div> </div>
{/if} {/if}
<div class="flex justify-center"> <div class="flex justify-center p-4 bg-kv-blue">
<KvButtonPrimary onclick={() => gameSession.endGame()}> <KvButtonPrimary
onclick={() => (showEndGameConfirm = true)}
>
{m.kv_play_end_game()} {m.kv_play_end_game()}
</KvButtonPrimary> </KvButtonPrimary>
</div> </div>

@ -193,66 +193,71 @@
prevBoardPhase !== "board" && prevBoardPhase !== "board" &&
!alreadyRevealed !alreadyRevealed
) { ) {
// First time entering board - do the reveal animation // First time entering board - wait 3 seconds showing logos, then reveal prices
boardRevealPhase = "revealing"; boardRevealPhase = "revealing";
revealedPrices = new Set(); revealedPrices = new Set();
// Custom reveal order: [ci, qi, order] - order determines when cell appears // 3 second delay before starting the price reveal animation
// Grid: 6 columns (C1-C6) × 5 rows (R1-R5) const BOARD_REVEAL_DELAY = 1000;
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],
];
// Sort by order and schedule reveals setTimeout(() => {
const sorted = [...revealOrder].sort((a, b) => a[2] - b[2]); // Custom reveal order: [ci, qi, order] - order determines when cell appears
sorted.forEach(([ci, qi, _order], idx) => { // Grid: 6 columns (C1-C6) × 5 rows (R1-R5)
const key = `${ci}-${qi}`; const revealOrder: [number, number, number][] = [
setTimeout(() => { // Row 1 (qi=0): 01 02 15 11 13 08
revealedPrices = new Set([...revealedPrices, key]); [0, 0, 1],
}, idx * 50); // 50ms between each cell [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( // Sort by order and schedule reveals
() => { const sorted = [...revealOrder].sort((a, b) => a[2] - b[2]);
boardRevealPhase = "revealed"; sorted.forEach(([ci, qi, _order], idx) => {
gameSession.markBoardRevealed(); // Mark as revealed so it won't animate again const key = `${ci}-${qi}`;
}, setTimeout(() => {
sorted.length * 50 + 100, 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) { } else if (currentPhase === "board" && alreadyRevealed) {
// Already revealed - show all prices immediately // Already revealed - show all prices immediately
boardRevealPhase = "revealed"; boardRevealPhase = "revealed";
@ -305,7 +310,7 @@
{#if session.categoriesIntroduced} {#if session.categoriesIntroduced}
<KvGameLogo <KvGameLogo
variant={roundVariant} variant={roundVariant}
class="w-[60vw] max-w-[885px] h-auto drop-shadow-[6px_6px_4px_rgba(0,0,0,0.5)]" class="h-[10vw] max-h-[192px] w-auto drop-shadow-[6px_6px_4px_rgba(0,0,0,0.5)]"
/> />
{:else} {:else}
<KvGameLogo <KvGameLogo
@ -325,14 +330,14 @@
> >
<KvGameLogo <KvGameLogo
variant={roundVariant} variant={roundVariant}
class="w-[60vw] max-w-[885px] h-auto drop-shadow-[6px_6px_4px_rgba(0,0,0,0.5)]" class="h-[10vw] max-h-[192px] w-auto drop-shadow-[6px_6px_4px_rgba(0,0,0,0.5)]"
/> />
</div> </div>
<!-- Category reveal - fades in on TOP of Villak, then pushed out --> <!-- Category reveal - fades in on TOP of Villak, then pushed out -->
{#if currentCat} {#if currentCat}
<div <div
class="absolute inset-0 bg-kv-black p-8 intro-category {introAnimPhase}" class="absolute inset-0 bg-kv-black intro-category {introAnimPhase}"
> >
<div <div
class="w-full h-full flex items-center justify-center bg-kv-blue overflow-hidden p-2" class="w-full h-full flex items-center justify-center bg-kv-blue overflow-hidden p-2"
@ -352,13 +357,13 @@
> >
<KvGameLogo <KvGameLogo
variant={roundVariant} variant={roundVariant}
class="w-[60vw] max-w-[885px] h-auto drop-shadow-[6px_6px_4px_rgba(0,0,0,0.5)]" class="h-[10vw] max-h-[192px] w-auto drop-shadow-[6px_6px_4px_rgba(0,0,0,0.5)]"
/> />
</div> </div>
</div> </div>
{:else if session.phase === "board" || session.phase === "question"} {:else if session.phase === "board" || session.phase === "question"}
<!-- Game Board - Full screen responsive grid --> <!-- Game Board - Full screen responsive grid -->
<div class="flex-1 flex flex-col p-4 lg:p-8 gap-4 h-full min-h-0"> <div class="flex-1 flex flex-col p-4 lg:p-8 gap-8 h-full min-h-0">
<!-- Category Headers - show round name until board is revealed --> <!-- Category Headers - show round name until board is revealed -->
<div <div
class="grid gap-2 lg:gap-4 shrink-0" class="grid gap-2 lg:gap-4 shrink-0"
@ -384,7 +389,7 @@
class="h-full w-auto drop-shadow-[6px_6px_4px_rgba(0,0,0,0.5)]" class="h-full w-auto drop-shadow-[6px_6px_4px_rgba(0,0,0,0.5)]"
/> />
</div> </div>
<div class="category-name"> <div class="category-name text-balance">
{cat.name || "???"} {cat.name || "???"}
</div> </div>
</div> </div>
@ -484,7 +489,9 @@
style="transform: scaleX(0.9225);" style="transform: scaleX(0.9225);"
> >
{#if session.showAnswer} {#if session.showAnswer}
<div class="text-kv-yellow kv-shadow-text"> <div
class="text-kv-yellow kv-shadow-text text-[clamp(64px,8vw,144px)]"
>
{questionData.question.answer} {questionData.question.answer}
</div> </div>
{:else} {:else}
@ -505,7 +512,7 @@
> >
<KvGameLogo <KvGameLogo
variant="hobevillak" variant="hobevillak"
class="w-[60vw] max-w-[885px] h-auto drop-shadow-[0_0_20px_rgba(192,192,192,0.8)]" class="h-[10vw] max-h-[192px] w-auto drop-shadow-[0_0_20px_rgba(192,192,192,0.8)]"
/> />
</div> </div>
<!-- Question overlay expanding from center --> <!-- Question overlay expanding from center -->
@ -564,7 +571,7 @@
> >
{#if session.showAnswer} {#if session.showAnswer}
<div <div
class="text-kv-yellow kv-shadow-text" class="text-kv-yellow kv-shadow-text text-[clamp(64px,8vw,144px)]"
> >
{questionData.question.answer} {questionData.question.answer}
</div> </div>
@ -584,7 +591,7 @@
{:else if session.phase === "daily-double"} {:else if session.phase === "daily-double"}
<!-- Daily Double (Hõbevillak) - Game board background with spinning overlay --> <!-- Daily Double (Hõbevillak) - Game board background with spinning overlay -->
<!-- Game Board Background (same as board phase) --> <!-- Game Board Background (same as board phase) -->
<div class="flex-1 flex flex-col p-4 lg:p-8 gap-4 h-full min-h-0"> <div class="flex-1 flex flex-col p-4 lg:p-8 gap-8 h-full min-h-0">
<!-- Category Headers --> <!-- Category Headers -->
<div <div
class="grid gap-2 lg:gap-4 shrink-0" class="grid gap-2 lg:gap-4 shrink-0"
@ -636,7 +643,7 @@
> >
<KvGameLogo <KvGameLogo
variant="hobevillak" variant="hobevillak"
class="w-[60vw] max-w-[885px] h-auto drop-shadow-[0_0_20px_rgba(192,192,192,0.8)]" class="h-[10vw] max-h-[192px] w-auto drop-shadow-[0_0_20px_rgba(192,192,192,0.8)]"
/> />
{#if session.dailyDoubleWager} {#if session.dailyDoubleWager}
<div <div
@ -670,7 +677,7 @@
<!-- Final category reveal - fades in on TOP of KULDVILLAK, then pushed out --> <!-- Final category reveal - fades in on TOP of KULDVILLAK, then pushed out -->
<div <div
class="absolute inset-0 bg-kv-black p-8 intro-category {introAnimPhase}" class="absolute inset-0 bg-kv-black intro-category {introAnimPhase}"
> >
<div <div
class="w-full h-full flex items-center justify-center bg-kv-blue overflow-hidden p-2" class="w-full h-full flex items-center justify-center bg-kv-blue overflow-hidden p-2"
@ -742,7 +749,9 @@
style="transform: scaleX(0.9225);" style="transform: scaleX(0.9225);"
> >
{#if session.showAnswer} {#if session.showAnswer}
<div class="text-kv-yellow kv-shadow-text"> <div
class="text-kv-yellow kv-shadow-text text-[clamp(64px,8vw,144px)]"
>
{session.finalRound?.answer} {session.finalRound?.answer}
</div> </div>
{:else} {:else}
@ -968,7 +977,6 @@
} }
/* Scale all elements proportionally to container */ /* Scale all elements proportionally to container */
.expand-overlay { .expand-overlay {
padding: clamp(4px, 3cqh, 32px);
gap: clamp(4px, 1.5cqh, 16px); gap: clamp(4px, 1.5cqh, 16px);
} }
.expand-overlay .font-kv-question { .expand-overlay .font-kv-question {
@ -1035,7 +1043,6 @@
} }
/* Scale elements for center expand */ /* Scale elements for center expand */
.expand-overlay-center { .expand-overlay-center {
padding: clamp(4px, 3cqh, 32px);
gap: clamp(4px, 1.5cqh, 16px); gap: clamp(4px, 1.5cqh, 16px);
} }
.expand-overlay-center .font-kv-question { .expand-overlay-center .font-kv-question {
@ -1143,6 +1150,12 @@
} }
.category-logo { .category-logo {
height: 1.2em; height: 1.2em;
max-width: 100%;
}
.category-logo :global(svg) {
max-width: 100%;
height: 100%;
width: auto;
} }
.category-content.show-logo .category-logo { .category-content.show-logo .category-logo {
opacity: 1; opacity: 1;

@ -1,3 +1,3 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.05957 1.5L11.3781 12.5V115.5L8.05957 126.5H45.9855L42.667 115.5V73.6719L82.0151 126.5H119.941L67.3188 59L115.674 1.50006H77.7485L42.667 50.5001V12.5L45.9855 1.50006L8.05957 1.5Z" fill="#FFAB00"/> <path d="M41.1692 53.4247L69.1227 18.3469L69.6477 17.6762C72.1759 14.3139 73.9344 10.0017 74.8782 4.68266L75.3597 0.603516H120.912L112.11 5.67812C108.463 8.11385 104.833 11.5122 101.228 15.9036L66.1687 58.5571L100.949 109.253C104.122 113.783 107.03 117.183 109.669 119.496C112.414 121.688 115.611 123.454 119.273 124.785L118.812 127.396H73.6992L74.2347 125.654C74.6513 124.301 74.865 122.827 74.865 121.224C74.865 119.433 74.5497 117.765 73.9268 116.208L73.9124 116.171L73.8992 116.133C73.381 114.58 72.4248 112.807 70.9807 110.809L70.9702 110.794L70.9609 110.78L41.1692 67.1546V108.469C41.1693 115.27 42.7295 120.85 45.7641 125.291L47.2023 127.396H7.22473L8.70898 125.278C10.2229 123.116 11.3047 120.739 11.9551 118.139L11.9591 118.122L11.9644 118.103C12.7285 115.376 13.121 112.17 13.121 108.469V18.4968C13.121 12.4177 11.5774 7.17358 8.5274 2.71015L7.08789 0.603516H47.6141L46.0917 2.73251C42.8134 7.31921 41.1692 12.5587 41.1692 18.4968V53.4247Z" fill="#FFAB00"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 355 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Loading…
Cancel
Save