From d4c5e677a2dcf9c5f900a5418868abf6a77d5141 Mon Sep 17 00:00:00 2001 From: albertlast Date: Fri, 31 Jul 2026 17:01:40 +0200 Subject: [PATCH] Revamp: 2-6 players, table layouts, two themes, table tools Rebuild of the app on the existing Vue 3 + Vite + Tailwind base. Game - 2 to 6 players, each seat rotated towards the edge its player sits at. - Three table layouts: facing (top/bottom rows), sides (quarter turn, left and right edges) and same side (upright stack). Picking the player count opens the layout step, which previews live on the table behind it. - Game presets: Standard, Commander, Energy, Duel 8000 and a board-game counter, with a freely adjustable starting life total. - Poison and energy counters, per-seat names and colours. - Table tools: dice (coin, d4-d100), match timer, random first player. Look - Two themes: Pulse (edge-to-edge colour, oversized numerals) and Arena (floating cards on a dark stage), both driven by CSS design tokens. - Radial control hub replaces the old corner menu. Touch - Pointer events with per-pointer ownership, so a second finger cannot end somebody else's hold on a shared tablet. - Touch targets, safe-area insets, 100dvh sizing and iOS callout handling. Structure - Domain layer under src/game (state, presets, seat layouts, persistence) with migration from the previous two-player storage keys. - Composables for hold-to-repeat, pending delta, dice, timer and screen APIs. - 144 unit tests replace the two smoke tests; vitest config, npm scripts and CI updated to run type-check, tests and build. Removes the @radial-color-picker dependency in favour of a built-in hue slider. Co-Authored-By: Claude Opus 5 --- .github/workflows/node.js.yml | 28 +- .prettierrc | 5 +- README.md | 134 ++++- doc/App.png | Bin 14868 -> 0 bytes doc/App.webp | Bin 0 -> 22160 bytes doc/dice.webp | Bin 0 -> 20724 bytes doc/layout-chooser.webp | Bin 0 -> 30254 bytes doc/layout-sides.webp | Bin 0 -> 22746 bytes doc/players-six.webp | Bin 0 -> 29064 bytes doc/settings.webp | Bin 0 -> 46608 bytes doc/starter.webp | Bin 0 -> 18184 bytes doc/theme-arena.webp | Bin 0 -> 28458 bytes env.d.ts | 1 + index.html | 27 +- package-lock.json | 40 +- package.json | 16 +- src/App.vue | 168 +++--- src/assets/base.css | 62 --- src/assets/logo.svg | 1 - src/assets/main.css | 13 - src/assets/styles/index.css | 20 +- src/assets/styles/tokens.css | 200 +++++++ src/components/AppIcon.vue | 160 ++++++ src/components/ColorView.vue | 79 --- src/components/ControlHub.vue | 151 +++++ src/components/DiceSheet.vue | 129 +++++ src/components/GameStage.vue | 60 ++ src/components/LayoutPreview.vue | 81 +++ src/components/MatchTimerBar.vue | 90 +++ src/components/OverlaySheet.vue | 163 ++++++ src/components/PlayerPanel.vue | 354 ++++++++++++ src/components/PlayerSeat.vue | 64 +++ src/components/PlayerView.vue | 143 ----- src/components/ReloadPrompt.vue | 76 ++- src/components/SettingView.vue | 86 --- src/components/SettingsSheet.vue | 516 ++++++++++++++++++ src/components/StarterOverlay.vue | 84 +++ src/components/__tests__/App.spec.ts | 103 ++++ src/components/__tests__/ControlHub.spec.ts | 72 +++ src/components/__tests__/DiceSheet.spec.ts | 72 +++ src/components/__tests__/GameStage.spec.ts | 86 +++ .../__tests__/MatchTimerBar.spec.ts | 43 ++ src/components/__tests__/OverlaySheet.spec.ts | 50 ++ src/components/__tests__/PlayerPanel.spec.ts | 163 ++++++ src/components/__tests__/PlayerView.spec.ts | 12 - src/components/__tests__/SettingView.spec.ts | 12 - .../__tests__/SettingsSheet.spec.ts | 178 ++++++ .../__tests__/StarterOverlay.spec.ts | 30 + src/components/icons/ReCheckboxBlankLine.vue | 14 - src/components/icons/ReCheckboxLine.vue | 15 - src/components/icons/RePaletteLine.vue | 16 - src/components/icons/ReSettings5Line.vue | 15 - src/components/icons/ThMoreAlt.vue | 15 - src/components/icons/TiRefresh.vue | 15 - src/composables/__tests__/useDice.spec.ts | 83 +++ .../__tests__/useHoldRepeat.spec.ts | 118 ++++ .../__tests__/useMatchTimer.spec.ts | 85 +++ .../__tests__/usePendingDelta.spec.ts | 73 +++ src/composables/__tests__/useScreen.spec.ts | 149 +++++ src/composables/useDice.ts | 42 ++ src/composables/useHoldRepeat.ts | 85 +++ src/composables/useMatchTimer.ts | 60 ++ src/composables/usePendingDelta.ts | 46 ++ src/composables/useScreen.ts | 101 ++++ src/game/__tests__/layouts.spec.ts | 141 +++++ src/game/__tests__/storage.spec.ts | 45 ++ src/game/__tests__/store.spec.ts | 250 +++++++++ src/game/inject.ts | 12 + src/game/layouts.ts | 188 +++++++ src/game/presets.ts | 58 ++ src/game/storage.ts | 91 +++ src/game/store.ts | 244 +++++++++ src/game/types.ts | 68 +++ src/main.ts | 3 - src/test/pwaRegisterStub.ts | 10 + src/test/setup.ts | 8 + src/theme/__tests__/tokens.spec.ts | 52 ++ src/theme/__tests__/useTheme.spec.ts | 57 ++ src/theme/themes.ts | 42 ++ src/theme/useTheme.ts | 27 + tsconfig.app.json | 6 +- tsconfig.config.json | 6 +- tsconfig.vitest.json | 8 +- vite-env.d.ts | 7 - vite.config.ts | 27 +- vitest.config.ts | 28 +- 86 files changed, 5333 insertions(+), 749 deletions(-) delete mode 100644 doc/App.png create mode 100644 doc/App.webp create mode 100644 doc/dice.webp create mode 100644 doc/layout-chooser.webp create mode 100644 doc/layout-sides.webp create mode 100644 doc/players-six.webp create mode 100644 doc/settings.webp create mode 100644 doc/starter.webp create mode 100644 doc/theme-arena.webp delete mode 100644 src/assets/base.css delete mode 100644 src/assets/logo.svg delete mode 100644 src/assets/main.css create mode 100644 src/assets/styles/tokens.css create mode 100644 src/components/AppIcon.vue delete mode 100644 src/components/ColorView.vue create mode 100644 src/components/ControlHub.vue create mode 100644 src/components/DiceSheet.vue create mode 100644 src/components/GameStage.vue create mode 100644 src/components/LayoutPreview.vue create mode 100644 src/components/MatchTimerBar.vue create mode 100644 src/components/OverlaySheet.vue create mode 100644 src/components/PlayerPanel.vue create mode 100644 src/components/PlayerSeat.vue delete mode 100644 src/components/PlayerView.vue delete mode 100644 src/components/SettingView.vue create mode 100644 src/components/SettingsSheet.vue create mode 100644 src/components/StarterOverlay.vue create mode 100644 src/components/__tests__/App.spec.ts create mode 100644 src/components/__tests__/ControlHub.spec.ts create mode 100644 src/components/__tests__/DiceSheet.spec.ts create mode 100644 src/components/__tests__/GameStage.spec.ts create mode 100644 src/components/__tests__/MatchTimerBar.spec.ts create mode 100644 src/components/__tests__/OverlaySheet.spec.ts create mode 100644 src/components/__tests__/PlayerPanel.spec.ts delete mode 100644 src/components/__tests__/PlayerView.spec.ts delete mode 100644 src/components/__tests__/SettingView.spec.ts create mode 100644 src/components/__tests__/SettingsSheet.spec.ts create mode 100644 src/components/__tests__/StarterOverlay.spec.ts delete mode 100644 src/components/icons/ReCheckboxBlankLine.vue delete mode 100644 src/components/icons/ReCheckboxLine.vue delete mode 100644 src/components/icons/RePaletteLine.vue delete mode 100644 src/components/icons/ReSettings5Line.vue delete mode 100644 src/components/icons/ThMoreAlt.vue delete mode 100644 src/components/icons/TiRefresh.vue create mode 100644 src/composables/__tests__/useDice.spec.ts create mode 100644 src/composables/__tests__/useHoldRepeat.spec.ts create mode 100644 src/composables/__tests__/useMatchTimer.spec.ts create mode 100644 src/composables/__tests__/usePendingDelta.spec.ts create mode 100644 src/composables/__tests__/useScreen.spec.ts create mode 100644 src/composables/useDice.ts create mode 100644 src/composables/useHoldRepeat.ts create mode 100644 src/composables/useMatchTimer.ts create mode 100644 src/composables/usePendingDelta.ts create mode 100644 src/composables/useScreen.ts create mode 100644 src/game/__tests__/layouts.spec.ts create mode 100644 src/game/__tests__/storage.spec.ts create mode 100644 src/game/__tests__/store.spec.ts create mode 100644 src/game/inject.ts create mode 100644 src/game/layouts.ts create mode 100644 src/game/presets.ts create mode 100644 src/game/storage.ts create mode 100644 src/game/store.ts create mode 100644 src/game/types.ts create mode 100644 src/test/pwaRegisterStub.ts create mode 100644 src/test/setup.ts create mode 100644 src/theme/__tests__/tokens.spec.ts create mode 100644 src/theme/__tests__/useTheme.spec.ts create mode 100644 src/theme/themes.ts create mode 100644 src/theme/useTheme.ts delete mode 100644 vite-env.d.ts diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 61330ba..8832c8f 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -1,17 +1,16 @@ -# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# Installs dependencies, type-checks, runs the unit tests and builds the app # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs name: Node.js CI on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] jobs: build: - runs-on: ubuntu-latest strategy: @@ -20,11 +19,16 @@ jobs: # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v6 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v6 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - - run: npm ci - - run: npm run test + - uses: actions/checkout@v6 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm ci + - name: Type check + run: npm run type-check + - name: Unit tests + run: npm run test + - name: Build + run: npm run build-only diff --git a/.prettierrc b/.prettierrc index 1818847..17c3d4c 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,7 @@ { "tabWidth": 2, "useTabs": false, - "semi" :false, - "singleQuote": true + "semi": false, + "singleQuote": true, + "printWidth": 100 } diff --git a/README.md b/README.md index 10aa871..9a496a9 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,129 @@ # GameLifeCounter -Is a basic life counter for games, -it can handle two persons at the same time. - -[App](https://albertlast.github.io/GameLifeCounter/) - -Keeping holding the - or + area to jump 10 steps - -You can use the setting menu to switch to fullscreen. -When android with chromebase browser is used, -you can also disable the screensaver (enable wakelock). - -Support pwa -> can be installed locally on supported devices. - -![App](doc/App.png) - -## Requirments - -Only a webserver to delifer the js files is needed, -the website runs full on client side. - -## Project Setup +A life counter for board and card games that runs entirely in the browser. + +[Open the app](https://albertlast.github.io/GameLifeCounter/) + +Two player table in the Pulse theme + +## Features + +- **2 to 6 players.** Picking the player count asks how the table is seated, + and the table behind the chooser updates as you pick: + - _Facing_ (default) puts players along the top and bottom edge facing each + other, the way the two player table works. Life totals and the -/+ buttons + read horizontally for everybody. + - _Sides_ turns the same table a quarter turn for players sitting along the + left and right edge. + - _Same side_ stacks every seat upright, for when nobody sits opposite. + + Each seat is rotated towards the edge its player sits at, so nobody reads + their life total upside down. + +- **Tap and hold.** A tap changes the life total by one step, holding a button + keeps applying the big step until you let go. Recent changes are summed up + and shown above the total ("-7") before fading away. +- **Game presets.** Standard (20), Commander (40 with poison), Energy (poison + and energy counters), Duel 8000 (steps of 50) and a neutral board-game + counter. The starting life total can be adjusted freely. +- **Two themes.** _Pulse_ keeps the original edge-to-edge colour blocks with + oversized numerals; _Arena_ puts floating player cards on a dark stage. +- **Player identity.** Each seat has its own name and colour. +- **Table tools.** Dice (coin, d4–d100), a match timer and a random + first-player picker, all reachable from the hub in the middle of the table. +- **Installable and offline.** The app is a PWA and keeps its state in local + storage, including the life totals and colours of the previous version. +- **Screen options.** Fullscreen and, where the browser supports it, a wake + lock that stops the screen from sleeping mid-game. + +## Screenshots + + + + + + + + + + +
+ Six player table with names, poison counters and the match timer + Six players, Commander preset. Named seats, poison counters + and the match timer running along the top. + + Four player table in the Arena theme + Arena theme. Floating player cards on a dark stage, here with + poison and energy counters. +
+ Table layout chooser with the table visible behind it + Layout chooser. Opens right after the player count; each + option is drawn for your table and the table behind updates as you pick. + + Four player table with the sides layout + Sides layout. The same table turned a quarter turn for + players sitting along the left and right edge. +
+ + + + + + + +
+ Settings sheet + Settings. Players, preset, starting life, theme, names and + colours. + + Dice sheet showing a d20 roll + Dice. Coin and d4 to d100, with the recent rolls kept. + + Random first player announcement + Random first player. Announced in that player's colour; the + crown stays on their seat. +
+ +## Requirements + +Only a webserver to deliver the static files; the app runs fully client side. + +## Project setup ```sh npm install ``` -### Compile and Hot-Reload for Development +### Compile and hot-reload for development ```sh npm run dev ``` -### Type-Check, Compile and Minify for Production +### Type-check, compile and minify for production ```sh npm run build ``` -### Run Unit Tests with [Vitest](https://vitest.dev/) +### Run the unit tests with [Vitest](https://vitest.dev/) ```sh -npm run test:unit +npm run test # single run +npm run test:unit # watch mode +npm run test:coverage # with coverage report ``` + +## Project structure + +| Path | Contents | +| ------------------------------ | --------------------------------------------------------------- | +| `src/game/` | Game state, presets, seat layouts and persistence | +| `src/theme/` | Theme catalogue and the binding to `` | +| `src/composables/` | Hold-to-repeat, pending delta, dice, timer, fullscreen/wakelock | +| `src/components/` | Vue components | +| `src/assets/styles/tokens.css` | Design tokens; a theme only overrides this block | + +Adding a theme means adding an entry to `src/theme/themes.ts` and a +`[data-theme='…']` token block — components read colours and sizes from +variables only. diff --git a/doc/App.png b/doc/App.png deleted file mode 100644 index cf4e84cda8c7b2c6d217dd9863eb056037c0470a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14868 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%0DiY>Pr*K25dzQw^;623dP1zPrs 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 diff --git a/doc/App.webp b/doc/App.webp new file mode 100644 index 0000000000000000000000000000000000000000..e181e00c07212caa7574738e17e6f0e975486626 GIT binary patch literal 22160 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 XVA_;R9k5<3tk^AWAoN+IwwwR}3{nbwD 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≺NLj04!3eV?wARB2@#P_uZmoueELYVN{Qg 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

