@freegamestore/games

中文文档
0.20.3发布于 2026/11/07MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 2495

Shared React UI primitives for games on FreeGameStore — GameShell, GameTopbar, etc. Brand-consistent, scroll-free.

原始使用文档
文档目录22 节
  1. Installation
  2. Styling (Tailwind 4 on tokens)
  3. Quick Start
  4. Components
  5. GameShell
  6. GameTopbar
  7. GameAuth
  8. GameButton
  9. GameModal
  10. GameOverScreen
  11. GameResultScreen
  12. GameConfirm
  13. GameThemeToggle
  14. GameTextSizeToggle
  15. Leaderboard
  16. Hooks
  17. useAuth
  18. useLeaderboard
  19. useGameSounds
  20. useSound
  21. Exports
  22. License

@freegamestore/games

Shared React UI primitives for games on freegamestore.online. Brand-consistent layout, scroll-free viewport lock, touch-friendly controls, and synthesized sound effects.

Version: 0.19.0

Installation

npm i @freegamestore/games
# or
pnpm add @freegamestore/games

Peer dependency: React 19+.

Styling (Tailwind 4 on tokens)

The components are styled with Tailwind utilities on the platform tokens (ADR-001). The package ships class names, not CSS, so your game compiles them. In the game's web/src/index.css, directly after @import "tailwindcss":

@import "tailwindcss";
@import "@freegamestore/games/theme.css";
@source "../node_modules/@freegamestore/games/dist";

@theme inline {
  --color-paper: var(--paper);
  --color-panel: var(--panel);
  --color-ink: var(--ink);
  --color-muted: var(--muted);
  --color-line: var(--line);
  --color-accent: var(--accent);
  --color-danger: var(--danger);
  --font-display: "Fraunces", serif;
}
  • @source makes Tailwind scan this package's dist for the classes the components use. Without it the SDK renders unstyled.
  • theme.css adds what the components need beyond the token mapping: the animate-fgs-* animations and their keyframes (it replaces the CSS the components used to inject), accent-ink, accent-soft and the fgs radius and text sizes. Durations and easings read the --motion-* tokens.
  • The @theme inline block is the token mapping every template-game-* carries. A game on the templates already has it.
  • Floating UI (GameModal, GameConfirm, StartScreen, GameResultScreen) sits on the opaque bg-panel surface. Only the scrim behind it is translucent.
  • Nothing is injected at runtime. The *_CSS strings (CONFETTI_CSS, SCORE_POP_CSS, RESULT_SCREEN_CSS, START_SCREEN_CSS) are removed in 0.19.0; GAME_TOKENS_CSS stays as the documented token helper.
  • Inline style remains only for values computed at runtime (confetti piece positions, score-popup coordinates, stagger indexes), each marked // Dynamic: …. src/styling.test.ts fails on anything else.
  • Hooks for tests and overrides are data-fgs="…" attributes (result, confetti, start, start-artwork, score-popup). The old fgs-* class names are gone.

Quick Start

import { GameShell, GameTopbar } from '@freegamestore/games'

export default function App() {
  return (
    <GameShell topbar={<GameTopbar title="Chess" />}>
      {/* your game canvas / DOM */}
    </GameShell>
  )
}

Components

GameShell

Root layout wrapper. Locks the game to 100svh, prevents document scroll, disables text selection and touch callout. Every game wraps its content in this.

<GameShell topbar={<GameTopbar title="Tetris" />}>
  <MyGame />
</GameShell>
PropTypeDescription
topbarReactNode?Topbar element (typically <GameTopbar />)
childrenReactNodeGame content — fills remaining viewport

GameTopbar

The single allowed topbar. Same font, padding, and color tokens across every game.

// Simple: just the title. The score pill is hidden by default (#348)
<GameTopbar title="Tetris" />

// Opt in to the live score pill
<GameTopbar title="Tetris" score={42} showScore />

// Custom stats + play/pause controls
<GameTopbar
  title="Pac-Man"
  stats={[
    { label: 'Score', value: 1200, accent: true },
    { label: 'Lives', value: 3 },
    { label: 'Level', value: 5 },
  ]}
  rules={<p>Eat all the dots. Avoid ghosts.</p>}
  onPlayPause={togglePause}
  paused={isPaused}
  onRestart={restart}
