@mideind/netskrafl-react 1.9.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -27509,6 +27509,54 @@ const requestMoves = (state, options) => {
27509
27509
  });
27510
27510
  };
27511
27511
 
27512
+ /*
27513
+
27514
+ Types.ts
27515
+
27516
+ Common type definitions for the Explo/Netskrafl user interface
27517
+
27518
+ Copyright (C) 2025 Miðeind ehf.
27519
+ Author: Vilhjalmur Thorsteinsson
27520
+
27521
+ The Creative Commons Attribution-NonCommercial 4.0
27522
+ International Public License (CC-BY-NC 4.0) applies to this software.
27523
+ For further information, see https://github.com/mideind/Netskrafl
27524
+
27525
+ */
27526
+ // Global constants
27527
+ const RACK_SIZE = 7;
27528
+ const ROWIDS = 'ABCDEFGHIJKLMNO';
27529
+ const BOARD_SIZE = ROWIDS.length;
27530
+ const EXTRA_WIDE_LETTERS = 'q';
27531
+ const WIDE_LETTERS = 'zxmæ';
27532
+ const ZOOM_FACTOR = 1.5;
27533
+ const GATA_DAGSINS_MIN_DATE = '2025-11-01';
27534
+ const ERROR_MESSAGES = {
27535
+ // Translations are found in /static/assets/messages.json
27536
+ 1: 'Enginn stafur lagður niður',
27537
+ 2: 'Fyrsta orð verður að liggja um byrjunarreitinn',
27538
+ 3: 'Orð verður að vera samfellt á borðinu',
27539
+ 4: 'Orð verður að tengjast orði sem fyrir er',
27540
+ 5: 'Reitur þegar upptekinn',
27541
+ 6: 'Ekki má vera eyða í orði',
27542
+ 7: 'word_not_found',
27543
+ 8: 'word_not_found',
27544
+ 9: 'Of margir stafir lagðir niður',
27545
+ 10: 'Stafur er ekki í rekkanum',
27546
+ 11: 'Of fáir stafir eftir, skipting ekki leyfð',
27547
+ 12: 'Of mörgum stöfum skipt',
27548
+ 13: 'Leik vantar á borðið - notið F5/Refresh',
27549
+ 14: 'Notandi ekki innskráður - notið F5/Refresh',
27550
+ 15: 'Rangur eða óþekktur notandi',
27551
+ 16: 'Viðureign finnst ekki',
27552
+ 17: 'Viðureign er ekki utan tímamarka',
27553
+ 18: 'Netþjónn gat ekki tekið við leiknum - reyndu aftur',
27554
+ 19: 'Véfenging er ekki möguleg í þessari viðureign',
27555
+ 20: 'Síðasti leikur er ekki véfengjanlegur',
27556
+ 21: 'Aðeins véfenging eða pass leyfileg',
27557
+ server: 'Netþjónn gat ekki tekið við leiknum - reyndu aftur',
27558
+ };
27559
+
27512
27560
  /*
27513
27561
 
27514
27562
  Audio.ts
@@ -27636,52 +27684,2073 @@ function getAudioManager(state, soundUrls) {
27636
27684
  return audioManager;
27637
27685
  }
27638
27686
 
27687
+ var Leikir = {
27688
+ en: "Moves",
27689
+ nb: "Trekk",
27690
+ nn: "Trekk",
27691
+ pl: "Ruchy",
27692
+ ga: "Bogann"
27693
+ };
27694
+ var submit_move = {
27695
+ is: "Leika",
27696
+ en: "Move",
27697
+ nb: "Trekk",
27698
+ nn: "Trekk",
27699
+ pl: "Wykonaj ruch",
27700
+ ga: "Bog"
27701
+ };
27702
+ var Spjall = {
27703
+ en: "Chat",
27704
+ nb: "Chat",
27705
+ nn: "Chat",
27706
+ pl: "Czat",
27707
+ ga: "Comhrá"
27708
+ };
27709
+ var player_info = {
27710
+ is: "Upplýsingar um leikmann",
27711
+ en: "Player information",
27712
+ nb: "Spillerinformasjon",
27713
+ nn: "Spelarinformasjon",
27714
+ pl: "Informacje o graczu",
27715
+ ga: "Eolas Imreoir"
27716
+ };
27717
+ var explain_email = {
27718
+ is: "Ef póstfang er gefið upp mun Netskrafl geta sent tölvupóst þegar þú átt leik",
27719
+ en: "If given, Explo can send e-mail to the address when it's your turn in a game",
27720
+ nb: "Hvis oppgitt, kan Explo sende e-post til adressen når det er din tur i et spill",
27721
+ nn: "Viss oppgjeve, kan Explo sende e-post til adressa når det er din tur i eit spel",
27722
+ pl: "Jeśli podano, Explo może wysłać e-mail na ten adres, gdy nadejdzie Twoja kolej w grze",
27723
+ ga: "Más tugadh é, is féidir le Explo ríomhphost a sheoladh chuig an seoladh nuair atá sé do sheal sa chluiche"
27724
+ };
27725
+ var explain_sound = {
27726
+ is: "Stillir hvort hljóðmerki heyrast t.d. þegar andstæðingur leikur og þegar sigur vinnst",
27727
+ en: "Controls whether sounds are played, e.g. when an opponent makes a move or when you win",
27728
+ nb: "Kontrollerer om lyder spilles, f.eks. når en motstander gjør et trekk eller når du vinner",
27729
+ nn: "Kontrollerer om lydar blir spelte, t.d. når ein motstandar gjer eit trekk eller når du vinn",
27730
+ pl: "Kontroluje, czy dźwięki są odtwarzane, np. gdy przeciwnik wykonuje ruch lub gdy wygrywasz",
27731
+ ga: "Rialaíonn sé an seinntear fuaimeanna, m.sh. nuair a bhogann freasúra nó nuair a bhuaigh tú"
27732
+ };
27733
+ var Vista = {
27734
+ en: "Save",
27735
+ nb: "Lagre",
27736
+ nn: "Lagre",
27737
+ pl: "Zapisz",
27738
+ ga: "Sábháil"
27739
+ };
27740
+ var no_helpers = {
27741
+ is: "Með því að velja þessa merkingu lýsir þú því yfir að þú\nskraflir við aðra leikmenn ",
27742
+ en: "By selecting this option, you declare that you play\nagainst other humans ",
27743
+ nb: "Ved å velge dette alternativet, erklærer du at du spiller\nmot andre mennesker ",
27744
+ nn: "Ved å velje dette, stadfester du at du spelar\nmot andre menneske ",
27745
+ pl: "Wybierając tę opcję, deklarujesz, że grasz\nprzeciwko innym ludziom ",
27746
+ ga: "Trí an rogha seo a roghnú, dearbhaíonn tú go n-imríonn tú\ni gcoinne daoine eile "
27747
+ };
27748
+ var Ferill = {
27749
+ en: "History",
27750
+ nb: "Historikk",
27751
+ nn: "Historikk",
27752
+ pl: "Historia",
27753
+ ga: "Stair"
27754
+ };
27755
+ var opp_move = {
27756
+ is: "{opponent} á leik",
27757
+ en: "{opponent}'s turn",
27758
+ nb: "{opponent}s tur",
27759
+ nn: "{opponent} sin tur",
27760
+ pl: "Tura {opponent}",
27761
+ ga: "Seal {opponent}"
27762
+ };
27763
+ var Framvinda = {
27764
+ en: "Progress",
27765
+ nb: "Fremgang",
27766
+ nn: "Framgang",
27767
+ pl: "Postęp",
27768
+ ga: "Dul chun cinn"
27769
+ };
27770
+ var Keppnishamur = {
27771
+ en: "Pro mode",
27772
+ nb: "Pro-modus",
27773
+ nn: "Pro-modus",
27774
+ pl: "Tryb Pro",
27775
+ ga: "Modh Pro"
27776
+ };
27777
+ var with_clock = {
27778
+ is: "Með klukku, 2 x {duration} mínútur",
27779
+ en: "With clock, 2 x {duration} minutes",
27780
+ nb: "Med klokke, 2 x {duration} minutter",
27781
+ nn: "Med klokke, 2 x {duration} minutt",
27782
+ pl: "Z zegarem, 2 x {duration} minut",
27783
+ ga: "Le clog, 2 x {duration} nóiméad"
27784
+ };
27785
+ var Hafna = {
27786
+ en: "Decline",
27787
+ nb: "Avslå",
27788
+ nn: "Avslå",
27789
+ pl: "Odrzuć",
27790
+ ga: "Diúltaigh"
27791
+ };
27792
+ var Afturkalla = {
27793
+ en: "Retract",
27794
+ nb: "Trekk tilbake",
27795
+ nn: "Trekk tilbake",
27796
+ pl: "Wycofaj",
27797
+ ga: "Tarraing siar"
27798
+ };
27799
+ var Hvernig = {
27800
+ en: "How",
27801
+ nb: "Hvordan",
27802
+ nn: "Korleis",
27803
+ pl: "Jak",
27804
+ ga: "Conas"
27805
+ };
27806
+ var Sigur = {
27807
+ en: "Win",
27808
+ nb: "Seier",
27809
+ nn: "Siger",
27810
+ pl: "Zwycięstwo",
27811
+ ga: "Bua"
27812
+ };
27813
+ var Jafntefli = {
27814
+ en: "Draw",
27815
+ nb: "Uavgjort",
27816
+ nn: "Uavgjort",
27817
+ pl: "Remis",
27818
+ ga: "Tarraingt"
27819
+ };
27820
+ var Tap = {
27821
+ en: "Loss",
27822
+ nb: "Tap",
27823
+ nn: "Tap",
27824
+ pl: "Porażka",
27825
+ ga: "Caill"
27826
+ };
27827
+ var Elo = {
27828
+ is: "Elo",
27829
+ en: "Elo",
27830
+ nb: "Elo",
27831
+ nn: "Elo",
27832
+ pl: "Elo",
27833
+ ga: "Elo"
27834
+ };
27835
+ var Lengd = {
27836
+ en: "Duration",
27837
+ nb: "Varighet",
27838
+ nn: "Varigheit",
27839
+ pl: "Czas trwania",
27840
+ ga: "Fad"
27841
+ };
27842
+ var Einkenni = {
27843
+ en: "Identifier",
27844
+ nb: "Identifikator",
27845
+ nn: "Identifikator",
27846
+ pl: "Identyfikator",
27847
+ ga: "Aitheantóir"
27848
+ };
27849
+ var stafur = {
27850
+ en: "letter",
27851
+ nb: "bokstav",
27852
+ nn: "bokstav",
27853
+ pl: "litera",
27854
+ ga: "litir"
27855
+ };
27856
+ var Senda = {
27857
+ en: "Send",
27858
+ nb: "Send",
27859
+ nn: "Send",
27860
+ pl: "Wyślij",
27861
+ ga: "Seol"
27862
+ };
27863
+ var Pass = {
27864
+ en: "Pass",
27865
+ nb: "Pass",
27866
+ nn: "Pass",
27867
+ pl: "Pas",
27868
+ ga: "Pas"
27869
+ };
27870
+ var letter = {
27871
+ is: "staf",
27872
+ en: "letter",
27873
+ nb: "bokstav",
27874
+ nn: "bokstav",
27875
+ pl: "litera",
27876
+ ga: "litir"
27877
+ };
27878
+ var letters = {
27879
+ is: "stafi",
27880
+ en: "letters",
27881
+ nb: "bokstaver",
27882
+ nn: "bokstavar",
27883
+ pl: "litery",
27884
+ ga: "litreacha"
27885
+ };
27886
+ var exchanged = {
27887
+ is: "Skipti um {numtiles} {letters}",
27888
+ en: "Exchanged {numtiles} {letters}",
27889
+ nb: "Byttet ut {numtiles} {letters}",
27890
+ nn: "Bytte ut {numtiles} {letters}",
27891
+ pl: "Wymieniono {numtiles} {letters}",
27892
+ ga: "Mhalartaithe {numtiles} {letters}"
27893
+ };
27894
+ var click_on_game = {
27895
+ is: " - smelltu á viðureign til að skoða stöðuna og leika ef ",
27896
+ en: " - click on a game to view it and make a move if ",
27897
+ nb: " - klikk på et spill for å åpne det og gjøre et trekk hvis ",
27898
+ nn: " - klikk på eit spel for å opne det og gjere eit trekk viss ",
27899
+ pl: " - kliknij na grę, aby ją zobaczyć i wykonać ruch, jeśli ",
27900
+ ga: " - cliceáil ar chluiche chun é a fheiceáil agus bogadh má "
27901
+ };
27902
+ var click_on_challenge = {
27903
+ is: " - smelltu á áskorun til að taka henni og hefja viðureign, eða á ",
27904
+ en: " - click on a challenge to accept it and start a game, or on ",
27905
+ nb: " - klikk på en utfordring for å akseptere den og starte et spill, eller på ",
27906
+ nn: " - klikk på ei utfordring for å akseptere ho og starte eit spel, eller på ",
27907
+ pl: " - kliknij na wyzwanie, aby je zaakceptować i rozpocząć grę, lub na ",
27908
+ ga: " - cliceáil ar dhúshlán chun glacadh leis agus cluiche a thosú, nó ar "
27909
+ };
27910
+ var click_to_review = {
27911
+ is: " - smelltu á viðureign til að skoða hana og rifja upp",
27912
+ en: " - click on a game to review it",
27913
+ nb: " - klikk på et spill for å åpne det og gå gjennom det",
27914
+ nn: " - klikk på eit spel for å opne det og gå gjennom det",
27915
+ pl: " - kliknij na grę, aby ją przejrzeć",
27916
+ ga: " - cliceáil ar chluiche chun athbhreithniú a dhéanamh air"
27917
+ };
27918
+ var Loka = {
27919
+ en: "Close",
27920
+ nb: "Lukk",
27921
+ nn: "Lukk",
27922
+ pl: "Zamknij",
27923
+ ga: "Dún"
27924
+ };
27925
+ var opponent_emptied_rack = {
27926
+ is: "Andstæðingur tæmdi rekkann - þú getur véfengt eða sagt pass",
27927
+ en: "Your opponent emptied the rack - you can challenge or pass",
27928
+ nb: "Motstanderen tømte stativet - du kan utfordre eller passere",
27929
+ nn: "Motstandaren tømde stativet - du kan utfordre eller passere",
27930
+ pl: "Twój przeciwnik opróżnił stojak - możesz rzucić wyzwanie lub spasować",
27931
+ ga: "D'fholmhaigh do chéile comhraic an raca - is féidir leat dúshlán a thabhairt nó pas a fháil"
27932
+ };
27933
+ var Skipta = {
27934
+ en: "Exchange",
27935
+ nb: "Bytte",
27936
+ nn: "Byte",
27937
+ pl: "Wymiana",
27938
+ ga: "Malartú"
27939
+ };
27940
+ var elo_list_choice = {
27941
+ is: "Fólk | Allar | Keppnishamur",
27942
+ en: "Humans | All | Pro mode",
27943
+ nb: "Mennesker | Alle | Pro modus",
27944
+ nn: "Menneske | Alle | Pro-modus",
27945
+ pl: "Ludzie | Wszyscy | Tryb Pro",
27946
+ ga: "Daoine | Gach | Modh Pro"
27947
+ };
27948
+ var stats_choice = {
27949
+ is: "Með þjörkum eða án",
27950
+ en: "With or without robot games",
27951
+ nb: "Med eller uten robotspill",
27952
+ nn: "Med eller utan robotspel",
27953
+ pl: "Z grami robotów lub bez",
27954
+ ga: "Le cluichí róbón nó gan iad"
27955
+ };
27956
+ var Vinningshlutfall = {
27957
+ en: "Winning ratio",
27958
+ nb: "Vinningsforhold",
27959
+ nn: "Vinningsforhold",
27960
+ pl: "Stosunek wygranych",
27961
+ ga: "Cóimheas bua"
27962
+ };
27963
+ var word_not_found = {
27964
+ en: "'{word}' is not in the dictionary",
27965
+ is: "'{word}' finnst ekki í orðasafni",
27966
+ nb: "'{word}' finnes ikke i ordboken",
27967
+ nn: "'{word}' finst ikkje i ordboka",
27968
+ pl: "'{word}' nie znajduje się w słowniku",
27969
+ ga: "Níl '{word}' sa fhoclóir"
27970
+ };
27971
+ var welcome_2 = {
27972
+ is: [
27973
+ "Til auðkenningar tengir Netskrafl tölvupóstfang og nafn við hvern notanda. ",
27974
+ "Að öðru leyti eru ekki geymdar aðrar upplýsingar um notendur ",
27975
+ "en þær sem þeir skrá sjálfir. Annáll er haldinn um umferð um vefinn og um ",
27976
+ "aðgerðir notenda, í því skyni að endurbæta Netskrafl."
27977
+ ],
27978
+ en: [
27979
+ "For identification, Explo associates an e-mail address and a name with each user. ",
27980
+ "Apart from this, Explo only stores information which is voluntarily ",
27981
+ "entered by users themselves. A log is kept of web traffic and events within Explo, ",
27982
+ "for the purpose of improving the service."
27983
+ ],
27984
+ nb: [
27985
+ "For identifikasjon knytter Explo en e-postadresse og et navn til hver bruker. ",
27986
+ "Utover dette lagrer Explo kun informasjon som frivillig ",
27987
+ "legges inn av brukerne selv. En logg føres over webtrafikk og hendelser innen Explo, ",
27988
+ "i hensikt å forbedre tjenesten."
27989
+ ],
27990
+ nn: [
27991
+ "For identifikasjon knyter Explo ei e-postadresse og eit namn til kvar brukar. ",
27992
+ "Utover dette lagrar Explo berre informasjon som frivillig ",
27993
+ "blir lagt inn av brukarane sjølve. Ein logg blir ført over webtrafikk og hendingar innan Explo, ",
27994
+ "i føremål å forbetre tenesta."
27995
+ ],
27996
+ pl: [
27997
+ "W celu identyfikacji Explo przypisuje każdemu użytkownikowi adres e-mail i nazwę. ",
27998
+ "Poza tym Explo przechowuje tylko te informacje, które dobrowolnie wprowadzają sami ",
27999
+ "użytkownicy. Prowadzony jest rejestr ruchu internetowego i zdarzeń w Explo w celu ",
28000
+ "ulepszenia usługi."
28001
+ ],
28002
+ ga: [
28003
+ "Chun aitheantais, ceanglaíonn Explo seoladh ríomhphoist agus ainm le gach úsáideoir. ",
28004
+ "Seachas sin, ní stóráiltear ag Explo ach an fhaisnéis a chuirtear isteach go deonach ",
28005
+ "ag na húsáideoirí iad féin. Coinnítear logáil ar thrácht gréasáin agus imeachtaí laistigh de Explo, ",
28006
+ "leis an gcuspóir an tseirbhís a fheabhsú."
28007
+ ]
28008
+ };
28009
+ var welcome_1 = {
28010
+ is: [
28011
+ "Netskrafl notar Google Accounts innskráningu, þá sömu og er notuð m.a. í Gmail. ",
28012
+ "Til að auðkenna þig sem notanda og halda innskráningunni virkri ",
28013
+ "er óhjákvæmilegt að geyma þar til gerða smáköku (<i>cookie</i>) ",
28014
+ "í vafranum þínum."
28015
+ ],
28016
+ en: [
28017
+ "Explo uses Google Accounts for user authentication, similarly to e.g. Gmail. ",
28018
+ "To remember your login and maintain your session, it is necessary to store a ",
28019
+ "cookie within your browser."
28020
+ ],
28021
+ nb: [
28022
+ "Explo bruker Google-kontoer for brukerautentisering, likt som f.eks. Gmail. ",
28023
+ "For å huske din innlogging og opprettholde din økt, er det nødvendig å lagre en ",
28024
+ "informasjonskapsel i nettleseren din."
28025
+ ],
28026
+ nn: [
28027
+ "Explo brukar Google-kontoar for brukarautentisering, likt som t.d. Gmail. ",
28028
+ "For å hugse innlogginga di og halde ved like økta di, er det naudsynt å lagre ein ",
28029
+ "informasjonskapsel i nettlesaren din."
28030
+ ],
28031
+ pl: [
28032
+ "Explo używa kont Google do uwierzytelniania użytkownika, podobnie jak np. Gmail. ",
28033
+ "Aby zapamiętać twoje logowanie i utrzymać sesję, konieczne jest przechowanie ",
28034
+ "ciasteczka w twojej przeglądarce."
28035
+ ],
28036
+ ga: [
28037
+ "Úsáideann Explo Cuntais Google le haghaidh fíordheimhniú úsáideora, cosúil le Gmail, mar shampla. ",
28038
+ "Chun do logáil isteach a mheabhrú agus do sheisiún a chothabháil, tá sé riachtanach fianán a stóráil ",
28039
+ "i do bhrabhsálaí."
28040
+ ]
28041
+ };
28042
+ var welcome_0 = {
28043
+ is: [
28044
+ "Netskrafl er vettvangur ",
28045
+ "<b>yfir 40.000 íslenskra skraflara</b>",
28046
+ " á netinu."
28047
+ ],
28048
+ en: [
28049
+ "Explo is a venue for ",
28050
+ "<b>tens of thousands of crossword game enthusiasts</b> ",
28051
+ "on the Internet."
28052
+ ],
28053
+ nb: [
28054
+ "Explo er et møtested for ",
28055
+ "<b>tusenvis av kryssordspillentusiaster</b>",
28056
+ " på internett."
28057
+ ],
28058
+ nn: [
28059
+ "Explo er ein møtestad for ",
28060
+ "<b>tusenvis av kryssordspelentusiastar</b>",
28061
+ " på internett."
28062
+ ],
28063
+ pl: [
28064
+ "Explo to miejsce dla ",
28065
+ "<b>dziesiątek tysięcy entuzjastów gier krzyżówkowych</b>",
28066
+ " w Internecie."
28067
+ ],
28068
+ ga: [
28069
+ "Is ionad é Explo do ",
28070
+ "<b>na deich mílte díograiseoirí cluiche crosfhocal</b>",
28071
+ " ar an Idirlíon."
28072
+ ]
28073
+ };
28074
+ var Stigatafla = {
28075
+ en: "Leaderboard",
28076
+ nb: "Toppliste",
28077
+ nn: "Toppliste",
28078
+ pl: "Tabela wyników",
28079
+ ga: "Clár ceannairí"
28080
+ };
28081
+ var date_format = {
28082
+ is: "{day}. {month}",
28083
+ en: "{month} {day}",
28084
+ nb: "{day}. {month}",
28085
+ nn: "{day}. {month}",
28086
+ pl: "{day} {month}",
28087
+ ga: "{day} {month}"
28088
+ };
28089
+ var january = {
28090
+ is: "janúar",
28091
+ en: "January",
28092
+ nb: "januar",
28093
+ nn: "januar",
28094
+ pl: "styczeń",
28095
+ ga: "Eanáir"
28096
+ };
28097
+ var february = {
28098
+ is: "febrúar",
28099
+ en: "February",
28100
+ nb: "februar",
28101
+ nn: "februar",
28102
+ pl: "luty",
28103
+ ga: "Feabhra"
28104
+ };
28105
+ var march = {
28106
+ is: "mars",
28107
+ en: "March",
28108
+ nb: "mars",
28109
+ nn: "mars",
28110
+ pl: "marzec",
28111
+ ga: "Márta"
28112
+ };
28113
+ var april = {
28114
+ is: "apríl",
28115
+ en: "April",
28116
+ nb: "april",
28117
+ nn: "april",
28118
+ pl: "kwiecień",
28119
+ ga: "Aibreán"
28120
+ };
28121
+ var may = {
28122
+ is: "maí",
28123
+ en: "May",
28124
+ nb: "mai",
28125
+ nn: "mai",
28126
+ pl: "maj",
28127
+ ga: "Bealtaine"
28128
+ };
28129
+ var june = {
28130
+ is: "júní",
28131
+ en: "June",
28132
+ nb: "juni",
28133
+ nn: "juni",
28134
+ pl: "czerwiec",
28135
+ ga: "Meitheamh"
28136
+ };
28137
+ var july = {
28138
+ is: "júlí",
28139
+ en: "July",
28140
+ nb: "juli",
28141
+ nn: "juli",
28142
+ pl: "lipiec",
28143
+ ga: "Iúil"
28144
+ };
28145
+ var august = {
28146
+ is: "ágúst",
28147
+ en: "August",
28148
+ nb: "august",
28149
+ nn: "august",
28150
+ pl: "sierpień",
28151
+ ga: "Lúnasa"
28152
+ };
28153
+ var september = {
28154
+ is: "september",
28155
+ en: "September",
28156
+ nb: "september",
28157
+ nn: "september",
28158
+ pl: "wrzesień",
28159
+ ga: "Meán Fómhair"
28160
+ };
28161
+ var october = {
28162
+ is: "október",
28163
+ en: "October",
28164
+ nb: "oktober",
28165
+ nn: "oktober",
28166
+ pl: "październik",
28167
+ ga: "Deireadh Fómhair"
28168
+ };
28169
+ var november = {
28170
+ is: "nóvember",
28171
+ en: "November",
28172
+ nb: "november",
28173
+ nn: "november",
28174
+ pl: "listopad",
28175
+ ga: "Samhain"
28176
+ };
28177
+ var december = {
28178
+ is: "desember",
28179
+ en: "December",
28180
+ nb: "desember",
28181
+ nn: "desember",
28182
+ pl: "grudzień",
28183
+ ga: "Nollaig"
28184
+ };
28185
+ var messagesData = {
28186
+ Leikir: Leikir,
28187
+ submit_move: submit_move,
28188
+ "Viðureignir": {
28189
+ en: "Games",
28190
+ nb: "Spill",
28191
+ nn: "Spel",
28192
+ pl: "Gry",
28193
+ ga: "Cluichí"
28194
+ },
28195
+ "Borðið": {
28196
+ en: "Board",
28197
+ nb: "Brett",
28198
+ nn: "Brett",
28199
+ pl: "Plansza",
28200
+ ga: "Bord"
28201
+ },
28202
+ "Tveggja stafa orð": {
28203
+ en: "Two letter words",
28204
+ nb: "Tobokstavsord",
28205
+ nn: "Tobokstavsord",
28206
+ pl: "Dwuliterowe słowa",
28207
+ ga: "Focail dhá litir"
28208
+ },
28209
+ Spjall: Spjall,
28210
+ "Þessi vefslóð er ekki rétt": {
28211
+ en: "Unknown URL",
28212
+ nb: "Ukjent URL",
28213
+ nn: "Ukjend URL",
28214
+ pl: "Nieznany URL",
28215
+ ga: "URL Anaithnid"
28216
+ },
28217
+ player_info: player_info,
28218
+ "Einkenni:": {
28219
+ en: "User identifier:",
28220
+ nb: "Brukeridentifikator:",
28221
+ nn: "Brukaridentifikator:",
28222
+ pl: "Identyfikator użytkownika:",
28223
+ ga: "Aitheantóir Úsáideora:"
28224
+ },
28225
+ "Verður að vera útfyllt": {
28226
+ en: "Required field",
28227
+ nb: "Obligatorisk felt",
28228
+ nn: "Obligatorisk felt",
28229
+ pl: "Pole wymagane",
28230
+ ga: "Réimse Riachtanach"
28231
+ },
28232
+ "Fullt nafn:": {
28233
+ en: "Full name:",
28234
+ nb: "Fullt navn:",
28235
+ nn: "Fullt namn:",
28236
+ pl: "Pełne imię i nazwisko:",
28237
+ ga: "Ainm Iomlán:"
28238
+ },
28239
+ "Valfrjálst - sýnt í notendalistum Netskrafls": {
28240
+ en: "Optional - shown in user lists",
28241
+ nb: "Valgfritt - vist i brukerlister",
28242
+ nn: "Valfritt - vist i brukarlister",
28243
+ pl: "Opcjonalne - pokazane na listach użytkowników",
28244
+ ga: "Roghnach - le feiceáil i liostaí úsáideoirí"
28245
+ },
28246
+ "Tölvupóstfang:": {
28247
+ en: "E-mail:",
28248
+ nb: "E-post:",
28249
+ nn: "E-post:",
28250
+ pl: "E-mail:",
28251
+ ga: "Ríomhphost:"
28252
+ },
28253
+ explain_email: explain_email,
28254
+ "Hljóðmerki:": {
28255
+ en: "Sounds:",
28256
+ nb: "Lyder:",
28257
+ nn: "Lydar:",
28258
+ pl: "Dźwięki:",
28259
+ ga: "Fuaimeanna:"
28260
+ },
28261
+ "Hljóð á/af": {
28262
+ en: "Sound on/off",
28263
+ nb: "Lyd på/av",
28264
+ nn: "Lyd på/av",
28265
+ pl: "Dźwięki włącz/wyłącz",
28266
+ ga: "Fuaimeanna ar/amuigh"
28267
+ },
28268
+ "Lúðraþytur eftir sigur:": {
28269
+ en: "Fanfare after a win:",
28270
+ nb: "Trompeter etter seier:",
28271
+ nn: "Trompeter etter siger:",
28272
+ pl: "Trąbki po zwycięstwie:",
28273
+ ga: "Trumpaí tar éis bua:"
28274
+ },
28275
+ "Lúðraþytur á/af": {
28276
+ en: "Fanfare on/off",
28277
+ nb: "Trompeter på/av",
28278
+ nn: "Trompeter på/av",
28279
+ pl: "Trąbki włącz/wyłącz",
28280
+ ga: "Trumpaí ar/amuigh"
28281
+ },
28282
+ explain_sound: explain_sound,
28283
+ "Sýna reitagildi:": {
28284
+ en: "Show multipliers:",
28285
+ nb: "Vis multiplikatorer:",
28286
+ nn: "Vis multiplikatorar:",
28287
+ pl: "Pokaż mnożniki:",
28288
+ ga: "Taispeáin iolraitheoirí:"
28289
+ },
28290
+ "Nýi skraflpokinn:": {
28291
+ en: "Use the new bag:",
28292
+ nb: "Bruk den nye posen:",
28293
+ nn: "Bruk den nye posen:",
28294
+ pl: "Użyj nowego worka:",
28295
+ ga: "Bain úsáid as an mála nua:"
28296
+ },
28297
+ "Án hjálpartækja:": {
28298
+ en: "Without helpers:",
28299
+ nb: "Uten hjelpemidler:",
28300
+ nn: "Utan hjelpemiddel:",
28301
+ pl: "Bez pomocy:",
28302
+ ga: "Gan chúntóirí:"
28303
+ },
28304
+ Vista: Vista,
28305
+ "Hætta við": {
28306
+ en: "Cancel",
28307
+ nb: "Avbryt",
28308
+ nn: "Bryt av",
28309
+ pl: "Anuluj",
28310
+ ga: "Cealaigh"
28311
+ },
28312
+ "Skrá mig út": {
28313
+ en: "Log out",
28314
+ nb: "Logg ut",
28315
+ nn: "Logg ut",
28316
+ pl: "Wyloguj się",
28317
+ ga: "Logáil Amach"
28318
+ },
28319
+ "Þú ert áskrifandi!": {
28320
+ en: "You're a subscriber!",
28321
+ nb: "Du er en abonnent!",
28322
+ nn: "Du er ein abonnent!",
28323
+ pl: "Jesteś subskrybentem!",
28324
+ ga: "Is síntiúsóir tú!"
28325
+ },
28326
+ "Gerast áskrifandi": {
28327
+ en: "Subscribe",
28328
+ nb: "Abonner",
28329
+ nn: "Abonner",
28330
+ pl: "Subskrybuj",
28331
+ ga: "Liostáil"
28332
+ },
28333
+ "Tek við áskorunum!": {
28334
+ en: "Accepting challenges!",
28335
+ nb: "Aksepterer utfordringer!",
28336
+ nn: "Tek imot utfordringar!",
28337
+ pl: "Akceptuje wyzwania!",
28338
+ ga: "Ag glacadh le dúshláin!"
28339
+ },
28340
+ "Til í viðureign með klukku!": {
28341
+ en: "Ready for timed games!",
28342
+ nb: "Klar for tidsbegrensede spill!",
28343
+ nn: "Klar for tidsavgrensa spel!",
28344
+ pl: "Gotowy na gry z czasem!",
28345
+ ga: "Réidh do chluichí ama!"
28346
+ },
28347
+ "Stillir hvort ": {
28348
+ en: "Controls whether ",
28349
+ nb: "Kontrollerer om ",
28350
+ nn: "Styrer om ",
28351
+ pl: "Kontroluje czy ",
28352
+ ga: "Rialaíonn sé an "
28353
+ },
28354
+ "minnismiði": {
28355
+ en: "a memo sticker",
28356
+ nb: "en huskelapp",
28357
+ nn: "ein hugselapp",
28358
+ pl: "naklejka memo",
28359
+ ga: "nóta meabhrúcháin"
28360
+ },
28361
+ " um margföldunargildi reita er sýndur við borðið": {
28362
+ en: " with square multipliers is shown beside the board",
28363
+ nb: " med rute-multiplikatorer vises ved siden av brettet",
28364
+ nn: " med rute-multiplikatorar blir vist ved sida av brettet",
28365
+ pl: " z mnożnikami kwadratów jest pokazany obok planszy",
28366
+ ga: " le hiolraitheoirí cearnóg le feiceáil in aice leis an mbord"
28367
+ },
28368
+ "Gefur til kynna hvort þú sért reiðubúin(n) að\nskrafla með ": {
28369
+ en: "Indicates whether you are ready to play with\n",
28370
+ nb: "Indikerer om du er klar til å spille med\n",
28371
+ nn: "Indikerer om du er klar til å spele med\n",
28372
+ pl: "Wskazuje czy jesteś gotowy do gry z\n",
28373
+ ga: "Léiríonn sé an bhfuil tú ullamh chun imeartha le\n"
28374
+ },
28375
+ "nýja íslenska skraflpokanum": {
28376
+ en: "the new tile bag",
28377
+ nb: "den nye brikkeposen",
28378
+ nn: "den nye brikkeposen",
28379
+ pl: "nowym workiem z kafelkami",
28380
+ ga: "an mála tíleanna nua"
28381
+ },
28382
+ no_helpers: no_helpers,
28383
+ "án stafrænna hjálpartækja": {
28384
+ en: "without helpers or tools",
28385
+ nb: "uten hjelpemidler eller verktøy",
28386
+ nn: "utan hjelpemiddel eller verktøy",
28387
+ pl: "bez pomocy lub narzędzi",
28388
+ ga: "gan chúntóirí ná uirlisí"
28389
+ },
28390
+ " af nokkru tagi": {
28391
+ en: " of any kind",
28392
+ nb: " av noe slag",
28393
+ nn: " av noko slag",
28394
+ pl: " jakiegokolwiek rodzaju",
28395
+ ga: " de chineál ar bith"
28396
+ },
28397
+ "Áskoranir": {
28398
+ en: "Challenges",
28399
+ nb: "Utfordringer",
28400
+ nn: "Utfordringar",
28401
+ pl: "Wyzwania",
28402
+ ga: "Dúshláin"
28403
+ },
28404
+ "Andstæðingar": {
28405
+ en: "Opponents",
28406
+ nb: "Motstandere",
28407
+ nn: "Motstandarar",
28408
+ pl: "Przeciwnicy",
28409
+ ga: "Freasúra"
28410
+ },
28411
+ Ferill: Ferill,
28412
+ "Þú átt leik": {
28413
+ en: "Your turn",
28414
+ nb: "Din tur",
28415
+ nn: "Din tur",
28416
+ pl: "Twoja kolej",
28417
+ ga: "Do sheal"
28418
+ },
28419
+ "Viðureign lokið": {
28420
+ en: "Game over",
28421
+ nb: "Spillet er over",
28422
+ nn: "Spelet er over",
28423
+ pl: "Koniec gry",
28424
+ ga: "Cluiche thart"
28425
+ },
28426
+ opp_move: opp_move,
28427
+ "Átt þú leik?": {
28428
+ en: "Your turn?",
28429
+ nb: "Din tur?",
28430
+ nn: "Din tur?",
28431
+ pl: "Twoja kolej?",
28432
+ ga: "Do sheal?"
28433
+ },
28434
+ "Langt frá síðasta leik?": {
28435
+ en: "Long time elapsed since last move?",
28436
+ nb: "Lang tid siden siste trekk?",
28437
+ nn: "Lang tid sidan siste trekk?",
28438
+ pl: "Dużo czasu minęło od ostatniego ruchu?",
28439
+ ga: "An bhfuil am fada caite ón mbogadh deireanach?"
28440
+ },
28441
+ "Síðasti leikur": {
28442
+ en: "Last move",
28443
+ nb: "Siste trekk",
28444
+ nn: "Siste trekk",
28445
+ pl: "Ostatni ruch",
28446
+ ga: "Bogadh deireanach"
28447
+ },
28448
+ "Andstæðingur": {
28449
+ en: "Opponent",
28450
+ nb: "Motstander",
28451
+ nn: "Motstandar",
28452
+ pl: "Przeciwnik",
28453
+ ga: "Iomaitheoir"
28454
+ },
28455
+ "Staða": {
28456
+ en: "Score",
28457
+ nb: "Poengsum",
28458
+ nn: "Poengsum",
28459
+ pl: "Wynik",
28460
+ ga: "Scór"
28461
+ },
28462
+ Framvinda: Framvinda,
28463
+ Keppnishamur: Keppnishamur,
28464
+ "Venjuleg ótímabundin viðureign": {
28465
+ en: "Standard Mode without time limit",
28466
+ nb: "Standardmodus uten tidsbegrensning",
28467
+ nn: "Standardmodus utan tidsbegrensing",
28468
+ pl: "Tryb standardowy bez limitu czasu",
28469
+ ga: "Gnáthchluiche gan teorainn ama"
28470
+ },
28471
+ with_clock: with_clock,
28472
+ Hafna: Hafna,
28473
+ Afturkalla: Afturkalla,
28474
+ "Án hjálpartækja": {
28475
+ en: "Without helpers",
28476
+ nb: "Uten hjelpemidler",
28477
+ nn: "Utan hjelpemiddel",
28478
+ pl: "Bez pomocy",
28479
+ ga: "Gan chúntóirí"
28480
+ },
28481
+ "Skoða feril": {
28482
+ en: "View history",
28483
+ nb: "Vis historikk",
28484
+ nn: "Vis historikk",
28485
+ pl: "Zobacz historię",
28486
+ ga: "Féach ar stair"
28487
+ },
28488
+ "Gamli pokinn": {
28489
+ en: "Old tile bag",
28490
+ nb: "Gammel brikkepose",
28491
+ nn: "Gammal brikkepose",
28492
+ pl: "Stary worek z kafelkami",
28493
+ ga: "Mála tíleanna sean"
28494
+ },
28495
+ "Hvenær": {
28496
+ en: "When",
28497
+ nb: "Når",
28498
+ nn: "Når",
28499
+ pl: "Kiedy",
28500
+ ga: "Cathain"
28501
+ },
28502
+ "Áskorandi": {
28503
+ en: "Challenger",
28504
+ nb: "Utfordrer",
28505
+ nn: "Utfordrar",
28506
+ pl: "Wyzwający",
28507
+ ga: "Dúshlánóir"
28508
+ },
28509
+ Hvernig: Hvernig,
28510
+ Sigur: Sigur,
28511
+ Jafntefli: Jafntefli,
28512
+ Tap: Tap,
28513
+ "Úrslit": {
28514
+ en: "Result",
28515
+ nb: "Resultat",
28516
+ nn: "Resultat",
28517
+ pl: "Wynik",
28518
+ ga: "Toradh"
28519
+ },
28520
+ "Viðureign lauk": {
28521
+ en: "Game finished",
28522
+ nb: "Spill avsluttet",
28523
+ nn: "Spel avslutta",
28524
+ pl: "Gra zakończona",
28525
+ ga: "Cluiche críochnaithe"
28526
+ },
28527
+ "Mennskir andstæðingar": {
28528
+ en: "Human opponents",
28529
+ nb: "Menneskelige motstandere",
28530
+ nn: "Menneskelege motstandarar",
28531
+ pl: "Ludzcy przeciwnicy",
28532
+ ga: "Freasúra daonna"
28533
+ },
28534
+ "Allir andstæðingar": {
28535
+ en: "All opponents",
28536
+ nb: "Alle motstandere",
28537
+ nn: "Alle motstandarar",
28538
+ pl: "Wszyscy przeciwnicy",
28539
+ ga: "Gach freasúra"
28540
+ },
28541
+ Elo: Elo,
28542
+ Lengd: Lengd,
28543
+ "Uppáhald": {
28544
+ en_US: "Favorite",
28545
+ en: "Favourite",
28546
+ nb: "Favoritt",
28547
+ nn: "Favoritt",
28548
+ pl: "Ulubione",
28549
+ ga: "Is fearr leat"
28550
+ },
28551
+ "Uppáhalds": {
28552
+ en_US: "Favorites",
28553
+ en: "Favourites",
28554
+ nb: "Favoritter",
28555
+ nn: "Favorittar",
28556
+ pl: "Ulubione",
28557
+ ga: "Roghanna"
28558
+ },
28559
+ "Skora á": {
28560
+ en: "Challenge",
28561
+ nb: "Utfordre",
28562
+ nn: "Utfordre",
28563
+ pl: "Wyzwanie",
28564
+ ga: "Dúshlán"
28565
+ },
28566
+ Einkenni: Einkenni,
28567
+ "Nafn og merki": {
28568
+ en: "Full name and badges",
28569
+ nb: "Fullt navn og merker",
28570
+ nn: "Fullt namn og merke",
28571
+ pl: "Pełna nazwa i odznaki",
28572
+ ga: "Ainm iomlán agus suaitheantais"
28573
+ },
28574
+ " finnst ekki": {
28575
+ en: " not found",
28576
+ nb: " ikke funnet",
28577
+ nn: " ikkje funne",
28578
+ pl: " nie znaleziono",
28579
+ ga: " gan aimsiú"
28580
+ },
28581
+ "Þjarkar": {
28582
+ en: "Robots",
28583
+ nb: "Roboter",
28584
+ nn: "Robotar",
28585
+ pl: "Roboty",
28586
+ ga: "Róbónna"
28587
+ },
28588
+ "Álínis": {
28589
+ en: "Online",
28590
+ nb: "Pålogget",
28591
+ nn: "Pålogga",
28592
+ pl: "Online",
28593
+ ga: "Ar líne"
28594
+ },
28595
+ "Svipaðir": {
28596
+ en: "Similar",
28597
+ nb: "Lignende",
28598
+ nn: "Liknande",
28599
+ pl: "Podobne",
28600
+ ga: "Cosúil"
28601
+ },
28602
+ "Topp 100": {
28603
+ en: "Top 100",
28604
+ nb: "Topp 100",
28605
+ nn: "Topp 100",
28606
+ pl: "Top 100",
28607
+ ga: "Barr 100"
28608
+ },
28609
+ "orð": {
28610
+ en: "word",
28611
+ nb: "ord",
28612
+ nn: "ord",
28613
+ pl: "słowo",
28614
+ ga: "focal"
28615
+ },
28616
+ stafur: stafur,
28617
+ Senda: Senda,
28618
+ Pass: Pass,
28619
+ letter: letter,
28620
+ letters: letters,
28621
+ exchanged: exchanged,
28622
+ "Gaf viðureign": {
28623
+ en: "Resigned",
28624
+ nb: "Ga opp",
28625
+ nn: "Gav opp",
28626
+ pl: "Zrezygnował",
28627
+ ga: "D'éirigh as"
28628
+ },
28629
+ "Véfengdi lögn": {
28630
+ en: "Challenged move",
28631
+ nb: "Utfordret trekk",
28632
+ nn: "Utfordra trekk",
28633
+ pl: "Ruch zakwestionowany",
28634
+ ga: "Bogadh dúshlánach"
28635
+ },
28636
+ "Óleyfileg lögn": {
28637
+ en: "Invalid move",
28638
+ nb: "Ugyldig trekk",
28639
+ nn: "Ugyldig trekk",
28640
+ pl: "Nieprawidłowy ruch",
28641
+ ga: "Bogadh neamhbhailí"
28642
+ },
28643
+ "Röng véfenging": {
28644
+ en: "Unsuccessful challenge",
28645
+ nb: "Mislykket utfordring",
28646
+ nn: "Mislukka utfordring",
28647
+ pl: "Nieudane wyzwanie",
28648
+ ga: "Dúshlán gan rath"
28649
+ },
28650
+ "Umframtími": {
28651
+ en: "Extra time",
28652
+ nb: "Ekstra tid",
28653
+ nn: "Ekstra tid",
28654
+ pl: "Dodatkowy czas",
28655
+ ga: "Am breise"
28656
+ },
28657
+ "Stafaleif: engin": {
28658
+ en: "Rack leave: none",
28659
+ nb: "Rack forlater: ingen",
28660
+ nn: "Stativ forlèt: ingen",
28661
+ pl: "Pozostawienie na stojaku: brak",
28662
+ ga: "Fágáil raca: dada"
28663
+ },
28664
+ "Stafaleif: ": {
28665
+ en: "Rack leave: ",
28666
+ nb: "Rack forlater: ",
28667
+ nn: "Stativ forlèt: ",
28668
+ pl: "Pozostawienie na stojaku: ",
28669
+ ga: "Fágáil raca: "
28670
+ },
28671
+ "Ný áskorun": {
28672
+ en: "New challenge",
28673
+ nb: "Ny utfordring",
28674
+ nn: "Ny utfordring",
28675
+ pl: "Nowe wyzwanie",
28676
+ ga: "Dúshlán nua"
28677
+ },
28678
+ "Viðureign án klukku": {
28679
+ en: "Game with no time limit",
28680
+ nb: "Spill uten tidsbegrensning",
28681
+ nn: "Spel utan tidsbegrensing",
28682
+ pl: "Gra bez limitu czasu",
28683
+ ga: "Cluiche gan teorainn ama"
28684
+ },
28685
+ "Nota ": {
28686
+ en: "Enable ",
28687
+ nb: "Aktiver ",
28688
+ nn: "Aktiver ",
28689
+ pl: "Włącz ",
28690
+ ga: "Cumasaigh "
28691
+ },
28692
+ "handvirka véfengingu": {
28693
+ en: "manual challenges",
28694
+ nb: "manuelle utfordringer",
28695
+ nn: "manuelle utfordringar",
28696
+ pl: "ręczne wyzwania",
28697
+ ga: "dúshláin láimhe"
28698
+ },
28699
+ "(\"keppnishamur\")": {
28700
+ en: "(\"Pro mode\")",
28701
+ nb: "(\"Pro-modus\")",
28702
+ nn: "(\"Pro-modus\")",
28703
+ pl: "(\"Tryb Pro\")",
28704
+ ga: "(\"Modh Pro\")"
28705
+ },
28706
+ "Viðureignir sem standa yfir": {
28707
+ en: "Games in progress",
28708
+ nb: "Pågående spill",
28709
+ nn: "Pågåande spel",
28710
+ pl: "Trwające gry",
28711
+ ga: "Cluichí ar siúl"
28712
+ },
28713
+ click_on_game: click_on_game,
28714
+ " þú átt leik": {
28715
+ en: " it's your turn",
28716
+ nb: " det er din tur",
28717
+ nn: " det er din tur",
28718
+ pl: " to twoja kolej",
28719
+ ga: " tá sé do sheal"
28720
+ },
28721
+ "Skorað á þig": {
28722
+ en: "Other players have challenged you",
28723
+ nb: "Andre spillere har utfordret deg",
28724
+ nn: "Andre spelarar har utfordra deg",
28725
+ pl: "Inni gracze wyzwali cię",
28726
+ ga: "Tá dúshláin curtha ort ag imreoirí eile"
28727
+ },
28728
+ click_on_challenge: click_on_challenge,
28729
+ " til að hafna henni": {
28730
+ en: " to decline it",
28731
+ nb: " for å avslå det",
28732
+ nn: " for å avslå det",
28733
+ pl: " aby je odrzucić",
28734
+ ga: " chun é a dhiúltú"
28735
+ },
28736
+ "Þú skorar á aðra": {
28737
+ en: "You challenge other players",
28738
+ nb: "Du utfordrer andre spillere",
28739
+ nn: "Du utfordrar andre spelarar",
28740
+ pl: "Wyzwasz innych graczy",
28741
+ ga: "Dúshlánaíonn tú imreoirí eile"
28742
+ },
28743
+ " - smelltu á ": {
28744
+ en: " - click on ",
28745
+ nb: " - klikk på ",
28746
+ nn: " - klikk på ",
28747
+ pl: " - kliknij na ",
28748
+ ga: " - cliceáil ar "
28749
+ },
28750
+ " til að afturkalla áskorun": {
28751
+ en: " to retract an issued challenge",
28752
+ nb: " for å trekke tilbake en utfordring",
28753
+ nn: " for å trekkje tilbake ei utfordring",
28754
+ pl: " aby wycofać wydane wyzwanie",
28755
+ ga: " chun dúshlán eisithe a tharraingt siar"
28756
+ },
28757
+ "Nýlegar viðureignir þínar": {
28758
+ en: "Your recent games",
28759
+ nb: "Dine nylige spill",
28760
+ nn: "Dine nylege spel",
28761
+ pl: "Twoje ostatnie gry",
28762
+ ga: "Do chluichí le déanaí"
28763
+ },
28764
+ click_to_review: click_to_review,
28765
+ "Einkenni eða nafn": {
28766
+ en: "Identifier or name",
28767
+ nb: "Identifikator eller navn",
28768
+ nn: "Identifikator eller namn",
28769
+ pl: "Identyfikator lub nazwa",
28770
+ ga: "Aitheantóir nó ainm"
28771
+ },
28772
+ "Upplýsingar og hjálp": {
28773
+ en: "Information and help",
28774
+ nb: "Informasjon og hjelp",
28775
+ nn: "Informasjon og hjelp",
28776
+ pl: "Informacje i pomoc",
28777
+ ga: "Eolas agus cabhair"
28778
+ },
28779
+ "1 dagur": {
28780
+ en: "1 day",
28781
+ nb: "1 dag",
28782
+ nn: "1 dag",
28783
+ pl: "1 dzień",
28784
+ ga: "1 lá"
28785
+ },
28786
+ " dagar": {
28787
+ en: " days",
28788
+ nb: " dager",
28789
+ nn: " dagar",
28790
+ pl: " dni",
28791
+ ga: " laethanta"
28792
+ },
28793
+ " og ": {
28794
+ en: " and ",
28795
+ nb: " og ",
28796
+ nn: " og ",
28797
+ pl: " i ",
28798
+ ga: " agus "
28799
+ },
28800
+ "1 klst": {
28801
+ en: "1 hour",
28802
+ nb: "1 time",
28803
+ nn: "1 time",
28804
+ pl: "1 godzina",
28805
+ ga: "1 uair"
28806
+ },
28807
+ " klst": {
28808
+ en: " hours",
28809
+ nb: " timer",
28810
+ nn: " timar",
28811
+ pl: " godzin",
28812
+ ga: " uair an chloig"
28813
+ },
28814
+ "1 mínúta": {
28815
+ en: "1 minute",
28816
+ nb: "1 minutt",
28817
+ nn: "1 minutt",
28818
+ pl: "1 minuta",
28819
+ ga: "1 nóiméad"
28820
+ },
28821
+ " mínútur": {
28822
+ en: " minutes",
28823
+ nb: " minutter",
28824
+ nn: " minutt",
28825
+ pl: " minuty",
28826
+ ga: " nóiméad"
28827
+ },
28828
+ "Viðureign með klukku": {
28829
+ en: "Timed game",
28830
+ nb: "Tidsbegrenset spill",
28831
+ nn: "Tidsbegrensa spel",
28832
+ pl: "Gra zegarowa",
28833
+ ga: "Cluiche ama"
28834
+ },
28835
+ "Áskrifandi": {
28836
+ en: "Subscriber",
28837
+ nb: "Abonnent",
28838
+ nn: "Abonnent",
28839
+ pl: "Subskrybent",
28840
+ ga: "Síntiúsóir"
28841
+ },
28842
+ "Nýjustu viðureignir": {
28843
+ en: "Recent games",
28844
+ nb: "Nylige spill",
28845
+ nn: "Nylege spel",
28846
+ pl: "Najnowsze gry",
28847
+ ga: "Cluichí is déanaí"
28848
+ },
28849
+ Loka: Loka,
28850
+ "Besta orð ": {
28851
+ en: "Best word ",
28852
+ nb: "Beste ord ",
28853
+ nn: "Beste ord ",
28854
+ pl: "Najlepsze słowo ",
28855
+ ga: "An focal is fearr "
28856
+ },
28857
+ "Hæsta skor ": {
28858
+ en: "Highest score ",
28859
+ nb: "Høyeste poengsum ",
28860
+ nn: "Høgaste poengsum ",
28861
+ pl: "Najwyższy wynik ",
28862
+ ga: "An scór is airde "
28863
+ },
28864
+ " stig": {
28865
+ en: " points",
28866
+ nb: " poeng",
28867
+ nn: " poeng",
28868
+ pl: " punkty",
28869
+ ga: " pointí"
28870
+ },
28871
+ "Til hamingju með sigurinn!": {
28872
+ en: "You won - congratulations!",
28873
+ nb: "Du vant - gratulerer!",
28874
+ nn: "Du vann - gratulerer!",
28875
+ pl: "Wygrałeś - gratulacje!",
28876
+ ga: "Bhuaigh tú - comhghairdeas!"
28877
+ },
28878
+ "Viðureigninni er lokið": {
28879
+ en: "Game over",
28880
+ nb: "Spillet er over",
28881
+ nn: "Spelet er over",
28882
+ pl: "Gra zakończona",
28883
+ ga: "Cluiche thart"
28884
+ },
28885
+ opponent_emptied_rack: opponent_emptied_rack,
28886
+ "Viltu gefa leikinn?": {
28887
+ en: "Do you want to resign the game?",
28888
+ nb: "Ønsker du å gi opp spillet?",
28889
+ nn: "Ønskjer du å gje opp spelet?",
28890
+ pl: "Czy chcesz zrezygnować z gry?",
28891
+ ga: "Ar mhaith leat éirí as an gcluiche?"
28892
+ },
28893
+ " Já": {
28894
+ en: " Yes",
28895
+ nb: " Ja",
28896
+ nn: " Ja",
28897
+ pl: " Tak",
28898
+ ga: " Tá"
28899
+ },
28900
+ " Nei": {
28901
+ en: " No",
28902
+ nb: " Nei",
28903
+ nn: " Nei",
28904
+ pl: " Nie",
28905
+ ga: " Níl"
28906
+ },
28907
+ "Segja pass?": {
28908
+ en: "Pass the turn?",
28909
+ nb: "Passere turen?",
28910
+ nn: "Passere turen?",
28911
+ pl: "Spasować kolej?",
28912
+ ga: "Pas an tsealaíocht?"
28913
+ },
28914
+ "2x3 pöss í röð ljúka viðureign": {
28915
+ en: "2x3 passes in a row end the game",
28916
+ nb: "2x3 pasninger på rad avslutter spillet",
28917
+ nn: "2x3 pasningar på rad avsluttar spelet",
28918
+ pl: "2x3 pasy z rzędu kończą grę",
28919
+ ga: "Críochnaíonn 2x3 pasanna as a chéile an cluiche"
28920
+ },
28921
+ "Viðureign lýkur þar með": {
28922
+ en: "This finishes the game",
28923
+ nb: "Dette avslutter spillet",
28924
+ nn: "Dette avsluttar spelet",
28925
+ pl: "To kończy grę",
28926
+ ga: "Críochnaíonn sé seo an cluiche"
28927
+ },
28928
+ Skipta: Skipta,
28929
+ "Smelltu á flísarnar sem þú vilt skipta": {
28930
+ en: "Click on the tiles that you wish to exchange",
28931
+ nb: "Klikk på brikkene du ønsker å bytte",
28932
+ nn: "Klikk på brikkene du ønskjer å byte",
28933
+ pl: "Kliknij na kafelki, które chcesz wymienić",
28934
+ ga: "Cliceáil ar na tíleanna ar mhaith leat a mhalartú"
28935
+ },
28936
+ "Véfengja lögn?": {
28937
+ en: "Challenge move?",
28938
+ nb: "Utfordre trekk?",
28939
+ nn: "Utfordre trekk?",
28940
+ pl: "Wyzwanie ruchu?",
28941
+ ga: "Dúshlán bogadh?"
28942
+ },
28943
+ "Röng véfenging kostar 10 stig": {
28944
+ en: "Wrong challenge costs 10 points",
28945
+ nb: "Feil utfordring koster 10 poeng",
28946
+ nn: "Feil utfordring kostar 10 poeng",
28947
+ pl: "Błędne wyzwanie kosztuje 10 punktów",
28948
+ ga: "Cosnaíonn dúshlán mícheart 10 bpointe"
28949
+ },
28950
+ "Er álínis": {
28951
+ en: "Is online",
28952
+ nb: "Er pålogget",
28953
+ nn: "Er pålogga",
28954
+ pl: "Jest online",
28955
+ ga: "Ar líne"
28956
+ },
28957
+ "Álínis?": {
28958
+ en: "Online?",
28959
+ nb: "Pålogget?",
28960
+ nn: "Pålogga?",
28961
+ pl: "Online?",
28962
+ ga: "Ar líne?"
28963
+ },
28964
+ "Röð": {
28965
+ en: "Rank",
28966
+ nb: "Rangering",
28967
+ nn: "Rangering",
28968
+ pl: "Ranking",
28969
+ ga: "Rang"
28970
+ },
28971
+ "Röð í gær": {
28972
+ en: "Rank yesterday",
28973
+ nb: "Rangering i går",
28974
+ nn: "Rangering i går",
28975
+ pl: "Ranking wczoraj",
28976
+ ga: "Rang inné"
28977
+ },
28978
+ "Röð fyrir viku": {
28979
+ en: "Rank a week ago",
28980
+ nb: "Rangering for en uke siden",
28981
+ nn: "Rangering for ei veke sidan",
28982
+ pl: "Ranking tydzień temu",
28983
+ ga: "Rang seachtain ó shin"
28984
+ },
28985
+ "1d": {
28986
+ en: "1d",
28987
+ nb: "1d",
28988
+ nn: "1d",
28989
+ pl: "1d",
28990
+ ga: "1l"
28991
+ },
28992
+ "7d": {
28993
+ en: "7d",
28994
+ nb: "7d",
28995
+ nn: "7d",
28996
+ pl: "7d",
28997
+ ga: "7l"
28998
+ },
28999
+ "30d": {
29000
+ en: "30d",
29001
+ nb: "30d",
29002
+ nn: "30d",
29003
+ pl: "30d",
29004
+ ga: "30l"
29005
+ },
29006
+ "Elo-stig": {
29007
+ en: "Elo points",
29008
+ nb: "Elo-poeng",
29009
+ nn: "Elo-poeng",
29010
+ pl: "Punkty Elo",
29011
+ ga: "Pointí Elo"
29012
+ },
29013
+ "Elo-stig í gær": {
29014
+ en: "Elo points yesterday",
29015
+ nb: "Elo-poeng i går",
29016
+ nn: "Elo-poeng i går",
29017
+ pl: "Punkty Elo wczoraj",
29018
+ ga: "Pointí Elo inné"
29019
+ },
29020
+ "Elo-stig fyrir viku": {
29021
+ en: "Elo points a week ago",
29022
+ nb: "Elo-poeng for en uke siden",
29023
+ nn: "Elo-poeng for ei veke sidan",
29024
+ pl: "Punkty Elo tydzień temu",
29025
+ ga: "Pointí Elo seachtain ó shin"
29026
+ },
29027
+ "Elo-stig fyrir mánuði": {
29028
+ en: "Elo points a month ago",
29029
+ nb: "Elo-poeng for en måned siden",
29030
+ nn: "Elo-poeng for ein månad sidan",
29031
+ pl: "Punkty Elo miesiąc temu",
29032
+ ga: "Pointí Elo mí ó shin"
29033
+ },
29034
+ elo_list_choice: elo_list_choice,
29035
+ stats_choice: stats_choice,
29036
+ "Fjöldi viðureigna": {
29037
+ en: "Number of games",
29038
+ nb: "Antall spill",
29039
+ nn: "Tal på spel",
29040
+ pl: "Liczba gier",
29041
+ ga: "Líon cluichí"
29042
+ },
29043
+ Vinningshlutfall: Vinningshlutfall,
29044
+ "Meðalstigafjöldi": {
29045
+ en: "Average score",
29046
+ nb: "Gjennomsnittlig poengsum",
29047
+ nn: "Gjennomsnittleg poengsum",
29048
+ pl: "Średni wynik",
29049
+ ga: "Scór meánach"
29050
+ },
29051
+ " gegn öllum ": {
29052
+ en: " against all ",
29053
+ nb: " mot alle ",
29054
+ nn: " mot alle ",
29055
+ pl: " przeciwko wszystkim ",
29056
+ ga: " in aghaidh gach "
29057
+ },
29058
+ " gegn þér ": {
29059
+ en: " against you ",
29060
+ nb: " mot deg ",
29061
+ nn: " mot deg ",
29062
+ pl: " przeciwko tobie ",
29063
+ ga: " in aghaidh tú "
29064
+ },
29065
+ " - veldu lengd viðureignar:": {
29066
+ en: " - choose game duration:",
29067
+ nb: " - velg spillvarighet:",
29068
+ nn: " - vel spelvarigheit:",
29069
+ pl: " - wybierz czas trwania gry:",
29070
+ ga: " - roghnaigh fad an chluiche:"
29071
+ },
29072
+ "2 x 10 mínútur": {
29073
+ en: "2 x 10 minutes",
29074
+ nb: "2 x 10 minutter",
29075
+ nn: "2 x 10 minutt",
29076
+ pl: "2 x 10 minut",
29077
+ ga: "2 x 10 nóiméad"
29078
+ },
29079
+ "2 x 15 mínútur": {
29080
+ en: "2 x 15 minutes",
29081
+ nb: "2 x 15 minutter",
29082
+ nn: "2 x 15 minutt",
29083
+ pl: "2 x 15 minut",
29084
+ ga: "2 x 15 nóiméad"
29085
+ },
29086
+ "2 x 20 mínútur": {
29087
+ en: "2 x 20 minutes",
29088
+ nb: "2 x 20 minutter",
29089
+ nn: "2 x 20 minutt",
29090
+ pl: "2 x 20 minut",
29091
+ ga: "2 x 20 nóiméad"
29092
+ },
29093
+ "2 x 25 mínútur": {
29094
+ en: "2 x 25 minutes",
29095
+ nb: "2 x 25 minutter",
29096
+ nn: "2 x 25 minutt",
29097
+ pl: "2 x 25 minut",
29098
+ ga: "2 x 25 nóiméad"
29099
+ },
29100
+ "2 x 30 mínútur": {
29101
+ en: "2 x 30 minutes",
29102
+ nb: "2 x 30 minutter",
29103
+ nn: "2 x 30 minutt",
29104
+ pl: "2 x 30 minut",
29105
+ ga: "2 x 30 nóiméad"
29106
+ },
29107
+ "Báðir leikmenn lýsa því yfir að þeir skrafla ": {
29108
+ en: "Both players declare that they play ",
29109
+ nb: "Begge spillere erklærer at de spiller ",
29110
+ nn: "Begge spelarar erklærer at dei spelar ",
29111
+ pl: "Obaj gracze deklarują, że grają ",
29112
+ ga: "Dearbhaíonn an bheirt imreoirí go n-imríonn siad "
29113
+ },
29114
+ "Flísar sem eftir eru": {
29115
+ en: "Tiles remaining",
29116
+ nb: "Fliser igjen",
29117
+ nn: "Fliser igjen",
29118
+ pl: "Pozostałe kafelki",
29119
+ ga: "Tíleanna fágtha"
29120
+ },
29121
+ "Hvaða staf táknar auða flísin?": {
29122
+ en: "Which letter does the blank tile represent?",
29123
+ nb: "Hvilken bokstav representerer den blanke flisen?",
29124
+ nn: "Kva bokstav representerer den blanke flisa?",
29125
+ pl: "Jaką literę reprezentuje pusty kafelek?",
29126
+ ga: "Cén litir a léiríonn an tíl bán?"
29127
+ },
29128
+ "Þvinga til uppgjafar": {
29129
+ en: "Force to resign",
29130
+ nb: "Tving til å gi opp",
29131
+ nn: "Tving til å gje opp",
29132
+ pl: "Zmusić do rezygnacji",
29133
+ ga: "Éignigh le héirí as"
29134
+ },
29135
+ "14 dagar liðnir án leiks": {
29136
+ en: "14 days elapsed without a move",
29137
+ nb: "14 dager gått uten trekk",
29138
+ nn: "14 dagar gått utan trekk",
29139
+ pl: "Upłynęło 14 dni bez ruchu",
29140
+ ga: "14 lá caite gan bogadh"
29141
+ },
29142
+ "Gefa viðureign": {
29143
+ en: "Resign from game",
29144
+ nb: "Gi opp spillet",
29145
+ nn: "Gje opp spelet",
29146
+ pl: "Zrezygnować z gry",
29147
+ ga: "Éirigh as an gcluiche"
29148
+ },
29149
+ "Skipta stöfum": {
29150
+ en: "Exchange tiles",
29151
+ nb: "Bytt fliser",
29152
+ nn: "Byt fliser",
29153
+ pl: "Wymień kafelki",
29154
+ ga: "Malartú tíleanna"
29155
+ },
29156
+ word_not_found: word_not_found,
29157
+ "Smelltu til að fletta upp": {
29158
+ en: "Click to look up",
29159
+ nb: "Klikk for å slå opp",
29160
+ nn: "Klikk for å slå opp",
29161
+ pl: "Kliknij, aby wyszukać",
29162
+ ga: "Cliceáil chun cuardach"
29163
+ },
29164
+ "Skoða yfirlit": {
29165
+ en: "Review game",
29166
+ nb: "Gjennomgå spill",
29167
+ nn: "Gjennomgå spel",
29168
+ pl: "Przejrzyj grę",
29169
+ ga: "Athbhreithniú cluiche"
29170
+ },
29171
+ "Skraflað án hjálpartækja": {
29172
+ en: "Game without helpers or tools",
29173
+ nb: "Spill uten hjelpemidler eller verktøy",
29174
+ nn: "Spel utan hjelpemiddel eller verktøy",
29175
+ pl: "Gra bez pomocy lub narzędzi",
29176
+ ga: "Cluiche gan chúntóirí nó uirlisí"
29177
+ },
29178
+ "Skraflar án hjálpartækja": {
29179
+ en: "Plays without helpers or tools",
29180
+ nb: "Spiller uten hjelpemidler eller verktøy",
29181
+ nn: "Spelar utan hjelpemiddel eller verktøy",
29182
+ pl: "Gra bez pomocy lub narzędzi",
29183
+ ga: "Imríonn gan chúntóirí nó uirlisí"
29184
+ },
29185
+ "Til í viðureign með klukku": {
29186
+ en: "Willing to play timed games",
29187
+ nb: "Villig til å spille tidsbegrensede spill",
29188
+ nn: "Villig til å spele tidsbegrensa spel",
29189
+ pl: "Chętny do gry w gry zegarowe",
29190
+ ga: "Toilteanach cluichí ama a imirt"
29191
+ },
29192
+ "Álínis og tekur við áskorunum": {
29193
+ en: "Online and accepting challenges",
29194
+ nb: "Pålogget og aksepterer utfordringer",
29195
+ nn: "Pålogga og aksepterer utfordringar",
29196
+ pl: "Online i akceptuje wyzwania",
29197
+ ga: "Ar líne agus ag glacadh le dúshláin"
29198
+ },
29199
+ "Enginn stafur lagður niður": {
29200
+ en: "No tile played",
29201
+ nb: "Ingen flis spilt",
29202
+ nn: "Inga flis spelt",
29203
+ pl: "Żaden kafelek nie został położony",
29204
+ ga: "Níor imríodh aon tíl"
29205
+ },
29206
+ "Fyrsta orð verður að liggja um byrjunarreitinn": {
29207
+ en: "First word must cover the start square",
29208
+ nb: "Første ord må dekke startfeltet",
29209
+ nn: "Første ord må dekkje startfeltet",
29210
+ pl: "Pierwsze słowo musi pokryć pole startowe",
29211
+ ga: "Caithfidh an chéad fhocal an cearnóg tosaigh a chlúdach"
29212
+ },
29213
+ "Orð verður að vera samfellt á borðinu": {
29214
+ en: "Word must be placed consecutively on the board",
29215
+ nb: "Ord må plasseres etter hverandre på brettet",
29216
+ nn: "Ord må plasserast etter kvarandre på brettet",
29217
+ pl: "Słowo musi być umieszczone kolejno na planszy",
29218
+ ga: "Caithfear an focal a chur go leanúnach ar an mbord"
29219
+ },
29220
+ "Orð verður að tengjast orði sem fyrir er": {
29221
+ en: "Word must be connected to another word on the board",
29222
+ nb: "Ord må være koblet til et annet ord på brettet",
29223
+ nn: "Ord må vere kopla til eit anna ord på brettet",
29224
+ pl: "Słowo musi być połączone z innym słowem na planszy",
29225
+ ga: "Caithfidh an focal a bheith ceangailte le focal eile ar an mbord"
29226
+ },
29227
+ "Reitur þegar upptekinn": {
29228
+ en: "Square is already occupied",
29229
+ nb: "Feltet er allerede opptatt",
29230
+ nn: "Feltet er allereie oppteke",
29231
+ pl: "Pole jest już zajęte",
29232
+ ga: "Tá an cearnóg áitithe cheana"
29233
+ },
29234
+ "Ekki má vera eyða í orði": {
29235
+ en: "Word cannot contain a space",
29236
+ nb: "Ord kan ikke inneholde et mellomrom",
29237
+ nn: "Ord kan ikkje innehalde eit mellomrom",
29238
+ pl: "Słowo nie może zawierać spacji",
29239
+ ga: "Ní féidir spás a bheith i bhfocal"
29240
+ },
29241
+ "Of margir stafir lagðir niður": {
29242
+ en: "Too many tiles laid down",
29243
+ nb: "For mange fliser lagt ned",
29244
+ nn: "For mange fliser lagde ned",
29245
+ pl: "Położono zbyt wiele kafelków",
29246
+ ga: "Leagadh síos an iomarca tíleanna"
29247
+ },
29248
+ "Stafur er ekki í rekkanum": {
29249
+ en: "Tile is not present in the player's rack",
29250
+ nb: "Flisen er ikke til stede i spillerens stativ",
29251
+ nn: "Flisa er ikkje til stades i spelaren sitt stativ",
29252
+ pl: "Kafelek nie znajduje się na stojaku gracza",
29253
+ ga: "Níl an tíl i raca an imreora"
29254
+ },
29255
+ "Of fáir stafir eftir, skipting ekki leyfð": {
29256
+ en: "Too few tiles left in bag; exchange not permitted",
29257
+ nb: "For få fliser igjen i posen; bytte ikke tillatt",
29258
+ nn: "For få fliser igjen i posen; byte ikkje tillate",
29259
+ pl: "Zbyt mało kafelków w worku; wymiana niedozwolona",
29260
+ ga: "Ró-bheag tíleanna fágtha sa mhála; malartú toirmiscthe"
29261
+ },
29262
+ "Of mörgum stöfum skipt": {
29263
+ en: "Too many tiles exchanged",
29264
+ nb: "For mange fliser byttet",
29265
+ nn: "For mange fliser bytte",
29266
+ pl: "Wymieniono zbyt wiele kafelków",
29267
+ ga: "Malartaíodh an iomarca tíleanna"
29268
+ },
29269
+ "Leik vantar á borðið - endurglæðið vefráparann": {
29270
+ en: "Move missing from board - refresh browser",
29271
+ nb: "Trekk mangler på brettet - oppdater nettleseren",
29272
+ nn: "Trekk manglar på brettet - oppdater nettlesaren",
29273
+ pl: "Ruch nieobecny na planszy - odśwież przeglądarkę",
29274
+ ga: "Bogadh in easnamh ón mbord - athnuaigh an brabhsálaí"
29275
+ },
29276
+ "Notandi ekki innskráður - endurglæðið vefráparann": {
29277
+ en: "User not logged in - refresh browser",
29278
+ nb: "Bruker ikke logget inn - oppdater nettleseren",
29279
+ nn: "Brukar ikkje logga inn - oppdater nettlesaren",
29280
+ pl: "Użytkownik nie jest zalogowany - odśwież przeglądarkę",
29281
+ ga: "Úsáideoir gan logáil isteach - athnuaigh an brabhsálaí"
29282
+ },
29283
+ "Rangur eða óþekktur notandi": {
29284
+ en: "Wrong or unknown user",
29285
+ nb: "Feil eller ukjent bruker",
29286
+ nn: "Feil eller ukjend brukar",
29287
+ pl: "Błędny lub nieznany użytkownik",
29288
+ ga: "Úsáideoir mícheart nó anaithnid"
29289
+ },
29290
+ "Viðureign finnst ekki": {
29291
+ en: "Game not found",
29292
+ nb: "Spill ikke funnet",
29293
+ nn: "Spel ikkje funne",
29294
+ pl: "Gra nie znaleziona",
29295
+ ga: "Cluiche gan aimsiú"
29296
+ },
29297
+ "Viðureign er ekki utan tímamarka": {
29298
+ en: "Game has not exceeded time limit",
29299
+ nb: "Spillet har ikke overskredet tidsbegrensningen",
29300
+ nn: "Spelet har ikkje overskride tidsbegrensinga",
29301
+ pl: "Gra nie przekroczyła limitu czasu",
29302
+ ga: "Níor sháraigh an cluiche an teorainn ama"
29303
+ },
29304
+ "Netþjónn gat ekki tekið við leiknum - reyndu aftur": {
29305
+ en: "Server is unable to process move - try again",
29306
+ nb: "Serveren kan ikke behandle trekket - prøv igjen",
29307
+ nn: "Tenaren kan ikkje behandle trekket - prøv igjen",
29308
+ pl: "Serwer nie może przetworzyć ruchu - spróbuj ponownie",
29309
+ ga: "Ní féidir leis an bhfreastalaí an gluaiseacht a phróiseáil - bain triail eile as"
29310
+ },
29311
+ "Véfenging er ekki möguleg í þessari viðureign": {
29312
+ en: "A challenge is not possible in this game",
29313
+ nb: "En utfordring er ikke mulig i dette spillet",
29314
+ nn: "Ei utfordring er ikkje mogleg i dette spelet",
29315
+ pl: "Wyzwanie nie jest możliwe w tej grze",
29316
+ ga: "Ní féidir dúshlán a thabhairt sa chluiche seo"
29317
+ },
29318
+ "Síðasti leikur er ekki véfengjanlegur": {
29319
+ en: "The last move cannot be challenged",
29320
+ nb: "Siste trekk kan ikke utfordres",
29321
+ nn: "Siste trekk kan ikkje utfordrast",
29322
+ pl: "Ostatniego ruchu nie można zakwestionować",
29323
+ ga: "Ní féidir an gluaiseacht deireanach a dhúshlánú"
29324
+ },
29325
+ "Aðeins véfenging eða pass leyfileg": {
29326
+ en: "Only a challenge or a pass are possible",
29327
+ nb: "Bare en utfordring eller pass er mulig",
29328
+ nn: "Berre ei utfordring eller pass er mogleg",
29329
+ pl: "Możliwe jest tylko wyzwanie lub pas",
29330
+ ga: "Níl ach dúshlán nó pas indéanta"
29331
+ },
29332
+ welcome_2: welcome_2,
29333
+ welcome_1: welcome_1,
29334
+ welcome_0: welcome_0,
29335
+ "Skrái þig inn...": {
29336
+ en: "Logging you in...",
29337
+ nb: "Logger deg inn...",
29338
+ nn: "Loggar deg inn...",
29339
+ pl: "Logowanie...",
29340
+ ga: "Ag logáil isteach tú..."
29341
+ },
29342
+ "Innskrá": {
29343
+ en: "Log in",
29344
+ nb: "Logg inn",
29345
+ nn: "Logg inn",
29346
+ pl: "Zaloguj się",
29347
+ ga: "Logáil isteach"
29348
+ },
29349
+ "Smelltu til að raða eftir seinni staf": {
29350
+ en: "Click to sort by last letter",
29351
+ nb: "Klikk for å sortere etter siste bokstav",
29352
+ nn: "Klikk for å sortere etter siste bokstav",
29353
+ pl: "Kliknij, aby posortować według ostatniej litery",
29354
+ ga: "Cliceáil chun sórtáil de réir an litir deireanaigh"
29355
+ },
29356
+ "Smelltu til að raða eftir fyrri staf": {
29357
+ en: "Click to sort by first letter",
29358
+ nb: "Klikk for å sortere etter første bokstav",
29359
+ nn: "Klikk for å sortere etter første bokstav",
29360
+ pl: "Kliknij, aby posortować według pierwszej litery",
29361
+ ga: "Cliceáil chun sórtáil de réir an chéad litir"
29362
+ },
29363
+ "Hvernig reitirnir margfalda stigin": {
29364
+ en: "How squares multiply points",
29365
+ nb: "Hvordan ruter multipliserer poeng",
29366
+ nn: "Korleis ruter multipliserer poeng",
29367
+ pl: "Jak kwadraty mnożą punkty",
29368
+ ga: "Conas a iolraíonn cearnóga pointí"
29369
+ },
29370
+ "Loka þessari hjálp": {
29371
+ en: "Close this help",
29372
+ nb: "Lukk denne hjelpen",
29373
+ nn: "Lukk denne hjelpa",
29374
+ pl: "Zamknij tę pomoc",
29375
+ ga: "Dún an chabhair seo"
29376
+ },
29377
+ "Stokka upp rekka": {
29378
+ en: "Shuffle rack",
29379
+ nb: "Stokk stativet",
29380
+ nn: "Stokk stativet",
29381
+ pl: "Przetasuj stojak",
29382
+ ga: "Measc raca"
29383
+ },
29384
+ "Færa stafi aftur í rekka": {
29385
+ en: "Recall tiles into rack",
29386
+ nb: "Hent fliser tilbake til stativet",
29387
+ nn: "Hent fliser tilbake til stativet",
29388
+ pl: "Przywróć kafelki do stojaka",
29389
+ ga: "Cuimhnigh tíleanna isteach sa raca"
29390
+ },
29391
+ "Besta mögulega lögn": {
29392
+ en: "Best possible move",
29393
+ nb: "Beste mulige trekk",
29394
+ nn: "Beste mogelege trekk",
29395
+ pl: "Najlepszy możliwy ruch",
29396
+ ga: "An bogadh is fearr is féidir"
29397
+ },
29398
+ "Nei, takk": {
29399
+ en: "No, thanks",
29400
+ nb: "Nei, takk",
29401
+ nn: "Nei, takk",
29402
+ pl: "Nie, dziękuję",
29403
+ ga: "Ní hea"
29404
+ },
29405
+ "Fyrri dagur": {
29406
+ en: "Previous day",
29407
+ nb: "Forrige dag",
29408
+ nn: "Førre dag",
29409
+ pl: "Poprzedni dzień",
29410
+ ga: "An lá roimhe"
29411
+ },
29412
+ "Næsti dagur": {
29413
+ en: "Next day",
29414
+ nb: "Neste dag",
29415
+ nn: "Neste dag",
29416
+ pl: "Następny dzień",
29417
+ ga: "An lá dar gcionn"
29418
+ },
29419
+ "Frammistaða": {
29420
+ en: "Performance",
29421
+ nb: "Ytelse",
29422
+ nn: "Yting",
29423
+ pl: "Wyniki",
29424
+ ga: "Feidhmíocht"
29425
+ },
29426
+ "Tölfræði": {
29427
+ en: "Statistics",
29428
+ nb: "Statistikk",
29429
+ nn: "Statistikk",
29430
+ pl: "Statystyki",
29431
+ ga: "Staitisticí"
29432
+ },
29433
+ Stigatafla: Stigatafla,
29434
+ "Tölfræði og stigatafla": {
29435
+ en: "Stats and leaderboard",
29436
+ nb: "Statistikk og toppliste",
29437
+ nn: "Statistikk og toppliste",
29438
+ pl: "Statystyki i tabela",
29439
+ ga: "Staitisticí agus clár"
29440
+ },
29441
+ "Þín besta": {
29442
+ en: "Your best",
29443
+ nb: "Din beste",
29444
+ nn: "Di beste",
29445
+ pl: "Twój najlepszy",
29446
+ ga: "Do cheann is fearr"
29447
+ },
29448
+ "Þú leiðir!": {
29449
+ en: "You lead!",
29450
+ nb: "Du leder!",
29451
+ nn: "Du leier!",
29452
+ pl: "Prowadzisz!",
29453
+ ga: "Tá tú chun tosaigh!"
29454
+ },
29455
+ "Best til þessa": {
29456
+ en: "Best so far",
29457
+ nb: "Beste hittil",
29458
+ nn: "Beste hittil",
29459
+ pl: "Najlepszy dotąd",
29460
+ ga: "Is fearr go dtí seo"
29461
+ },
29462
+ "Sýna besta leik": {
29463
+ en: "Show best move",
29464
+ nb: "Vis beste trekk",
29465
+ nn: "Vis beste trekk",
29466
+ pl: "Pokaż najlepszy ruch",
29467
+ ga: "Taispeáin an bogadh is fearr"
29468
+ },
29469
+ "Sýna leik": {
29470
+ en: "Show move",
29471
+ nb: "Vis trekk",
29472
+ nn: "Vis trekk",
29473
+ pl: "Pokaż ruch",
29474
+ ga: "Taispeáin bogadh"
29475
+ },
29476
+ "Smelltu til að sjá lausn": {
29477
+ en: "Click to see solution",
29478
+ nb: "Klikk for å se løsning",
29479
+ nn: "Klikk for å sjå løysing",
29480
+ pl: "Kliknij, aby zobaczyć rozwiązanie",
29481
+ ga: "Cliceáil chun an réiteach a fheiceáil"
29482
+ },
29483
+ "Þú": {
29484
+ en: "You",
29485
+ nb: "Du",
29486
+ nn: "Du",
29487
+ pl: "Ty",
29488
+ ga: "Tú"
29489
+ },
29490
+ "Sæki tölfræði...": {
29491
+ en: "Loading stats...",
29492
+ nb: "Laster statistikk...",
29493
+ nn: "Lastar statistikk...",
29494
+ pl: "Ładowanie statystyk...",
29495
+ ga: "Ag lódáil staitisticí..."
29496
+ },
29497
+ "Engin tölfræði til að sýna": {
29498
+ en: "No statistics to show",
29499
+ nb: "Ingen statistikk å vise",
29500
+ nn: "Ingen statistikk å vise",
29501
+ pl: "Brak statystyk do wyświetlenia",
29502
+ ga: "Níl aon staitisticí le taispeáint"
29503
+ },
29504
+ "Núverandi striklota": {
29505
+ en: "Current streak",
29506
+ nb: "Nåværende rekke",
29507
+ nn: "Noverande rekkje",
29508
+ pl: "Bieżąca seria",
29509
+ ga: "Sraith reatha"
29510
+ },
29511
+ "Lengsta striklota": {
29512
+ en: "Longest streak",
29513
+ nb: "Lengste rekke",
29514
+ nn: "Lengste rekkje",
29515
+ pl: "Najdłuższa seria",
29516
+ ga: "An tsraith is faide"
29517
+ },
29518
+ "Hæsta skori náð": {
29519
+ en: "Best score achieved",
29520
+ nb: "Beste oppnådde poengsum",
29521
+ nn: "Beste oppnådde poengsum",
29522
+ pl: "Najwyższy wynik",
29523
+ ga: "An scór is fearr"
29524
+ },
29525
+ "Striklota hæsta skors": {
29526
+ en: "Best score streak",
29527
+ nb: "Rekke med beste poengsum",
29528
+ nn: "Rekkje med beste poengsum",
29529
+ pl: "Seria najwyższych wyników",
29530
+ ga: "Sraith scór is fearr"
29531
+ },
29532
+ "Heildarfjöldi daga": {
29533
+ en: "Total days played",
29534
+ nb: "Totalt antall dager",
29535
+ nn: "Totalt tal på dagar",
29536
+ pl: "Łączna liczba dni",
29537
+ ga: "Iomlán laethanta"
29538
+ },
29539
+ "Hleð stigatöflu...": {
29540
+ en: "Loading leaderboard...",
29541
+ nb: "Laster toppliste...",
29542
+ nn: "Lastar toppliste...",
29543
+ pl: "Ładowanie tabeli...",
29544
+ ga: "Ag lódáil an chláir..."
29545
+ },
29546
+ "Engin stig skráð enn": {
29547
+ en: "No scores yet",
29548
+ nb: "Ingen poeng ennå",
29549
+ nn: "Ingen poeng enno",
29550
+ pl: "Brak wyników",
29551
+ ga: "Gan scóir fós"
29552
+ },
29553
+ "Reyna síðar": {
29554
+ en: "Try later",
29555
+ nb: "Prøv senere",
29556
+ nn: "Prøv seinare",
29557
+ pl: "Spróbuj później",
29558
+ ga: "Bain triail níos déanaí"
29559
+ },
29560
+ " mínútur.": {
29561
+ en: " minutes.",
29562
+ nb: " minutter.",
29563
+ nn: " minutt.",
29564
+ pl: " minut.",
29565
+ ga: " nóiméad."
29566
+ },
29567
+ date_format: date_format,
29568
+ january: january,
29569
+ february: february,
29570
+ march: march,
29571
+ april: april,
29572
+ may: may,
29573
+ june: june,
29574
+ july: july,
29575
+ august: august,
29576
+ september: september,
29577
+ october: october,
29578
+ november: november,
29579
+ december: december,
29580
+ "[sentinel]": {
29581
+ }
29582
+ };
29583
+
27639
29584
  /*
27640
29585
 
27641
- Types.ts
29586
+ i18n.ts
27642
29587
 
27643
- Common type definitions for the Explo/Netskrafl user interface
29588
+ Single page UI for Netskrafl/Explo using the Mithril library
27644
29589
 
27645
29590
  Copyright (C) 2025 Miðeind ehf.
27646
- Author: Vilhjalmur Thorsteinsson
29591
+ Author: Vilhjálmur Þorsteinsson
27647
29592
 
27648
29593
  The Creative Commons Attribution-NonCommercial 4.0
27649
29594
  International Public License (CC-BY-NC 4.0) applies to this software.
27650
29595
  For further information, see https://github.com/mideind/Netskrafl
27651
29596
 
29597
+
29598
+ This module contains internationalization (i18n) utility functions,
29599
+ allowing for translation of displayed text between languages.
29600
+
29601
+ Text messages are embedded directly from the messages.json file
29602
+ at build time.
29603
+
27652
29604
  */
