@freegamestore/games 0.16.0 → 0.19.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 (114) hide show
  1. package/README.md +106 -8
  2. package/TOKENS.md +132 -0
  3. package/dist/Confetti.d.ts +18 -0
  4. package/dist/Confetti.d.ts.map +1 -0
  5. package/dist/Confetti.js +43 -0
  6. package/dist/Confetti.js.map +1 -0
  7. package/dist/GameAuth.d.ts.map +1 -1
  8. package/dist/GameAuth.js +5 -87
  9. package/dist/GameAuth.js.map +1 -1
  10. package/dist/GameButton.d.ts +2 -1
  11. package/dist/GameButton.d.ts.map +1 -1
  12. package/dist/GameButton.js +16 -67
  13. package/dist/GameButton.js.map +1 -1
  14. package/dist/GameConfirm.d.ts +1 -1
  15. package/dist/GameConfirm.d.ts.map +1 -1
  16. package/dist/GameConfirm.js +1 -7
  17. package/dist/GameConfirm.js.map +1 -1
  18. package/dist/GameModal.d.ts +3 -2
  19. package/dist/GameModal.d.ts.map +1 -1
  20. package/dist/GameModal.js +3 -40
  21. package/dist/GameModal.js.map +1 -1
  22. package/dist/GameOverScreen.d.ts +3 -2
  23. package/dist/GameOverScreen.d.ts.map +1 -1
  24. package/dist/GameOverScreen.js +3 -37
  25. package/dist/GameOverScreen.js.map +1 -1
  26. package/dist/GameResultScreen.d.ts +41 -0
  27. package/dist/GameResultScreen.d.ts.map +1 -0
  28. package/dist/GameResultScreen.js +25 -0
  29. package/dist/GameResultScreen.js.map +1 -0
  30. package/dist/GameShell.d.ts +3 -0
  31. package/dist/GameShell.d.ts.map +1 -1
  32. package/dist/GameShell.js +4 -43
  33. package/dist/GameShell.js.map +1 -1
  34. package/dist/GameTextSizeToggle.d.ts +1 -1
  35. package/dist/GameTextSizeToggle.d.ts.map +1 -1
  36. package/dist/GameTextSizeToggle.js +1 -16
  37. package/dist/GameTextSizeToggle.js.map +1 -1
  38. package/dist/GameThemeToggle.d.ts +1 -1
  39. package/dist/GameThemeToggle.d.ts.map +1 -1
  40. package/dist/GameThemeToggle.js +44 -21
  41. package/dist/GameThemeToggle.js.map +1 -1
  42. package/dist/GameTopbar.d.ts +12 -7
  43. package/dist/GameTopbar.d.ts.map +1 -1
  44. package/dist/GameTopbar.js +54 -188
  45. package/dist/GameTopbar.js.map +1 -1
  46. package/dist/GameTopbar.module.css +201 -0
  47. package/dist/Leaderboard.d.ts.map +1 -1
  48. package/dist/Leaderboard.js +2 -5
  49. package/dist/Leaderboard.js.map +1 -1
  50. package/dist/StartScreen.d.ts +41 -0
  51. package/dist/StartScreen.d.ts.map +1 -0
  52. package/dist/StartScreen.js +30 -0
  53. package/dist/StartScreen.js.map +1 -0
  54. package/dist/feel/ScorePopup.d.ts +20 -0
  55. package/dist/feel/ScorePopup.d.ts.map +1 -0
  56. package/dist/feel/ScorePopup.js +51 -0
  57. package/dist/feel/ScorePopup.js.map +1 -0
  58. package/dist/feel/easing.d.ts +14 -0
  59. package/dist/feel/easing.d.ts.map +1 -0
  60. package/dist/feel/easing.js +12 -0
  61. package/dist/feel/easing.js.map +1 -0
  62. package/dist/feel/haptics.d.ts +19 -0
  63. package/dist/feel/haptics.d.ts.map +1 -0
  64. package/dist/feel/haptics.js +24 -0
  65. package/dist/feel/haptics.js.map +1 -0
  66. package/dist/feel/index.d.ts +17 -0
  67. package/dist/feel/index.d.ts.map +1 -0
  68. package/dist/feel/index.js +17 -0
  69. package/dist/feel/index.js.map +1 -0
  70. package/dist/feel/particles.d.ts +46 -0
  71. package/dist/feel/particles.d.ts.map +1 -0
  72. package/dist/feel/particles.js +61 -0
  73. package/dist/feel/particles.js.map +1 -0
  74. package/dist/feel/reducedMotion.d.ts +6 -0
  75. package/dist/feel/reducedMotion.d.ts.map +1 -0
  76. package/dist/feel/reducedMotion.js +10 -0
  77. package/dist/feel/reducedMotion.js.map +1 -0
  78. package/dist/feel/shake.d.ts +6 -0
  79. package/dist/feel/shake.d.ts.map +1 -0
  80. package/dist/feel/shake.js +17 -0
  81. package/dist/feel/shake.js.map +1 -0
  82. package/dist/feel/tween.d.ts +9 -0
  83. package/dist/feel/tween.d.ts.map +1 -0
  84. package/dist/feel/tween.js +15 -0
  85. package/dist/feel/tween.js.map +1 -0
  86. package/dist/feel/useHaptics.d.ts +12 -0
  87. package/dist/feel/useHaptics.d.ts.map +1 -0
  88. package/dist/feel/useHaptics.js +16 -0
  89. package/dist/feel/useHaptics.js.map +1 -0
  90. package/dist/feel/useParticles.d.ts +18 -0
  91. package/dist/feel/useParticles.d.ts.map +1 -0
  92. package/dist/feel/useParticles.js +79 -0
  93. package/dist/feel/useParticles.js.map +1 -0
  94. package/dist/feel/useShake.d.ts +14 -0
  95. package/dist/feel/useShake.d.ts.map +1 -0
  96. package/dist/feel/useShake.js +17 -0
  97. package/dist/feel/useShake.js.map +1 -0
  98. package/dist/index.d.ts +5 -2
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +14 -2
  101. package/dist/index.js.map +1 -1
  102. package/dist/theme.css +93 -0
  103. package/dist/tokens.d.ts +112 -0
  104. package/dist/tokens.d.ts.map +1 -0
  105. package/dist/tokens.js +106 -0
  106. package/dist/tokens.js.map +1 -0
  107. package/dist/useGameSounds.d.ts.map +1 -1
  108. package/dist/useGameSounds.js +37 -15
  109. package/dist/useGameSounds.js.map +1 -1
  110. package/dist/useLeaderboard.d.ts +8 -1
  111. package/dist/useLeaderboard.d.ts.map +1 -1
  112. package/dist/useLeaderboard.js +21 -11
  113. package/dist/useLeaderboard.js.map +1 -1
  114. package/package.json +23 -7
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.19.0
6
6
 
