diff --git a/README.md b/README.md
index 9eb0e50..797e3a4 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
**A design skill that studies what you admire — and rebuilds your content with it.**
→ Live demo: **[hallmark-murex.vercel.app](https://hallmark-murex.vercel.app)** · twenty-two themes, eight worked tests, press `T` to cycle.
-→ Current version: **v0.8.0** — adds genres (editorial · modern-minimal · atmospheric · playful), `design.md` for whole-project redesigns, lazy reference loading, the 2+1 font rule with 45-gate slop test, component-library exports (tokens.css always emitted; Tailwind / DTCG / shadcn formats inline in design.md), a Study gallery on the marketing site, and seven new themes (Quiet refreshed, plus Bloom, Coral, Violet, Aurora, Halo, Plume).
+→ Current version: **v0.8.0** — adds genres (editorial · modern-minimal · atmospheric · playful), `design.md` for whole-project redesigns, lazy reference loading, the 2+1 font rule with 50-gate slop test, component-library exports (tokens.css always emitted; Tailwind / DTCG / shadcn formats inline in design.md), a Study gallery on the marketing site, and seven new themes (Quiet refreshed, plus Bloom, Coral, Violet, Aurora, Halo, Plume).
@@ -97,7 +97,7 @@ Each page is its own self-contained HTML + CSS — no shared theme, no shared la
## What's inside
-- **[`SKILL.md`](skill/SKILL.md)** — the routing file. Six-step design flow (including `Step 2.5 · Check project memory` reading `.hallmark/log.json`), 45-gate slop test, output contract, always-on `tokens.css` export.
+- **[`SKILL.md`](skill/SKILL.md)** — the routing file. Six-step design flow (including `Step 2.5 · Check project memory` reading `.hallmark/log.json`), 50-gate slop test, output contract, always-on `tokens.css` export.
- **[`references/`](skill/references/)** — short, opinionated rule files: typography, colour, layout, motion, microinteractions, interaction-and-states (with the input-state checklist), responsive, copy, anti-patterns, the 21 named macrostructures, the 36 component archetypes with variation knobs, the 6 primitive structure axes, the vision-extraction protocol for `study`, hero enrichment, custom-craft (CSS art over Lottie), assets, the slop-test gates, four genre rule-overlays, per-verb dispatchers, and the export-formats reference (Tailwind / DTCG / shadcn / tokens.css).
- **[`docs/`](docs/)** — human-reading content: **[`recipes.md`](docs/recipes.md)** (8 worked briefs + a canonical try-it prompt) and **[`study-examples.md`](docs/study-examples.md)** (3 worked DNA-extractions). Not auto-loaded by the skill.
- **[`site/`](site/)** — a self-demonstrating landing page. Hand-written HTML + CSS + ES module, no framework, no build step. **Twenty-two themes** clustered into four genres: **editorial** (Specimen, Atelier, Newsprint, Salon, Linen, Almanac, Garden, Studio, Sport, Riso, Brutal, Manifesto), **modern-minimal** (Quiet, Coral, Violet), **atmospheric** (Midnight, Terminal, Bloom, Aurora, Halo), **playful** (Pastel, Plume). Switching themes literally rebuilds the page — different hero archetype, different footer archetype.
@@ -112,7 +112,7 @@ Each page is its own self-contained HTML + CSS — no shared theme, no shared la
- **Macrostructures over axes.** Pick one of 21 named whole-page shapes wholesale; the macrostructure stamp lives in the CSS comment, so the next Hallmark run picks something different.
- **Within-archetype variation.** Two Bento Grids should not be twins; each archetype has 2–3 picked-per-output knobs.
- **Microinteractions as discipline.** Silent success over celebratory toasts. Optimistic update + Undo over confirm dialogs. Hover delay 800 ms, focus delay 0 ms.
-- **A 45-gate slop test** runs before every output. One yes fails the build. Recent additions: typography discipline gates (39–40: max three font families per page, outlier face used in ≤ 2 slots) and input-state gates (41–45: no border-width layout shift, focus ring via outline not border, input height matches button height, helper-text slot reserves height, disabled state needs three independent signals).
+- **A 50-gate slop test** runs before every output. One yes fails the build. Recent additions: typography discipline gates (39–40: max three font families per page, outlier face used in ≤ 2 slots) and input-state gates (41–45: no border-width layout shift, focus ring via outline not border, input height matches button height, helper-text slot reserves height, disabled state needs three independent signals).
- **Project memory.** A per-project `.hallmark/log.json` records each run's macrostructure + theme + enrichment + brief summary. The skill reads the last 3–5 entries before picking and writes a new entry after each build, so consecutive Hallmark outputs in the same project don't repeat shapes or themes.
- **Theme-diversification rule.** Two consecutive themes must differ on at least one of three axes: paper band (dark / mid / light), display style (italic-serif / roman-serif / geometric-sans / mono / display-heavy / system-native), accent hue (warm / cool / neutral / chromatic-other).
- **Voice fixtures over LLM defaults.** Each of the 21 macrostructures ships with 2–3 example opening lines tuned to its tone. "Built for the modern team" is in the banned-phrases list.
diff --git a/site/css/components.css b/site/css/components.css
index 00da23e..b408217 100644
--- a/site/css/components.css
+++ b/site/css/components.css
@@ -681,6 +681,181 @@
.spec__body { margin-top: var(--space-xs); }
+/* — Foundations nav (left column of the spec sheet) ——————————
+ Each item is a clickable label. The active item shows an accent
+ dot, slightly bolder name, and a subtle inset background. */
+.found-nav__btn {
+ display: grid;
+ grid-template-columns: 2.5rem 1fr 0.5rem;
+ align-items: baseline;
+ gap: var(--space-sm);
+ padding: var(--space-md) 0 var(--space-md) var(--space-sm);
+ border-block-end: var(--rule-hair) solid var(--color-rule);
+ cursor: pointer;
+ user-select: none;
+ transition: background-color var(--dur-micro) var(--ease-out),
+ padding-inline-start var(--dur-short) var(--ease-out);
+}
+
+.found-nav__btn:hover {
+ background: var(--color-paper-2);
+}
+
+.found-nav__num {
+ font-family: var(--font-label);
+ font-size: var(--text-xs);
+ letter-spacing: var(--tracking-label);
+ color: var(--color-muted);
+ font-feature-settings: "tnum";
+}
+
+.found-nav__name {
+ font-family: var(--font-display);
+ font-style: var(--display-style, normal);
+ font-weight: var(--display-weight, 500);
+ font-size: var(--text-lg);
+ letter-spacing: var(--tracking-tight);
+ color: var(--color-ink-2);
+ line-height: 1;
+}
+
+.found-nav__dot {
+ display: inline-block;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: transparent;
+ align-self: center;
+ justify-self: end;
+ transition: background-color var(--dur-short) var(--ease-out);
+}
+
+[data-theme="riso"] .found-nav__name { text-transform: lowercase; }
+[data-theme="brutal"] .found-nav__name,
+[data-theme="manifesto"] .found-nav__name,
+[data-theme="sport"] .found-nav__name { text-transform: uppercase; }
+
+/* Active state — driven by which radio is :checked */
+#f-type:checked ~ .found-grid .found-nav__btn[for="f-type"],
+#f-colour:checked ~ .found-grid .found-nav__btn[for="f-colour"],
+#f-space:checked ~ .found-grid .found-nav__btn[for="f-space"],
+#f-motion:checked ~ .found-grid .found-nav__btn[for="f-motion"],
+#f-voice:checked ~ .found-grid .found-nav__btn[for="f-voice"] {
+ background: color-mix(in oklch, var(--color-accent) 5%, transparent);
+ padding-inline-start: var(--space-md);
+}
+
+#f-type:checked ~ .found-grid .found-nav__btn[for="f-type"] .found-nav__name,
+#f-colour:checked ~ .found-grid .found-nav__btn[for="f-colour"] .found-nav__name,
+#f-space:checked ~ .found-grid .found-nav__btn[for="f-space"] .found-nav__name,
+#f-motion:checked ~ .found-grid .found-nav__btn[for="f-motion"] .found-nav__name,
+#f-voice:checked ~ .found-grid .found-nav__btn[for="f-voice"] .found-nav__name {
+ color: var(--color-ink);
+}
+
+#f-type:checked ~ .found-grid .found-nav__btn[for="f-type"] .found-nav__dot,
+#f-colour:checked ~ .found-grid .found-nav__btn[for="f-colour"] .found-nav__dot,
+#f-space:checked ~ .found-grid .found-nav__btn[for="f-space"] .found-nav__dot,
+#f-motion:checked ~ .found-grid .found-nav__btn[for="f-motion"] .found-nav__dot,
+#f-voice:checked ~ .found-grid .found-nav__btn[for="f-voice"] .found-nav__dot {
+ background: var(--color-accent);
+}
+
+/* Focus-visible outline on the *button* when its corresponding radio
+ is focused (radios are off-screen but keyboard-focusable). */
+#f-type:focus-visible ~ .found-grid .found-nav__btn[for="f-type"],
+#f-colour:focus-visible ~ .found-grid .found-nav__btn[for="f-colour"],
+#f-space:focus-visible ~ .found-grid .found-nav__btn[for="f-space"],
+#f-motion:focus-visible ~ .found-grid .found-nav__btn[for="f-motion"],
+#f-voice:focus-visible ~ .found-grid .found-nav__btn[for="f-voice"] {
+ outline: 2px solid var(--color-focus);
+ outline-offset: -2px;
+}
+
+/* — Foundations panels (right column) —————————————————————— */
+.found-panels {
+ position: relative;
+ min-height: 24rem;
+}
+
+.found-panel {
+ display: none;
+ padding-inline-start: var(--space-xl);
+ border-inline-start: var(--rule-hair) solid var(--color-rule);
+}
+
+@media (max-width: 60rem) {
+ .found-panel {
+ padding-inline-start: 0;
+ border-inline-start: 0;
+ padding-block: var(--space-md) var(--space-xl);
+ }
+}
+
+#f-type:checked ~ .found-grid .found-panels .found-panel[data-panel="type"],
+#f-colour:checked ~ .found-grid .found-panels .found-panel[data-panel="colour"],
+#f-space:checked ~ .found-grid .found-panels .found-panel[data-panel="space"],
+#f-motion:checked ~ .found-grid .found-panels .found-panel[data-panel="motion"],
+#f-voice:checked ~ .found-grid .found-panels .found-panel[data-panel="voice"] {
+ display: grid;
+ gap: var(--space-lg);
+ animation: found-fade-in 280ms var(--ease-out);
+}
+
+@keyframes found-fade-in {
+ from { opacity: 0; transform: translateY(4px); }
+ to { opacity: 1; transform: none; }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .found-panel { animation: none !important; }
+}
+
+.found-panel__rule {
+ margin: 0;
+ font-family: var(--font-serif);
+ font-style: italic;
+ font-size: var(--text-md);
+ line-height: var(--lh-normal);
+ color: var(--color-ink-2);
+ max-width: 56ch;
+}
+
+[data-theme="brutal"] .found-panel__rule,
+[data-theme="manifesto"] .found-panel__rule,
+[data-theme="terminal"] .found-panel__rule,
+[data-theme="sport"] .found-panel__rule,
+[data-theme="almanac"] .found-panel__rule,
+[data-theme="quiet"] .found-panel__rule,
+[data-theme="bloom"] .found-panel__rule,
+[data-theme="aurora"] .found-panel__rule,
+[data-theme="halo"] .found-panel__rule,
+[data-theme="coral"] .found-panel__rule,
+[data-theme="violet"] .found-panel__rule {
+ font-family: var(--font-body);
+ font-style: normal;
+}
+
+.found-panel__viz {
+ margin-block-start: 0;
+}
+
+/* Mobile accordion — each panel tucks under its corresponding nav row */
+@media (max-width: 60rem) {
+ .found-grid > .found-panels { display: contents; }
+ .found-panel { order: 0; }
+ .found-nav__btn[for="f-type"] { order: 1; }
+ .found-panel[data-panel="type"] { order: 2; }
+ .found-nav__btn[for="f-colour"] { order: 3; }
+ .found-panel[data-panel="colour"] { order: 4; }
+ .found-nav__btn[for="f-space"] { order: 5; }
+ .found-panel[data-panel="space"] { order: 6; }
+ .found-nav__btn[for="f-motion"] { order: 7; }
+ .found-panel[data-panel="motion"] { order: 8; }
+ .found-nav__btn[for="f-voice"] { order: 9; }
+ .found-panel[data-panel="voice"] { order: 10; }
+}
+
/* — Swatches ——————————————————————————————————————— */
.swatch-row {
display: grid;
@@ -1820,6 +1995,114 @@
font-feature-settings: "ss01";
}
+/* Portfolio / gallery rhythm — distinguishes Studio from Specimen.
+ Section heads gain a "Selected work · 0X" register; the photographic
+ hero crops more aggressively (16/7 ratio); pull-quote treatment leans
+ editorial-magazine instead of agency-deck. */
+
+[data-theme="studio"] .section-label::after {
+ content: " · selected work";
+ color: var(--color-muted);
+ font-style: italic;
+ text-transform: lowercase;
+ letter-spacing: 0;
+ margin-inline-start: 0.4em;
+ font-feature-settings: "ss01";
+}
+
+[data-theme="studio"] .hero__photo {
+ aspect-ratio: 16 / 7;
+ border-radius: 0;
+ border-color: var(--color-rule-2);
+}
+
+[data-theme="studio"] .hero__photo-caption {
+ font-style: italic;
+ letter-spacing: 0;
+ text-transform: none;
+ font-feature-settings: "ss01";
+}
+
+[data-theme="studio"] .ex-card {
+ background: var(--color-paper-2);
+ border-color: var(--color-rule-2);
+}
+
+[data-theme="studio"] .ex-card__name {
+ font-style: italic;
+}
+
+[data-theme="studio"] .hero__quote p {
+ font-size: clamp(2.5rem, 4vw + 1rem, 4.75rem);
+ font-variation-settings: "opsz" 144, "SOFT" 50;
+}
+
+/* ───────────── ATELIER — sketchbook / working-archive ─────────────
+ Plate-numeral marginalia beside section heads, old-style figures in
+ body text, an italic display optical-size axis for hand-set feel.
+ Earns the workshop name without slipping into the salon-formal
+ register that Salon owns. */
+
+[data-theme="atelier"] .section-label .num {
+ font-family: var(--font-display);
+ font-style: italic;
+ font-weight: 400;
+ font-size: var(--text-md);
+ font-variation-settings: "opsz" 96;
+ color: var(--color-ink);
+}
+
+[data-theme="atelier"] .section-label::after {
+ content: " · plate";
+ color: var(--color-muted);
+ font-style: italic;
+ text-transform: lowercase;
+ letter-spacing: 0;
+ margin-inline-start: 0.4em;
+}
+
+[data-theme="atelier"] .section__title {
+ font-variation-settings: "opsz" 144, "SOFT" 60;
+}
+
+[data-theme="atelier"] .hero__display,
+[data-theme="atelier"] .hero__quote p,
+[data-theme="atelier"] .hero__salutation {
+ font-variation-settings: "opsz" 144, "SOFT" 70;
+}
+
+/* Old-style figures in body text — reads as hand-set, not synthesised */
+[data-theme="atelier"] .hero__lede,
+[data-theme="atelier"] .hero__letter-body,
+[data-theme="atelier"] .spec__rule,
+[data-theme="atelier"] .found-panel__rule,
+[data-theme="atelier"] .install-pane__next,
+[data-theme="atelier"] .ex-card__brief {
+ font-variant-numeric: oldstyle-nums;
+}
+
+/* Tabular numerals in the example tile counter — reads as catalogue */
+[data-theme="atelier"] .ex-card__num,
+[data-theme="atelier"] .gallery__num,
+[data-theme="atelier"] .found-nav__num {
+ font-variant-numeric: tabular-nums;
+ font-feature-settings: "tnum";
+}
+
+[data-theme="atelier"] .ex-card {
+ border-style: solid;
+ border-color: var(--color-rule);
+ background: var(--color-paper-2);
+}
+
+[data-theme="atelier"] .ex-card__open::before {
+ content: "see plate ";
+ color: var(--color-muted);
+ text-transform: lowercase;
+ font-style: italic;
+ letter-spacing: 0;
+}
+
/* ───────────── PASTEL — Geist sans display, soft surfaces ───────────── */
[data-theme="pastel"] .hero__display,
@@ -3993,6 +4276,50 @@ html[data-theme="halo"] aside {
line-height: 1.05;
}
+/* Sport — kinetic / scoreboard register.
+ Tabular numerals everywhere a number appears (counter, stat,
+ pricing, plate numbers); a top-bordered "leaderboard cell" treatment
+ on the section numeral; sharper sweep easing on .reveal. */
+
+[data-theme="sport"] .hero__stat,
+[data-theme="sport"] .ex-card__num,
+[data-theme="sport"] .gallery__num,
+[data-theme="sport"] .spec__num,
+[data-theme="sport"] .found-nav__num,
+[data-theme="sport"] .vs-toggle__num,
+[data-theme="sport"] .vs__panel-stat-num,
+[data-theme="sport"] .vs__panel-price-num,
+[data-theme="sport"] .install-pane__step,
+[data-theme="sport"] .dna__v {
+ font-variant-numeric: tabular-nums;
+ font-feature-settings: "tnum";
+}
+
+/* Section numeral as a leaderboard-style cell with a thin top rule */
+[data-theme="sport"] .section-label .num {
+ display: inline-block;
+ padding: 0.15em 0.45em 0.05em;
+ border-block-start: 2px solid var(--color-accent);
+ background: color-mix(in oklch, var(--color-accent) 6%, transparent);
+ font-variant-numeric: tabular-nums;
+ font-feature-settings: "tnum";
+}
+
+/* Sharper sweep easing on Sport reveals — sportier */
+[data-theme="sport"] .reveal.is-in {
+ animation-timing-function: cubic-bezier(0.86, 0, 0.07, 1);
+}
+
+/* Hero stat-row treatment — "MIN · 47 · LAP 12" register, when present */
+[data-theme="sport"] .hero__qualifier {
+ font-family: var(--font-mono);
+ font-style: normal;
+ font-size: var(--text-md);
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ color: var(--color-muted);
+}
+
/* ───────────── SALON — true-centred section heads + fleuron ─────────────
Salon's existing grid override (in sections.css) was fine but the
eyebrow + title weren't perfectly stacked. Tighten the gap between
diff --git a/site/css/sections.css b/site/css/sections.css
index 00a1dc7..54fcce2 100644
--- a/site/css/sections.css
+++ b/site/css/sections.css
@@ -20,6 +20,7 @@
.section {
display: grid;
gap: var(--space-2xl);
+ position: relative; /* anchors absolute children (hidden radios) */
}
.section__head {
@@ -214,6 +215,14 @@
the input at top:0 of the viewport; clicking a label far below
would scroll the page back to the top. */
.vs-toggle__input {
+ /* Pulled out of the section's grid (otherwise each hidden radio plus each
+ hidden .vs panel adds a `var(--space-2xl)` phantom gap between the
+ section header and the first visible child). Anchored absolute inside
+ the section (which is position: relative) so focus doesn't scroll the
+ page back to the viewport top. */
+ position: absolute;
+ top: 0;
+ left: 0;
appearance: none;
-webkit-appearance: none;
width: 0;
@@ -223,7 +232,6 @@
border: 0;
outline: 0;
opacity: 0;
- flex: 0 0 0;
pointer-events: none;
}
@@ -364,6 +372,56 @@
.spec--feature, .spec--strip { grid-column: auto; }
}
+/* — 05 · Foundations as vertical list + active panel ———————
+ Five clickable names on the left; the active card's rule + visual
+ demo on the right. Pure CSS via :checked + sibling selectors —
+ the radios sit absolutely-positioned (out of flow, like the W/W
+ toggle radios). Fits in ~one viewport on desktop. */
+.found-toggle__input {
+ position: absolute;
+ top: 0;
+ left: 0;
+ appearance: none;
+ -webkit-appearance: none;
+ width: 0;
+ height: 0;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ opacity: 0;
+ pointer-events: none;
+}
+
+.found-grid {
+ display: grid;
+ grid-template-columns: 14rem 1fr;
+ gap: var(--space-2xl);
+ align-items: start;
+}
+
+@media (max-width: 60rem) {
+ .found-grid {
+ grid-template-columns: 1fr;
+ gap: 0;
+ }
+}
+
+.found-nav {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: grid;
+ gap: 0;
+ border-block-start: var(--rule-hair) solid var(--color-rule);
+}
+
+.found-nav li { margin: 0; }
+
+@media (max-width: 60rem) {
+ .found-nav { display: contents; }
+}
+
/* — 05 · How it works ——————————————————————————————— */
.how { display: grid; gap: var(--space-xl); }
diff --git a/site/css/tokens.css b/site/css/tokens.css
index cc0569a..b6e9356 100644
--- a/site/css/tokens.css
+++ b/site/css/tokens.css
@@ -687,18 +687,21 @@
────────────────────────────────────────────────────────────── */
[data-theme="pastel"] {
- --color-paper: oklch(96% 0.022 285);
- --color-paper-2: oklch(93.5% 0.026 285);
- --color-paper-3: oklch(90% 0.030 285);
- --color-rule: oklch(82% 0.024 280);
- --color-rule-2: oklch(72% 0.030 275);
- --color-muted: oklch(50% 0.030 275);
- --color-neutral: oklch(38% 0.038 270);
- --color-ink-2: oklch(28% 0.040 270);
- --color-ink: oklch(22% 0.040 270);
- --color-accent: oklch(56% 0.130 282);
- --color-accent-ink: oklch(56% 0.130 282);
- --color-focus: oklch(56% 0.130 282);
+ /* Cool near-white paper — distinguishes from Plume's warm cream.
+ The "design-tool" register: cool ground, low-chroma indigo, soft
+ surfaces. No more overlap with the warm-soft register. */
+ --color-paper: oklch(98% 0.008 240);
+ --color-paper-2: oklch(96% 0.010 240);
+ --color-paper-3: oklch(93% 0.012 240);
+ --color-rule: oklch(88% 0.012 240);
+ --color-rule-2: oklch(76% 0.014 240);
+ --color-muted: oklch(52% 0.018 250);
+ --color-neutral: oklch(40% 0.022 255);
+ --color-ink-2: oklch(28% 0.025 260);
+ --color-ink: oklch(20% 0.028 265);
+ --color-accent: oklch(54% 0.110 282); /* slightly lower chroma indigo */
+ --color-accent-ink: oklch(98% 0.008 240);
+ --color-focus: oklch(54% 0.110 282);
--font-display: "Geist", "Söhne", ui-sans-serif, system-ui, sans-serif;
--font-body: "Geist", "Söhne", ui-sans-serif, system-ui, sans-serif;
diff --git a/site/index.html b/site/index.html
index 6f12204..c63c51f 100644
--- a/site/index.html
+++ b/site/index.html
@@ -91,7 +91,7 @@
+ style="--dot: oklch(98% 0.008 240); --dot-edge: oklch(54% 0.110 282);">