From e3170849273f1ee09bd892862657ffd22df61404 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Jul 2026 23:39:34 +0000 Subject: [PATCH] Cut card margins so shapes render larger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two layers of whitespace were shrinking every symbol: a 20px margin on the card's svg (40px of a ~99px card) and a 5-unit gap around each symbol box. Combined with a `max-height: 10vh` cap, a shape reached the player at ~17px on a phone. Drop `SYMBOL_MARGIN` to 2 (symbol box 35 -> 38 units), replace the svg margin with 3px of card padding, and size the card at `min(26vw, 20vh)` so it fills the board's three columns instead of being capped by viewport height. Shapes now land at ~27px on a 330px-wide phone and ~57px on desktop. Symbol size stays independent of shape count — `SYMBOL_SIZE` remains a single constant and the count only selects which of the nine fixed slots are filled, so one shape renders at exactly the size of each of nine. Verified in Chromium at 320/330/360px and 1440px: identical symbol boxes at every count, no horizontal overflow. The card sizing rule has to be a child selector. `CardSvg` nests one per symbol inside the root one, and a `.card svg` descendant selector applied the width to those too — in user units, swelling each symbol to fill the card. The old rule got away with it because `max-height: 10vh` exceeded the symbol box. Also scope the two overrides that sized by height (the history strip and the editor's four-column selector, which sits in a 500px modal), and refresh the scale figures in the deckBuilder README and shape skill docs. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Jt2TMy1ThEaBQPsfSN8cP3 --- .../references/architecture.md | 17 +++++++------- .../references/iconography.md | 23 ++++++++++--------- .../deckbuilder-shape/scripts/card-markup.tsx | 4 +--- .../scripts/render-shape.mjs | 6 ++--- src/components/game/board/index.css | 1 + src/components/game/board/index.tsx | 2 +- src/components/game/card/index.css | 23 +++++++++++++++---- .../game/previousSelection/index.css | 11 ++++----- src/deckBuilder/CardSvg.tsx | 4 ++-- src/deckBuilder/README.md | 9 ++++---- .../__snapshots__/deck.test.tsx.snap | 2 +- src/views/gameEditor/gameEditor.css | 8 +++++++ 12 files changed, 66 insertions(+), 44 deletions(-) diff --git a/.claude/skills/deckbuilder-shape/references/architecture.md b/.claude/skills/deckbuilder-shape/references/architecture.md index ef4eb3f..bcf07d8 100644 --- a/.claude/skills/deckbuilder-shape/references/architecture.md +++ b/.claude/skills/deckbuilder-shape/references/architecture.md @@ -42,14 +42,15 @@ while — this is a snapshot, not a guarantee. hardcoded to any one feature or shape name — see `views/gameEditor/__tests__/GameEditor.test.tsx` and `deckBuilder/__tests__/deckRules.test.ts` for the behavior this guarantees. -- **Symbols render tiny.** `CardSvg` places each symbol in a - `SYMBOL_SIZE = MAIN_VIEWPORT_SIZE / 3 - 5 = 35` unit box inside the 120-unit - card viewport — a 0.29x downscale of the shape's own `0 0 120 120` space — - and `src/components/game/card/index.css` caps the card at `max-height: 10vh`, - so a card is ~90px (~60px on a short window) and a symbol lands near 26 device - pixels. Combined scale from shape units to screen pixels: **~0.22x**. This is - the fact `references/iconography.md` exists to address, and why - `definePathShape`'s `strokeWidth="1"` is effectively invisible. +- **Symbols render small.** `CardSvg` places each symbol in a + `SYMBOL_SIZE = MAIN_VIEWPORT_SIZE / 3 - 2 = 38` unit box inside the 120-unit + card viewport — a 0.317x downscale of the shape's own `0 0 120 120` space — + and `src/components/game/card/index.css` sizes the card at + `min(26vw, 20vh)`, so a card is ~86px on a 330px-wide phone (~180px on + desktop) and a symbol lands near 27 device pixels at the small end. Combined + scale from shape units to screen pixels on a phone: **~0.23x**. This is the + fact `references/iconography.md` exists to address, and why `definePathShape`'s + `strokeWidth="1"` is effectively invisible. - Test runner is CRA/Jest (`yarn test` / `npm test`, both wrap `react-app-rewired test`). Tests assert markup, never appearance — the visual gate is `yarn render:shape ""` (`scripts/render-shape.mjs`), which diff --git a/.claude/skills/deckbuilder-shape/references/iconography.md b/.claude/skills/deckbuilder-shape/references/iconography.md index 5b182ee..6a9fe63 100644 --- a/.claude/skills/deckbuilder-shape/references/iconography.md +++ b/.claude/skills/deckbuilder-shape/references/iconography.md @@ -5,24 +5,25 @@ deliberately not being restyled — they are what the rules exist to improve on, not the pattern to copy. -## Why: a symbol is ~26 device pixels +## Why: a symbol is ~27 device pixels on a phone -Three scale reductions stack between the coordinates you type and what a player sees: +Two scale reductions stack between the coordinates you type and what a player sees: | Step | Where | Effect | | --- | --- | --- | -| Shape space → symbol box | `CardSvg.tsx` — `SYMBOL_SIZE = 120/3 - 5 = 35` | **0.29x** | -| Card viewport → card element | typical card ≈ 90px for a 120-unit viewport | **0.75x** | -| Combined | | **≈0.22x** | +| Shape space → symbol box | `CardSvg.tsx` — `SYMBOL_SIZE = 120/3 - 2 = 38` | **0.317x** | +| Card viewport → card element | phone card ≈ 90px for a 120-unit viewport | **0.75x** | +| Combined | | **≈0.23x** | -Plus `src/components/game/card/index.css` caps the card at `max-height: 10vh`, so on a -short window a card is ~60px and a symbol lands near **20px**. +`src/components/game/card/index.css` sizes the card at `min(26vw, 20vh)`, so the card +tracks the viewport: ~86px on a 330px-wide phone (symbol ≈ **27px**) up to ~180px on +desktop (symbol ≈ **57px**). Design against the phone figure — it is the one that hurts. -Concretely, in the default `0 0 120 120` shape space: +Concretely, in the default `0 0 120 120` shape space, at a 90px card: -- `stroke-width="1"` (what `definePathShape` emits) → **0.22px**. Invisible. -- `stroke-width="6"` → **1.3px**. The thinnest line that reliably survives. -- An 8-unit detail → **1.7px**. About the floor for anything you want seen. +- `stroke-width="1"` (what `definePathShape` emits) → **0.23px**. Invisible. +- `stroke-width="6"` → **1.4px**. The thinnest line that reliably survives. +- An 8-unit detail → **1.8px**. About the floor for anything you want seen. Set is a game of spotting differences fast. A symbol that needs squinting is a broken symbol, no matter how good it looks zoomed in. diff --git a/.claude/skills/deckbuilder-shape/scripts/card-markup.tsx b/.claude/skills/deckbuilder-shape/scripts/card-markup.tsx index 3e306c7..e50ba80 100644 --- a/.claude/skills/deckbuilder-shape/scripts/card-markup.tsx +++ b/.claude/skills/deckbuilder-shape/scripts/card-markup.tsx @@ -13,15 +13,13 @@ */ import * as React from "react"; import * as ReactDOMServer from "react-dom/server"; -import { CardSvg, MAIN_VIEWPORT_SIZE } from "../../../../src/deckBuilder/CardSvg"; +import { CardSvg, MAIN_VIEWPORT_SIZE, SYMBOL_SIZE } from "../../../../src/deckBuilder/CardSvg"; import { CardData, DEFAULT_CARD, NUMBERS, ROTATIONS, YOLKS } from "../../../../src/deckBuilder/features"; import { COLOR_SETS, ColorName, clampColorSet } from "../../../../src/deckBuilder/features/colors"; import { PATTERN_DEFS, PATTERN_NAMES, PatternName } from "../../../../src/deckBuilder/features/patterns"; import { SHAPE_NAMES, SHAPE_REGISTRY, ShapeName } from "../../../../src/deckBuilder/shapes"; import { Rotation } from "../../../../src/deckBuilder/types"; -/** Mirrors CardSvg's own symbol box: 1/3 of the card viewport, less a margin. */ -const SYMBOL_SIZE = MAIN_VIEWPORT_SIZE / 3 - 5; const DEFAULT_VIEW_BOX = "0 0 120 120"; /** Colors worth eyeballing: a mid hue, a dark hue, and pure black (the worst diff --git a/.claude/skills/deckbuilder-shape/scripts/render-shape.mjs b/.claude/skills/deckbuilder-shape/scripts/render-shape.mjs index 6a5b228..1464f49 100644 --- a/.claude/skills/deckbuilder-shape/scripts/render-shape.mjs +++ b/.claude/skills/deckbuilder-shape/scripts/render-shape.mjs @@ -25,9 +25,9 @@ const HERE = path.dirname(fileURLToPath(import.meta.url)); const REPO_ROOT = path.resolve(HERE, "../../../.."); const MARKUP_SCRIPT = path.join(HERE, "card-markup.tsx"); -/** Card sizes to eyeball, bracketing the app's real `max-height: 10vh` card. */ -const TRUE_SIZES = [60, 90, 140]; -/** The size the checks are stated against — a typical real card. */ +/** Card sizes to eyeball, bracketing the app's real `min(26vw, 20vh)` card. */ +const TRUE_SIZES = [90, 130, 180]; +/** The size the checks are stated against — the phone card, the worst case. */ const REFERENCE_CARD_PX = 90; const ZOOM_PX = 240; const VARIANT_PX = 90; diff --git a/src/components/game/board/index.css b/src/components/game/board/index.css index 90980e7..721bf0c 100644 --- a/src/components/game/board/index.css +++ b/src/components/game/board/index.css @@ -2,5 +2,6 @@ display: grid; grid-template-columns: auto auto auto; justify-content: center; + gap: 4px; } diff --git a/src/components/game/board/index.tsx b/src/components/game/board/index.tsx index 0d2a6eb..3570dd8 100644 --- a/src/components/game/board/index.tsx +++ b/src/components/game/board/index.tsx @@ -25,7 +25,7 @@ export const Board = (props: Props) => { {props.board.map((id: string) => { return (