OhMyGame
OhMyGame은 AI로 게임을 만들 수 있는 오픈소스 스튜디오로, 디자인·구축·플레이테스트·출판을 하나의 앱에서 모델 키와 함께 수행합니다
광고·계정 없이 조용한 퍼즐 게임, 정원·색상·수학·기억·미로·슬라이드 등
Quiet puzzle games for curious kids: twenty garden puzzles, an eight-colour theme studio, and no ads or accounts. Sudoku that explains why, memory, mazes, sliding tiles, and more — all playable in the
게임TypeScript · CSSGitHub @reed-soul온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기A garden of calm, ad-free puzzle games for children. Static-first monorepo; Sudoku is the first game.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A small garden of calm puzzle games for children — starting with Sudoku.
Every game here is static files. No accounts, no ads, no chat, no server, no telemetry. Puzzles are generated on the device the child is holding and never leave it. The product goal is narrow and deliberate: be a place where the answer to "can I play something?" is simply yes.
Live: puzzlebloom.taoliapp.com
Most games built for children are built to be returned to — a streak to protect, a chest to open, a friend's score to beat. Those mechanics work, which is the problem: they teach a child to keep playing rather than to finish.
A puzzle has the opposite shape. It ends, it is satisfying at the moment it is solved, and then there is nothing left to collect. Everything in this repo follows from that.
The other half is art direction. Cartoon creatures are not decoration here — they are the accessibility feature. Nine numerals are nine abstract squiggles to a five-year-old; nine faces with their own ears and horns are nine characters. The same board can be played with either.
| Numerals | Creatures | Hints that explain |
|---|---|---|
puzzlebloom/
├─ apps/web/ the site: router, pages, theme provider, styles
├─ games/sudoku/ the first game (React)
├─ packages/
│ ├─ sudoku-engine/ generator, solver, logical grading, teaching hints
│ ├─ theme-core/ theme model, built-in moods, contrast guard, share links
│ └─ ui/ locked icon set, buttons, segmented control, creature glyphs
├─ e2e/ Playwright: the seams the unit tests cannot reach
├─ docs/ spec, ADRs, screenshots
├─ wrangler.toml the Cloudflare Pages project (the live host)
└─ vercel.json the fallback host's config; Cloudflare reads
public/_headers natively
pnpm workspaces. No bundler plugins beyond React, no CSS framework, no state
library, no router dependency — the router is 90 lines in
apps/web/src/app/router.tsx.
pnpm install
pnpm dev # http://localhost:5173
pnpm lint # biome: lint and format, one tool, no plugin zoo
pnpm typecheck # tsc --noEmit across the app, the packages and the e2e specs
pnpm test # 189 engine tests + 42 palette guards
pnpm test:e2e # Playwright on Chromium and on WebKit as an iPhone
pnpm verify # all of the above, in the order CI runs them
The end-to-end suite needs both engines before its first run, and the WebKit one is not optional: the declared target is the current iPhone or iPad, and every browser on iOS is WebKit underneath.
pnpm exec playwright install chromium webkit
Node 26.12+ (.nvmrc; Node 26 is the current line and takes the LTS badge
from 24 in late 2026). The pnpm version is pinned in package.json, so a
laptop and CI use the same binary.
Playwright does not test the dev server. It builds, serves dist through
vite preview, and drives that — because the CSP, the worker chunk and the font
preloads only exist after a build, and those are half of what it is checking.
The engine is the only thing worth trusting. packages/sudoku-engine is a
port of HeyPuter/everything's Sudoku
(MIT — see THIRD_PARTY_NOTICES.md), kept verbatim
except for types. It guarantees two things the UI leans on:
techniques.js — which is why the hint button always has something true to
say, and never has to read the answer key.Generation runs in a Web Worker; Expert grids take a few hundred milliseconds and would otherwise freeze the tab.
Themes are ten colours and nothing else. Every tint, border and cell state
is a color-mix() of those ten, computed in CSS. That is why a theme a child
makes in the studio still looks designed rather than assembled — and why adding
a mood costs twelve lines.
Three of the ten exist because one colour cannot do three jobs:
| role | job | constraint |
|---|---|---|
brand | links, focus rings, the selected cell | 4.5:1 as text, so its chroma is capped |
accent | the loud colour: primary button, sun, bands | allowed to own ~26% of the screen |
sky | the wide decorative wash behind everything | no text on it, so its chroma is uncapped |
accentInk is the fourth and the one that unlocks the rest: a bright fill can
only carry a label if the label is allowed to be dark, and that pairing is
measured (9.7:1 on Meadow) rather than guessed. deriveAccentInk() computes it,
so the studio never asks a child to pick it.
The reasoning, the measurements and what is still unbuilt are in DESIGN-REVIEW-v0.2.md.
Themes are stored in localStorage and can be shared as a link: the whole
palette is packed into ?theme= as base64url, validated on arrival. No backend.
Nothing is fetched from anyone else. Baloo 2 and Nunito are self-hosted as
four woff2 variable subsets — 135 kB in total, 71 kB on first paint — with
font-display: swap and a preload for the two Latin faces. With no CDN, no font
host and no analytics, the CSP is 'self' for scripts, styles and fonts: there
is no third party left to allow. e2e/garden.spec.ts fails if a single request
leaves the origin.
Dark moods get their own cell tints. Mixing 7% white into a dark surface is
invisible, so :root[data-mood='dark'] raises the fill. See
tokens.css.
1–9 place, Backspace
clears, N notes, H hint, ⌘/Ctrl+Z undo.role="grid" of buttons.aria-live, and everything above collapses under
prefers-reduced-motion.prefers-reduced-motion.games/<id>/ with a package that exports a manifest and a component.apps/web/src/games/registry.ts.apps/web/src/App.tsx (lazy import).The landing page, the "growing" shelf and styling all follow automatically.
Keep the game's manifest out of the package index if the game is heavy — that
mistake pulls the whole game into the first paint (there is a comment about it
in registry.ts).
The build is ~400 kB of static files: any host that serves files works. It runs
on Cloudflare Pages, because the repo already speaks its dialect —
apps/web/public/_headers (the CSP, immutable caching) is read as-is — and
because a static bundle has no traffic to meter. The reasoning, with both
vendors' 2026 quotas side by side, is in
ADR-005.
Direct upload — what the live site does. The build happens locally or in CI and Pages only stores the result, so there is no build environment to configure:
pnpm deploy # build:seo + wrangler pages deploy + IndexNow ping
Git integration — connect the repo in the dashboard instead, and Pages builds on every push:
| Setting | Value |
|---|---|
| Build command | pnpm run build:seo |
| Build output directory | apps/web/dist |
| Root directory | leave at / (the pnpm filter builds the workspace) |
| Environment variable | PNPM_VERSION=12.15.0 |
Node comes from .nvmrc. The PNPM_VERSION line is not optional in this
path: the v3 build image ships pnpm 10.11.1 and does not read packageManager
from package.json, so without it the install runs on a pnpm older than the
lockfile expects.
The custom domain is one CNAME, because the zone lives at Aliyun DNS rather than at Cloudflare:
puzzlebloom CNAME puzzlebloom.pages.dev (DNS only, no proxy)
…plus the same hostname added to the Pages project, which is what issues the certificate. Any other host works the same way.
Vercel — import the repo; vercel.json already sets the build command,
output directory, cache headers and CSP. It stays supported, but
it is the fallback: free Hobby is capped at 100 GB of transfer and 1M requests a
month, and when it hits the cap the project pauses until the window resets.
Hobby is also licensed for non-commercial use only.
GitHub Pages does not fit: it serves no custom headers, so the CSP would
have to move into a <meta> tag and lose frame-ancestors.
The live site is registered with both engines that matter, and the parts that can live in the repository do:
| Google Search Console | property https://puzzlebloom.taoliapp.com/, verified by DNS, sitemap.xml submitted |
| Bing Webmaster Tools | verification meta tag in apps/web/index.html, sitemap submitted |
| IndexNow | key at apps/web/public/<key>.txt; pnpm indexnow pings Bing and friends after every deploy |
Everything else — title, description, canonical, Open Graph, JSON-LD, the sitemap, the 404 page — is generated, not typed twice:
apps/web/site.json one table: url, name, and title/description/priority per route
├── src/app/head.ts applies it at run time, on every client-side navigation
└── scripts/seo.mjs renders each route in a browser after the build and
writes dist/<route>/index.html, plus sitemap.xml,
robots.txt and 404.html
The prerender is why a crawler that does not run JavaScript still sees four
different documents rather than four copies of a shell — and why /no-such-page
can answer a real 404 instead of 200 with the app inside it. Edit
site.json, not the HTML.
Two related scripts run on demand:
node scripts/og.mjs # redraws apps/web/public/og.png (1200×630) in the site's own type
pnpm indexnow # re-ping Bing after a content change
The reasoning — prerender instead of a meta-framework, one table instead of three, a real 404 — is in ADR-006.
| Sudoku | shipped — 4 levels, daily puzzle, hints that explain, creatures mode |
| Nonogram | in the greenhouse |
| Memory Garden | in the greenhouse |
| Tangram | in the greenhouse |
MIT. The Sudoku engine is MIT, copyright the HeyPuter/everything contributors; that attribution lives in THIRD_PARTY_NOTICES.md and must stay if you fork this.
Built by a parent, for one small person and everyone like her.