27653
- // Global constants
27654
- const RACK_SIZE = 7;
27655
- const ROWIDS = 'ABCDEFGHIJKLMNO';
27656
- const BOARD_SIZE = ROWIDS.length;
27657
- const EXTRA_WIDE_LETTERS = 'q';
27658
- const WIDE_LETTERS = 'zxmæ';
27659
- const ZOOM_FACTOR = 1.5;
27660
- const ERROR_MESSAGES = {
27661
- // Translations are found in /static/assets/messages.json
27662
- 1: 'Enginn stafur lagður niður',
27663
- 2: 'Fyrsta orð verður liggja um byrjunarreitinn',
27664
- 3: 'Orð verður vera samfellt á borðinu',
27665
- 4: 'Orð verður tengjast orði sem fyrir er',
27666
- 5: 'Reitur þegar upptekinn',
27667
- 6: 'Ekki má vera eyða í orði',
27668
- 7: 'word_not_found',
27669
- 8: 'word_not_found',
27670
- 9: 'Of margir stafir lagðir niður',
27671
- 10: 'Stafur er ekki í rekkanum',
27672
- 11: 'Of fáir stafir eftir, skipting ekki leyfð',
27673
- 12: 'Of mörgum stöfum skipt',
27674
- 13: 'Leik vantar á borðið - notið F5/Refresh',
27675
- 14: 'Notandi ekki innskráður - notið F5/Refresh',
27676
- 15: 'Rangur eða óþekktur notandi',
27677
- 16: 'Viðureign finnst ekki',
27678
- 17: 'Viðureign er ekki utan tímamarka',
27679
- 18: 'Netþjónn gat ekki tekið við leiknum - reyndu aftur',
27680
- 19: 'Véfenging er ekki möguleg í þessari viðureign',
27681
- 20: 'Síðasti leikur er ekki véfengjanlegur',
27682
- 21: 'Aðeins véfenging eða pass leyfileg',
27683
- server: 'Netþjónn gat ekki tekið við leiknum - reyndu aftur',
27684
- };
29605
+ // Current exact user locale and fallback locale ("en" for "en_US"/"en_GB"/...)
29606
+ // This is overwritten in setLocale()
29607
+ let currentLocale = 'is_IS';
29608
+ let currentFallback = 'is';
29609
+ // Regex that matches embedded interpolations such as "Welcome, {username}!"
29610
+ // Interpolation identifiers should only contain ASCII characters, digits and '_'
29611
+ const rex = /{\s*(\w+)\s*}/g;
29612
+ let messages = {};
29613
+ let messagesLoaded = false;
29614
+ function getLocaleFromUrl() {
29615
+ // Check for a ?lang= query parameter to override the locale
29616
+ // Accepts formats like: lang=nb, lang=nb-NO, lang=nb_NO
29617
+ const params = new URLSearchParams(window.location.search);
29618
+ const lang = params.get('lang');
29619
+ if (!lang)
29620
+ return null;
29621
+ // Normalize: nb-NO → nb_NO
29622
+ return lang.replace('-', '_');
29623
+ }
29624
+ function hasAnyTranslation(msgs, locale) {
29625
+ // Return true if any translation is available for the given locale
29626
+ for (const key in msgs) {
29627
+ if (msgs[key][locale] !== undefined)
29628
+ return true;
29629
+ }
29630
+ return false;
29631
+ }
29632
+ function setLocale(locale, msgs) {
29633
+ // Set the current i18n locale and fallback
29634
+ currentLocale = locale;
29635
+ currentFallback = locale.split('_')[0];
29636
+ // For unsupported locales, i.e. locales that have no
29637
+ // translations available for them, fall back to English (U.S.).
29638
+ if (!hasAnyTranslation(msgs, currentLocale) &&
29639
+ !hasAnyTranslation(msgs, currentFallback)) {
29640
+ currentLocale = 'en_US';
29641
+ currentFallback = 'en';
29642
+ }
29643
+ // Flatten the Messages structure, enabling long strings
29644
+ // to be represented as string arrays in the messages.json file
29645
+ messages = {};
29646
+ for (const key in msgs) {
29647
+ for (const lc in msgs[key]) {
29648
+ let s = msgs[key][lc];
29649
+ if (Array.isArray(s))
29650
+ s = s.join('');
29651
+ if (messages[key] === undefined)
29652
+ messages[key] = {};
29653
+ // If the string s contains HTML markup of the form <tag>...</tag>,
29654
+ // convert it into a list of Mithril Vnode children corresponding to
29655
+ // the text and the tags
29656
+ if (s.match(/<[a-z]+>/)) {
29657
+ // Looks like the string contains HTML markup
29658
+ const vnodes = [];
29659
+ let i = 0;
29660
+ let tagMatch = null;
29661
+ while (i < s.length &&
29662
+ // biome-ignore lint/suspicious/noAssignInExpressions: Done for efficiency
29663
+ (tagMatch = s.slice(i).match(/<[a-z]+>/)) &&
29664
+ tagMatch.index !== undefined) {
29665
+ // Found what looks like an HTML tag
29666
+ // Calculate the index of the enclosed text within s
29667
+ const tag = tagMatch[0];
29668
+ const j = i + tagMatch.index + tag.length;
29669
+ // Find the end tag
29670
+ const end = s.indexOf(`</${tag.slice(1)}`, j);
29671
+ if (end < 0) {
29672
+ // No end tag - skip past this weirdness
29673
+ i = j;
29674
+ continue;
29675
+ }
29676
+ // Add the text preceding the tag
29677
+ if (tagMatch.index > 0)
29678
+ vnodes.push(s.slice(i, i + tagMatch.index));
29679
+ // Create the Mithril node corresponding to the tag and the enclosed text
29680
+ // and add it to the list
29681
+ vnodes.push(m(tag.slice(1, -1), s.slice(j, end)));
29682
+ // Advance the index past the end of the tag
29683
+ i = end + tag.length + 1;
29684
+ }
29685
+ // Push the final text part, if any
29686
+ if (i < s.length)
29687
+ vnodes.push(s.slice(i));
29688
+ // Reassign s to the list of vnodes
29689
+ s = vnodes;
29690
+ }
29691
+ messages[key][lc] = s;
29692
+ }
29693
+ }
29694
+ messagesLoaded = true;
29695
+ }
29696
+ function initMessages(locale) {
29697
+ // Initialize the i18n messages with the embedded messages data
29698
+ // and set the user's locale. A ?lang= URL parameter overrides the locale.
29699
+ const override = getLocaleFromUrl();
29700
+ setLocale(override || locale, messagesData);
29701
+ }
29702
+ function t(key, ips = {}) {
29703
+ // Main text translation function, supporting interpolation
29704
+ // and HTML tag substitution
29705
+ const msgDict = messages[key];
29706
+ if (msgDict === undefined)
29707
+ // No dictionary for this key - may actually be a missing entry
29708
+ return messagesLoaded ? key : '';
29709
+ // Lookup exact locale, then fallback, then resort to returning the key
29710
+ const message = msgDict[currentLocale] || msgDict[currentFallback] || key;
29711
+ // If we have an interpolation object, do the interpolation first
29712
+ return Object.keys(ips).length ? interpolate(message, ips) : message;
29713
+ }
29714
+ function ts(key, ips = {}) {
29715
+ // String translation function, supporting interpolation
29716
+ // but not HTML tag substitution
29717
+ const msgDict = messages[key];
29718
+ if (msgDict === undefined)
29719
+ // No dictionary for this key - may actually be a missing entry
29720
+ return messagesLoaded ? key : '';
29721
+ // Lookup exact locale, then fallback, then resort to returning the key
29722
+ const message = msgDict[currentLocale] || msgDict[currentFallback] || key;
29723
+ if (typeof message !== 'string')
29724
+ // This is actually an error - the client should be calling t() instead
29725
+ return '';
29726
+ // If we have an interpolation object, do the interpolation first
29727
+ return Object.keys(ips).length ? interpolate_string(message, ips) : message;
29728
+ }
29729
+ function mt(cls, children) {
29730
+ // Wrapper for the Mithril m() function that auto-translates
29731
+ // string and array arguments
29732
+ if (typeof children === 'string') {
29733
+ return m(cls, t(children));
29734
+ }
29735
+ if (Array.isArray(children)) {
29736
+ return m(cls, children.map((item) => (typeof item === 'string' ? t(item) : item)));
29737
+ }
29738
+ return m(cls, children);
29739
+ }
29740
+ function interpolate(message, ips) {
29741
+ // Replace interpolation placeholders with their corresponding values
29742
+ if (typeof message === 'string') {
29743
+ return message.replace(rex, (match, key) => ips[key] || match);
29744
+ }
29745
+ if (Array.isArray(message)) {
29746
+ return message.map((item) => interpolate(item, ips));
29747
+ }
29748
+ return message;
29749
+ }
29750
+ function interpolate_string(message, ips) {
29751
+ // Replace interpolation placeholders with their corresponding values
29752
+ return message.replace(rex, (match, key) => ips[key] || match);
29753
+ }
27685
29754
 
