mirror of
https://github.com/Nutlope/hallmark.git
synced 2026-08-14 12:35:33 +02:00
Custom art-direction ritual: the slate, the draw, postures, the direction contract
- custom-theme.md: reflex check at two altitudes, spent defaults, a seven-entry slate, the deterministic draw with wildcards, the scene sentence, four colour postures, a five-block direction contract with a promise-by-promise finish review; loosened triggers; reworked worked examples - scripts/seed.mjs: reproducible draw (FNV-1a over brief + date + reroll) - references/direction-atlas.json: 41 design-history lineages dealt as wildcards
This commit is contained in:
@@ -1,367 +1,297 @@
|
||||
# Custom theme — protocol
|
||||
# Custom theme - the ritual and the protocol
|
||||
|
||||
Loaded only when the user has opted into the **custom** theme route in Step 1 of the Design flow. Custom is **made-to-measure for one brief**, written inline into the page's `:root`, never a permanent catalog entry. It spans a **spectrum of depth**: at its lightest, a complete OKLCH palette + free-font pairing tuned to the brief while keeping Hallmark's structures (the *combination* is per-brief); at its fullest — **bespoke** — the page's *structure and composition* are designed from first principles too, bound to no catalog theme, genre, or macrostructure. One route, chosen depth.
|
||||
Loaded only when the custom route is confirmed at Step 2.6 of the Design flow. Custom is **made-to-measure for one brief**, written inline into the page's `:root`, never a permanent catalog entry. It spans two depths: **tuned** (a complete OKLCH palette + free-font pairing on Hallmark's structures) and **bespoke** (structure and composition designed from first principles too). One route, chosen depth.
|
||||
|
||||
**The freedom is the combination — and, at the bespoke depth, the whole structure — but never the floor.** Every constraint in [`color.md`](color.md), [`typography.md`](typography.md), and [`anti-patterns.md`](anti-patterns.md) still applies, and **every slop-test gate fires unchanged at every depth** — the gates are the floor that never moves. The Step 5 preview surfaces the palette + pairing (plus the bespoke structure, when there is one) in plain text *before* any code is emitted, so the user can redirect.
|
||||
**The freedom is the combination, never the floor.** Every constraint in [`color.md`](color.md), [`typography.md`](typography.md), and [`anti-patterns.md`](anti-patterns.md) still applies, and **every slop-test gate fires unchanged at every depth**. The Step 5 preview surfaces the direction, posture, palette, and pairing in plain text before any code is emitted, so the user can redirect.
|
||||
|
||||
Custom exists because the model's own taste is an attractor. Left alone, a model asked for "something unique" ships the same two or three looks per category, every time. The ritual below is a set of outside interventions that break that pull: named rejections, a deterministic draw, a scene the palette must obey, and a contract the build is audited against.
|
||||
|
||||
## Two routes, plain English
|
||||
|
||||
- **catalog** — the named-theme catalogue. Hallmark's 20 themes (Specimen, Midnight, Brutal, Garden, Atelier, Newsprint, Terminal, Manifesto, Almanac, Sport, Studio, Riso, Bloom, Coral, Cobalt, Aurora, Editorial, Carnival, Lumen, Hum). Each one is a fixed combination of paper-band, display-style, and accent-hue. The rotation rule cycles through them so two consecutive runs don't read alike. **This is the default.** Most briefs use it.
|
||||
- **custom** — made-to-measure, at the depth the brief needs:
|
||||
- **Tuned** — a one-off OKLCH palette + font pairing built for one brief, *keeping* Hallmark's structures, archetypes, and macrostructures. The rules (paper L bands, accent chroma caps, font ban list, all slop-test gates) still apply; only the *combination* is per-brief.
|
||||
- **Bespoke** — when the brief's *structure itself* is the ask, custom goes further and designs the whole page from first principles — its own palette, type, **and** composition — dropping the catalog's structures too, floored only by the universal slop-test gates. Same route, deeper end. See **§ Bespoke depth** below.
|
||||
- **catalog** - the 20 named themes, rotated by the Rotation block in [`SKILL.md`](../SKILL.md). The default. Most briefs use it and never hear otherwise.
|
||||
- **custom** - made-to-measure, at the depth the brief needs:
|
||||
- **Tuned** - a one-off palette + pairing built for one brief, keeping Hallmark's macrostructures and archetypes.
|
||||
- **Bespoke** - when the brief's structure itself is the ask, the composition is designed from first principles too. Same route, deeper end. See § Bespoke depth.
|
||||
|
||||
Either way, custom does **not** extend the catalog with a permanent theme.
|
||||
Custom never extends the catalog with a permanent theme.
|
||||
|
||||
## When to surface this fork — Step 1 trigger signals
|
||||
## § Triggers
|
||||
|
||||
Hallmark must **not** offer catalog-vs-custom on every prompt. That's friction, not discipline. Surface the fork only when the brief carries one of these signals:
|
||||
Do not offer catalog-vs-custom on every prompt; that is friction, not discipline. Surface the fork only when the brief carries one of these signals:
|
||||
|
||||
1. **Explicit ask** — the user types `custom`, "custom theme", "tailored to our brand", "make it ours", "something unique", "play around with the colors and fonts", "I want my own palette".
|
||||
2. **Named brand colour** — the user gives a specific anchor colour as a hex / OKLCH / brand name. Example: "use our terracotta", "the brand red is hex #c0392b", "anchor on sea-blue".
|
||||
3. **Multi-attribute aesthetic the catalog can't carry** — three or more vibe words pointing at a specific, off-catalog feel. Examples: "moss, lichen, soft pink, herbal" / "sun-drenched, market-day, carbon-black" / "late-night, neon, brutalist deli". Compare against the 20 catalog themes; if no single catalog theme is within one axis-step of the vibe, fire the fork. **One adjective ("warm", "technical", "playful") is not a signal — that's a tone, the catalog already carries it.**
|
||||
4. **Brand-mood reference attached** — the user attaches a colour swatch, a moodboard, a Pantone chip. (If they attach a *page* screenshot, route to `study` instead; custom is for brand colour / mood, study is for design DNA.)
|
||||
5. **A singular structural vision** (→ the *bespoke* depth) — the brief names a *structure or composition*, not just a palette/mood: "no theme / from scratch / fully bespoke / ignore the catalog / art-direct it", or a one-of-a-kind page-shape the macrostructure catalog has no entry for (a scroll-assembling poem, a ticket-shaped page, an interactive periodic table). Routes to custom's **bespoke depth** (§ Bespoke depth below). A palette or mood that's merely off-catalog is *tuned* custom, not bespoke.
|
||||
1. **Explicit ask** - "custom", "custom theme", "tailored to our brand", "make it ours", "something unique", "play with the colors and fonts", "I want my own palette".
|
||||
2. **Distinctiveness ask** - "distinctive", "stand out", "memorable", "not generic", "make it a whole thing", "nothing off the shelf".
|
||||
3. **Named brand colour** - a specific anchor as hex / OKLCH / name: "use our terracotta", "the brand red is #c0392b", "anchor on sea-blue".
|
||||
4. **Two or more vibe words** pointing somewhere the catalog cannot go: "moss, lichen, soft pink" / "sun-drenched, market-day" / "late-night, neon, brutalist deli". One adjective ("warm", "technical") is a tone, not a signal; the catalog already carries it.
|
||||
5. **Brand-mood reference attached** - a swatch, moodboard, or Pantone chip. (A page screenshot routes to `study` instead.)
|
||||
6. **A singular structural vision** (routes to the bespoke depth) - "no theme", "from scratch", "fully bespoke", "art-direct it", or a page shape no macrostructure covers (a scroll-assembled poem, a ticket-shaped page, an interactive periodic table).
|
||||
7. **The visible offer was taken** - Step 1 adds *"Want this made-to-measure? Say `custom`"* to greenfield product-launch briefs; the user saying `custom` there is signal enough.
|
||||
|
||||
If any signal fires, ask one short follow-up before picking a theme:
|
||||
If a signal fires, ask one short follow-up before picking a theme:
|
||||
|
||||
> *"This brief reads like a custom palette would fit better than the 20 named themes. Want me to construct a custom OKLCH palette + free-font pairing tuned to <one-line summary of the vibe>, or stay on the catalog for variety + speed?"*
|
||||
> *"This brief reads like a custom system would fit better than the 20 named themes. Want me to construct one tuned to <one-line summary of the vibe>, or stay on the catalog for variety + speed?"*
|
||||
|
||||
Wait for the user to answer. If they say custom (or yes / go) → continue this protocol from § A. If they say catalog (or no / stay catalog) → drop the fork and proceed with the catalog route. **Default to catalog** — silence routes to catalog, not custom.
|
||||
Wait for the answer. Custom (or yes / go) continues below from § The ritual. Catalog (or silence) drops the fork and proceeds with the catalog route. **Default is catalog; silence never routes to custom.** If no signal fires, do not mention the fork at all.
|
||||
|
||||
If **none** of the signals fires, do not mention the fork at all. Continue silently with the catalog flow.
|
||||
---
|
||||
|
||||
## § The ritual
|
||||
|
||||
Runs on every custom build, both depths, in order, before any palette work. Each step produces one or two written lines in the reply; the whole ritual reads as a short paragraph of decisions, not a form.
|
||||
|
||||
### R.1 · Reflex check
|
||||
|
||||
Name the category's obvious aesthetic AND the second-order trap, then reject both in writing. Two altitudes, both mandatory:
|
||||
|
||||
- First order: the aesthetic anyone would guess. Tea brand: sage, cream, forest green. Fintech: navy and emerald trust palette. AI tool: near-black with a glowing gradient.
|
||||
- Second order: the "tasteful" fallback a design-literate model reaches for after dodging the first. Tea: editorial serif on bone paper. Fintech: monochrome Swiss restraint. AI tool: mono type and terminal green.
|
||||
|
||||
Format: *"Reflex check: rejecting <first-order> (the category default) and <second-order> (the tasteful fallback)."* Landing on either after writing this line means the self-check failed; redirect before continuing.
|
||||
|
||||
### R.2 · Spent defaults
|
||||
|
||||
Hallmark's own house defaults count as already spent for this brief family. Declare it before writing the slate. The spent table:
|
||||
|
||||
| Brief family | Already spent (do not land here) |
|
||||
| --- | --- |
|
||||
| warm / bookish / food / family | cream paper · serif display · lamplight amber accent |
|
||||
| tech / developer / AI | near-black surface · one neon accent · mono labels |
|
||||
| editorial / studio / portfolio | bone paper · high-contrast serif · hairline rules everywhere |
|
||||
| playful / consumer | rounded sans · candy accent on white · blob shapes |
|
||||
| luxury / craft | ivory · letterspaced small caps · no accent |
|
||||
| health / science | clinical white · single blue · geometric sans |
|
||||
| finance | navy · emerald · tabular mono |
|
||||
| events / music | black · duotone photo treatment · condensed caps |
|
||||
|
||||
The spent look can still be *earned* if the draw lands on a direction that genuinely demands it; what is banned is drifting there by default.
|
||||
|
||||
### R.3 · The slate
|
||||
|
||||
List ~7 grounded directions, numbered 1-7. Each is a **concrete visual system, artifact, place, or ritual this audience already knows**, one line each, with the germ of a system grammar. Not adjectives, not moods. Good slate entries name things that exist: a regional print tradition, a specific era of packaging, an instrument's control surface, a municipal document, a shop interior the audience has stood in.
|
||||
|
||||
Format per entry: *"3. The pharmacy blotter: clinical buff card, one federal-blue rule system, dosage-table typography."*
|
||||
|
||||
### R.4 · The draw
|
||||
|
||||
The model never hand-picks from its own slate; its favourite is deterministic, and refusing that argmax is the whole point. An outside pick decides:
|
||||
|
||||
```
|
||||
node <skill-dir>/scripts/seed.mjs "<brief-slug>" --n 7 --wild 2
|
||||
```
|
||||
|
||||
The script prints a reproducible line: `draw: 4/7 (seed <slug>·<date>·r0) · wildcards: <name>, <name>`. Paste it into the Picks block. Re-roll (only when the user asks): rerun with `--reroll 1`, `--reroll 2`, and so on; each excludes nothing but lands elsewhere, and the seed key keeps every roll reproducible.
|
||||
|
||||
**Wildcards.** The two dealt entries come from [`direction-atlas.json`](direction-atlas.json), a curated deck of real design-history lineages. A wildcard **replaces the drawn slate entry only if it beats it on both axes: audience identification and product clarity.** Weigh in one written line each; discard losers without ceremony. Wildcards exist to break category gravity, not to win by novelty.
|
||||
|
||||
**Script-less fallback** (no Node, no shell): N = (letter count of the brief, spaces and punctuation excluded) mod 7, pick slate entry N + 1; each re-roll adds 3 (mod 7). Print the arithmetic so the pick is reproducible: *"draw fallback: 142 letters mod 7 = 2, entry 3."* Skip wildcards in fallback mode.
|
||||
|
||||
### R.5 · The scene sentence
|
||||
|
||||
One concrete sentence placing a real person at a real time and place with the product. *"A subscriber in Vermont, 6:43am in early January; the kettle just clicked off; one lamp on."* The scene decides **surface lightness and light temperature** before any palette work: that sentence forces a dark surface with warm interior light, not a daylit white. § B.2 consumes this decision directly. A scene that does not constrain the surface is not concrete enough; rewrite it.
|
||||
|
||||
### R.6 · Colour posture
|
||||
|
||||
Commit to exactly one, with a one-line justification:
|
||||
|
||||
- **Restrained** - accent <= 5% of any viewport; the catalog's discipline. Right when type and structure carry the brand.
|
||||
- **Committed** - one colour carries 30-60% of the surface, routed through the surface tokens (`--color-field`, the paper family), while the accent token proper stays <= 5%. Right when the brief IS a colour ("the brand is that orange").
|
||||
- **Full palette** - 3 or 4 named roles, each with a stated job (field, signal, seal, ink). Right for editorial systems and print-lineage directions.
|
||||
- **Drenched** - the surface IS the colour; ink and rules derive from it. Right for single-message pages and poster energy.
|
||||
|
||||
Posture is declared in the stamp and the log. Gate 23 reads the posture: a declared surface colour is not accent footprint; undeclared accent sprawl still fails. Contrast gates 40-41 bind unchanged on any coloured surface. See [`color.md`](color.md) § Colour postures.
|
||||
|
||||
### R.7 · The direction contract
|
||||
|
||||
Five blocks, <= 150 words total, written into the artifact as a comment ABOVE the stamp before any code, and mirrored in the Step 5 preview:
|
||||
|
||||
```
|
||||
/* direction contract · seed <key>
|
||||
* THESIS: the one-sentence position this page takes.
|
||||
* OWN-WORLD: what makes it not-anyone's-page; the drawn direction, named.
|
||||
* STORY: the scene sentence.
|
||||
* FIRST-VIEWPORT: what the fold shows, concretely.
|
||||
* FORM: structure + the one signature move.
|
||||
*/
|
||||
```
|
||||
|
||||
If a block reads like a mood ("elegant, premium feel"), the direction is not decided yet; rewrite it until each block is checkable. **The signature move** named in FORM is the one structural or visual idea a visitor would describe to a friend; a page without one is a template with nice tokens.
|
||||
|
||||
---
|
||||
|
||||
## § Bespoke depth
|
||||
|
||||
Most custom runs are tuned. **Bespoke** is the deep end, fired by signal 6: the brief's structure itself is the ask and no catalog shape fits. The ritual runs identically (R.1 through R.7); bespoke changes what the build may drop:
|
||||
|
||||
**It drops:** the named-theme tokens (palette written inline for this page only; § B still governs how); the genre cluster routing; the fixed macrostructure + archetype catalog (compose the structure for the idea; a novel hero, nav, or section is encouraged when it serves the FORM block); the diversification rotation (bespoke is a one-off, though it must not clone a recent bespoke run).
|
||||
|
||||
**It keeps:** every universal slop-test gate; accessibility and contrast (APCA / WCAG, visible `:focus-visible`, `prefers-reduced-motion`, semantic landmarks, alt text); the font ban list and free-baseline discipline (§ C); OKLCH palette discipline (§ B); one orchestrated motion; the Step 5 preview before code; the stamp + log.
|
||||
|
||||
Bespoke is more design judgment, not less: a bespoke page that reads generic, or trips a gate, has failed. It is also rare; reaching for bespoke on a vanilla brief is over-reach.
|
||||
|
||||
---
|
||||
|
||||
## § A · The one follow-up question
|
||||
|
||||
Once the user names `custom` as the theme route, ask **one** thing in **one** message:
|
||||
Once custom is confirmed, ask **one** thing in **one** message:
|
||||
|
||||
> *"Custom needs one input — describe the brand's vibe in 4–8 words. Examples: 'archival warmth, hand-set, no varnish' · 'industrial precision, cool, technical' · 'moss, lichen, soft pink, herbal' · 'sun-drenched, market-day, carbon black' · 'late-night, neon, brutalist deli'.*
|
||||
> *"Custom needs one input - describe the brand's vibe in 4-8 words. Examples: 'archival warmth, hand-set, no varnish' · 'industrial precision, cool, technical' · 'moss, lichen, soft pink, herbal'.*
|
||||
>
|
||||
> *Optional second input: an anchor colour — hex, OKLCH, or a name like 'terracotta', 'sea-blue', 'forest-green', 'dusty-pink'. If you skip it, I'll pick one from the vibe."*
|
||||
> *Optional second input: an anchor colour - hex, OKLCH, or a name like 'terracotta', 'sea-blue'. Skip it and I'll pick one from the vibe."*
|
||||
|
||||
**Do not ask anything else.** Audience / use / tone (Step 1) plus the brand vibe is already enough signal. The model has no business asking the user to nominate paper lightness or font weights — that's the model's job.
|
||||
|
||||
If the user gives just two or three words ("sun-drenched"), proceed; the recipe below extracts enough. If the user gives a paragraph, accept it but compress to 4–8 words for the stamp.
|
||||
|
||||
---
|
||||
|
||||
## § Bespoke depth — custom that designs the whole page
|
||||
|
||||
Most custom runs are *tuned* (a palette + pairing on Hallmark's existing structures). **Bespoke** is the deep end, fired by signal 5: the brief's *structure itself* is the ask and no catalog shape fits. At this depth custom designs the page from first principles — palette, type, **and** composition — and the only thing it inherits is the floor.
|
||||
|
||||
Confirm the route once (same discipline as any custom — default to catalog on silence), then take **one** input: *"the direction in a sentence or two — what should this page feel like and do that an off-the-shelf theme wouldn't?"*
|
||||
|
||||
**It drops** (only at this depth):
|
||||
- the named-theme tokens — write the palette inline for this page only (§ B still governs *how*);
|
||||
- the genre cluster routing — no editorial / atmospheric / modern-minimal / playful archetype defaults;
|
||||
- the fixed macrostructure + archetype catalog — compose the page's structure for the brief; a novel hero, nav, or section is *encouraged* when it serves the idea;
|
||||
- the diversification rotation — bespoke is a one-off (like studied-DNA), though it shouldn't clone a recent bespoke run.
|
||||
|
||||
**It keeps** — the non-negotiable floor, identical to tuned custom:
|
||||
- **every universal slop-test gate** ([`slop-test.md`](slop-test.md)) — the guarantee that survives the freedom;
|
||||
- accessibility & contrast (APCA / WCAG), a visible `:focus-visible`, `prefers-reduced-motion`, semantic landmarks, alt text;
|
||||
- the **font ban-list** (Gate 1) and free-baseline-only discipline (§ C);
|
||||
- **OKLCH palette discipline** (§ B) — tinted neutrals, no pure `#000`/`#fff`, accent kept to a signal unless the concept earns more;
|
||||
- one orchestrated motion; the Step 5 preview before code; the Step 6 stamp + log.
|
||||
|
||||
**Process:** read the brief + the one-line direction → design the *system and the one central move* (the idea that makes it not-a-template) → run the gates *as you compose* → surface the preview (palette, type, structure, central idea) → build, stamp, log. Bespoke is **more** design judgment, not less — a bespoke page that reads generic, or trips a gate, has failed; re-design.
|
||||
|
||||
**Stamp (bespoke runs):**
|
||||
```css
|
||||
/* Hallmark · route: custom (bespoke) · structure: <one-line shape> · idea: "<central move>"
|
||||
* paper: oklch(...) · accent: oklch(...) · display: <font> · body: <font>
|
||||
* axes: <paper-band> / <display-style> / <accent-hue> · gates: all-pass · studied: no
|
||||
*/
|
||||
```
|
||||
|
||||
**Bespoke is rare.** Most briefs are catalog; some are tuned custom; few are bespoke. Reaching for bespoke on a vanilla brief is over-reach — route to catalog.
|
||||
Do not ask anything else; audience / use / tone from Step 1 plus the vibe is enough. Two or three words is enough to proceed; a paragraph gets compressed to 4-8 words for the stamp. The ritual runs regardless of how the user answers; the vibe feeds R.3's slate.
|
||||
|
||||
---
|
||||
|
||||
## § B · Palette construction
|
||||
|
||||
Build the palette in this order. Each step cites the rule it's obeying — do not restate the rule, just apply it.
|
||||
Build in this order; each step applies the cited rule without restating it.
|
||||
|
||||
### B.0 · Posture and scene set the frame
|
||||
|
||||
Before any values: the scene sentence (R.5) fixes the paper's lightness band and light temperature; the posture (R.6) decides where large colour lives. Committed and Drenched route their colour through the paper / field tokens, never by inflating the accent's footprint.
|
||||
|
||||
### B.1 · Anchor accent first
|
||||
|
||||
- Convert the user's named or hex anchor into OKLCH.
|
||||
- Clamp chroma to **0.12–0.20** per [`color.md`](color.md) § "Accent — the discipline".
|
||||
- If user skipped: derive hue from the vibe — *warmth* → 30–60° · *technical/industrial* → 220–250° · *botanical/moss* → 130–160° · *late-night/neon* → 280–320° · *sun-drenched/market* → 60–80° amber. Keep chroma 0.12–0.16 (mid-saturation; saturation comes from contrast against neutral, not from chroma).
|
||||
- Convert the named or hex anchor to OKLCH; clamp chroma to **0.12-0.20** per [`color.md`](color.md) § "Accent - the discipline".
|
||||
- No anchor given: derive hue from the vibe: warmth 30-60° · technical 220-250° · botanical 130-160° · late-night neon 280-320° · sun-drenched 60-80°. Chroma 0.12-0.16.
|
||||
|
||||
### B.2 · Paper
|
||||
|
||||
- Derive paper L from the vibe:
|
||||
- bright/airy/breakfast/hand-set → **L 95–98 %** (warm-tinted)
|
||||
- archival/editorial/restrained → **L 92–95 %** (warm-tinted)
|
||||
- technical/clinical/spec-sheet → **L 98–100 % near-white** (cool-tinted; can equal #fff but tinted neutrals downstream)
|
||||
- dark/restless/late-night/manifesto → **L 12–18 %** (anchor-tinted)
|
||||
- **Always tint paper toward the anchor hue with chroma 0.005–0.020** per [`color.md`](color.md) § "Neutral tinting". Pure-white #fff is allowed only when ink + accent + greys carry the chroma; the paper itself never carries chroma 0 in *both* directions.
|
||||
- Paper-2 (one elevation step): step ±2–4 % L from paper.
|
||||
- Paper-3 (optional second step): step ±5–7 % L from paper. Skip on minimal palettes.
|
||||
- Paper L from the scene sentence: bright / airy → **L 95-98%** (warm-tinted) · archival / editorial → **L 92-95%** · clinical / spec-sheet → **L 98-100%** near-white, cool-tinted · dark / late-night → **L 12-18%**, anchor-tinted.
|
||||
- **Always tint paper toward the anchor hue, chroma 0.005-0.020** per [`color.md`](color.md) § "Neutral tinting".
|
||||
- Committed posture: the carried colour becomes `--color-field` at the scene's lightness band; paper stays a quiet neighbour. Drenched posture: paper IS the anchor at usable lightness; ink derives from it.
|
||||
- Paper-2: step ±2-4% L. Paper-3 (optional): ±5-7% L.
|
||||
|
||||
### B.3 · Ink
|
||||
|
||||
- If paper L < 50: ink L **88–96 %**.
|
||||
- If paper L ≥ 50: ink L **16–24 %**.
|
||||
- Tint ink chroma **0.005–0.014** toward anchor (a shade darker / lighter, never neutral).
|
||||
- Ink-2 (secondary text): step 4–8 % L away from ink toward paper. Same hue family.
|
||||
- Paper L < 50: ink L **88-96%**. Paper L >= 50: ink L **16-24%**.
|
||||
- Tint ink chroma **0.005-0.014** toward the anchor. Ink-2: 4-8% L toward paper, same hue family.
|
||||
|
||||
### B.4 · Supporting greys
|
||||
|
||||
Step by ~6–10 % L between paper and ink, all tinted toward anchor with chroma 0.005–0.018:
|
||||
|
||||
- `--color-rule` — dividers · L ~70–82 % (light paper) or ~26–34 % (dark paper).
|
||||
- `--color-rule-2` — secondary dividers · 4–6 % L closer to paper than rule.
|
||||
- `--color-muted` — de-emphasised text · L ~38–56 %.
|
||||
- `--color-neutral` — mid-grey equivalent · L ~30–56 %.
|
||||
|
||||
These are not arbitrary. The L-step gives the palette **typographic depth** without leaning on accent.
|
||||
Step ~6-10% L between paper and ink, all tinted toward the anchor (chroma 0.005-0.018): `--color-rule` (L ~70-82% light paper / ~26-34% dark), `--color-rule-2` (4-6% closer to paper), `--color-muted` (L ~38-56%), `--color-neutral` (L ~30-56%). The L-steps give typographic depth without leaning on accent.
|
||||
|
||||
### B.5 · Focus
|
||||
|
||||
- Same hue as accent, slightly higher chroma (0.18–0.22) for visibility.
|
||||
- Same L as accent ±5 %.
|
||||
- Used only on `:focus-visible` — must show instantly per [`microinteractions.md`](microinteractions.md) § "Focus is a first-class state".
|
||||
Same hue as accent, chroma 0.18-0.22, L ±5% of accent. `:focus-visible` only; appears instantly.
|
||||
|
||||
### B.6 · Accent-ink (overlay text colour on accent)
|
||||
### B.6 · Accent-ink
|
||||
|
||||
- If accent L > 50: use ink (text reads dark on accent fill).
|
||||
- If accent L ≤ 50: use paper (text reads light on accent fill).
|
||||
- Verify **APCA contrast ≥ 7:1** for body, ≥ 3:1 for large text per [`color.md`](color.md).
|
||||
Text on accent fills: accent L > 50 uses ink; accent L <= 50 uses paper. Verify APCA >= Lc 60 / WCAG 4.5:1 for body, 3:1 for large text.
|
||||
|
||||
### B.7 · Verification
|
||||
|
||||
- **Gate 7** (no pure #000 / #fff base): paper and ink both have chroma > 0. Pass.
|
||||
- **Gate 22** (no zero-chroma neutrals): every grey has chroma ≥ 0.005. Pass.
|
||||
- **Gate 23** (accent ≤ 5 % footprint): plan the accent's role on the page (active state, one wordmark dot, one CTA fill). Don't carpet a section in accent.
|
||||
Gate 7 (no pure #000/#fff): paper and ink both carry chroma. Gate 22 (no zero-chroma neutrals): every grey >= 0.005. Gate 23 (accent footprint): plan the accent's role; under Committed / Drenched the carried colour lives in field / paper tokens and the accent proper still keeps to a signal.
|
||||
|
||||
---
|
||||
|
||||
## § C · Font pairing
|
||||
|
||||
Custom pulls from the seven tone-pairings in [`typography.md`](typography.md) — Editorial, Technical, Brutalist, Soft, Luxury, Playful, Austere, Workshop. Each tone has a **free baseline** and a **paid upgrade**.
|
||||
Custom pulls from the tone pairings in [`typography.md`](typography.md), and **may mix tones** - that is the freedom:
|
||||
|
||||
### C.1 · The freedom
|
||||
- Editorial display + Technical body: an academic-tone SaaS.
|
||||
- Brutalist display + Editorial body: a manifesto magazine.
|
||||
- Playful display + Austere body: a creator-tool brand.
|
||||
- Luxury display + Technical body: a hand-crafted dev tool.
|
||||
|
||||
The catalog pairs Display-from-tone-X with Body-from-tone-X. **Custom can mix tones** — that's the whole point:
|
||||
|
||||
- Editorial display + Technical body (italic Fraunces wordmark + Geist body) — works for an academic-tone SaaS.
|
||||
- Brutalist display + Editorial body (Anton + Newsreader italic) — works for a left-leaning manifesto magazine.
|
||||
- Playful display + Austere body (Bricolage Grotesque + Inter Tight) — works for a creator-tool brand.
|
||||
- Luxury display + Technical body (Cormorant Garamond + JetBrains Mono) — works for a hand-crafted dev-tool.
|
||||
|
||||
Pick **one display face** and **one body face** from any tone's columns. Optional mono if the page has code or tabular data.
|
||||
|
||||
### C.2 · The discipline
|
||||
|
||||
- **Free baseline only** unless the user has confirmed paid licences. Per [`typography.md`](typography.md) § "The discipline": "Never name a paid font in code without confirming the user is licensed."
|
||||
- **Banned defaults still banned** per [`typography.md`](typography.md) § "Banned defaults" — Inter / Roboto / Open Sans / Poppins / Lato / Work Sans / DM Sans / Montserrat / system-ui as display all fail Gate 1.
|
||||
- **Variable fonts are preferred** when available (Fraunces, Bricolage Grotesque, Newsreader, Geist, EB Garamond, Inter Tight) — they support optical-size and weight axes for tighter typographic control.
|
||||
|
||||
### C.3 · The pair must read
|
||||
|
||||
Once you have display + body, mentally render the page:
|
||||
|
||||
- Does the display face have enough weight contrast (200/400 next to 700/900) per [`typography.md`](typography.md) § "Commit to extremes"?
|
||||
- Does the body face read at the chosen body size (≥ 14 px floor; default 1 rem) at the chosen measure (45–75 ch)?
|
||||
- If display is mono and body is mono — that's only allowed when the page IS the design (Terminal-aesthetic, true single-font specimen). Per [`typography.md`](typography.md) line 7.
|
||||
|
||||
If any answer is no, redirect — pick a different body face or shift the display weight.
|
||||
One display face, one body face, optional mono. The discipline: **free-baseline only** unless the user confirms licences; the banned defaults stay banned (gate 1); variable fonts preferred. Then confirm the pair reads: enough weight contrast in the display, body legible at >= 14px across 45-75ch, mono-on-mono only when the mono IS the design. The drawn direction (R.4) should be audible in the pairing: a ledger direction wants tabular figures; a playbill direction wants wood-type energy in the display slot.
|
||||
|
||||
---
|
||||
|
||||
## § D · Custom-axis computation
|
||||
## § D · Axes and posture
|
||||
|
||||
A custom theme must declare its three diversification-rule axis values explicitly so [`SKILL.md`](../SKILL.md) § "Theme-diversification rule" fires the same way as it does on catalog themes.
|
||||
A custom theme declares its diversification values explicitly so the Rotation block in [`SKILL.md`](../SKILL.md) fires the same as catalog:
|
||||
|
||||
### D.1 · Paper band
|
||||
|
||||
- **dark** — paper L < 30 %
|
||||
- **mid** — paper L 30–85 %
|
||||
- **light** — paper L > 85 %
|
||||
|
||||
### D.2 · Display style
|
||||
|
||||
Pick one based on the chosen display face:
|
||||
|
||||
- **italic-serif** — Fraunces italic, Newsreader italic, EB Garamond italic, Cormorant italic
|
||||
- **roman-serif** — Source Serif 4, Newsreader, Crimson Pro, Bitter, Cardo
|
||||
- **geometric-sans** — Geist, Bricolage Grotesque, Inter Tight, Manrope, Sora
|
||||
- **mono** — Geist Mono, JetBrains Mono, IBM Plex Mono, Space Mono
|
||||
- **display-condensed-italic** — Migra italic, Tobias italic
|
||||
- **display-condensed-bold** — Anton, Bebas Neue, Oswald, Barlow Condensed
|
||||
- **display-heavy** — Inter Tight 900, Bricolage 800, Druk-class
|
||||
- **slab-serif** — Roboto Slab, Bitter heavy, Zilla Slab
|
||||
- **system-native** — system-ui, Inter Tight 400 (austere)
|
||||
- **risograph-bold** — bold sans with hand-crafted feel
|
||||
- **handwritten** — Caveat, Sacramento, Patrick Hand (rare; only when brand demands)
|
||||
|
||||
### D.3 · Accent hue band
|
||||
|
||||
- **warm** — hue 10–60° (red, orange, amber)
|
||||
- **cool** — hue 200–300° (blue, indigo, cyan)
|
||||
- **neutral** — no chromatic accent (austere; chroma < 0.05)
|
||||
- **chromatic-other** — anything outside warm/cool/neutral. Sub-tag the specific anchor: `chromatic-green ~145°` · `chromatic-sage ~120°` · `chromatic-phosphor ~150°` · `chromatic-terracotta ~30°` · `chromatic-dusty-pink ~350°` · `chromatic-moss ~140°` · `chromatic-amber ~75°`.
|
||||
|
||||
### D.4 · Where these go
|
||||
|
||||
Write all three into the macrostructure stamp (§ E below) and the `.hallmark/log.json` entry (§ F below). They are the durable record. The next run reads them.
|
||||
|
||||
---
|
||||
- **Paper band:** dark (L < 30%) · mid (30-85%) · light (> 85%).
|
||||
- **Display style:** italic-serif · roman-serif · geometric-sans · grotesk-sans · mono · display-condensed-bold · display-heavy · slab-serif · system-native · risograph-bold · handwritten (rare).
|
||||
- **Accent hue band:** warm 10-60° · cool 200-300° · neutral (chroma < 0.05) · chromatic-other (sub-tag the anchor: `chromatic-moss ~140°`).
|
||||
- **Posture** (fourth logged value): restrained · committed · full-palette · drenched.
|
||||
|
||||
## § E · Stamp format
|
||||
|
||||
The CSS comment at the top of the produced stylesheet (per [`SKILL.md`](../SKILL.md) Step 6 § "Stamp the output"):
|
||||
|
||||
```css
|
||||
/* Hallmark · macrostructure: <name> · <hero archetype + knobs>
|
||||
* theme: custom · vibe: "<4–8 words>" · paper: oklch(<L>% <C> <H>) · accent: oklch(<L>% <C> <H>)
|
||||
* display: <font name> · body: <font name> · axes: <paper-band> / <display-style> / <accent-hue>
|
||||
* studied: no · context: <user-provided | inferred> · v0.6.x
|
||||
/* Hallmark · macrostructure: <name or "bespoke: <one-line shape>"> · <hero archetype + knobs>
|
||||
* theme: custom · direction: "<drawn direction name>" · vibe: "<4-8 words>"
|
||||
* paper: oklch(<L>% <C> <H>) · accent: oklch(<L>% <C> <H>) · display: <font> · body: <font>
|
||||
* axes: <paper-band> / <display-style> / <accent-hue> · posture: <posture>
|
||||
* seed: <slug>·<date>·r<n> · contract: kept (5/5) · studied: no · v1.2.0
|
||||
*/
|
||||
```
|
||||
|
||||
Concrete example:
|
||||
|
||||
```css
|
||||
/* Hallmark · macrostructure: Long Document · H5 hero knobs: salutation=time-stamp, body=2 paragraphs, signoff=initials
|
||||
* theme: custom · vibe: "archival warmth, hand-set, no varnish" · paper: oklch(94% 0.020 65) · accent: oklch(58% 0.16 35)
|
||||
* display: Fraunces italic · body: Source Serif 4 · axes: light / italic-serif / chromatic-terracotta
|
||||
* studied: no · context: explicit · v0.8.0
|
||||
*/
|
||||
```
|
||||
|
||||
The stamp is the durable record. `audit` reads it. The next run reads it. The user reads it.
|
||||
|
||||
---
|
||||
The `contract: kept (5/5)` line is written only after the finish review below confirms it. The direction contract comment (R.7) sits directly above this stamp.
|
||||
|
||||
## § F · `.hallmark/log.json` entry shape
|
||||
|
||||
Custom runs extend the existing schema with a `theme_axes` field and an optional `vibe` field:
|
||||
|
||||
```json
|
||||
{ "date": "2026-05-01",
|
||||
{ "date": "2026-07-23",
|
||||
"macrostructure": "Stat-Led",
|
||||
"theme": "custom",
|
||||
"theme_axes": "light / italic-serif / chromatic-terracotta",
|
||||
"vibe": "archival warmth, hand-set, no varnish",
|
||||
"direction": "Double-entry ledger",
|
||||
"posture": "committed",
|
||||
"seed": "loop-observability·2026-07-23·r0",
|
||||
"wildcard": true,
|
||||
"theme_axes": "dark / mono / cool",
|
||||
"vibe": "industrial precision, cool, technical",
|
||||
"nav": "N8", "footer": "Ft4",
|
||||
"enrichment": "none",
|
||||
"brief": "Coffeebox · subscription" }
|
||||
"brief": "Loop · payment-rail observability" }
|
||||
```
|
||||
|
||||
Catalog entries continue to record `theme: <name>` and skip `theme_axes` (the catalog's axes are looked up from [`tokens.css`](../../../site/css/tokens.css)). Step 2.5 logic uses the same diversification check on both — for catalog entries it reads the axes from tokens.css; for custom entries it reads them from the entry.
|
||||
Catalog entries keep recording `theme: <name>` and skip the custom fields. The Rotation block treats both the same: a custom run must differ from the previous entry on at least one axis, whatever route that entry used.
|
||||
|
||||
When rotating, **a custom run that follows another custom run must differ on at least one axis from the previous custom** — same rule as catalog-vs-catalog. A custom run that follows a catalog run must differ on at least one axis from the catalog's axes. The diversification rule is theme-route-blind.
|
||||
## § The finish review
|
||||
|
||||
Before the slop test, re-read the direction contract promise by promise: THESIS still the page's position? OWN-WORLD visible in the shipped surface, or did the build drift back toward a spent default? STORY's light still in the palette? FIRST-VIEWPORT actually what the fold shows? FORM's signature move present and load-bearing? Any unkept promise = revise, not ship. Then run Step 7 as normal (every gate, both depths). Custom-only; this review never replaces the gate sweep.
|
||||
|
||||
---
|
||||
|
||||
## § G · Three worked examples
|
||||
## § G · Worked examples
|
||||
|
||||
Concrete generations to seed model imitation. Each shows the brief, the user's vibe answer, the constructed palette, the chosen pair, and the stamp.
|
||||
### G.1 · Coffeebox, the full ritual
|
||||
|
||||
### G.1 · Archival café — "Coffeebox"
|
||||
**Brief:** *"Landing page for Coffeebox, a small-batch coffee subscription. Roast on Sunday, ship on Monday, drink Tuesday. Audience: people who already buy good coffee. Tone: warm, hand-set, editorial. Custom confirmed."* **Vibe answer:** *"archival warmth, hand-set, no varnish."* **Anchor:** *"terracotta."*
|
||||
|
||||
**Brief:** *"Build me a landing page for Coffeebox — a small-batch coffee subscription. Roast on Sunday, ship on Monday, drink Tuesday. Audience: people who already buy good coffee and want fewer trips to the shop. Tone: warm, hand-set, editorial — like a small café's chalkboard. Theme route: custom."*
|
||||
- **Reflex check:** rejecting kraft-paper-and-coffee-browns (category default) and italic-serif-on-cream editorial (tasteful fallback).
|
||||
- **Spent:** warm/food family, so cream + serif + lamplight is spent unless the draw earns it back.
|
||||
- **Slate:** 1 the roastery chalkboard; 2 the grocer's price ticket; 3 the shipping manifest; 4 the Sunday broadsheet food column; 5 the enamel tin label; 6 the postal frank and rubber stamp; 7 the harvest-lot auction sheet.
|
||||
- **Draw:** `node scripts/seed.mjs "coffeebox-subscription" --wild 2` → `draw: 6/7 (seed coffeebox-subscription·2026-07-23·r0) · wildcards: Seed packet, Card catalog`. Wildcard weigh-in: Seed packet loses to the postal direction on product clarity (subscription = things that arrive in the mail); Card catalog loses on audience identification. **Building: the postal frank.**
|
||||
- **Scene:** *"A subscriber on a Tuesday at 7:10am, tearing the strip on a kraft mailer at the kitchen counter, stamp-marks inked slightly off-square."* Light paper, warm, daylit.
|
||||
- **Posture:** Full palette (kraft field, frank red, ink black, airmail blue seal), each with a stated job.
|
||||
- **Contract:** THESIS: your coffee is mail, and mail used to be beautiful. OWN-WORLD: postal franking, done with restraint. STORY: the scene above. FIRST-VIEWPORT: wordmark as a circular frank, one mailer photo-placeholder, ship-date line, subscribe CTA as a stamp outline. FORM: Long Document rhythm; the signature move is the frank-mark rule system dating each section like a postmark.
|
||||
- **Palette:** paper `oklch(95% 0.015 80)` kraft-tinted · ink `oklch(24% 0.012 60)` · accent `oklch(55% 0.17 30)` frank red · seal `oklch(58% 0.10 240)` airmail blue (named role, footprint budgeted) · rule `oklch(80% 0.015 75)`. **Pair:** display Bricolage Grotesque (hand-set energy, roman) · body Source Serif 4 · mono JetBrains Mono for lot numbers. **Axes:** light / geometric-sans / warm · posture: full-palette.
|
||||
|
||||
**Vibe answer:** *"archival warmth, hand-set, no varnish."* **Anchor:** *"terracotta."*
|
||||
### G.2 · Loop, a Committed dark build
|
||||
|
||||
**Palette:**
|
||||
- paper `oklch(94% 0.020 65)` — warm-cream, hue 65 (amber-warm)
|
||||
- paper-2 `oklch(91% 0.022 65)` — one elevation step
|
||||
- ink `oklch(22% 0.014 60)` — warm dark brown-black
|
||||
- ink-2 `oklch(40% 0.014 60)` — warm secondary
|
||||
- rule `oklch(78% 0.018 65)` — warm hairline
|
||||
- muted `oklch(54% 0.014 60)` — warm grey
|
||||
- accent `oklch(58% 0.16 35)` — terracotta (hue 35, chroma 0.16)
|
||||
- accent-ink `oklch(96% 0.014 65)` — paper for text on accent
|
||||
- focus `oklch(56% 0.20 35)` — accent at higher chroma
|
||||
**Brief:** *"Loop is real-time payment-rail observability for fintech platform engineers. Use: try it / contact sales. Tone: industrial, cool, technical. Custom confirmed."* **Vibe:** *"industrial precision, cool, technical."* **Anchor:** *(skipped)*.
|
||||
|
||||
**Pair:** display **Fraunces italic** (Editorial, free) · body **Source Serif 4** (Editorial, free) · mono **JetBrains Mono** (Technical, free).
|
||||
- **Reflex check:** rejecting navy-and-emerald fintech trust (category default) and neon-on-black terminal cosplay (tasteful fallback).
|
||||
- **Slate:** 1 the double-entry ledger; 2 the SWIFT wire printout; 3 the control-room annunciator panel; 4 the oscilloscope face; 5 the bank vault engineering plate; 6 the reconciliation stamp; 7 the trading-floor pit board.
|
||||
- **Draw:** `draw: 4/7 (seed loop-observability·2026-07-23·r0) · wildcards: Line-printer output, Blueprint`. Line-printer output beats entry 4 on both axes (engineers live in logs; the report idiom carries latency tables natively). **Building: the line-printer report.**
|
||||
- **Scene:** *"An on-call engineer at 2am, one monitor lit, tearing yesterday's batch report off the tractor feed."* Dark surface, cool, one warm task light.
|
||||
- **Posture:** Committed. The greenbar stripe carries 40% of the surface as `--color-field`; the alert amber accent stays a signal.
|
||||
- **Contract:** THESIS: observability is a report you can trust at 2am. OWN-WORLD: greenbar line-printer output, modernised, not cosplayed. STORY: above. FIRST-VIEWPORT: masthead as a job header (run id, date), one live latency table in greenbar stripes, try-CTA as a form-feed break. FORM: Workbench macrostructure; the signature move is greenbar striping as the page's only surface rhythm.
|
||||
- **Palette:** paper `oklch(15% 0.012 220)` · field stripe `oklch(19% 0.020 160)` greenbar · ink `oklch(93% 0.010 210)` · accent `oklch(75% 0.15 75)` alert amber · rule `oklch(30% 0.012 215)`. **Pair:** display Space Grotesk 600 · body Geist · mono IBM Plex Mono for every figure. **Axes:** dark / grotesk-sans / chromatic-other (amber ~75°) · posture: committed.
|
||||
|
||||
**Axes:** **light / italic-serif / chromatic-terracotta**.
|
||||
### G.3 · Mossroot, compressed to the stamp
|
||||
|
||||
**Stamp:**
|
||||
```css
|
||||
/* Hallmark · macrostructure: Long Document · H5 hero knobs: salutation=time-stamp, body=2 paragraphs, signoff=initials
|
||||
* theme: custom · vibe: "archival warmth, hand-set, no varnish" · paper: oklch(94% 0.020 65) · accent: oklch(58% 0.16 35)
|
||||
* display: Fraunces italic · body: Source Serif 4 · axes: light / italic-serif / chromatic-terracotta
|
||||
* studied: no · context: explicit · v0.8.0
|
||||
*/
|
||||
```
|
||||
Herbal apothecary in Porto; vibe *"moss, lichen, soft pink, herbal"*; draw landed 2/7, "the herbarium sheet" (slate), wildcards discarded; scene: overcast morning shop light; posture: restrained.
|
||||
|
||||
### G.2 · Industrial fintech — "Loop"
|
||||
|
||||
**Brief:** *"Loop is a real-time payment-rail observability platform for fintechs. Audience: platform engineers. Use case: try it / contact sales. Tone: industrial, cool, technical. Theme route: custom."*
|
||||
|
||||
**Vibe answer:** *"industrial precision, cool, technical."* **Anchor:** *"sea-blue."*
|
||||
|
||||
**Palette:**
|
||||
- paper `oklch(13% 0.012 220)` — dark cool
|
||||
- paper-2 `oklch(17% 0.014 220)` — one step up
|
||||
- paper-3 `oklch(22% 0.014 220)` — two steps up (panels)
|
||||
- ink `oklch(94% 0.010 220)` — cool light
|
||||
- ink-2 `oklch(72% 0.010 220)`
|
||||
- rule `oklch(30% 0.012 220)`
|
||||
- muted `oklch(58% 0.012 220)`
|
||||
- accent `oklch(72% 0.16 220)` — sea-blue (cool)
|
||||
- focus `oklch(78% 0.20 220)`
|
||||
|
||||
**Pair:** display **Geist Mono 500** (Technical, free) · body **Geist** (Technical, free) · mono **Geist Mono** (Technical, free).
|
||||
|
||||
Note: this *is* a single-family page (Geist + Geist Mono are the same family at different widths). [`typography.md`](typography.md) line 7 allows it: "single-font pages are allowed only when the single font IS the design choice." For an industrial-precision fintech, that's the design choice.
|
||||
|
||||
**Axes:** **dark / mono / cool**.
|
||||
|
||||
**Stamp:**
|
||||
```css
|
||||
/* Hallmark · macrostructure: Workbench · F2 sticky-scroll knobs: pinned=right, content=trace-panel, steps=3
|
||||
* theme: custom · vibe: "industrial precision, cool, technical" · paper: oklch(13% 0.012 220) · accent: oklch(72% 0.16 220)
|
||||
* display: Geist Mono 500 · body: Geist · axes: dark / mono / cool
|
||||
* studied: no · context: explicit · v0.8.0
|
||||
*/
|
||||
```
|
||||
|
||||
### G.3 · Botanical apothecary — "Mossroot"
|
||||
|
||||
**Brief:** *"Mossroot is a small herbal apothecary in Porto. We make tinctures, salves, and tea blends. Audience: locals + visitors. Use: see what we make + visit. Tone: quiet, herbal, hand-poured. Theme route: custom."*
|
||||
|
||||
**Vibe answer:** *"moss, lichen, soft pink, herbal."* **Anchor:** *(skipped — pick from vibe)*.
|
||||
|
||||
The vibe names two hues: *moss* (greenish, ~140°) and *soft pink* (warm, ~350°). Pick **soft pink as the accent** (single anchor — custom is one-accent strict) and use the moss-green as the *paper tint* (chroma 0.018 toward 145°). This carries the dual-vibe without splitting accent.
|
||||
|
||||
**Palette:**
|
||||
- paper `oklch(96% 0.018 145)` — moss-tinted near-white
|
||||
- paper-2 `oklch(93% 0.020 145)`
|
||||
- ink `oklch(22% 0.014 140)` — moss-tinted dark
|
||||
- ink-2 `oklch(42% 0.014 140)`
|
||||
- rule `oklch(82% 0.018 145)`
|
||||
- muted `oklch(56% 0.014 140)`
|
||||
- accent `oklch(72% 0.13 350)` — dusty-pink (chromatic-other)
|
||||
- focus `oklch(70% 0.18 350)`
|
||||
|
||||
**Pair:** display **Cormorant Garamond** (Luxury, free) · body **EB Garamond** (Luxury, free) · mono **Geist Mono** (rare on this page; only for ingredient lists).
|
||||
|
||||
**Axes:** **light / roman-serif / chromatic-other (dusty-pink)**.
|
||||
|
||||
**Stamp:**
|
||||
```css
|
||||
/* Hallmark · macrostructure: Catalogue · F1 catalogue knobs: tiles=8, columns=2, rule=hairline-between
|
||||
* theme: custom · vibe: "moss, lichen, soft pink, herbal" · paper: oklch(96% 0.018 145) · accent: oklch(72% 0.13 350)
|
||||
* display: Cormorant Garamond · body: EB Garamond · axes: light / roman-serif / chromatic-other (dusty-pink)
|
||||
* studied: no · context: explicit · v0.8.0
|
||||
* theme: custom · direction: "the herbarium sheet" · vibe: "moss, lichen, soft pink, herbal"
|
||||
* paper: oklch(96% 0.018 145) · accent: oklch(72% 0.13 350) · display: Cormorant Garamond · body: EB Garamond
|
||||
* axes: light / roman-serif / chromatic-other (dusty-pink) · posture: restrained
|
||||
* seed: mossroot-apothecary·2026-07-23·r0 · contract: kept (5/5) · studied: no · v1.2.0
|
||||
*/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What custom does **not** do (worth restating)
|
||||
## What custom does **not** do
|
||||
|
||||
1. **Does not invent themes that ignore the rules.** Every paper L band, accent chroma cap, neutral-tinting requirement, font ban, and slop-test gate carries forward. The freedom is the *combination* — not the rules.
|
||||
2. **Does not save themes for reuse.** A custom run is per-output. The skill does not write back to [`tokens.css`](../../../site/css/tokens.css). If the user wants a permanent theme, they paste the custom palette into tokens.css themselves and name it.
|
||||
3. **Does not ask multiple follow-up questions.** One vibe answer (+ optional anchor) is enough. The audience/use/tone from Step 1 plus the brief plus the macrostructure pick already give the model 80 % of the signal.
|
||||
4. **Does not relax the diversification rule.** Custom entries declare their three axes the same way catalog entries do; the rotation rule fires on both, theme-route-blind.
|
||||
5. **Does not bypass the Step 5 preview.** The custom palette + pairing surface in plain text *before* any code is emitted, so the user can redirect early.
|
||||
|
||||
If any of those five lines is bent, the custom output is over-invented. Audit it; redirect.
|
||||
1. **Does not invent themes that ignore the rules.** Every band, cap, tint requirement, font ban, and slop-test gate carries forward. The freedom is the combination.
|
||||
2. **Does not save themes for reuse.** A custom run is per-output; nothing writes back to `tokens.css`.
|
||||
3. **Does not ask multiple follow-up questions.** One vibe answer (+ optional anchor) is enough.
|
||||
4. **Does not relax the diversification rule.** Custom entries declare axes + posture; the rotation fires on both routes.
|
||||
5. **Does not bypass the Step 5 preview.** Direction, posture, palette, and pairing surface in plain text before code.
|
||||
6. **Does not skip the draw.** The model's own #1 pick is exactly what the draw exists to refuse; a custom run with no seed line in the stamp is over-invented. Audit it; redirect.
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
[
|
||||
{ "name": "Basel grid", "lineage": "Swiss International Style, Basel and Zurich schools, 1950s-70s", "grammar": "white surface with the grid exposed as hairlines · one neo-grotesque family at two weights · content locks to columns, one red element per viewport", "fits": ["institutions", "agencies", "engineering tools"] },
|
||||
{ "name": "Bauhaus catalog", "lineage": "Bauhaus Dessau exhibition catalogs and prospectuses, 1920s", "grammar": "warm off-white with primary-colour geometry · geometric sans, heavy lowercase · diagonal composition and rotated type earn their angle", "fits": ["design tools", "education", "manifestos"] },
|
||||
{ "name": "Constructivist poster", "lineage": "Rodchenko and El Lissitzky poster work, 1920s", "grammar": "bone paper, red and black only · condensed caps set on the diagonal · photomontage crops and hard wedges direct the eye", "fits": ["events", "campaigns", "bold launches"] },
|
||||
{ "name": "Deco travel poster", "lineage": "Art Deco railway and ocean-liner posters, 1920s-30s", "grammar": "dusk gradients as flat bands, never mesh · tall geometric display caps with tight tracking · one monumental object dominates, small caption footer", "fits": ["travel", "hospitality", "events"] },
|
||||
{ "name": "NASA standards", "lineage": "NASA Graphics Standards Manual, Danne and Blackburn, 1975", "grammar": "cool white with one federal red accent · the worm logotype instinct, wide grotesque caps · specification pages: numbered figures, ruled captions", "fits": ["aerospace-adjacent tech", "developer platforms", "hardware"] },
|
||||
{ "name": "Bell manual", "lineage": "Saul Bass AT&T and Bell System identity standards, 1969-84", "grammar": "flat corporate blues on white · rounded geometric sans, generous sizes · systems diagrams with dot-and-line pictograms", "fits": ["telecom", "infrastructure", "B2B services"] },
|
||||
{ "name": "Rams instruction card", "lineage": "Braun product literature under Dieter Rams, 1960s-70s", "grammar": "pale grey surface, colour only on the control being explained · small akzidenz-style labels, numbered · exploded product views with index lines", "fits": ["hardware", "consumer devices", "productivity tools"] },
|
||||
{ "name": "Olivetti showroom", "lineage": "Olivetti advertising and showroom graphics, Pintori era, 1950s", "grammar": "cream with playful primary confetti restrained to one zone · humanist sans with occasional script flourish · the machine drawn as a character, not photographed", "fits": ["creative tools", "hardware with personality", "agencies"] },
|
||||
{ "name": "Penguin grid", "lineage": "Penguin paperback covers, Tschichold and Marber grid, 1947-62", "grammar": "one flat band structure dividing the page by fixed ratio · serif title case, publisher-mark discipline · colour codes the category, band positions never move", "fits": ["publishing", "content platforms", "catalogs"] },
|
||||
{ "name": "Paste-up mechanical", "lineage": "Phototype and Letraset paste-up boards, 1970s studios", "grammar": "white board with visible crop marks and registration ticks · mixed display faces as physical specimens · elements sit slightly off-true, taped not templated", "fits": ["portfolios", "studios", "zines"] },
|
||||
{ "name": "Riso zine", "lineage": "Risograph small-press printing, 1980s-now", "grammar": "uncoated paper, two soy-ink spot colours, visible misregistration · chunky bold sans, hand lettering allowed · overprint textures and halftone dots", "fits": ["community", "events", "indie products"] },
|
||||
{ "name": "Whole Earth spread", "lineage": "Whole Earth Catalog, 1968-74", "grammar": "newsprint cream, ink-dense · typewriter body with bold serif headers · dense multi-column item reviews with inline images and prices", "fits": ["marketplaces", "directories", "tool collections"] },
|
||||
{ "name": "Airline timetable", "lineage": "Mid-century airline timetables and route maps, 1950s-60s", "grammar": "white with one airline livery colour · tabular figures everywhere, tiny caps for city codes · the schedule table IS the layout, route arcs as decoration", "fits": ["logistics", "scheduling tools", "travel"] },
|
||||
{ "name": "Survey sheet", "lineage": "USGS topographic quadrangle sheets, 20th century", "grammar": "paper white with hairline contour density · small caps for place names, italic only for water · marginalia frame: datum notes, scale bars, sheet numbers", "fits": ["mapping", "data platforms", "field tools"] },
|
||||
{ "name": "Naturalist plate", "lineage": "Audubon and 19th-century field-guide lithograph plates", "grammar": "warm ivory, one specimen centered · engraved serif captions with latin binomials · figure numbers and leader lines, plate framed by whitespace", "fits": ["nature brands", "food and botanicals", "science"] },
|
||||
{ "name": "Seed packet", "lineage": "American seed-packet lithography, 1900s-40s", "grammar": "saturated produce illustration on cream · ornamented display caps with banner ribbons · front-of-pack hierarchy: variety name, vignette, planting table on the back", "fits": ["food", "garden", "subscription boxes"] },
|
||||
{ "name": "Vitrine label", "lineage": "Museum object labels and vitrine cards", "grammar": "gallery white, ink only · one quiet serif at two sizes, generous leading · object number, title line, medium line, provenance paragraph", "fits": ["luxury", "portfolios", "archives"] },
|
||||
{ "name": "Transit wayfinding", "lineage": "Vignelli subway diagram and Calvert-Kinneir motorway signage, 1960s-70s", "grammar": "high-contrast panel colours with white reversal · one transport grotesque, mixed case, huge x-height · destination hierarchy: line, station, exit; arrows do the work", "fits": ["navigation-heavy apps", "platforms", "logistics"] },
|
||||
{ "name": "Teletext page", "lineage": "Ceefax and Minitel broadcast text services, 1970s-80s", "grammar": "true black with saturated RGB block colours · double-height bitmap caps · page numbers, block mosaics as graphics", "fits": ["news", "dashboards", "retro consumer"] },
|
||||
{ "name": "Card catalog", "lineage": "Library card catalogs and microfiche archives", "grammar": "manila and buff cards, rubber-stamp red · typewriter mono with underline emphasis · drawer-and-card structure, tabbed dividers, call numbers", "fits": ["search products", "archives", "knowledge tools"] },
|
||||
{ "name": "Double-entry ledger", "lineage": "Bookkeeping ledgers and banker's stationery, 19th-20th century", "grammar": "pale green or buff ruled paper · copperplate headers over mono figures · debit and credit columns, running balance, red for deficit only", "fits": ["fintech", "accounting", "analytics"] },
|
||||
{ "name": "Letterpress playbill", "lineage": "Broadside ballads and theatre playbills, 18th-19th century", "grammar": "cream rag paper, black ink, one spot red · a different wood-type face per line, all centered · stacked bill-of-fare hierarchy with rule dividers", "fits": ["events", "restaurants", "launches"] },
|
||||
{ "name": "Planetarium chart", "lineage": "Star atlases and planetarium programs, 19th-20th century", "grammar": "deep blue-black with pinpoint white and gold · classical serif with letterspaced small caps · constellation lines connect content nodes, coordinate ring frames", "fits": ["AI and science tools", "night-mode products", "observability"] },
|
||||
{ "name": "Concrete poem", "lineage": "Concrete poetry movement, Gomringer and the Noigandres group, 1950s-60s", "grammar": "bare page, ink only · one sans, one size, placement IS the meaning · words arranged as the thing they describe", "fits": ["manifestos", "studios", "single-idea landings"] },
|
||||
{ "name": "Matchbook", "lineage": "American matchbook covers, 1930s-60s", "grammar": "one saturated ground colour, foil-adjacent accents · script wordmark over condensed caps address line · tiny canvas discipline: front hook, spine message, back offer", "fits": ["hospitality", "small brands", "micro-sites"] },
|
||||
{ "name": "Classifieds page", "lineage": "Phone-book yellow pages and newspaper classifieds", "grammar": "yellow or newsprint ground, ink-dense · bold condensed headers over 6pt body · column rules, ad boxes of mixed sizes, alphabetical tabs", "fits": ["directories", "marketplaces", "local services"] },
|
||||
{ "name": "Line-printer output", "lineage": "Punched-card decks and tractor-feed line-printer reports, 1960s-70s", "grammar": "greenbar striped paper · uppercase mono only · fixed-width report blocks, page headers with job numbers", "fits": ["developer tools", "batch and data products", "retro-tech"] },
|
||||
{ "name": "Catalog colophon", "lineage": "Exhibition-catalog colophons and fine-press back matter", "grammar": "warm white, ink, one metallic-adjacent accent · old-style serif with true small caps · edition numbering, typeface credits, paper stock notes as content", "fits": ["studios", "limited products", "print-adjacent brands"] },
|
||||
{ "name": "Matchbox label", "lineage": "Japanese matchbox labels, Meiji through Showa eras", "grammar": "flat two-ink illustration on kraft · hand-drawn logotype in a cartouche · one animal or object as the whole brand, border frames it", "fits": ["consumer goods", "cafes", "playful brands"] },
|
||||
{ "name": "Pharma sachet", "lineage": "Geigy pharmaceutical packaging and literature, 1950s-60s", "grammar": "clinical white with one large abstract geometric figure · akzidenz-class grotesque, lowercase · dosage-table clarity, the abstract shape carries all emotion", "fits": ["health", "science SaaS", "precision tools"] },
|
||||
{ "name": "Filmstrip frame", "lineage": "Mid-century instructional filmstrips and slide decks", "grammar": "warm kodachrome cast, rounded-corner frames · captioned lower-third bars · numbered frames advance the argument, sprocket rhythm as motif", "fits": ["education", "onboarding flows", "storytelling"] },
|
||||
{ "name": "Annual report modernism", "lineage": "Paul Rand and corporate annual reports, 1950s-70s", "grammar": "white with one witty die-cut-style graphic device · serif body, sans display, both restrained · full-page chart spreads treated as art plates", "fits": ["B2B", "fintech", "holding pages"] },
|
||||
{ "name": "Isotype board", "lineage": "Otto Neurath's Isotype pictogram system, 1920s-40s", "grammar": "flat white, black, one signal red · pictogram rows where quantity = repetition, never scaling · captions beneath, unit legend at the foot", "fits": ["data storytelling", "civic tech", "reports"] },
|
||||
{ "name": "Nautical chart", "lineage": "Admiralty nautical charts, 19th-20th century", "grammar": "pale sea-tint with buff land · italic hydrographic labels, upright coastal caps · depth soundings as scattered numerals, compass rose earns its place", "fits": ["maritime", "exploration products", "maps"] },
|
||||
{ "name": "Recipe card", "lineage": "Mid-century boxed recipe cards", "grammar": "cream card with one gingham or tint band · friendly serif title, typewriter body · ingredients rail left, method numbered right, index-tab category", "fits": ["food", "how-to products", "home brands"] },
|
||||
{ "name": "Racing livery", "lineage": "Motorsport liveries and roundel number plates, 1960s-70s", "grammar": "one team colour blocked against white, meatball roundel · italic-free condensed numerals, huge · sponsor-patch grid disciplined to the tail", "fits": ["performance tools", "sports", "speed-led brands"] },
|
||||
{ "name": "Herbarium sheet", "lineage": "Botanical herbarium specimen sheets", "grammar": "aged white sheet, pressed-specimen centered · typed determinavit labels bottom-right · accession stamps and annotation slips accumulate as texture", "fits": ["botanicals", "archival products", "collections"] },
|
||||
{ "name": "Blueprint", "lineage": "Cyanotype architectural and engineering blueprints", "grammar": "prussian-blue ground with white line work · stencil-adjacent caps for title block · orthographic projections, dimension chains, revision table", "fits": ["construction", "architecture tools", "planning software"] },
|
||||
{ "name": "Specimen broadsheet", "lineage": "Type-foundry specimen sheets, 19th century", "grammar": "cream broadsheet, ink only · every size of one family shown as content · waterfall settings and lorem-free showings, foundry footer", "fits": ["type-adjacent tools", "portfolios", "editorial"] },
|
||||
{ "name": "Observatory logbook", "lineage": "Scientific expedition and observatory logbooks", "grammar": "ivory ruled pages, iron-gall ink · cursive-adjacent serif for entries, upright for data · dated entries with sketches pasted in, marginal coordinates", "fits": ["research tools", "journals", "long-form products"] },
|
||||
{ "name": "Bus blind", "lineage": "London bus destination blinds and roller signs", "grammar": "black ground, white and yellow letterforms only · one grotesque, capitals, extreme size · destination stacking, via-points in smaller line", "fits": ["transit", "bold single-message landings", "events"] }
|
||||
]
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env node
|
||||
// Hallmark · the draw. Deterministic outside pick for the custom ritual.
|
||||
// The model lists ~7 grounded directions (the slate) but its own favourite is
|
||||
// deterministic, so this script assigns WHICH entry gets built. Same brief,
|
||||
// same day, same reroll count -> same draw. Usage:
|
||||
//
|
||||
// node seed.mjs "<brief-slug>" [--n 7] [--reroll 0] [--wild 2] [--atlas <path>]
|
||||
//
|
||||
// Prints one line the model pastes into the Picks block, e.g.:
|
||||
// draw: 4/7 (seed coffeebox-subscription·2026-07-23·r0) · wildcards: Airline timetable, Herbarium sheet
|
||||
//
|
||||
// No dependencies. Node 18+.
|
||||
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
function fnv1a(str) {
|
||||
let h = 0x811c9dc5;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
h ^= str.charCodeAt(i);
|
||||
h = Math.imul(h, 0x01000193) >>> 0;
|
||||
}
|
||||
return h >>> 0;
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length === 0 || args[0].startsWith('--')) {
|
||||
console.error('usage: node seed.mjs "<brief-slug>" [--n 7] [--reroll 0] [--wild 2] [--atlas <path>]');
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const slug = args[0].toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
||||
const flag = (name, dflt) => {
|
||||
const i = args.indexOf(`--${name}`);
|
||||
return i !== -1 && args[i + 1] !== undefined ? args[i + 1] : dflt;
|
||||
};
|
||||
|
||||
const n = Math.max(2, parseInt(flag('n', '7'), 10) || 7);
|
||||
const reroll = Math.max(0, parseInt(flag('reroll', '0'), 10) || 0);
|
||||
const wild = Math.max(0, parseInt(flag('wild', '2'), 10) || 0);
|
||||
const date = new Date().toISOString().slice(0, 10);
|
||||
|
||||
const seedKey = `${slug}·${date}·r${reroll}`;
|
||||
const hash = fnv1a(seedKey);
|
||||
const index = (hash % n) + 1; // 1-based slate index
|
||||
|
||||
let wildcards = [];
|
||||
if (wild > 0) {
|
||||
const atlasPath = flag('atlas', join(dirname(fileURLToPath(import.meta.url)), '..', 'references', 'direction-atlas.json'));
|
||||
try {
|
||||
const atlas = JSON.parse(readFileSync(atlasPath, 'utf8'));
|
||||
const picked = new Set();
|
||||
let salt = 0;
|
||||
while (picked.size < Math.min(wild, atlas.length)) {
|
||||
const j = fnv1a(`${seedKey}·w${picked.size}·s${salt}`) % atlas.length;
|
||||
if (picked.has(j)) { salt++; continue; }
|
||||
picked.add(j);
|
||||
salt = 0;
|
||||
}
|
||||
wildcards = [...picked].map((j) => atlas[j]);
|
||||
} catch (e) {
|
||||
console.error(`(atlas unavailable at ${atlasPath}; drawing without wildcards)`);
|
||||
}
|
||||
}
|
||||
|
||||
let line = `draw: ${index}/${n} (seed ${seedKey})`;
|
||||
if (wildcards.length) line += ` · wildcards: ${wildcards.map((w) => w.name).join(', ')}`;
|
||||
console.log(line);
|
||||
|
||||
for (const w of wildcards) {
|
||||
console.log(` wildcard · ${w.name} · lineage: ${w.lineage}`);
|
||||
console.log(` grammar: ${w.grammar}`);
|
||||
console.log(` fits: ${w.fits.join(', ')}`);
|
||||
}
|
||||
Reference in New Issue
Block a user