ayz?sPtNoM;rCP zF!DPO0&K(i_I@U_Lf&S#%VAG%I0!QJ!3yfCE7IJW@k$Yl{;IO~xG{MaGfQD6gRpt@ zsqq0>)~~#5%m4nkI&F}wI_OJuhPOG&8=9poCX+keG#*~JO$(uI5$CS*Hj8yxK#=KP z>EAMK-F}C!1$~|FYb98geqWnO_15-L1&a6-(~4XXEgjc8r;eH{wy0TdOs?qs(jE=> z-%%*)sNr|(O`?nwG#;3-{DM9;c)0@Tto_|+VW={8$>CMAr2#kB)?KgArRoi?@y)=d z$?g*!NdQ(#TkEx*>3SAyiAc2Fx|l$BmFjCUDXA!_-ea{}F%Y1^+RLB*x^-Rb4Rwf#&hw|8{Z#0`3 z6t`-B=!>bt7XP9+K$u;hqC<4Iga&fPij6s*jSM13saH;LBYkhP(sdxEKZiJ5&~-+5 z#Q5Fhxnvf6S(u`)V8Ig^cQ{>R@iP5uX1VY(Qcr?!LY40yEF7bPnsB+W*fRr&x_q+# zz?H2zPo%m2M96i^jpB2bUHQ2?PH#8gbPaLVL)wm8+nkd3LTDRE00003wTBKa*!SkM zWjlTG8($vyMZ$udfJ^cNL_>Gi0whMxe|7w|GBQ9s4kS%~HX73kvX;8Zox0;qHU)NY zMAGHF0d@p_#0>MbTx-vVgE?ZA1p;YM;AwI6nX%=>>E|SNp}UeifA

?WOCsjCx=P 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 FfB@bpE{6aB literal 0 HcmV?d00001 diff --git a/doc/layout-sides.webp b/doc/layout-sides.webp new file mode 100644 index 0000000000000000000000000000000000000000..e320c28c658bc8e4c9f06deb10fe750b7d1e6498 GIT binary patch literal 22746 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 L0FC-9jvxR4q$Yby literal 0 HcmV?d00001 diff --git a/doc/settings.webp b/doc/settings.webp new file mode 100644 index 0000000000000000000000000000000000000000..d20640504ddff2bd99469b448c27819fd80f3f95 GIT binary patch literal 46608 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�jFZQl@AKZf23snLgtlCW=$-#3U`*L* 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 acnbZu;n1X7zlpr!>EYET1?9hbULXL5xP)N< literal 0 HcmV?d00001 diff --git a/doc/starter.webp b/doc/starter.webp new file mode 100644 index 0000000000000000000000000000000000000000..583ad716e96919da189d255d29679d572ece485c GIT binary patch literal 18184 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

-zWk>KjaN^@<@;dOjg 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 literal 0 HcmV?d00001 diff --git a/doc/theme-arena.webp b/doc/theme-arena.webp new file mode 100644 index 0000000000000000000000000000000000000000..b85f8ffd171d3ebdb776a3498bbec57b056a19ad GIT binary patch literal 28458 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�Pf-QllJ#8H815g1ALrzKg9)e-g*Ld!AvwL$Ri{EQi-d+uHGRj?@a zWkO`(uZ7%}IR!s%TvwFaKk>yd=u1|WnmdEX9V5+iVpNnd?W|pqpMr`! z*eEbw$O-ITV_IBhs#0qq$0GefwyvWfs_BE|4brgx?5$BcUCHNIofQqqGctO5zsJLy3F2KQGZNHg!22FIf@w-B<;q(;Vr&k@IrEq^#9nCR2F*TrRoQkNcjQoD7-NTNQ#PcgTmIDg z zgd^wlVXM};(v9!oWkqxi*5&w?zML97iCky#cxT#PAnCu%3|6E*n+0t-sVfkEmnpW% z!$^y!Hz-X}`^lFR$!7>DM%3+6y22Xx0}fdGn+!xun4*R<_5^}$RYjh;pr8DpgMaLK zn#>Eg-2TopSSTn;1b#6WkaIv~4AL$zUsm_SPGz)-&sYoV0xi znY9NYrzc8gD{J(-h$SEU?$!>s`KlfyzQs29V9hF9q$se4{3E!|2x|)}AHz{gIBzI` z40bv3AG(Ij%IwCU^K#<6p0hb#$A-s$SIx|AFnDMA=-Et~TGdU-)= zCT`(bI^c}x2n{EAyF9hhd~S0f`4&{~w%b@J8_=_`Z%ZFWMvV+_mX}udUw=|IltVSa=^9A-)KI-m}Wo<@|wGA++D|K)7}J4O5(4zkzU;lj&;Loz%&< z^gw%hx@J13?8fs~M@L_&*(^r+{h4N;(j8fqMN68C>@mrTHk$Z&&XLI_Cb6_KDGvYa z&afgX0KWPUekkk$5e`B!VWWX5z}F_&EnGJtt}%bDkw;=|$w<9?xY+Zn4^g>P?+VA_Iw9DjK@q>{I?;H zJ-aiWB}v!PS5&L6%$6m6&bX4r#DrY&Kob>^T2GVWTZT;W&aWP1J&ah5gEwflA;@** zF=k@vvI4_7Yk~S1p4z}#?x<#9>h|ux4t#5j9p)cVK&(ZbGub(A)iS=)LDZY;4J2PU zwxZyVU&;LvNGEe3^z)>oHoliUhOWi(qwru+5SI-&D$|Vn>0hu$hGRX04-sD13n{V+ z${|GT;=_r;mxy#XD+F+ejA`qxHkXt(RLwtRD^Un(YzgS~H7IX5y&V#W%g;#lI)O3G zqyi_#ZX+4vE}uggsTcVg)?yPWfmMA6Zq8yEjCzcO2#T~RD&fIgp|w>Nqkr?sH*MX! zK!Ca|h!Y7_L!4N;<6edoit5zstkQGevjtdw6}LVPHs1gf+>cQxZ@Swx?NNOHp`Gbu z`?O)1Fx4ZZ9j_xM-b}^?b4ef6o&q^aRRry{%RzVF&VtMcU~Id$_?A}fXoLt{-qoo& zer%qGZXe~cV;=j!cs)DN$B*xlFTx$@Om`*UAN{#Ll{0Ei{fGeE`;<{i{nos2diG!Z zy5JKBL9`!oux>?G1U*xQnLw&rDKEV%I8%qNi+Amy(X8dqSt!=VM^cL?fy(IAe)oDx zh&N3$JG?Bdinl7ee%v#JTmVMP5CeaG??b-_^2(G>(KCqA2-^W{w1i-Q!JKF@#+ZpQ zRMZMN$MMDocIS@;;}b(>>kn}&R?df+ja;JmgRYWroY6nZIQLaAO-7O_WeuYNq z@DxO^2_Kq?4W++Hd&$B!Ot^KNpF(Nxf!Mj*y1M1}QN5?(;_G&~4?{o1C8yiW?F%bW zHH4Sd!<`yKJywA{gK7$!EIoo4FFDkp?ChS%f#29#QEdV?A9EH{Sd{#D-{fDo+NtXn zsD(pI*|?{BFKXY(UI~dms+X4IeplDJssj1lND>ZAxTJ}eN;ANIgxQX3>U&=HOyt6I zrwzqptP8kM`g2Nx?4RPIYT#c}qMs0FVybqR!#en1`#^=K>~6Jr=dTNYNF*oDqK7|r zzNC~<&aB04Cn+p~g3+qDg+p_G84BSR8c(oqmTBqJN6RxeN8qobjTwbz68-3b;=5(RsNX+nIe`R>>5w_JQ9h>)E}F=dIB65x5w!l05ZB8- zJdguajh*k%?N}+l`Lg-qFp7WIMmo0P(W_dm+h+&s%HH42q<|;6TIdGZ^1JbkRx0*U zF0S$>zkxa|GvMr|QpFG=$3s`LA2L~OnHCT=TzH?4HOXF3O!QAW?BrOy^~3+cuQJak zVUW@pz`og?Puy&{F9A0i0kN#GOG^c(Q?v%AdiUeeHmYM@3E327%j7Gha52g|ulWlU zMm#axYk)Tsyqa(;K|Y*D*u)05<{WL6dgGTqo%PFN2~%h{;5t*Rxwd9Q#O^2jG?_j? zc7-vmB{JFZZw0Yphhn!KLf5XUvZ8=V!^{+rlfN13^$f%fwSX0cW zUFV=ApbHwFBc+hhAiAU;9BPm?Zi#CmT7j3rri-^k>U_S-NcB)Z3;XHO^sAKOl1A;9 zky^(}P`Emt@f%n3TK=}d;)sf}DbJ7@1Hn|EKhpj5<|kqBZOOHNIw7~=>UJ0xZjjXh zrnhVS$5ERO@heyeVib+==&(R|wFL2lh=79Vbfn_u>#(4`8Cr873VgKk3ZINzVPmA{9?BNIHF`C9r8&2m~y4n6LOG81}*v2D@NXOF(yuT z9``vfCuwwogM}vhQ18_7(y%a$(nc4zr;*Ss4Bs*ESLQ0j-8BH57jZiuD*8i_AxbNL zwA}(9--H^yqc=vQT%xeJqYE{b_p-*ucVCrstC|a`Q_iWK*q^?z-&#@VC4wy`j(_=7 z1Z%^+vI~M@Hf?C;ogID#IvcjPSw?o^V*n>6pMJH7ff_ds2TiYrx*W;X0NY9Ouix7x z37&)X8H)0J-mn{u0E=)6Ct(QxvV2E=)hQo2g$ObGb%`^*1n-1HhfOB*?AxFlba%LHb_WvI$x?^0iPP9X=1IB|J=nw0$u>&Erw^2p?uAY22@S%KL_Eaz zwl#Ag8RX;RDs_^$HiAN60QWdzh+Gg)LCstJWmTwcD&H+EK5qzuNNxVzlk}4?neJ~d zUFL(b#Mhi!1;|`DA00CQC>iu%bdu}Pmy|+H@=uMg2 z6&Z6|$7WXr6Lx_QjxVr>`@=kL9}OGVSG7Zef{(~8WvB;|97Xj_pcMN7V&ilMpv5N1 zQG%r`&n|BrH$C;hM4zpeW{~Q%(w}=Rhsj*@aTChqGxHG5l!8?t6rd*U0v7;+ zULP{5xG1HKPqIIT$4oL}p=ZsFjdV?Fwoc)uDj>?Ccii$dr6n7Em*bUYRQK7-qNmk8 z$ydC;0}CKOZ&R1q7P-Y$4E74(gp`%yec-2jF@@Z6OB<&Qrj;zaUe7S3eu+OFo1>3q ztSD#{Z8OwZZyeCRAZLyhFPjOP&2~dj|8oCzn}9NUTs&5j$SYCNq@}DQx2=k}m*MCE zfrC`k#$@-4S7qEv)N-0V1MO@Ilel>8P95#W)$HXdTU`pX`{$V;%>|7~d_2)cI+mrb zDdAC5_9rKTs$fbe8*njtV!AZw-gohmfTgyIZiJ-0Jv8W4Y?4d)NRZGjMx=c(YI&V!-AF|x*I!nM5zI`Q{!lSt%N)HGRY6Ro!zot?YSd{T%XUbzYKLn)M zVPW7iOd(&19Ew1IZ2Yz`{`(>peOH%~6}^@~U-IYtB(er8AV{(>`Ykw?b*)O5OWGdl zMUN^8EoBmzsQ;F7nL$m9c9A{Nv~sGhVejyyPvp?Z31;mDr+66~-D!S!jrc=i(PlSy z3-8w<@SLtQ$_PzU?jkLRSm2h)R+1N4OWgUy@*1-gy6CdC}r+nDAhiyUBcHvtd(<`{zj9t z$}J-v=o`@NYYTFjMk_^dG|Gor1>3du$7I@g?wO7B$`BVu@-Lp#!M|k)gEnH^%nGZtoK4Pk12S`df3Y@9p+f%-#GEFvHU1DD8XdC=gm8_F z#m^iz{cFZt@p$q3W*;zEDAe2%1(#EXkBuMWpldV5>r;c556KY}ws+IyO(E3Z=(S*e zdkJB)Z?H;;!DND9E4O?8q}bI{TYX6u@%-`{Fco-pp7n=ni?)EjD)hB^5uM2sKOS6aWr0c} zo1uaesoj=--(Mb3G#(X%Jfc2-B#jXxI9GmzfXb}E+nV4rj=@v;*#^fWiVI!{fO(#` zw+B@WBl+V_ks7ZPu(pwHoOKcQtP%Y2rf!W-7no$0NTv?vWJx>VHERe zbc+6o=r#ogn&YdK>HPZjC&7&I%pHAZir(x|i?iXD0gV?N#Z*$%<&II_+00K3Q?r(7k}$GPD-|pjBk1H6 z`O22(dwstk>Oc+}QWw4NQ~JWbt#bvYAyz%`mr!gG=a4iC8%9cdCzIpIR7v&zfX3(F zErWepA$M5lx&(NMDFjrw@^v2$3RbaVyK8~W7Tn`*36q;6o9!|W#j$pNipco@#b)V3 zw-)BjbTymo?7y#8^G#vhu6{5cG;@EPgdgwl%dF3*D-O|R$V3pnjWv!4)gdzmY%?3L9=J6KU#&5I-vu&av--4mw`x%A;%f*+LdI5n$T=AlVr zc5_<`=%gQe)pMbAv|J7&BI?k#%kDc{$3(%y;<%X&n%L6I zQO5^NY*dB9WSBq`gj|>s;d^Zq1W-q;xMAy~e$r!Vl5{}AmCIPO3##kSIPAOV2|TjB zE21jN3oUHmAdn^xTv0?~V#xQ~l`2EUkcYOJ+N<-gbsWp$>AdV1epAq&Z*=I8~ruy?@DQZ7*-ET9zb+)6U!(j~?kwlb%MrMM=5eiGx zR*OC@5+i`QQ)Cnw^OAtv1`lTp9swWlEkxM*rs=(qwJ#bwmQ}&PBkRcAW1}30&Ok?= zp1T`uJ>NTFtm1Gy=YJL#;;k4Cy9)@X?CHicdk$w~MbpmP7JC|!WGG$Xh#9X-Pqc>e z7cX2)SxvH+P!rg=K)NuguF=X$Q+`_5k?28Ug&+2o|CRlEG6s*9H-S;bzqxXOrx7Th z-HkYX!ArewgZmSl{;_T9}(DQ1<(h~b&t^$`BjEq`T>E(LXy-{F) zGP@788y!mMgD(3a_M+NhK%>j;Yhl#7cmH-+}$E_$arJ8oPHz{W;EwzZRV_#me+c zd0k5E1>2BKxSU+Ba>1eS((nwutzFSYa44sZnvog~H6ogNbOVJvV6RGV0!R9{p%NGu zrYQ%2HdvxYL`N;e*UP~vWdm+I4vdowh8SB?g1tSM!13f*ScNBdF`X5?TRKAMeTpgY;upL~bq#iksTHhxOp@%6wdb&1 zoyfLqAn%S$ni>ZxCWT6oDMIJlhN-!>S1@7bC`H4ijLj&%iRUN8QnQN_Rt^7Q=i`RG ze)K}DF`)!lS}kEua>21m3e#VkO z;c>zO{05avBB-$NJ)3$8D6u8Tlevr2`mRSSS~c^ee)qO|A8L-6mg;-*m%S*q!d;E2V1pe zS_pIjp(C~CN1%I<+i4<_qA0kxRGG;RSj^Xe_V1o)4E{eKf;(ZQC<{|8X6V8UhpMAO z#9{qfWcOR9>40}KV0k`W&7UodBvS+MaKe(|C+qr{0vQvhNx^p;zyrpqf@}b&wKxK z_J^E~A1Tq}H8X_&#jJF^-iH%MCHyhyxBo5~8_U*ddyHpNk*ql)`=A)&bGH*m70w>| z=iBAE1vFS`$ZgQt#0cM}a%@b{~Ss%8AyhDQq%&jy?GsR(`O;-71o^SA>;-$WBD`NyqNG=omFgQlirfa5G z8mM;kw`um`w>(h-j}(a55%-%zJWXH`k;LKff0yz>3s-guHpsru-;$NV?eH!GXH#g4 z=1aYR#-Bw z6=u5C=<-1vfQV$W=6cfX3Uk8(I>vl5V{>-0FQ(SA zgWr|{)E#bh_BlYAz1Z<|K@jtj4n{-Z|@fjHWO^$rhaM zkb~!@O7s&h&E@n)ZCvq}%>6E{%M_{5dQCb?>rrovfYPR~w4=l{x)WB^g`_r#uZPH5 zF(s0rMv1*>3UV_%(2jlE|BEXdu{IZ?`!d3lBG#9_12w^efK^4p>=(1yihGhJ;|yJ8 zzs4;7Tr<(3%;ElD;E+U;bMnp;3Y+jDwzSv&V$!df4=AVch3iW}CKxN4i5J!nL*cWQr_yv?m(n%mG=PoR<-1A?rp?8j^k z?I^j^d_N86amqREj~!uy2p!Q21f_91j?hBxTm-6(0BsG-bIQtjy>{`6!GfsaRO%}) zJgJAW6lW=3lN4B0kb@*UKnS~*D)6UwxY>L=qzXs06{azOziP*_9Tk*6Y`V?g=BGT4 zcJ1q@aKgunBU8{O3!4+3oQi{8-Z39mv)wC}oumFI2!=A+M{U%k&##M~hON6={B+6+ z%|#loV{XU8`r!~LZDTXp>Q`eG@Wh=|ov-B$s~?aXs!N;4RZ?L!F)y(>wNqZs(7eehFJtn+}IJrWx>C-KR&*cQ;A5+jv|+v_IUzh(@TZp?}3e7h6ZA$j$#{` z12Z#|&(_$0bnVN}s#-Zz(>0NTK7t9$0a`@G)i8vGvLKr0uqP?6*TH9mC)>lcJJL$O zHPuKDx=?Nx!^jZ$29#)f2*hij334Ic7S9#nF=ixj2=vuPdEb6qe8Of@x(nNNiTJsO zLi{#BPENv71piF^qN(WSI1@r{doW&{Ry-))^JnC*MT5!%@W;nj0Ww4HD1X5wQo7?Fr>2eJ+HoRsuU&h~roY4H8dY6C($s&su#{P(W z1(yJjE#8w$PmdtZrD)Fyn$1;-rp5~sKT*$VX!88=&sUd&Yd?y7 zD?536B=V)chtNku4|^J;*2fqrdAt6mF$_tX26?Q}4PH{!gokLzr_TdC+9zJzQJ9M> z=V+YDDc>+C@uXck{j4s-6r6yaBP>lBR;o9-V|MvWNa=;U01o3hU&h*m@Lw^dsWFgfh(yOd+vkQX_vk3QXCaB%`Rf&(Xdm_e=ZMJZN)vkIyaKo7ip{Sr0{Gu zhVcn#N9ZV3Auji6R_KhSX5oqk05R1MsN39y23sBhyq*pjic`VZcGfFG9x8Q=(%;Iz zta_0u>J?B>ak3+lBH$%vnpdU`-&5dld&n3{~%kW zzWVmvb?YYzPJnNiD%V&{if()d;YsE`qKoVG>&oI_*1r@|*0bfP&p2YxNf1a!*x`QJKshiMVV~VO)^@s`WaKcK+0U+v&7A*j z0XKaN3ng13VV{jNoH1cydgCjd8DxoE_0hf2zw6xuQH-NO_9-=mFs37C**?Kr2d2=% zSP~C8`r%!7@pwz_&0sdDVIEuHfaJ2eBn*^ie*)p1*0NolfCLjQ+fe^qK(N}Qn*PpP zu7>Y_k-V&m6;y#-fX+Cib*KS-B5eP}kA@Tk5vGIRz1VzJy@kID#JphWp4d)ALn5I+ zUG_tc8f08_?Z~00KW7GfHI?{#If$gGD9C8f{`IfGY?@S<#qOAv3#Thp8-j6D)MRNlpHd@gcq%{#%PGTM+B@>YDpl4BW!`=Oed#7+s}yvRG>GscdIt z6xzLbUu91oHFtTTc4>%K@}E;&sK@S!5FeeNin(elEGFv($%lctBFK*;X|HLRwLU1s zzF(l8a>`3Fr0Usp=Q^JW(?hb=(q;1-CfDm7v&nsy*cGD zS}a^<5-qJp?iKE}1@=^HhuB`V+H=Tl+U>;W6T#4&i5&U545VWIAiYidC{01tPeJiM z3{j6snO9eQ2WcK^<1tG;MS@h8%csuE#Ap?|orDZFh``)G>U0j8wDmAAm@RUE-YFSV zdq#kSHrlPt!O&XS4K^n~XMzBfp|X1{lTm;a`QMsyR@JK6?Dsc?C#DE&7*5pK>tHB2 zW5lyhUhQS{hQNG#7~6E-utDkyZwed4t2Y3o+q;t3pBu^F9bc!r*iwH4g|V!#Na1>% zqs_mXqoOEJtYUSrPdE_bDk@t_SSd9A;*XqXGi+Iku}4Nu4EaS+CoA(`zsOgCJ2DQ= zqi}nf)`XY{X*h(H(BAxFtpcei2<=n19Zox6!!K!_=rWjQebhn{ap{cIY8DIo!%{5vTPhAMIxPKm!u(UO!>bO=4XF{i}98CgkMUosi!A z5IC1dPmdVG+68a^W|%NPXV8R>KF`d+ogOrl_S1PD4~P3z#3u{cW6ue!@A!SkU3WkryK4?81z9_j@R04t|lh+0BWl zy%y$$>2*xMEy<-iqIhKpZYDWz#C@5j6h8^h5Y3uu%S4;AxEJ~V1zRqp(Y*a;V^>iH z_fnw>J3|8~6pcZS8>IL-aGi{u04{k*N?GDJ8q5y-JGb*r(MpmUK>vV!XUq{vH;*}% zw+Yi+SJwIeN&bni>_hwN22jZgivevFH`;mzgQscQcUpel`Pb+5zl%vI#?@<|PigSM zSe&&VC6vC4oghH`-BC7ZYu2in!@&I+ zYF|T_hb7y`3D782TYJ~@LS&6PTc)U_u-Z4W^+V;h)fCk3YBw9Uic8FRNvj!%Y31DS(2(U%&0#;K0Q#pg5WHfSOogd&wm9H_OFn8j2&XxM> zo`k+|em)*M%+@v=1l9E{8r!{h>H*T=fymszY$OOQvpInk_0Js zU-pP2l2Lu>{nA_+3*4Q=&w4wKFI>bHYSX0Hl3}<%dLy)_vyvA@uRpJiQ8|i{1hrfj z^tmnq!q@%<5&I8FHSVVZmg%W(!E(WKRN??L5&;}=)e@85Q8hmx0&T14xr0{`UO}&I zd}1CjB0O^84~+Q0azR?;qs4A0cBf33GzJLJCS(Pr8-_y!F5@7ou;J{@a|M)8=uGoU z`aKiux+s5|xBt*t&6WI~3P`=^aPuDyoOFwbuvXBTW7iaDiBN3B;g;~ax%(0S zJnl~ORZIN?dfkd z9EvF%uCqFg(7kBBBNLwSsMC@_f{C+w~fku01LY;7ow zYm%@Vr4V+j4KtP>LLQ?D%0cXB5iFtCarqjW%pPwr$2N*7=E6}{t=fS2yId?N0ukY+MftTm&WtPov^5!5&cSv_;$Afk z-{4MB&lZyFf~Q(+gzF0IZ45HNnieSzViVI~!2;CT=VZlKJc$RV2o4)i(+O+mkHKa= z&P~7XJ~2ZKO&DNjrsji;bz99m?k_8&iq5XFp&H_cbq!W@{OU8KZ0 zq_O746zS(35NWqTeJAndcHkcq(Zui!U2iG|vyV#J2)Gqh?I)$OpClqkO6cG55Z7DY z8dW@1C%h7hl$=XR3Z|VNj=5(u3LqCy2b)|fD=u+ttw!>Uklp&zhy^(W$|=vD$fr}J zjWQLw1u1Nl=YBI~=>q_(*tjLfC=Ygl_I4Qp&^tocazxKL_E^juO5ntd`CaE|qwc>W zyc-t#xmIk^NMe67Iu~4Mo5!ibp1pY-e|*cXQ^SrW@q9X_fu(%HRdCgKv;zy7K0UKn z()neKB30A*q=t?^@<-lEJUTVv3X5mU|D@%qdHH`U7;L614X+CwkSF@r21Zlt%;&;_ z70cNNzEJd=&2DpbTdc>%_N26P(IyWWr^g0}FweVBhtjFolKtWgPPE%j`jluoxKtdY zY&j4yWUNuIbHv~4jkEc5O{NG8JVKHLK(y_stYC(kn(a_hsAb16WGQ)>~m8ISWz^N#_K9kGhv1XtavSR+drt zz2$i2TQt*vep`Y&aBjaueORa~;3L$8LVUl?a)oHc$6T8Z;!2lN!+6RbAd* zUA>puGvX3_ZA}mVDL+&LMC2z+*MRMja4BQXPd@)0@=vU(c2$*qBVKOdq6Nv=n?gGNET{x{tr5ZVjib+HA?3 z>JWiaGi!=Lx95aEQ1B6&jx_xT&r&}32fb0b|NB%Zj4Ith4P7RJ^27c- z>aFi8hes-Q**!8{k9acca>_9F%SUt=WDPZOc@5{Kcu1Yjd>s+^GWmUiOLJx(Q~B^= zX(Z^Tm#Mv{-^c;%?*D?BK(y#UZTQdn3q@#?RqZuk>80T6G+MF}C(8}J&CvUu*mUnd zu%rKl;HUrdwEAPE7zZVgTl|A>;J>PJpJ}f5hC5^^^Zj5kd_kbKSz#_iPV%23fTGhN z{rDFbc+illbt_e(jB=TTq$e^pwwTya5T#ta_Jp9;4QTOlTjwRpLUIz(m>}JJacMds zV+({v*b&_0rR0WCC{^ot|5W~Xgmp*fsW~Mr+^e&K$)79asyz3&w@Bivd>m$>6sCNw zn;Cdi`%1IIR+~I!?XJu0k&@u(N!)=0rt-2PM`?Iwyl%vU@!I73KPAVLQP$rj#7@7x zTNGbbIB`^=np)0Roz#+CXskj<#P%}q_J(A=rwqLC>S$aR9c7pu<3G94gf~#OwAM-V z&a9soh!fhqew2Qoxh4t-lklvD248jSa^UE5jmXo3Nj<%tU zT4D!I4MIJp67R?0H2hOG2Xerd>hBDqN_F(Y@y|kxz0ajd3cc)FQ_ZeDTxPHeq)1{B zwqV5`7;h{FkD1A04|!`A$%;?&|k*FSc< zE{j<-L?Gtm1Vy49Ec{QK^cD)H)y+K~rU6PYr1*0v>)FuSu;B&Djgf|ZS17}~p`Ne3 z+VnHnLC6W0=Y_sp80m-yMu%hH0s@o&6q%DT{W#mnqV|CIXB%8_7RQMlt+uzzmz4(_ zzUPGSe)M&gcIE9y(@N-r>6!ADe31ZW9Mo*&g;M2mJYykr@VRanoR!AC8XL6O1Jm35 z+ITkZ|MhpGYoimF`Uzk(DAr01P?K3y%Xsk%GWkF+|MQME-5B4Dmpn0@q(nNymHt47 zoOWU9Lw%F7TKS;42>10XLk38b29`dpKBx*$85Q$lrqIPgYJ{OKW1rx1>F|eSjj_U+st(#OOik8nM zFJ##^CM~Ud_Q2k=FZw~@W#jQtI#rEMY^P^AgF&`8$TQ@fq?c8!y{I8U;OQ`&MEyT@ zf{R;VjJN`0sgvi&B2HC02jRs(_aMLcGFJA8k^cLVx$*t}HOxZGiCj-sAoF_p;D_^< z-V635=9fjH;m#WRmV$$a^(cOTB|w&%U*$ATdcHDZvqCU(=bp9YGdmdp|!3zu5DQ7r1s?xmj?qX%MMWiQGGs=v06=-d^F;%q6Mim&oO z$?9~oLG=HQFj=AeN(&^4)uo|Fcm$z8T_R-_=9DEcKYW~P(KN3k&JC0G?XaPm>H(nX zg_GTyyuIY?b_BHV=|A*&jNnfa_5|Y(D4|v*%Dmdpe7aPTiZAqg(Yn9;HtW~AM<4~7 zA%lIK7uUS3`v#?rVFC2|CksPsmlG2nut;>Kb1xlRa#%{pw}ZlmJ|*~ygJJUto4u?1 z45XuI_io@R48bn*?KH}=aJGvL1jkS?>+Yomz%uq_H)*<|aaFEPTpd49WtusI;G{*J zMwZs%Xv&fF+>gy7M+TK+RWima7&9!n@6tmuM%&&2Ap73V7%aOEt}R63o-DM?kKcmN z{{bbJ#A=pJNq^PpC#h$#*#`Ct7~VOa!j#r369Agj`C+Fsald6rWlRrSbGKjJJFm7Q zp|KT^6!Dz}YPQxHOOjy@CWPNni;tA51q3!aDUPNl$?P}et3{0q*`AxCVO%};^wX#_ zX0E%qw>LU>wSIa+Zi*n za&!dTMAX!~d**2LGe^0_eQP>hTLp;_8h?B*q5S$#U z2ALi)xW0a%jItiMYHjRU&OL7=uyTD?RxxC$tQaw`(ccCsyCae8-??a^apz@s;7LXK zz*ALTp9?Y*j(dV>XHIP#&=@c=z#ZTumy=hMy`tl!0Q(OmQ!Lw$E^#?tK6!C*uSSzw zBIEdu(QOG)^D0{bXdbPfr`8wLzBs}sC?%urCtre@=y}m$7e)%R;cF{cGD@40T2~U3 z>}v~&GO3eMd<+F?y0bj_rfH_)zHDQcA7eR_IxNV<mHtaVz(;n13QYeD z4AVm*XbIJT{0%XTcooaJm?`gD5zKQdy%$(>NmlEnqCZ=|-TCEZ)GVzn5=XA*mK1in z-656)<+?afQjT3LSLIMi_D%^mY5+4p0+LNFO%c&My3^9F(U#n{?k4?I8CZsS^<2yR zw1p9fnL4dOq)Rik?kcUjOI**&Pf@pVOc9>5{wkKgSK^0obngq23>{Qs^wHZ+v(PlI zLxivXwY@yIpe13#Td%q|GuRu}tE|{;rxAHjR2?emNX)TC<>*umK@zX5xiI{$km=FOiY_R|P!G z4vX`9X3NUf6Ej99^y%S6m-oMJVcsC-)Bg~rm^6=RAPSCB{GwP#NS*$D0k)I%iI}ub!Od3uJ0s;^{;n(loY>@mJzGlU<$}}})PX8?`9X%)sb^qbl~y{x`};-K$&iK}p6avRSB{5W8UlIE z)|vu^*{&ETba3XLXqVA4E_AHI$C;A-?E4@DfeUZP6~&zEk1#BTFe^z~0Q*p!W#rwGe^>0yj(10rDVNPTlA!Y+g4WEKp}G{S zPM9W5dCE!NsshhEE+;lkdLYbM@G`!#qh`SWth82WEvHnC%S5RSlFg})OoOR%q>XXw z??)9e4!nN|NQfqF8r0qNZ{U8Y>G#6I)n>u+ns-?L-8pX;^j4~E3%3A$J>#mI)JuG6 z7*Wh#Q+92?G9-;`GSAI}_t?L3NmfX z5tf!#V+L?}nRveQ@EwHTBM47yuOXPIAVRfyi`O*zg|xY4v=6I*kDlUnV2Lwjnyg+C zLzp`lrUCoLQNHKz^9@G1=X41^o-E7w;l?`!W5e*uPO+~2{@~a5<0s%+nIhSKd%K?by~Rtki4q&g*S){K&=rZ8i?6${9sF!D(bIQhL5f2nN64&G=h zj%P;6rq83Kd+fz3hwtc3aYdGnR`~<&lM4<>7=Ie23ob2aan(l+FB>8aRx`)xKs0>( zk`#MQ&4hqN_@L8q7i<-E5|P`MMs^ZU#U~`t?dr0(MQ;DrWlt!XkGh4Q%_w`*M&~Ty z3eUcZmyJ}}qcwHgkPQ7u_^Z8*ToXyCbVW7k42OYwar}AXZVCzs+czUV#~0uk#&V** zfCAU3mB$!vErY4_AKHc8ge9Z9e~eO4!j;k&^4D=N!Y9mi)XwaO?1BTv=9s_)ua3Y; z4pifus*#KJlC!h41*8Rc!Rem*-7Vt=B~kLwNhQaEUniNRbyfALBResL-I6sBd`zZd{JbtcmU`b*uhNZ)6AxM8G*=3`>G&#nLm>z#28>`}bELIIp< zL_P$*)3v3N9kWNU097)4>e6yuTsl4ZQcmdE%at0LZ}E%(b0r=F&*Da#8Q|i3v0}H!96t2=5IkB@Yk8a*~zDRsUzB z;}T6$lzE3AtBSp6h7W@In|#Bb4|#AP3R~#JuG0*)0_%DtF(Gyv?pcy`b`zdA8a&Cv%XO*4XBqF}vpAI|AQ1@_A>GIujtJ1o z*J@}Xovxf1264o&MH~-Vf@raTAM@A!8*zs40 z$$_BKM=om~`=xzg|K_ErrSXeSR?CBQTc0a8nPlPz2HKN|p{tm`U5Yo>g1MrJkQMW- z$6g?Yxvj_le^!9?_qnn%NnNy%BVO84NJu*Ukqy=evZ<(PBA*+55R6{NOrbx(cD*9q zbsM&3Zc;%i>9O7EKYglExcW$Et55R35Iet{c*0$qKnN(1d<+PX_HMZrQcQnB$umxv zOp_>^ExO%!yOooa3%T?5^n~`gefh4Xj0v(UD%iW?Cvb=#mTsf>@r8jOqJvf;V@|1*uFFpnl^U=Dws}X%3lqx~J)Bf|1H@2N)8$Fn zs=(LRi3o$DIJiFrja*T%E9!-fd&@2IJy zi(V&jhRH7c5@rv3@!E@rQ2c5yow&x2!zn>^J)Qkuh?b+uYh>;?oH&46Q#Ag}{E{wB z+?Eh;wLohIRG`mUgtiUN(NHI25(!+ZCcBqk#EFNlNX&i7k?fl*Ez5jEZ}#BRZ)^By zv|=$l6rVfWGw6;taa#}0pYmFxuW|HOJgck=$jKPWYA2_MuJOn;ZBjb}@QfY^b}ed# zd}W;pU-3psN%pkF?zI5Ny>0|>8TB@j$8#}A;gJ(kRD_6u{M9kT0MQKo=P?4av~=)r z@Ch9DK96ji|K_Ltb7w;cB%&kX2aj@yZK*0^!kG$nY3?tzW#Z@tEU~~+(olBZQU9dm zgy^$_O>=|O9yAaLY`cVt8`pe9Al_vS z)z;R3$>*Pk@U~6-neBHi?{)rWN(DeLm9Nu>sCr_R&&=#4uZ`(|sSCT@QqUBRmb98h zm#uP~hQRv@978c0c)1zGY%we|!CvL6tD2+k5~WoH)hI74n~zrQV#{me3xV&ZYnvN` zOCowU<8m5xqR9NF`8Me=j=12wMZ-s>0NK5iF7~JH&!mxpSXDK7CK`em+G6V&=kgwX znPw@wwIWYDco6Ezmb(n+Dz<}F%A!KP^46q(>m6yY%7W}(<1uUHv=gj?lKEz2J|jlm zY}A*h&{i|7VPwaZ`IR0>_#t2 zrSTt@6kEJuH!^37x9Cyty&~U}RW>whahhKqP7tR2F|UqUuwhJENJBuF#b4D3e;-zm z)zqV2Ta6>NKvxecxl6t z&yC&)m#^etBn$dwn4)x%dN#1ID1&e?A7$VoCXs)Gz_m7kS=``>Cw>ID)khOqsf7F3 z$o6bEUCHp}&pJP)5DhXu|H!Mxl+aa0PQ0eTI)+3xQw$fJm_>$u>o@tR6`gskUGZg9erY#vASfU5e#A~?grdd{z z6ut@MP^eJVlEUDh74a8>;-n{hU4~66x(WMiZ7vM?#P9oTxHVKX8Vp9%1UKWHztH;> z9Y3iFMQnDLl&l^h>~oM#x4Y3eo!m3bM2q`qa17euJ1c=42n+#AK1M(XKk( zNJsKS1L9WV57c0%_j8&kQa%d&UO4ny((sDjI7*%2n{{D_;D6G>IO_OHPqjw4>Sjn_ z5r@J3(A}@kPftD3y?&UFi&Q}K>$D|Pa_OP+%(*6A#U%fsE{M6Ce}n4XJK>U&_|rH4 z>xv3?!1=;j=^<3Y?cjcX+=L%CX|XE{_Bn`B@SOi(uj=VilsXm1tLKQ(qI}j3!M!U% zt<4^6WaD(SF5>IngmiL*4bp{zxN!hgseb`dCTKFZ{fpH-l2V5F))h%F=BTxnGidoG z&xF%_e&UZC(UHy)Q>nrbV-?|mOMH~GD$apBqgtoEoi0-`Kg1x>tIHj+^9~IzWtM5~ zKmi^c!~+iJu+Z+2HvOk>FE;GKSw}*TM9K6v&Kia@f&!XnX=C6;RHuq^D3{VA;>L>8 zPb6|BvGPKcMFJTmhuh`Gqi+vsCTnZ)Va#al_7>_@Fuw!>TcSDoxGK94NVCiYdd>DE z!CuILeHh~_UkXGp@Lud4I>U8I#q~PrV5jG+6>vAzIk9hK-tJWYmv8VtqDb2yaT4CZY|QC`{y&tCguv#Fbh8n<_W-tl6W z{#D~V7nSv+%ICfZ^@|5CWGxqs_kCafFhR|9_!W34^KARmO8=N*rgnKwvX^2d#XAM2 z3qD7SOc$}p-}y$W{$<}Us@A{lal;1tj7X>*<6=kA9f@aFTO-HAu1zsH%OWC-we^5b zlKMN$UAcZjWyIT@;!m3pupb?IXt)x z_$7&4B4i4tRu2g19BtxVd_!tzqqv>a_pBoD-fc73AqPYAP#lsB(w#_)w$Y=;fmpgz z$|*_$jFGoh_^M+Nb0Tx-H(j$dPGax^{D8&Nn*KHrTsMw)Z?N{Fl8qkwoEDp8aE@dK zIS1&XJW$B+UC>@gQ)`Nb@-83~F^7k2(Lju2K2NI~VSo;x`ii8BREp0^06MEk$WrgQNY+%Gq}U4W)S#9g#Ni^U$8<5K3rB8AL{>u+g$<6cCCzh zgvDUDmVj{pBzCtTd6)^Lp>m)}Y-nlg7@m4?gy>RosKVQu2_GE_3yaNS#v|(Od{ml; zq#@9%0=?9Cn1T_RQZ1MyMB=!~uF_>v1@0a`;;&&F&7xzcd9_1&MfKWj{W#0PBOGj& zX#2M)pT*a0nM4LpQY?49M{@NRu4~?6lc&DWa)myIAHTSjH{>CU)eOLM=WOO3gnK;A z3%WD&%o1iNSczl=q&TP*p18hN8L^a^*beK|iLGqp}G-@wSF_J05#BfUOObSNfk z>tLe1v9qgGYsGFvaVYcLnpQ-vR4LZNZAAvsl>zCYY|Zf1f`-w`0H}Fy>Eo#jPpp9K zfnNCnNO>B7FFfX&ohUyJv-@CK=omS!|Jp2h5K>=+)v=DgNnp*iMxWcG>{u)f15XL0`C1ebTf5pZ|4l-|VDXg_zhAK^kdm#uX}sNm-&W7iK8K04lDX1)34h zbpyFTm1;n@U#JD+R1L||poi5>Qyqf(^vO!ZDrnnEtp3f51k~hi7g)*2kKNkmXg$g1 z&_`&y`Jp7YweMr;f3J)G01Qk_k{83ijt@^NzpKcpN5T-jHQZP%zB!9*p}%9&6DFgBRy4s!OB!RIg$u^zP>S(= z3C>WHk7U^l>uMLgI2Qgijr&ah!W%ki|5f94@wu--f zmL7}U?s1NQJqBXl-FQdDk5u^y9OWig8cohld?aMnC$jLxIFe7E zg#w+sd1x~;YnI(?JDwte+Kxt9^l26>SrYM2ZU+kGEg13RbbmC*nJBBa`JeI;Hr&JU zk&DKez_duNsO822-?rOgF_|xjfrNGrsuKe#fab*MlU<7fg?mEhacV4{WhcvxS&vf zy=*BX``*D3RH15wJjv>0pr`3yhL*+JUdw~@s~Cz>f8xo7-a81!?&Z}@FA8e^=>&mM z{oY`>y&MAk%E zX;ZjORwoWTKTeONpgf&)jQRQZ2#nj9L7TH2O2%`nfCoh4ETc z2DS_}i28nenrC#()0o>hZPG5*|uPXZy_VJo8wFlc_zU)pw-w7`J`c~ zS2J3P{0p$CFYuqa&r6mGK&hZVk*ib$XGKQDQ5){E80RkK^n1Ec^)T$xyWL)=6JWmr zw0-Aq;es3FC&)P&55It6m>$IV^QZ;c{}SFdeUWe8(lJ1)U;*TsR|D!BDp5_by5yHE z8QXI~?VAU7d5l9UtTX(3Z+Hg)3Zd$oMx6+a9G9EQcFUxB&$_4OV# zy4vk;`4kx0xh$vsn&k4gUZTg$08ppM1}yb*XXmkCMl=!CEro9--mtsLD}8@VhqAI2 zhs&^lW~HB}8Qq?UxF~>b+;b$OUp=`-Gj05FEGl%?<%g@cU+|a5{~@w=iC{cp<8vJ0w~e9xMM*n zl3E_n#Bu8T>LZ27Q~trG&1~zOvZpiln0S-L_yVh@eFu&_Brlv#FT`*$n($()H$Ovn zFEf0$p8|$lH6c2V_H+3-m>#4piB~M|-@mXX_5MDdh*wsMf2rA)XOz~KAH2W?`_uOY z!e?>%i}e7yI^ASF%a9y)6vVvQJx8t%i(y9+2p{C-E=}pF$ql2QCULie+G7~-E8>%MgDcVS*_*v>ay9fp z+St3+FG^Kt8q|=jFyy&D#Xuw8qAt^bWigW3KOmaU);LL%KSdH1WnVrX15_uu_3O90 zGU;rPVZ(c`I=C$SN<%xP;@s`}XH%~Ov#xdg7wG9wFaWQez9W7g9uqvDxNf-Ik{ve; zbW9`0kp7a@0WOL(LZyejLK=({_K|T4pW9_~aK9vjE-;8<_dBAHeSXrko`(1SUz~+NXdPH0^_9PM&6iBx#U)Dv!5gLniC6IhZ2 zsZtK-%WeKp?8=xJr3|P29g9#h9xhNrSOb>BPP(O?|A9abc&7NRc4v|47`S+1IesD$ zvi9lV-<$^F%@*7K1t;k8jarhJ60$CE)^1m1UH#{3%a9B5;QIg87Q&ZMhT71Zr6tQ58 zgCk0LbTCff0000Zz=oiztIiY_AD{pL007+a%7Pb2@+8or000002Bm#6<(MM?00000 z2aF4aZ2gZ)=s*jP^yBcDmdbMo00000008@0@Bjb+001*O;07L$jqQiIpi+RgfB*mh007e- zcr?D^{NxO08PWcU3vvJe00000BmF<|m^v{rk3#a9noB)>b?O`GTPfOic=Q}ET@ZwX zZ0k4v%Y!t$nGnJh00000200d3X70eD&AIcZ!z2zZSTgwC>qjiHg5?1;cp_G6b$S-$ zzT8eg|8*FpFR}Bx&wUu5K*txmadc#whq~w)*U*20yT@mAWpT(y^`g!6M_nN?b!@(4 zArPPd0000CXZ0+lx)ImmBuMYaTDy~Lq4_0)F|v}R@4rSmI``-US{xIRJQ()%azp2) ziXM%rRVe)89R|YM?(bAnvXUZV>u$iQEM6WnL5NBtoWf@Ur#HRg=(r5LIWep(NW(?l za8C_53^=+82|xvXc!44X0^pMtD>60aNfOTT?SCsxt(5 zvuqskFznc?$QDX}iUW=E!KWF8hJprnzApyI4&>%-{DG10ge=KEh@MZ$k}jyba{Q7F HU;qFBcF?}{ literal 0 HcmV?d00001 diff --git a/env.d.ts b/env.d.ts index 11f02fe..04636e8 100644 --- a/env.d.ts +++ b/env.d.ts @@ -1 +1,2 @@ /// +/// diff --git a/index.html b/index.html index 6d3a8d9..2a63fad 100644 --- a/index.html +++ b/index.html @@ -1,16 +1,31 @@ - + - + + + + + + + - - + + - - + + Game Life Counter diff --git a/package-lock.json b/package-lock.json index 01eacc5..112d557 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,13 @@ { "name": "gamelifecounter", - "version": "0.0.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gamelifecounter", - "version": "0.0.0", + "version": "1.0.0", "dependencies": { - "@radial-color-picker/vue-color-picker": "^6.0.0", "vue": "^3.5.27" }, "devDependencies": { @@ -22,6 +21,7 @@ "jsdom": "^27.4.0", "npm-run-all": "^4.1.5", "postcss": "^8.5.3", + "prettier": "^3.4.2", "tailwindcss": "^4.1.10", "typescript": "^5.8.2", "vite": "^7.3.1", @@ -2434,24 +2434,6 @@ "node": ">=14" } }, - "node_modules/@radial-color-picker/rotator": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@radial-color-picker/rotator/-/rotator-3.0.2.tgz", - "integrity": "sha512-JiSVkN/hSMKqIcQz8KaeLvbzA0UrZkKQL2Q8s4MUxehjLcciTSctG75V8trHk0tPkTfglZjLlmg+S5+xO3GBLQ==", - "license": "MIT" - }, - "node_modules/@radial-color-picker/vue-color-picker": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@radial-color-picker/vue-color-picker/-/vue-color-picker-6.0.0.tgz", - "integrity": "sha512-mYITvZq7P37xrTfSjl0pDMSGlxo6nDawc8NdxGgHJhrvnPQsbLar0i/wJRK6A94NsDucJxHJdE5nbsajkVewwg==", - "license": "MIT", - "dependencies": { - "@radial-color-picker/rotator": "3.0.2" - }, - "peerDependencies": { - "vue": "^3.0.0" - } - }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.53", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", @@ -6831,6 +6813,22 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prettier": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-bytes": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", diff --git a/package.json b/package.json index 5d9328c..c0640ef 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,20 @@ { "name": "gamelifecounter", - "version": "0.0.0", + "version": "1.0.0", "private": true, "type": "module", "scripts": { "dev": "vite", "build": "run-p type-check build-only", - "preview": "vite preview", - "test:unit": "vitest --environment jsdom --root src/", "build-only": "vite build", - "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false", - "test": "npm run build-only" + "preview": "vite preview", + "type-check": "vue-tsc --build", + "test": "vitest run", + "test:unit": "vitest", + "test:coverage": "vitest run --coverage", + "format": "prettier --write \"{src,.}/**/*.{ts,vue,css,json,html}\"" }, "dependencies": { - "@radial-color-picker/vue-color-picker": "^6.0.0", "vue": "^3.5.27" }, "devDependencies": { @@ -27,6 +28,7 @@ "jsdom": "^27.4.0", "npm-run-all": "^4.1.5", "postcss": "^8.5.3", + "prettier": "^3.4.2", "tailwindcss": "^4.1.10", "typescript": "^5.8.2", "vite": "^7.3.1", @@ -34,4 +36,4 @@ "vitest": "^4.0.16", "vue-tsc": "^2.2.10" } -} \ No newline at end of file +} diff --git a/src/App.vue b/src/App.vue index 442f0ca..0f8d695 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,115 +1,85 @@ - diff --git a/src/assets/base.css b/src/assets/base.css deleted file mode 100644 index 1f27637..0000000 --- a/src/assets/base.css +++ /dev/null @@ -1,62 +0,0 @@ -/* color palette from */ -:root { - --vt-c-white: #ffffff; - --vt-c-white-soft: #f8f8f8; - --vt-c-white-mute: #f2f2f2; - - --vt-c-black: #181818; - --vt-c-black-soft: #222222; - --vt-c-black-mute: #282828; - - --vt-c-indigo: #2c3e50; - - --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); - --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); - --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); - --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); - - --vt-c-text-light-1: var(--vt-c-indigo); - --vt-c-text-light-2: rgba(60, 60, 60, 0.66); - --vt-c-text-dark-1: var(--vt-c-white); - --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); -} - -/* semantic color variables for this project */ -:root { - --color-background: var(--vt-c-black); - --color-background-soft: var(--vt-c-black-soft); - --color-background-mute: var(--vt-c-black-mute); - - --color-border: var(--vt-c-divider-dark-2); - --color-border-hover: var(--vt-c-divider-dark-1); - - --color-heading: var(--vt-c-text-dark-1); - --color-text: var(--vt-c-text-dark-2); - - --section-gap: 160px; -} - -*, -*::before, -*::after { - box-sizing: border-box; - margin: 0; - position: relative; - font-weight: normal; -} - -body { - min-height: 100vh; - color: var(--color-text); - background: var(--color-background); - transition: color 0.5s, background-color 0.5s; - line-height: 1.6; - font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, - Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; - font-size: 15px; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - user-select: none; - -webkit-user-select: none; -} diff --git a/src/assets/logo.svg b/src/assets/logo.svg deleted file mode 100644 index bc826fe..0000000 --- a/src/assets/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/main.css b/src/assets/main.css deleted file mode 100644 index 13480a1..0000000 --- a/src/assets/main.css +++ /dev/null @@ -1,13 +0,0 @@ -@import './base.css'; - -#app { - width: 100%; -} - -a, -.green { - text-decoration: none; - color: hsla(160, 100%, 37%, 1); - transition: 0.4s; -} - diff --git a/src/assets/styles/index.css b/src/assets/styles/index.css index 977c1f9..8a51938 100644 --- a/src/assets/styles/index.css +++ b/src/assets/styles/index.css @@ -1,20 +1,2 @@ @import 'tailwindcss'; - -/* - The default border color has changed to `currentcolor` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. -*/ -@layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } -} - +@import './tokens.css'; diff --git a/src/assets/styles/tokens.css b/src/assets/styles/tokens.css new file mode 100644 index 0000000..402c1ec --- /dev/null +++ b/src/assets/styles/tokens.css @@ -0,0 +1,200 @@ +/* + Design tokens for GameLifeCounter. + + Everything the UI paints comes from these variables. A theme only has to + redefine the token block; components never hard-code colours. The per-player + hue is injected as `--player-hue` on the panel element. + + IMPORTANT: a token whose value reads `--player-hue` must be declared on the + panel itself (see the `[data-theme] .panel` blocks below). `var()` inside a + custom property is substituted in the context of the element that *declares* + it, so the same token declared on `:root` would always resolve the hue to its + fallback and paint every player the same colour. +*/ + +:root { + color-scheme: dark; + + --font-ui: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + --font-display: var(--font-ui); + + --radius-lg: 26px; + --radius-md: 16px; + --radius-sm: 10px; + + --ease: cubic-bezier(0.22, 0.9, 0.24, 1); + --dur-fast: 130ms; + --dur: 220ms; + + --safe-top: env(safe-area-inset-top, 0px); + --safe-right: env(safe-area-inset-right, 0px); + --safe-bottom: env(safe-area-inset-bottom, 0px); + --safe-left: env(safe-area-inset-left, 0px); + + /* Chrome = menus, sheets and everything floating above the stage. */ + --chrome-bg: rgba(17, 18, 25, 0.94); + --chrome-raised: rgba(255, 255, 255, 0.07); + --chrome-fg: #f4f6fb; + --chrome-muted: rgba(244, 246, 251, 0.6); + --chrome-border: rgba(255, 255, 255, 0.14); + --chrome-shadow: 0 24px 70px rgba(0, 0, 0, 0.6); + --accent: #7cf5d5; + --accent-contrast: #06231c; + --danger: #ff7a7a; + + /* Stage + panel defaults, overridden per theme. */ + --stage-bg: #0b0d13; + --stage-pad: 0px; + --stage-gap: 0px; + --panel-radius: 0px; + --panel-fg: #ffffff; + --panel-border: none; + --panel-shadow: none; + --panel-pad: 0px; + --life-weight: 800; + --life-letter-spacing: -0.04em; + --life-shadow: none; + --zone-size: 100%; + --zone-radius: 0; + --zone-bg: transparent; + --zone-border: none; + --zone-opacity: 0.55; + --name-opacity: 0.72; +} + +/* ── Pulse ─────────────────────────────────────────────────────────────── + Successor to the original look: saturated colour from edge to edge and + numerals as big as the seat allows. */ +[data-theme='pulse'] { + --stage-bg: #08080c; + --stage-pad: 0px; + --stage-gap: 2px; + --panel-radius: 0px; + --panel-pad: 0px; + --panel-fg: #ffffff; + --life-weight: 800; + --zone-opacity: 0.5; + --name-opacity: 0.68; +} + +[data-theme='pulse'] .panel { + --panel-bg: linear-gradient( + 155deg, + hsl(var(--player-hue, 0) 96% 54%) 0%, + hsl(var(--player-hue, 0) 90% 40%) 55%, + hsl(var(--player-hue, 0) 92% 31%) 100% + ); + --panel-shadow: inset 0 0 120px hsl(var(--player-hue, 0) 100% 22% / 0.55); + --life-shadow: 0 6px 34px hsl(var(--player-hue, 0) 100% 18% / 0.65); + --chip-bg: rgba(0, 0, 0, 0.22); + --chip-border: rgba(255, 255, 255, 0.28); +} + +/* ── Arena ─────────────────────────────────────────────────────────────── + New direction: dark stage, each seat is a floating card with explicit + controls and a light, wide life readout. */ +[data-theme='arena'] { + --stage-bg: radial-gradient(130% 120% at 50% 0%, #161c2b 0%, #0a0c12 55%, #06070b 100%); + --stage-pad: 10px; + --stage-gap: 10px; + --panel-radius: var(--radius-lg); + --panel-pad: clamp(8px, 2.5%, 22px); + --panel-fg: #eef2ff; + --life-weight: 300; + --life-letter-spacing: -0.05em; + --zone-size: min(26cqw, 30cqh); + --zone-radius: 999px; + --zone-opacity: 0.9; + --name-opacity: 0.85; +} + +[data-theme='arena'] .panel { + --panel-bg: + linear-gradient( + 160deg, + hsl(var(--player-hue, 0) 62% 24% / 0.92) 0%, + hsl(var(--player-hue, 0) 45% 11% / 0.94) 70% + ), + #0d0f16; + --panel-border: 1px solid hsl(var(--player-hue, 0) 75% 62% / 0.34); + --panel-shadow: + 0 18px 44px rgba(0, 0, 0, 0.5), inset 0 1px 0 hsl(var(--player-hue, 0) 90% 80% / 0.18); + --life-shadow: 0 0 46px hsl(var(--player-hue, 0) 95% 55% / 0.28); + --zone-bg: hsl(var(--player-hue, 0) 70% 70% / 0.12); + --zone-border: 1px solid hsl(var(--player-hue, 0) 80% 70% / 0.3); + --chip-bg: hsl(var(--player-hue, 0) 60% 70% / 0.12); + --chip-border: hsl(var(--player-hue, 0) 75% 70% / 0.32); +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html, +body, +#app { + width: 100%; + height: 100%; + /* Mobile browsers: follow the viewport as the address bar collapses. */ + height: 100dvh; +} + +body { + background: var(--stage-bg); + color: var(--chrome-fg); + font-family: var(--font-ui); + /* 16px keeps iOS from zooming when an input in the settings sheet is focused. */ + font-size: 16px; + line-height: 1.4; + overflow: hidden; + overscroll-behavior: none; + touch-action: manipulation; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -webkit-tap-highlight-color: transparent; + -webkit-touch-callout: none; + -webkit-user-select: none; + user-select: none; +} + +/* + Grows a small control's touch target to roughly 44px without changing how it + looks — for the icon buttons that cannot afford to get visually bigger. +*/ +.touch-target { + position: relative; +} + +.touch-target::after { + content: ''; + position: absolute; + inset: -10px; +} + +button { + font: inherit; + color: inherit; + background: none; + border: 0; + cursor: pointer; + touch-action: manipulation; +} + +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.001ms !important; + } +} diff --git a/src/components/AppIcon.vue b/src/components/AppIcon.vue new file mode 100644 index 0000000..01cfa1e --- /dev/null +++ b/src/components/AppIcon.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/src/components/ColorView.vue b/src/components/ColorView.vue deleted file mode 100644 index 7375b9c..0000000 --- a/src/components/ColorView.vue +++ /dev/null @@ -1,79 +0,0 @@ -import RePaletteLineVue from './icons/RePaletteLine.vue'; - - - - - diff --git a/src/components/ControlHub.vue b/src/components/ControlHub.vue new file mode 100644 index 0000000..0412e1f --- /dev/null +++ b/src/components/ControlHub.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/src/components/DiceSheet.vue b/src/components/DiceSheet.vue new file mode 100644 index 0000000..89835f5 --- /dev/null +++ b/src/components/DiceSheet.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/src/components/GameStage.vue b/src/components/GameStage.vue new file mode 100644 index 0000000..c4d584f --- /dev/null +++ b/src/components/GameStage.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/src/components/LayoutPreview.vue b/src/components/LayoutPreview.vue new file mode 100644 index 0000000..68c4456 --- /dev/null +++ b/src/components/LayoutPreview.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/src/components/MatchTimerBar.vue b/src/components/MatchTimerBar.vue new file mode 100644 index 0000000..e3ecad5 --- /dev/null +++ b/src/components/MatchTimerBar.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/src/components/OverlaySheet.vue b/src/components/OverlaySheet.vue new file mode 100644 index 0000000..1eaa792 --- /dev/null +++ b/src/components/OverlaySheet.vue @@ -0,0 +1,163 @@ + + + + + diff --git a/src/components/PlayerPanel.vue b/src/components/PlayerPanel.vue new file mode 100644 index 0000000..2097287 --- /dev/null +++ b/src/components/PlayerPanel.vue @@ -0,0 +1,354 @@ + + + + + diff --git a/src/components/PlayerSeat.vue b/src/components/PlayerSeat.vue new file mode 100644 index 0000000..73e876a --- /dev/null +++ b/src/components/PlayerSeat.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/src/components/PlayerView.vue b/src/components/PlayerView.vue deleted file mode 100644 index 1fff946..0000000 --- a/src/components/PlayerView.vue +++ /dev/null @@ -1,143 +0,0 @@ - - - - diff --git a/src/components/ReloadPrompt.vue b/src/components/ReloadPrompt.vue index d6bc86e..ff31592 100644 --- a/src/components/ReloadPrompt.vue +++ b/src/components/ReloadPrompt.vue @@ -3,47 +3,65 @@ import { useRegisterSW } from 'virtual:pwa-register/vue' const { offlineReady, needRefresh, updateServiceWorker } = useRegisterSW() -const close = async () => { +const close = () => { offlineReady.value = false needRefresh.value = false } - diff --git a/src/components/SettingView.vue b/src/components/SettingView.vue deleted file mode 100644 index 91811e7..0000000 --- a/src/components/SettingView.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - - - diff --git a/src/components/SettingsSheet.vue b/src/components/SettingsSheet.vue new file mode 100644 index 0000000..99c9722 --- /dev/null +++ b/src/components/SettingsSheet.vue @@ -0,0 +1,516 @@ + + + + + diff --git a/src/components/StarterOverlay.vue b/src/components/StarterOverlay.vue new file mode 100644 index 0000000..bc8899d --- /dev/null +++ b/src/components/StarterOverlay.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/src/components/__tests__/App.spec.ts b/src/components/__tests__/App.spec.ts new file mode 100644 index 0000000..0576a55 --- /dev/null +++ b/src/components/__tests__/App.spec.ts @@ -0,0 +1,103 @@ +import { describe, expect, it, vi } from 'vitest' +import { mount } from '@vue/test-utils' +import App from '../../App.vue' +import PlayerPanel from '../PlayerPanel.vue' +import SettingsSheet from '../SettingsSheet.vue' +import DiceSheet from '../DiceSheet.vue' +import StarterOverlay from '../StarterOverlay.vue' +import { useGame } from '../../game/store' + +const openHub = async (wrapper: ReturnType) => { + await wrapper.find('.hub__toggle').trigger('click') +} + +describe('App', () => { + it('shows the table and applies the stored theme', () => { + const wrapper = mount(App) + + expect(wrapper.findAllComponents(PlayerPanel)).toHaveLength(2) + expect(document.documentElement.dataset.theme).toBe('pulse') + }) + + it('opens the settings sheet from the hub', async () => { + const wrapper = mount(App) + + await openHub(wrapper) + await wrapper.find('[aria-label="Settings"]').trigger('click') + + expect(wrapper.findComponent(SettingsSheet).exists()).toBe(true) + + await wrapper.findComponent(SettingsSheet).vm.$emit('close') + expect(wrapper.findComponent(SettingsSheet).exists()).toBe(false) + }) + + it('opens the dice sheet from the hub', async () => { + const wrapper = mount(App) + + await openHub(wrapper) + await wrapper.find('[aria-label="Roll dice"]').trigger('click') + + expect(wrapper.findComponent(DiceSheet).exists()).toBe(true) + }) + + it('toggles the match timer without losing its state', async () => { + // Attached to the document so visibility of the v-show'd bar is real. + const wrapper = mount(App, { attachTo: document.body }) + const timer = () => wrapper.find('[role="timer"]') + + expect(timer().isVisible()).toBe(false) + + await openHub(wrapper) + await wrapper.find('[aria-label="Match timer"]').trigger('click') + expect(timer().isVisible()).toBe(true) + + await openHub(wrapper) + await wrapper.find('[aria-label="Match timer"]').trigger('click') + expect(timer().isVisible()).toBe(false) + + wrapper.unmount() + }) + + it('starts a new game from the hub', async () => { + const wrapper = mount(App) + const game = useGame() + game.adjustLife(0, -13) + + await openHub(wrapper) + await wrapper.find('[aria-label="New game"]').trigger('click') + + expect(game.playerAt(0)?.life).toBe(20) + expect(wrapper.findComponent(PlayerPanel).props('isFirstPlayer')).toBe(false) + }) + + it('picks and announces a random first player', async () => { + vi.spyOn(Math, 'random').mockReturnValue(0.99) + const wrapper = mount(App) + + await openHub(wrapper) + await wrapper.find('[aria-label="Random first player"]').trigger('click') + + const overlay = wrapper.findComponent(StarterOverlay) + expect(overlay.exists()).toBe(true) + expect(overlay.props('player').id).toBe(1) + expect(wrapper.findAllComponents(PlayerPanel)[1].props('isFirstPlayer')).toBe(true) + + await overlay.find('.starter').trigger('click') + expect(wrapper.findComponent(StarterOverlay).exists()).toBe(false) + // The crown stays on the table after the announcement is dismissed. + expect(wrapper.findAllComponents(PlayerPanel)[1].props('isFirstPlayer')).toBe(true) + }) + + it('keeps life totals across a reload', async () => { + const first = mount(App) + await first.findAllComponents(PlayerPanel)[0].vm.$emit('life', -5) + first.unmount() + + // A reload re-reads local storage; the shared store is rebuilt from it. + const { resetGameStore } = await import('../../game/store') + resetGameStore() + const second = mount(App) + + expect(second.findAllComponents(PlayerPanel)[0].props('player').life).toBe(15) + }) +}) diff --git a/src/components/__tests__/ControlHub.spec.ts b/src/components/__tests__/ControlHub.spec.ts new file mode 100644 index 0000000..d06ff42 --- /dev/null +++ b/src/components/__tests__/ControlHub.spec.ts @@ -0,0 +1,72 @@ +import { describe, expect, it } from 'vitest' +import { mount } from '@vue/test-utils' +import ControlHub from '../ControlHub.vue' + +describe('ControlHub', () => { + it('starts collapsed with the actions out of the tab order', () => { + const wrapper = mount(ControlHub) + + expect(wrapper.find('.hub').attributes('data-open')).toBe('false') + expect(wrapper.find('.hub__toggle').attributes('aria-expanded')).toBe('false') + expect( + wrapper.findAll('.hub__action').every((action) => action.attributes('tabindex') === '-1'), + ).toBe(true) + }) + + it('makes the closed ring inert rather than aria-hidden', async () => { + // aria-hidden on the ring is invalid: it keeps focus after an action is + // picked, and hiding a focused element from assistive tech is an error. + // The binding resolves to `undefined` when open so the attribute is really + // removed: a literal `inert="false"` would still make the ring inert. + const wrapper = mount(ControlHub) + const inert = () => wrapper.find('.hub__ring').attributes('inert') + + expect(inert()).toBeDefined() + expect(wrapper.find('.hub__ring').attributes('aria-hidden')).toBeUndefined() + + await wrapper.find('.hub__toggle').trigger('click') + + expect(inert()).toBeUndefined() + }) + + it('opens and closes the ring', async () => { + const wrapper = mount(ControlHub) + + await wrapper.find('.hub__toggle').trigger('click') + expect(wrapper.find('.hub').attributes('data-open')).toBe('true') + + await wrapper.find('.hub__backdrop').trigger('click') + expect(wrapper.find('.hub').attributes('data-open')).toBe('false') + }) + + it('offers every table action', () => { + const wrapper = mount(ControlHub) + const labels = wrapper.findAll('.hub__action').map((action) => action.attributes('aria-label')) + + expect(labels).toEqual([ + 'Settings', + 'Roll dice', + 'Match timer', + 'Random first player', + 'New game', + ]) + }) + + it('spreads the actions evenly around the centre', () => { + const wrapper = mount(ControlHub) + const angles = wrapper.findAll('.hub__action').map((action) => action.attributes('style') ?? '') + + expect(angles[0]).toContain('--angle: -90deg') + expect(angles[1]).toContain('--angle: -18deg') + }) + + it('emits the chosen action and collapses again', async () => { + const wrapper = mount(ControlHub) + await wrapper.find('.hub__toggle').trigger('click') + + await wrapper.find('[aria-label="Roll dice"]').trigger('click') + + expect(wrapper.emitted('action')).toEqual([['dice']]) + expect(wrapper.find('.hub').attributes('data-open')).toBe('false') + }) +}) diff --git a/src/components/__tests__/DiceSheet.spec.ts b/src/components/__tests__/DiceSheet.spec.ts new file mode 100644 index 0000000..c0baa0b --- /dev/null +++ b/src/components/__tests__/DiceSheet.spec.ts @@ -0,0 +1,72 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { mount } from '@vue/test-utils' +import DiceSheet from '../DiceSheet.vue' + +beforeEach(() => vi.useFakeTimers()) +afterEach(() => { + vi.useRealTimers() + vi.restoreAllMocks() +}) + +describe('DiceSheet', () => { + it('offers a coin plus the usual dice', () => { + const wrapper = mount(DiceSheet) + + expect(wrapper.findAll('.die').map((die) => die.text())).toEqual([ + 'Coin', + 'd4', + 'd6', + 'd8', + 'd10', + 'd12', + 'd20', + 'd100', + ]) + }) + + it('waits for the first roll before showing a result', () => { + const wrapper = mount(DiceSheet) + + expect(wrapper.find('.result__value').text()).toBe('—') + expect(wrapper.find('.result__hint').text()).toBe('Pick a die') + }) + + it('shows the rolled value and which die produced it', async () => { + vi.spyOn(Math, 'random').mockReturnValue(0.5) + const wrapper = mount(DiceSheet) + + await wrapper.findAll('.die')[6].trigger('click') + + expect(wrapper.find('.result__value').text()).toBe('11') + expect(wrapper.find('.result__hint').text()).toBe('d20 · 1–20') + }) + + it('reads a two sided die as a coin flip', async () => { + vi.spyOn(Math, 'random').mockReturnValue(0.1) + const wrapper = mount(DiceSheet) + + await wrapper.findAll('.die')[0].trigger('click') + + expect(wrapper.find('.result__value').text()).toBe('Heads') + }) + + it('keeps earlier rolls out of the way until there are some', async () => { + vi.spyOn(Math, 'random').mockReturnValue(0.5) + const wrapper = mount(DiceSheet) + + await wrapper.findAll('.die')[2].trigger('click') + expect(wrapper.find('.history').exists()).toBe(false) + + await wrapper.findAll('.die')[2].trigger('click') + expect(wrapper.findAll('.history__item')).toHaveLength(1) + expect(wrapper.find('.history__item').text()).toBe('d6: 4') + }) + + it('closes from the sheet header', async () => { + const wrapper = mount(DiceSheet) + + await wrapper.find('.sheet__close').trigger('click') + + expect(wrapper.emitted('close')).toHaveLength(1) + }) +}) diff --git a/src/components/__tests__/GameStage.spec.ts b/src/components/__tests__/GameStage.spec.ts new file mode 100644 index 0000000..18cc9a7 --- /dev/null +++ b/src/components/__tests__/GameStage.spec.ts @@ -0,0 +1,86 @@ +import { describe, expect, it } from 'vitest' +import { mount } from '@vue/test-utils' +import { nextTick } from 'vue' +import GameStage from '../GameStage.vue' +import PlayerPanel from '../PlayerPanel.vue' +import { createGameStore } from '../../game/store' +import { GAME_STORE_KEY } from '../../game/inject' + +const mountStage = (store = createGameStore(), props: Record = {}) => ({ + store, + wrapper: mount(GameStage, { + props, + global: { provide: { [GAME_STORE_KEY as symbol]: store } }, + }), +}) + +describe('GameStage', () => { + it('seats every active player and rotates them towards their edge', async () => { + const { store, wrapper } = mountStage() + + expect(wrapper.findAllComponents(PlayerPanel)).toHaveLength(2) + expect(wrapper.findAll('.seat').map((seat) => seat.attributes('data-rotation'))).toEqual([ + '180', + '0', + ]) + + store.setPlayerCount(6) + await nextTick() + + expect(wrapper.findAllComponents(PlayerPanel)).toHaveLength(6) + expect(wrapper.findAll('.seat').map((seat) => seat.attributes('data-rotation'))).toEqual([ + '180', + '180', + '180', + '0', + '0', + '0', + ]) + }) + + it('lays the grid out from the seat layout', () => { + const store = createGameStore({ playerCount: 3 }) + const { wrapper } = mountStage(store) + const style = wrapper.find('.stage').attributes('style') ?? '' + + expect(style).toContain('grid-template-columns: repeat(2, 1fr)') + expect(style).toContain('grid-template-rows: repeat(2, 1fr)') + }) + + it('routes life changes to the right player', async () => { + const { store, wrapper } = mountStage() + + await wrapper.findAllComponents(PlayerPanel)[1].vm.$emit('life', -3) + + expect(store.playerAt(0)?.life).toBe(20) + expect(store.playerAt(1)?.life).toBe(17) + }) + + it('routes counter changes to the right player', async () => { + const { store, wrapper } = mountStage() + + await wrapper.findAllComponents(PlayerPanel)[0].vm.$emit('counter', 'poison', 2) + + expect(store.playerAt(0)?.counters.poison).toBe(2) + }) + + it('hands the preset steps and counters to each panel', () => { + const store = createGameStore() + store.applyPreset('duel8000') + const { wrapper } = mountStage(store) + + expect(wrapper.findComponent(PlayerPanel).props()).toMatchObject({ + step: 50, + bigStep: 500, + counters: [], + }) + }) + + it('flags the randomly picked starting player', () => { + const { wrapper } = mountStage(createGameStore(), { firstPlayerId: 1 }) + const panels = wrapper.findAllComponents(PlayerPanel) + + expect(panels[0].props('isFirstPlayer')).toBe(false) + expect(panels[1].props('isFirstPlayer')).toBe(true) + }) +}) diff --git a/src/components/__tests__/MatchTimerBar.spec.ts b/src/components/__tests__/MatchTimerBar.spec.ts new file mode 100644 index 0000000..b52a445 --- /dev/null +++ b/src/components/__tests__/MatchTimerBar.spec.ts @@ -0,0 +1,43 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { mount } from '@vue/test-utils' +import MatchTimerBar from '../MatchTimerBar.vue' + +beforeEach(() => vi.useFakeTimers()) +afterEach(() => vi.useRealTimers()) + +describe('MatchTimerBar', () => { + it('starts at zero and paused', () => { + const wrapper = mount(MatchTimerBar) + + expect(wrapper.find('.timer__value').text()).toBe('00:00') + expect(wrapper.find('.timer__glyph').attributes('data-running')).toBe('false') + expect(wrapper.find('[aria-label="Start timer"]').exists()).toBe(true) + }) + + it('runs, pauses and resets', async () => { + const wrapper = mount(MatchTimerBar) + + await wrapper.find('[aria-label="Start timer"]').trigger('click') + vi.advanceTimersByTime(62_000) + await wrapper.vm.$nextTick() + + expect(wrapper.find('.timer__value').text()).toBe('01:02') + expect(wrapper.find('.timer__glyph').attributes('data-running')).toBe('true') + + await wrapper.find('[aria-label="Pause timer"]').trigger('click') + vi.advanceTimersByTime(10_000) + await wrapper.vm.$nextTick() + expect(wrapper.find('.timer__value').text()).toBe('01:02') + + await wrapper.find('[aria-label="Reset timer"]').trigger('click') + expect(wrapper.find('.timer__value').text()).toBe('00:00') + }) + + it('asks to be hidden', async () => { + const wrapper = mount(MatchTimerBar) + + await wrapper.find('[aria-label="Hide timer"]').trigger('click') + + expect(wrapper.emitted('close')).toHaveLength(1) + }) +}) diff --git a/src/components/__tests__/OverlaySheet.spec.ts b/src/components/__tests__/OverlaySheet.spec.ts new file mode 100644 index 0000000..2ecb5b0 --- /dev/null +++ b/src/components/__tests__/OverlaySheet.spec.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest' +import { mount } from '@vue/test-utils' +import OverlaySheet from '../OverlaySheet.vue' + +const mountSheet = () => + mount(OverlaySheet, { + props: { title: 'Dice', subtitle: 'Settle it with a roll' }, + slots: { default: '

body

' }, + attachTo: document.body, + }) + +describe('OverlaySheet', () => { + it('renders as a labelled modal with its content', () => { + const wrapper = mountSheet() + + expect(wrapper.find('[role="dialog"]').attributes('aria-label')).toBe('Dice') + expect(wrapper.find('.sheet__title').text()).toBe('Dice') + expect(wrapper.find('.sheet__subtitle').text()).toBe('Settle it with a roll') + expect(wrapper.find('.content').text()).toBe('body') + }) + + it('closes on the backdrop, the close button and Escape', async () => { + const backdrop = mountSheet() + await backdrop.find('.overlay__backdrop').trigger('click') + expect(backdrop.emitted('close')).toHaveLength(1) + + const button = mountSheet() + await button.find('.sheet__close').trigger('click') + expect(button.emitted('close')).toHaveLength(1) + + const escape = mountSheet() + document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })) + expect(escape.emitted('close')).toHaveLength(1) + }) + + it('stops listening for Escape once unmounted', async () => { + const wrapper = mountSheet() + wrapper.unmount() + + document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })) + + expect(wrapper.emitted('close')).toBeUndefined() + }) + + it('leaves out the subtitle when there is none', () => { + const wrapper = mount(OverlaySheet, { props: { title: 'Settings' } }) + + expect(wrapper.find('.sheet__subtitle').exists()).toBe(false) + }) +}) diff --git a/src/components/__tests__/PlayerPanel.spec.ts b/src/components/__tests__/PlayerPanel.spec.ts new file mode 100644 index 0000000..367857b --- /dev/null +++ b/src/components/__tests__/PlayerPanel.spec.ts @@ -0,0 +1,163 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { mount } from '@vue/test-utils' +import { nextTick, reactive } from 'vue' +import PlayerPanel from '../PlayerPanel.vue' +import type { Player } from '../../game/types' + +const makePlayer = (overrides: Partial = {}): Player => + reactive({ + id: 0, + name: 'Ada', + hue: 200, + life: 20, + counters: { poison: 0, energy: 0 }, + ...overrides, + }) + +const mountPanel = (props: Record = {}) => + mount(PlayerPanel, { props: { player: makePlayer(), ...props } }) + +beforeEach(() => vi.useFakeTimers()) +afterEach(() => vi.useRealTimers()) + +describe('PlayerPanel', () => { + it('shows the player name and life total', () => { + const wrapper = mountPanel() + + expect(wrapper.find('.name').text()).toContain('Ada') + expect(wrapper.find('.life').text()).toBe('20') + expect(wrapper.find('section').attributes('aria-label')).toBe('Ada: 20 life') + }) + + it('exposes the player hue and digit count to the theme', () => { + const wrapper = mountPanel({ player: makePlayer({ life: 8000, hue: 120 }) }) + const style = wrapper.find('section').attributes('style') ?? '' + + expect(style).toContain('--player-hue: 120') + expect(style).toContain('--digits: 4') + }) + + it('applies a single step on a tap', async () => { + const wrapper = mountPanel() + + await wrapper.find('.zone--minus').trigger('pointerdown') + await wrapper.find('.zone--minus').trigger('pointerup') + + expect(wrapper.emitted('life')).toEqual([[-1]]) + }) + + it('applies the big step repeatedly while held', async () => { + const wrapper = mountPanel({ step: 1, bigStep: 10 }) + + await wrapper.find('.zone--plus').trigger('pointerdown') + vi.advanceTimersByTime(500) + vi.advanceTimersByTime(360) + await wrapper.find('.zone--plus').trigger('pointerup') + + expect(wrapper.emitted('life')).toEqual([[10], [10], [10]]) + }) + + it('does not let a second finger on the same button add a step', async () => { + const wrapper = mountPanel() + const minus = wrapper.find('.zone--minus') + + await minus.trigger('pointerdown', { pointerId: 1 }) + await minus.trigger('pointerdown', { pointerId: 2 }) + await minus.trigger('pointerup', { pointerId: 2 }) + await minus.trigger('pointerup', { pointerId: 1 }) + + expect(wrapper.emitted('life')).toEqual([[-1]]) + }) + + it('keeps the two life buttons independent for simultaneous touches', async () => { + const wrapper = mountPanel() + + await wrapper.find('.zone--minus').trigger('pointerdown', { pointerId: 1 }) + await wrapper.find('.zone--plus').trigger('pointerdown', { pointerId: 2 }) + await wrapper.find('.zone--plus').trigger('pointerup', { pointerId: 2 }) + await wrapper.find('.zone--minus').trigger('pointerup', { pointerId: 1 }) + + expect(wrapper.emitted('life')).toEqual([[1], [-1]]) + }) + + it('drops a press that is cancelled by the browser', async () => { + const wrapper = mountPanel() + + await wrapper.find('.zone--minus').trigger('pointerdown') + await wrapper.find('.zone--minus').trigger('pointercancel') + vi.advanceTimersByTime(2000) + + expect(wrapper.emitted('life')).toBeUndefined() + }) + + it('supports the keyboard', async () => { + const wrapper = mountPanel({ step: 5 }) + + await wrapper.find('.zone--plus').trigger('keydown', { key: 'Enter' }) + + expect(wrapper.emitted('life')).toEqual([[5]]) + }) + + it('shows the running total of recent changes and fades it out', async () => { + const wrapper = mountPanel() + const minus = wrapper.find('.zone--minus') + + await minus.trigger('pointerdown') + await minus.trigger('pointerup') + await minus.trigger('pointerdown') + await minus.trigger('pointerup') + await nextTick() + + const delta = wrapper.find('.delta') + expect(delta.text()).toBe('-2') + expect(delta.classes()).toContain('delta--visible') + + vi.advanceTimersByTime(2500) + await nextTick() + + expect(wrapper.find('.delta').classes()).not.toContain('delta--visible') + }) + + it('hides the counters unless the preset uses them', () => { + expect(mountPanel().find('.chips').exists()).toBe(false) + expect( + mountPanel({ counters: ['poison'] }) + .find('.chips') + .exists(), + ).toBe(true) + }) + + it('emits counter changes in both directions', async () => { + const wrapper = mountPanel({ counters: ['poison', 'energy'] }) + const chips = wrapper.findAll('.chip') + + expect(chips).toHaveLength(2) + await chips[0].findAll('button')[1].trigger('click') + await chips[1].findAll('button')[0].trigger('click') + + expect(wrapper.emitted('counter')).toEqual([ + ['poison', 1], + ['energy', -1], + ]) + }) + + it('marks the randomly picked starting player', () => { + expect(mountPanel().find('.name__crown').exists()).toBe(false) + expect(mountPanel({ isFirstPlayer: true }).find('.name__crown').exists()).toBe(true) + expect(mountPanel({ isFirstPlayer: true }).find('section').attributes('data-first')).toBe( + 'true', + ) + }) + + it('clears a stale delta when a new game resets the life total', async () => { + const player = makePlayer({ life: 20 }) + const wrapper = mountPanel({ player }) + + await wrapper.find('.zone--minus').trigger('pointerdown') + await wrapper.find('.zone--minus').trigger('pointerup') + player.life = 8000 + await nextTick() + + expect(wrapper.find('.delta').classes()).not.toContain('delta--visible') + }) +}) diff --git a/src/components/__tests__/PlayerView.spec.ts b/src/components/__tests__/PlayerView.spec.ts deleted file mode 100644 index 38a6fd0..0000000 --- a/src/components/__tests__/PlayerView.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { mount } from '@vue/test-utils' -import { nextTick } from 'vue' - -import PlayerView from '../PlayerView.vue' - -test('moount Calc', () => { - expect(PlayerView).toBeTruthy() - - const wrapper = mount(PlayerView) - - expect(wrapper.text()).toContain('020') -}) diff --git a/src/components/__tests__/SettingView.spec.ts b/src/components/__tests__/SettingView.spec.ts deleted file mode 100644 index dbe5bd2..0000000 --- a/src/components/__tests__/SettingView.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { mount } from '@vue/test-utils' -import { nextTick } from 'vue' - -import SettingView from '../SettingView.vue' - -test('mount SettingView', () => { - expect(SettingView).toBeTruthy() - - const wrapper = mount(SettingView) - - expect(wrapper.text()).toContain('Fullscreen') -}) diff --git a/src/components/__tests__/SettingsSheet.spec.ts b/src/components/__tests__/SettingsSheet.spec.ts new file mode 100644 index 0000000..a86c06d --- /dev/null +++ b/src/components/__tests__/SettingsSheet.spec.ts @@ -0,0 +1,178 @@ +import { describe, expect, it } from 'vitest' +import { mount } from '@vue/test-utils' +import { nextTick } from 'vue' +import SettingsSheet from '../SettingsSheet.vue' +import LayoutPreview from '../LayoutPreview.vue' +import { createGameStore } from '../../game/store' +import { GAME_STORE_KEY } from '../../game/inject' + +const mountSheet = (store = createGameStore()) => ({ + store, + wrapper: mount(SettingsSheet, { + global: { provide: { [GAME_STORE_KEY as symbol]: store } }, + }), +}) + +describe('SettingsSheet', () => { + it('offers every supported player count and marks the current one', () => { + const { wrapper } = mountSheet() + const buttons = wrapper.findAll('.segmented__item') + + expect(buttons.map((button) => button.text())).toEqual(['2', '3', '4', '5', '6']) + expect(buttons[0].attributes('aria-pressed')).toBe('true') + }) + + it('asks for the layout right after the player count is chosen', async () => { + const { store, wrapper } = mountSheet() + + await wrapper.findAll('.segmented__item')[2].trigger('click') + + expect(store.state.playerCount).toBe(4) + expect(wrapper.find('[role="dialog"]').attributes('aria-label')).toBe('Table layout') + // The table stays readable behind the layout step so it previews live. + expect(wrapper.find('.overlay').attributes('data-preview')).toBe('true') + }) + + it('applies a layout from the layout step and shows which one is active', async () => { + const { store, wrapper } = mountSheet() + await wrapper.findAll('.segmented__item')[4].trigger('click') + + const options = wrapper.findAll('.option') + expect(options.map((option) => option.find('.option__name').text())).toEqual([ + 'Facing', + 'Sides', + 'Same side', + ]) + expect(options[0].attributes('aria-pressed')).toBe('true') + + await options[1].trigger('click') + + expect(store.state.layoutId).toBe('sides') + expect(wrapper.findAll('.option')[1].attributes('aria-pressed')).toBe('true') + }) + + it('draws a seat diagram per layout option', async () => { + const { wrapper } = mountSheet() + await wrapper.findAll('.segmented__item')[0].trigger('click') + + const diagrams = wrapper.findAllComponents(LayoutPreview) + expect(diagrams).toHaveLength(3) + expect( + diagrams[0].findAll('.preview__seat').map((seat) => seat.attributes('data-edge')), + ).toEqual(['top', 'bottom']) + expect( + diagrams[1].findAll('.preview__seat').map((seat) => seat.attributes('data-edge')), + ).toEqual(['left', 'right']) + }) + + it('goes back to the settings page from the layout step', async () => { + const { wrapper } = mountSheet() + await wrapper.findAll('.segmented__item')[1].trigger('click') + + await wrapper.find('.sheet__close').trigger('click') + + expect(wrapper.find('[role="dialog"]').attributes('aria-label')).toBe('Settings') + expect(wrapper.emitted('close')).toBeUndefined() + }) + + it('closes everything from Done on the layout step', async () => { + const { wrapper } = mountSheet() + await wrapper.findAll('.segmented__item')[1].trigger('click') + + await wrapper.find('.action').trigger('click') + + expect(wrapper.emitted('close')).toHaveLength(1) + }) + + it('opens the layout step from the settings row without changing the count', async () => { + const { store, wrapper } = mountSheet() + + await wrapper.find('.layout-row').trigger('click') + + expect(store.state.playerCount).toBe(2) + expect(wrapper.find('[role="dialog"]').attributes('aria-label')).toBe('Table layout') + }) + + it('applies a preset and restarts the game', async () => { + const { store, wrapper } = mountSheet() + store.adjustLife(0, -8) + + const commander = wrapper.findAll('.option')[1] + await commander.trigger('click') + + expect(store.state.presetId).toBe('commander') + expect(store.state.startingLife).toBe(40) + expect(store.playerAt(0)?.life).toBe(40) + }) + + it('steps the starting life up and down', async () => { + const { store, wrapper } = mountSheet() + const steps = wrapper.findAll('.stepper__button') + + await steps[0].trigger('click') // -10 + await steps[3].trigger('click') // +10 + await steps[2].trigger('click') // +1 + + expect(store.state.startingLife).toBe(21) + expect(wrapper.find('.stepper__value').text()).toBe('21') + }) + + it('starts a new game and closes', async () => { + const { store, wrapper } = mountSheet() + store.adjustLife(1, -6) + + await wrapper.find('.action').trigger('click') + + expect(store.playerAt(1)?.life).toBe(20) + expect(wrapper.emitted('close')).toHaveLength(1) + }) + + it('switches the theme', async () => { + const { store, wrapper } = mountSheet() + + await wrapper.findAll('.theme')[1].trigger('click') + + expect(store.state.themeId).toBe('arena') + expect(wrapper.findAll('.theme')[1].attributes('aria-pressed')).toBe('true') + }) + + it('lists only the seated players for renaming and recolouring', async () => { + const store = createGameStore({ playerCount: 3 }) + const { wrapper } = mountSheet(store) + + expect(wrapper.findAll('.player')).toHaveLength(3) + + const nameInput = wrapper.findAll('.player__name')[1] + await nameInput.setValue('Grace') + await nameInput.trigger('change') + expect(store.playerAt(1)?.name).toBe('Grace') + + const hueInput = wrapper.findAll('.player__hue')[0] + await hueInput.setValue('300') + expect(store.playerAt(0)?.hue).toBe(300) + }) + + it('restores the default colours', async () => { + const { store, wrapper } = mountSheet() + store.setHue(0, 5) + + await wrapper.find('.link').trigger('click') + + expect(store.playerAt(0)?.hue).toBe(32) + }) + + it('hides display toggles the browser cannot do', () => { + const { wrapper } = mountSheet() + + expect(wrapper.findAll('.toggle')).toHaveLength(0) + }) + + it('closes when asked', async () => { + const { wrapper } = mountSheet() + + await wrapper.find('.sheet__close').trigger('click') + + await nextTick() + expect(wrapper.emitted('close')).toHaveLength(1) + }) +}) diff --git a/src/components/__tests__/StarterOverlay.spec.ts b/src/components/__tests__/StarterOverlay.spec.ts new file mode 100644 index 0000000..5cf3500 --- /dev/null +++ b/src/components/__tests__/StarterOverlay.spec.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest' +import { mount } from '@vue/test-utils' +import StarterOverlay from '../StarterOverlay.vue' +import type { Player } from '../../game/types' + +const player: Player = { + id: 2, + name: 'Grace', + hue: 140, + life: 40, + counters: { poison: 0, energy: 0 }, +} + +describe('StarterOverlay', () => { + it('announces the chosen player in their colour', () => { + const wrapper = mount(StarterOverlay, { props: { player } }) + + expect(wrapper.find('.starter__name').text()).toBe('Grace') + expect(wrapper.find('[role="dialog"]').attributes('aria-label')).toBe('Grace starts') + expect(wrapper.find('.starter__card').attributes('style')).toContain('--player-hue: 140') + }) + + it('dismisses on tap', async () => { + const wrapper = mount(StarterOverlay, { props: { player } }) + + await wrapper.find('.starter').trigger('click') + + expect(wrapper.emitted('close')).toHaveLength(1) + }) +}) diff --git a/src/components/icons/ReCheckboxBlankLine.vue b/src/components/icons/ReCheckboxBlankLine.vue deleted file mode 100644 index 4c4eaa6..0000000 --- a/src/components/icons/ReCheckboxBlankLine.vue +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/src/components/icons/ReCheckboxLine.vue b/src/components/icons/ReCheckboxLine.vue deleted file mode 100644 index 35bc168..0000000 --- a/src/components/icons/ReCheckboxLine.vue +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/src/components/icons/RePaletteLine.vue b/src/components/icons/RePaletteLine.vue deleted file mode 100644 index 4efb983..0000000 --- a/src/components/icons/RePaletteLine.vue +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/src/components/icons/ReSettings5Line.vue b/src/components/icons/ReSettings5Line.vue deleted file mode 100644 index 2ff8bee..0000000 --- a/src/components/icons/ReSettings5Line.vue +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/src/components/icons/ThMoreAlt.vue b/src/components/icons/ThMoreAlt.vue deleted file mode 100644 index 134182d..0000000 --- a/src/components/icons/ThMoreAlt.vue +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/src/components/icons/TiRefresh.vue b/src/components/icons/TiRefresh.vue deleted file mode 100644 index c31990a..0000000 --- a/src/components/icons/TiRefresh.vue +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/src/composables/__tests__/useDice.spec.ts b/src/composables/__tests__/useDice.spec.ts new file mode 100644 index 0000000..bd8138f --- /dev/null +++ b/src/composables/__tests__/useDice.spec.ts @@ -0,0 +1,83 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { effectScope } from 'vue' +import { DICE_SIDES, rollDie, useDice } from '../useDice' + +describe('rollDie', () => { + it('covers the whole range of the die', () => { + expect(rollDie(6, () => 0)).toBe(1) + expect(rollDie(6, () => 0.999)).toBe(6) + expect(rollDie(20, () => 0.5)).toBe(11) + }) + + it('never exceeds the die even for a random() of 1', () => { + for (const sides of DICE_SIDES) { + expect(rollDie(sides, () => 1)).toBe(sides) + } + }) + + it('stays sane for nonsense input', () => { + expect(rollDie(1, () => 0.99)).toBe(2) + expect(rollDie(6.7, () => 0)).toBe(1) + }) + + it('produces every face over many rolls', () => { + const seen = new Set() + for (let i = 0; i < 400; i += 1) seen.add(rollDie(6)) + expect([...seen].sort()).toEqual([1, 2, 3, 4, 5, 6]) + }) +}) + +describe('useDice', () => { + let scope: ReturnType + + beforeEach(() => vi.useFakeTimers()) + afterEach(() => { + scope?.stop() + vi.useRealTimers() + }) + + const setup = (random: () => number) => { + scope = effectScope() + return scope.run(() => useDice(random))! + } + + it('records the roll and keeps a short history', () => { + const dice = setup(() => 0.5) + + const result = dice.roll(20) + + expect(result).toEqual({ sides: 20, value: 11 }) + expect(dice.last.value).toEqual(result) + expect(dice.history.value[0]).toEqual(result) + }) + + it('caps the history at eight entries, newest first', () => { + const dice = setup(() => 0) + + for (let i = 0; i < 12; i += 1) dice.roll(6) + dice.roll(20) + + expect(dice.history.value).toHaveLength(8) + expect(dice.history.value[0].sides).toBe(20) + }) + + it('flags a roll as animating and settles again', () => { + const dice = setup(() => 0.5) + + dice.roll(6) + expect(dice.rolling.value).toBe(true) + + vi.advanceTimersByTime(320) + expect(dice.rolling.value).toBe(false) + }) + + it('clears the results', () => { + const dice = setup(() => 0.5) + dice.roll(6) + + dice.clear() + + expect(dice.last.value).toBeNull() + expect(dice.history.value).toEqual([]) + }) +}) diff --git a/src/composables/__tests__/useHoldRepeat.spec.ts b/src/composables/__tests__/useHoldRepeat.spec.ts new file mode 100644 index 0000000..4a62fc2 --- /dev/null +++ b/src/composables/__tests__/useHoldRepeat.spec.ts @@ -0,0 +1,118 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { effectScope } from 'vue' +import { useHoldRepeat } from '../useHoldRepeat' + +const setup = () => { + const onTap = vi.fn() + const onHold = vi.fn() + const scope = effectScope() + const hold = scope.run(() => + useHoldRepeat({ onTap, onHold, holdDelay: 500, repeatInterval: 100 }), + )! + return { onTap, onHold, hold, scope } +} + +beforeEach(() => vi.useFakeTimers()) +afterEach(() => vi.useRealTimers()) + +describe('useHoldRepeat', () => { + it('applies a single tap on a short press', () => { + const { hold, onTap, onHold } = setup() + + hold.start() + vi.advanceTimersByTime(200) + hold.stop() + + expect(onTap).toHaveBeenCalledTimes(1) + expect(onHold).not.toHaveBeenCalled() + }) + + it('repeats while held and skips the tap on release', () => { + const { hold, onTap, onHold } = setup() + + hold.start() + vi.advanceTimersByTime(500) + expect(onHold).toHaveBeenCalledTimes(1) + expect(hold.isHolding.value).toBe(true) + + vi.advanceTimersByTime(300) + expect(onHold).toHaveBeenCalledTimes(4) + + hold.stop() + vi.advanceTimersByTime(1000) + + expect(onHold).toHaveBeenCalledTimes(4) + expect(onTap).not.toHaveBeenCalled() + expect(hold.isHolding.value).toBe(false) + }) + + it('cancelling a press applies nothing', () => { + const { hold, onTap, onHold } = setup() + + hold.start() + vi.advanceTimersByTime(550) + hold.cancel() + vi.advanceTimersByTime(1000) + + expect(onHold).toHaveBeenCalledTimes(1) + expect(onTap).not.toHaveBeenCalled() + }) + + it('ignores a second start while a press is active', () => { + const { hold, onTap } = setup() + + hold.start() + hold.start() + hold.stop() + + expect(onTap).toHaveBeenCalledTimes(1) + }) + + it('lets only the finger that started the press end it', () => { + const { hold, onTap, onHold } = setup() + + hold.start(1) + vi.advanceTimersByTime(500) + expect(onHold).toHaveBeenCalledTimes(1) + + // A second finger lands on the same button and lifts again. + hold.start(2) + hold.stop(2) + vi.advanceTimersByTime(200) + expect(onHold).toHaveBeenCalledTimes(3) + expect(onTap).not.toHaveBeenCalled() + + hold.stop(1) + vi.advanceTimersByTime(500) + expect(onHold).toHaveBeenCalledTimes(3) + }) + + it('ignores a cancel that belongs to another finger', () => { + const { hold, onHold } = setup() + + hold.start(1) + hold.cancel(2) + vi.advanceTimersByTime(500) + + expect(onHold).toHaveBeenCalledTimes(1) + }) + + it('a release without a press does nothing', () => { + const { hold, onTap } = setup() + + hold.stop() + + expect(onTap).not.toHaveBeenCalled() + }) + + it('stops repeating when the scope is disposed', () => { + const { hold, onHold, scope } = setup() + + hold.start() + vi.advanceTimersByTime(500) + scope.stop() + vi.advanceTimersByTime(1000) + + expect(onHold).toHaveBeenCalledTimes(1) + }) +}) diff --git a/src/composables/__tests__/useMatchTimer.spec.ts b/src/composables/__tests__/useMatchTimer.spec.ts new file mode 100644 index 0000000..61f8aa5 --- /dev/null +++ b/src/composables/__tests__/useMatchTimer.spec.ts @@ -0,0 +1,85 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { effectScope } from 'vue' +import { formatDuration, useMatchTimer } from '../useMatchTimer' + +describe('formatDuration', () => { + it('formats minutes and seconds, adding hours only when needed', () => { + expect(formatDuration(0)).toBe('00:00') + expect(formatDuration(9_000)).toBe('00:09') + expect(formatDuration(61_000)).toBe('01:01') + expect(formatDuration(3_600_000)).toBe('1:00:00') + expect(formatDuration(-500)).toBe('00:00') + }) +}) + +describe('useMatchTimer', () => { + let clock = 1000 + let scope: ReturnType + let timer: ReturnType + + beforeEach(() => { + vi.useFakeTimers() + clock = 1000 + scope = effectScope() + timer = scope.run(() => useMatchTimer(() => clock))! + }) + + afterEach(() => { + scope.stop() + vi.useRealTimers() + }) + + it('counts wall-clock time while running', () => { + timer.start() + clock += 5000 + vi.advanceTimersByTime(1000) + + expect(timer.running.value).toBe(true) + expect(timer.elapsed.value).toBe(5000) + expect(timer.label.value).toBe('00:05') + }) + + it('freezes on pause and continues from there', () => { + timer.start() + clock += 5000 + vi.advanceTimersByTime(1000) + timer.pause() + + clock += 10_000 + vi.advanceTimersByTime(1000) + expect(timer.elapsed.value).toBe(5000) + + timer.start() + clock += 2000 + vi.advanceTimersByTime(500) + expect(timer.elapsed.value).toBe(7000) + }) + + it('toggles and resets', () => { + timer.toggle() + expect(timer.running.value).toBe(true) + + timer.toggle() + expect(timer.running.value).toBe(false) + + clock += 3000 + timer.start() + clock += 4000 + vi.advanceTimersByTime(500) + timer.reset() + + expect(timer.elapsed.value).toBe(0) + expect(timer.running.value).toBe(false) + expect(timer.label.value).toBe('00:00') + }) + + it('stops ticking once the scope is gone', () => { + timer.start() + scope.stop() + + clock += 9000 + vi.advanceTimersByTime(2000) + + expect(timer.elapsed.value).toBe(0) + }) +}) diff --git a/src/composables/__tests__/usePendingDelta.spec.ts b/src/composables/__tests__/usePendingDelta.spec.ts new file mode 100644 index 0000000..da6f4f3 --- /dev/null +++ b/src/composables/__tests__/usePendingDelta.spec.ts @@ -0,0 +1,73 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { effectScope } from 'vue' +import { usePendingDelta } from '../usePendingDelta' + +const setup = (timeout = 2000) => { + const scope = effectScope() + return { pending: scope.run(() => usePendingDelta(timeout))!, scope } +} + +beforeEach(() => vi.useFakeTimers()) +afterEach(() => vi.useRealTimers()) + +describe('usePendingDelta', () => { + it('accumulates changes and formats them with a sign', () => { + const { pending } = setup() + + pending.add(-1) + pending.add(-6) + + expect(pending.visible.value).toBe(true) + expect(pending.value.value).toBe(-7) + expect(pending.label.value).toBe('-7') + + pending.add(10) + expect(pending.label.value).toBe('+3') + }) + + it('hides and restarts after the timeout', () => { + const { pending } = setup() + + pending.add(-4) + vi.advanceTimersByTime(1999) + expect(pending.visible.value).toBe(true) + + vi.advanceTimersByTime(1) + expect(pending.visible.value).toBe(false) + expect(pending.value.value).toBe(0) + + pending.add(-2) + expect(pending.value.value).toBe(-2) + }) + + it('extends the timeout while taps keep coming', () => { + const { pending } = setup() + + pending.add(1) + vi.advanceTimersByTime(1800) + pending.add(1) + vi.advanceTimersByTime(1800) + + expect(pending.visible.value).toBe(true) + expect(pending.value.value).toBe(2) + }) + + it('ignores zero and invalid deltas', () => { + const { pending } = setup() + + pending.add(0) + pending.add(Number.NaN) + + expect(pending.visible.value).toBe(false) + }) + + it('reset clears the readout immediately', () => { + const { pending } = setup() + + pending.add(5) + pending.reset() + + expect(pending.visible.value).toBe(false) + expect(pending.value.value).toBe(0) + }) +}) diff --git a/src/composables/__tests__/useScreen.spec.ts b/src/composables/__tests__/useScreen.spec.ts new file mode 100644 index 0000000..7dc5d61 --- /dev/null +++ b/src/composables/__tests__/useScreen.spec.ts @@ -0,0 +1,149 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { mount } from '@vue/test-utils' +import { defineComponent } from 'vue' +import { useFullscreen, useWakeLock } from '../useScreen' + +const harness = (composable: () => T) => + mount( + defineComponent({ + setup: () => ({ api: composable() }), + template: '
', + }), + ) + +const stubFullscreenApi = () => { + const requestFullscreen = vi.fn(async () => { + Object.defineProperty(document, 'fullscreenElement', { + value: document.documentElement, + configurable: true, + }) + }) + const exitFullscreen = vi.fn(async () => { + Object.defineProperty(document, 'fullscreenElement', { value: null, configurable: true }) + }) + + Object.defineProperty(document.documentElement, 'requestFullscreen', { + value: requestFullscreen, + configurable: true, + }) + Object.defineProperty(document, 'exitFullscreen', { value: exitFullscreen, configurable: true }) + Object.defineProperty(document, 'fullscreenElement', { value: null, configurable: true }) + + return { requestFullscreen, exitFullscreen } +} + +afterEach(() => { + Reflect.deleteProperty(document.documentElement, 'requestFullscreen') + Reflect.deleteProperty(document, 'exitFullscreen') + Reflect.deleteProperty(navigator, 'wakeLock') +}) + +describe('useFullscreen', () => { + it('reports no support when the browser lacks the API', () => { + const wrapper = harness(() => useFullscreen()) + + expect(wrapper.vm.api.supported.value).toBe(false) + }) + + it('enters and leaves fullscreen', async () => { + const { requestFullscreen, exitFullscreen } = stubFullscreenApi() + const wrapper = harness(() => useFullscreen()) + + expect(wrapper.vm.api.supported.value).toBe(true) + + await wrapper.vm.api.toggle() + expect(requestFullscreen).toHaveBeenCalled() + expect(wrapper.vm.api.isFullscreen.value).toBe(true) + + await wrapper.vm.api.toggle() + expect(exitFullscreen).toHaveBeenCalled() + expect(wrapper.vm.api.isFullscreen.value).toBe(false) + }) + + it('follows fullscreen changes made outside the app', async () => { + stubFullscreenApi() + const wrapper = harness(() => useFullscreen()) + + Object.defineProperty(document, 'fullscreenElement', { + value: document.documentElement, + configurable: true, + }) + document.dispatchEvent(new Event('fullscreenchange')) + + expect(wrapper.vm.api.isFullscreen.value).toBe(true) + }) + + it('surfaces a rejected request instead of throwing', async () => { + stubFullscreenApi() + Object.defineProperty(document.documentElement, 'requestFullscreen', { + value: vi.fn(async () => { + throw new Error('denied') + }), + configurable: true, + }) + + const wrapper = harness(() => useFullscreen()) + await wrapper.vm.api.toggle() + + expect(wrapper.vm.api.error.value).toBe('denied') + expect(wrapper.vm.api.isFullscreen.value).toBe(false) + }) +}) + +describe('useWakeLock', () => { + const stubWakeLock = (request: () => Promise) => { + Object.defineProperty(navigator, 'wakeLock', { value: { request }, configurable: true }) + } + + const fakeSentinel = () => { + const listeners: (() => void)[] = [] + return { + release: vi.fn(async () => listeners.forEach((listener) => listener())), + addEventListener: (_type: string, listener: () => void) => listeners.push(listener), + } + } + + it('reports no support when the browser lacks the API', () => { + const wrapper = harness(() => useWakeLock()) + + expect(wrapper.vm.api.supported.value).toBe(false) + }) + + it('acquires and releases the lock', async () => { + const sentinel = fakeSentinel() + stubWakeLock(async () => sentinel) + const wrapper = harness(() => useWakeLock()) + + await wrapper.vm.api.toggle() + expect(wrapper.vm.api.active.value).toBe(true) + + await wrapper.vm.api.toggle() + expect(sentinel.release).toHaveBeenCalled() + expect(wrapper.vm.api.active.value).toBe(false) + }) + + it('goes inactive when the browser releases the lock itself', async () => { + const sentinel = fakeSentinel() + stubWakeLock(async () => sentinel) + const wrapper = harness(() => useWakeLock()) + + await wrapper.vm.api.toggle() + await sentinel.release() + + expect(wrapper.vm.api.active.value).toBe(false) + }) + + it('keeps the error message when the request is denied', async () => { + stubWakeLock(async () => { + const error = new Error('not allowed') + error.name = 'NotAllowedError' + throw error + }) + const wrapper = harness(() => useWakeLock()) + + await wrapper.vm.api.toggle() + + expect(wrapper.vm.api.active.value).toBe(false) + expect(wrapper.vm.api.error.value).toContain('NotAllowedError') + }) +}) diff --git a/src/composables/useDice.ts b/src/composables/useDice.ts new file mode 100644 index 0000000..d309441 --- /dev/null +++ b/src/composables/useDice.ts @@ -0,0 +1,42 @@ +import { ref } from 'vue' + +export const DICE_SIDES = [2, 4, 6, 8, 10, 12, 20, 100] as const +export type DiceSides = (typeof DICE_SIDES)[number] + +export interface DiceRoll { + sides: number + value: number +} + +export function rollDie(sides: number, random: () => number = Math.random): number { + const faces = Math.max(2, Math.floor(sides)) + return Math.min(faces, Math.floor(random() * faces) + 1) +} + +export function useDice(random: () => number = Math.random) { + const last = ref(null) + const history = ref([]) + const rolling = ref(false) + let settleId: ReturnType | null = null + + const roll = (sides: number): DiceRoll => { + const result: DiceRoll = { sides, value: rollDie(sides, random) } + last.value = result + history.value = [result, ...history.value].slice(0, 8) + + rolling.value = true + if (settleId !== null) clearTimeout(settleId) + settleId = setTimeout(() => { + rolling.value = false + }, 320) + + return result + } + + const clear = () => { + last.value = null + history.value = [] + } + + return { last, history, rolling, roll, clear } +} diff --git a/src/composables/useHoldRepeat.ts b/src/composables/useHoldRepeat.ts new file mode 100644 index 0000000..752369c --- /dev/null +++ b/src/composables/useHoldRepeat.ts @@ -0,0 +1,85 @@ +import { onScopeDispose, ref } from 'vue' + +export interface HoldRepeatOptions { + /** Fired on a short press. */ + onTap: () => void + /** Fired once the press turns into a hold, then repeatedly. */ + onHold: () => void + /** Delay before a press becomes a hold. */ + holdDelay?: number + /** Interval between repeats while held. */ + repeatInterval?: number +} + +/** + * Press-and-hold behaviour for the life buttons: a tap applies the small + * step, holding applies the big step over and over until release. + * + * A press belongs to the finger that started it: on a shared tablet a second + * finger landing on the same button must not end somebody else's hold, so + * `stop`/`cancel` only listen to the pointer that called `start`. + */ +export function useHoldRepeat(options: HoldRepeatOptions) { + const holdDelay = options.holdDelay ?? 500 + const repeatInterval = options.repeatInterval ?? 180 + + const isHolding = ref(false) + let timerId: ReturnType | null = null + let didHold = false + let active = false + let ownerId: number | null = null + + const clearTimer = () => { + if (timerId !== null) { + clearTimeout(timerId) + timerId = null + } + } + + const repeat = () => { + options.onHold() + timerId = setTimeout(repeat, repeatInterval) + } + + /** True when the event comes from the finger that owns the current press. */ + const owns = (pointerId?: number) => + ownerId === null || pointerId === undefined || pointerId === ownerId + + const start = (pointerId?: number) => { + if (active) return + active = true + ownerId = pointerId ?? null + didHold = false + clearTimer() + timerId = setTimeout(() => { + didHold = true + isHolding.value = true + repeat() + }, holdDelay) + } + + /** Release: applies a tap when the press never became a hold. */ + const stop = (pointerId?: number) => { + if (!active || !owns(pointerId)) return + active = false + ownerId = null + clearTimer() + if (!didHold) options.onTap() + didHold = false + isHolding.value = false + } + + /** Aborts the press without applying anything (pointer left / cancelled). */ + const cancel = (pointerId?: number) => { + if (!owns(pointerId)) return + active = false + ownerId = null + didHold = false + isHolding.value = false + clearTimer() + } + + onScopeDispose(cancel) + + return { start, stop, cancel, isHolding } +} diff --git a/src/composables/useMatchTimer.ts b/src/composables/useMatchTimer.ts new file mode 100644 index 0000000..8e71271 --- /dev/null +++ b/src/composables/useMatchTimer.ts @@ -0,0 +1,60 @@ +import { computed, onScopeDispose, ref } from 'vue' + +export function formatDuration(ms: number): string { + const totalSeconds = Math.max(0, Math.floor(ms / 1000)) + const hours = Math.floor(totalSeconds / 3600) + const minutes = Math.floor((totalSeconds % 3600) / 60) + const seconds = totalSeconds % 60 + const pad = (value: number) => value.toString().padStart(2, '0') + return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}` +} + +/** Wall-clock based match timer, so it stays correct if the tab is throttled. */ +export function useMatchTimer(now: () => number = () => Date.now()) { + const elapsed = ref(0) + const running = ref(false) + let startedAt = 0 + let intervalId: ReturnType | null = null + + const stopTicking = () => { + if (intervalId !== null) { + clearInterval(intervalId) + intervalId = null + } + } + + const tick = () => { + if (running.value) elapsed.value = now() - startedAt + } + + const start = () => { + if (running.value) return + startedAt = now() - elapsed.value + running.value = true + stopTicking() + intervalId = setInterval(tick, 250) + tick() + } + + const pause = () => { + if (!running.value) return + tick() + running.value = false + stopTicking() + } + + const toggle = () => (running.value ? pause() : start()) + + const reset = () => { + stopTicking() + running.value = false + elapsed.value = 0 + startedAt = now() + } + + const label = computed(() => formatDuration(elapsed.value)) + + onScopeDispose(stopTicking) + + return { elapsed, running, label, start, pause, toggle, reset } +} diff --git a/src/composables/usePendingDelta.ts b/src/composables/usePendingDelta.ts new file mode 100644 index 0000000..4771a23 --- /dev/null +++ b/src/composables/usePendingDelta.ts @@ -0,0 +1,46 @@ +import { computed, onScopeDispose, ref } from 'vue' + +const formatter = new Intl.NumberFormat('en-US', { signDisplay: 'exceptZero' }) + +/** + * Accumulates the life changes of the last few seconds so a player can see + * "-7" while they tap, then it fades out. + */ +export function usePendingDelta(timeout = 2500) { + const value = ref(0) + const visible = ref(false) + let timerId: ReturnType | null = null + + const clear = () => { + if (timerId !== null) { + clearTimeout(timerId) + timerId = null + } + } + + const add = (delta: number) => { + if (!Number.isFinite(delta) || delta === 0) return + if (!visible.value) { + value.value = 0 + visible.value = true + } + value.value += delta + clear() + timerId = setTimeout(() => { + visible.value = false + value.value = 0 + }, timeout) + } + + const reset = () => { + clear() + visible.value = false + value.value = 0 + } + + const label = computed(() => formatter.format(value.value)) + + onScopeDispose(clear) + + return { value, visible, label, add, reset } +} diff --git a/src/composables/useScreen.ts b/src/composables/useScreen.ts new file mode 100644 index 0000000..5823d06 --- /dev/null +++ b/src/composables/useScreen.ts @@ -0,0 +1,101 @@ +import { onMounted, onScopeDispose, ref } from 'vue' + +/** Fullscreen toggle that stays in sync when the user leaves via Esc. */ +export function useFullscreen() { + const supported = ref( + typeof document !== 'undefined' && + typeof document.documentElement?.requestFullscreen === 'function', + ) + const isFullscreen = ref(false) + const error = ref(null) + + const sync = () => { + if (typeof document === 'undefined') return + isFullscreen.value = document.fullscreenElement !== null + } + + const toggle = async () => { + if (!supported.value) return + error.value = null + try { + if (document.fullscreenElement) await document.exitFullscreen() + else await document.documentElement.requestFullscreen() + } catch (e) { + error.value = e instanceof Error ? e.message : String(e) + } + sync() + } + + onMounted(() => { + sync() + document.addEventListener('fullscreenchange', sync) + }) + onScopeDispose(() => { + if (typeof document !== 'undefined') { + document.removeEventListener('fullscreenchange', sync) + } + }) + + return { supported, isFullscreen, error, toggle } +} + +type WakeLockNavigator = Navigator & { + wakeLock?: { request(type: 'screen'): Promise } +} + +/** Keeps the screen awake during a game, re-acquiring after tab switches. */ +export function useWakeLock() { + const nav = typeof navigator === 'undefined' ? undefined : (navigator as WakeLockNavigator) + const supported = ref(typeof nav?.wakeLock?.request === 'function') + const active = ref(false) + const error = ref(null) + let sentinel: WakeLockSentinel | null = null + + const request = async () => { + if (!supported.value || !nav?.wakeLock) return + try { + sentinel = await nav.wakeLock.request('screen') + active.value = true + sentinel.addEventListener('release', () => { + active.value = false + sentinel = null + }) + } catch (e) { + error.value = e instanceof Error ? `${e.name}: ${e.message}` : String(e) + active.value = false + } + } + + const release = async () => { + try { + await sentinel?.release() + } catch { + // Sentinel may already be gone; the state below is what matters. + } + sentinel = null + active.value = false + } + + const toggle = async () => { + error.value = null + if (active.value) await release() + else await request() + } + + // Browsers drop the lock when the tab is hidden; take it back on return. + const onVisibility = () => { + if (document.visibilityState === 'visible' && active.value && sentinel === null) { + void request() + } + } + + onMounted(() => document.addEventListener('visibilitychange', onVisibility)) + onScopeDispose(() => { + if (typeof document !== 'undefined') { + document.removeEventListener('visibilitychange', onVisibility) + } + void release() + }) + + return { supported, active, error, toggle } +} diff --git a/src/game/__tests__/layouts.spec.ts b/src/game/__tests__/layouts.spec.ts new file mode 100644 index 0000000..51db869 --- /dev/null +++ b/src/game/__tests__/layouts.spec.ts @@ -0,0 +1,141 @@ +import { describe, expect, it } from 'vitest' +import { TABLE_LAYOUTS, clampPlayerCount, findLayout, isLayoutId, seatLayout } from '../layouts' +import { MAX_PLAYERS, MIN_PLAYERS } from '../types' + +describe('clampPlayerCount', () => { + it('keeps counts inside the supported range', () => { + expect(clampPlayerCount(1)).toBe(MIN_PLAYERS) + expect(clampPlayerCount(7)).toBe(MAX_PLAYERS) + expect(clampPlayerCount(3.4)).toBe(3) + expect(clampPlayerCount(Number.NaN)).toBe(MIN_PLAYERS) + }) +}) + +describe('seatLayout', () => { + it('provides one seat per player for every supported count', () => { + for (let count = MIN_PLAYERS; count <= MAX_PLAYERS; count += 1) { + expect(seatLayout(count).seats).toHaveLength(count) + } + }) + + it('faces two players at each other across the device', () => { + const layout = seatLayout(2) + + expect(layout).toMatchObject({ columns: 1, rows: 2 }) + expect(layout.seats.map((seat) => seat.rotation)).toEqual([180, 0]) + expect(layout.seats[0].gridArea).toBe('1 / 1 / 2 / 2') + }) + + it('gives the third player the full bottom row', () => { + const layout = seatLayout(3) + + expect(layout.seats[2].gridArea).toBe('2 / 1 / 3 / 3') + expect(layout.seats[2].rotation).toBe(0) + }) + + it('seats everyone at the top or bottom edge, never sideways', () => { + for (let count = MIN_PLAYERS; count <= MAX_PLAYERS; count += 1) { + const rotations = seatLayout(count).seats.map((seat) => seat.rotation) + expect(rotations.every((rotation) => rotation === 0 || rotation === 180)).toBe(true) + } + }) + + it('splits five and six players into a facing top and bottom row', () => { + expect(seatLayout(6)).toMatchObject({ columns: 3, rows: 2 }) + expect(seatLayout(6).seats.map((seat) => seat.rotation)).toEqual([180, 180, 180, 0, 0, 0]) + + // Three across the top, two wider seats across the bottom. + expect(seatLayout(5).seats.map((seat) => seat.rotation)).toEqual([180, 180, 180, 0, 0]) + expect(seatLayout(5).seats[3].gridArea).toBe('2 / 1 / 3 / 4') + expect(seatLayout(5).seats[4].gridArea).toBe('2 / 4 / 3 / 7') + }) + + it('puts the top row first so seat order runs around the table', () => { + for (let count = MIN_PLAYERS; count <= MAX_PLAYERS; count += 1) { + const seats = seatLayout(count).seats + expect(seats[0].rotation, `players=${count}`).toBe(180) + expect(seats[seats.length - 1].rotation, `players=${count}`).toBe(0) + } + }) + + it('falls back to the facing layout for an unknown id', () => { + expect(seatLayout(4, 'nonsense')).toEqual(seatLayout(4, 'facing')) + }) + + it('fills the grid without gaps or overlaps, in every layout', () => { + for (const layoutId of TABLE_LAYOUTS.map((entry) => entry.id)) { + for (let count = MIN_PLAYERS; count <= MAX_PLAYERS; count += 1) { + const layout = seatLayout(count, layoutId) + const where = `${layoutId}/${count}` + const cells = new Set() + + expect(layout.seats, where).toHaveLength(count) + + for (const seat of layout.seats) { + const [rowStart, colStart, rowEnd, colEnd] = seat.gridArea + .split('/') + .map((part) => Number.parseInt(part.trim(), 10)) + + for (let row = rowStart; row < rowEnd; row += 1) { + for (let col = colStart; col < colEnd; col += 1) { + const key = `${row}:${col}` + expect(cells.has(key), `${where} cell ${key} used twice`).toBe(false) + cells.add(key) + } + } + } + + expect(cells.size, where).toBe(layout.rows * layout.columns) + } + } + }) +}) + +describe('table layouts', () => { + it('offers facing, sides and same-side seating', () => { + expect(TABLE_LAYOUTS.map((layout) => layout.id)).toEqual(['facing', 'sides', 'stacked']) + expect(isLayoutId('sides')).toBe(true) + expect(isLayoutId('diagonal')).toBe(false) + expect(findLayout('diagonal').id).toBe('facing') + }) + + it('turns the facing table a quarter turn for side seating', () => { + const facing = seatLayout(6, 'facing') + const sides = seatLayout(6, 'sides') + + expect(sides).toMatchObject({ columns: facing.rows, rows: facing.columns }) + expect(sides.seats.map((seat) => seat.rotation)).toEqual([90, 90, 90, 270, 270, 270]) + expect(seatLayout(2, 'sides').seats.map((seat) => seat.rotation)).toEqual([90, 270]) + }) + + /** + * The panel's top points away from its player: 90° for somebody at the left + * edge, 270° at the right. Getting these the wrong way round leaves both + * columns readable only upside down. + */ + it('turns left-column seats to 90° and right-column seats to 270°', () => { + for (let count = MIN_PLAYERS; count <= MAX_PLAYERS; count += 1) { + for (const seat of seatLayout(count, 'sides').seats) { + const column = Number.parseInt(seat.gridArea.split('/')[1]!.trim(), 10) + expect(seat.rotation, `players=${count} column=${column}`).toBe(column === 1 ? 90 : 270) + } + } + }) + + it('keeps the uneven split when turned', () => { + // Three seats down the left edge, two down the right. + expect(seatLayout(5, 'sides').seats.map((seat) => seat.rotation)).toEqual([ + 90, 90, 90, 270, 270, + ]) + expect(seatLayout(5, 'sides')).toMatchObject({ columns: 2, rows: 6 }) + }) + + it('stacks every seat upright for one-sided seating', () => { + for (let count = MIN_PLAYERS; count <= MAX_PLAYERS; count += 1) { + const layout = seatLayout(count, 'stacked') + + expect(layout).toMatchObject({ columns: 1, rows: count }) + expect(layout.seats.every((seat) => seat.rotation === 0)).toBe(true) + } + }) +}) diff --git a/src/game/__tests__/storage.spec.ts b/src/game/__tests__/storage.spec.ts new file mode 100644 index 0000000..8820935 --- /dev/null +++ b/src/game/__tests__/storage.spec.ts @@ -0,0 +1,45 @@ +import { describe, expect, it, vi } from 'vitest' +import { clearLegacyState, readJson, readLegacyState, writeJson } from '../storage' + +describe('storage helpers', () => { + it('round-trips JSON', () => { + writeJson('glc.test', { a: 1 }) + expect(readJson<{ a: number }>('glc.test')).toEqual({ a: 1 }) + }) + + it('returns null for missing or broken entries', () => { + expect(readJson('glc.missing')).toBeNull() + localStorage.setItem('glc.broken', '{') + expect(readJson('glc.broken')).toBeNull() + }) + + it('does not throw when storage rejects writes', () => { + const setItem = vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => { + throw new Error('QuotaExceededError') + }) + + expect(() => writeJson('glc.test', { a: 1 })).not.toThrow() + setItem.mockRestore() + }) +}) + +describe('legacy state', () => { + it('reads nothing when the old keys are absent', () => { + expect(readLegacyState()).toBeNull() + }) + + it('fills in defaults for partially stored legacy data', () => { + localStorage.setItem('glcP1Hue.Color', '120') + + expect(readLegacyState()).toEqual({ lives: [20, 20], hues: [120, 222] }) + }) + + it('clears the old keys', () => { + localStorage.setItem('glcfirst.life', '5') + localStorage.setItem('glcP2Hue.Color', '10') + + clearLegacyState() + + expect(readLegacyState()).toBeNull() + }) +}) diff --git a/src/game/__tests__/store.spec.ts b/src/game/__tests__/store.spec.ts new file mode 100644 index 0000000..150b8b0 --- /dev/null +++ b/src/game/__tests__/store.spec.ts @@ -0,0 +1,250 @@ +import { describe, expect, it } from 'vitest' +import { createGameStore, sanitizeState, useGame, DEFAULT_HUES } from '../store' +import { STORAGE_KEY } from '../storage' +import { findPreset } from '../presets' +import type { GameState } from '../types' + +const readPersisted = (): GameState => JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '{}') + +describe('game store defaults', () => { + it('starts a two player standard game', () => { + const game = createGameStore() + + expect(game.state.playerCount).toBe(2) + expect(game.state.presetId).toBe('standard') + expect(game.state.themeId).toBe('pulse') + expect(game.state.startingLife).toBe(20) + expect(game.activePlayers.value).toHaveLength(2) + expect(game.activePlayers.value.map((player) => player.life)).toEqual([20, 20]) + expect(game.activePlayers.value.map((player) => player.hue)).toEqual(DEFAULT_HUES.slice(0, 2)) + }) + + it('keeps six players in memory so seats remember their name and colour', () => { + const game = createGameStore() + game.setName(4, 'Ada') + game.setPlayerCount(6) + + expect(game.activePlayers.value).toHaveLength(6) + expect(game.activePlayers.value[4].name).toBe('Ada') + }) +}) + +describe('life and counters', () => { + it('adds and removes life, including below zero', () => { + const game = createGameStore() + + game.adjustLife(0, -5) + game.adjustLife(0, -20) + + expect(game.playerAt(0)?.life).toBe(-5) + }) + + it('ignores life changes for unknown players and invalid deltas', () => { + const game = createGameStore() + + game.adjustLife(99, 5) + game.adjustLife(0, Number.NaN) + + expect(game.playerAt(0)?.life).toBe(20) + }) + + it('never lets a counter drop below zero', () => { + const game = createGameStore() + + game.adjustCounter(1, 'poison', 3) + game.adjustCounter(1, 'poison', -10) + + expect(game.playerAt(1)?.counters.poison).toBe(0) + }) +}) + +describe('presets and new games', () => { + it('applying a preset sets the life total and restarts', () => { + const game = createGameStore() + game.adjustLife(0, -12) + + game.applyPreset('commander') + + expect(game.state.presetId).toBe('commander') + expect(game.state.startingLife).toBe(40) + expect(game.counters.value).toEqual(['poison']) + expect(game.activePlayers.value.every((player) => player.life === 40)).toBe(true) + }) + + it('falls back to the standard preset for unknown ids', () => { + expect(findPreset('nope').id).toBe('standard') + }) + + it('resetting clears life and counters for every seat', () => { + const game = createGameStore() + game.setStartingLife(30) + game.adjustLife(0, -9) + game.adjustCounter(0, 'poison', 4) + game.adjustCounter(1, 'energy', 2) + + game.resetGame() + + expect(game.state.players.every((player) => player.life === 30)).toBe(true) + expect(game.state.players.every((player) => player.counters.poison === 0)).toBe(true) + expect(game.state.players.every((player) => player.counters.energy === 0)).toBe(true) + }) +}) + +describe('table configuration', () => { + it('clamps the player count to the supported range', () => { + const game = createGameStore() + + game.setPlayerCount(99) + expect(game.state.playerCount).toBe(6) + + game.setPlayerCount(0) + expect(game.state.playerCount).toBe(2) + }) + + it('wraps hues into 0-359 and trims names', () => { + const game = createGameStore() + + game.setHue(0, 400) + game.setHue(1, -30) + game.setName(0, ' Bo ') + game.setName(1, ' ') + + expect(game.playerAt(0)?.hue).toBe(40) + expect(game.playerAt(1)?.hue).toBe(330) + expect(game.playerAt(0)?.name).toBe('Bo') + expect(game.playerAt(1)?.name).toBe('Player 2') + }) + + it('restores the default colours', () => { + const game = createGameStore() + game.setHue(0, 111) + + game.resetColors() + + expect(game.playerAt(0)?.hue).toBe(DEFAULT_HUES[0]) + }) + + it('remembers the table layout and rejects unknown ones', () => { + const game = createGameStore() + + expect(game.state.layoutId).toBe('facing') + + game.setLayout('sides') + expect(game.state.layoutId).toBe('sides') + expect(game.tableLayout.value.name).toBe('Sides') + expect(game.layout.value.seats[0].rotation).toBe(90) + + game.setLayout('diagonal') + expect(game.state.layoutId).toBe('sides') + }) + + it('keeps the layout when the player count changes', () => { + const game = createGameStore() + game.setLayout('stacked') + + game.setPlayerCount(5) + + expect(game.state.layoutId).toBe('stacked') + expect(game.layout.value.seats).toHaveLength(5) + }) + + it('only accepts known themes', () => { + const game = createGameStore() + + game.setTheme('arena') + expect(game.state.themeId).toBe('arena') + + game.setTheme('hot-pink') + expect(game.state.themeId).toBe('arena') + }) + + it('picks a first player inside the active seats', () => { + const game = createGameStore() + game.setPlayerCount(4) + + expect(game.randomFirstPlayer(() => 0)).toBe(0) + expect(game.randomFirstPlayer(() => 0.99)).toBe(3) + // Guards against a random() that returns exactly 1. + expect(game.randomFirstPlayer(() => 1)).toBe(3) + }) +}) + +describe('persistence', () => { + it('writes every change to local storage', () => { + const game = createGameStore() + + game.adjustLife(0, -3) + game.setPlayerCount(5) + + const stored = readPersisted() + expect(stored.playerCount).toBe(5) + expect(stored.players[0].life).toBe(17) + }) + + it('restores a stored game on the next visit', () => { + const first = createGameStore() + first.applyPreset('commander') + first.setPlayerCount(3) + first.adjustLife(2, -7) + + const second = createGameStore(JSON.parse(localStorage.getItem(STORAGE_KEY)!)) + + expect(second.state.playerCount).toBe(3) + expect(second.state.presetId).toBe('commander') + expect(second.playerAt(2)?.life).toBe(33) + }) + + it('migrates the two player data written by the old version', () => { + localStorage.setItem('glcfirst.life', '13') + localStorage.setItem('glcsecond.life', '7') + localStorage.setItem('glcP1Hue.Color', '99') + localStorage.setItem('glcP2Hue.Color', '250') + + const game = useGame() + + expect(game.playerAt(0)?.life).toBe(13) + expect(game.playerAt(1)?.life).toBe(7) + expect(game.playerAt(0)?.hue).toBe(99) + expect(game.playerAt(1)?.hue).toBe(250) + expect(localStorage.getItem('glcfirst.life')).toBeNull() + }) + + it('survives corrupted storage', () => { + localStorage.setItem(STORAGE_KEY, '{not json') + + const game = useGame() + + expect(game.state.playerCount).toBe(2) + expect(game.playerAt(0)?.life).toBe(20) + }) +}) + +describe('sanitizeState', () => { + it('repairs partial and out-of-range values', () => { + const state = sanitizeState({ + presetId: 'unknown', + themeId: 'nope', + layoutId: 'sideways', + playerCount: 42, + startingLife: Number.NaN, + players: [{ name: '', hue: 720, life: 8.6, counters: { poison: -4 } }], + } as never) + + expect(state.presetId).toBe('standard') + expect(state.themeId).toBe('pulse') + expect(state.layoutId).toBe('facing') + expect(state.playerCount).toBe(6) + expect(state.startingLife).toBe(20) + expect(state.players).toHaveLength(6) + expect(state.players[0]).toMatchObject({ + name: 'Player 1', + hue: 0, + life: 9, + counters: { poison: 0, energy: 0 }, + }) + }) + + it('returns defaults for anything that is not an object', () => { + expect(sanitizeState(null).playerCount).toBe(2) + }) +}) diff --git a/src/game/inject.ts b/src/game/inject.ts new file mode 100644 index 0000000..00b5054 --- /dev/null +++ b/src/game/inject.ts @@ -0,0 +1,12 @@ +import { inject, type InjectionKey } from 'vue' +import { useGame, type GameStore } from './store' + +export const GAME_STORE_KEY: InjectionKey = Symbol('glc.game') + +/** + * Components resolve the store through injection so tests can mount them + * with an isolated store; falls back to the shared app store. + */ +export function useGameStore(): GameStore { + return inject(GAME_STORE_KEY, null) ?? useGame() +} diff --git a/src/game/layouts.ts b/src/game/layouts.ts new file mode 100644 index 0000000..6077358 --- /dev/null +++ b/src/game/layouts.ts @@ -0,0 +1,188 @@ +import type { PlayerCount, SeatLayout, SeatPlacement, Rotation } from './types' +import { MAX_PLAYERS, MIN_PLAYERS } from './types' + +interface SeatSpec { + row: number + column: number + rowSpan?: number + columnSpan?: number + rotation: Rotation +} + +interface LayoutSpec { + columns: number + rows: number + seats: SeatSpec[] +} + +export type LayoutId = 'facing' | 'sides' | 'stacked' + +export interface LayoutDefinition { + id: LayoutId + name: string + description: string +} + +export const TABLE_LAYOUTS: LayoutDefinition[] = [ + { + id: 'facing', + name: 'Facing', + description: 'Players sit along the top and bottom edge, facing each other.', + }, + { + id: 'sides', + name: 'Sides', + description: 'Players sit along the left and right edge; seats turn sideways.', + }, + { + id: 'stacked', + name: 'Same side', + description: 'Everyone sits on the same side; all seats stay upright.', + }, +] + +export const DEFAULT_LAYOUT_ID: LayoutId = 'facing' + +export function isLayoutId(value: unknown): value is LayoutId { + return typeof value === 'string' && TABLE_LAYOUTS.some((layout) => layout.id === value) +} + +export function findLayout(id: string): LayoutDefinition { + const match = TABLE_LAYOUTS.find((layout) => layout.id === id) + return match ?? TABLE_LAYOUTS[0]! +} + +function toPlacement(spec: SeatSpec): SeatPlacement { + const rowEnd = spec.row + (spec.rowSpan ?? 1) + const columnEnd = spec.column + (spec.columnSpan ?? 1) + return { + rotation: spec.rotation, + gridArea: `${spec.row} / ${spec.column} / ${rowEnd} / ${columnEnd}`, + } +} + +/** + * The `facing` seat maps: players sit along the top and bottom edge the way the + * two player table works, so life totals read horizontally. Seats in the top + * row are flipped 180°, the bottom row stays upright. + */ +const FACING_SPECS: Record = { + 2: { + columns: 1, + rows: 2, + seats: [ + { row: 1, column: 1, rotation: 180 }, + { row: 2, column: 1, rotation: 0 }, + ], + }, + 3: { + columns: 2, + rows: 2, + seats: [ + { row: 1, column: 1, rotation: 180 }, + { row: 1, column: 2, rotation: 180 }, + { row: 2, column: 1, columnSpan: 2, rotation: 0 }, + ], + }, + 4: { + columns: 2, + rows: 2, + seats: [ + { row: 1, column: 1, rotation: 180 }, + { row: 1, column: 2, rotation: 180 }, + { row: 2, column: 1, rotation: 0 }, + { row: 2, column: 2, rotation: 0 }, + ], + }, + // Three across the top, two across the bottom; the sixth-column grid lets + // the two rows use different seat widths without a gap. + 5: { + columns: 6, + rows: 2, + seats: [ + { row: 1, column: 1, columnSpan: 2, rotation: 180 }, + { row: 1, column: 3, columnSpan: 2, rotation: 180 }, + { row: 1, column: 5, columnSpan: 2, rotation: 180 }, + { row: 2, column: 1, columnSpan: 3, rotation: 0 }, + { row: 2, column: 4, columnSpan: 3, rotation: 0 }, + ], + }, + 6: { + columns: 3, + rows: 2, + seats: [ + { row: 1, column: 1, rotation: 180 }, + { row: 1, column: 2, rotation: 180 }, + { row: 1, column: 3, rotation: 180 }, + { row: 2, column: 1, rotation: 0 }, + { row: 2, column: 2, rotation: 0 }, + { row: 2, column: 3, rotation: 0 }, + ], + }, +} + +/** + * Rotation of a facing seat once the table is turned a quarter turn. + * + * A player reads the panel with its top pointing away from them, so somebody + * at the left edge needs it turned 90° (top towards the right of the screen) + * and somebody at the right edge 270°. The top row therefore becomes the left + * column at 90°, and the bottom row the right column at 270°. + */ +const TURNED: Record = { 0: 270, 90: 0, 180: 90, 270: 180 } + +/** + * `sides` is the facing table turned a quarter turn: rows become columns, the + * top group moves to the left edge and the bottom group to the right edge. + */ +function turn(spec: LayoutSpec): LayoutSpec { + return { + columns: spec.rows, + rows: spec.columns, + seats: spec.seats.map((seat) => ({ + row: seat.column, + column: seat.row, + rowSpan: seat.columnSpan, + columnSpan: seat.rowSpan, + rotation: TURNED[seat.rotation], + })), + } +} + +/** `stacked` puts every seat in its own upright row, for one-sided seating. */ +function stacked(playerCount: PlayerCount): LayoutSpec { + return { + columns: 1, + rows: playerCount, + seats: Array.from({ length: playerCount }, (_, index) => ({ + row: index + 1, + column: 1, + rotation: 0 as Rotation, + })), + } +} + +function specFor(playerCount: PlayerCount, layoutId: LayoutId): LayoutSpec { + switch (layoutId) { + case 'sides': + return turn(FACING_SPECS[playerCount]) + case 'stacked': + return stacked(playerCount) + default: + return FACING_SPECS[playerCount] + } +} + +export function clampPlayerCount(count: number): PlayerCount { + if (!Number.isFinite(count)) return MIN_PLAYERS + return Math.min(MAX_PLAYERS, Math.max(MIN_PLAYERS, Math.round(count))) as PlayerCount +} + +export function seatLayout(playerCount: number, layoutId: string = DEFAULT_LAYOUT_ID): SeatLayout { + const spec = specFor(clampPlayerCount(playerCount), findLayout(layoutId).id) + return { + columns: spec.columns, + rows: spec.rows, + seats: spec.seats.map(toPlacement), + } +} diff --git a/src/game/presets.ts b/src/game/presets.ts new file mode 100644 index 0000000..3c73810 --- /dev/null +++ b/src/game/presets.ts @@ -0,0 +1,58 @@ +import type { GamePreset } from './types' + +export const GAME_PRESETS: GamePreset[] = [ + { + id: 'standard', + name: 'Standard', + description: 'Duel life total, single steps.', + startingLife: 20, + step: 1, + bigStep: 10, + counters: [], + }, + { + id: 'commander', + name: 'Commander', + description: 'Multiplayer life total with poison.', + startingLife: 40, + step: 1, + bigStep: 10, + counters: ['poison'], + }, + { + id: 'energy', + name: 'Energy', + description: 'Life plus poison and energy counters.', + startingLife: 20, + step: 1, + bigStep: 10, + counters: ['poison', 'energy'], + }, + { + id: 'duel8000', + name: 'Duel 8000', + description: 'Large life pool, steps of 50.', + startingLife: 8000, + step: 50, + bigStep: 500, + counters: [], + }, + { + id: 'board', + name: 'Board game', + description: 'Neutral counter starting at zero.', + startingLife: 0, + step: 1, + bigStep: 10, + counters: [], + }, +] + +export const DEFAULT_PRESET_ID = 'standard' + +export function findPreset(id: string): GamePreset { + return ( + GAME_PRESETS.find((preset) => preset.id === id) ?? + GAME_PRESETS.find((preset) => preset.id === DEFAULT_PRESET_ID)! + ) +} diff --git a/src/game/storage.ts b/src/game/storage.ts new file mode 100644 index 0000000..a12225a --- /dev/null +++ b/src/game/storage.ts @@ -0,0 +1,91 @@ +export const STORAGE_KEY = 'glc.state.v2' + +/** Keys written by the pre-revamp two-player version. */ +const LEGACY_KEYS = { + p1Life: 'glcfirst.life', + p2Life: 'glcsecond.life', + p1Hue: 'glcP1Hue.Color', + p2Hue: 'glcP2Hue.Color', +} as const + +function storage(): Storage | null { + try { + return typeof localStorage === 'undefined' ? null : localStorage + } catch { + // Storage can throw when cookies/site data are blocked. + return null + } +} + +export function readJson(key: string): T | null { + const store = storage() + if (!store) return null + try { + const raw = store.getItem(key) + return raw === null ? null : (JSON.parse(raw) as T) + } catch { + return null + } +} + +export function writeJson(key: string, value: unknown): void { + const store = storage() + if (!store) return + try { + store.setItem(key, JSON.stringify(value)) + } catch { + // Quota or privacy mode: the app stays usable without persistence. + } +} + +export function removeKey(key: string): void { + const store = storage() + if (!store) return + try { + store.removeItem(key) + } catch { + // ignore + } +} + +export interface LegacySnapshot { + lives: [number, number] + hues: [number, number] +} + +function readLegacyNumber(key: string): number | null { + const store = storage() + if (!store) return null + try { + const raw = store.getItem(key) + if (raw === null) return null + const value = Number.parseFloat(raw) + return Number.isFinite(value) ? value : null + } catch { + return null + } +} + +/** + * Reads the two-player data written by earlier releases so returning users + * keep their life totals and colours. Returns null when nothing is stored. + */ +export function readLegacyState(): LegacySnapshot | null { + const p1Life = readLegacyNumber(LEGACY_KEYS.p1Life) + const p2Life = readLegacyNumber(LEGACY_KEYS.p2Life) + const p1Hue = readLegacyNumber(LEGACY_KEYS.p1Hue) + const p2Hue = readLegacyNumber(LEGACY_KEYS.p2Hue) + + if (p1Life === null && p2Life === null && p1Hue === null && p2Hue === null) { + return null + } + + return { + lives: [p1Life ?? 20, p2Life ?? 20], + hues: [p1Hue ?? 32, p2Hue ?? 222], + } +} + +export function clearLegacyState(): void { + Object.values(LEGACY_KEYS).forEach(removeKey) +} diff --git a/src/game/store.ts b/src/game/store.ts new file mode 100644 index 0000000..80b3b10 --- /dev/null +++ b/src/game/store.ts @@ -0,0 +1,244 @@ +import { computed, reactive, readonly } from 'vue' +import type { CounterKind, GameState, Player } from './types' +import { MAX_PLAYERS } from './types' +import { DEFAULT_PRESET_ID, findPreset } from './presets' +import { DEFAULT_LAYOUT_ID, clampPlayerCount, findLayout, isLayoutId, seatLayout } from './layouts' +import { DEFAULT_THEME_ID, isThemeId } from '../theme/themes' +import { STORAGE_KEY, clearLegacyState, readJson, readLegacyState, writeJson } from './storage' + +export const DEFAULT_HUES = [32, 222, 145, 305, 355, 265] + +function defaultPlayer(index: number, life: number): Player { + return { + id: index, + name: `Player ${index + 1}`, + hue: DEFAULT_HUES[index] ?? 0, + life, + counters: { poison: 0, energy: 0 }, + } +} + +function toFiniteNumber(value: unknown, fallback: number): number { + const parsed = typeof value === 'number' ? value : Number.parseFloat(String(value)) + return Number.isFinite(parsed) ? parsed : fallback +} + +function normaliseHue(value: unknown, fallback: number): number { + const hue = toFiniteNumber(value, fallback) + return ((Math.round(hue) % 360) + 360) % 360 +} + +function defaultState(): GameState { + const preset = findPreset(DEFAULT_PRESET_ID) + return { + presetId: preset.id, + themeId: DEFAULT_THEME_ID, + layoutId: DEFAULT_LAYOUT_ID, + playerCount: 2, + startingLife: preset.startingLife, + players: Array.from({ length: MAX_PLAYERS }, (_, index) => + defaultPlayer(index, preset.startingLife), + ), + } +} + +/** Rebuilds a trustworthy state object from whatever was persisted. */ +export function sanitizeState(raw: Partial | null): GameState { + const base = defaultState() + if (!raw || typeof raw !== 'object') return base + + const preset = findPreset(typeof raw.presetId === 'string' ? raw.presetId : base.presetId) + const startingLife = Math.round(toFiniteNumber(raw.startingLife, preset.startingLife)) + const storedPlayers = Array.isArray(raw.players) ? raw.players : [] + + return { + presetId: preset.id, + themeId: isThemeId(raw.themeId) ? raw.themeId : base.themeId, + layoutId: isLayoutId(raw.layoutId) ? raw.layoutId : base.layoutId, + playerCount: clampPlayerCount(toFiniteNumber(raw.playerCount, base.playerCount)), + startingLife, + players: Array.from({ length: MAX_PLAYERS }, (_, index) => { + const fallback = defaultPlayer(index, startingLife) + const stored = storedPlayers[index] as Partial | undefined + if (!stored || typeof stored !== 'object') return fallback + return { + id: index, + name: + typeof stored.name === 'string' && stored.name.trim().length > 0 + ? stored.name.slice(0, 24) + : fallback.name, + hue: normaliseHue(stored.hue, fallback.hue), + life: Math.round(toFiniteNumber(stored.life, fallback.life)), + counters: { + poison: Math.max(0, Math.round(toFiniteNumber(stored.counters?.poison, 0))), + energy: Math.max(0, Math.round(toFiniteNumber(stored.counters?.energy, 0))), + }, + } + }), + } +} + +function loadState(): GameState { + const stored = readJson>(STORAGE_KEY) + if (stored) return sanitizeState(stored) + + const legacy = readLegacyState() + if (!legacy) return defaultState() + + // One-off upgrade from the two-player release. + const migrated = defaultState() + legacy.lives.forEach((life, index) => { + const player = migrated.players[index] + if (player) player.life = Math.round(life) + }) + legacy.hues.forEach((hue, index) => { + const player = migrated.players[index] + if (player) player.hue = normaliseHue(hue, player.hue) + }) + clearLegacyState() + return migrated +} + +export function createGameStore(initial?: Partial) { + const state = reactive(initial ? sanitizeState(initial) : loadState()) + + const persist = () => writeJson(STORAGE_KEY, state) + + const preset = computed(() => findPreset(state.presetId)) + const activePlayers = computed(() => state.players.slice(0, state.playerCount)) + const tableLayout = computed(() => findLayout(state.layoutId)) + const layout = computed(() => seatLayout(state.playerCount, state.layoutId)) + const counters = computed(() => preset.value.counters) + + const playerAt = (id: number): Player | undefined => + state.players.find((player) => player.id === id) + + const adjustLife = (id: number, delta: number) => { + const player = playerAt(id) + if (!player || !Number.isFinite(delta)) return + player.life = Math.round(player.life + delta) + persist() + } + + const setLife = (id: number, life: number) => { + const player = playerAt(id) + if (!player || !Number.isFinite(life)) return + player.life = Math.round(life) + persist() + } + + const adjustCounter = (id: number, kind: CounterKind, delta: number) => { + const player = playerAt(id) + if (!player || !Number.isFinite(delta)) return + player.counters[kind] = Math.max(0, Math.round(player.counters[kind] + delta)) + persist() + } + + const setHue = (id: number, hue: number) => { + const player = playerAt(id) + if (!player) return + player.hue = normaliseHue(hue, player.hue) + persist() + } + + const setName = (id: number, name: string) => { + const player = playerAt(id) + if (!player) return + const trimmed = name.trim() + player.name = trimmed.length > 0 ? trimmed.slice(0, 24) : `Player ${id + 1}` + persist() + } + + const setPlayerCount = (count: number) => { + state.playerCount = clampPlayerCount(count) + persist() + } + + const setStartingLife = (life: number) => { + if (!Number.isFinite(life)) return + state.startingLife = Math.round(life) + persist() + } + + /** Puts every player back to the starting life and clears their counters. */ + const resetGame = () => { + state.players.forEach((player) => { + player.life = state.startingLife + player.counters.poison = 0 + player.counters.energy = 0 + }) + persist() + } + + const applyPreset = (presetId: string) => { + const next = findPreset(presetId) + state.presetId = next.id + state.startingLife = next.startingLife + resetGame() + } + + const setTheme = (themeId: string) => { + if (!isThemeId(themeId)) return + state.themeId = themeId + persist() + } + + const setLayout = (layoutId: string) => { + if (!isLayoutId(layoutId)) return + state.layoutId = layoutId + persist() + } + + const resetColors = () => { + state.players.forEach((player, index) => { + player.hue = DEFAULT_HUES[index] ?? 0 + }) + persist() + } + + /** Returns the id of a randomly chosen starting player. */ + const randomFirstPlayer = (random: () => number = Math.random): number => { + const seats = activePlayers.value + // Math.random() is [0, 1) but a stubbed one may return exactly 1. + const index = Math.min(Math.max(0, Math.floor(random() * seats.length)), seats.length - 1) + return seats[index]?.id ?? 0 + } + + return { + state: readonly(state), + preset, + activePlayers, + layout, + tableLayout, + counters, + playerAt, + adjustLife, + setLife, + adjustCounter, + setHue, + setName, + setPlayerCount, + setStartingLife, + resetGame, + applyPreset, + setTheme, + setLayout, + resetColors, + randomFirstPlayer, + persist, + } +} + +export type GameStore = ReturnType + +let singleton: GameStore | null = null + +export function useGame(): GameStore { + if (!singleton) singleton = createGameStore() + return singleton +} + +/** Test seam: drops the shared store so the next `useGame()` rebuilds it. */ +export function resetGameStore(): void { + singleton = null +} diff --git a/src/game/types.ts b/src/game/types.ts new file mode 100644 index 0000000..290856b --- /dev/null +++ b/src/game/types.ts @@ -0,0 +1,68 @@ +export type CounterKind = 'poison' | 'energy' + +export interface Player { + id: number + name: string + /** Hue (0-359) used by every theme as the player's identity colour. */ + hue: number + life: number + counters: Record +} + +export interface GamePreset { + id: string + name: string + description: string + startingLife: number + /** Life change of a single tap. */ + step: number + /** Life change applied repeatedly while a button is held down. */ + bigStep: number + /** Extra counters shown on the player panel. */ + counters: CounterKind[] +} + +/** + * How far the panel turns so it reads upright from its player's seat: 0° for + * the bottom edge, 180° for the top and — in the `sides` layout — 90° for the + * left edge and 270° for the right. + */ +export type Rotation = 0 | 90 | 180 | 270 + +export interface SeatPlacement { + /** Rotation that makes the panel readable from the player's seat. */ + rotation: Rotation + /** CSS `grid-area` shorthand: row-start / col-start / row-end / col-end. */ + gridArea: string +} + +export interface SeatLayout { + columns: number + rows: number + seats: SeatPlacement[] +} + +export interface GameState { + presetId: string + themeId: string + layoutId: string + playerCount: number + startingLife: number + players: Player[] +} + +export const MIN_PLAYERS = 2 +export const MAX_PLAYERS = 6 + +/** Player counts with a defined seat layout. */ +export type PlayerCount = 2 | 3 | 4 | 5 | 6 + +export const COUNTER_LABELS: Record = { + poison: 'Poison', + energy: 'Energy', +} + +export const COUNTER_ICONS: Record = { + poison: '☠', + energy: '⚡', +} diff --git a/src/main.ts b/src/main.ts index 804997c..391b2ac 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,9 +1,6 @@ import { createApp } from 'vue' import App from './App.vue' -import './assets/main.css' - -//tailwind css import './assets/styles/index.css' createApp(App).mount('#app') diff --git a/src/test/pwaRegisterStub.ts b/src/test/pwaRegisterStub.ts new file mode 100644 index 0000000..1ec51b7 --- /dev/null +++ b/src/test/pwaRegisterStub.ts @@ -0,0 +1,10 @@ +import { ref } from 'vue' + +/** Stand-in for `virtual:pwa-register/vue`, which only exists in a Vite build. */ +export function useRegisterSW() { + return { + offlineReady: ref(false), + needRefresh: ref(false), + updateServiceWorker: async () => {}, + } +} diff --git a/src/test/setup.ts b/src/test/setup.ts new file mode 100644 index 0000000..eb66f29 --- /dev/null +++ b/src/test/setup.ts @@ -0,0 +1,8 @@ +import { beforeEach } from 'vitest' +import { resetGameStore } from '../game/store' + +beforeEach(() => { + localStorage.clear() + resetGameStore() + document.documentElement.removeAttribute('data-theme') +}) diff --git a/src/theme/__tests__/tokens.spec.ts b/src/theme/__tests__/tokens.spec.ts new file mode 100644 index 0000000..72bbbb5 --- /dev/null +++ b/src/theme/__tests__/tokens.spec.ts @@ -0,0 +1,52 @@ +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { describe, expect, it } from 'vitest' + +// Resolved from the project root: under jsdom `import.meta.url` is not a file URL. +const css = readFileSync(resolve(process.cwd(), 'src/assets/styles/tokens.css'), 'utf8').replace( + /\/\*[\s\S]*?\*\//g, + '', +) + +interface Rule { + selector: string + body: string +} + +const rules: Rule[] = [...css.matchAll(/([^{}]+)\{([^{}]*)\}/g)].map((match) => ({ + selector: match[1].trim(), + body: match[2], +})) + +describe('theme tokens', () => { + /** + * `var()` inside a custom property is substituted where the property is + * *declared*. A hue-dependent token declared on `:root` therefore resolves + * `--player-hue` to its fallback and paints every player the same colour, so + * those declarations have to live on the panel that carries the hue. + */ + it('declares every hue-dependent token on the panel itself', () => { + const offenders = rules + .filter((rule) => rule.body.includes('var(--player-hue')) + .filter((rule) => !rule.selector.includes('.panel')) + .map((rule) => rule.selector) + + expect(offenders).toEqual([]) + }) + + it('still defines the hue-dependent panel tokens for both themes', () => { + for (const theme of ['pulse', 'arena']) { + const rule = rules.find((r) => r.selector === `[data-theme='${theme}'] .panel`) + expect(rule, `${theme} panel tokens missing`).toBeDefined() + expect(rule!.body).toContain('--panel-bg') + expect(rule!.body).toContain('var(--player-hue') + } + }) + + it('keeps a fallback hue on every var(--player-hue) reference', () => { + const references = css.match(/var\(--player-hue[^)]*\)/g) ?? [] + + expect(references.length).toBeGreaterThan(0) + expect(references.every((reference) => reference.includes(','))).toBe(true) + }) +}) diff --git a/src/theme/__tests__/useTheme.spec.ts b/src/theme/__tests__/useTheme.spec.ts new file mode 100644 index 0000000..13f389c --- /dev/null +++ b/src/theme/__tests__/useTheme.spec.ts @@ -0,0 +1,57 @@ +import { afterEach, describe, expect, it } from 'vitest' +import { effectScope, nextTick } from 'vue' +import { createGameStore } from '../../game/store' +import { useTheme } from '../useTheme' +import { findTheme, isThemeId, THEMES } from '../themes' + +describe('theme catalogue', () => { + it('offers a bleed and a card variant', () => { + expect(THEMES.map((theme) => theme.variant).sort()).toEqual(['bleed', 'card']) + }) + + it('recognises only known ids and falls back safely', () => { + expect(isThemeId('pulse')).toBe(true) + expect(isThemeId('nope')).toBe(false) + expect(isThemeId(7)).toBe(false) + expect(findTheme('nope').id).toBe('pulse') + }) +}) + +describe('useTheme', () => { + let scope: ReturnType | null = null + + afterEach(() => { + scope?.stop() + scope = null + document.head.querySelector('meta[name="theme-color"]')?.remove() + }) + + it('mirrors the stored theme onto the document', async () => { + const meta = document.createElement('meta') + meta.name = 'theme-color' + meta.content = '#ffffff' + document.head.appendChild(meta) + + const game = createGameStore() + scope = effectScope() + const { theme } = scope.run(() => useTheme(game))! + + expect(document.documentElement.dataset.theme).toBe('pulse') + expect(meta.content).toBe('#111014') + + game.setTheme('arena') + await nextTick() + + expect(theme.value.id).toBe('arena') + expect(document.documentElement.dataset.theme).toBe('arena') + expect(meta.content).toBe('#0b0d13') + }) + + it('works when the page has no theme-color meta tag', () => { + const game = createGameStore({ themeId: 'arena' }) + scope = effectScope() + scope.run(() => useTheme(game)) + + expect(document.documentElement.dataset.theme).toBe('arena') + }) +}) diff --git a/src/theme/themes.ts b/src/theme/themes.ts new file mode 100644 index 0000000..0c77286 --- /dev/null +++ b/src/theme/themes.ts @@ -0,0 +1,42 @@ +export type ThemeId = 'pulse' | 'arena' + +export interface ThemeDefinition { + id: ThemeId + name: string + tagline: string + /** + * How a player panel is drawn: + * - `bleed`: edge-to-edge colour field, the whole half is the tap target. + * - `card`: dark stage with a floating card and explicit controls. + */ + variant: 'bleed' | 'card' + /** Two hues used for the settings preview swatch. */ + preview: [number, number] +} + +export const THEMES: [ThemeDefinition, ThemeDefinition] = [ + { + id: 'pulse', + name: 'Pulse', + tagline: 'Full-bleed colour, oversized numerals.', + variant: 'bleed', + preview: [32, 222], + }, + { + id: 'arena', + name: 'Arena', + tagline: 'Dark stage with floating player cards.', + variant: 'card', + preview: [265, 190], + }, +] + +export const DEFAULT_THEME_ID: ThemeId = 'pulse' + +export function isThemeId(value: unknown): value is ThemeId { + return typeof value === 'string' && THEMES.some((theme) => theme.id === value) +} + +export function findTheme(id: string): ThemeDefinition { + return THEMES.find((theme) => theme.id === id) ?? THEMES[0] +} diff --git a/src/theme/useTheme.ts b/src/theme/useTheme.ts new file mode 100644 index 0000000..d0c00aa --- /dev/null +++ b/src/theme/useTheme.ts @@ -0,0 +1,27 @@ +import { computed, watchEffect } from 'vue' +import { useGame } from '../game/store' +import { findTheme } from './themes' +import type { GameStore } from '../game/store' + +const THEME_COLORS: Record = { + pulse: '#111014', + arena: '#0b0d13', +} + +/** + * Keeps `` and the browser theme-colour in sync with the + * theme stored in the game state. + */ +export function useTheme(store: GameStore = useGame()) { + const theme = computed(() => findTheme(store.state.themeId)) + + watchEffect(() => { + if (typeof document === 'undefined') return + document.documentElement.dataset.theme = theme.value.id + + const meta = document.querySelector('meta[name="theme-color"]') + if (meta) meta.content = THEME_COLORS[theme.value.id] ?? '#111014' + }) + + return { theme } +} diff --git a/tsconfig.app.json b/tsconfig.app.json index b5b2ca9..895ac92 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -1,13 +1,13 @@ { "extends": "@vue/tsconfig/tsconfig.dom.json", "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], - "exclude": ["src/**/__tests__/*"], + "exclude": ["src/**/__tests__/*", "src/test/**/*"], "compilerOptions": { "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "baseUrl": ".", "paths": { "@/*": ["./src/*"] - }, - "allowJs": true + } } } diff --git a/tsconfig.config.json b/tsconfig.config.json index a2b025d..c8c8181 100644 --- a/tsconfig.config.json +++ b/tsconfig.config.json @@ -1,9 +1,9 @@ { "extends": "@vue/tsconfig/tsconfig.json", - "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"], + "include": ["vite.config.*", "vitest.config.*"], "compilerOptions": { "composite": true, - "types": ["vitest/globals"] - + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.config.tsbuildinfo", + "types": ["node", "vitest/globals"] } } diff --git a/tsconfig.vitest.json b/tsconfig.vitest.json index 8fa2848..01f3775 100644 --- a/tsconfig.vitest.json +++ b/tsconfig.vitest.json @@ -1,9 +1,13 @@ { "extends": "./tsconfig.app.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], "exclude": [], "compilerOptions": { "composite": true, - "lib": [], - "types": ["node", "jsdom","vitest/globals"] + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo", + "types": ["node", "vitest/globals"], + // Specs index into query results (`findAll(...)[0]`) constantly; the strict + // index checks stay on for application code. + "noUncheckedIndexedAccess": false } } diff --git a/vite-env.d.ts b/vite-env.d.ts deleted file mode 100644 index 3de2bbf..0000000 --- a/vite-env.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/// - -declare module "*.vue" { - import type { DefineComponent } from "vue" - const component: DefineComponent<{}, {}, any> - export default component -} diff --git a/vite.config.ts b/vite.config.ts index 0f2c2e6..f54ae44 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,15 +2,26 @@ import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' - import { VitePWA } from 'vite-plugin-pwa' -// https://vitejs.dev/config/ +// https://vite.dev/config/ export default defineConfig({ + base: './', plugins: [ vue(), VitePWA({ + registerType: 'prompt', manifest: { + name: 'Game Life Counter', + short_name: 'Life Counter', + description: + 'Life counter for board and card games — 2 to 6 players, presets, dice and a match timer.', + theme_color: '#111014', + background_color: '#08080c', + display: 'standalone', + orientation: 'any', + start_url: './', + scope: './', icons: [ { src: './icons/heart.svg', @@ -18,19 +29,19 @@ export default defineConfig({ type: 'image/svg+xml', purpose: 'any', }, + { + src: './icons/heart.png', + sizes: '512x512', + type: 'image/png', + purpose: 'any maskable', + }, ], }, }), ], - base: './', resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, - test: { - coverage: { - reporter: ['text', 'json', 'html'], - }, - }, }) diff --git a/vitest.config.ts b/vitest.config.ts index d8f45dc..2ad9d98 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,12 +1,30 @@ -/// +import { fileURLToPath, URL } from 'node:url' -import { defineConfig } from "vite" -import Vue from "@vitejs/plugin-vue" +import { defineConfig } from 'vitest/config' +import vue from '@vitejs/plugin-vue' export default defineConfig({ - plugins: [Vue()], + plugins: [vue()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + // The PWA virtual module only exists inside a Vite build. + 'virtual:pwa-register/vue': fileURLToPath( + new URL('./src/test/pwaRegisterStub.ts', import.meta.url), + ), + }, + }, test: { globals: true, - environment: "jsdom", + environment: 'jsdom', + include: ['src/**/*.spec.ts'], + setupFiles: ['./src/test/setup.ts'], + restoreMocks: true, + coverage: { + provider: 'v8', + reporter: ['text', 'html', 'json'], + include: ['src/**/*.{ts,vue}'], + exclude: ['src/main.ts', 'src/test/**', 'src/**/*.spec.ts'], + }, }, })