@freegamestore/games 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +61 -8
  2. package/TOKENS.md +132 -0
  3. package/dist/Confetti.d.ts +20 -0
  4. package/dist/Confetti.d.ts.map +1 -0
  5. package/dist/Confetti.js +76 -0
  6. package/dist/Confetti.js.map +1 -0
  7. package/dist/GameConfirm.d.ts +1 -1
  8. package/dist/GameConfirm.d.ts.map +1 -1
  9. package/dist/GameModal.d.ts +1 -1
  10. package/dist/GameModal.d.ts.map +1 -1
  11. package/dist/GameOverScreen.d.ts +1 -1
  12. package/dist/GameOverScreen.d.ts.map +1 -1
  13. package/dist/GameResultScreen.d.ts +46 -0
  14. package/dist/GameResultScreen.d.ts.map +1 -0
  15. package/dist/GameResultScreen.js +157 -0
  16. package/dist/GameResultScreen.js.map +1 -0
  17. package/dist/GameTextSizeToggle.d.ts +1 -1
  18. package/dist/GameTextSizeToggle.d.ts.map +1 -1
  19. package/dist/GameThemeToggle.d.ts +1 -1
  20. package/dist/GameThemeToggle.d.ts.map +1 -1
  21. package/dist/GameThemeToggle.js +43 -7
  22. package/dist/GameThemeToggle.js.map +1 -1
  23. package/dist/GameTopbar.d.ts +12 -7
  24. package/dist/GameTopbar.d.ts.map +1 -1
  25. package/dist/GameTopbar.js +54 -188
  26. package/dist/GameTopbar.js.map +1 -1
  27. package/dist/GameTopbar.module.css +201 -0
  28. package/dist/StartScreen.d.ts +48 -0
  29. package/dist/StartScreen.d.ts.map +1 -0
  30. package/dist/StartScreen.js +159 -0
  31. package/dist/StartScreen.js.map +1 -0
  32. package/dist/feel/ScorePopup.d.ts +25 -0
  33. package/dist/feel/ScorePopup.d.ts.map +1 -0
  34. package/dist/feel/ScorePopup.js +67 -0
  35. package/dist/feel/ScorePopup.js.map +1 -0
  36. package/dist/feel/easing.d.ts +14 -0
  37. package/dist/feel/easing.d.ts.map +1 -0
  38. package/dist/feel/easing.js +12 -0
  39. package/dist/feel/easing.js.map +1 -0
  40. package/dist/feel/haptics.d.ts +19 -0
  41. package/dist/feel/haptics.d.ts.map +1 -0
  42. package/dist/feel/haptics.js +24 -0
  43. package/dist/feel/haptics.js.map +1 -0
  44. package/dist/feel/index.d.ts +17 -0
  45. package/dist/feel/index.d.ts.map +1 -0
  46. package/dist/feel/index.js +17 -0
  47. package/dist/feel/index.js.map +1 -0
  48. package/dist/feel/particles.d.ts +46 -0
  49. package/dist/feel/particles.d.ts.map +1 -0
  50. package/dist/feel/particles.js +61 -0
  51. package/dist/feel/particles.js.map +1 -0
  52. package/dist/feel/reducedMotion.d.ts +6 -0
  53. package/dist/feel/reducedMotion.d.ts.map +1 -0
  54. package/dist/feel/reducedMotion.js +10 -0
  55. package/dist/feel/reducedMotion.js.map +1 -0
  56. package/dist/feel/shake.d.ts +6 -0
  57. package/dist/feel/shake.d.ts.map +1 -0
  58. package/dist/feel/shake.js +17 -0
  59. package/dist/feel/shake.js.map +1 -0
  60. package/dist/feel/tween.d.ts +9 -0
  61. package/dist/feel/tween.d.ts.map +1 -0
  62. package/dist/feel/tween.js +15 -0
  63. package/dist/feel/tween.js.map +1 -0
  64. package/dist/feel/useHaptics.d.ts +12 -0
  65. package/dist/feel/useHaptics.d.ts.map +1 -0
  66. package/dist/feel/useHaptics.js +16 -0
  67. package/dist/feel/useHaptics.js.map +1 -0
  68. package/dist/feel/useParticles.d.ts +18 -0
  69. package/dist/feel/useParticles.d.ts.map +1 -0
  70. package/dist/feel/useParticles.js +79 -0
  71. package/dist/feel/useParticles.js.map +1 -0
  72. package/dist/feel/useShake.d.ts +14 -0
  73. package/dist/feel/useShake.d.ts.map +1 -0
  74. package/dist/feel/useShake.js +17 -0
  75. package/dist/feel/useShake.js.map +1 -0
  76. package/dist/index.d.ts +5 -2
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +14 -2
  79. package/dist/index.js.map +1 -1
  80. package/dist/tokens.d.ts +112 -0
  81. package/dist/tokens.d.ts.map +1 -0
  82. package/dist/tokens.js +106 -0
  83. package/dist/tokens.js.map +1 -0
  84. package/dist/useGameSounds.d.ts.map +1 -1
  85. package/dist/useGameSounds.js +37 -15
  86. package/dist/useGameSounds.js.map +1 -1
  87. package/dist/useLeaderboard.d.ts +8 -1
  88. package/dist/useLeaderboard.d.ts.map +1 -1
  89. package/dist/useLeaderboard.js +21 -11
  90. package/dist/useLeaderboard.js.map +1 -1
  91. package/package.json +21 -6
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Shared React UI primitives for games on **freegamestore.online**. Brand-consistent layout, scroll-free viewport lock, touch-friendly controls, and synthesized sound effects.
4
4
 
