Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions .claude/skills/deckbuilder-shape/references/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<name>"` (`scripts/render-shape.mjs`), which
Expand Down
23 changes: 12 additions & 11 deletions .claude/skills/deckbuilder-shape/references/iconography.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 1 addition & 3 deletions .claude/skills/deckbuilder-shape/scripts/card-markup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions .claude/skills/deckbuilder-shape/scripts/render-shape.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/components/game/board/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
display: grid;
grid-template-columns: auto auto auto;
justify-content: center;
gap: 4px;
}

2 changes: 1 addition & 1 deletion src/components/game/board/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const Board = (props: Props) => {
{props.board.map((id: string) => {
return (
<button
className="btn btn-link m-1 p-0"
className="btn btn-link m-0 p-0"
onClick={() => props.onSelect(id)}
key={id}
>
Expand Down
23 changes: 18 additions & 5 deletions src/components/game/card/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
display: flex;
justify-content: center;
align-items: center;
/* padding: 5px; */
padding: 3px;
border: 0px;
border-radius: 4px;
}
Expand All @@ -29,12 +29,25 @@
display: flex;
}

.card svg {
margin: 20px;
max-height: 10vh;
/*
* Cards are sized to fill the board's three columns. `height: auto` lets the
* 0 0 120 120 viewBox keep the card square; the vh term caps growth on desktop.
* These win over the width="100%" height="100%" presentation attributes CardSvg
* emits.
*
* Must stay a child selector: CardSvg nests one <svg> per symbol inside the root
* one, and a descendant selector would resize those too — in user units, so each
* symbol would swell to fill the whole card.
*/
.card-content > svg {
display: block;
width: min(26vw, 20vh);
height: auto;
margin: 0;
}

.card img {
border-radius: 4px;
max-height: 15vh;
max-width: min(26vw, 20vh);
max-height: min(26vw, 20vh);
}
11 changes: 5 additions & 6 deletions src/components/game/previousSelection/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@
}

.previous-selection .card {
margin: 5px;
margin: 3px;
}

.previous-selection .card svg {
margin: 10px;
.previous-selection .card-content > svg {
width: min(14vw, 10vh);
}

.previous-selection .card img,
.previous-selection .card svg {
.previous-selection .card img {
height: 8vh;
}
}
4 changes: 2 additions & 2 deletions src/deckBuilder/CardSvg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { Rotation, ShapeFeatureSupport } from "./types";
export const MAIN_VIEWPORT_SIZE = 120;

const DEFAULT_SHAPE_VIEW_BOX = "0 0 120 120";
const SYMBOL_MARGIN = 5;
const SYMBOL_SIZE = MAIN_VIEWPORT_SIZE / 3 - SYMBOL_MARGIN;
const SYMBOL_MARGIN = 2;
export const SYMBOL_SIZE = MAIN_VIEWPORT_SIZE / 3 - SYMBOL_MARGIN;

/** The nine symbol slots on a card, center first so odd counts stay centered. */
const POSITIONS = (() => {
Expand Down
9 changes: 5 additions & 4 deletions src/deckBuilder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,14 @@ renaming one is a breaking change.

## Legibility

`CardSvg` fits each symbol into a 35-unit box in the 120-unit card viewport, and
a card renders at roughly 90px — so a symbol reaches the player at about **26
device pixels**. Shapes have to be drawn for that size. New shapes should:
`CardSvg` fits each symbol into a 38-unit box in the 120-unit card viewport, and
a card renders at `min(26vw, 20vh)` — roughly 86px on a narrow phone — so a
symbol reaches the player at about **27 device pixels**. Shapes have to be drawn
for that size. New shapes should:

- Outline everything with one uniform
`stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"`.
(`definePathShape`'s `strokeWidth="1"` works out to 0.22px on a card.)
(`definePathShape`'s `strokeWidth="1"` works out to 0.23px on a card.)
- Layer base fills, outlines, and highlights into separate `<g>` groups, keeping
the resolved `fill` paint on the base layer so patterns have room to read.
- Favor `<circle>`/`<ellipse>`/`<rect>` and `M`/`L`/`Q`/`T` paths over cubic
Expand Down
2 changes: 1 addition & 1 deletion src/deckBuilder/__tests__/__snapshots__/deck.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`card markup applies rotation, filter, and pattern 1`] = `"<svg height=\\"100%\\" width=\\"100%\\" viewBox=\\"0 0 120 120\\" xmlns=\\"http://www.w3.org/2000/svg\\"><defs><filter id=\\"flt-test-0_0_0_0_0\\"><feDropShadow dx=\\"1\\" dy=\\"1\\" stdDeviation=\\"0.5\\"></feDropShadow></filter><pattern id=\\"pat-test-0_0_0_0_0\\" width=\\"8\\" height=\\"10\\" patternUnits=\\"userSpaceOnUse\\" patternTransform=\\"rotate(90)\\"><rect width=\\"8\\" height=\\"10\\" fill=\\"#98a9ea\\"></rect><line stroke=\\"#4363d8\\" stroke-width=\\"5px\\" y2=\\"15\\"></line></pattern></defs><svg x=\\"42.5\\" y=\\"42.5\\" width=\\"35\\" height=\\"35\\" viewBox=\\"0 0 120 120\\"><g filter=\\"url(#flt-test-0_0_0_0_0)\\" transform=\\"rotate(90, 60, 60)\\"><path d=\\"M0.5,119.5 L79.5,119.5 L79.5,80.5 L39.5,80.5 L39.5,0.5 L0.5,0.5 L0.5,119.5 Z\\" fill=\\"url(#pat-test-0_0_0_0_0)\\" stroke=\\"#4363d8\\" stroke-width=\\"1\\" fill-rule=\\"evenodd\\"></path></g></svg></svg>"`;
exports[`card markup applies rotation, filter, and pattern 1`] = `"<svg height=\\"100%\\" width=\\"100%\\" viewBox=\\"0 0 120 120\\" xmlns=\\"http://www.w3.org/2000/svg\\"><defs><filter id=\\"flt-test-0_0_0_0_0\\"><feDropShadow dx=\\"1\\" dy=\\"1\\" stdDeviation=\\"0.5\\"></feDropShadow></filter><pattern id=\\"pat-test-0_0_0_0_0\\" width=\\"8\\" height=\\"10\\" patternUnits=\\"userSpaceOnUse\\" patternTransform=\\"rotate(90)\\"><rect width=\\"8\\" height=\\"10\\" fill=\\"#98a9ea\\"></rect><line stroke=\\"#4363d8\\" stroke-width=\\"5px\\" y2=\\"15\\"></line></pattern></defs><svg x=\\"41\\" y=\\"41\\" width=\\"38\\" height=\\"38\\" viewBox=\\"0 0 120 120\\"><g filter=\\"url(#flt-test-0_0_0_0_0)\\" transform=\\"rotate(90, 60, 60)\\"><path d=\\"M0.5,119.5 L79.5,119.5 L79.5,80.5 L39.5,80.5 L39.5,0.5 L0.5,0.5 L0.5,119.5 Z\\" fill=\\"url(#pat-test-0_0_0_0_0)\\" stroke=\\"#4363d8\\" stroke-width=\\"1\\" fill-rule=\\"evenodd\\"></path></g></svg></svg>"`;
8 changes: 8 additions & 0 deletions src/views/gameEditor/gameEditor.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@
border-radius: 3px;;
}

/*
* Four columns here, not the board's three, and inside a modal that maxes out at
* 500px on desktop — so this has to stay well under a quarter of that.
*/
.cardSelector .card-content > svg {
width: min(18vw, 8vh);
}

.cardSelector .cardSelector-container {
position: relative;
}
Expand Down
Loading