7
7
  ## Installation
8
8
 
@@ -14,6 +14,50 @@ pnpm add @freegamestore/games
14
14
 
15
15
  Peer dependency: React 19+.
16
16
 
17
+ ## Styling (Tailwind 4 on tokens)
18
+
19
+ The components are styled with **Tailwind utilities on the platform tokens**
20
+ ([ADR-001](https://github.com/freegamestore-online/platform/blob/main/docs/adr/001-tailwind-on-fgs-tokens.md)).
21
+ The package ships class names, not CSS, so **your game compiles them**. In the
22
+ game's `web/src/index.css`, directly after `@import "tailwindcss"`:
23
+
24
+ ```css
25
+ @import "tailwindcss";
26
+ @import "@freegamestore/games/theme.css";
27
+ @source "../node_modules/@freegamestore/games/dist";
28
+
29
+ @theme inline {
30
+ --color-paper: var(--paper);
31
+ --color-panel: var(--panel);
32
+ --color-ink: var(--ink);
33
+ --color-muted: var(--muted);
34
+ --color-line: var(--line);
35
+ --color-accent: var(--accent);
36
+ --color-danger: var(--danger);
37
+ --font-display: "Fraunces", serif;
38
+ }
39
+ ```
40
+
41
+ - `@source` makes Tailwind scan this package's `dist` for the classes the
42
+ components use. Without it the SDK renders unstyled.
43
+ - `theme.css` adds what the components need beyond the token mapping: the
44
+ `animate-fgs-*` animations and their keyframes (it replaces the CSS the
45
+ components used to inject), `accent-ink`, `accent-soft` and the `fgs` radius
46
+ and text sizes. Durations and easings read the `--motion-*` tokens.
47
+ - The `@theme inline` block is the token mapping every `template-game-*` carries.
48
+ A game on the templates already has it.
49
+ - Floating UI (`GameModal`, `GameConfirm`, `StartScreen`, `GameResultScreen`)
50
+ sits on the opaque `bg-panel` surface. Only the scrim behind it is translucent.
51
+ - Nothing is injected at runtime. The `*_CSS` strings (`CONFETTI_CSS`,
52
+ `SCORE_POP_CSS`, `RESULT_SCREEN_CSS`, `START_SCREEN_CSS`) are removed in 0.19.0;
53
+ `GAME_TOKENS_CSS` stays as the documented token helper.
54
+ - Inline `style` remains only for values computed at runtime (confetti piece
55
+ positions, score-popup coordinates, stagger indexes), each marked
56
+ `// Dynamic: …`. `src/styling.test.ts` fails on anything else.
57
+ - Hooks for tests and overrides are `data-fgs="…"` attributes
58
+ (`result`, `confetti`, `start`, `start-artwork`, `score-popup`). The old
59
+ `fgs-*` class names are gone.
60
+
17
61
  ## Quick Start
18
62
 
19
63
  ```tsx
@@ -21,7 +65,7 @@ import { GameShell, GameTopbar } from '@freegamestore/games'
21
65
 
22
66
  export default function App() {
23
67
  return (
24
- <GameShell topbar={<GameTopbar title="Chess" score={42} />}>
68
+ <GameShell topbar={<GameTopbar title="Chess" />}>
25
69
  {/* your game canvas / DOM */}
26
70
  </GameShell>
27
71
  )
@@ -35,7 +79,7 @@ export default function App() {
35
79
  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
80
 
37
81
  ```tsx
38
- <GameShell topbar={<GameTopbar title="Tetris" score={42} />}>
82
+ <GameShell topbar={<GameTopbar title="Tetris" />}>
39
83
  <MyGame />
40
84
  </GameShell>
41
85
  ```
@@ -50,8 +94,11 @@ Root layout wrapper. Locks the game to `100svh`, prevents document scroll, disab
50
94
  The single allowed topbar. Same font, padding, and color tokens across every game.
51
95
 
52
96
  ```tsx
53
- // Simple: just a score
54
- <GameTopbar title="Tetris" score={42} />
97
+ // Simple: just the title. The score pill is hidden by default (#348)
98
+ <GameTopbar title="Tetris" />
99
+
100
+ // Opt in to the live score pill
101
+ <GameTopbar title="Tetris" score={42} showScore />
55
102
 
56
103
  // Custom stats + play/pause controls
57
104
  <GameTopbar
@@ -71,8 +118,9 @@ The single allowed topbar. Same font, padding, and color tokens across every gam
71
118
  | Prop | Type | Description |
72
119
  |------|------|-------------|
73
120
  | `title` | `string?` | Game name, left side |
74
- | `score` | `number?` | Convenience for single-score games |
75
- | `stats` | `GameTopbarStat[]?` | Custom stat lineup (replaces `score`) |
121
+ | `score` | `number?` | Live score, shown only when `showScore` is set |
122
+ | `showScore` | `boolean?` | Show the live score pill. Off by default (#348) |
123
+ | `stats` | `GameTopbarStat[]?` | Custom stat lineup (lives, level, time) |
76
124
  | `actions` | `ReactNode?` | Right-side buttons (max 2) |
77
125
  | `rules` | `ReactNode?` | Game instructions — shows info icon, opens overlay on tap |
78
126
  | `onPlayPause` | `() => void?` | Renders play/pause icon button |
@@ -84,7 +132,7 @@ The single allowed topbar. Same font, padding, and color tokens across every gam
84
132
  Sign-in / avatar widget for the topbar `actions` slot. Shows "Sign in" when signed out, avatar + dropdown when signed in.
85
133
 
86
134
  ```tsx
87
- <GameTopbar title="Chess" score={42} actions={<GameAuth />} />
135
+ <GameTopbar title="Chess" actions={<GameAuth />} />
88
136
  ```
89
137
 
90
138
  No props — uses `useAuth` internally.
@@ -149,6 +197,49 @@ Standard game-over overlay with score, high score, and play again button.
149
197
  | `onPlayAgain` | `() => void` | Play again handler |
150
198
  | `children` | `ReactNode?` | Extra content below the score |
151
199
 
200
+
201
+ ### GameResultScreen
202
+
203
+ 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.
204
+
205
+ ```tsx
206
+ <GameResultScreen
207
+ tone={won ? 'win' : 'lose'}
208
+ eyebrow="Level 4"
209
+ title={won ? 'Level 4 cleared!' : 'Game over'}
210
+ message={won ? undefined : 'Your tray filled up.'}
211
+ stats={[
212
+ { label: 'Score', value: score.toLocaleString() },
213
+ { label: 'Moves', value: moves },
214
+ ]}
215
+ badge={newBest ? 'New best!' : undefined}
216
+ action={won ? { label: 'Next level', onClick: next } : { label: 'Play again', onClick: restart }}
217
+ >
218
+ <Leaderboard {...leaderboard} />
219
+ </GameResultScreen>
220
+ ```
221
+
222
+ | Prop | Type | Description |
223
+ |------|------|-------------|
224
+ | `tone` | `'win' \| 'lose'` | `win` shows confetti and the accent title colour |
225
+ | `eyebrow` | `string?` | Small line above the title, such as the level name |
226
+ | `title` | `string` | The headline |
227
+ | `message` | `string?` | One line under the title |
228
+ | `stats` | `{ label, value }[]?` | Stat rows in a two-column grid. `value` may be a number |
229
+ | `badge` | `ReactNode?` | Highlight line under the stats, such as "New best!" |
230
+ | `action` | `{ label, onClick }` | The primary button. It is focused when the screen opens |
231
+ | `children` | `ReactNode?` | Extra content between the badge and the action |
232
+
233
+ The screen covers the viewport and has `role="dialog"`. Motion is the
234
+ `animate-fgs-*` utilities from `theme.css` (see [Styling](#styling-tailwind-4-on-tokens)),
235
+ which stop under `prefers-reduced-motion`, and the confetti is hidden for players
236
+ who ask for less motion. Its colours come from the platform tokens and its
237
+ durations from the `--motion-*` tokens in [TOKENS.md](./TOKENS.md), with the
238
+ light-theme fallbacks in `theme.css`.
239
+
240
+ `Confetti` is exported on its own, for a game that wants the celebration
241
+ somewhere else.
242
+
152
243
  ### GameConfirm
153
244
 
154
245
  Confirm/cancel dialog built on GameModal.
@@ -234,6 +325,13 @@ const { topScores, recentScores, submitScore, loading, refresh } = useLeaderboar
234
325
  const { ok, rank } = await submitScore(1200)
235
326
  ```
236
327
 
328
+ 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:
329
+
330
+ ```tsx
331
+ const today = new Date().toISOString().slice(0, 10)
332
+ const { topScores, submitScore } = useLeaderboard('tile-match', { period: today })
333
+ ```
334
+
237
335
  ### useGameSounds
238
336
 
239
337
  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,18 @@
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
+ export interface ConfettiProps {
5
+ /** Colours cycled across the pieces. Default: the accent and the status colours. */
6
+ colours?: readonly string[];
7
+ }
8
+ /**
9
+ * Falling confetti for a win. Each piece's position, drift, spin and timing
10
+ * come from its index, so the render is the same every time. Renders nothing
11
+ * under prefers-reduced-motion.
12
+ *
13
+ * The falling motion is the `animate-fgs-confetti` utility from the SDK's
14
+ * `theme.css`. Only the per-piece values below are inline, because they are
15
+ * computed from the piece's index.
16
+ */
17
+ export declare function Confetti({ colours }: ConfettiProps): React.JSX.Element | null;
18
+ //# 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;AASF,MAAM,WAAW,aAAa;IAC5B,oFAAoF;IACpF,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EAAE,OAA0B,EAAE,EAAE,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,IAAI,CA4BhG"}
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx } 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('danger', LIGHT_COLOURS.error),
13
+ ];
14
+ /** True when the player has asked for less motion. False on the server, where there is no window. */
15
+ function prefersReducedMotion() {
16
+ return typeof window !== 'undefined' && typeof window.matchMedia === 'function'
17
+ ? window.matchMedia('(prefers-reduced-motion: reduce)').matches
18
+ : false;
19
+ }
20
+ /**
21
+ * Falling confetti for a win. Each piece's position, drift, spin and timing
22
+ * come from its index, so the render is the same every time. Renders nothing
23
+ * under prefers-reduced-motion.
24
+ *
25
+ * The falling motion is the `animate-fgs-confetti` utility from the SDK's
26
+ * `theme.css`. Only the per-piece values below are inline, because they are
27
+ * computed from the piece's index.
28
+ */
29
+ export function Confetti({ colours = CONFETTI_COLOURS }) {
30
+ if (prefersReducedMotion())
31
+ return null;
32
+ return (_jsx("div", { "data-fgs": "confetti", className: "pointer-events-none absolute inset-0 z-0 overflow-hidden motion-reduce:hidden", "aria-hidden": "true", children: Array.from({ length: PIECES }, (_, i) => (_jsx("span", { "data-fgs": "confetti-piece", className: "absolute -top-3 left-(--x) h-[0.8rem] w-2 animate-fgs-confetti rounded-[2px] bg-(--colour) nth-[3n]:size-[0.6rem] nth-[3n]:rounded-full",
33
+ // Dynamic: position, drift, spin, timing and colour all come from the piece index.
34
+ style: {
35
+ '--x': `${(i * 37) % 100}%`,
36
+ '--drift': `${((i * 53) % 41) - 20}px`,
37
+ '--spin': `${((i * 97) % 720) - 360}deg`,
38
+ '--delay': `${(i % 7) * 70}ms`,
39
+ '--duration': `${1500 + (i % 5) * 200}ms`,
40
+ '--colour': colours[i % colours.length],
41
+ } }, i))) }));
42
+ }
43
+ //# 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,QAAQ,EAAE,aAAa,CAAC,KAAK,CAAC;CACjC,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;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ,CAAC,EAAE,OAAO,GAAG,gBAAgB,EAAiB;IACpE,IAAI,oBAAoB,EAAE;QAAE,OAAO,IAAI,CAAC;IACxC,OAAO,CACL,0BACW,UAAU,EACnB,SAAS,EAAC,+EAA+E,iBAC7E,MAAM,YAEjB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CACxC,2BAEW,gBAAgB,EACzB,SAAS,EAAC,yIAAyI;YACnJ,mFAAmF;YACnF,KAAK,EACH;gBACE,KAAK,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,GAAG,GAAG;gBAC3B,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,IAAI;gBACtC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,KAAK;gBACxC,SAAS,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI;gBAC9B,YAAY,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,IAAI;gBACzC,UAAU,EAAE,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;aACjB,IAZrB,CAAC,CAcN,CACH,CAAC,GACE,CACP,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"GameAuth.d.ts","sourceRoot":"","sources":["../src/GameAuth.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAIpC;;;;;;GAMG;AACH,wBAAgB,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,CAsK5C"}
1
+ {"version":3,"file":"GameAuth.d.ts","sourceRoot":"","sources":["../src/GameAuth.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAQpC;;;;;;GAMG;AACH,wBAAgB,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,CAsE5C"}
package/dist/GameAuth.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from 'react';
3
3
  import { useAuth } from './useAuth.js';
4
+ /** Shared by the sign-in and avatar buttons: bare, 44px minimum touch target. */
5
+ const BARE_BUTTON = 'm-0 inline-flex min-h-11 min-w-11 cursor-pointer touch-manipulation items-center justify-center border-0 bg-transparent p-0 [-webkit-tap-highlight-color:transparent]';
4
6
  /**
5
7
  * Sign-in / avatar widget for the GameTopbar `actions` slot.
6
8
  *
@@ -27,100 +29,16 @@ export function GameAuth() {
27
29
  if (loading)
28
30
  return _jsx("div", {});
29
31
  if (user === null) {
30
- return (_jsx("button", { onClick: signIn, style: {
31
- background: 'none',
32
- border: 'none',
33
- cursor: 'pointer',
34
- fontFamily: '"Manrope", system-ui, sans-serif',
35
- fontSize: '0.7rem',
36
- fontWeight: 600,
37
- color: 'var(--muted, #6b7280)',
38
- padding: 0,
39
- margin: 0,
40
- // 44px minimum touch target
41
- minWidth: '44px',
42
- minHeight: '44px',
43
- display: 'inline-flex',
44
- alignItems: 'center',
45
- justifyContent: 'center',
46
- WebkitTapHighlightColor: 'transparent',
47
- touchAction: 'manipulation',
48
- }, children: "Sign in" }));
32
+ return (_jsx("button", { onClick: signIn, className: `${BARE_BUTTON} text-[0.7rem] font-semibold text-muted`, children: "Sign in" }));
49
33
  }
50
34
  const firstName = user.name.split(' ')[0] ?? user.name;
51
35
  // Google accounts without a profile picture have an empty avatar URL;
52
36
  // render an initial-letter circle instead of a broken <img>.
53
37
  const avatarUrl = user.avatar?.trim();
54
38
  const initial = (firstName.charAt(0) || '?').toUpperCase();
55
- return (_jsxs("div", { ref: ref, style: { position: 'relative' }, children: [_jsxs("button", { onClick: () => setOpen((v) => !v), style: {
56
- background: 'none',
57
- border: 'none',
58
- cursor: 'pointer',
59
- display: 'inline-flex',
60
- alignItems: 'center',
61
- gap: '0.35rem',
62
- padding: 0,
63
- margin: 0,
64
- minWidth: '44px',
65
- minHeight: '44px',
66
- justifyContent: 'center',
67
- WebkitTapHighlightColor: 'transparent',
68
- touchAction: 'manipulation',
69
- }, children: [avatarUrl ? (_jsx("img", { src: avatarUrl, alt: "", width: 24, height: 24, style: {
70
- width: '24px',
71
- height: '24px',
72
- borderRadius: '50%',
73
- objectFit: 'cover',
74
- flexShrink: 0,
75
- } })) : (_jsx("span", { "aria-hidden": "true", style: {
76
- width: '24px',
77
- height: '24px',
78
- borderRadius: '50%',
79
- flexShrink: 0,
80
- display: 'inline-flex',
81
- alignItems: 'center',
82
- justifyContent: 'center',
83
- background: 'var(--accent, #6b7280)',
84
- color: '#fff',
85
- fontFamily: '"Manrope", system-ui, sans-serif',
86
- fontSize: '0.65rem',
87
- fontWeight: 700,
88
- lineHeight: 1,
89
- }, children: initial })), _jsx("span", { style: {
90
- fontFamily: '"Manrope", system-ui, sans-serif',
91
- fontSize: '0.7rem',
92
- fontWeight: 600,
93
- color: 'var(--ink, #1a1a1a)',
94
- whiteSpace: 'nowrap',
95
- }, children: firstName })] }), open && (_jsx("div", { style: {
96
- position: 'absolute',
97
- top: '100%',
98
- right: 0,
99
- marginTop: '0.25rem',
100
- background: 'var(--panel, #fff)',
101
- border: '1px solid var(--line, #e5e5e5)',
102
- borderRadius: '0.5rem',
103
- boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
104
- zIndex: 100,
105
- minWidth: '7rem',
106
- }, children: _jsx("button", { onClick: () => {
39
+ return (_jsxs("div", { ref: ref, className: "relative", children: [_jsxs("button", { onClick: () => setOpen((v) => !v), className: `${BARE_BUTTON} gap-[0.35rem]`, children: [avatarUrl ? (_jsx("img", { src: avatarUrl, alt: "", width: 24, height: 24, className: "size-6 shrink-0 rounded-full object-cover" })) : (_jsx("span", { "aria-hidden": "true", className: "inline-flex size-6 shrink-0 items-center justify-center rounded-full bg-accent text-[0.65rem] font-bold leading-none text-white", children: initial })), _jsx("span", { className: "whitespace-nowrap text-[0.7rem] font-semibold text-ink", children: firstName })] }), open && (_jsx("div", { className: "absolute right-0 top-full z-[100] mt-1 min-w-28 rounded-lg border border-line bg-panel shadow-[0_4px_12px_rgb(0_0_0/0.1)]", children: _jsx("button", { onClick: () => {
107
40
  setOpen(false);
108
41
  signOut();
109
- }, style: {
110
- display: 'flex',
111
- alignItems: 'center',
112
- width: '100%',
113
- background: 'none',
114
- border: 'none',
115
- cursor: 'pointer',
116
- fontFamily: '"Manrope", system-ui, sans-serif',
117
- fontSize: '0.7rem',
118
- fontWeight: 600,
119
- color: 'var(--ink, #1a1a1a)',
120
- padding: '0.5rem 0.75rem',
121
- minHeight: '44px',
122
- WebkitTapHighlightColor: 'transparent',
123
- touchAction: 'manipulation',
124
- }, children: "Sign out" }) }))] }));
42
+ }, className: "flex min-h-11 w-full cursor-pointer touch-manipulation items-center border-0 bg-transparent px-3 py-2 text-[0.7rem] font-semibold text-ink [-webkit-tap-highlight-color:transparent]", children: "Sign out" }) }))] }));
125
43
  }
126
44
  //# sourceMappingURL=GameAuth.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"GameAuth.js","sourceRoot":"","sources":["../src/GameAuth.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"GameAuth.js","sourceRoot":"","sources":["../src/GameAuth.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,iFAAiF;AACjF,MAAM,WAAW,GACf,uKAAuK,CAAC;AAE1K;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ;IACtB,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC;IACrD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAEzC,uCAAuC;IACvC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,MAAM,OAAO,GAAG,CAAC,CAAa,EAAE,EAAE;YAChC,IAAI,GAAG,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAc,CAAC,EAAE,CAAC;gBAC3D,OAAO,CAAC,KAAK,CAAC,CAAC;YACjB,CAAC;QACH,CAAC,CAAC;QACF,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QACxD,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAC1E,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,IAAI,OAAO;QAAE,OAAO,eAAO,CAAC;IAE5B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO,CACL,iBAAQ,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,WAAW,yCAAyC,wBAElF,CACV,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC;IACvD,sEAAsE;IACtE,6DAA6D;IAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;IACtC,MAAM,OAAO,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAE3D,OAAO,CACL,eAAK,GAAG,EAAE,GAAG,EAAE,SAAS,EAAC,UAAU,aACjC,kBAAQ,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,WAAW,gBAAgB,aACjF,SAAS,CAAC,CAAC,CAAC,CACX,cACE,GAAG,EAAE,SAAS,EACd,GAAG,EAAC,EAAE,EACN,KAAK,EAAE,EAAE,EACT,MAAM,EAAE,EAAE,EACV,SAAS,EAAC,2CAA2C,GACrD,CACH,CAAC,CAAC,CAAC,CACF,8BACc,MAAM,EAClB,SAAS,EAAC,iIAAiI,YAE1I,OAAO,GACH,CACR,EACD,eAAM,SAAS,EAAC,wDAAwD,YAAE,SAAS,GAAQ,IACpF,EAER,IAAI,IAAI,CACP,cAAK,SAAS,EAAC,2HAA2H,YACxI,iBACE,OAAO,EAAE,GAAG,EAAE;wBACZ,OAAO,CAAC,KAAK,CAAC,CAAC;wBACf,OAAO,EAAE,CAAC;oBACZ,CAAC,EACD,SAAS,EAAC,sLAAsL,yBAGzL,GACL,CACP,IACG,CACP,CAAC;AACJ,CAAC"}
@@ -15,7 +15,8 @@ export interface GameButtonProps {
15
15
  }
16
16
  /**
17
17
  * Prescribed game button. Touch-friendly (min 44px target), brand-consistent
18
- * styling. Three variants, three sizes — all opinionated, nothing custom.
18
+ * styling. Four variants, three sizes — all opinionated, nothing custom.
19
+ * Utilities on the platform tokens; the press feedback is `active:scale`.
19
20
  */
20
21
  export declare function GameButton({ children, variant, size, onClick, disabled, block, }: GameButtonProps): React.JSX.Element;
21
22
  //# sourceMappingURL=GameButton.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GameButton.d.ts","sourceRoot":"","sources":["../src/GameButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,QAAQ,CAAC;AAC7E,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,SAAS,CAAC;IACpB,wCAAwC;IACxC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,yEAAyE;IACzE,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kBAAkB;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA0BD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,OAAmB,EACnB,IAAW,EACX,OAAO,EACP,QAAgB,EAChB,KAAa,GACd,EAAE,eAAe,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA8DrC"}
1
+ {"version":3,"file":"GameButton.d.ts","sourceRoot":"","sources":["../src/GameButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,QAAQ,CAAC;AAC7E,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,SAAS,CAAC;IACpB,wCAAwC;IACxC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,yEAAyE;IACzE,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kBAAkB;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAeD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,OAAmB,EACnB,IAAW,EACX,OAAO,EACP,QAAgB,EAChB,KAAa,GACd,EAAE,eAAe,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAerC"}
@@ -1,76 +1,25 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  const SIZE = {
3
- sm: {
4
- minHeight: '2.75rem',
5
- padding: '0.5rem 1rem',
6
- fontSize: '0.85rem',
7
- borderRadius: '0.625rem',
8
- },
9
- md: {
10
- minHeight: '3rem',
11
- padding: '0.625rem 1.25rem',
12
- fontSize: '0.9rem',
13
- borderRadius: '0.75rem',
14
- },
15
- lg: {
16
- minHeight: '3.5rem',
17
- padding: '0.75rem 1.75rem',
18
- fontSize: '1rem',
19
- borderRadius: '0.875rem',
20
- },
3
+ sm: 'min-h-11 px-4 py-2 text-[0.85rem] rounded-[0.625rem]',
4
+ md: 'min-h-12 px-5 py-[0.625rem] text-[0.9rem] rounded-xl',
5
+ lg: 'min-h-14 px-7 py-3 text-base rounded-[0.875rem]',
6
+ };
7
+ const VARIANT = {
8
+ primary: 'bg-accent text-white',
9
+ secondary: 'bg-panel text-ink ring-1 ring-inset ring-line',
10
+ ghost: 'bg-transparent text-muted',
11
+ danger: 'bg-danger text-white',
21
12
  };
22
13
  /**
23
14
  * Prescribed game button. Touch-friendly (min 44px target), brand-consistent
24
- * styling. Three variants, three sizes — all opinionated, nothing custom.
15
+ * styling. Four variants, three sizes — all opinionated, nothing custom.
16
+ * Utilities on the platform tokens; the press feedback is `active:scale`.
25
17
  */
26
18
  export function GameButton({ children, variant = 'primary', size = 'md', onClick, disabled = false, block = false, }) {
27
- const s = SIZE[size];
28
- const base = {
29
- display: block ? 'flex' : 'inline-flex',
30
- width: block ? '100%' : undefined,
31
- alignItems: 'center',
32
- justifyContent: 'center',
33
- gap: '0.5rem',
34
- minHeight: s.minHeight,
35
- padding: s.padding,
36
- fontSize: s.fontSize,
37
- fontFamily: '"Manrope", system-ui, sans-serif',
38
- fontWeight: 700,
39
- lineHeight: 1,
40
- borderRadius: s.borderRadius,
41
- border: 'none',
42
- cursor: disabled ? 'default' : 'pointer',
43
- opacity: disabled ? 0.4 : 1,
44
- transition: 'transform 120ms ease, opacity 120ms ease',
45
- WebkitTapHighlightColor: 'transparent',
46
- touchAction: 'manipulation',
47
- };
48
- const variantStyles = {
49
- primary: {
50
- background: 'var(--accent, #10b981)',
51
- color: '#fff',
52
- },
53
- secondary: {
54
- background: 'var(--panel, #f5f3f0)',
55
- color: 'var(--ink, #1a1a1a)',
56
- boxShadow: 'inset 0 0 0 1px var(--line, #e5e5e5)',
57
- },
58
- ghost: {
59
- background: 'transparent',
60
- color: 'var(--muted, #6b7280)',
61
- },
62
- danger: {
63
- background: '#dc2626',
64
- color: '#ffffff',
65
- },
66
- };
67
- return (_jsx("button", { style: { ...base, ...variantStyles[variant] }, onClick: disabled ? undefined : onClick, disabled: disabled, onPointerDown: (e) => {
68
- if (!disabled)
69
- e.currentTarget.style.transform = 'scale(0.96)';
70
- }, onPointerUp: (e) => {
71
- e.currentTarget.style.transform = '';
72
- }, onPointerLeave: (e) => {
73
- e.currentTarget.style.transform = '';
74
- }, children: children }));
19
+ const layout = block ? 'flex w-full' : 'inline-flex';
20
+ const state = disabled
21
+ ? 'cursor-default opacity-40'
22
+ : 'cursor-pointer enabled:active:scale-[0.96]';
23
+ return (_jsx("button", { className: `${layout} ${SIZE[size]} ${VARIANT[variant]} ${state} touch-manipulation items-center justify-center gap-2 border-0 font-bold leading-none transition-[transform,opacity] duration-[120ms] [-webkit-tap-highlight-color:transparent]`, onClick: disabled ? undefined : onClick, disabled: disabled, children: children }));
75
24
  }
76
25
  //# sourceMappingURL=GameButton.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"GameButton.js","sourceRoot":"","sources":["../src/GameButton.tsx"],"names":[],"mappings":";AAkBA,MAAM,IAAI,GAGN;IACF,EAAE,EAAE;QACF,SAAS,EAAE,SAAS;QACpB,OAAO,EAAE,aAAa;QACtB,QAAQ,EAAE,SAAS;QACnB,YAAY,EAAE,UAAU;KACzB;IACD,EAAE,EAAE;QACF,SAAS,EAAE,MAAM;QACjB,OAAO,EAAE,kBAAkB;QAC3B,QAAQ,EAAE,QAAQ;QAClB,YAAY,EAAE,SAAS;KACxB;IACD,EAAE,EAAE;QACF,SAAS,EAAE,QAAQ;QACnB,OAAO,EAAE,iBAAiB;QAC1B,QAAQ,EAAE,MAAM;QAChB,YAAY,EAAE,UAAU;KACzB;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,EACzB,QAAQ,EACR,OAAO,GAAG,SAAS,EACnB,IAAI,GAAG,IAAI,EACX,OAAO,EACP,QAAQ,GAAG,KAAK,EAChB,KAAK,GAAG,KAAK,GACG;IAChB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IAErB,MAAM,IAAI,GAAwB;QAChC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa;QACvC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;QACjC,UAAU,EAAE,QAAQ;QACpB,cAAc,EAAE,QAAQ;QACxB,GAAG,EAAE,QAAQ;QACb,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,UAAU,EAAE,kCAAkC;QAC9C,UAAU,EAAE,GAAG;QACf,UAAU,EAAE,CAAC;QACb,YAAY,EAAE,CAAC,CAAC,YAAY;QAC5B,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;QACxC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC3B,UAAU,EAAE,0CAA0C;QACtD,uBAAuB,EAAE,aAAa;QACtC,WAAW,EAAE,cAAc;KAC5B,CAAC;IAEF,MAAM,aAAa,GAAmD;QACpE,OAAO,EAAE;YACP,UAAU,EAAE,wBAAwB;YACpC,KAAK,EAAE,MAAM;SACd;QACD,SAAS,EAAE;YACT,UAAU,EAAE,uBAAuB;YACnC,KAAK,EAAE,qBAAqB;YAC5B,SAAS,EAAE,sCAAsC;SAClD;QACD,KAAK,EAAE;YACL,UAAU,EAAE,aAAa;YACzB,KAAK,EAAE,uBAAuB;SAC/B;QACD,MAAM,EAAE;YACN,UAAU,EAAE,SAAS;YACrB,KAAK,EAAE,SAAS;SACjB;KACF,CAAC;IAEF,OAAO,CACL,iBACE,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,aAAa,CAAC,OAAO,CAAC,EAAE,EAC7C,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,EACvC,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE;YACnB,IAAI,CAAC,QAAQ;gBAAG,CAAC,CAAC,aAA6B,CAAC,KAAK,CAAC,SAAS,GAAG,aAAa,CAAC;QAClF,CAAC,EACD,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE;YAChB,CAAC,CAAC,aAA6B,CAAC,KAAK,CAAC,SAAS,GAAG,EAAE,CAAC;QACxD,CAAC,EACD,cAAc,EAAE,CAAC,CAAC,EAAE,EAAE;YACnB,CAAC,CAAC,aAA6B,CAAC,KAAK,CAAC,SAAS,GAAG,EAAE,CAAC;QACxD,CAAC,YAEA,QAAQ,GACF,CACV,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"GameButton.js","sourceRoot":"","sources":["../src/GameButton.tsx"],"names":[],"mappings":";AAkBA,MAAM,IAAI,GAAmC;IAC3C,EAAE,EAAE,sDAAsD;IAC1D,EAAE,EAAE,sDAAsD;IAC1D,EAAE,EAAE,iDAAiD;CACtD,CAAC;AAEF,MAAM,OAAO,GAAsC;IACjD,OAAO,EAAE,sBAAsB;IAC/B,SAAS,EAAE,+CAA+C;IAC1D,KAAK,EAAE,2BAA2B;IAClC,MAAM,EAAE,sBAAsB;CAC/B,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,EACzB,QAAQ,EACR,OAAO,GAAG,SAAS,EACnB,IAAI,GAAG,IAAI,EACX,OAAO,EACP,QAAQ,GAAG,KAAK,EAChB,KAAK,GAAG,KAAK,GACG;IAChB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC;IACrD,MAAM,KAAK,GAAG,QAAQ;QACpB,CAAC,CAAC,2BAA2B;QAC7B,CAAC,CAAC,4CAA4C,CAAC;IAEjD,OAAO,CACL,iBACE,SAAS,EAAE,GAAG,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,iLAAiL,EAChP,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,EACvC,QAAQ,EAAE,QAAQ,YAEjB,QAAQ,GACF,CACV,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