/>
PropTypeDescription
titlestring?Game name, left side
scorenumber?Live score, shown only when showScore is set
showScoreboolean?Show the live score pill. Off by default (#348)
statsGameTopbarStat[]?Custom stat lineup (lives, level, time)
actionsReactNode?Right-side buttons (max 2)
rulesReactNode?Game instructions — shows info icon, opens overlay on tap
onPlayPause() => void?Renders play/pause icon button
pausedboolean?Controls play/pause icon state
onRestart() => void?Renders restart icon button

GameAuth

Sign-in / avatar widget for the topbar actions slot. Shows "Sign in" when signed out, avatar + dropdown when signed in.

<GameTopbar title="Chess" actions={<GameAuth />} />

No props — uses useAuth internally.

GameButton

Touch-friendly button. Min 44px touch target. Four variants, three sizes.

<GameButton variant="primary" size="lg" onClick={start}>Play Again</GameButton>
<GameButton variant="secondary" size="sm" onClick={undo}>Undo</GameButton>
<GameButton variant="ghost" size="sm" onClick={flip}>Flip</GameButton>
<GameButton variant="danger" onClick={quit}>Quit</GameButton>
PropTypeDefaultDescription
childrenReactNode(required)Button content
variant'primary' | 'secondary' | 'ghost' | 'danger''primary'Visual style
size'sm' | 'md' | 'lg''md'Touch-target size
onClick() => void?Click handler
disabledboolean?falseDisabled state
blockboolean?falseFull width

Sizes: sm (44px), md (48px), lg (56px).

GameModal

Fullscreen modal overlay. Closes on backdrop click or Escape.

<GameModal open={showSettings} onClose={() => setShowSettings(false)} title="Settings">
  <p>Game settings here</p>
</GameModal>
PropTypeDescription
openbooleanWhether the modal is visible
onClose() => voidCalled on backdrop click or Escape
titlestring?Header text
childrenReactNodeModal content

GameOverScreen

Standard game-over overlay with score, high score, and play again button.

<GameOverScreen
  score={1200}
  highScore={1500}
  onPlayAgain={restart}
>
  <Leaderboard {...leaderboard} />
</GameOverScreen>
PropTypeDescription
scorenumberFinal score
highScorenumber?Personal best (shows "New high score!" when beaten)
onPlayAgain() => voidPlay again handler
childrenReactNode?Extra content below the score

GameResultScreen

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.

<GameResultScreen
  tone={won ? 'win' : 'lose'}
  eyebrow="Level 4"
  title={won ? 'Level 4 cleared!' : 'Game over'}
  message={won ? undefined : 'Your tray filled up.'}
  stats={[
    { label: 'Score', value: score.toLocaleString() },
    { label: 'Moves', value: moves },
  ]}
  badge={newBest ? 'New best!' : undefined}
  action={won ? { label: 'Next level', onClick: next } : { label: 'Play again', onClick: restart }}
>
  <Leaderboard {...leaderboard} />
</GameResultScreen>
PropTypeDescription
tone'win' | 'lose'win shows confetti and the accent title colour
eyebrowstring?Small line above the title, such as the level name
titlestringThe headline
messagestring?One line under the title
stats{ label, value }[]?Stat rows in a two-column grid. value may be a number
badgeReactNode?Highlight line under the stats, such as "New best!"
action{ label, onClick }The primary button. It is focused when the screen opens
childrenReactNode?Extra content between the badge and the action

The screen covers the viewport and has role="dialog". Motion is the animate-fgs-* utilities from theme.css (see Styling), which stop under prefers-reduced-motion, and the confetti is hidden for players who ask for less motion. Its colours come from the platform tokens and its durations from the --motion-* tokens in TOKENS.md, with the light-theme fallbacks in theme.css.

Confetti is exported on its own, for a game that wants the celebration somewhere else.

GameConfirm

Confirm/cancel dialog built on GameModal.

<GameConfirm
  open={showQuit}
  title="Quit Game?"
  message="Your progress will be lost."
  onConfirm={quit}
  onCancel={() => setShowQuit(false)}
  variant="danger"
/>
PropTypeDefaultDescription
openbooleanWhether visible
titlestringDialog title
messagestringBody text
onConfirm() => voidConfirm handler
onCancel() => voidCancel handler
confirmLabelstring?'Confirm'Confirm button text
cancelLabelstring?'Cancel'Cancel button text
variant'default' | 'danger''default'Confirm button style

GameThemeToggle

Compact sun/moon theme toggle (28px). Cycles system/light/dark. Designed for the topbar actions slot.

<GameTopbar title="Chess" actions={<><GameThemeToggle /><GameTextSizeToggle /></>} />

No props.

GameTextSizeToggle

Compact text-size toggle (28px). Cycles default/large/small. Shows A/A+/A-. Designed for the topbar actions slot.

<GameTopbar title="Chess" actions={<GameTextSizeToggle />} />

No props.

Leaderboard

Tabbed leaderboard display (Top / Recent). Use with the useLeaderboard hook.

const { topScores, recentScores, loading } = useLeaderboard('chess')

<Leaderboard topScores={topScores} recentScores={recentScores} loading={loading} />
PropTypeDescription
topScoresLeaderboardEntry[]All-time top scores
recentScoresLeaderboardEntry[]Recent scores
loadingbooleanLoading state

Hooks

useAuth

Auth state for FreeGameStore (Google OAuth via auth.freegamestore.online).

const { user, loading, signIn, signOut } = useAuth()

Returns { user: User | null, loading: boolean, signIn: () => void, signOut: () => void }.

useLeaderboard

Fetch and submit scores to the platform leaderboard.

No leaderboard service runs today. The leaderboard worker was removed (#491), so boards are always empty and submitScore resolves { ok: false }. The hook and <Leaderboard> stay exported so existing games keep building; save high scores locally with localStorage.

const { topScores, recentScores, submitScore, loading, refresh } = useLeaderboard('tetris')

// Submit a score
const { ok, rank } = await submitScore(1200)

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:

const today = new Date().toISOString().slice(0, 10)
const { topScores, submitScore } = useLeaderboard('tile-match', { period: today })

useGameSounds

Synthesized sound effects via Web Audio API. Zero audio files. All sounds auto-respect the mute toggle.

const { playMove, playScore, playError, playGameOver, playLevelUp, playDrop, playClear, playTick } = useGameSounds()

// Play a sound (no-op when muted)
playScore()
SoundUse case
playMovePiece moved, card flipped, button pressed
playScoreScored a point, matched, correct answer
playErrorWrong answer, hit obstacle, lost life
playGameOverGame over — descending tones
playLevelUpLevel up / achievement — ascending arpeggio
playDropHard drop / thud
playClearLine clear / combo
playTickCountdown tick / timer warning

useSound

Low-level mute state from the SDK's SoundProvider. Games should check muted before playing any custom audio.

const { muted, toggle } = useSound()
if (!muted) myCustomAudio.play()

Exports

@freegamestore/games → GameShell, GameTopbar, GameAuth, GameButton, GameModal,
                        GameOverScreen, GameConfirm, GameThemeToggle,
                        GameTextSizeToggle, Leaderboard, useAuth, useLeaderboard,
                        useGameSounds, useSound

License

MIT

版本与发布记录

@freegamestore/games 共发布 26 个版本,首次发布于 2026/05/08;过去 12 个月发布了 26 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.20.3latest2026/11/078 天前
0.20.22026/11/078 天前
0.20.12026/11/078 天前
0.20.02026/11/069 天前
0.19.12026/11/0312 天前
0.19.02026/10/3115 天前

关于 @freegamestore/games 的常见问题

@freegamestore/games 是什么?

Shared React UI primitives for games on FreeGameStore — GameShell, GameTopbar, etc. Brand-consistent, scroll-free.

如何安装 @freegamestore/games?

在项目目录运行 npm install @freegamestore/games,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^19.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@freegamestore/games 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@freegamestore/games 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@freegamestore/games 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@freegamestore/games 可以商用吗?

@freegamestore/games 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@freegamestore/games 的下载量有多少?

根据 npm 官方下载统计,@freegamestore/games 在 2026/10/16 – 2026/11/14 这 30 天内被下载 2,495 次,日均约 83 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@freegamestore/games 还在维护吗?

最新版本 0.20.3 发布于 2026/11/07(8 天前);至今共发布 26 个版本,其中过去 12 个月发布了 26 个;首次发布于 2026/05/08。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。