27686
29755
  /*
27687
29756
 
@@ -27708,6 +29777,21 @@ var hasZoomed = false;
27708
29777
  // Old-style (non-single-page) game URL prefix
27709
29778
  const BOARD_PREFIX = '/board?game=';
27710
29779
  const BOARD_PREFIX_LEN = BOARD_PREFIX.length;
29780
+ // Translation keys for month names (january, february, etc.)
29781
+ const MONTH_KEYS = [
29782
+ 'january',
29783
+ 'february',
29784
+ 'march',
29785
+ 'april',
29786
+ 'may',
29787
+ 'june',
29788
+ 'july',
29789
+ 'august',
29790
+ 'september',
29791
+ 'october',
29792
+ 'november',
29793
+ 'december',
29794
+ ];
27711
29795
  function addPinchZoom(attrs, funcZoomIn, funcZoomOut) {
27712
29796
  // Install event handlers for the pointer target
27713
29797
  attrs.onpointerdown = pointerdown_handler;
@@ -27922,6 +30006,15 @@ function valueOrK(value, breakpoint = 10000) {
27922
30006
  value = Math.round(value / 1000);
27923
30007
  return `${sign}${value}K`;
27924
30008
  }
30009
+ function formatDate(dateStr) {
30010
+ // Format YYYY-MM-DD to localized date using the date_format template
30011
+ // e.g., "2. október" in Icelandic, "October 2" in English
30012
+ // Ensure date is parsed as UTC for consistent formatting
30013
+ const date = new Date(`${dateStr}T00:00:00Z`);
30014
+ const day = date.getUTCDate().toString();
30015
+ const month = ts(MONTH_KEYS[date.getUTCMonth()]);
30016
+ return ts('date_format', { day, month });
30017
+ }
27925
30018
 
27926
30019
  /*
27927
30020
 
@@ -27941,10 +30034,14 @@ function valueOrK(value, breakpoint = 10000) {
27941
30034
  */
