diff --git a/@lab/ll-CARDSHED/.stitch/designs/S01-main-menu.html b/@lab/ll-CARDSHED/.stitch/designs/S01-main-menu.html new file mode 100644 index 0000000..03db0b0 --- /dev/null +++ b/@lab/ll-CARDSHED/.stitch/designs/S01-main-menu.html @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + +
+
+
+
+
+
+
+ +
+ +
+WELCOME TO +

CARD SHED

+

A shifting-trump card game for three or four friends.

+
+ + +
+ + + + + \ No newline at end of file diff --git a/@lab/ll-CARDSHED/.stitch/designs/S01-main-menu.png b/@lab/ll-CARDSHED/.stitch/designs/S01-main-menu.png new file mode 100644 index 0000000..1d3dd29 Binary files /dev/null and b/@lab/ll-CARDSHED/.stitch/designs/S01-main-menu.png differ diff --git a/@lab/ll-CARDSHED/apps/ui/src/App.tsx b/@lab/ll-CARDSHED/apps/ui/src/App.tsx index 8e085d5..311bf5e 100644 --- a/@lab/ll-CARDSHED/apps/ui/src/App.tsx +++ b/@lab/ll-CARDSHED/apps/ui/src/App.tsx @@ -1,39 +1,95 @@ /* - * Boilerplate boot screen — placeholder ONLY. + * App shell — routes between the MVP screens. * - * Per @lab/ll-CARDSHED/.claude/rules/ui-design-pipeline.md, no hand-rolled - * UI past this stub. M3 onward replaces every screen via Skill: stitch-design. - * - * The smoke-import from @cardshed/core proves the workspace file: link works. + * M3 wires MainMenu (S01). M4 replaces the "player-setup" placeholder with the + * real PlayerSetup screen + matchStore. Per .claude/rules/ui-design-pipeline.md, + * every screen past MainMenu has a Stitch origin in docs/SCREENS/. */ -import { createDeck } from "@cardshed/core"; +import { useState } from "react"; +import { MainMenu, type MainMenuAction } from "./features/menu/MainMenu"; -const DECK_SIZE = createDeck().length; +type Screen = + | "mainmenu" + | "player-setup-placeholder" + | "rules-placeholder" + | "settings-placeholder"; export default function App() { - return ( -
-
-

- CARD SHED — MVP M1 -

-

- Scaffold boots. The table is empty. -
- Stitch-driven screens land at M2 → M11. -

+ const [screen, setScreen] = useState("mainmenu"); + + const onMenuAction = (action: MainMenuAction) => { + if (action === "play") setScreen("player-setup-placeholder"); + else if (action === "rules") setScreen("rules-placeholder"); + else if (action === "settings") setScreen("settings-placeholder"); + }; -
-
core: @cardshed/core linked
-
deck: {DECK_SIZE} cards
-
+ if (screen === "mainmenu") { + return ; + } + return setScreen("mainmenu")} />; +} + +function Placeholder({ + screen, + onBack, +}: { + screen: Exclude; + onBack: () => void; +}) { + const labels: Record< + Exclude, + { title: string; milestone: string } + > = { + "player-setup-placeholder": { title: "Player Setup", milestone: "M4 (S02)" }, + "rules-placeholder": { title: "Rules Help", milestone: "M11 (S09)" }, + "settings-placeholder": { title: "Settings", milestone: "M11 (S10)" }, + }; + const { title, milestone } = labels[screen]; -
- Next: docs/STORYBOARD.md §5 wireframes, then{" "} - Skill: stitch-design. -
-
+ return ( +
+

+ {title} +

+

+ Coming at {milestone}. +

+
); } diff --git a/@lab/ll-CARDSHED/apps/ui/src/features/menu/MainMenu.tsx b/@lab/ll-CARDSHED/apps/ui/src/features/menu/MainMenu.tsx new file mode 100644 index 0000000..473d64a --- /dev/null +++ b/@lab/ll-CARDSHED/apps/ui/src/features/menu/MainMenu.tsx @@ -0,0 +1,210 @@ +/* + * S01 MainMenu. + * Stitch origin: docs/SCREENS/main-menu.md (DS-1, Stitch screen cb1645e2f8f84834914174930efbbfb6). + * Tokens consumed verbatim from .stitch/DESIGN.md §9 — do not invent. + */ + +import { cn } from "../../lib/cn"; + +export type MainMenuAction = "play" | "rules" | "settings"; + +interface MainMenuProps { + onAction: (action: MainMenuAction) => void; + version?: string; +} + +export function MainMenu({ onAction, version = "v0.x" }: MainMenuProps) { + return ( +
+ + +
+
+ + Welcome to + +

+ CARD SHED +

+

+ A shifting-trump card game for three or four friends. +

+
+ + +
+ +
+
+ {version} + + MVP + + hot-seat browser +
+
+
+ ); +} + +function PrimaryButton({ + label, + onClick, +}: { + label: string; + onClick: () => void; +}) { + return ( + + ); +} + +function GhostButton({ + label, + onClick, +}: { + label: string; + onClick: () => void; +}) { + return ( + + ); +} + +function FannedCardsMotif() { + const cardStyle = { + width: 280, + height: 400, + background: "var(--color-ink-strong)", + border: "3px solid var(--color-outline-variant)", + borderRadius: "var(--radius-lg)", + filter: "drop-shadow(0 10px 20px rgba(0, 0, 0, 0.5))", + transformOrigin: "bottom center", + } satisfies React.CSSProperties; + + return ( +
+
+
+
+
+
+
+ ); +} diff --git a/@lab/ll-CARDSHED/apps/ui/src/index.css b/@lab/ll-CARDSHED/apps/ui/src/index.css index 620cac9..e00e7a3 100644 --- a/@lab/ll-CARDSHED/apps/ui/src/index.css +++ b/@lab/ll-CARDSHED/apps/ui/src/index.css @@ -1,16 +1,71 @@ +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Playfair+Display:wght@600;700&display=swap"); @import "tailwindcss"; /* - * Placeholder tokens — REPLACED on first `stitch-design` run (M2). - * Do not hand-author beyond this point. See .stitch/DESIGN.md and - * .claude/rules/ui-design-pipeline.md. + * DS-1 — generated by `Skill: stitch-design` (Stitch project 4083518914509964664). + * Source of truth: @lab/ll-CARDSHED/.stitch/DESIGN.md §9. + * Do NOT hand-author tokens — regenerate via the skill and bump DS version. */ @theme { - --color-surface: #0e0f12; - --color-surface-raised: #181a1f; - --color-ink: #f1ece1; - --color-ink-muted: #a8a194; - --color-accent: #c9a050; + /* color — felt + chrome */ + --color-felt-base: #1a2421; + --color-felt-glow: #232d29; + --color-bg-page: #101415; + --color-surface-lowest: #0b0f10; + --color-surface-low: #191c1e; + --color-surface: #1d2022; + --color-surface-high: #272a2c; + --color-surface-highest: #323537; + --color-surface-bright: #363a3b; + + /* color — ink */ + --color-ink: #e0e3e5; + --color-ink-strong: #bec9c4; + --color-ink-muted: #c3c8c5; + --color-ink-faint: #8d928f; + + /* color — accents */ + --color-accent: #ee9800; + --color-accent-soft: #ffb95f; + --color-accent-ink: #5b3800; + --color-accent-on: #472a00; + --color-legal: #6bd8cb; + --color-legal-soft: #89f5e7; + --color-legal-ink: #00201d; + --color-legal-strong: #1a998d; + --color-danger: #ffb4ab; + --color-danger-soft: #ffdad6; + --color-danger-strong: #93000a; + --color-danger-ink: #690005; + + /* color — card */ + --color-card-face: #ffffff; + --color-card-border: #434846; + --color-card-ink-black: #101415; + --color-card-ink-red: #ef4444; + --color-card-back: #1a2421; + + /* color — outlines */ + --color-outline: #8d928f; + --color-outline-variant: #434846; + + /* typography */ + --font-display: "Playfair Display", "Iowan Old Style", "Palatino", serif; + --font-body: "Inter", "Segoe UI", system-ui, sans-serif; + --font-card: "Inter", system-ui, sans-serif; + + /* spacing */ + --spacing-unit: 8px; + --spacing-gutter: 16px; + --spacing-panel-padding: 24px; + --spacing-table-margin: 32px; + + /* radius */ + --radius-sm: 0.25rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 40px; + --radius-pill: 9999px; } html, @@ -20,12 +75,14 @@ body, } body { - background: var(--color-surface); + margin: 0; + background: radial-gradient( + circle at center, + var(--color-felt-glow) 0%, + var(--color-felt-base) 70%, + var(--color-bg-page) 100% + ); color: var(--color-ink); - font-family: - "Inter", - "Segoe UI", - system-ui, - sans-serif; + font-family: var(--font-body); -webkit-font-smoothing: antialiased; } diff --git a/@lab/ll-CARDSHED/apps/ui/src/lib/cn.ts b/@lab/ll-CARDSHED/apps/ui/src/lib/cn.ts new file mode 100644 index 0000000..b500fb1 --- /dev/null +++ b/@lab/ll-CARDSHED/apps/ui/src/lib/cn.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]): string { + return twMerge(clsx(inputs)); +} diff --git a/@lab/ll-CARDSHED/docs/DECISIONS/2026-05-21-stitch-run-2.md b/@lab/ll-CARDSHED/docs/DECISIONS/2026-05-21-stitch-run-2.md new file mode 100644 index 0000000..65cab14 --- /dev/null +++ b/@lab/ll-CARDSHED/docs/DECISIONS/2026-05-21-stitch-run-2.md @@ -0,0 +1,55 @@ +# Stitch Run #2 — S01 MainMenu + +- **UTC:** 2026-05-21 +- **Stitch project:** `projects/4083518914509964664` ("ll-CARDSHED — MVP hot-seat") +- **Design system used:** `assets/a70557bcdeed4f9ca600d28ffca0d467` (DS-1, "Tournament Card Elite") — passed via `designSystem` parameter so DS-1 propagates verbatim +- **Screen produced:** `projects/4083518914509964664/screens/cb1645e2f8f84834914174930efbbfb6` ("Main Menu — CARD SHED") +- **Local artifacts:** + - `.stitch/designs/S01-main-menu.html` (9.5 KB) + - `.stitch/designs/S01-main-menu.png` (166 KB, 2560 × 2714 source res, designed for the 1440 × 900 viewport) +- **Issue:** #149 (first half — M3 only; M4 deferred to a follow-up session due to warm-session-drop) +- **Branch:** `feat/cardshed-main-menu` + +## Why this is "Stitch Run #2" and not "DS-2" + +DS-1 remains the authoritative design system. This run **consumed** DS-1 via the `designSystem` parameter — it did **not** regenerate it. The Stitch call returned the same `theme` block byte-identical to what S03 Table produced, confirming DS-1 propagates cleanly. + +DS-2 would be triggered only by the criteria in `.stitch/DESIGN.md` §10 sibling — none of which apply here. + +## What was generated + +A landing screen matching `docs/STORYBOARD.md` §5 S01 wireframe. Stitch chose: + +- **Background:** radial-gradient from `#232D29` (center) → `#1A2421` (70%) → `#101415` (corners). Adds a subtle SVG noise texture overlay at 3% opacity (skipped in implementation — felt unnecessary). +- **Brand block:** "WELCOME TO" eyebrow (12px Inter 600), "CARD SHED" h1 (Playfair Display 48px 700, 0.15em tracking), one-line tagline (Inter 18px). +- **Button stack:** 280 × 56 px pills. PLAY is solid gold (`secondary-container`) with `0 0 24px rgba(245,158,11,0.25)` glow. RULES and SETTINGS are ghost-style (1px `outline-variant` border, transparent fill, hover swaps to `surface-container-high` + `outline` border). +- **Decorative fanned cards:** 3 stylized card backs in lower-left corner, 7% opacity, rotated 25° + per-card stagger. Decorative only. +- **Footer:** version pipe-separated tokens (`v0.x · MVP · hot-seat browser`) at 40% opacity, `pointer-events-none`. + +## What was NOT shipped from Stitch's output + +- **Mouse-tracking radial gradient.** Stitch's HTML included a `mousemove` listener that re-paints the body background to follow the cursor. Skipped — felt distracting against the calm-tournament tone, and the brief explicitly anchors "no moving animations on first paint other than the gold-glow." +- **Audio cue on button hover.** Stitch's HTML included a commented-out `Audio()` instantiation. MVP scope (per PRP 3) explicitly excludes audio (M16 is Level-0 bot, not audio). Removed entirely. +- **`v1.0.4 Tournament Mode Active` secondary footer line.** Stitch added it as ornament; doesn't reflect any real state. Removed. +- **`onclick="window.location.reload()"` on the PLAY button.** Replaced with a real `onAction("play")` callback wired through App.tsx. + +## Implementation deltas worth recording + +| Stitch | Implementation | Why | +|--------|----------------|-----| +| Inline `tailwind.config` for color/font/spacing | Consume `@theme` tokens from `apps/ui/src/index.css` | DS-1 already provides them — no need to redeclare per screen | +| Buttons use Tailwind `bg-secondary-container` etc. | Buttons use `style={{ background: "var(--color-accent)" }}` | Tailwind v4's `bg-*` utilities map to v3-era token names that DS-1 renames (`accent` not `secondary-container`); using CSS vars directly avoids the rename collision | +| Hover state defined via Tailwind `hover:bg-…` | Hover state defined via `onMouseEnter`/`onMouseLeave` | Same reason — Tailwind v4 utility generation against `--color-surface-high` is awkward inline | +| Material Symbols font loaded | Not loaded | No icons on this screen; defer the Material Symbols import to a screen that actually needs it (Settings dialog at M11) | + +The token names in DS-1 (`--color-accent`, `--color-ink-strong`, etc.) are domain-named, not Material-3-named. Stitch's HTML uses the Material-3 names (`secondary-container`, `on-surface-variant`) because that's the vocabulary the Stitch theme engine speaks. The bridge is straightforward 1:1 — see the table in `docs/SCREENS/main-menu.md` § "Token consumption". + +## Cost of this Stitch run + +- **One generate_screen_from_text call.** Returned synchronously this time (no timeout), unlike Run #1. +- **One list_design_systems call** to fetch the DS-1 asset ID. +- **Zero edit_screens calls** — no iteration needed; the first generation matched the brief. + +## Open question carried into M4 + +For M4 (PlayerSetup, S02), the same DS-1 asset ID propagation should work. M4 needs another generate_screen_from_text call which (per the warm-session-drop invariant) requires a fresh Claude Code session. diff --git a/@lab/ll-CARDSHED/docs/SCREENS/main-menu.md b/@lab/ll-CARDSHED/docs/SCREENS/main-menu.md new file mode 100644 index 0000000..828d27c --- /dev/null +++ b/@lab/ll-CARDSHED/docs/SCREENS/main-menu.md @@ -0,0 +1,118 @@ +# S01 — MainMenu + +> **Status:** Stitch-origin, implemented as `apps/ui/src/features/menu/MainMenu.tsx`. +> **DS version:** DS-1. +> **Engine state owned:** none — pre-match landing. + +## Storyboard reference + +- `docs/STORYBOARD.md` §3 (inventory row S01) +- `docs/STORYBOARD.md` §5 (S01 wireframe) +- `docs/STORYBOARD.md` §1 (premise — drives the aesthetic anchor) +- `PRPs/cardshed-03-experience-prp.md` §B (screen inventory, M3 milestone, lines 758–773) + +## Stitch provenance + +| Field | Value | +|-------|-------| +| Project | `projects/4083518914509964664` ("ll-CARDSHED — MVP hot-seat") | +| Screen | `projects/4083518914509964664/screens/cb1645e2f8f84834914174930efbbfb6` ("Main Menu — CARD SHED") | +| Design system | `assets/a70557bcdeed4f9ca600d28ffca0d467` ("Tournament Card Elite" / DS-1) | +| Source HTML | `.stitch/designs/S01-main-menu.html` (9.5 KB) | +| Source PNG | `.stitch/designs/S01-main-menu.png` (166 KB, 2560 × 2714 source res) | +| UTC | 2026-05-21 | +| Decision doc | [`docs/DECISIONS/2026-05-21-stitch-run-2.md`](../DECISIONS/2026-05-21-stitch-run-2.md) | + +## Component contract + +`MainMenu` is a stateless presentational component. + +```tsx +import { MainMenu, type MainMenuAction } from "./features/menu/MainMenu"; + + { /* "play" | "rules" | "settings" */ }} + version="v0.x" +/> +``` + +| Prop | Type | Required | Notes | +|------|------|----------|-------| +| `onAction` | `(action: "play" \| "rules" \| "settings") => void` | yes | Caller owns routing. App.tsx delegates to its `screen` state. | +| `version` | `string` | no, default `"v0.x"` | Rendered into the footer chip. | + +## Layout regions (1440 × 900 desktop canonical) + +``` +┌──────────────────────────────────────────────────────────────┐ +│ (felt radial gradient bg) │ +│ │ +│ WELCOME TO │ ← label-caps eyebrow +│ │ +│ CARD SHED │ ← display-lg brand (h1) +│ │ +│ A shifting-trump card game for three or four │ ← body-lg tagline +│ friends. │ +│ │ +│ ┌──────────────────┐ │ +│ │ PLAY │ ← gold pill, glow │ +│ └──────────────────┘ │ +│ ┌──────────────────┐ │ +│ │ RULES │ ← ghost pill │ +│ └──────────────────┘ │ +│ ┌──────────────────┐ │ +│ │ SETTINGS │ ← ghost pill │ +│ └──────────────────┘ │ +│ ╱╱╱╱ │ +│ ╱╱╱╱╱ │ ← fanned-cards motif +│╱╱╱╱╱╱ │ (lower-left, 7% opacity) +│ │ +│ v0.x · MVP · hot-seat browser │ ← footer +└──────────────────────────────────────────────────────────────┘ +``` + +## Token consumption (DS-1) + +| UI element | DS-1 token | +|------------|------------| +| Page background | radial-gradient(`var(--color-felt-glow)` → `var(--color-felt-base)` → `var(--color-bg-page)`) | +| Eyebrow "WELCOME TO" | `var(--font-body)` 12px/600 + `letter-spacing: 0.05em` + `var(--color-ink-muted)` | +| Brand "CARD SHED" | `var(--font-display)` 48px/700 + `letter-spacing: 0.15em` + `var(--color-ink-strong)` | +| Tagline | `var(--font-body)` 18px/400 + `var(--color-ink-muted)` | +| Primary PLAY pill | `bg var(--color-accent)`, `color var(--color-accent-ink)`, `border-radius var(--radius-pill)`, glow `0 0 24px rgba(245,158,11,0.25)` | +| Ghost pill | `border var(--color-outline-variant)`, `color var(--color-ink)`, hover swaps to `bg var(--color-surface-high)` + `border var(--color-outline)` | +| Footer dividers | 1×12px bars in `var(--color-outline-variant)` | +| Fanned cards | `bg var(--color-ink-strong)`, `border var(--color-outline-variant)`, `border-radius var(--radius-lg)` | + +No inline hex colors. No bespoke spacing. Every value above is sourced from `.stitch/DESIGN.md` §9 → `apps/ui/src/index.css`. + +## Accessibility + +- Brand-mark is a semantic `

` — single h1 per route. +- Each button: `