5
- **Version:** 0.14.0
5
+ **Version:** 0.18.0
6
6
 
7
7
  ## Installation
8
8
 
@@ -21,7 +21,7 @@ import { GameShell, GameTopbar } from '@freegamestore/games'
21
21
 
22
22
  export default function App() {
23
23
  return (
24
- <GameShell topbar={<GameTopbar title="Chess" score={42} />}>
24
+ <GameShell topbar={<GameTopbar title="Chess" />}>
25
25
  {/* your game canvas / DOM */}
26
26
  </GameShell>
27
27
  )
@@ -35,7 +35,7 @@ export default function App() {
35
35
  Root layout wrapper. Locks the game to `100svh`, prevents document scroll, disables text selection and touch callout. Every game wraps its content in this.
36
36
 
37
37
  ```tsx
38
- <GameShell topbar={<GameTopbar title="Tetris" score={42} />}>
38
+ <GameShell topbar={<GameTopbar title="Tetris" />}>
39
39
  <MyGame />
40
40
  </GameShell>
41
41
  ```
@@ -50,8 +50,11 @@ Root layout wrapper. Locks the game to `100svh`, prevents document scroll, disab
50
50
  The single allowed topbar. Same font, padding, and color tokens across every game.
51
51
 
52
52
  ```tsx
53
- // Simple: just a score
54
- <GameTopbar title="Tetris" score={42} />
53
+ // Simple: just the title. The score pill is hidden by default (#348)
54
+ <GameTopbar title="Tetris" />
55
+
56
+ // Opt in to the live score pill
57
+ <GameTopbar title="Tetris" score={42} showScore />
55
58
 
56
59
  // Custom stats + play/pause controls
57
60
  <GameTopbar
@@ -71,8 +74,9 @@ The single allowed topbar. Same font, padding, and color tokens across every gam
71
74
  | Prop | Type | Description |
72
75
  |------|------|-------------|
73
76
  | `title` | `string?` | Game name, left side |
74
- | `score` | `number?` | Convenience for single-score games |
75
- | `stats` | `GameTopbarStat[]?` | Custom stat lineup (replaces `score`) |
77
+ | `score` | `number?` | Live score, shown only when `showScore` is set |
78
+ | `showScore` | `boolean?` | Show the live score pill. Off by default (#348) |
79
+ | `stats` | `GameTopbarStat[]?` | Custom stat lineup (lives, level, time) |
76
80
  | `actions` | `ReactNode?` | Right-side buttons (max 2) |
77
81
  | `rules` | `ReactNode?` | Game instructions — shows info icon, opens overlay on tap |
78
82
  | `onPlayPause` | `() => void?` | Renders play/pause icon button |
@@ -84,7 +88,7 @@ The single allowed topbar. Same font, padding, and color tokens across every gam
84
88
  Sign-in / avatar widget for the topbar `actions` slot. Shows "Sign in" when signed out, avatar + dropdown when signed in.
85
89
 
86
90
  ```tsx
87
- <GameTopbar title="Chess" score={42} actions={<GameAuth />} />
91
+ <GameTopbar title="Chess" actions={<GameAuth />} />
88
92
  ```
89
93
 
90
94
  No props — uses `useAuth` internally.
@@ -149,6 +153,48 @@ Standard game-over overlay with score, high score, and play again button.
149
153
  | `onPlayAgain` | `() => void` | Play again handler |
150
154
  | `children` | `ReactNode?` | Extra content below the score |
151
155
 
156
+
157
+ ### GameResultScreen
158
+
159
+ An animated result screen for a finished level or game. A win shows falling confetti and an accent title. A loss shows the card alone. The action is focused when the screen opens. Use `GameOverScreen` for a plain game-over card. `GameResultScreen` is the animated version.
160
+
161
+ ```tsx
162
+ <GameResultScreen
163
+ tone={won ? 'win' : 'lose'}
164
+ eyebrow="Level 4"
165
+ title={won ? 'Level 4 cleared!' : 'Game over'}
166
+ message={won ? undefined : 'Your tray filled up.'}
167
+ stats={[
168
+ { label: 'Score', value: score.toLocaleString() },
169
+ { label: 'Moves', value: moves },
170
+ ]}
171
+ badge={newBest ? 'New best!' : undefined}
172
+ action={won ? { label: 'Next level', onClick: next } : { label: 'Play again', onClick: restart }}
173
+ >
174
+ <Leaderboard {...leaderboard} />
175
+ </GameResultScreen>
176
+ ```
177
+
178
+ | Prop | Type | Description |
179
+ |------|------|-------------|
180
+ | `tone` | `'win' \| 'lose'` | `win` shows confetti and the accent title colour |
181
+ | `eyebrow` | `string?` | Small line above the title, such as the level name |
182
+ | `title` | `string` | The headline |
183
+ | `message` | `string?` | One line under the title |
184
+ | `stats` | `{ label, value }[]?` | Stat rows in a two-column grid. `value` may be a number |
185
+ | `badge` | `ReactNode?` | Highlight line under the stats, such as "New best!" |
186
+ | `action` | `{ label, onClick }` | The primary button. It is focused when the screen opens |
187
+ | `children` | `ReactNode?` | Extra content between the badge and the action |
188
+
189
+ The screen covers the viewport and has `role="dialog"`. Motion is CSS only and
190
+ stops under `prefers-reduced-motion`, and the confetti is hidden for players who
191
+ ask for less motion. Its colours and motion read the design tokens from
192
+ [TOKENS.md](./TOKENS.md), with light-theme fallbacks, so it works without
193
+ `GAME_TOKENS_CSS`.
194
+
195
+ `Confetti` is exported on its own, for a game that wants the celebration
196
+ somewhere else.
197
+
152
198
  ### GameConfirm
153
199
 
154
200
  Confirm/cancel dialog built on GameModal.
@@ -234,6 +280,13 @@ const { topScores, recentScores, submitScore, loading, refresh } = useLeaderboar
234
280
  const { ok, rank } = await submitScore(1200)
235
281
  ```
236
282
 
283
+ Pass `period` (a UTC day, `YYYY-MM-DD`) for a per-day board, such as a daily puzzle. Omit it for the all-time board:
284
+
285
+ ```tsx
286
+ const today = new Date().toISOString().slice(0, 10)
287
+ const { topScores, submitScore } = useLeaderboard('tile-match', { period: today })
288
+ ```
289
+
237
290
  ### useGameSounds
238
291
 
239
292
  Synthesized sound effects via Web Audio API. Zero audio files. All sounds auto-respect the mute toggle.
package/TOKENS.md ADDED
@@ -0,0 +1,132 @@
1
+ # Design tokens
2
+
3
+ `@freegamestore/games` defines the brand, typography, shape and motion tokens
4
+ that every FreeGameStore game uses. They are lifted from the Mosaic Rush pilot,
5
+ which is the first game built on them. The source is `src/tokens.ts`. This file
6
+ is the reference.
7
+
8
+ ## Where the values live
9
+
10
+ | Layer | Where | What it does |
11
+ |---|---|---|
12
+ | Definitions | `src/tokens.ts` | Exports the values as TypeScript constants and builds `GAME_TOKENS_CSS`. |
13
+ | Consumption | SDK components | Read `var(--paper)` and the other tokens inline, with a fallback for each. |
14
+ | Injection | Each game | Adds `GAME_TOKENS_CSS` to its stylesheet. Nothing is injected on import, because the package is `sideEffects: false`. |
15
+
16
+ Before this package defined them, the brand colours (`--paper`, `--ink`,
17
+ `--accent`, `--panel`, `--line`, `--muted`) were defined only in each game's own
18
+ `index.css`. Components in the SDK used them with fallbacks, so a game that
19
+ did not define them still rendered. Defining them here gives every game the
20
+ same values.
21
+
22
+ ## Using the tokens
23
+
24
+ Add the stylesheet once, at the top of the game:
25
+
26
+ ```tsx
27
+ import { GAME_TOKENS_CSS } from '@freegamestore/games';
28
+
29
+ // In the root component:
30
+ <style>{GAME_TOKENS_CSS}</style>
31
+ ```
32
+
33
+ Or copy `GAME_TOKENS_CSS` into `index.css`, which is what Mosaic Rush does.
34
+ Then use the tokens in your own CSS:
35
+
36
+ ```css
37
+ .card {
38
+ background: var(--panel);
39
+ border: 1px solid var(--line);
40
+ border-radius: var(--radius);
41
+ font-family: var(--font-display);
42
+ transition: transform var(--motion-fast) var(--motion-ease-out);
43
+ }
44
+ ```
45
+
46
+ The dark theme applies under `data-theme="dark"` on `<html>`.
47
+ `GameThemeToggle` sets that attribute. The theme follows the system preference
48
+ until the player picks one.
49
+
50
+ ## Colours
51
+
52
+ Light values are the defaults on `:root`. The dark values override only the
53
+ tokens that differ, under `:root[data-theme="dark"]`.
54
+
55
+ | Token | Light | Dark | Use |
56
+ |---|---|---|---|
57
+ | `--paper` | `#ffffff` | `#0f0f0f` | Page and board background. |
58
+ | `--ink` | `#1a1a1a` | `#f5f5f5` | Primary text. |
59
+ | `--muted` | `#6b7280` | `#9ca3af` | Secondary text and captions. |
60
+ | `--line` | `#e5e7eb` | `#2d2d2d` | Hairline borders on empty surfaces. |
61
+ | `--line-strong` | `#d1d5db` | `#404040` | Borders on buttons and toggles. |
62
+ | `--panel` | `#f9fafb` | `#1a1a1a` | Raised cards and overlays. |
63
+ | `--glass` | `rgba(255, 255, 255, 0.8)` | `rgba(15, 15, 15, 0.8)` | Frosted overlay surfaces. |
64
+ | `--dock` | `#ffffff` | `#1a1a1a` | Bottom dock. |
65
+ | `--success` | `#16a34a` | `#22c55e` | Success status. |
66
+ | `--warning` | `#d97706` | `#fbbf24` | Warning status. |
67
+ | `--error` | `#dc2626` | `#ef4444` | Error status. |
68
+ | `--accent` | `#2563eb` | `#2563eb` | Primary actions and highlights. The same in both themes. |
69
+ | `--accent-soft` | `#eff6ff` | `#1a2e26` | Quiet fill behind accent text, such as badges. |
70
+ | `--accent-ink` | `#2563eb` | `#60a5fa` | Accent used as text. Lighter in dark mode so it passes AA. |
71
+
72
+ Ink, muted and accent-ink each meet WCAG AA (4.5:1) on `--paper` in both themes.
73
+ `src/tokens.test.ts` checks this.
74
+
75
+ Use `--accent` for fills, such as buttons, and `--accent-ink` for accent text.
76
+ `--accent` on a dark surface does not pass AA as text.
77
+
78
+ ## Typography
79
+
80
+ Only display headings and score values use the display face. Body text keeps the
81
+ platform's default sizes.
82
+
83
+ | Token | Value | Use |
84
+ |---|---|---|
85
+ | `--font-display` | `"Fraunces", Georgia, serif` | Titles and score values. |
86
+ | `--font-body` | `"Manrope", system-ui, sans-serif` | Everything else. |
87
+ | `--text-stat` | `1.25rem` | Score and stat values. |
88
+ | `--text-title` | `clamp(2rem, 9vw, 2.6rem)` | Overlay titles. |
89
+ | `--text-display` | `clamp(2.25rem, 11vw, 3.25rem)` | The start-screen title. |
90
+
91
+ The brand fonts (Manrope and Fraunces) are the only ones allowed. The compliance
92
+ suite checks for them.
93
+
94
+ ## Shape
95
+
96
+ | Token | Value | Use |
97
+ |---|---|---|
98
+ | `--radius` | `1.25rem` | Corner radius for cards and overlays. |
99
+
100
+ ## Motion
101
+
102
+ Every animation reads its duration and easing from these tokens, so one change
103
+ affects the whole game.
104
+
105
+ | Token | Value | Use |
106
+ |---|---|---|
107
+ | `--motion-fast` | `120ms` | Presses and pops. |
108
+ | `--motion-base` | `240ms` | Standard transitions. |
109
+ | `--motion-slow` | `420ms` | Larger entrances and overlays. |
110
+ | `--motion-ease-out` | `cubic-bezier(0.2, 0.9, 0.3, 1)` | Default ease-out. |
111
+ | `--motion-ease-spring` | `cubic-bezier(0.2, 0.9, 0.3, 1.3)` | Overshooting pops. |
112
+ | `--motion-duration` | `var(--motion-fast)`, so `120ms` | Default duration for a control transition, such as a press. Reduced motion sets it to `0ms`. |
113
+ | `--motion-easing` | `var(--motion-ease-out)` | Default easing for a control transition. |
114
+
115
+ ### Reduced motion
116
+
117
+ Under `prefers-reduced-motion: reduce`, the three durations are set to `0ms`.
118
+ Animations finish at once and only the final state shows. The easings do not
119
+ change.
120
+
121
+ Animations that depend on a duration then finish instantly. Any animation that
122
+ is pure decoration should also be disabled explicitly, as Mosaic Rush does for
123
+ its confetti and float effects.
124
+
125
+ ## Compatibility
126
+
127
+ - Adding these tokens changes no existing export. The SDK components keep their
128
+ fallbacks.
129
+ - Games that already define `--paper` and the others in their own `index.css`
130
+ keep working as long as their own stylesheet loads after the tokens, so its declarations win.
131
+ - The store-site aliases from Mosaic Rush (`--bg`, `--surface`, `--border`) are
132
+ not part of this package.
@@ -0,0 +1,20 @@
1
+ import type * as React from 'react';
2
+ /** The default palette: the four status-and-accent colours, following the theme. */
3
+ export declare const CONFETTI_COLOURS: readonly string[];
4
+ /**
5
+ * Styles for the falling pieces. Each piece's duration and delay are set
6
+ * inline. Under prefers-reduced-motion the layer is hidden, because the
7
+ * falling pieces are decoration only.
8
+ */
9
+ export declare const CONFETTI_CSS = "\n.fgs-confetti {\n position: absolute;\n inset: 0;\n z-index: 0;\n pointer-events: none;\n overflow: hidden;\n}\n.fgs-confetti-piece {\n position: absolute;\n top: -12px;\n left: var(--x);\n width: 0.5rem;\n height: 0.8rem;\n border-radius: 2px;\n background: var(--colour);\n animation: fgs-confetti-fall var(--duration, 1.6s) cubic-bezier(0.3, 0, 0.7, 1) var(--delay, 0ms) both;\n}\n.fgs-confetti-piece:nth-child(3n) {\n width: 0.6rem;\n height: 0.6rem;\n border-radius: 999px;\n}\n@keyframes fgs-confetti-fall {\n from { transform: translate3d(0, 0, 0) rotate(0deg); opacity: 1; }\n 80% { opacity: 1; }\n to { transform: translate3d(var(--drift, 0px), 620px, 0) rotate(var(--spin, 360deg)); opacity: 0; }\n}\n@media (prefers-reduced-motion: reduce) {\n .fgs-confetti {\n display: none;\n }\n}\n";
10
+ export interface ConfettiProps {
11
+ /** Colours cycled across the pieces. Default: the accent and the status colours. */
12
+ colours?: readonly string[];
13
+ }
14
+ /**
15
+ * Falling confetti for a win. Each piece's position, drift, spin and timing
16
+ * come from its index, so the render is the same every time. Renders nothing
17
+ * under prefers-reduced-motion.
18
+ */
19
+ export declare function Confetti({ colours }: ConfettiProps): React.JSX.Element | null;
20
+ //# sourceMappingURL=Confetti.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Confetti.d.ts","sourceRoot":"","sources":["../src/Confetti.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AASpC,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAK7C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,YAAY,2zBAiCxB,CAAC;AASF,MAAM,WAAW,aAAa;IAC5B,oFAAoF;IACpF,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,EAAE,OAA0B,EAAE,EAAE,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,IAAI,CAuBhG"}
@@ -0,0 +1,76 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { LIGHT_COLOURS } from './tokens.js';
3
+ /** Number of falling pieces. */
4
+ const PIECES = 30;
5
+ /** A CSS variable with a fallback taken from the #209 light tokens. */
6
+ const v = (name, fallback) => `var(--${name}, ${fallback})`;
7
+ /** The default palette: the four status-and-accent colours, following the theme. */
8
+ export const CONFETTI_COLOURS = [
9
+ v('accent', LIGHT_COLOURS.accent),
10
+ v('success', LIGHT_COLOURS.success),
11
+ v('warning', LIGHT_COLOURS.warning),
12
+ v('error', LIGHT_COLOURS.error),
13
+ ];
14
+ /**
15
+ * Styles for the falling pieces. Each piece's duration and delay are set
16
+ * inline. Under prefers-reduced-motion the layer is hidden, because the
17
+ * falling pieces are decoration only.
18
+ */
19
+ export const CONFETTI_CSS = `
20
+ .fgs-confetti {
21
+ position: absolute;
22
+ inset: 0;
23
+ z-index: 0;
24
+ pointer-events: none;
25
+ overflow: hidden;
26
+ }
27
+ .fgs-confetti-piece {
28
+ position: absolute;
29
+ top: -12px;
30
+ left: var(--x);
31
+ width: 0.5rem;
32
+ height: 0.8rem;
33
+ border-radius: 2px;
34
+ background: var(--colour);
35
+ animation: fgs-confetti-fall var(--duration, 1.6s) cubic-bezier(0.3, 0, 0.7, 1) var(--delay, 0ms) both;
36
+ }
37
+ .fgs-confetti-piece:nth-child(3n) {
38
+ width: 0.6rem;
39
+ height: 0.6rem;
40
+ border-radius: 999px;
41
+ }
42
+ @keyframes fgs-confetti-fall {
43
+ from { transform: translate3d(0, 0, 0) rotate(0deg); opacity: 1; }
44
+ 80% { opacity: 1; }
45
+ to { transform: translate3d(var(--drift, 0px), 620px, 0) rotate(var(--spin, 360deg)); opacity: 0; }
46
+ }
47
+ @media (prefers-reduced-motion: reduce) {
48
+ .fgs-confetti {
49
+ display: none;
50
+ }
51
+ }
52
+ `;
53
+ /** True when the player has asked for less motion. False on the server, where there is no window. */
54
+ function prefersReducedMotion() {
55
+ return typeof window !== 'undefined' && typeof window.matchMedia === 'function'
56
+ ? window.matchMedia('(prefers-reduced-motion: reduce)').matches
57
+ : false;
58
+ }
59
+ /**
60
+ * Falling confetti for a win. Each piece's position, drift, spin and timing
61
+ * come from its index, so the render is the same every time. Renders nothing
62
+ * under prefers-reduced-motion.
63
+ */
64
+ export function Confetti({ colours = CONFETTI_COLOURS }) {
65
+ if (prefersReducedMotion())
66
+ return null;
67
+ return (_jsxs("div", { className: "fgs-confetti", "aria-hidden": "true", children: [_jsx("style", { children: CONFETTI_CSS }), Array.from({ length: PIECES }, (_, i) => (_jsx("span", { className: "fgs-confetti-piece", style: {
68
+ '--x': `${(i * 37) % 100}%`,
69
+ '--drift': `${((i * 53) % 41) - 20}px`,
70
+ '--spin': `${((i * 97) % 720) - 360}deg`,
71
+ '--delay': `${(i % 7) * 70}ms`,
72
+ '--duration': `${1500 + (i % 5) * 200}ms`,
73
+ '--colour': colours[i % colours.length],
74
+ } }, i)))] }));
75
+ }
76
+ //# sourceMappingURL=Confetti.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Confetti.js","sourceRoot":"","sources":["../src/Confetti.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,gCAAgC;AAChC,MAAM,MAAM,GAAG,EAAE,CAAC;AAElB,uEAAuE;AACvE,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,QAAgB,EAAE,EAAE,CAAC,SAAS,IAAI,KAAK,QAAQ,GAAG,CAAC;AAE5E,oFAAoF;AACpF,MAAM,CAAC,MAAM,gBAAgB,GAAsB;IACjD,CAAC,CAAC,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC;IACjC,CAAC,CAAC,SAAS,EAAE,aAAa,CAAC,OAAO,CAAC;IACnC,CAAC,CAAC,SAAS,EAAE,aAAa,CAAC,OAAO,CAAC;IACnC,CAAC,CAAC,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC;CAChC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiC3B,CAAC;AAEF,qGAAqG;AACrG,SAAS,oBAAoB;IAC3B,OAAO,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,UAAU;QAC7E,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,kCAAkC,CAAC,CAAC,OAAO;QAC/D,CAAC,CAAC,KAAK,CAAC;AACZ,CAAC;AAOD;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,EAAE,OAAO,GAAG,gBAAgB,EAAiB;IACpE,IAAI,oBAAoB,EAAE;QAAE,OAAO,IAAI,CAAC;IACxC,OAAO,CACL,eAAK,SAAS,EAAC,cAAc,iBAAa,MAAM,aAC9C,0BAAQ,YAAY,GAAS,EAC5B,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CACxC,eAEE,SAAS,EAAC,oBAAoB,EAC9B,KAAK,EACH;oBACE,KAAK,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,GAAG,GAAG;oBAC3B,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,IAAI;oBACtC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,KAAK;oBACxC,SAAS,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI;oBAC9B,YAAY,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,IAAI;oBACzC,UAAU,EAAE,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;iBACjB,IAVrB,CAAC,CAYN,CACH,CAAC,IACE,CACP,CAAC;AACJ,CAAC"}
@@ -12,5 +12,5 @@ export interface GameConfirmProps {
12
12
  * Confirm/cancel dialog for games. Built on GameModal.
13
13
  * Use for: quit game, restart, delete save, etc.
14
14
  */
15
- export declare function GameConfirm({ open, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, variant, }: GameConfirmProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function GameConfirm({ open, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, variant, }: GameConfirmProps): import("react").JSX.Element;
16
16
  //# sourceMappingURL=GameConfirm.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GameConfirm.d.ts","sourceRoot":"","sources":["../src/GameConfirm.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CAChC;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,KAAK,EACL,OAAO,EACP,YAAwB,EACxB,WAAsB,EACtB,SAAS,EACT,QAAQ,EACR,OAAmB,GACpB,EAAE,gBAAgB,2CA4BlB"}
1
+ {"version":3,"file":"GameConfirm.d.ts","sourceRoot":"","sources":["../src/GameConfirm.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CAChC;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,KAAK,EACL,OAAO,EACP,YAAwB,EACxB,WAAsB,EACtB,SAAS,EACT,QAAQ,EACR,OAAmB,GACpB,EAAE,gBAAgB,+BA4BlB"}
@@ -10,5 +10,5 @@ export interface GameModalProps {
10
10
  * Closes on backdrop click or Escape key.
11
11
  * Styled with platform design tokens for consistency across all games.
12
12
  */
13
- export declare function GameModal({ open, onClose, title, children }: GameModalProps): import("react/jsx-runtime").JSX.Element | null;
13
+ export declare function GameModal({ open, onClose, title, children }: GameModalProps): import("react").JSX.Element | null;
14
14
  //# sourceMappingURL=GameModal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GameModal.d.ts","sourceRoot":"","sources":["../src/GameModal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAa,MAAM,OAAO,CAAC;AAElD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,cAAc,kDAiF3E"}
1
+ {"version":3,"file":"GameModal.d.ts","sourceRoot":"","sources":["../src/GameModal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAa,MAAM,OAAO,CAAC;AAElD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,cAAc,sCAiF3E"}
@@ -14,5 +14,5 @@ export interface GameOverScreenProps {
14
14
  * play again button, and optional extra content.
15
15
  * Renders as a centered overlay on top of the game board.
16
16
  */
17
- export declare function GameOverScreen({ score, highScore, onPlayAgain, children }: GameOverScreenProps): import("react/jsx-runtime").JSX.Element;
17
+ export declare function GameOverScreen({ score, highScore, onPlayAgain, children }: GameOverScreenProps): import("react").JSX.Element;
18
18
  //# sourceMappingURL=GameOverScreen.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GameOverScreen.d.ts","sourceRoot":"","sources":["../src/GameOverScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,mBAAmB,2CA+E9F"}
1
+ {"version":3,"file":"GameOverScreen.d.ts","sourceRoot":"","sources":["../src/GameOverScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,mBAAmB,+BA+E9F"}
@@ -0,0 +1,46 @@
1
+ import type * as React from 'react';
2
+ import type { ReactNode } from 'react';
3
+ export interface GameResultStat {
4
+ /** Short label, shown small and uppercase. */
5
+ label: string;
6
+ /** The value, shown large in the display face. */
7
+ value: string | number;
8
+ }
9
+ export interface GameResultScreenProps {
10
+ /** 'win' shows confetti and the accent title colour. 'lose' shows the card without confetti. */
11
+ tone: 'win' | 'lose';
12
+ /** Small line above the title, e.g. the level name. */
13
+ eyebrow?: string;
14
+ /** The headline, set large in the display face. */
15
+ title: string;
16
+ /** One line under the title. */
17
+ message?: string;
18
+ /** Stat rows shown in a two-column grid. Omit or pass [] for none. */
19
+ stats?: readonly GameResultStat[];
20
+ /** A highlight line under the stats, such as "New best!". */
21
+ badge?: ReactNode;
22
+ /** The primary action. It is focused when the screen opens. */
23
+ action: {
24
+ label: string;
25
+ onClick: () => void;
26
+ };
27
+ /** Extra content between the badge and the action, such as a leaderboard submit button. */
28
+ children?: ReactNode;
29
+ }
30
+ /**
31
+ * Styles for the result screen. Durations and easings come from the motion
32
+ * tokens. Everything stops under prefers-reduced-motion. Class names carry the
33
+ * `fgs-` prefix, so they cannot collide with a game's own styles.
34
+ */
35
+ export declare const RESULT_SCREEN_CSS: string;
36
+ /**
37
+ * An animated result screen for a finished level or game. It is a modal dialog
38
+ * with a title, optional stats and badge, optional extra content, and one
39
+ * action. A win adds falling confetti. The action is focused when the screen
40
+ * opens, so keyboard and screen-reader users land on the next step.
41
+ *
42
+ * It covers the viewport. Motion is CSS only, and it stops under
43
+ * prefers-reduced-motion.
44
+ */
45
+ export declare function GameResultScreen({ tone, eyebrow, title, message, stats, badge, action, children, }: GameResultScreenProps): React.JSX.Element;
46
+ //# sourceMappingURL=GameResultScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GameResultScreen.d.ts","sourceRoot":"","sources":["../src/GameResultScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMvC,MAAM,WAAW,cAAc;IAC7B,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,gGAAgG;IAChG,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;IACrB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAClC,6DAA6D;IAC7D,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,+DAA+D;IAC/D,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC;IAC/C,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAKD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,QA8H7B,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,OAAO,EACP,KAAK,EACL,OAAO,EACP,KAAU,EACV,KAAK,EACL,MAAM,EACN,QAAQ,GACT,EAAE,qBAAqB,qBA+CvB"}
@@ -0,0 +1,157 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useRef } from 'react';
3
+ import { Confetti } from './Confetti.js';
4
+ import { GameButton } from './GameButton.js';
5
+ import { LIGHT_COLOURS, MOTION_TOKENS, SHAPE_TOKENS, TYPOGRAPHY_TOKENS } from './tokens.js';
6
+ /** A CSS variable with a fallback taken from the #209 light tokens, so the screen renders without GAME_TOKENS_CSS. */
7
+ const v = (name, fallback) => `var(--${name}, ${fallback})`;
8
+ /**
9
+ * Styles for the result screen. Durations and easings come from the motion
10
+ * tokens. Everything stops under prefers-reduced-motion. Class names carry the
11
+ * `fgs-` prefix, so they cannot collide with a game's own styles.
12
+ */
13
+ export const RESULT_SCREEN_CSS = `
14
+ .fgs-result-overlay {
15
+ position: fixed;
16
+ inset: 0;
17
+ z-index: 800;
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ padding: 1rem;
22
+ overflow: hidden;
23
+ background: color-mix(in srgb, ${v('paper', LIGHT_COLOURS.paper)} 72%, transparent);
24
+ backdrop-filter: blur(5px);
25
+ animation: fgs-result-fade ${v('motion-base', MOTION_TOKENS.base)} ease-out both;
26
+ }
27
+ .fgs-result-card {
28
+ position: relative;
29
+ z-index: 1;
30
+ display: flex;
31
+ flex-direction: column;
32
+ align-items: center;
33
+ gap: 0.5rem;
34
+ width: min(20rem, 100%);
35
+ padding: 1.25rem 1.1rem 1.1rem;
36
+ border-radius: ${v('radius', SHAPE_TOKENS.radius)};
37
+ text-align: center;
38
+ background: ${v('panel', LIGHT_COLOURS.panel)};
39
+ border: 1px solid ${v('line', LIGHT_COLOURS.line)};
40
+ box-shadow:
41
+ inset 0 1px 0 rgb(255 255 255 / 0.4),
42
+ 0 1px 2px rgb(0 0 0 / 0.06),
43
+ 0 22px 40px -20px rgb(0 0 0 / 0.45);
44
+ animation: fgs-result-enter ${v('motion-slow', MOTION_TOKENS.slow)} cubic-bezier(0.2, 0.9, 0.3, 1.15) both;
45
+ }
46
+ .fgs-result-eyebrow {
47
+ margin: 0;
48
+ font-size: 0.72rem;
49
+ font-weight: 700;
50
+ letter-spacing: 0.12em;
51
+ text-transform: uppercase;
52
+ color: ${v('muted', LIGHT_COLOURS.muted)};
53
+ }
54
+ .fgs-result-title {
55
+ margin: 0;
56
+ font-family: ${v('font-display', TYPOGRAPHY_TOKENS.fontDisplay)};
57
+ font-weight: 800;
58
+ font-optical-sizing: auto;
59
+ font-variation-settings: "opsz" 144;
60
+ font-size: ${v('text-title', TYPOGRAPHY_TOKENS.textTitle)};
61
+ line-height: 1;
62
+ letter-spacing: -0.02em;
63
+ color: ${v('ink', LIGHT_COLOURS.ink)};
64
+ text-wrap: balance;
65
+ animation: fgs-result-enter ${v('motion-slow', MOTION_TOKENS.slow)} cubic-bezier(0.2, 0.9, 0.3, 1.2) 120ms both;
66
+ }
67
+ .fgs-result-win .fgs-result-title {
68
+ color: ${v('accent-ink', LIGHT_COLOURS.accentInk)};
69
+ }
70
+ .fgs-result-message {
71
+ margin: 0;
72
+ font-size: 0.9rem;
73
+ color: ${v('muted', LIGHT_COLOURS.muted)};
74
+ }
75
+ .fgs-result-stats {
76
+ display: grid;
77
+ grid-template-columns: repeat(2, minmax(0, 1fr));
78
+ gap: 0.5rem;
79
+ width: 100%;
80
+ margin: 0.5rem 0 0;
81
+ }
82
+ .fgs-result-stat {
83
+ padding: 0.5rem 0.4rem;
84
+ border-radius: 0.75rem;
85
+ background: ${v('paper', LIGHT_COLOURS.paper)};
86
+ border: 1px solid ${v('line', LIGHT_COLOURS.line)};
87
+ box-shadow: inset 0 -2px 0 rgb(0 0 0 / 0.04);
88
+ animation: fgs-result-enter ${v('motion-base', MOTION_TOKENS.base)} ease-out both;
89
+ animation-delay: calc(260ms + var(--i, 0) * 70ms);
90
+ }
91
+ .fgs-result-stat:last-child:nth-child(odd) {
92
+ grid-column: 1 / -1;
93
+ }
94
+ .fgs-result-stat dt {
95
+ font-size: 0.68rem;
96
+ font-weight: 700;
97
+ letter-spacing: 0.08em;
98
+ text-transform: uppercase;
99
+ color: ${v('muted', LIGHT_COLOURS.muted)};
100
+ }
101
+ .fgs-result-stat dd {
102
+ margin: 0.15rem 0 0;
103
+ font-family: ${v('font-display', TYPOGRAPHY_TOKENS.fontDisplay)};
104
+ font-weight: 800;
105
+ font-size: ${v('text-stat', TYPOGRAPHY_TOKENS.textStat)};
106
+ color: ${v('ink', LIGHT_COLOURS.ink)};
107
+ font-variant-numeric: tabular-nums;
108
+ }
109
+ .fgs-result-badge {
110
+ margin: 0.25rem 0 0;
111
+ font-size: 0.85rem;
112
+ font-weight: 700;
113
+ color: ${v('accent-ink', LIGHT_COLOURS.accentInk)};
114
+ animation: fgs-result-enter ${v('motion-base', MOTION_TOKENS.base)} ease-out 560ms both;
115
+ }
116
+ .fgs-result-actions {
117
+ width: 100%;
118
+ margin-top: 0.5rem;
119
+ animation: fgs-result-enter ${v('motion-base', MOTION_TOKENS.base)} ease-out 480ms both;
120
+ }
121
+ @keyframes fgs-result-fade {
122
+ from { opacity: 0; }
123
+ to { opacity: 1; }
124
+ }
125
+ @keyframes fgs-result-enter {
126
+ from { opacity: 0; transform: translateY(10px); }
127
+ to { opacity: 1; transform: none; }
128
+ }
129
+ @media (prefers-reduced-motion: reduce) {
130
+ .fgs-result-overlay,
131
+ .fgs-result-card,
132
+ .fgs-result-title,
133
+ .fgs-result-stat,
134
+ .fgs-result-badge,
135
+ .fgs-result-actions {
136
+ animation: none;
137
+ }
138
+ }
139
+ `;
140
+ /**
141
+ * An animated result screen for a finished level or game. It is a modal dialog
142
+ * with a title, optional stats and badge, optional extra content, and one
143
+ * action. A win adds falling confetti. The action is focused when the screen
144
+ * opens, so keyboard and screen-reader users land on the next step.
145
+ *
146
+ * It covers the viewport. Motion is CSS only, and it stops under
147
+ * prefers-reduced-motion.
148
+ */
149
+ export function GameResultScreen({ tone, eyebrow, title, message, stats = [], badge, action, children, }) {
150
+ const titleId = useId();
151
+ const cardRef = useRef(null);
152
+ useEffect(() => {
153
+ cardRef.current?.querySelector('.fgs-result-actions button')?.focus();
154
+ }, []);
155
+ return (_jsxs("div", { className: `fgs-result-overlay fgs-result-${tone}`, role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, children: [_jsx("style", { children: RESULT_SCREEN_CSS }), tone === 'win' && _jsx(Confetti, {}), _jsxs("div", { className: "fgs-result-card", ref: cardRef, children: [eyebrow && _jsx("p", { className: "fgs-result-eyebrow", children: eyebrow }), _jsx("h2", { id: titleId, className: "fgs-result-title", children: title }), message && _jsx("p", { className: "fgs-result-message", children: message }), stats.length > 0 && (_jsx("dl", { className: "fgs-result-stats", children: stats.map((stat, i) => (_jsxs("div", { className: "fgs-result-stat", style: { '--i': i }, children: [_jsx("dt", { children: stat.label }), _jsx("dd", { children: stat.value })] }, stat.label))) })), badge ? _jsx("div", { className: "fgs-result-badge", children: badge }) : null, children, _jsx("div", { className: "fgs-result-actions", children: _jsx(GameButton, { variant: "primary", size: "lg", block: true, onClick: action.onClick, children: action.label }) })] })] }));
156
+ }
157
+ //# sourceMappingURL=GameResultScreen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GameResultScreen.js","sourceRoot":"","sources":["../src/GameResultScreen.tsx"],"names":[],"mappings":"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"}
@@ -2,5 +2,5 @@
2
2
  * Compact text-size toggle for game topbars. 28px square, shows A/A+/A-.
3
3
  * Designed to fit in GameTopbar's `actions` slot alongside GameThemeToggle.
4
4
  */
5
- export declare function GameTextSizeToggle(): import("react/jsx-runtime").JSX.Element;
5
+ export declare function GameTextSizeToggle(): import("react").JSX.Element;
6
6
  //# sourceMappingURL=GameTextSizeToggle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GameTextSizeToggle.d.ts","sourceRoot":"","sources":["../src/GameTextSizeToggle.tsx"],"names":[],"mappings":"AAsBA;;;GAGG;AACH,wBAAgB,kBAAkB,4CA0CjC"}
1
+ {"version":3,"file":"GameTextSizeToggle.d.ts","sourceRoot":"","sources":["../src/GameTextSizeToggle.tsx"],"names":[],"mappings":"AAsBA;;;GAGG;AACH,wBAAgB,kBAAkB,gCA0CjC"}
@@ -2,5 +2,5 @@
2
2
  * Compact theme toggle for game topbars. Sun/moon icon, 28px square.
3
3
  * Designed to fit in GameTopbar's `actions` slot.
4
4
  */
5
- export declare function GameThemeToggle(): import("react/jsx-runtime").JSX.Element;
5
+ export declare function GameThemeToggle(): import("react").JSX.Element;
6
6
  //# sourceMappingURL=GameThemeToggle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GameThemeToggle.d.ts","sourceRoot":"","sources":["../src/GameThemeToggle.tsx"],"names":[],"mappings":"AA8BA;;;GAGG;AACH,wBAAgB,eAAe,4CAyE9B"}
1
+ {"version":3,"file":"GameThemeToggle.d.ts","sourceRoot":"","sources":["../src/GameThemeToggle.tsx"],"names":[],"mappings":"AAiDA;;;GAGG;AACH,wBAAgB,eAAe,gCAwF9B"}