27942
30035
  class Actions {
27943
30036
  constructor(model) {
30037
+ this.riddleLoading = false; // Prevents concurrent loadRiddle calls
27944
30038
  this.model = model;
27945
30039
  // Media and Firebase listeners will be initialized
27946
30040
  // when view is available
27947
30041
  }
30042
+ get isRiddleLoading() {
30043
+ return this.riddleLoading;
30044
+ }
27948
30045
  onNavigateTo(routeName, params, view) {
27949
30046
  // We have navigated to a new route
27950
30047
  // If navigating to something other than help,
@@ -28450,13 +30547,18 @@ class Actions {
28450
30547
  this.model.leaderboard = [];
28451
30548
  }
28452
30549
  else {
28453
- // Convert dictionary to array and sort by score (desc), then timestamp (desc)
28454
- const entries = Object.keys(json).map((userId) => ({
30550
+ const { riddle } = this.model;
30551
+ const bestPossibleScore = riddle?.bestPossibleScore ?? Number.POSITIVE_INFINITY;
30552
+ // Convert dictionary to array and filter out invalid entries
30553
+ const entries = Object.keys(json)
30554
+ .map((userId) => ({
28455
30555
  userId: json[userId].userId || userId,
28456
30556
  displayName: json[userId].displayName || '',
28457
30557
  score: json[userId].score || 0,
28458
30558
  timestamp: json[userId].timestamp || '',
28459
- }));
30559
+ }))
30560
+ // Filter out invalid entries (score > bestPossibleScore)
30561
+ .filter((entry) => entry.score <= bestPossibleScore);
28460
30562
  // Sort by score descending, then by timestamp ascending (earlier first)
28461
30563
  entries.sort((a, b) => {
28462
30564
  if (b.score !== a.score) {
@@ -28498,217 +30600,77 @@ class Actions {
28498
30600
  // This enables clicking on it to see it on the board
28499
30601
  if (!moveExists) {
28500
30602
  riddle.playerMoves.push({
28501
- word,
28502
- score,
28503
- coord,
28504
- timestamp,
28505
- });
28506
- }
28507
- m.redraw();
28508
- }
28509
- async fetchRiddle(date, locale) {
28510
- // Create the game via model
28511
- if (!this.model)
28512
- throw new Error('Model is not initialized');
28513
- await this.model.initRiddle(date, locale);
28514
- // Attach Firebase listeners
28515
- this.attachListenerToRiddle(date, locale);
28516
- }
28517
- cleanupRiddle(date, locale) {
28518
- // Detach Firebase listeners
28519
- this.detachListenerFromRiddle(date, locale);
28520
- }
28521
- } // class Actions
28522
- function createRouteResolver(actions, view) {
28523
- // Return a map of routes to onmatch and render functions
28524
- const model = actions.model;
28525
- // let state = model.state;
28526
- return model.paths.reduce((acc, item) => {
28527
- acc[item.route] = {
28528
- // Navigating to a new route (passed in the second parameter)
28529
- onmatch: (args) => {
28530
- // Automatically close all dialogs
28531
- view.popAllDialogs();
28532
- view.resetScale();
28533
- actions.onNavigateTo(item.name, args, view);
28534
- },
28535
- // Render a view on a model
28536
- render: () => {
28537
- return view.appView(item.name);
28538
- },
28539
- };
28540
- return acc;
28541
- }, {});
28542
- }
28543
-
28544
- /*
28545
-
28546
- i8n.ts
28547
-
28548
- Single page UI for Netskrafl/Explo using the Mithril library
28549
-
28550
- Copyright (C) 2025 Miðeind ehf.
28551
- Author: Vilhjálmur Þorsteinsson
28552
-
28553
- The Creative Commons Attribution-NonCommercial 4.0
28554
- International Public License (CC-BY-NC 4.0) applies to this software.
28555
- For further information, see https://github.com/mideind/Netskrafl
28556
-
28557
-
28558
- This module contains internationalization (i18n) utility functions,
28559
- allowing for translation of displayed text between languages.
28560
-
28561
- Text messages for individual locales are loaded from the
28562
- /static/assets/messages.json file, which is fetched from the server.
28563
-
28564
- */
28565
- // Current exact user locale and fallback locale ("en" for "en_US"/"en_GB"/...)
28566
- // This is overwritten in setLocale()
28567
- let currentLocale = 'is_IS';
28568
- let currentFallback = 'is';
28569
- // Regex that matches embedded interpolations such as "Welcome, {username}!"
28570
- // Interpolation identifiers should only contain ASCII characters, digits and '_'
28571
- const rex = /{\s*(\w+)\s*}/g;
28572
- let messages = {};
28573
- let messagesLoaded = false;
28574
- function hasAnyTranslation(msgs, locale) {
28575
- // Return true if any translation is available for the given locale
28576
- for (const key in msgs) {
28577
- if (msgs[key][locale] !== undefined)
28578
- return true;
28579
- }
28580
- return false;
28581
- }
28582
- function setLocale(locale, msgs) {
28583
- // Set the current i18n locale and fallback
28584
- currentLocale = locale;
28585
- currentFallback = locale.split('_')[0];
28586
- // For unsupported locales, i.e. locales that have no
28587
- // translations available for them, fall back to English (U.S.).
28588
- if (!hasAnyTranslation(msgs, currentLocale) &&
28589
- !hasAnyTranslation(msgs, currentFallback)) {
28590
- currentLocale = 'en_US';
28591
- currentFallback = 'en';
28592
- }
28593
- // Flatten the Messages structure, enabling long strings
28594
- // to be represented as string arrays in the messages.json file
28595
- messages = {};
28596
- for (const key in msgs) {
28597
- for (const lc in msgs[key]) {
28598
- let s = msgs[key][lc];
28599
- if (Array.isArray(s))
28600
- s = s.join('');
28601
- if (messages[key] === undefined)
28602
- messages[key] = {};
28603
- // If the string s contains HTML markup of the form <tag>...</tag>,
28604
- // convert it into a list of Mithril Vnode children corresponding to
28605
- // the text and the tags
28606
- if (s.match(/<[a-z]+>/)) {
28607
- // Looks like the string contains HTML markup
28608
- const vnodes = [];
28609
- let i = 0;
28610
- let tagMatch = null;
28611
- while (i < s.length &&
28612
- // biome-ignore lint/suspicious/noAssignInExpressions: Done for efficiency
28613
- (tagMatch = s.slice(i).match(/<[a-z]+>/)) &&
28614
- tagMatch.index !== undefined) {
28615
- // Found what looks like an HTML tag
28616
- // Calculate the index of the enclosed text within s
28617
- const tag = tagMatch[0];
28618
- const j = i + tagMatch.index + tag.length;
28619
- // Find the end tag
28620
- const end = s.indexOf(`</${tag.slice(1)}`, j);
28621
- if (end < 0) {
28622
- // No end tag - skip past this weirdness
28623
- i = j;
28624
- continue;
28625
- }
28626
- // Add the text preceding the tag
28627
- if (tagMatch.index > 0)
28628
- vnodes.push(s.slice(i, i + tagMatch.index));
28629
- // Create the Mithril node corresponding to the tag and the enclosed text
28630
- // and add it to the list
28631
- vnodes.push(m(tag.slice(1, -1), s.slice(j, end)));
28632
- // Advance the index past the end of the tag
28633
- i = end + tag.length + 1;
28634
- }
28635
- // Push the final text part, if any
28636
- if (i < s.length)
28637
- vnodes.push(s.slice(i));
28638
- // Reassign s to the list of vnodes
28639
- s = vnodes;
28640
- }
28641
- messages[key][lc] = s;
28642
- }
28643
- }
28644
- messagesLoaded = true;
28645
- }
28646
- async function loadMessages(state, locale) {
28647
- // Load the internationalization message JSON file from the server
28648
- // and set the user's locale
28649
- try {
28650
- const messages = await requestWithoutAuth(state, {
28651
- method: 'GET',
28652
- url: '/static/assets/messages.json',
28653
- withCredentials: false, // Cookies are not allowed for CORS request
28654
- });
28655
- setLocale(locale, messages);
28656
- }
28657
- catch {
28658
- setLocale(locale, {});
28659
- }
28660
- }
28661
- function t(key, ips = {}) {
28662
- // Main text translation function, supporting interpolation
28663
- // and HTML tag substitution
28664
- const msgDict = messages[key];
28665
- if (msgDict === undefined)
28666
- // No dictionary for this key - may actually be a missing entry
28667
- return messagesLoaded ? key : '';
28668
- // Lookup exact locale, then fallback, then resort to returning the key
28669
- const message = msgDict[currentLocale] || msgDict[currentFallback] || key;
28670
- // If we have an interpolation object, do the interpolation first
28671
- return Object.keys(ips).length ? interpolate(message, ips) : message;
28672
- }
28673
- function ts(key, ips = {}) {
28674
- // String translation function, supporting interpolation
28675
- // but not HTML tag substitution
28676
- const msgDict = messages[key];
28677
- if (msgDict === undefined)
28678
- // No dictionary for this key - may actually be a missing entry
28679
- return messagesLoaded ? key : '';
28680
- // Lookup exact locale, then fallback, then resort to returning the key
28681
- const message = msgDict[currentLocale] || msgDict[currentFallback] || key;
28682
- if (typeof message !== 'string')
28683
- // This is actually an error - the client should be calling t() instead
28684
- return '';
28685
- // If we have an interpolation object, do the interpolation first
28686
- return Object.keys(ips).length ? interpolate_string(message, ips) : message;
28687
- }
28688
- function mt(cls, children) {
28689
- // Wrapper for the Mithril m() function that auto-translates
28690
- // string and array arguments
28691
- if (typeof children === 'string') {
28692
- return m(cls, t(children));
30603
+ word,
30604
+ score,
30605
+ coord,
30606
+ timestamp,
30607
+ });
30608
+ }
30609
+ m.redraw();
28693
30610
  }
28694
- if (Array.isArray(children)) {
28695
- return m(cls, children.map((item) => (typeof item === 'string' ? t(item) : item)));
30611
+ async fetchRiddle(date, locale) {
30612
+ // Create the riddle via the model
30613
+ if (!this.model)
30614
+ throw new Error('Model is not initialized');
30615
+ await this.model.initRiddle(date, locale);
30616
+ // Attach Firebase listeners
30617
+ this.attachListenerToRiddle(date, locale);
28696
30618
  }
28697
- return m(cls, children);
28698
- }
28699
- function interpolate(message, ips) {
28700
- // Replace interpolation placeholders with their corresponding values
28701
- if (typeof message === 'string') {
28702
- return message.replace(rex, (match, key) => ips[key] || match);
30619
+ async loadRiddle(date, locale) {
30620
+ // Load a specific riddle date, handling cleanup of previous listeners
30621
+ // Prevent concurrent loads (e.g., from rapid navigation clicks)
30622
+ if (this.riddleLoading)
30623
+ return;
30624
+ this.riddleLoading = true;
30625
+ try {
30626
+ const currentRiddle = this.model.riddle;
30627
+ const today = new Date().toISOString().split('T')[0];
30628
+ const MIN_DATE = GATA_DAGSINS_MIN_DATE; // Earliest allowed date
30629
+ let effectiveDate = date;
30630
+ if (effectiveDate < MIN_DATE || effectiveDate > today) {
30631
+ effectiveDate = today;
30632
+ // Update URL to reflect the corrected date
30633
+ const url = new URL(window.location.href);
30634
+ url.searchParams.set('date', effectiveDate);
30635
+ window.history.pushState({}, '', url.toString());
30636
+ }
30637
+ // If we have an existing riddle, detach its listeners first
30638
+ if (currentRiddle) {
30639
+ this.detachListenerFromRiddle(currentRiddle.date, currentRiddle.locale);
30640
+ }
30641
+ // Fetch and initialize the new riddle
30642
+ await this.fetchRiddle(effectiveDate, locale);
30643
+ m.redraw();
30644
+ }
30645
+ finally {
30646
+ this.riddleLoading = false;
30647
+ }
28703
30648
  }
28704
- if (Array.isArray(message)) {
28705
- return message.map((item) => interpolate(item, ips));
30649
+ cleanupRiddle(date, locale) {
30650
+ // Detach Firebase listeners
30651
+ this.detachListenerFromRiddle(date, locale);
28706
30652
  }
28707
- return message;
28708
- }
28709
- function interpolate_string(message, ips) {
28710
- // Replace interpolation placeholders with their corresponding values
28711
- return message.replace(rex, (match, key) => ips[key] || match);
30653
+ } // class Actions
30654
+ function createRouteResolver(actions, view) {
30655
+ // Return a map of routes to onmatch and render functions
30656
+ const model = actions.model;
30657
+ // let state = model.state;
30658
+ return model.paths.reduce((acc, item) => {
30659
+ acc[item.route] = {
30660
+ // Navigating to a new route (passed in the second parameter)
30661
+ onmatch: (args) => {
30662
+ // Automatically close all dialogs
30663
+ view.popAllDialogs();
30664
+ view.resetScale();
30665
+ actions.onNavigateTo(item.name, args, view);
30666
+ },
30667
+ // Render a view on a model
30668
+ render: () => {
30669
+ return view.appView(item.name);
30670
+ },
30671
+ };
30672
+ return acc;
30673
+ }, {});
28712
30674
  }
28713
30675
 
28714
30676
  /*
@@ -29525,11 +31487,11 @@ const Buttons = {
29525
31487
  game.player !== null) {
29526
31488
  // Indicate that it is the opponent's turn; offer to force a resignation
29527
31489
  // if the opponent hasn't moved for 14 days
31490
+ const opponentName = game.nickname[1 - game.player];
29528
31491
  r.push(m('.opp-turn', { style: { visibility: 'visible' } }, [
29529
31492
  m('span.move-indicator'),
29530
31493
  nbsp(),
29531
- m('strong', game.nickname[1 - game.player]),
29532
- ts(' á leik'),
31494
+ m('strong', ts('opp_move', { opponent: opponentName })),
29533
31495
  nbsp(),
29534
31496
  // The following inline button is only
29535
31497
  // displayed in the fullscreen UI
@@ -30125,76 +32087,74 @@ const BoardArea = {
30125
32087
  return m('.board-area', r);
30126
32088
  },
30127
32089
  };
30128
- const Board = (initialVnode) => {
32090
+ const Board = {
30129
32091
  // The game board, a 15x15 table plus row (A-O) and column (1-15) identifiers
30130
- const { view, game, review } = initialVnode.attrs;
30131
- function colid() {
30132
- // The column identifier row
30133
- const r = [];
30134
- r.push(m('td'));
30135
- for (let col = 1; col <= 15; col++)
30136
- r.push(m('td', col.toString()));
30137
- return m('tr.colid', r);
30138
- }
30139
- function row(rowid) {
30140
- // Each row of the board
30141
- const r = [];
30142
- r.push(m('td.rowid', { key: `R${rowid}` }, rowid));
30143
- for (let col = 1; col <= 15; col++) {
30144
- const coord = rowid + col.toString();
30145
- if (game && coord in game.tiles)
30146
- // There is a tile in this square: render it
30147
- r.push(m(TileSquare, {
30148
- view,
30149
- game,
30150
- key: coord,
30151
- coord: coord,
30152
- opponent: false,
30153
- }));
30154
- else if (review)
30155
- // Empty, inert square
30156
- r.push(m(ReviewTileSquare, {
30157
- view,
30158
- game,
30159
- key: coord,
30160
- coord: coord,
30161
- opponent: false,
30162
- }));
30163
- // Empty square which is a drop target
30164
- else
30165
- r.push(m(DropTargetSquare, {
30166
- view,
30167
- game,
30168
- key: coord,
30169
- coord: coord,
30170
- }));
30171
- }
30172
- return m('tr', r);
30173
- }
30174
- function allrows() {
30175
- // Return a list of all rows on the board
30176
- const r = [];
30177
- r.push(colid());
30178
- const rows = 'ABCDEFGHIJKLMNO';
30179
- for (const rw of rows)
30180
- r.push(row(rw));
30181
- return r;
30182
- }
30183
- return {
30184
- view: () => {
30185
- // const scale = view.boardScale || 1.0;
30186
- const attrs = {};
30187
- // Add handlers for pinch zoom functionality
30188
- // Note: resist the temptation to pass zoomIn/zoomOut directly,
30189
- // as that would not bind the 'this' pointer correctly
30190
- addPinchZoom(attrs, () => view.zoomIn(), () => view.zoomOut());
30191
- /*
32092
+ view: (vnode) => {
32093
+ const { view, game, review } = vnode.attrs;
32094
+ // const scale = view.boardScale || 1.0;
32095
+ const attrs = {};
32096
+ // Add handlers for pinch zoom functionality
32097
+ // Note: resist the temptation to pass zoomIn/zoomOut directly,
32098
+ // as that would not bind the 'this' pointer correctly
32099
+ addPinchZoom(attrs, () => view.zoomIn(), () => view.zoomOut());
32100
+ /*
30192
32101
  if (scale !== 1.0)
30193
32102
  attrs.style = `transform: scale(${scale})`;
30194
32103
  */
30195
- return m('.board', { id: 'board-parent' }, m('table.board', attrs, m('tbody', allrows())));
30196
- },
30197
- };
32104
+ function colid() {
32105
+ // The column identifier row
32106
+ const r = [];
32107
+ r.push(m('td'));
32108
+ for (let col = 1; col <= 15; col++)
32109
+ r.push(m('td', col.toString()));
32110
+ return m('tr.colid', r);
32111
+ }
32112
+ function row(rowid) {
32113
+ // Each row of the board
32114
+ const r = [];
32115
+ r.push(m('td.rowid', { key: `R${rowid}` }, rowid));
32116
+ for (let col = 1; col <= 15; col++) {
32117
+ const coord = rowid + col.toString();
32118
+ if (game && coord in game.tiles)
32119
+ // There is a tile in this square: render it
32120
+ r.push(m(TileSquare, {
32121
+ view,
32122
+ game,
32123
+ key: coord,
32124
+ coord: coord,
32125
+ opponent: false,
32126
+ }));
32127
+ else if (review)
32128
+ // Empty, inert square
32129
+ r.push(m(ReviewTileSquare, {
32130
+ view,
32131
+ game,
32132
+ key: coord,
32133
+ coord: coord,
32134
+ opponent: false,
32135
+ }));
32136
+ // Empty square which is a drop target
32137
+ else
32138
+ r.push(m(DropTargetSquare, {
32139
+ view,
32140
+ game,
32141
+ key: coord,
32142
+ coord: coord,
32143
+ }));
32144
+ }
32145
+ return m('tr', r);
32146
+ }
32147
+ function allrows() {
32148
+ // Return a list of all rows on the board
32149
+ const r = [];
32150
+ r.push(colid());
32151
+ const rows = 'ABCDEFGHIJKLMNO';
32152
+ for (const rw of rows)
32153
+ r.push(row(rw));
32154
+ return r;
32155
+ }
32156
+ return m('.board', { id: 'board-parent' }, m('table.board', attrs, m('tbody', allrows())));
32157
+ },
30198
32158
  };
30199
32159
 
30200
32160
  /*
@@ -30408,6 +32368,90 @@ const GataDagsinsHelp = {
30408
32368
  },
30409
32369
  };
30410
32370
 
32371
+ /*
32372
+
32373
+ DateNavigator.ts
32374
+
32375
+ Component for navigating between Gáta Dagsins dates
32376
+
32377
+ Copyright (C) 2025 Miðeind ehf.
32378
+ Author: Vilhjálmur Þorsteinsson
32379
+
32380
+ The Creative Commons Attribution-NonCommercial 4.0
32381
+ International Public License (CC-BY-NC 4.0) applies to this software.
32382
+ For further information, see https://github.com/mideind/Netskrafl
32383
+
32384
+ */
32385
+ const DateNavigator = {
32386
+ view: (vnode) => {
32387
+ const { view, date, locale } = vnode.attrs;
32388
+ const { actions } = view;
32389
+ // Helper to add days to a YYYY-MM-DD string
32390
+ const addDays = (dateStr, days) => {
32391
+ // Create date object in UTC to ensure calculations are timezone-agnostic
32392
+ const d = new Date(`${dateStr}T00:00:00Z`);
32393
+ d.setUTCDate(d.getUTCDate() + days);
32394
+ return d.toISOString().split('T')[0];
32395
+ };
32396
+ const today = new Date().toISOString().split('T')[0];
32397
+ const prevDate = addDays(date, -1);
32398
+ const nextDate = addDays(date, 1);
32399
+ const isLoading = actions.isRiddleLoading;
32400
+ // Disable navigation while loading or when at date boundaries
32401
+ const prevEnabled = !isLoading && prevDate >= GATA_DAGSINS_MIN_DATE;
32402
+ const nextEnabled = !isLoading && nextDate <= today;
32403
+ const navigateTo = (newDate) => {
32404
+ // Update URL without reloading the page
32405
+ const url = new URL(window.location.href);
32406
+ url.searchParams.set('date', newDate);
32407
+ window.history.pushState({}, '', url.toString());
32408
+ // Load the new riddle via the actions controller
32409
+ actions.loadRiddle(newDate, locale);
32410
+ };
32411
+ // Keyboard handler for Enter/Space key activation
32412
+ const handleKeydown = (enabled, action) => {
32413
+ if (!enabled)
32414
+ return undefined;
32415
+ return (e) => {
32416
+ if (e.key === 'Enter' || e.key === ' ') {
32417
+ e.preventDefault();
32418
+ action();
32419
+ }
32420
+ };
32421
+ };
32422
+ const tsPrevDay = ts('Fyrri dagur');
32423
+ const tsNextDay = ts('Næsti dagur');
32424
+ return m('.date-navigator', [
32425
+ // Previous day button
32426
+ m(`.nav-arrow.prev${prevEnabled ? '' : '.disabled'}`, {
32427
+ role: 'button',
32428
+ tabindex: prevEnabled ? 0 : -1,
32429
+ 'aria-disabled': !prevEnabled,
32430
+ 'aria-label': tsPrevDay,
32431
+ onclick: prevEnabled
32432
+ ? () => navigateTo(prevDate)
32433
+ : undefined,
32434
+ onkeydown: handleKeydown(prevEnabled, () => navigateTo(prevDate)),
32435
+ title: prevEnabled ? tsPrevDay : '',
32436
+ }, glyph('chevron-left')),
32437
+ // Date display
32438
+ m('.nav-date', formatDate(date)),
32439
+ // Next day button
32440
+ m(`.nav-arrow.next${nextEnabled ? '' : '.disabled'}`, {
32441
+ role: 'button',
32442
+ tabindex: nextEnabled ? 0 : -1,
32443
+ 'aria-disabled': !nextEnabled,
32444
+ 'aria-label': tsNextDay,
32445
+ onclick: nextEnabled
32446
+ ? () => navigateTo(nextDate)
32447
+ : undefined,
32448
+ onkeydown: handleKeydown(nextEnabled, () => navigateTo(nextDate)),
32449
+ title: nextEnabled ? tsNextDay : '',
32450
+ }, glyph('chevron-right')),
32451
+ ]);
32452
+ },
32453
+ };
32454
+
30411
32455
  /*
30412
32456
 
30413
32457
  SunCorona.ts
@@ -30481,6 +32525,13 @@ const SunCorona = {
30481
32525
  */
30482
32526
  // Mobile-only horizontal status display
30483
32527
  const MobileStatus = () => {
32528
+ // Keyboard handler for button activation
32529
+ const handleKeydown = (action) => (e) => {
32530
+ if (e.key === 'Enter' || e.key === ' ') {
32531
+ e.preventDefault();
32532
+ action();
32533
+ }
32534
+ };
30484
32535
  return {
30485
32536
  view: (vnode) => {
30486
32537
  const { view, bestMove, onMoveClick, onStatsClick } = vnode.attrs;
@@ -30490,6 +32541,12 @@ const MobileStatus = () => {
30490
32541
  const { bestPossibleScore, globalBestScore, personalBestScore } = riddle;
30491
32542
  // Determine if player achieved best possible score
30492
32543
  const celebrate = bestMove && bestMove.word !== '';
32544
+ const today = new Date().toISOString().split('T')[0];
32545
+ const lookingAtOldRiddle = riddle.date < today;
32546
+ // If we don't have an explicit solution, use the global best score as fallback
32547
+ const solution = lookingAtOldRiddle
32548
+ ? riddle.solution || globalBestScore
32549
+ : null;
30493
32550
  // Determine current leader score (may be this player or another)
30494
32551
  let leaderScore = 0;
30495
32552
  let isPlayerLeading = false;
@@ -30502,13 +32559,32 @@ const MobileStatus = () => {
30502
32559
  leaderScore = personalBestScore;
30503
32560
  isPlayerLeading = personalBestScore > 0;
30504
32561
  }
32562
+ // Handler for clicking on best possible score
32563
+ const isScoreClickable = !!(solution || celebrate);
32564
+ const handleScoreClick = () => {
32565
+ if (solution) {
32566
+ // Zoom out when showing solution for old riddles
32567
+ view.zoomOut();
32568
+ // Recreate the solution word on the board
32569
+ onMoveClick(solution.word, solution.coord);
32570
+ }
32571
+ else if (celebrate) {
32572
+ // Recreate the best move word on the board
32573
+ onMoveClick(bestMove.word, bestMove.coord);
32574
+ }
32575
+ };
32576
+ const tsStatsAndLeaderboard = ts('Tölfræði og stigatafla');
30505
32577
  return m('.mobile-status-container', [
30506
32578
  // Current word score (leftmost) - uses RiddleScore component in mobile mode
30507
32579
  m('.mobile-status-item.left', m(RiddleScore, { riddle, mode: 'mobile' })),
30508
32580
  // Interactive card containing player best and leader scores
30509
32581
  m('.mobile-status-card', {
32582
+ role: 'button',
32583
+ tabindex: 0,
32584
+ 'aria-label': tsStatsAndLeaderboard,
30510
32585
  onclick: onStatsClick,
30511
- title: ts('Tölfræði og stigatafla'),
32586
+ onkeydown: handleKeydown(onStatsClick),
32587
+ title: tsStatsAndLeaderboard,
30512
32588
  }, [
30513
32589
  // Player's best score
30514
32590
  m('.mobile-status-card-item.player-best', [
@@ -30529,8 +32605,15 @@ const MobileStatus = () => {
30529
32605
  // Best possible score
30530
32606
  m('.mobile-status-item.right.best-possible' +
30531
32607
  (celebrate ? '.celebrate' : ''), {
30532
- onclick: () => celebrate &&
30533
- onMoveClick(bestMove.word, bestMove.coord),
32608
+ role: isScoreClickable ? 'button' : undefined,
32609
+ tabindex: isScoreClickable ? 0 : undefined,
32610
+ 'aria-label': isScoreClickable
32611
+ ? ts('Sýna besta leik')
32612
+ : undefined,
32613
+ onclick: handleScoreClick,
32614
+ onkeydown: isScoreClickable
32615
+ ? handleKeydown(handleScoreClick)
32616
+ : undefined,
30534
32617
  }, [
30535
32618
  // Wrapper for score and corona to position them together
30536
32619
  m('.mobile-best-score-wrapper', [
@@ -30571,30 +32654,9 @@ function getMedalIcon(rank) {
30571
32654
  return null;
30572
32655
  }
30573
32656
  }
30574
- function formatDate(dateStr) {
30575
- // Format YYYY-MM-DD to Icelandic date (e.g., "2. okt.")
30576
- const date = new Date(`${dateStr}T00:00:00`);
30577
- const day = date.getDate();
30578
- const months = [
30579
- 'janúar',
30580
- 'febrúar',
30581
- 'mars',
30582
- 'apríl',
30583
- 'maí',
30584
- 'júní',
30585
- 'júlí',
30586
- 'ágúst',
30587
- 'september',
30588
- 'október',
30589
- 'nóvember',
30590
- 'desember',
30591
- ];
30592
- const month = months[date.getMonth()];
30593
- return `${day}. ${month}`;
30594
- }
30595
32657
  const LeaderboardView = {
30596
32658
  view: (vnode) => {
30597
- const { leaderboard, currentUserId, date, loading = false, } = vnode.attrs;
32659
+ const { leaderboard, currentUserId, loading = false } = vnode.attrs;
30598
32660
  if (loading) {
30599
32661
  return m('.leaderboard-view.loading', m('.loading-message', ts('Hleð stigatöflu...')));
30600
32662
  }
@@ -30602,9 +32664,6 @@ const LeaderboardView = {
30602
32664
  return m('.leaderboard-view.empty', m('.empty-message', ts('Engin stig skráð enn')));
30603
32665
  }
30604
32666
  return m('.leaderboard-view', [
30605
- m('.leaderboard-header', [
30606
- m('.leaderboard-title', formatDate(date)),
30607
- ]),
30608
32667
  m('.leaderboard-list', {
30609
32668
  // Allow touch scrolling but prevent events from bubbling to backdrop
30610
32669
  ontouchmove: (e) => {
@@ -30712,22 +32771,37 @@ const StatsView = {
30712
32771
  const TabBar = {
30713
32772
  view: (vnode) => {
30714
32773
  const { tabs, activeTab, onTabChange } = vnode.attrs;
30715
- return m('.tab-bar', tabs.map((tab) => m(`.tab-item${activeTab === tab.id ? '.active' : ''}`, {
30716
- key: tab.id,
30717
- onclick: () => onTabChange(tab.id),
30718
- }, [
30719
- m('span.tab-icon-wrapper', [
30720
- tab.iconGlyph
30721
- ? m('span.tab-icon', glyph(tab.iconGlyph))
30722
- : tab.icon
30723
- ? m('span.tab-icon', tab.icon)
32774
+ // Keyboard handler for tab activation
32775
+ const handleKeydown = (tabId) => (e) => {
32776
+ if (e.key === 'Enter' || e.key === ' ') {
32777
+ e.preventDefault();
32778
+ onTabChange(tabId);
32779
+ }
32780
+ };
32781
+ return m('.tab-bar[role=tablist]', tabs.map((tab) => {
32782
+ const isActive = activeTab === tab.id;
32783
+ return m(`.tab-item${isActive ? '.active' : ''}`, {
32784
+ key: tab.id,
32785
+ role: 'tab',
32786
+ tabindex: isActive ? 0 : -1,
32787
+ 'aria-selected': isActive,
32788
+ 'aria-label': tab.label,
32789
+ onclick: () => onTabChange(tab.id),
32790
+ onkeydown: handleKeydown(tab.id),
32791
+ }, [
32792
+ m('span.tab-icon-wrapper', [
32793
+ tab.iconGlyph
32794
+ ? m('span.tab-icon', glyph(tab.iconGlyph))
32795
+ : tab.icon
32796
+ ? m('span.tab-icon', tab.icon)
32797
+ : null,
32798
+ tab.badgeGlyph
32799
+ ? m('span.tab-badge', glyph(tab.badgeGlyph))
30724
32800
  : null,
30725
- tab.badgeGlyph
30726
- ? m('span.tab-badge', glyph(tab.badgeGlyph))
30727
- : null,
30728
- ]),
30729
- m('span.tab-label', tab.label),
30730
- ])));
32801
+ ]),
32802
+ m('span.tab-label', tab.label),
32803
+ ]);
32804
+ }));
30731
32805
  },
30732
32806
  };
30733
32807
 
@@ -30801,156 +32875,200 @@ function calculateZoneHeights(allocation) {
30801
32875
  };
30802
32876
  }
30803
32877
  // Best possible score component (green circle at top)
30804
- const BestPossibleScore = () => {
30805
- return {
30806
- view: (vnode) => {
30807
- const { score, bestMove, onMoveClick } = vnode.attrs;
30808
- // Determine the label based on achievement status
30809
- let topLabel;
30810
- if (bestMove?.word) {
30811
- // Current player achieved it - show their word
30812
- topLabel = removeBlankMarkers(bestMove.word);
32878
+ const BestPossibleScore = {
32879
+ view: (vnode) => {
32880
+ const { score, bestMove, onMoveClick, solution } = vnode.attrs;
32881
+ // Determine the label based on achievement status
32882
+ let topLabel;
32883
+ const celebrate = bestMove && bestMove.word !== '';
32884
+ if (celebrate) {
32885
+ // Current player achieved it - show their word
32886
+ topLabel = removeBlankMarkers(bestMove.word);
32887
+ }
32888
+ else {
32889
+ // Not achieved yet - show default label
32890
+ topLabel = ts('Besta mögulega lögn');
32891
+ }
32892
+ const title = solution && !celebrate ? ts('Smelltu til að sjá lausn') : '';
32893
+ const isClickable = !!(solution || celebrate);
32894
+ const handleClick = () => {
32895
+ if (solution) {
32896
+ // Recreate the solution word on the board
32897
+ onMoveClick(solution.word, solution.coord);
30813
32898
  }
30814
- else {
30815
- // Not achieved yet - show default label
30816
- topLabel = ts('Besta mögulega lögn');
32899
+ else if (celebrate) {
32900
+ // Recreate the best move word on the board
32901
+ onMoveClick(bestMove.word, bestMove.coord);
30817
32902
  }
30818
- const celebrate = bestMove && bestMove.word !== '';
30819
- return m(`.thermometer-best-score${celebrate ? '.celebrate' : ''}`, m('.thermometer-best-score-container', {
30820
- onclick: () => celebrate &&
30821
- onMoveClick(bestMove.word, bestMove.coord),
30822
- }, [
30823
- // Sun corona behind the circle when celebrating
30824
- celebrate ? m(SunCorona, { animate: true }) : null,
30825
- m('.thermometer-best-circle', score.toString()),
30826
- m('.thermometer-best-label', topLabel),
30827
- ]));
30828
- },
30829
- };
32903
+ };
32904
+ const handleKeydown = isClickable
32905
+ ? (e) => {
32906
+ if (e.key === 'Enter' || e.key === ' ') {
32907
+ e.preventDefault();
32908
+ handleClick();
32909
+ }
32910
+ }
32911
+ : undefined;
32912
+ return m(`.thermometer-best-score${celebrate ? '.celebrate' : ''}`, m('.thermometer-best-score-container', {
32913
+ role: isClickable ? 'button' : undefined,
32914
+ tabindex: isClickable ? 0 : undefined,
32915
+ 'aria-label': isClickable
32916
+ ? ts('Sýna besta leik')
32917
+ : undefined,
32918
+ onclick: handleClick,
32919
+ onkeydown: handleKeydown,
32920
+ title,
32921
+ }, [
32922
+ // Sun corona behind the circle when celebrating
32923
+ celebrate ? m(SunCorona, { animate: true }) : null,
32924
+ m('.thermometer-best-circle', score.toString()),
32925
+ m('.thermometer-best-label', topLabel),
32926
+ ]));
32927
+ },
30830
32928
  };
30831
32929
  // Current global best score component (circled score)
30832
- const GlobalBestScore = () => {
30833
- return {
30834
- view: (vnode) => {
30835
- const { score, thisPlayer } = vnode.attrs;
30836
- return m(`.thermometer-current-score${thisPlayer ? '.this-player' : ''}`, [m('.thermometer-current-circle', score.toString())]);
30837
- },
30838
- };
32930
+ const GlobalBestScore = {
32931
+ view: (vnode) => {
32932
+ const { score, thisPlayer } = vnode.attrs;
32933
+ return m(`.thermometer-current-score${thisPlayer ? '.this-player' : ''}`, [m('.thermometer-current-circle', score.toString())]);
32934
+ },
30839
32935
  };
30840
32936
  // Player moves overlay wrapper component with zone-aware positioning
30841
- const PlayerMovesOverlay = () => {
30842
- return {
30843
- view: (vnode) => {
30844
- // Zones: "hot", "warm", "cold"
30845
- // zoneHeights: How tall each zone is, as a percentage of the thermometer height
30846
- // zoneAllocation: A dictionary with the list of moves for each zone
30847
- const { zoneHeights, zoneAllocation, onMoveClick } = vnode.attrs;
30848
- // Calculate zone boundaries
30849
- const coldBottom = 0;
30850
- const coldTop = zoneHeights.cold;
30851
- const warmBottom = coldTop;
30852
- const warmTop = warmBottom + zoneHeights.warm;
30853
- const hotBottom = warmTop;
30854
- const allMoveElements = [];
30855
- function scoreDetails(move) {
30856
- if (move.isGlobalBestScore) {
30857
- if (move.word === '') {
30858
- // Another player holds the top score
30859
- return [
30860
- m(GlobalBestScore, {
30861
- thisPlayer: false,
30862
- score: move.score,
30863
- }),
30864
- ];
30865
- }
30866
- else {
30867
- // This player is the holder of the top score
30868
- return [
30869
- m(GlobalBestScore, {
30870
- thisPlayer: true,
30871
- score: move.score,
30872
- }),
30873
- m('.thermometer-move-word', removeBlankMarkers(move.word)),
30874
- m('.thermometer-move-coord', `(${move.coord})`),
30875
- ];
30876
- }
32937
+ const PlayerMovesOverlay = {
32938
+ view: (vnode) => {
32939
+ // Zones: "hot", "warm", "cold"
32940
+ // zoneHeights: How tall each zone is, as a percentage of the thermometer height
32941
+ // zoneAllocation: A dictionary with the list of moves for each zone
32942
+ const { zoneHeights, zoneAllocation, onMoveClick } = vnode.attrs;
32943
+ // Calculate zone boundaries
32944
+ const coldBottom = 0;
32945
+ const coldTop = zoneHeights.cold;
32946
+ const warmBottom = coldTop;
32947
+ const warmTop = warmBottom + zoneHeights.warm;
32948
+ const hotBottom = warmTop;
32949
+ const allMoveElements = [];
32950
+ function scoreDetails(move) {
32951
+ if (move.isGlobalBestScore) {
32952
+ if (move.word === '') {
32953
+ // Another player holds the top score
32954
+ return [
32955
+ m(GlobalBestScore, {
32956
+ thisPlayer: false,
32957
+ score: move.score,
32958
+ }),
32959
+ ];
30877
32960
  }
30878
32961
  else {
32962
+ // This player is the holder of the top score
30879
32963
  return [
30880
- m('.thermometer-move-score', move.score.toString()),
32964
+ m(GlobalBestScore, {
32965
+ thisPlayer: true,
32966
+ score: move.score,
32967
+ }),
30881
32968
  m('.thermometer-move-word', removeBlankMarkers(move.word)),
30882
32969
  m('.thermometer-move-coord', `(${move.coord})`),
30883
32970
  ];
30884
32971
  }
30885
32972
  }
30886
- function addZoneMoves(zoneMoves, zoneName, zoneStart, zoneHeight) {
30887
- const num = zoneMoves.length;
30888
- const segmentHeight = zoneHeight / (num + 1);
30889
- zoneMoves.forEach((move, index) => {
30890
- // The zoneMoves list is in descending order, and we want
30891
- // the first (highest) move to be at the top of the zone,
30892
- // i.e. with the highest percentage position, which is
30893
- // calculated from the bottom of the thermometer.
30894
- const position = zoneStart + segmentHeight * (num - index);
30895
- allMoveElements.push(m(`.thermometer-move-overlay .${zoneName}`, {
30896
- key: `${zoneName}-${index}`,
30897
- style: `--move-position: ${position.toFixed(2)}%`,
30898
- // Only call onMoveClick if the move was actually made
30899
- // by the current player
30900
- onclick: () => move.word &&
30901
- move.coord &&
30902
- onMoveClick(move.word, move.coord),
30903
- }, scoreDetails(move)));
30904
- });
32973
+ else {
32974
+ return [
32975
+ m('.thermometer-move-score', move.score.toString()),
32976
+ m('.thermometer-move-word', removeBlankMarkers(move.word)),
32977
+ m('.thermometer-move-coord', `(${move.coord})`),
32978
+ ];
30905
32979
  }
30906
- addZoneMoves(zoneAllocation.cold, 'cold', coldBottom, zoneHeights.cold);
30907
- addZoneMoves(zoneAllocation.warm, 'warm', warmBottom, zoneHeights.warm);
30908
- addZoneMoves(zoneAllocation.hot, 'hot', hotBottom, zoneHeights.hot);
30909
- return m('.thermometer-moves-overlay-wrapper', allMoveElements);
30910
- },
30911
- };
32980
+ }
32981
+ function addZoneMoves(zoneMoves, zoneName, zoneStart, zoneHeight) {
32982
+ const num = zoneMoves.length;
32983
+ const segmentHeight = zoneHeight / (num + 1);
32984
+ zoneMoves.forEach((move, index) => {
32985
+ // The zoneMoves list is in descending order, and we want
32986
+ // the first (highest) move to be at the top of the zone,
32987
+ // i.e. with the highest percentage position, which is
32988
+ // calculated from the bottom of the thermometer.
32989
+ const position = zoneStart + segmentHeight * (num - index);
32990
+ // Only clickable if the move was made by the current player
32991
+ const isClickable = !!(move.word && move.coord);
32992
+ const handleClick = () => {
32993
+ if (isClickable) {
32994
+ onMoveClick(move.word, move.coord);
32995
+ }
32996
+ };
32997
+ const handleKeydown = isClickable
32998
+ ? (e) => {
32999
+ if (e.key === 'Enter' || e.key === ' ') {
33000
+ e.preventDefault();
33001
+ handleClick();
33002
+ }
33003
+ }
33004
+ : undefined;
33005
+ allMoveElements.push(m(`.thermometer-move-overlay .${zoneName}`, {
33006
+ key: `${zoneName}-${index}`,
33007
+ style: `--move-position: ${position.toFixed(2)}%`,
33008
+ role: isClickable ? 'button' : undefined,
33009
+ tabindex: isClickable ? 0 : undefined,
33010
+ 'aria-label': isClickable
33011
+ ? `${ts('Sýna leik')}: ${removeBlankMarkers(move.word)}`
33012
+ : undefined,
33013
+ onclick: handleClick,
33014
+ onkeydown: handleKeydown,
33015
+ }, scoreDetails(move)));
33016
+ });
33017
+ }
33018
+ addZoneMoves(zoneAllocation.cold, 'cold', coldBottom, zoneHeights.cold);
33019
+ addZoneMoves(zoneAllocation.warm, 'warm', warmBottom, zoneHeights.warm);
33020
+ addZoneMoves(zoneAllocation.hot, 'hot', hotBottom, zoneHeights.hot);
33021
+ return m('.thermometer-moves-overlay-wrapper', allMoveElements);
33022
+ },
30912
33023
  };
30913
33024
  // Main thermometer component with dynamic layout algorithm
30914
- const Thermometer = () => {
30915
- return {
30916
- view: (vnode) => {
30917
- const { riddle, selectedMoves, bestMove, onMoveClick } = vnode.attrs;
30918
- const { warmBoundary, hotBoundary, bestPossibleScore } = riddle;
30919
- const zoneAllocation = allocateMovesToZones(warmBoundary, hotBoundary, selectedMoves);
30920
- const zoneHeights = calculateZoneHeights(zoneAllocation);
30921
- return m('.thermometer-container', [
30922
- // Main thermometer body with dynamic color zones and overlaid moves
30923
- m('.thermometer-body', [
30924
- // Dynamic color zones background
30925
- m('.thermometer-zones', [
30926
- m('.thermometer-zone.hot', {
30927
- style: `--zone-size: ${zoneHeights.hot + zoneHeights.warm + zoneHeights.cold}%`,
30928
- }),
30929
- m('.thermometer-zone.warm', {
30930
- style: `--zone-size: ${zoneHeights.warm + zoneHeights.cold}%`,
30931
- }),
30932
- m('.thermometer-zone.cold', {
30933
- style: `--zone-size: ${zoneHeights.cold}%`,
30934
- }),
30935
- ]),
30936
- // Player moves positioned using zone-aware algorithm
30937
- m(PlayerMovesOverlay, {
30938
- moves: selectedMoves,
30939
- maxScore: bestPossibleScore,
30940
- zoneHeights,
30941
- zoneAllocation,
30942
- onMoveClick,
33025
+ const Thermometer = {
33026
+ view: (vnode) => {
33027
+ const { riddle, selectedMoves, bestMove, onMoveClick } = vnode.attrs;
33028
+ const { warmBoundary, hotBoundary, bestPossibleScore, globalBestScore, } = riddle;
33029
+ const zoneAllocation = allocateMovesToZones(warmBoundary, hotBoundary, selectedMoves);
33030
+ const zoneHeights = calculateZoneHeights(zoneAllocation);
33031
+ const today = new Date().toISOString().split('T')[0];
33032
+ const lookingAtOldRiddle = riddle.date < today;
33033
+ // If we don't have an explicit solution, use the global best score as fallback
33034
+ const solution = lookingAtOldRiddle
33035
+ ? riddle.solution || globalBestScore
33036
+ : null;
33037
+ return m('.thermometer-container', [
33038
+ // Main thermometer body with dynamic color zones and overlaid moves
33039
+ m('.thermometer-body', [
33040
+ // Dynamic color zones background
33041
+ m('.thermometer-zones', [
33042
+ m('.thermometer-zone.hot', {
33043
+ style: `--zone-size: ${zoneHeights.hot +
33044
+ zoneHeights.warm +
33045
+ zoneHeights.cold}%`,
33046
+ }),
33047
+ m('.thermometer-zone.warm', {
33048
+ style: `--zone-size: ${zoneHeights.warm + zoneHeights.cold}%`,
33049
+ }),
33050
+ m('.thermometer-zone.cold', {
33051
+ style: `--zone-size: ${zoneHeights.cold}%`,
30943
33052
  }),
30944
33053
  ]),
30945
- // Best possible score at top
30946
- m(BestPossibleScore, {
30947
- score: bestPossibleScore,
30948
- bestMove,
33054
+ // Player moves positioned using zone-aware algorithm
33055
+ m(PlayerMovesOverlay, {
33056
+ moves: selectedMoves,
33057
+ maxScore: bestPossibleScore,
33058
+ zoneHeights,
33059
+ zoneAllocation,
30949
33060
  onMoveClick,
30950
33061
  }),
30951
- ]);
30952
- },
30953
- };
33062
+ ]),
33063
+ // Best possible score at top
33064
+ m(BestPossibleScore, {
33065
+ score: bestPossibleScore,
33066
+ bestMove,
33067
+ onMoveClick,
33068
+ solution,
33069
+ }),
33070
+ ]);
33071
+ },
30954
33072
  };
30955
33073
 
30956
33074
  /*
@@ -31027,7 +33145,6 @@ const RightSideTabs = () => {
31027
33145
  ? m(LeaderboardView, {
31028
33146
  leaderboard: view.model.leaderboard || [],
31029
33147
  currentUserId: state?.netskraflUserId || '',
31030
- date: riddle.date,
31031
33148
  loading: false,
31032
33149
  })
31033
33150
  : null,
@@ -31065,20 +33182,35 @@ const GataDagsinsRightSide = {
31065
33182
  return m('.gatadagsins-right-side-wrapper', riddle
31066
33183
  ? [
31067
33184
  // Mobile-only status bar (visible on mobile, hidden on desktop)
31068
- m('.gatadagsins-mobile-status', m(MobileStatus, {
31069
- view,
31070
- selectedMoves,
31071
- bestMove,
31072
- onMoveClick,
31073
- onStatsClick,
31074
- })),
33185
+ // We wrap the mobile content to include the date navigator
33186
+ m('.gatadagsins-mobile-wrapper', [
33187
+ m('.mobile-date-nav-container', m(DateNavigator, {
33188
+ view,
33189
+ date: riddle.date,
33190
+ locale: riddle.locale,
33191
+ })),
33192
+ m('.gatadagsins-mobile-status', m(MobileStatus, {
33193
+ view,
33194
+ selectedMoves,
33195
+ bestMove,
33196
+ onMoveClick,
33197
+ onStatsClick,
33198
+ })),
33199
+ ]),
31075
33200
  // Desktop-only tabbed view (hidden on mobile, visible on desktop)
31076
- m('.gatadagsins-thermometer-column', m(RightSideTabs, {
31077
- view,
31078
- selectedMoves,
31079
- bestMove,
31080
- onMoveClick,
31081
- })),
33201
+ m('.gatadagsins-thermometer-column', [
33202
+ m('.desktop-date-nav-container', m(DateNavigator, {
33203
+ view,
33204
+ date: riddle.date,
33205
+ locale: riddle.locale,
33206
+ })),
33207
+ m(RightSideTabs, {
33208
+ view,
33209
+ selectedMoves,
33210
+ bestMove,
33211
+ onMoveClick,
33212
+ }),
33213
+ ]),
31082
33214
  ]
31083
33215
  : null);
31084
33216
  },
@@ -31176,7 +33308,6 @@ const StatsModal = () => {
31176
33308
  ? m(LeaderboardView, {
31177
33309
  leaderboard: view.model.leaderboard || [],
31178
33310
  currentUserId: state?.netskraflUserId || '',
31179
- date: riddle.date,
31180
33311
  loading: false,
31181
33312
  })
31182
33313
  : null,
@@ -31892,9 +34023,9 @@ class BaseGame {
31892
34023
  }
31893
34024
  async updateScore() {
31894
34025
  // Re-calculate the current word score
31895
- const scoreResult = this.calcScore();
31896
34026
  this.wordGood = false;
31897
34027
  this.wordBad = false;
34028
+ const scoreResult = this.calcScore();
31898
34029
  if (scoreResult === undefined || !scoreResult.word) {
31899
34030
  this.currentScore = undefined;
31900
34031
  this.currentWord = '';
@@ -31910,8 +34041,12 @@ class BaseGame {
31910
34041
  // This is not a manual-wordcheck game:
31911
34042
  // Check the word that has been laid down
31912
34043
  const found = await wordChecker.checkWords(this.state, this.locale, scoreResult.words);
31913
- this.wordGood = found;
31914
- this.wordBad = !found;
34044
+ if (this.currentWord === scoreResult.word) {
34045
+ // Safety check: ensure that the current word has not changed
34046
+ // while the wordChecker was awaiting the server response
34047
+ this.wordGood = found;
34048
+ this.wordBad = !found;
34049
+ }
31915
34050
  }
31916
34051
  }
31917
34052
  }
@@ -33280,6 +35415,7 @@ class Riddle extends BaseGame {
33280
35415
  this.groupBestScore = null;
33281
35416
  this.personalBestScore = 0;
33282
35417
  this.playerMoves = [];
35418
+ this.solution = null;
33283
35419
  this.date = date;
33284
35420
  this.model = model;
33285
35421
  }
@@ -33309,8 +35445,6 @@ class Riddle extends BaseGame {
33309
35445
  return !this.showingDialog;
33310
35446
  }
33311
35447
  async load(date, locale) {
33312
- this.date = date;
33313
- this.locale = locale;
33314
35448
  const { state } = this;
33315
35449
  try {
33316
35450
  if (!state)
@@ -33322,6 +35456,8 @@ class Riddle extends BaseGame {
33322
35456
  body: { date, locale },
33323
35457
  });
33324
35458
  if (response.ok) {
35459
+ this.date = date;
35460
+ this.locale = locale;
33325
35461
  this.board_type = response.riddle.board_type || 'standard';
33326
35462
  this.alphabet = response.riddle.alphabet || '';
33327
35463
  this.tile_scores = response.riddle.tile_scores || {};
@@ -33332,6 +35468,7 @@ class Riddle extends BaseGame {
33332
35468
  this.globalBestScore = null;
33333
35469
  this.groupBestScore = null;
33334
35470
  this.personalBestScore = 0;
35471
+ this.solution = response.riddle.solution || null;
33335
35472
  this.warmBoundary =
33336
35473
  this.bestPossibleScore * WARM_COLD_BOUNDARY_RATIO;
33337
35474
  this.hotBoundary =
@@ -33342,16 +35479,20 @@ class Riddle extends BaseGame {
33342
35479
  if (state.netskraflUserId) {
33343
35480
  const persistedMoves = loadLocalMoves(state.netskraflUserId, date);
33344
35481
  if (persistedMoves.length > 0) {
33345
- // Convert from IPlayerMove to RiddleWord format, preserving timestamps
33346
- this.playerMoves = persistedMoves.map((move) => ({
33347
- word: move.word,
33348
- score: move.score,
33349
- coord: move.coord,
33350
- timestamp: move.timestamp || new Date().toISOString(), // Use stored timestamp or fallback
33351
- }));
33352
- // Update personal best score from persisted moves
33353
- const bestMove = persistedMoves.reduce((best, current) => current.score > best.score ? current : best);
33354
- this.personalBestScore = bestMove.score;
35482
+ // Filter out invalid moves (score > bestPossibleScore)
35483
+ const validMoves = persistedMoves.filter((move) => move.score <= this.bestPossibleScore);
35484
+ if (validMoves.length > 0) {
35485
+ // Convert from IPlayerMove to RiddleWord format, preserving timestamps
35486
+ this.playerMoves = validMoves.map((move) => ({
35487
+ word: move.word,
35488
+ score: move.score,
35489
+ coord: move.coord,
35490
+ timestamp: move.timestamp || new Date().toISOString(), // Use stored timestamp or fallback
35491
+ }));
35492
+ // Update personal best score from valid moves only
35493
+ const bestMove = validMoves.reduce((best, current) => current.score > best.score ? current : best);
35494
+ this.personalBestScore = bestMove.score;
35495
+ }
33355
35496
  }
33356
35497
  }
33357
35498
  }
@@ -33394,6 +35535,10 @@ class Riddle extends BaseGame {
33394
35535
  !this.currentCoord) {
33395
35536
  return;
33396
35537
  }
35538
+ // Sanity check: move score cannot exceed best possible score
35539
+ if (this.currentScore > this.bestPossibleScore) {
35540
+ return;
35541
+ }
33397
35542
  // Check whether the move already exists
33398
35543
  const move = {
33399
35544
  score: this.currentScore,
@@ -33424,23 +35569,27 @@ class Riddle extends BaseGame {
33424
35569
  // If the move is not valid or was already played, return
33425
35570
  if (!move)
33426
35571
  return;
33427
- const { state } = this;
35572
+ const { state, date, playerMoves } = this;
33428
35573
  if (!state || !state.netskraflUserId)
33429
35574
  return;
33430
35575
  // Save all moves to localStorage (local backup/cache)
33431
35576
  // Convert RiddleWord[] to IPlayerMove[] for persistence
33432
- const movesToSave = this.playerMoves.map((m) => ({
35577
+ const movesToSave = playerMoves.map((m) => ({
33433
35578
  score: m.score,
33434
35579
  word: m.word,
33435
35580
  coord: m.coord,
33436
35581
  timestamp: m.timestamp,
33437
35582
  }));
33438
- saveLocalMoves(state.netskraflUserId, this.date, movesToSave);
35583
+ saveLocalMoves(state.netskraflUserId, date, movesToSave);
33439
35584
  // If the move does not improve the personal best, we're done
33440
35585
  if (move.score <= this.personalBestScore)
33441
35586
  return;
33442
35587
  // This is the best score we've seen yet
33443
35588
  this.personalBestScore = move.score;
35589
+ // If this is not today's riddle, do not submit to server
35590
+ const today = timestamp.split('T')[0];
35591
+ if (date !== today)
35592
+ return;
33444
35593
  // Submit to server; the server handles all Firebase updates
33445
35594
  // (achievements, stats, global best, leaderboard)
33446
35595
  this.submitRiddleWord(move);
@@ -33448,11 +35597,19 @@ class Riddle extends BaseGame {
33448
35597
  updateGlobalBestScore(best) {
33449
35598
  // Update the global best score, typically as a result
33450
35599
  // of a Firebase notification from the server
35600
+ // Ignore if score exceeds best possible score
35601
+ if (best.score > this.bestPossibleScore) {
35602
+ return;
35603
+ }
33451
35604
  this.globalBestScore = best;
33452
35605
  }
33453
35606
  updateGroupBestScore(best) {
33454
35607
  // Update the group best score, typically as a result
33455
35608
  // of a Firebase notification from the server
35609
+ // Ignore if score exceeds best possible score
35610
+ if (best.score > this.bestPossibleScore) {
35611
+ return;
35612
+ }
33456
35613
  this.groupBestScore = best;
33457
35614
  }
33458
35615
  recreateWordOnBoard(word, startCoord) {
@@ -33716,8 +35873,8 @@ class Model {
33716
35873
  this.state = state;
33717
35874
  this.isExplo = state.isExplo;
33718
35875
  this.maxFreeGames = state.isExplo ? MAX_FREE_EXPLO : MAX_FREE_NETSKRAFL;
33719
- // Load localized text messages from the messages.json file
33720
- loadMessages(state, state.locale);
35876
+ // Initialize localized text messages from the embedded messages.json
35877
+ initMessages(state.locale);
33721
35878
  }
33722
35879
  // Simple POST request with JSON body (most common case)
33723
35880
  async post(url, body) {