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);">