mirror of
https://github.com/Nutlope/hallmark.git
synced 2026-08-14 12:35:33 +02:00
Slop test · consolidate 70 gates → 57 (merge redundant) and resync every reference
Merge the clear duplicates (gradient text into the gradient gate, macrostructure- repeat into the structural-fingerprint gate, the overflow-x:clip mandate into the horizontal-scroll gate, emoji-icon into the icon-libraries gate), collapse the five contrast gates to two, the five input-state gates to one, and the four hero gates to two. No coverage lost. Renumber 1–57 and update every gate-number and gate-total reference across SKILL.md, the reference docs, the landing copy, and the example builds.
This commit is contained in:
@@ -10,7 +10,7 @@ Made by Together AI.
|
||||
<img src="site/OG-hallmark.png" alt="Hallmark — A design skill that refuses to look AI-generated" />
|
||||
</p>
|
||||
|
||||
Hallmark picks a macrostructure for the brief, dresses it in one of twenty themes, runs seventy slop-test gates plus a pre-emit self-critique, and refuses the on-distribution defaults every LLM was trained into. Two pages by Hallmark for two different briefs feel like different sites, not colour-swaps of the same template.
|
||||
Hallmark picks a macrostructure for the brief, dresses it in one of twenty themes, runs fifty-seven slop-test gates plus a pre-emit self-critique, and refuses the on-distribution defaults every LLM was trained into. Two pages by Hallmark for two different briefs feel like different sites, not colour-swaps of the same template.
|
||||
|
||||
---
|
||||
|
||||
@@ -62,7 +62,7 @@ Each page is self-contained HTML + CSS, stamped with its macrostructure in the C
|
||||
|
||||
## Custom <sup>NEW</sup>
|
||||
|
||||
Most briefs route to the catalog. But when a brief carries real creative intent — a named brand colour, a vibe the catalog can't carry, or a layout that *is* the point — Hallmark switches to **Custom** and designs the whole page from first principles: a made-to-measure OKLCH palette, a free-font pairing, and a structure no catalog theme would produce. Same seventy slop-test gates. No template underneath.
|
||||
Most briefs route to the catalog. But when a brief carries real creative intent — a named brand colour, a vibe the catalog can't carry, or a layout that *is* the point — Hallmark switches to **Custom** and designs the whole page from first principles: a made-to-measure OKLCH palette, a free-font pairing, and a structure no catalog theme would produce. Same fifty-seven slop-test gates. No template underneath.
|
||||
|
||||
Four one-line briefs, four pages that share nothing:
|
||||
|
||||
|
||||
@@ -3542,7 +3542,7 @@ html[data-theme="bloom"] body {
|
||||
/* Bloom — left-aligned section heads (hero stays centered above). The
|
||||
atmospheric glow does the centering work — re-centering section heads
|
||||
reads as a templated AI fingerprint. Tag stays above the title in the
|
||||
same column (matches slop-test gate 66). */
|
||||
same column (matches slop-test gate 54). */
|
||||
[data-theme="bloom"] .section__head {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: left;
|
||||
|
||||
@@ -149,7 +149,7 @@ p { margin: 0; }
|
||||
/* no forced nowrap — let the line break on narrow sheets */
|
||||
.broadside__title-2 {
|
||||
display: block;
|
||||
font-weight: 500; /* roman — emphasis via weight, never italic (gate 40a) */
|
||||
font-weight: 500; /* roman — emphasis via weight, never italic (gate 38a) */
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
/* the drop-figure: an oversized initial in the printer's red */
|
||||
|
||||
@@ -153,7 +153,7 @@ body {
|
||||
letter-spacing: var(--track-tight);
|
||||
color: var(--ink);
|
||||
/* keep words whole at comfortable widths; the narrow breakpoint switches to
|
||||
break-word as a last resort so long names can't overflow (gate 63) */
|
||||
break-word as a last resort so long names can't overflow (gate 51) */
|
||||
overflow-wrap: normal;
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -613,7 +613,7 @@ body {
|
||||
never under it; the puck is var(--stamp-size-s) + its inset offset */
|
||||
.nameplate { padding-right: var(--stamp-clear); }
|
||||
/* narrow widths: allow last-resort intra-word breaks so a long station/line
|
||||
name can never overflow the slimmer column (gate 63) */
|
||||
name can never overflow the slimmer column (gate 51) */
|
||||
.nameplate__line { font-size: var(--text-xl); overflow-wrap: break-word; }
|
||||
|
||||
.perforation__rule { letter-spacing: 0.08em; }
|
||||
|
||||
@@ -159,7 +159,7 @@ button { font: inherit; color: inherit; }
|
||||
Intro / central explainer
|
||||
=================================================================== */
|
||||
.intro {
|
||||
/* hero pads heavier at the bottom (gate 54): it sits INTO the table
|
||||
/* hero pads heavier at the bottom (gate 44): it sits INTO the table
|
||||
below rather than floating. block-end ≈ 2× block-start. */
|
||||
max-width: var(--maxw-page);
|
||||
margin-inline: auto;
|
||||
|
||||
@@ -284,7 +284,7 @@ body::after { /* directional grain — fine diagonal hatch */
|
||||
padding-top: clamp(var(--space-xl), 5vw, var(--space-3xl));
|
||||
}
|
||||
.spine__head {
|
||||
display: block; /* gate 66 — eyebrow + heading single column */
|
||||
display: block; /* gate 54 — eyebrow + heading single column */
|
||||
margin-bottom: clamp(var(--space-xl), 6vw, var(--space-3xl));
|
||||
}
|
||||
.spine__eyebrow {
|
||||
|
||||
@@ -53,7 +53,7 @@ ul, ol { list-style: none; }
|
||||
padding-inline: var(--gutter);
|
||||
}
|
||||
|
||||
/* timecode + name stamp that opens every section (vertical stack — gate 66) */
|
||||
/* timecode + name stamp that opens every section (vertical stack — gate 54) */
|
||||
.stamp {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
@@ -343,7 +343,7 @@ section[tabindex]:focus { outline: none; }
|
||||
.track--b .track__copy { grid-column: 1; grid-row: 1 / 3; align-self: center; }
|
||||
.track--b .minwave { grid-column: 1 / -1; }
|
||||
|
||||
.track__head { grid-column: 1; } /* eyebrow + heading stacked (gate 66) */
|
||||
.track__head { grid-column: 1; } /* eyebrow + heading stacked (gate 54) */
|
||||
.track__title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 460;
|
||||
|
||||
+6
-6
@@ -56,10 +56,10 @@
|
||||
<link rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;600;700;800&family=Anton&family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,400;0,9..144,500;0,9..144,600;0,9..144,900;1,9..144,400;1,9..144,500&family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&family=Hanken+Grotesk:wght@400;500;600;700&family=Inter:wght@400;500;600;700&family=Inter+Tight:ital,wght@0,500;0,600;0,700;0,800;1,500;1,600;1,700&family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;1,6..72,400;1,6..72,500&family=Playfair+Display:ital,wght@1,400;1,500;1,600&family=Public+Sans:wght@400;500;600;700&family=Young+Serif&family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" />
|
||||
|
||||
<link rel="stylesheet" href="css/tokens.css?v=17" />
|
||||
<link rel="stylesheet" href="css/base.css?v=17" />
|
||||
<link rel="stylesheet" href="css/components.css?v=17" />
|
||||
<link rel="stylesheet" href="css/sections.css?v=17" />
|
||||
<link rel="stylesheet" href="css/tokens.css?v=18" />
|
||||
<link rel="stylesheet" href="css/base.css?v=18" />
|
||||
<link rel="stylesheet" href="css/components.css?v=18" />
|
||||
<link rel="stylesheet" href="css/sections.css?v=18" />
|
||||
|
||||
<script>
|
||||
// Load theme before paint to avoid flash. Order: ?theme= → localStorage → default.
|
||||
@@ -1127,7 +1127,7 @@
|
||||
<ul class="foot__stats">
|
||||
<li><span class="foot__stat-num tnum">20</span><span class="foot__stat-key">themes</span></li>
|
||||
<li><span class="foot__stat-num tnum">21</span><span class="foot__stat-key">macrostructures</span></li>
|
||||
<li><span class="foot__stat-num tnum">70</span><span class="foot__stat-key">slop-test gates</span></li>
|
||||
<li><span class="foot__stat-num tnum">57</span><span class="foot__stat-key">slop-test gates</span></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -1148,7 +1148,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="module" src="js/main.js?v=17"></script>
|
||||
<script type="module" src="js/main.js?v=18"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+9
-9
@@ -141,7 +141,7 @@ const COPY = {
|
||||
proofLabel: "Proof",
|
||||
proofA: "21 macrostructures · 50 archetypes",
|
||||
proofB: "14 navs · 8 footers · 4 hero polish",
|
||||
proofC: "70-gate slop test",
|
||||
proofC: "57-gate slop test",
|
||||
cta: "Read the rules",
|
||||
stat: "21",
|
||||
qualifier: "macrostructures",
|
||||
@@ -156,14 +156,14 @@ const COPY = {
|
||||
newsprint: {
|
||||
eyebrow: "Volume I · Issue 02 · 28 April 2026",
|
||||
title: HERO_TITLE,
|
||||
lede: "Twenty themes. Twenty-one named page shapes. Fifty component archetypes — fourteen navs, eight footers, four hero polish patterns. A 70-gate slop test that gates every output before it ships. Hallmark is the rulebook the LLM never read.",
|
||||
lede: "Twenty themes. Twenty-one named page shapes. Fifty component archetypes — fourteen navs, eight footers, four hero polish patterns. A 57-gate slop test that gates every output before it ships. Hallmark is the rulebook the LLM never read.",
|
||||
ctaLabel: "Distribution",
|
||||
proofLabel: "From the rule sheet",
|
||||
proofA: "Multi-column body, justified",
|
||||
proofB: "Hairline rules, not boxes",
|
||||
proofC: "Outlined CTAs, never pills",
|
||||
cta: "Read in full",
|
||||
stat: "70",
|
||||
stat: "57",
|
||||
qualifier: "gates",
|
||||
quote: "We compose the page like a broadsheet. Hairlines, columns, restraint.",
|
||||
attrib: "From the Hallmark rule sheet",
|
||||
@@ -221,14 +221,14 @@ const COPY = {
|
||||
proofLabel: "Catalogued",
|
||||
proofA: "21 macrostructures · 50 archetypes",
|
||||
proofB: "14 navs · 8 footers · 4 polish patterns",
|
||||
proofC: "70 slop-test gates",
|
||||
proofC: "57 slop-test gates",
|
||||
cta: "Open the index",
|
||||
stat: "420",
|
||||
qualifier: "theme × shape combinations",
|
||||
quote: "An almanac is a book that knows where to look.",
|
||||
attrib: "Almanac, frontispiece",
|
||||
salutation: "Reference note,",
|
||||
letterBody: "This page is a reference, not an argument. The numbers are the point: 21 macrostructures, 50 archetypes, 20 themes, 70 slop-test gates. Cross-referenced so the next page Hallmark builds is genuinely different from the last.",
|
||||
letterBody: "This page is a reference, not an argument. The numbers are the point: 21 macrostructures, 50 archetypes, 20 themes, 57 slop-test gates. Cross-referenced so the next page Hallmark builds is genuinely different from the last.",
|
||||
signoff: "— editor",
|
||||
captionA: "Vol. III",
|
||||
captionB: "Plate 12",
|
||||
@@ -465,7 +465,7 @@ const COPY = {
|
||||
proofLabel: "ON THE BILL",
|
||||
proofA: "21 page-shapes, 50 archetypes",
|
||||
proofB: "Duo-tone drops, hard-offset shadows",
|
||||
proofC: "70-gate slop test, every build",
|
||||
proofC: "57-gate slop test, every build",
|
||||
cta: "See the lineup",
|
||||
stat: "20",
|
||||
qualifier: "THEMES. ZERO TEMPLATES.",
|
||||
@@ -485,7 +485,7 @@ const COPY = {
|
||||
proofLabel: "00 · SPEC",
|
||||
proofA: "21 page-shapes · 50 archetypes",
|
||||
proofB: "lowercase serif · mono labels · no orb",
|
||||
proofC: "70-gate slop test, every emit",
|
||||
proofC: "57-gate slop test, every emit",
|
||||
cta: "read the docs",
|
||||
stat: "20",
|
||||
qualifier: "themes, instrument-grade.",
|
||||
@@ -500,12 +500,12 @@ const COPY = {
|
||||
hum: {
|
||||
eyebrow: "A design skill",
|
||||
title: "Make your AI agent a designer.",
|
||||
lede: "The anti-slop design skill for Claude Code, Cursor, and Codex. Twenty themes with real structural variety, and a 70-gate check before anything ships.",
|
||||
lede: "The anti-slop design skill for Claude Code, Cursor, and Codex. Twenty themes with real structural variety, and a 57-gate check before anything ships.",
|
||||
ctaLabel: "Install",
|
||||
proofLabel: "What's inside",
|
||||
proofA: "21 page-shapes · 50 archetypes",
|
||||
proofB: "9 heroes · 14 navs · 8 footers",
|
||||
proofC: "70-gate slop test, every build",
|
||||
proofC: "57-gate slop test, every build",
|
||||
cta: "See examples",
|
||||
stat: "20",
|
||||
qualifier: "themes, all alive.",
|
||||
|
||||
+12
-12
@@ -45,15 +45,15 @@ These six disciplines are **not** verb-specific. They apply to default Design, `
|
||||
|
||||
1. **Pre-emit self-critique.** Before handing back any output, score it 1–5 on six axes — Philosophy, Hierarchy, Execution, Specificity, Restraint, Variety. Anything **< 3** triggers a revision pass. Stamp the six scores at the top of the artifact (`/* Hallmark · pre-emit critique: P5 H4 E5 S4 R5 V5 */`). See [`references/slop-test.md`](references/slop-test.md) § Pre-emit self-critique.
|
||||
|
||||
2. **Honest copy — no fabricated content.** If the user did not supply a metric, do not invent one. Stat-led layouts, comparison rows, and proof bars must use real numbers, a placeholder (`—` plus a labelled grey block, "metric to confirm"), or a different macrostructure. *"+47 % conversion"*, *"trusted by 50,000+ teams"*, and *"10× faster"* are slop the moment they're invented. Same rule for testimonials, logos, and case-study counts. See [`references/anti-patterns.md` § Invented metrics](references/anti-patterns.md) and slop-test gate **56**.
|
||||
2. **Honest copy — no fabricated content.** If the user did not supply a metric, do not invent one. Stat-led layouts, comparison rows, and proof bars must use real numbers, a placeholder (`—` plus a labelled grey block, "metric to confirm"), or a different macrostructure. *"+47 % conversion"*, *"trusted by 50,000+ teams"*, and *"10× faster"* are slop the moment they're invented. Same rule for testimonials, logos, and case-study counts. See [`references/anti-patterns.md` § Invented metrics](references/anti-patterns.md) and slop-test gate **46**.
|
||||
|
||||
3. **Locked tokens — no mid-render improvisation.** Once a theme is selected at Step 2.6, every colour and every `font-family` declaration in the artifact must reference a named token (`var(--color-accent)`, `font-family: var(--font-display)`). Inline OKLCH / hex / `rgb()` values, or a `font-family: "Some Font"` declaration that bypasses the token block, are not allowed. If a value is needed that doesn't exist as a token, lift it into the token block as a new named variable, then reference it. See [`references/anti-patterns.md` § Mid-render token improvisation](references/anti-patterns.md) and slop-test gate **58**.
|
||||
3. **Locked tokens — no mid-render improvisation.** Once a theme is selected at Step 2.6, every colour and every `font-family` declaration in the artifact must reference a named token (`var(--color-accent)`, `font-family: var(--font-display)`). Inline OKLCH / hex / `rgb()` values, or a `font-family: "Some Font"` declaration that bypasses the token block, are not allowed. If a value is needed that doesn't exist as a token, lift it into the token block as a new named variable, then reference it. See [`references/anti-patterns.md` § Mid-render token improvisation](references/anti-patterns.md) and slop-test gate **48**.
|
||||
|
||||
4. **Re-drawn chrome forbidden.** Hallmark must not hand-build fake browser bars (URL pill + traffic-light dots), fake phone frames, fake code-block windows (mock title bar + dots wrapping a `<pre>`), or fake IDE chrome — the user's environment already supplies real chrome. Use real screenshots wrapped in a `<figure>` (with at most a hairline border), or omit the chrome and let the content stand on its own. See [`references/anti-patterns.md` § Re-drawn UI chrome](references/anti-patterns.md) and slop-test gate **57**.
|
||||
4. **Re-drawn chrome forbidden.** Hallmark must not hand-build fake browser bars (URL pill + traffic-light dots), fake phone frames, fake code-block windows (mock title bar + dots wrapping a `<pre>`), or fake IDE chrome — the user's environment already supplies real chrome. Use real screenshots wrapped in a `<figure>` (with at most a hairline border), or omit the chrome and let the content stand on its own. See [`references/anti-patterns.md` § Re-drawn UI chrome](references/anti-patterns.md) and slop-test gate **47**.
|
||||
|
||||
5. **Mobile responsiveness — every emit verified at 320 / 375 / 414 / 768 px.** Hallmark's output must render flawlessly at all four widths. The non-negotiables: no horizontal scroll (gate 36), no two-line clickable text — buttons, primary nav links, footer links, breadcrumbs, CTAs (gate 59); image-bearing grid tracks use `minmax(0, 1fr)`, never bare `1fr` (gate 61); root has `overflow-x: clip` on both `html` and `body` — never `hidden` (gate 62); display headers wrap inside long words via `overflow-wrap: anywhere; min-width: 0` (gate 63); section heads collapse to one column on mobile across every theme variant (gate 64); radio-tab patterns don't scroll-jump (gate 65). See [`references/responsive.md` § Mobile — non-negotiable](references/responsive.md). This is a hard floor, not a wish list.
|
||||
5. **Mobile responsiveness — every emit verified at 320 / 375 / 414 / 768 px.** Hallmark's output must render flawlessly at all four widths. The non-negotiables: no horizontal scroll + root `overflow-x: clip` on both `html` and `body`, never `hidden` (gate 34); no two-line clickable text — buttons, primary nav links, footer links, breadcrumbs, CTAs (gate 49); image-bearing grid tracks use `minmax(0, 1fr)`, never bare `1fr` (gate 50); display headers wrap inside long words via `overflow-wrap: anywhere; min-width: 0` (gate 51); section heads collapse to one column on mobile across every theme variant (gate 52); radio-tab patterns don't scroll-jump (gate 53). See [`references/responsive.md` § Mobile — non-negotiable](references/responsive.md). This is a hard floor, not a wish list.
|
||||
|
||||
6. **Typography purity — no italic headers.** Headings and display type are always roman (`font-style: normal`). An italicised emphasis word inside an otherwise-upright heading (`Built to <em>think</em>`) is one of the most reliable AI tells; so is an all-italic display face on headings. Carry emphasis with weight, accent colour, or a drawn underline. Italic survives only as *body-copy* emphasis inside running paragraphs. See [`references/anti-patterns.md` § Italic headers](references/anti-patterns.md) and slop-test gate **40a**.
|
||||
6. **Typography purity — no italic headers.** Headings and display type are always roman (`font-style: normal`). An italicised emphasis word inside an otherwise-upright heading (`Built to <em>think</em>`) is one of the most reliable AI tells; so is an all-italic display face on headings. Carry emphasis with weight, accent colour, or a drawn underline. Italic survives only as *body-copy* emphasis inside running paragraphs. See [`references/anti-patterns.md` § Italic headers](references/anti-patterns.md) and slop-test gate **38a**.
|
||||
|
||||
---
|
||||
|
||||
@@ -77,7 +77,7 @@ If two signals fire, route component. If only the page flow fires (multi-section
|
||||
- **Step 2.6 · Theme route** — same. If a `tokens.css` or `design.md` exists, the component uses those tokens. Otherwise it asks "is there a system to follow, or should I pick one?" — defaulting to *catalog* if the user is silent.
|
||||
- **2+1 font discipline** — same.
|
||||
- **State discipline — STRICTER.** Every interactive component MUST ship code for **all 8 states**: default · hover · `:focus-visible` · `:active` · disabled · loading · error · success. The 8-state checklist in [`interaction-and-states.md`](references/interaction-and-states.md) is mandatory, not advisory.
|
||||
- **Slop test — universal-only subset.** Run the visual / microinteraction / contrast (gates 46–50) / a11y / typography gates. Skip the diversification gates (no `.hallmark/log.json` entry — components don't rotate) and skip the layout-safety gates that assume a full page.
|
||||
- **Slop test — universal-only subset.** Run the visual / microinteraction / contrast (gates 40–41) / a11y / typography gates. Skip the diversification gates (no `.hallmark/log.json` entry — components don't rotate) and skip the layout-safety gates that assume a full page.
|
||||
|
||||
### What Component-scope skips
|
||||
|
||||
@@ -341,7 +341,7 @@ By the time you reach this step, one of four things is true:
|
||||
|
||||
**Custom is a quiet branch, not a default question.** Most briefs route to catalog and the user never sees the words "catalog" or "custom." The 20 named themes plus the rotation rule already deliver structural variety; the fork is reserved for when the brief specifically asks for a tuned look the catalog can't carry.
|
||||
|
||||
A custom theme is a **complete** OKLCH palette + font pairing tuned to the brief — not a one-off colour swap, not an excuse to bypass the rules. Every constraint in [`color.md`](references/color.md), [`typography.md`](references/typography.md), and [`anti-patterns.md`](references/anti-patterns.md) still applies. The 70 slop-test gates fire unchanged. The Step 5 preview block surfaces the palette + pairing in plain text **before** any code is emitted, so the user can redirect.
|
||||
A custom theme is a **complete** OKLCH palette + font pairing tuned to the brief — not a one-off colour swap, not an excuse to bypass the rules. Every constraint in [`color.md`](references/color.md), [`typography.md`](references/typography.md), and [`anti-patterns.md`](references/anti-patterns.md) still applies. The 57 slop-test gates fire unchanged. The Step 5 preview block surfaces the palette + pairing in plain text **before** any code is emitted, so the user can redirect.
|
||||
|
||||
The diversification rule is theme-route-blind: a custom run that follows another custom (or a catalog) must differ on at least one of the three axes from the previous entry, same as catalog-vs-catalog. Custom entries record their three axes explicitly into `.hallmark/log.json` (see [`custom-theme.md`](references/custom-theme.md) § F).
|
||||
|
||||
@@ -378,7 +378,7 @@ The non-negotiables live in [`references/`](references/). **Be precise about wha
|
||||
- [`preview-examples.md`](references/preview-examples.md) — load only if you need a worked example of the Step 5 preview block format. The bullet list in Step 5 itself is normally enough; reach for the file only when picking unusual macrostructures / custom themes.
|
||||
|
||||
**Load-at-the-end (Step 7 only):**
|
||||
- [`slop-test.md`](references/slop-test.md) — **strictly Step 7, after Build.** The 70 gates are a post-emit check, not a pre-emit reference. Pre-loading slop-test.md costs ~7K tokens for nothing — the gates inform fixes, not generation. If a gate fails at Step 7, fix and re-test; do not consult the file earlier "to know what to avoid" — that's what `anti-patterns.md` is for.
|
||||
- [`slop-test.md`](references/slop-test.md) — **strictly Step 7, after Build.** The 57 gates are a post-emit check, not a pre-emit reference. Pre-loading slop-test.md costs ~7K tokens for nothing — the gates inform fixes, not generation. If a gate fails at Step 7, fix and re-test; do not consult the file earlier "to know what to avoid" — that's what `anti-patterns.md` is for.
|
||||
- [`contract.md`](references/contract.md) — load at handoff time for output-contract + scope rules.
|
||||
- [`export-formats.md`](references/export-formats.md) — load at Step 6 only when the project warrants multi-format exports (i.e. has a `design.md`). Single-page builds emit `tokens.css` from the in-memory token state and don't need this file.
|
||||
|
||||
@@ -416,7 +416,7 @@ Before emitting any code, output a tight summary of what you're about to ship. T
|
||||
- **Enrichment** · none (typography only)
|
||||
- **Sections** · Hero · Logos · Stats · Features · Testimonials · Pricing · FAQ · CTA · Footer
|
||||
- **Motion** · counter · pricing-lift · pulse-once
|
||||
- **Slop test** · 70 / 70 ✓ (run after Build)
|
||||
- **Slop test** · 57 / 57 ✓ (run after Build)
|
||||
- **Diversification** · differs from Newsprint on display style + accent hue
|
||||
```
|
||||
|
||||
@@ -427,7 +427,7 @@ Before emitting any code, output a tight summary of what you're about to ship. T
|
||||
3. **Enrichment** — the chosen archetype + tier, or *none (typography only)*.
|
||||
4. **Sections** — section names separated by ` · `, in DOM order.
|
||||
5. **Motion** — microinteraction primitives separated by ` · `, or *none — typography only*. Always under three primitives per the [`microinteractions.md`](references/microinteractions.md) hard rules.
|
||||
6. **Slop test** — `70 / 70 ✓` if all gates pass, or `N / 70 — fails: <gate numbers>` if any are open. Run the slop test BEFORE writing this row; the slop test is Step 7.
|
||||
6. **Slop test** — `57 / 57 ✓` if all gates pass, or `N / 57 — fails: <gate numbers>` if any are open. Run the slop test BEFORE writing this row; the slop test is Step 7.
|
||||
7. **Diversification** *(optional, only when `.hallmark/log.json` has prior entries)* — what axes differ vs the previous run.
|
||||
|
||||
**Then one quiet CTA line, italicised, after the bullets:**
|
||||
@@ -447,7 +447,7 @@ Emit code that satisfies the tone and structural fingerprint. Match the complexi
|
||||
Always:
|
||||
|
||||
- **Hero headline — match font-size to copy length.** When you write the headline yourself (no user-supplied copy), aim for **≤ 7 words and ≤ 50 chars** from the start. For longer headlines, apply the size-by-length brackets in [`typography.md § Hero headline sizing`](references/typography.md): 21–50 chars use `--text-display`; 51–90 chars cap at `--text-display-s`; > 90 chars rewrite shorter or cap at `--text-4xl`. Aggressive-display themes (Brutal, Riso, Manifesto) auto-step down one rung past 50 chars — their 6.5–9rem ceiling is for short statements only.
|
||||
- **Section tags / eyebrows — default OFF.** Do NOT emit `01 · THE TOUR`, `02 / FEATURES`, `Chapter Three`, or any uppercase mono-cap section number / kicker / label unless either (a) the user explicitly asked for chapter / step / section numbering, OR (b) the macrostructure is Long Document, Manifesto, or Catalogue numbered AND the content is genuinely ordinal. Cap at 1–2 per page even then. **When a tag IS used, always stack vertical — tag above, heading directly underneath in the same column.** The tag-left / heading-right two-column pattern (a.k.a. hanging header, left-margin label) is banned outright — it is the single most reliable templated-editorial tell, and slop-test gate **66** auto-fails it.
|
||||
- **Section tags / eyebrows — default OFF.** Do NOT emit `01 · THE TOUR`, `02 / FEATURES`, `Chapter Three`, or any uppercase mono-cap section number / kicker / label unless either (a) the user explicitly asked for chapter / step / section numbering, OR (b) the macrostructure is Long Document, Manifesto, or Catalogue numbered AND the content is genuinely ordinal. Cap at 1–2 per page even then. **When a tag IS used, always stack vertical — tag above, heading directly underneath in the same column.** The tag-left / heading-right two-column pattern (a.k.a. hanging header, left-margin label) is banned outright — it is the single most reliable templated-editorial tell, and slop-test gate **54** auto-fails it.
|
||||
- Use OKLCH for every colour. Declare tokens as CSS custom properties at `:root`.
|
||||
- Use a 4pt spacing scale with semantic names (`--space-sm`, `--space-md`, …).
|
||||
- Pick a distinctive display face and a refined body face. Pairings, not single-font pages — *unless* the single-font choice IS the design (a true terminal-aesthetic page is monospace-only on purpose; that's allowed).
|
||||
@@ -466,7 +466,7 @@ Always:
|
||||
|
||||
### 7. The slop test
|
||||
|
||||
Before handing back, run the output through the 70-gate slop test in [`references/slop-test.md`](references/slop-test.md). Every answer must be **no**. Load that file at this step (not earlier — it isn't needed until handoff). The active genre matters: some gates are universal, some are genre-scoped (atmospheric loosens the radial-bloom gate; modern-minimal loosens the zero-chroma neutral gate; etc.). The full per-genre overrides are listed inline in `slop-test.md`.
|
||||
Before handing back, run the output through the 57-gate slop test in [`references/slop-test.md`](references/slop-test.md). Every answer must be **no**. Load that file at this step (not earlier — it isn't needed until handoff). The active genre matters: some gates are universal, some are genre-scoped (atmospheric loosens the radial-bloom gate; modern-minimal loosens the zero-chroma neutral gate; etc.). The full per-genre overrides are listed inline in `slop-test.md`.
|
||||
|
||||
Run the slop test BEFORE writing the Slop test row in the Step 5 preview block — that row reflects the actual outcome of this step.
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ Eyebrows are **default OFF**. They are not a stylistic flourish you reach for to
|
||||
|
||||
**Fix.** Ship the page with **zero eyebrows** unless either (a) the user explicitly asked for chapter / step / section numbering, OR (b) the macrostructure is **Long Document, Manifesto, or Catalogue numbered** AND the content is genuinely ordinal (steps you must do in order, chapters that build, items in a deliberate sequence). Even then: cap at 1–2 per page.
|
||||
|
||||
**Hard ban: tag-left / header-right two-column section heads.** Patterns like `01 · THE TOUR` in a narrow left column with the section title to its right (a.k.a. *hanging header*, *left-margin label*) are the single most reliable AI-templated tell for editorial-style SaaS pages. When an eyebrow IS used, **the heading goes directly underneath it, in the same column** — vertical stack only. Never a two-column section-head grid. Gate 66 enforces this.
|
||||
**Hard ban: tag-left / header-right two-column section heads.** Patterns like `01 · THE TOUR` in a narrow left column with the section title to its right (a.k.a. *hanging header*, *left-margin label*) are the single most reliable AI-templated tell for editorial-style SaaS pages. When an eyebrow IS used, **the heading goes directly underneath it, in the same column** — vertical stack only. Never a two-column section-head grid. Gate 54 enforces this.
|
||||
|
||||
**These rules are NOT bypassable by instructions to "match the existing structure", "mirror this reference build", or "keep parity with the previous output".** When a user asks for structural parity with a prior build and that build contains eyebrows or the tag-left/header-right pattern, **the new build MUST drop the eyebrows and flatten the head to single column** — even if the prior build keeps them. Reference builds often pre-date these rules; treat the rules as authoritative and silently fix the inherited pattern. Don't perpetuate a banned pattern to honour a parity instruction.
|
||||
|
||||
@@ -212,7 +212,7 @@ A stat-led layout, comparison row, or proof bar carrying numbers the user never
|
||||
|
||||
**Why it fails.** Audiences read invented stats as fast as they read invented testimonials. A page that lies on its proof bar can't be trusted on its claims either, and the AI tell is unmistakable: every fabricated number reads "this was generated, not written".
|
||||
|
||||
**Fix.** Three options, in order of preference: (1) replace the number with `—` and a labelled grey block ("metric to confirm" or "stat pending"); (2) ask the user for the real number and pause the run; (3) rebuild the section without the proof slot — a stat-led macrostructure with no real stats is the wrong macrostructure. The number-shaped hole is honest; the fabricated number is slop. *(Slop-test gate 56.)*
|
||||
**Fix.** Three options, in order of preference: (1) replace the number with `—` and a labelled grey block ("metric to confirm" or "stat pending"); (2) ask the user for the real number and pause the run; (3) rebuild the section without the proof slot — a stat-led macrostructure with no real stats is the wrong macrostructure. The number-shaped hole is honest; the fabricated number is slop. *(Slop-test gate 46.)*
|
||||
|
||||
### Generic emoji as feature icon
|
||||
|
||||
@@ -220,7 +220,7 @@ A feature card, value prop, step number, or pricing tier with `✨` `🚀` `⚡`
|
||||
|
||||
**Why it fails.** Emoji are typography of a sort, but they are not part of the page's typographic system — they're rendered by the OS and look different on every device, they break the icon's stroke voice (you've now mixed a Phosphor-style line icon with a Twemoji blob), and the choice is recognisably the AI default. Sparkle-emoji-as-AI-shortcut is the cliché of the 2024–2025 era.
|
||||
|
||||
**Fix.** Pick a single icon library and ship it ([assets.md](assets.md) names the canon). Or build a custom SVG mark. Or omit the icon entirely and lead with typography — most feature lists don't need icons. *(Slop-test gate 60.)*
|
||||
**Fix.** Pick a single icon library and ship it ([assets.md](assets.md) names the canon). Or build a custom SVG mark. Or omit the icon entirely and lead with typography — most feature lists don't need icons. *(Slop-test gate 30.)*
|
||||
|
||||
### Re-drawn UI chrome
|
||||
|
||||
@@ -228,7 +228,7 @@ A fake browser bar (URL pill + traffic-light dots) wrapping a screenshot. A fake
|
||||
|
||||
**Why it fails.** The user already has the chrome — their browser, their phone, their IDE all *are* chrome. Redrawing it in a page is like printing a photograph of a picture frame inside a real picture frame. The fakery is also bad: the URL is wrong, the dots aren't macOS dots, the notch is the wrong shape. Audiences pattern-match re-drawn chrome as "AI invented a UI that already exists" within a glance.
|
||||
|
||||
**Fix.** Use a real screenshot wrapped in `<figure>` (with a hairline border at most). For phone mockups, use a transparent-PNG device frame from a vendor or a real product photograph — never a CSS-drawn one. For code blocks, use the system `<pre>` with a typographic frame (top rule + label + bottom rule), not a faked window-chrome. The page's job is to show content, not to imitate the OS. *(Slop-test gate 57.)*
|
||||
**Fix.** Use a real screenshot wrapped in `<figure>` (with a hairline border at most). For phone mockups, use a transparent-PNG device frame from a vendor or a real product photograph — never a CSS-drawn one. For code blocks, use the system `<pre>` with a typographic frame (top rule + label + bottom rule), not a faked window-chrome. The page's job is to show content, not to imitate the OS. *(Slop-test gate 47.)*
|
||||
|
||||
### Mid-render token improvisation
|
||||
|
||||
@@ -236,7 +236,7 @@ A theme is selected at the top of the run, but the artifact contains inline colo
|
||||
|
||||
**Why it fails.** Token discipline is the difference between a system and a freestyle. Once a theme is locked, every colour and every font in the file must reference a named token (`var(--color-accent)`, `font-family: var(--font-display)`). Inline values are how cohesion erodes — by the third edit pass, the page has eight colours instead of three, and the editorial restraint that made the theme work is gone. Audiences don't see the inline value, but they feel the looseness.
|
||||
|
||||
**Fix.** Every colour and every font in the artifact must come through `var(--token-name)`. If you need a value that doesn't exist as a token, add it to the token block first (`--color-accent-warm: oklch(...)`) and then reference it. Inline OKLCH or one-off hex values mid-render are not allowed. *(Slop-test gate 58. See also [SKILL.md § Locked tokens](../SKILL.md).)*
|
||||
**Fix.** Every colour and every font in the artifact must come through `var(--token-name)`. If you need a value that doesn't exist as a token, add it to the token block first (`--color-accent-warm: oklch(...)`) and then reference it. Inline OKLCH or one-off hex values mid-render are not allowed. *(Slop-test gate 48. See also [SKILL.md § Locked tokens](../SKILL.md).)*
|
||||
|
||||
### Wrap-to-two-lines clickable text
|
||||
|
||||
@@ -244,7 +244,7 @@ A button label, nav link, footer link, breadcrumb, or CTA reads on two lines bec
|
||||
|
||||
**Why it fails.** Clickable affordances are one-line objects. The reader scans the label, decides whether to click, moves on. A two-line label slows the scan, breaks the row's vertical rhythm (button height grows, sibling buttons stay the same), and signals "this page wasn't tested at this width". It's a responsive-discipline tell.
|
||||
|
||||
**Fix.** In order of preference: (1) shorten the label — *"Get started free" → "Start free"*; *"Read the documentation" → "Read docs"*. Most CTA labels are too long. (2) Set `white-space: nowrap` on the affordance and let the parent flex container reflow. (3) Drop a non-essential nav item at narrow widths via `hidden=until-found` or `display: none`. (4) Collapse the nav into a sheet/menu under a threshold. *Never* let a primary CTA or nav link wrap. *(Slop-test gate 59. See [responsive.md § Clickable text — never wraps](responsive.md).)*
|
||||
**Fix.** In order of preference: (1) shorten the label — *"Get started free" → "Start free"*; *"Read the documentation" → "Read docs"*. Most CTA labels are too long. (2) Set `white-space: nowrap` on the affordance and let the parent flex container reflow. (3) Drop a non-essential nav item at narrow widths via `hidden=until-found` or `display: none`. (4) Collapse the nav into a sheet/menu under a threshold. *Never* let a primary CTA or nav link wrap. *(Slop-test gate 49. See [responsive.md § Clickable text — never wraps](responsive.md).)*
|
||||
|
||||
### Lottie shortcut
|
||||
|
||||
|
||||
@@ -15,6 +15,6 @@ Sticky left pane, scrolling right pane that cycles through related screenshots.
|
||||
.sticky-stack { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2xl); }
|
||||
/* `calc(--banner-height + --space-xl)` so the sticky pane docks below the
|
||||
nav with breathing room. Falls back to --space-xl alone when no sticky
|
||||
nav is on the page (slop-test gate 68). */
|
||||
nav is on the page (slop-test gate 56). */
|
||||
.pane-sticky { position: sticky; top: calc(var(--banner-height, 0px) + var(--space-xl)); align-self: start; z-index: var(--z-sticky); }
|
||||
```
|
||||
|
||||
@@ -11,9 +11,9 @@ Heading remains in viewport while content scrolls beneath. Orientation aid.
|
||||
```
|
||||
```css
|
||||
/* If the page has a sticky top nav, offset by its height so the sticky
|
||||
head docks BENEATH it instead of bleeding over (slop-test gate 68).
|
||||
head docks BENEATH it instead of bleeding over (slop-test gate 56).
|
||||
Use --z-sticky (in-page) so the nav's --z-sticky-nav out-paints it. */
|
||||
.head-sticky { position: sticky; top: var(--banner-height, 0px); background: var(--color-paper); padding-block: var(--space-sm); border-bottom: 1px solid var(--color-ink); z-index: var(--z-sticky); }
|
||||
```
|
||||
|
||||
**Sticky pairing rule:** if the page emits a sticky `<header>` / `<nav>` / `.banner` (anything with `position: sticky; top: 0`), you MUST also declare `--banner-height` (a px value matching the nav's height) and `--z-sticky-nav` (≥ 1 above `--z-sticky`) in `tokens.css`. The S3 recipe above pulls both. Without those tokens the section head paints over the nav during scroll — see slop-test gate 68.
|
||||
**Sticky pairing rule:** if the page emits a sticky `<header>` / `<nav>` / `.banner` (anything with `position: sticky; top: 0`), you MUST also declare `--banner-height` (a px value matching the nav's height) and `--z-sticky-nav` (≥ 1 above `--z-sticky`) in `tokens.css`. The S3 recipe above pulls both. Without those tokens the section head paints over the nav during scroll — see slop-test gate 56.
|
||||
|
||||
@@ -133,9 +133,9 @@ These are not arbitrary. The L-step gives the palette **typographic depth** with
|
||||
|
||||
### B.7 · Verification
|
||||
|
||||
- **Gate 8** (no pure #000 / #fff base): paper and ink both have chroma > 0. Pass.
|
||||
- **Gate 24** (no zero-chroma neutrals): every grey has chroma ≥ 0.005. Pass.
|
||||
- **Gate 25** (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 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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -16,15 +16,15 @@ Brief mentions any of: *AI tool, generative, music, video, image, voice, late-ni
|
||||
|
||||
- **Display** — Geist Sans 600 or similar weighty sans, plain English, no ornament. Letter-spacing tight (`-0.03em` or tighter).
|
||||
- **Body** — same family, 400. Light grey on dark (`oklch(86% 0.008 40)`).
|
||||
- **Accent** — single warm hue (orange / amber / red / pink). Used in radial-gradient blooms on the canvas, on focus rings, on small tags. Never on display text (that's gate 5 universal — gradient text stays banned).
|
||||
- **Accent** — single warm hue (orange / amber / red / pink). Used in radial-gradient blooms on the canvas, on focus rings, on small tags. Never on display text (that's gate 2 universal — gradient text stays banned).
|
||||
- **Layout** — centred or near-centred heroes. The canvas itself is the design; the type sits on top of an atmospheric ground.
|
||||
- **Motion** — fade-in only. No slide, no bounce. The atmosphere does the work.
|
||||
- **Copy tone** — direct, slightly poetic, specific. *"Make a house song about quitting your job."* is the calibration.
|
||||
|
||||
## What this genre allows
|
||||
|
||||
- **Radial-gradient bloom** on the body background — up to two blooms, each ~20–30 % footprint, fixed-attached, no animation. Gate 31 universal is loosened here.
|
||||
- **Centred heroes** — gate 7 universal is loosened. The canvas frames the type.
|
||||
- **Radial-gradient bloom** on the body background — up to two blooms, each ~20–30 % footprint, fixed-attached, no animation. Gate 29 universal is loosened here.
|
||||
- **Centred heroes** — gate 6 universal is loosened. The canvas frames the type.
|
||||
- **Pill-rounded CTAs** with accent fill — confident, not pastel.
|
||||
- **Glow shadows** on hover (cards lift toward the user with a soft warm shadow).
|
||||
- **Larger expressive type** — display can hit 6 rem (`clamp(3rem, 6vw + 1rem, 6rem)`).
|
||||
@@ -36,7 +36,7 @@ Brief mentions any of: *AI tool, generative, music, video, image, voice, late-ni
|
||||
- **Hairlines** — atmospheric uses elevated cards (`paper-2`, `paper-3`) instead of hairline-on-paper.
|
||||
- **Multiple accent hues** — one warm bloom + one secondary (pink/red) is the maximum. No teal-and-amber juggling.
|
||||
- **Glassmorphism** — banned. Atmospheric is *atmospheric*, not glass.
|
||||
- **Gradient text** — gate 5 universal. Stays banned.
|
||||
- **Gradient text** — gate 2 universal. Stays banned.
|
||||
|
||||
## Voice fixtures
|
||||
|
||||
|
||||
@@ -37,11 +37,11 @@ Default. Pick editorial when the brief does not name a specialised aesthetic —
|
||||
The universal slop-test gates apply, plus these editorial-specific bans:
|
||||
|
||||
- **Pill-rounded buttons** with gradient fill — pill is fine, gradient on a pill is not.
|
||||
- **Centred-everything heroes** (gate 7 universal). Editorial heroes are left-biased or asymmetric.
|
||||
- **Centred-everything heroes** (gate 6 universal). Editorial heroes are left-biased or asymmetric.
|
||||
- **Card-in-card** layouts (gate 4 universal).
|
||||
- **Three-column equal-icon-tile feature grid** (gate 3 universal).
|
||||
- **Glassmorphism** — never; the medium is paper, not glass.
|
||||
- **Pure black or pure white** as paper or ink (gate 8). Tint everything toward the anchor.
|
||||
- **Pure black or pure white** as paper or ink (gate 7). Tint everything toward the anchor.
|
||||
|
||||
## Voice fixtures
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ The two differ on every axis a glance registers: Coral is warm-grey + coral + Ge
|
||||
## What this genre allows
|
||||
|
||||
- **Pill-rounded CTAs** — both filled and outlined. Black-filled primary + white-outlined secondary is the canonical pair.
|
||||
- **Pure white paper** (`#fff` / `oklch(100% 0 0)`) — gate 8 is loosened here.
|
||||
- **Zero-chroma neutrals** — gate 24 is loosened here. The Stripe / ElevenLabs school is monochrome by design.
|
||||
- **Pure white paper** (`#fff` / `oklch(100% 0 0)`) — gate 7 is loosened here.
|
||||
- **Zero-chroma neutrals** — gate 22 is loosened here. The Stripe / ElevenLabs school is monochrome by design.
|
||||
- **Two-column hero with title-left + paragraph-right** — explicitly canonical for this genre.
|
||||
- **Refined card surface** with very subtle border (`oklch(91% 0 0)`) and 8 px radius.
|
||||
- **Large, tight-set displays** (`clamp(2.5rem, 5vw + 0.5rem, 4.75rem)`).
|
||||
@@ -38,8 +38,8 @@ The two differ on every axis a glance registers: Coral is warm-grey + coral + Ge
|
||||
- **Hairline-everything** — borders are thin but visible, not the editorial 0.5 px hairline aesthetic.
|
||||
- **Asymmetric prose columns** — modern-minimal aligns left, justified to a regular grid.
|
||||
- **Drop caps, fleurons, ornament** — none of it.
|
||||
- **Bouncy / overshoot easings** — gate 13 universal applies strictly here.
|
||||
- **Gradient text** — gate 5 universal. Stays banned.
|
||||
- **Bouncy / overshoot easings** — gate 12 universal applies strictly here.
|
||||
- **Gradient text** — gate 2 universal. Stays banned.
|
||||
- **Glassmorphism** — banned.
|
||||
|
||||
## Voice fixtures
|
||||
@@ -56,7 +56,7 @@ The two differ on every axis a glance registers: Coral is warm-grey + coral + Ge
|
||||
- **Acceptable also:** N1 Wordmark + 2 links (when destinations are genuinely minimal); N9 Edge-aligned minimal (when the brand earns the silence).
|
||||
- **Default footer:** Ft2 Inline single line — wordmark + tagline + tiny credit, hairline rule above. Restrained.
|
||||
- **Acceptable also:** Ft1 Mast-headed; Ft5 Statement (when the page wants a closing line).
|
||||
- **Banned for modern-minimal:** N6 Newspaper masthead (editorial vocabulary); N7 Brutal slab (fights the restraint); Ft8 Marquee scroll (kinetic, wrong voice); Ft3 Index columns at full saturation (the AI-footer fingerprint — gate 52).
|
||||
- **Banned for modern-minimal:** N6 Newspaper masthead (editorial vocabulary); N7 Brutal slab (fights the restraint); Ft8 Marquee scroll (kinetic, wrong voice); Ft3 Index columns at full saturation (the AI-footer fingerprint — gate 43).
|
||||
|
||||
See [`component-cookbook.md`](../component-cookbook.md) § Navigation and § Footers for the full archetypes + code.
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ Brief mentions any of: *fun, consumer, casual, family, kids, friendly, approacha
|
||||
- **Comic Sans, Comic Neue, anything that signals "we're zany"** — playful stays sophisticated, even at full vibrancy.
|
||||
- **Bouncy / overshoot easings** — playful uses smooth easings by default. **Hum is the documented exception** — spring overshoot (`cubic-bezier(0.34, 1.56, 0.64, 1)`) is canonical on the primary CTA and character moment (one each per page).
|
||||
- **Glassmorphism** — banned across all genres.
|
||||
- **Gradient text** — gate 5 universal. Stays banned.
|
||||
- **Gradient text** — gate 2 universal. Stays banned.
|
||||
|
||||
## Voice fixtures
|
||||
|
||||
|
||||
@@ -361,7 +361,7 @@ hero?.addEventListener('pointermove', (e) => {
|
||||
A huge edition number / year / chapter glyph set in display-italic in a hero corner. The numeral *means something* — issue 22, year 2026, chapter 03, version 0.8. Reads as editorial · newsprint · almanac.
|
||||
|
||||
*Use when:* the page genuinely has an edition / issue / chapter / version semantic — magazines, journals, archived work, dated essays.
|
||||
*Avoid when:* the numeral has no semantic anchor. A random "42" in the corner reads as decoration, which is slop (see slop-test gate 55).
|
||||
*Avoid when:* the numeral has no semantic anchor. A random "42" in the corner reads as decoration, which is slop (see slop-test gate 45).
|
||||
|
||||
```html
|
||||
<header class="hero hero--num">
|
||||
@@ -385,10 +385,10 @@ A huge edition number / year / chapter glyph set in display-italic in a hero cor
|
||||
|
||||
Every hero — enriched or not, polished or not — obeys these rules.
|
||||
|
||||
- **Footprint.** The hero takes 70–90 % of the first viewport's height — no more, no less. `min-height: 100vh / 100dvh` is the AI fingerprint (gate 7); a hero that's only 20 % of the viewport feels like a header. Aim for `min-height: clamp(60vh, 75dvh, 88dvh)` and let content settle inside.
|
||||
- **Fit the fold — content, not just the box.** The Footprint rule caps the hero's *height*; this caps its *content*. On a 13″ laptop (~800 px tall) the eyebrow + headline + lede + primary CTA must all be visible **without scrolling**. When they aren't, it's almost always wasted vertical space — an oversized display `clamp()` max, display line-height near 1.2, a 3-line lede, or `padding-block` bloat. Pull the clamp max down, set display line-height 1.0–1.1, hold the lede to ~2 lines, trim the padding. **Right-size, don't cramp** — a hero that already fits needs no shrinking, and this never means tiny type or no whitespace. Slop-test gate 70 enforces this.
|
||||
- **Asymmetric padding.** `padding-block-end` ≥ 1.3× `padding-block-start`. The hero sits *into* the page; symmetric padding floats. Slop-test gate 54 enforces this.
|
||||
- **Never centre everything.** Eyebrow + title + lede + CTA all stacked centred is the AI fingerprint. Pick at most *two* centred elements; break alignment for the others. Gate 53 enforces this. Centred-narrow heroes are admissible only when the genre is editorial / atelier *and* the eyebrow or CTA breaks alignment.
|
||||
- **Footprint.** The hero takes 70–90 % of the first viewport's height — no more, no less. `min-height: 100vh / 100dvh` is the AI fingerprint (gate 6); a hero that's only 20 % of the viewport feels like a header. Aim for `min-height: clamp(60vh, 75dvh, 88dvh)` and let content settle inside.
|
||||
- **Fit the fold — content, not just the box.** The Footprint rule caps the hero's *height*; this caps its *content*. On a 13″ laptop (~800 px tall) the eyebrow + headline + lede + primary CTA must all be visible **without scrolling**. When they aren't, it's almost always wasted vertical space — an oversized display `clamp()` max, display line-height near 1.2, a 3-line lede, or `padding-block` bloat. Pull the clamp max down, set display line-height 1.0–1.1, hold the lede to ~2 lines, trim the padding. **Right-size, don't cramp** — a hero that already fits needs no shrinking, and this never means tiny type or no whitespace. Slop-test gate 44 enforces this.
|
||||
- **Asymmetric padding.** `padding-block-end` ≥ 1.3× `padding-block-start`. The hero sits *into* the page; symmetric padding floats. Slop-test gate 44 enforces this.
|
||||
- **Never centre everything.** Eyebrow + title + lede + CTA all stacked centred is the AI fingerprint. Pick at most *two* centred elements; break alignment for the others. Gate 6 enforces this. Centred-narrow heroes are admissible only when the genre is editorial / atelier *and* the eyebrow or CTA breaks alignment.
|
||||
- **Entrance animation.** Pick one of {fade, sweep, none} per element — never both fade *and* sweep on the same element. Duration ≤ 220 ms. Disable on `prefers-reduced-motion: reduce`. Cross-reference the "One orchestrated reveal per page" rule below.
|
||||
- **Headline typography.** Prefer one display weight + tight tracking (-0.02em to -0.04em) over default 0; line-height 0.95–1.05 for display, never 1.2 (which inherits the body line-height and reads as un-set type). Avoid two display weights on the same headline (a `<strong>` in a different weight inside the title is AI's idea of "emphasis"; pick one weight, let the words carry).
|
||||
- **One polish pattern, max.** HP1–HP4 are mutually exclusive on a single hero. A vertical rail *and* a marquee-overflow *and* a cursor spotlight *and* a decorative numeral on one hero is a panic attack. Pick one.
|
||||
|
||||
@@ -169,7 +169,7 @@ The outline starts transparent at 2 px so when the focus ring appears, the box g
|
||||
|
||||
## Contrast discipline
|
||||
|
||||
Hallmark output must pass slop-test gates 46–50 before shipping. Compute contrast for every `(color, background-color)` pair on the page. The common failures Hallmark output trips on:
|
||||
Hallmark output must pass slop-test gates 40–41 before shipping. Compute contrast for every `(color, background-color)` pair on the page. The common failures Hallmark output trips on:
|
||||
|
||||
1. **Text on a flipped surface.** `.section--ink { background: var(--color-ink); }` flips the surface dark; nested text still inherits `color: var(--color-ink)` → ink-on-ink. Fix: any rule that sets a dark `background` must *also* set `color: var(--color-paper)` in the same rule.
|
||||
2. **Button text on accent fill.** `background: var(--color-accent); color: white;` — but white is 4.5:1 against this accent only if `--color-accent` is dark enough. Use `var(--color-accent-ink)` instead, which the theme guarantees passes ≥ APCA Lc 60.
|
||||
|
||||
@@ -42,7 +42,7 @@ Layout is where "AI-generated" gets caught. Equal columns, everything centred, e
|
||||
## Asymmetry techniques
|
||||
|
||||
- **Wide left margin.** Treat the left as a permanent negative space — narrow column of labels, wide column of content. **Labels must NOT be section eyebrows / numbers paired with the heading** — that's gate-66-banned. Reserve this technique for body-level micro-labels (caption, footnote, date) alongside body copy.
|
||||
- **Hanging headers.** ⚠️ **Opt-in only.** Section labels sit in the left margin; content flows to the right. Permitted only when the user explicitly asks for an editorial / hanging-header layout AND no eyebrow / number / chapter tag sits in the left margin. The eyebrow-left / heading-right pattern is banned by slop-test gate 66 — it's the most reliable templated-editorial AI tell. Default to a stacked single-column section head.
|
||||
- **Hanging headers.** ⚠️ **Opt-in only.** Section labels sit in the left margin; content flows to the right. Permitted only when the user explicitly asks for an editorial / hanging-header layout AND no eyebrow / number / chapter tag sits in the left margin. The eyebrow-left / heading-right pattern is banned by slop-test gate 54 — it's the most reliable templated-editorial AI tell. Default to a stacked single-column section head.
|
||||
- **Offset grids.** Odd columns wider than even. Or the other way.
|
||||
- **Grid-breaks.** One element that deliberately extends past a column boundary: a pull-quote, a photograph, a rule, a number.
|
||||
- **Generous top, tight bottom** (or vice-versa). Sections don't need to be evenly padded.
|
||||
@@ -82,7 +82,7 @@ Layout is where "AI-generated" gets caught. Equal columns, everything centred, e
|
||||
|
||||
The clipped-edge enrichment archetype (E1) — and any other deliberately overflowing element (full-bleed marquee, oversized headline that exceeds the viewport on small screens, a tilted figure that pushes past a column) — needs a parent that *visually* shows the overflow without letting the document scroll horizontally.
|
||||
|
||||
The default is unsafe: a `width: calc(100% + 12vw)` figure inside a section with `overflow: visible` makes the document scroll horizontally on every viewport. The page feels broken on touch devices. Slop-test gate 36 fails on this.
|
||||
The default is unsafe: a `width: calc(100% + 12vw)` figure inside a section with `overflow: visible` makes the document scroll horizontally on every viewport. The page feels broken on touch devices. Slop-test gate 34 fails on this.
|
||||
|
||||
**Always pair clipped-edge with a global clip.** At the top of the stylesheet:
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ Each section transition uses theme-appropriate vertical spacing — `--space-3xl
|
||||
**Voice rules for SaaS sections:**
|
||||
|
||||
- **Pricing:** show the actual price. Sales-led pricing on every tier ("Contact us") signals the brand doesn't trust the buyer.
|
||||
- **Testimonials:** include the quoted person's role *and* company. Abstract "Engineering Manager" testimonials are slop. If the brief is a real product, use real names. If the brief is a placeholder, use plausible names — never "Jane Doe" / "John Smith" (gate 20).
|
||||
- **Testimonials:** include the quoted person's role *and* company. Abstract "Engineering Manager" testimonials are slop. If the brief is a real product, use real names. If the brief is a placeholder, use plausible names — never "Jane Doe" / "John Smith" (gate 19).
|
||||
- **FAQ:** answer like a person, not a sales doc. "Yes — Stripe and Adyen are both supported out of the box" beats "Our platform integrates with leading payment providers."
|
||||
- **CTA strip:** one button. Not two. The repetition is the call to action.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Four sample Step 5 preview blocks for the model to imitate, varied across macros
|
||||
> - **Enrichment** · Tier-B hand-built SVG (a 60-line coffee bean with `@property --rise` 6 s breathing-loop)
|
||||
> - **Sections** · Masthead · Letter · Three Notes · Visit · Colophon
|
||||
> - **Motion** · breathing-loop on bean only (respects `prefers-reduced-motion`)
|
||||
> - **Slop test** · 70 / 70 ✓
|
||||
> - **Slop test** · 57 / 57 ✓
|
||||
> - **Diversification** · first run for this project
|
||||
|
||||
*Bento Grid (SaaS, motion-on):*
|
||||
@@ -23,7 +23,7 @@ Four sample Step 5 preview blocks for the model to imitate, varied across macros
|
||||
> - **Enrichment** · E1 Clipped-Edge Demo Video, Tier-A CSS-art trace waterfall
|
||||
> - **Sections** · Hero · 6-tile Bento (stat · sparkline · quote · code · integrations · spotlight) · Index Footer
|
||||
> - **Motion** · counter · pricing-lift · CSS marquee on integrations strip
|
||||
> - **Slop test** · 70 / 70 ✓
|
||||
> - **Slop test** · 57 / 57 ✓
|
||||
> - **Diversification** · differs from Plain on paper hue (light-cool vs pure-white) + accent (indigo vs ink-blue)
|
||||
|
||||
*Manifesto (declarative, no enrichment):*
|
||||
@@ -34,7 +34,7 @@ Four sample Step 5 preview blocks for the model to imitate, varied across macros
|
||||
> - **Enrichment** · none (typography only — voice carries the brand)
|
||||
> - **Sections** · Masthead · Title · Five Declarations · Bleed Band · What We Refuse · Working Rules · Practice · Reading · Colophon
|
||||
> - **Motion** · none — typography only
|
||||
> - **Slop test** · 70 / 70 ✓
|
||||
> - **Slop test** · 57 / 57 ✓
|
||||
> - **Diversification** · differs from Newsprint on paper band (dark vs light) + display style (display-heavy vs geometric-sans)
|
||||
|
||||
*Custom (Coffeebox archival café):*
|
||||
@@ -45,5 +45,5 @@ Four sample Step 5 preview blocks for the model to imitate, varied across macros
|
||||
> - **Enrichment** · Tier-A pure-CSS coffee bean (60-line SVG, breathing-loop optional)
|
||||
> - **Sections** · Masthead · Letter · Three Notes · Visit · Colophon
|
||||
> - **Motion** · breathing-loop on bean (with reduced-motion fallback)
|
||||
> - **Slop test** · 70 / 70 ✓
|
||||
> - **Slop test** · 57 / 57 ✓
|
||||
> - **Diversification** · custom axes: light / italic-serif / chromatic-terracotta — differs from previous catalog Newsprint on accent hue + display style
|
||||
|
||||
@@ -6,13 +6,13 @@ Mobile-first. Content-driven breakpoints. No desktop-only interactions.
|
||||
|
||||
Every Hallmark output must render flawlessly at **320 px, 375 px, 414 px, and 768 px** CSS-pixel widths. Eyeball each viewport before marking the output complete:
|
||||
|
||||
- No horizontal scroll (slop-test gate 36)
|
||||
- No clickable text wrapping to two lines (gate 59)
|
||||
- No image-bearing grid pushing the layout past viewport — use `minmax(0, 1fr)`, never bare `1fr`, on tracks containing images (gate 61)
|
||||
- Root carries `overflow-x: clip` on both `html` and `body` — never `hidden` (gate 62)
|
||||
- Display headers wrap inside long words via `overflow-wrap: anywhere; min-width: 0` (gate 63)
|
||||
- Section heads collapse to one column on mobile across every theme variant — per-theme overrides need a matching mobile rule (gate 64)
|
||||
- No scroll-jump on radio-tab clicks — radios in normal flow OR JS guard with `focus({ preventScroll: true })` (gate 65)
|
||||
- No horizontal scroll (slop-test gate 34)
|
||||
- No clickable text wrapping to two lines (gate 49)
|
||||
- No image-bearing grid pushing the layout past viewport — use `minmax(0, 1fr)`, never bare `1fr`, on tracks containing images (gate 50)
|
||||
- Root carries `overflow-x: clip` on both `html` and `body` — never `hidden` (gate 34)
|
||||
- Display headers wrap inside long words via `overflow-wrap: anywhere; min-width: 0` (gate 51)
|
||||
- Section heads collapse to one column on mobile across every theme variant — per-theme overrides need a matching mobile rule (gate 52)
|
||||
- No scroll-jump on radio-tab clicks — radios in normal flow OR JS guard with `focus({ preventScroll: true })` (gate 53)
|
||||
|
||||
This is a hard floor, not a wish list. A page that fails any of these on any of those four widths is not done. The slop-test gates listed run automatically — keep this checklist near the screen while building.
|
||||
|
||||
@@ -87,7 +87,7 @@ Buttons, primary nav links, footer links, tab labels, breadcrumbs, and CTAs must
|
||||
|
||||
**Never:** let a primary CTA or top-level nav link wrap. Long footer-link labels can wrap *only* in a footer column where wrapping is part of the column's rhythm — not in the inline footer link strip (Ft2).
|
||||
|
||||
This is gate **59** in [`slop-test.md`](slop-test.md). Audit any output that ships interactive affordances and confirm none wrap at the breakpoints listed above.
|
||||
This is gate **49** in [`slop-test.md`](slop-test.md). Audit any output that ships interactive affordances and confirm none wrap at the breakpoints listed above.
|
||||
|
||||
## Viewport units
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Slop test — 70 gates + pre-emit self-critique
|
||||
# Slop test — 57 gates + pre-emit self-critique
|
||||
|
||||
Run this list before handing back any output. Every answer must be **no**. Update the Step 5 preview block's `Slop test` row to reflect the actual outcome of this run.
|
||||
|
||||
@@ -8,7 +8,7 @@ Some gates are **universal** (apply to every genre); some are **genre-scoped** (
|
||||
|
||||
## Pre-emit self-critique (six axes)
|
||||
|
||||
Run this **before** the gate list, not after. Score the planned output 1–5 on each axis. Anything **< 3 on any axis triggers a revision pass** before the gate sweep — don't bring known weakness into a seventy-gate review.
|
||||
Run this **before** the gate list, not after. Score the planned output 1–5 on each axis. Anything **< 3 on any axis triggers a revision pass** before the gate sweep — don't bring known weakness into a fifty-seven-gate review.
|
||||
|
||||
Two passes is normal. Three is a sign the brief is wrong, not the design — re-read the brief.
|
||||
|
||||
@@ -28,88 +28,87 @@ Record the six scores in a one-line stamp comment at the top of the file: `/* Ha
|
||||
## Visual
|
||||
|
||||
1. Is the display font Inter, Roboto, Open Sans, Poppins, Lato, or a system default?
|
||||
2. Is there a purple-to-blue (or cyan-to-magenta) gradient anywhere? *Genre note: atmospheric allows radial gradients on background only — never on text or pill buttons.*
|
||||
2. Is there a purple-to-blue (or cyan-to-magenta) gradient anywhere — **including a `background-clip: text` gradient headline**? *Genre note: atmospheric allows radial gradients on background only — never on text or pill buttons. No genre allows gradient text.*
|
||||
3. Is there a 3-equal-column card grid with icon-above-heading tiles?
|
||||
4. Is any card nested inside another card?
|
||||
5. Is there a `background-clip: text` gradient headline? (Universal — no genre allows gradient text.)
|
||||
6. Is any card using a thick coloured left/right side-stripe border?
|
||||
7. Is the hero `min-height: 100vh` with everything centred? *Genre note: atmospheric and playful allow centred heroes when the canvas itself is the design (Suno-style).*
|
||||
8. Is pure `#000` or pure `#fff` used as a base colour anywhere? *Genre note: modern-minimal allows pure `#fff` paper (the Stripe / ElevenLabs school).*
|
||||
5. Is any card using a thick coloured left/right side-stripe border?
|
||||
6. **Hero shape — centred-everything.** Is the hero `min-height: 100vh` with everything centred, OR are the eyebrow, title, lede, AND CTA all stacked on the same centred vertical axis? Auto-fail. Pick at most two centred elements and break alignment for the rest; the eyebrow or CTA should sit off-axis (margin-aligned, right-flush, numeral-anchored). *Genre note: atmospheric and playful allow a centred hero when the canvas itself is the design (Suno-style); editorial / atelier allow a centred-narrow hero, but even then the eyebrow or CTA sits off-axis.*
|
||||
7. Is pure `#000` or pure `#fff` used as a base colour anywhere? *Genre note: modern-minimal allows pure `#fff` paper (the Stripe / ElevenLabs school).*
|
||||
|
||||
## Structural
|
||||
|
||||
9. Does the page use the *same* structural fingerprint as the last page you built? (Hero → 3 features → CTA → footer is the AI structural template; reject it.)
|
||||
10. Are sections separated only by equal whitespace, with no rule, no ornament, no colour shift — every section identical in rhythm?
|
||||
8. Does the page reuse a structure it shouldn't — either the generic AI template (Hero → 3 features → CTA → footer), **or** the *same* structural fingerprint / macrostructure as a previous Hallmark output in this project? Read the file system: if a `.hallmark/log.json` entry or a CSS macrostructure stamp exists, this build's macrostructure must differ from the last.
|
||||
9. Are sections separated only by equal whitespace, with no rule, no ornament, no colour shift — every section identical in rhythm?
|
||||
|
||||
## Microinteractions
|
||||
|
||||
11. Is `transition-all` (or `transition: all`) used anywhere? (Specify the properties.)
|
||||
12. Is `hover:scale-105` (or any uniform hover-scale) applied across multiple unrelated elements?
|
||||
13. Are bouncy / overshoot easings (`cubic-bezier(0.34, 1.56, ...)`, etc.) used on UI state changes — buttons, modals, tooltips? (Reserve overshoots for physical interactions only.)
|
||||
14. Does any element have *more than one* hover effect at the same time (translate + scale + shadow + colour + rotate)?
|
||||
15. Are you animating `width`, `height`, `top`, `left`, `margin`, or `padding` anywhere?
|
||||
16. Does the focus ring transition into existence (fade in)? (Focus rings must appear instantly — keyboard users need an immediate indicator.)
|
||||
17. Is there a celebratory success toast for an action whose effect the user can already see? (Silent success is taste; toasts are for failures and invisible effects.)
|
||||
18. Are tooltip hover-delay and focus-delay equal? (Hover should delay 800–1000 ms; focus should be 0 ms.)
|
||||
19. Is auto-rotating content (carousel, banner, stats) lacking pause-on-hover-and-focus? (WCAG 2.2.2.)
|
||||
20. Is there a placeholder name "Jane Doe / John Smith" or a startup cliché (Acme, Nexus, Seamless, Unleash)?
|
||||
10. Is `transition-all` (or `transition: all`) used anywhere? (Specify the properties.)
|
||||
11. Is `hover:scale-105` (or any uniform hover-scale) applied across multiple unrelated elements?
|
||||
12. Are bouncy / overshoot easings (`cubic-bezier(0.34, 1.56, ...)`, etc.) used on UI state changes — buttons, modals, tooltips? (Reserve overshoots for physical interactions only.)
|
||||
13. Does any element have *more than one* hover effect at the same time (translate + scale + shadow + colour + rotate)?
|
||||
14. Are you animating `width`, `height`, `top`, `left`, `margin`, or `padding` anywhere?
|
||||
15. Does the focus ring transition into existence (fade in)? (Focus rings must appear instantly — keyboard users need an immediate indicator.)
|
||||
16. Is there a celebratory success toast for an action whose effect the user can already see? (Silent success is taste; toasts are for failures and invisible effects.)
|
||||
17. Are tooltip hover-delay and focus-delay equal? (Hover should delay 800–1000 ms; focus should be 0 ms.)
|
||||
18. Is auto-rotating content (carousel, banner, stats) lacking pause-on-hover-and-focus? (WCAG 2.2.2.)
|
||||
19. Is there a placeholder name "Jane Doe / John Smith" or a startup cliché (Acme, Nexus, Seamless, Unleash)?
|
||||
|
||||
## Variety
|
||||
|
||||
21. Is the `/* Hallmark · macrostructure: <name> · ... */` stamp missing from the top of the CSS? (It must be present.)
|
||||
22. Is the macrostructure I picked the same as a previous Hallmark output's stamp in this project? (Read the file system; if a stamp exists, mine must differ.)
|
||||
23. Did I default to the **Specimen** macrostructure (numbered left-margin labels + huge serif + asymmetric spans + typographic-only CTA) when the brief did not explicitly call for editorial / foundry / specimen energy? (Specimen fall-through is banned.) *Genre note: atmospheric, modern-minimal, and playful never default to Specimen — only editorial does, and only when the brief signals it.*
|
||||
20. Is the `/* Hallmark · macrostructure: <name> · ... */` stamp missing from the top of the CSS? (It must be present.)
|
||||
21. Did I default to the **Specimen** macrostructure (numbered left-margin labels + huge serif + asymmetric spans + typographic-only CTA) when the brief did not explicitly call for editorial / foundry / specimen energy? (Specimen fall-through is banned.) *Genre note: atmospheric, modern-minimal, and playful never default to Specimen — only editorial does, and only when the brief signals it.*
|
||||
|
||||
## Implementation gates
|
||||
|
||||
24. Does any neutral / surface colour have `oklch(... 0 ...)` (zero chroma)? Pure greys read as flat. Tint every neutral toward the anchor hue — minimum 0.005 chroma. *Genre note: modern-minimal allows zero-chroma neutrals (the monochrome Stripe / ElevenLabs school).*
|
||||
25. Does the accent colour cover more than ~5 % of any single viewport (count by area: solid fills, large headings in accent, full-bleed accent backgrounds)? If yes, retreat — accent is for emphasis, not for filling. *Genre note: atmospheric allows accent-tinted radial blooms covering up to ~20 % of the canvas, since the bloom is the design.*
|
||||
26. Is any padding / gap / margin a value that isn't on the named spacing scale (`--space-3xs` … `--space-5xl`, multiples of 4 px)? Arbitrary `padding: 17px` is a tell.
|
||||
27. Is any prose container's `max-width` outside the 45–75 ch range? Measure must read; under 45 ch is choppy, over 75 ch loses the eye.
|
||||
28. Does any interactive element lack `:focus-visible`, `:active`, OR `:disabled` styling? (Eight states is the rule. Default + hover is two; you need at least default + hover + focus-visible + active + disabled present in code.)
|
||||
29. Is there any `transform` / `animation` keyframe that is NOT covered by a `@media (prefers-reduced-motion: reduce)` fallback? Every motion gets a reduced-motion alternative.
|
||||
22. Does any neutral / surface colour have `oklch(... 0 ...)` (zero chroma)? Pure greys read as flat. Tint every neutral toward the anchor hue — minimum 0.005 chroma. *Genre note: modern-minimal allows zero-chroma neutrals (the monochrome Stripe / ElevenLabs school).*
|
||||
23. Does the accent colour cover more than ~5 % of any single viewport (count by area: solid fills, large headings in accent, full-bleed accent backgrounds)? If yes, retreat — accent is for emphasis, not for filling. *Genre note: atmospheric allows accent-tinted radial blooms covering up to ~20 % of the canvas, since the bloom is the design.*
|
||||
24. Is any padding / gap / margin a value that isn't on the named spacing scale (`--space-3xs` … `--space-5xl`, multiples of 4 px)? Arbitrary `padding: 17px` is a tell.
|
||||
25. Is any prose container's `max-width` outside the 45–75 ch range? Measure must read; under 45 ch is choppy, over 75 ch loses the eye.
|
||||
26. Does any interactive element lack `:focus-visible`, `:active`, OR `:disabled` styling? (Eight states is the rule. Default + hover is two; you need at least default + hover + focus-visible + active + disabled present in code.)
|
||||
27. Is there any `transform` / `animation` keyframe that is NOT covered by a `@media (prefers-reduced-motion: reduce)` fallback? Every motion gets a reduced-motion alternative.
|
||||
|
||||
## Hero enrichment gates
|
||||
|
||||
(When the page carries enrichment — see [`hero-enrichment.md`](hero-enrichment.md).)
|
||||
|
||||
30. If the page has a demo video, does it autoplay with sound, lack a `poster`, lack `fetchpriority="high"`, or use `loading="lazy"` on the LCP element? (LCP-killers fail this gate.)
|
||||
31. If the page has an abstract background, is it more than one accent colour, more than ~5 % footprint, or animating mesh-gradient on the whole page? (Aurora blobs and mesh-on-everything fail this gate.) *Genre note: atmospheric allows up to two warm-toned radial blooms covering ~20–30 % of the canvas, fixed-attached, no animation.*
|
||||
32. Does the page mix two or more icon libraries? (Material + Heroicons + Lucide on the same page = the icon-set tell.)
|
||||
33. If the page has illustration, did I default to a Lottie library when a hand-built SVG or pure-CSS shape would have worked? (Lottie is last resort, not the default.)
|
||||
28. If the page has a demo video, does it autoplay with sound, lack a `poster`, lack `fetchpriority="high"`, or use `loading="lazy"` on the LCP element? (LCP-killers fail this gate.)
|
||||
29. If the page has an abstract background, is it more than one accent colour, more than ~5 % footprint, or animating mesh-gradient on the whole page? (Aurora blobs and mesh-on-everything fail this gate.) *Genre note: atmospheric allows up to two warm-toned radial blooms covering ~20–30 % of the canvas, fixed-attached, no animation.*
|
||||
30. **Icon tells.** Does the page (a) mix two or more icon libraries (Material + Heroicons + Lucide on the same page), OR (b) use an emoji glyph (✨ 🚀 ⚡ 🔥 🎯 ✅) as a feature-card / value-prop / step / pricing-tier icon? Either is an AI-default tell. Pick one icon library (Lucide / Phosphor / Heroicons — see [assets.md](assets.md)), build a custom SVG, or drop the icon and lead with typography.
|
||||
31. If the page has illustration, did I default to a Lottie library when a hand-built SVG or pure-CSS shape would have worked? (Lottie is last resort, not the default.)
|
||||
|
||||
## Diversification gates
|
||||
|
||||
(Cross-reference `.hallmark/log.json` when present.)
|
||||
|
||||
34. If I used the same archetype as a previous Hallmark output (per `.hallmark/log.json` or the latest macrostructure stamp), did I pick at least one different *variation knob*? Two Bento Grids with `tiles=6, spans=irregular, accent=corner-only` are the same Bento — the within-archetype knobs in [`component-cookbook.md`](component-cookbook.md) exist precisely to prevent that. State the knob deltas in the stamp.
|
||||
35. Does any visual-only `<svg>`, custom-art `<div>`, `<canvas>`, or decorative figure lack `aria-label` or `aria-hidden="true"`? Hand-built CSS art and SVG illustrations need an accessible name *or* an explicit hide. Skipping this is the new accessibility tell.
|
||||
32. If I used the same archetype as a previous Hallmark output (per `.hallmark/log.json` or the latest macrostructure stamp), did I pick at least one different *variation knob*? Two Bento Grids with `tiles=6, spans=irregular, accent=corner-only` are the same Bento — the within-archetype knobs in [`component-cookbook.md`](component-cookbook.md) exist precisely to prevent that. State the knob deltas in the stamp.
|
||||
33. Does any visual-only `<svg>`, custom-art `<div>`, `<canvas>`, or decorative figure lack `aria-label` or `aria-hidden="true"`? Hand-built CSS art and SVG illustrations need an accessible name *or* an explicit hide. Skipping this is the new accessibility tell.
|
||||
|
||||
## Layout-safety gates
|
||||
|
||||
(The page must survive every viewport.)
|
||||
|
||||
36. Does the page horizontally scroll on any viewport between 320 px and 1920 px? Open the rendered page; drag the dev-tools width slider across that range. If a horizontal scrollbar appears at any width, fail. The fix is `html { overflow-x: clip; }` plus `body { overflow-x: clip; }` as a safety net for any clipped-edge enrichment that pushes past the viewport. Use `overflow-x: clip` (not `hidden`) — `clip` preserves `position: sticky` and `position: fixed` on descendants. (Cross-reference: [`layout-and-space.md` § Page-edge clipping](layout-and-space.md).)
|
||||
37. For every decorative effect on text — highlighter `<mark>` / `<em>` band / accent stroke / underline — did I visually confirm the position and size? A highlighter band must sit behind the x-height (`linear-gradient(180deg, transparent ~38%, accent ~38%, accent ~92%, transparent ~92%)`), **not** at the baseline (which reads as a fat underline). Underlines must be 1–2 px and offset 1–2 px from the baseline, never 5+ px. Decorative strokes must not exceed 5 % of the viewport (gate 25). The check is *visual*: imagine the rendered output and confirm the band lands in the right vertical zone.
|
||||
38. Are interactive bars (nav, toolbar, command bar, hero CTA row, footer link strip) explicitly vertically centered? Default flex layouts inherit `align-items: stretch`, which makes a button taller than its sibling text and breaks the visual baseline. Every flex row mixing height-different elements (button + text, icon + text, mark + body) must declare `align-items: center` and `line-height: 1` on the items with intrinsic height. Inheriting `line-height: 1.55` from `html` fights the row's vertical rhythm.
|
||||
34. Does the page horizontally scroll on any viewport between 320 px and 1920 px? Open the rendered page; drag the dev-tools width slider across that range. If a horizontal scrollbar appears at any width, fail. The required fix is `overflow-x: clip` on **both** `html` and `body` — use `clip`, not `hidden` (`clip` preserves `position: sticky` and `position: fixed` on descendants). This is a hard requirement on every emitted page, not only when scroll is observed. (Cross-reference: [`layout-and-space.md` § Page-edge clipping](layout-and-space.md).)
|
||||
35. For every decorative effect on text — highlighter `<mark>` / `<em>` band / accent stroke / underline — did I visually confirm the position and size? A highlighter band must sit behind the x-height (`linear-gradient(180deg, transparent ~38%, accent ~38%, accent ~92%, transparent ~92%)`), **not** at the baseline (which reads as a fat underline). Underlines must be 1–2 px and offset 1–2 px from the baseline, never 5+ px. Decorative strokes must not exceed 5 % of the viewport (gate 23). The check is *visual*: imagine the rendered output and confirm the band lands in the right vertical zone.
|
||||
36. Are interactive bars (nav, toolbar, command bar, hero CTA row, footer link strip) explicitly vertically centered? Default flex layouts inherit `align-items: stretch`, which makes a button taller than its sibling text and breaks the visual baseline. Every flex row mixing height-different elements (button + text, icon + text, mark + body) must declare `align-items: center` and `line-height: 1` on the items with intrinsic height. Inheriting `line-height: 1.55` from `html` fights the row's vertical rhythm.
|
||||
|
||||
## Typography discipline gates
|
||||
|
||||
(Three faces is the ceiling. See [`typography.md` § The 2+1 rule](typography.md).)
|
||||
|
||||
39. Does the page use **more than three** distinct `font-family` families? Count: `--font-display`, `--font-body`, and at most one outlier (`--font-outlier` for wordmark / hero stat / pull quote). A fourth family on the page — e.g. body + display + mono in code blocks + a separate display for the hero — is slop. Same family at different weights counts as one family. Mono counts as a family if used in any non-code context (captions, labels, numerals). If you find four, drop one back to the body or display face.
|
||||
40. Is the **outlier face used in more than two slots** on the page? The outlier is a register, not a third surface — wordmark + hero stat is the canonical pair, or wordmark + masthead, or hero stat + pull quote. Three slots = the outlier is now a third body font; collapse it back to the body face.
|
||||
40a. Is any **heading or display type italic** (`font-style: italic` on `h1`–`h6`, a `.*__title`, `.hero__title`, a wordmark, a stat figure, a footer statement, or an `<em>`/`<i>` inside a heading)? If yes, fail. Italic headers — above all the single italicised emphasis-word inside an upright headline — are a top AI tell. Headers are roman; emphasis comes from weight, accent colour, or a drawn underline. Italic is allowed *only* as body-copy emphasis inside running paragraphs. (Applies to every theme; Studio / Garden / Sport — historically italic-display — are reworked to roman.)
|
||||
37. Does the page use **more than three** distinct `font-family` families? Count: `--font-display`, `--font-body`, and at most one outlier (`--font-outlier` for wordmark / hero stat / pull quote). A fourth family on the page — e.g. body + display + mono in code blocks + a separate display for the hero — is slop. Same family at different weights counts as one family. Mono counts as a family if used in any non-code context (captions, labels, numerals). If you find four, drop one back to the body or display face.
|
||||
38. Is the **outlier face used in more than two slots** on the page? The outlier is a register, not a third surface — wordmark + hero stat is the canonical pair, or wordmark + masthead, or hero stat + pull quote. Three slots = the outlier is now a third body font; collapse it back to the body face.
|
||||
38a. Is any **heading or display type italic** (`font-style: italic` on `h1`–`h6`, a `.*__title`, `.hero__title`, a wordmark, a stat figure, a footer statement, or an `<em>`/`<i>` inside a heading)? If yes, fail. Italic headers — above all the single italicised emphasis-word inside an upright headline — are a top AI tell. Headers are roman; emphasis comes from weight, accent colour, or a drawn underline. Italic is allowed *only* as body-copy emphasis inside running paragraphs. (Applies to every theme; Studio / Garden / Sport — historically italic-display — are reworked to roman.)
|
||||
|
||||
## Input-state gates
|
||||
## Input-state gate
|
||||
|
||||
(Inputs are where almost-right UIs lose. See [`interaction-and-states.md` § Input field states](interaction-and-states.md).)
|
||||
|
||||
41. Does any input, textarea, or select field change `border-width` between states (default → hover → focus → error)? Default is 1 px; hover, focus, and error must keep `border-width: 1px`. Border-width changes cause layout shift. State changes go to `background-color`, `outline`, `box-shadow`, or `border-color` — never `border-width`.
|
||||
42. Does the input focus ring use `border` instead of `outline`? The focus ring must be `outline: 2px solid var(--color-focus)` with `outline-offset: 1px`. Reserving `outline: 2px solid transparent` in the default state prevents geometry shift on activate. A focus ring built from a `border` change is a tell.
|
||||
43. Is the input height different from the height of an adjacent button on the same form? Form inputs and the form's submit button share a single base height (44 px floor). 38 px input + 44 px button is the most common form-tuning slop and reads as un-designed.
|
||||
44. Is the helper-text container collapsed when no helper or error is shown? The helper slot must reserve `min-height: 1lh` even when empty so that an error appearing doesn't push the page down. Vertical jump on validation is a tell.
|
||||
45. Is the disabled input state signalled by *only* `opacity: 0.5`? Disabled needs three independent signals: `opacity: 0.55` AND `cursor: not-allowed` AND `aria-disabled="true"` (or the native `disabled` attribute). One channel is missable; three are not.
|
||||
39. Do input / textarea / select fields handle every state correctly? Fail on **any** of these five:
|
||||
- **Border-width shifts between states** — default / hover / focus / error must all keep `border-width: 1px`. State changes go to `background-color`, `outline`, `box-shadow`, or `border-color` — never `border-width` (it shifts layout).
|
||||
- **Focus ring built from `border` instead of `outline`** — must be `outline: 2px solid var(--color-focus)` with `outline-offset: 1px`; reserve `outline: 2px solid transparent` at rest to prevent geometry shift on activate.
|
||||
- **Input height ≠ adjacent button height** on the same form — share one base height (44 px floor); 38 px input + 44 px button is the most common form-tuning slop.
|
||||
- **Helper-text slot collapses when empty** — reserve `min-height: 1lh` even with no helper / error, so an appearing error doesn't push the page down.
|
||||
- **Disabled signalled by `opacity` alone** — disabled needs three channels: `opacity: 0.55` AND `cursor: not-allowed` AND the native `disabled` attribute (or `aria-disabled="true"`).
|
||||
|
||||
## Contrast & readability
|
||||
|
||||
@@ -117,91 +116,76 @@ Universal — apply to every genre. These gates catch the real-world failures th
|
||||
|
||||
Contrast computation: for every `(color, background-color)` pair on the page, run **APCA Lc** OR **WCAG 2.1 ratio**. OKLCH lightness is a fast pre-check — if `|L_text − L_bg| < 50 %`, the pair likely fails 4.5:1 — confirm with a full calculation.
|
||||
|
||||
Thresholds:
|
||||
- Body text (under 24 px regular OR under 18 px bold): **WCAG 4.5:1 / APCA Lc ≥ 60**.
|
||||
- Large text / icons / focus rings: **WCAG 3:1 / APCA Lc ≥ 45**.
|
||||
40. **Contrast thresholds.** Does any text, icon, or `:focus-visible` ring fail its threshold against its *computed* background? Pair every `color` declaration with its effective `background-color` and verify. Thresholds: **body text** (under 24 px regular OR under 18 px bold) needs **WCAG 4.5:1 / APCA Lc ≥ 60**; **large text** (≥ 24 px regular / ≥ 18 px bold), **icons**, and **focus rings** need **WCAG 3:1 / APCA Lc ≥ 45**. The most-missed cases: text inside a card that inherits `color` but the card switched to `background: var(--color-paper-2)`; muted text (`var(--color-muted)`) on `var(--color-paper-3)`; a focus ring whose `--color-focus` clears 3:1 against the element but not the page surface.
|
||||
|
||||
46. Does any **body text** have a contrast ratio below **4.5:1** against its computed background? Pair every `color` declaration with its effective `background-color` and verify. The most-missed cases are: text inside cards that inherit `color` but the card switched to `background: var(--color-paper-2)` and the text is now too close in lightness; muted text (`color: var(--color-muted)`) on `background: var(--color-paper-3)` — both are mid-lightness and fail.
|
||||
41. **The contrast failures that ship most often.** Fail on **any**:
|
||||
- **Button text ≈ button fill** — if the computed text colour and fill are within **5 % lightness AND 0.05 chroma** in OKLCH, fail. This catches the black-on-black bug (`color: var(--color-ink)` on `background: var(--color-ink)` — the model forgot `--color-accent-ink` / `--color-paper`).
|
||||
- **`--color-accent-ink` missing or unused** — whenever `--color-accent` fills a surface that carries text, `--color-accent-ink` must be defined, verify ≥ APCA Lc 60 / WCAG 4.5:1 against `--color-accent`, and be applied as the `color` on that fill.
|
||||
- **Dark-section ink-on-ink** — any section / panel whose `background-color` is OKLCH lightness < 50 % must also swap its text colour (typically to `--color-paper`) and ensure nested children inherit. A class that sets `background: <dark>` must set `color: <light>` in the same rule (or be wrapped in a parent that does). Common failure: a `.vs__col:first-child` painted with accent / ink but the inner panels still using default ink-coloured text.
|
||||
|
||||
47. Does any **large text** (≥ 24 px regular OR ≥ 18 px bold) or **icon** or **`:focus-visible` ring** have a contrast ratio below **3:1** against its background? Same calculation, looser threshold. Specifically check focus rings — `outline: 2px solid var(--color-focus)` only passes if `--color-focus` has ≥ 3:1 contrast against *both* the element and the page surface.
|
||||
|
||||
48. Does any **button** have `color` ≈ `background-color` on its fill? The canary check: if the computed text colour and the computed background colour are within **5 % lightness AND 0.05 chroma** in OKLCH, fail the gate. This catches the common bug where `color: var(--color-ink)` sits on `background: var(--color-ink)` (black-on-black slop) — the LLM forgot to use `--color-accent-ink` (or `--color-paper`) for text-on-fill.
|
||||
|
||||
49. When `--color-accent` is used as a fill anywhere on the page (button, badge, surface), is `--color-accent-ink` **also defined** (in `:root` or theme tokens) AND used as the `color` for text on that fill? If `--color-accent-ink` is missing, Hallmark output is one careless `color: white` away from a low-contrast accident. The token must exist, must verify ≥ APCA Lc 60 / WCAG 4.5:1 against `--color-accent`, and must be applied wherever accent fills a surface that carries text.
|
||||
|
||||
50. Does any **dark section** (a section or panel whose `background-color` has OKLCH lightness < 50 %) carry text that uses the page-default `color: var(--color-ink)` — i.e. ink-on-ink in a section that flipped its surface? Sections that swap to a dark surface MUST also swap their text colour (typically to `--color-paper`) and ensure nested children inherit. The fix is explicit: any class that sets `background: <dark>` must also set `color: <light>` in the same rule, OR be wrapped in a parent that does. The most common failure: a `.vs__col:first-child` painted with the accent or ink colour but the inner panels still using default ink-coloured text.
|
||||
|
||||
The CSS stamp at Step 6 should record the result: `· contrast: pass (46–50)` if all five gates pass, or `· contrast: FAIL gates <list>` if any are open. Fix before shipping.
|
||||
The CSS stamp at Step 6 should record the result: `· contrast: pass (40–41)` if both gates pass, or `· contrast: FAIL gates <list>` if any are open. Fix before shipping.
|
||||
|
||||
## Nav · footer · hero structural slop
|
||||
|
||||
Universal — apply to every genre. These gates catch the most-recognised AI fingerprints in nav, footer, and hero shape. They sit alongside the structural-fingerprint gate (gate 9): gate 9 catches the *page* fingerprint; 51–55 catch the *chrome* fingerprints that sit on top of it.
|
||||
Universal — apply to every genre. These gates catch the most-recognised AI fingerprints in nav, footer, and hero shape. They sit alongside the structural-fingerprint gate (gate 8): gate 8 catches the *page* fingerprint; 42–45 catch the *chrome* fingerprints that sit on top of it.
|
||||
|
||||
51. **Nav fingerprint.** Is the page's `<nav>` (or top-of-page `<header>` with role="banner") the AI default — wordmark-left + 4–5 inline text links centred-or-right + button-right at full viewport width + 1 px hairline border-bottom + white background? If yes, fail unless the brief explicitly justifies N1 (the page has only 2 destinations *and* the routing table for the genre allows N1). Hallmark output should rotate among N1, N3, N4, N5, N6, N7, N8, N9 from [`component-cookbook.md`](component-cookbook.md) § Navigation.
|
||||
42. **Nav fingerprint.** Is the page's `<nav>` (or top-of-page `<header>` with role="banner") the AI default — wordmark-left + 4–5 inline text links centred-or-right + button-right at full viewport width + 1 px hairline border-bottom + white background? If yes, fail unless the brief explicitly justifies N1 (the page has only 2 destinations *and* the routing table for the genre allows N1). Hallmark output should rotate among N1, N3, N4, N5, N6, N7, N8, N9 from [`component-cookbook.md`](component-cookbook.md) § Navigation.
|
||||
|
||||
52. **Footer fingerprint.** Is the `<footer>` the AI default — 4 columns of links (Product / Company / Resources / Legal) + social-icon row + tiny copyright at the very bottom + 1 px hairline top-border + neutral grey background? If yes, fail unless the page is a genuine docs root or hub. Default to Ft1, Ft2, Ft4, Ft5, Ft6, Ft7, or Ft8 from [`component-cookbook.md`](component-cookbook.md) § Footers.
|
||||
43. **Footer fingerprint.** Is the `<footer>` the AI default — 4 columns of links (Product / Company / Resources / Legal) + social-icon row + tiny copyright at the very bottom + 1 px hairline top-border + neutral grey background? If yes, fail unless the page is a genuine docs root or hub. Default to Ft1, Ft2, Ft4, Ft5, Ft6, Ft7, or Ft8 from [`component-cookbook.md`](component-cookbook.md) § Footers.
|
||||
|
||||
53. **Hero centred-everything.** Are eyebrow, title, lede, AND CTA all stacked centred on the same vertical axis? Auto-fail. Pick at most two centred elements; break alignment for the others. Centred-narrow heroes are admissible *only* on editorial / atelier voice, and even then the eyebrow or CTA should sit off-axis (margin-aligned, right-flush, or numeral-anchored).
|
||||
44. **Hero fit — sits into the page and fits the fold.** Two checks, both on the rendered hero: (a) Is `padding-block-end` ≥ 1.3× `padding-block-start`? Symmetric or top-heavy padding makes the hero float off the page; heavier bottom padding pulls it into the next section's rhythm. (b) On a standard laptop viewport — test at **1280×800** (13″), not just 1440×900 — can the hero's essential content (eyebrow, headline, lede, **and the primary CTA**, plus any hero visual's focal point) all be seen **without scrolling**? This is the other half of gate 6 (which caps the hero's *shape*): this caps its *content*. A hero can satisfy `min-height` and still overflow because the content is intrinsically too tall — usual culprits are an **oversized display clamp**, **loose line-height** (display at ~1.2 instead of 1.0–1.1), a **lede that runs 3+ lines**, or **bloated `padding-block`**. Right-size to the fold: pull the display `clamp()` max down until the headline fits 2–3 lines, set display line-height 1.0–1.1, hold the lede to ~2 lines (≤ ~60 ch), trim hero padding. **Don't overcorrect:** a hero that already fits passes untouched — this never means tiny type or stripped whitespace. Long-form / art-directed statements (a poem broadside, a scroll-poster) may legitimately run taller, but even then the **first screen must read as a complete, deliberate composition** — never a headline sliced in half by the fold.
|
||||
|
||||
54. **Hero padding asymmetry.** Is `padding-block-end` ≥ 1.3× `padding-block-start` on the hero container? If hero pads symmetrically (or pads *more* on top), it floats off the page. Hero must sit *into* the page — heavier bottom padding pulls it down into the next section's rhythm. Compute on the rendered output's hero element.
|
||||
45. **Decorative-without-purpose.** Does the hero contain a decorative element (cursor, scanline, gradient blob, abstract shape, ornament, badge, sticker) that has no semantic anchor in the content? Fail. Decoration must be motivated: a cursor inside a typed command (signals "you'd type next"), a numeral that names an issue / year / version / chapter, a gradient that responds to interaction (HP3 cursor-spotlight), a stamp that names an authorship or date. Random ornaments — a "42" in the corner with no edition meaning, a cursor floating beside a hero, a Pantone chip with no colour rationale — are slop.
|
||||
|
||||
55. **Decorative-without-purpose.** Does the hero contain a decorative element (cursor, scanline, gradient blob, abstract shape, ornament, badge, sticker) that has no semantic anchor in the content? Fail. Decoration must be motivated: a cursor inside a typed command (signals "you'd type next"), a numeral that names an issue / year / version / chapter, a gradient that responds to interaction (HP3 cursor-spotlight), a stamp that names an authorship or date. Random ornaments — a "42" in the corner with no edition meaning, a cursor floating beside a hero, a Pantone chip with no colour rationale — are slop.
|
||||
|
||||
The CSS stamp at Step 6 should record the result alongside contrast: `· nav: N# · footer: Ft# · slop: pass (51–55)`. If any of 51–55 fail, fix before shipping.
|
||||
The CSS stamp at Step 6 should record the result alongside contrast: `· nav: N# · footer: Ft# · slop: pass (42–45)`. If any of 42–45 fail, fix before shipping.
|
||||
|
||||
## Honest copy · no fabricated content
|
||||
|
||||
Universal — apply to every genre. The page must not invent facts about the user's product, team, or market.
|
||||
|
||||
56. **Invented metric.** Does the page contain any quantitative claim — "10× faster", "saves 5 hours per week", "trusted by 50,000+ teams", "99.9 % uptime", "+47 % conversion" — that the user did not supply, that has no source, and that the model fabricated to fill a stat-led layout, comparison row, or proof bar? If yes, fail. The fix is one of: replace the number with `—` and a labelled grey block, replace it with a question to the user ("metric to confirm"), or rebuild the section without the proof slot. Stat-led macrostructures are slop the moment their stats become decorative. *(See [anti-patterns.md § Invented metrics](anti-patterns.md).)*
|
||||
46. **Invented metric.** Does the page contain any quantitative claim — "10× faster", "saves 5 hours per week", "trusted by 50,000+ teams", "99.9 % uptime", "+47 % conversion" — that the user did not supply, that has no source, and that the model fabricated to fill a stat-led layout, comparison row, or proof bar? If yes, fail. The fix is one of: replace the number with `—` and a labelled grey block, replace it with a question to the user ("metric to confirm"), or rebuild the section without the proof slot. Stat-led macrostructures are slop the moment their stats become decorative. *(See [anti-patterns.md § Invented metrics](anti-patterns.md).)*
|
||||
|
||||
## Re-drawn UI chrome
|
||||
|
||||
Universal. Hallmark must reuse the user's existing chrome (browser, OS, IDE) instead of redrawing it.
|
||||
|
||||
57. **Re-drawn chrome.** Did Hallmark hand-build a fake browser bar (URL pill + traffic-light dots), a fake phone frame (rounded rectangle + notch + speaker slit), a fake code-block frame (mock window-chrome around a `<pre>`), a fake terminal frame, or a fake IDE chrome (file tabs + activity bar + sidebar) using HTML/CSS or SVG? If yes, fail. Re-drawn chrome is one of the strongest "looks AI-generated" tells — the model invented a UI that already exists in the user's environment. The fix: use a `<picture>` or `<figure>` containing a real screenshot, or omit the chrome and let the content stand on its own. *(See [anti-patterns.md § Re-drawn UI chrome](anti-patterns.md).)*
|
||||
47. **Re-drawn chrome.** Did Hallmark hand-build a fake browser bar (URL pill + traffic-light dots), a fake phone frame (rounded rectangle + notch + speaker slit), a fake code-block frame (mock window-chrome around a `<pre>`), a fake terminal frame, or a fake IDE chrome (file tabs + activity bar + sidebar) using HTML/CSS or SVG? If yes, fail. Re-drawn chrome is one of the strongest "looks AI-generated" tells — the model invented a UI that already exists in the user's environment. The fix: use a `<picture>` or `<figure>` containing a real screenshot, or omit the chrome and let the content stand on its own. *(See [anti-patterns.md § Re-drawn UI chrome](anti-patterns.md).)*
|
||||
|
||||
## Token discipline
|
||||
|
||||
Universal. The theme picks the palette and font stack at the top of the run; the rest of the run consumes tokens, never invents them.
|
||||
|
||||
58. **Mid-render token improvisation.** Did Hallmark introduce any colour value (`#hex`, `oklch(...)`, `rgb(...)`, `hsl(...)`) or `font-family` declaration *outside* the design tokens defined in `:root` / `[data-theme="..."]`? If yes, fail. Every colour and every font in the artifact must reference a named token (`var(--color-accent)`, `font-family: var(--font-display)`). Inline OKLCH or one-off hexes are mid-render improvisation — the model picked the theme, then forgot it and freestyled. The fix: lift the value into the token block as a new named variable, or replace it with an existing token. *(See [SKILL.md § Locked tokens](../SKILL.md) and [anti-patterns.md § Mid-render token improvisation](anti-patterns.md).)*
|
||||
48. **Mid-render token improvisation.** Did Hallmark introduce any colour value (`#hex`, `oklch(...)`, `rgb(...)`, `hsl(...)`) or `font-family` declaration *outside* the design tokens defined in `:root` / `[data-theme="..."]`? If yes, fail. Every colour and every font in the artifact must reference a named token (`var(--color-accent)`, `font-family: var(--font-display)`). Inline OKLCH or one-off hexes are mid-render improvisation — the model picked the theme, then forgot it and freestyled. The fix: lift the value into the token block as a new named variable, or replace it with an existing token. *(See [SKILL.md § Locked tokens](../SKILL.md) and [anti-patterns.md § Mid-render token improvisation](anti-patterns.md).)*
|
||||
|
||||
## Responsive — clickable affordances
|
||||
|
||||
Universal. Buttons, links, and nav items must remain readable as single-line affordances when the viewport shrinks.
|
||||
|
||||
59. **Two-line clickable text.** Does any button label, primary nav link, footer link, tab label, breadcrumb, or CTA text wrap to two or more lines at any viewport between 320 px and 1920 px? If yes, fail. Clickable text reading on two lines looks broken — visitors read it as a styling error, not as intentional. The fix is one of: shorten the label (the best fix; "Get started free" → "Start free"), set `white-space: nowrap` on the affordance and let the parent reflow, drop a non-essential nav item at narrow widths via `hidden=until-found`, or collapse the nav into a sheet/menu. Never let a CTA or nav link wrap. *(See [responsive.md § Clickable text — never wraps](responsive.md).)*
|
||||
49. **Two-line clickable text.** Does any button label, primary nav link, footer link, tab label, breadcrumb, or CTA text wrap to two or more lines at any viewport between 320 px and 1920 px? If yes, fail. Clickable text reading on two lines looks broken — visitors read it as a styling error, not as intentional. The fix is one of: shorten the label (the best fix; "Get started free" → "Start free"), set `white-space: nowrap` on the affordance and let the parent reflow, drop a non-essential nav item at narrow widths via `hidden=until-found`, or collapse the nav into a sheet/menu. Never let a CTA or nav link wrap. *(See [responsive.md § Clickable text — never wraps](responsive.md).)*
|
||||
|
||||
60. **Emoji-as-feature-icon.** Does any feature card, value prop, step number, or pricing tier carry an emoji glyph (✨ 🚀 ⚡ 🔥 🎯 ✅) as its primary icon? If yes, fail. Emoji-as-icon is one of the strongest "AI-default" tells — the model reached for a Unicode glyph instead of choosing an icon library, building a custom mark, or omitting the icon entirely. The fix: pick a single icon library (Lucide / Phosphor / Heroicons — see [assets.md](assets.md)), build a custom SVG, or drop the icon and lead with typography.
|
||||
|
||||
The CSS stamp at Step 6 should record results: `· honest: pass (56) · chrome: pass (57) · tokens: pass (58) · responsive: pass (59) · icons: pass (60)`. Any failure must be fixed before shipping.
|
||||
The CSS stamp at Step 6 should record results: `· honest: pass (46) · chrome: pass (47) · tokens: pass (48) · responsive: pass (49) · icons: pass (30)`. Any failure must be fixed before shipping.
|
||||
|
||||
## Mobile-responsiveness — the non-negotiables
|
||||
|
||||
Universal. Every emitted page must render flawlessly at 320 px, 375 px, 414 px, and 768 px CSS-pixel widths. Gates 36 (no horizontal scroll) and 59 (no two-line clickable text) already cover the headline cases; 61–65 below codify the patterns the marketing-site responsiveness pass uncovered. Eyeball each viewport before marking the output complete.
|
||||
Universal. Every emitted page must render flawlessly at 320 px, 375 px, 414 px, and 768 px CSS-pixel widths. Gates 34 (no horizontal scroll) and 49 (no two-line clickable text) already cover the headline cases; 50–57 below codify the patterns the marketing-site responsiveness pass uncovered. Eyeball each viewport before marking the output complete.
|
||||
|
||||
61. **Image-bearing grid track without `minmax(0, 1fr)`.** Does any `grid-template-columns` (or `grid-template-rows`) containing a `1fr` track render an `<img>` / `<picture>` / image-bearing element inside one of those tracks? If yes, the track must be `minmax(0, 1fr)` instead. Plain `1fr` resolves to `minmax(auto, 1fr)`, where `auto` minimum is the largest content's intrinsic width — for a 1024 + px native image, that's 1024 + px minimum, which pushes the layout past viewport on phones. The fix is one character per track: `1fr` → `minmax(0, 1fr)`.
|
||||
50. **Image-bearing grid track without `minmax(0, 1fr)`.** Does any `grid-template-columns` (or `grid-template-rows`) containing a `1fr` track render an `<img>` / `<picture>` / image-bearing element inside one of those tracks? If yes, the track must be `minmax(0, 1fr)` instead. Plain `1fr` resolves to `minmax(auto, 1fr)`, where `auto` minimum is the largest content's intrinsic width — for a 1024 + px native image, that's 1024 + px minimum, which pushes the layout past viewport on phones. The fix is one character per track: `1fr` → `minmax(0, 1fr)`.
|
||||
|
||||
62. **Root missing `overflow-x: clip`.** Does the artifact lack `overflow-x: clip` on both `html` and `body`? If yes, fail. `clip` (not `hidden`) prevents horizontal scroll without creating a scroll context, so `position: sticky` descendants keep working. Hard requirement on every emitted page. Add to the base reset: `html, body { overflow-x: clip; }`.
|
||||
51. **Display headers without long-word wrap.** Does any element rendering display-size text (`h1`, `.hero__display`, `.section__title`, `.skill-row__title`, hero-equivalent classes) lack `overflow-wrap: anywhere; min-width: 0`? If yes, fail. Long hyphenated words ("AI-generated", uppercase compound brand names) overflow viewport because the only break opportunity is at the hyphen — `overflow-wrap: anywhere` lets the engine break inside the word as a last resort.
|
||||
|
||||
63. **Display headers without long-word wrap.** Does any element rendering display-size text (`h1`, `.hero__display`, `.section__title`, `.skill-row__title`, hero-equivalent classes) lack `overflow-wrap: anywhere; min-width: 0`? If yes, fail. Long hyphenated words ("AI-generated", uppercase compound brand names) overflow viewport because the only break opportunity is at the hyphen — `overflow-wrap: anywhere` lets the engine break inside the word as a last resort.
|
||||
52. **Per-theme section-head override without mobile collapse.** When a theme or variant overrides `.section__head { grid-template-columns: ... }` to anything other than `1fr`, does it also include the mobile-collapse rule, OR does a global `[data-theme] .section__head { grid-template-columns: 1fr }` exist at `@media (max-width: 48rem)` with matching specificity? If neither, fail. Theme-specific 2-column heads keep their template on mobile, the title wraps onto the section label, and the page reads broken (most visible on Sport: italic Anton title overlapping "02 / EXAMPLES").
|
||||
|
||||
64. **Per-theme section-head override without mobile collapse.** When a theme or variant overrides `.section__head { grid-template-columns: ... }` to anything other than `1fr`, does it also include the mobile-collapse rule, OR does a global `[data-theme] .section__head { grid-template-columns: 1fr }` exist at `@media (max-width: 48rem)` with matching specificity? If neither, fail. Theme-specific 2-column heads keep their template on mobile, the title wraps onto the section label, and the page reads broken (most visible on Sport: italic Anton title overlapping "02 / EXAMPLES").
|
||||
53. **CSS-only radio tab pattern that scroll-jumps.** When implementing tab toggles via `<input type="radio">` siblings + `:checked` selectors, does the artifact either (a) keep the radios in normal document flow with zero size + opacity 0 (no `position: absolute; top: 0`), OR (b) ship a JS handler that intercepts label clicks, calls `e.preventDefault()`, manually sets `radio.checked = true`, dispatches `change`, and focuses with `{ preventScroll: true }`? If the radios are at `position: absolute; top: 0` with no JS guard, fail. Default-position radios cause the page to jump to the section's top on every tab click — visible on every viewport but most disruptive on mobile.
|
||||
|
||||
65. **CSS-only radio tab pattern that scroll-jumps.** When implementing tab toggles via `<input type="radio">` siblings + `:checked` selectors, does the artifact either (a) keep the radios in normal document flow with zero size + opacity 0 (no `position: absolute; top: 0`), OR (b) ship a JS handler that intercepts label clicks, calls `e.preventDefault()`, manually sets `radio.checked = true`, dispatches `change`, and focuses with `{ preventScroll: true }`? If the radios are at `position: absolute; top: 0` with no JS guard, fail. Default-position radios cause the page to jump to the section's top on every tab click — visible on every viewport but most disruptive on mobile.
|
||||
54. **Section eyebrow / tag beside the heading (tag-left, header-right).** Does any section render an eyebrow / number / mono-cap label (`01 · THE TOUR`, `02 / FEATURES`, `Chapter Three`) in a column to the left of, or to the right of, the section heading on the same horizontal row? Auto-fail. The pattern reads as a templated editorial-SaaS tell within seconds. When an eyebrow is used at all (see [`anti-patterns.md § Eyebrow on every section`](anti-patterns.md) — default OFF), the heading goes **directly underneath it in the same column**, vertical stack only. Concretely: **any `<header>`, `<div>`, or `<section>` wrapper — regardless of class name (`.section__head`, `.workbench__head`, `.pricing__head`, `.catches__head`, `.foo__intro`, anything macrostructure- or section-specific)** — that contains both an eyebrow / label / number / kicker element AND a heading element MUST resolve to single-column layout: `display: block`, `display: flex; flex-direction: column`, or `display: grid; grid-template-columns: 1fr` (or `minmax(0, 1fr)`). Any multi-column grid on such a wrapper (`grid-template-columns: 1fr 2fr`, `minmax(0, 0.4fr) minmax(0, 1fr)`, `auto 1fr`, etc.) is banned. **The rule binds on the *content shape* — eyebrow + heading in the same wrapper — not on a specific class-name allowlist.** It supersedes the "Left-margin" axis in [`structure.md`](structure.md) and the "Hanging headers" pattern in [`layout-and-space.md`](layout-and-space.md) for any head that carries an eyebrow. **NOT bypassable by "preserve structural parity" / "mirror this reference" / "match the prior build" instructions** — if a reference build ships the banned pattern (most pre-rule builds do), silently flatten it in the new build. The rules win over parity. Reference builds may pre-date this gate; the gate is authoritative.
|
||||
|
||||
66. **Section eyebrow / tag beside the heading (tag-left, header-right).** Does any section render an eyebrow / number / mono-cap label (`01 · THE TOUR`, `02 / FEATURES`, `Chapter Three`) in a column to the left of, or to the right of, the section heading on the same horizontal row? Auto-fail. The pattern reads as a templated editorial-SaaS tell within seconds. When an eyebrow is used at all (see [`anti-patterns.md § Eyebrow on every section`](anti-patterns.md) — default OFF), the heading goes **directly underneath it in the same column**, vertical stack only. Concretely: **any `<header>`, `<div>`, or `<section>` wrapper — regardless of class name (`.section__head`, `.workbench__head`, `.pricing__head`, `.catches__head`, `.foo__intro`, anything macrostructure- or section-specific)** — that contains both an eyebrow / label / number / kicker element AND a heading element MUST resolve to single-column layout: `display: block`, `display: flex; flex-direction: column`, or `display: grid; grid-template-columns: 1fr` (or `minmax(0, 1fr)`). Any multi-column grid on such a wrapper (`grid-template-columns: 1fr 2fr`, `minmax(0, 0.4fr) minmax(0, 1fr)`, `auto 1fr`, etc.) is banned. **The rule binds on the *content shape* — eyebrow + heading in the same wrapper — not on a specific class-name allowlist.** It supersedes the "Left-margin" axis in [`structure.md`](structure.md) and the "Hanging headers" pattern in [`layout-and-space.md`](layout-and-space.md) for any head that carries an eyebrow. **NOT bypassable by "preserve structural parity" / "mirror this reference" / "match the prior build" instructions** — if a reference build ships the banned pattern (most pre-rule-66 builds do), silently flatten it in the new build. The rules win over parity. Reference builds may pre-date this gate; the gate is authoritative.
|
||||
55. **All-caps display heads with line-height < 1.0 → cap-collision on wrap.** Does any display-size element (`.hero__display`, `.section__title`, `h1`, `h2`, or anything `≥ --text-2xl`) declare both `text-transform: uppercase` AND a `line-height` (or `--lh-tight`) below `1.0`? If yes, fail. Uppercase glyphs have no descenders and their cap-tops sit at the very top of the line box — at `line-height: 0.94` (the old Manifesto / Sport / Brutal default) the cap-tops of line N+1 visibly collide with the baseline or commas of line N when the title wraps. The condensed display faces (Anton, Inter Tight 900, Bebas Neue) make this worse. **Floor for all-caps display heads is `line-height: 1.0`; recommended `1.02–1.08`.** Either bump `--lh-tight` ≥ 1.0 for the theme, or drop `text-transform: uppercase` on the display element. Most visible when a two-line `.section__title` wraps with a trailing comma on line one ("SAME PROMPT, TWO / DIFFERENT OUTPUTS.") — the comma + cap-D fuse into a single glyph blob.
|
||||
|
||||
67. **All-caps display heads with line-height < 1.0 → cap-collision on wrap.** Does any display-size element (`.hero__display`, `.section__title`, `h1`, `h2`, or anything `≥ --text-2xl`) declare both `text-transform: uppercase` AND a `line-height` (or `--lh-tight`) below `1.0`? If yes, fail. Uppercase glyphs have no descenders and their cap-tops sit at the very top of the line box — at `line-height: 0.94` (the old Manifesto / Sport / Brutal default) the cap-tops of line N+1 visibly collide with the baseline or commas of line N when the title wraps. The condensed display faces (Anton, Inter Tight 900, Bebas Neue) make this worse. **Floor for all-caps display heads is `line-height: 1.0`; recommended `1.02–1.08`.** Either bump `--lh-tight` ≥ 1.0 for the theme, or drop `text-transform: uppercase` on the display element. Most visible when a two-line `.section__title` wraps with a trailing comma on line one ("SAME PROMPT, TWO / DIFFERENT OUTPUTS.") — the comma + cap-D fuse into a single glyph blob.
|
||||
56. **Sticky element at `top: 0` below a sticky page-level nav → bleed.** Does the artifact declare `position: sticky; top: 0;` on any element OTHER than the page's top-level nav / banner / header, when a sticky `<header>` / `<nav>` / `.banner` also exists at `top: 0` (i.e. there are two sticky-at-top-0 elements on the page)? Auto-fail. Both stick to the viewport top during scroll and overlap; the deeper-in-DOM element paints over the nav (visible as a "section header bleeding into the nav bar" glitch). Fix: define a `--banner-height` token (~44–64 px depending on nav design) and offset every secondary sticky to `top: var(--banner-height)`, so it docks **beneath** the nav. Also give the nav a higher z-index than in-page sticky elements — split `--z-sticky` (in-page, e.g. 200) from `--z-sticky-nav` (top nav, e.g. 300) so the nav always out-paints when sticky boxes momentarily overlap. This gate fires only when the page actually has sticky elements (S3 sticky-pinned section heads, F2 sticky-scroll feature stacks, sticky tables-of-contents); pages without sticky behaviour pass trivially.
|
||||
|
||||
68. **Sticky element at `top: 0` below a sticky page-level nav → bleed.** Does the artifact declare `position: sticky; top: 0;` on any element OTHER than the page's top-level nav / banner / header, when a sticky `<header>` / `<nav>` / `.banner` also exists at `top: 0` (i.e. there are two sticky-at-top-0 elements on the page)? Auto-fail. Both stick to the viewport top during scroll and overlap; the deeper-in-DOM element paints over the nav (visible as a "section header bleeding into the nav bar" glitch). Fix: define a `--banner-height` token (~44–64 px depending on nav design) and offset every secondary sticky to `top: var(--banner-height)`, so it docks **beneath** the nav. Also give the nav a higher z-index than in-page sticky elements — split `--z-sticky` (in-page, e.g. 200) from `--z-sticky-nav` (top nav, e.g. 300) so the nav always out-paints when sticky boxes momentarily overlap. This gate fires only when the page actually has sticky elements (S3 sticky-pinned section heads, F2 sticky-scroll feature stacks, sticky tables-of-contents); pages without sticky behaviour pass trivially.
|
||||
57. **Studied DNA discarded for a catalog theme.** Did a `study` diagnosis emit earlier in the conversation, AND does the build's CSS stamp's `theme:` field name a catalog theme (Specimen, Midnight, Brutal, Garden, Atelier, Newsprint, Terminal, Manifesto, Almanac, Sport, Studio, Riso, Bloom, Coral, Cobalt, Aurora, Editorial, Carnival, Lumen, Hum) rather than `studied-DNA (source: ...)` — without the user having explicitly pivoted ("use Newsprint instead", "ignore the DNA", "rotate to a different theme")? Auto-fail. The studied DNA was meant to be the system (SKILL.md § 2.6 Condition 0); defaulting back to catalog is the attractor pull. Fix: re-emit using the studied DNA's tokens directly (paper OKLCH, accent OKLCH, named candidate fonts, macrostructure, archetypes) and update the stamp to `theme: studied-DNA (source: <URL or image>)` with the inline values. This gate is trivially passed when no recent study exists in conversation scope.
|
||||
|
||||
69. **Studied DNA discarded for a catalog theme.** Did a `study` diagnosis emit earlier in the conversation, AND does the build's CSS stamp's `theme:` field name a catalog theme (Specimen, Midnight, Brutal, Garden, Atelier, Newsprint, Terminal, Manifesto, Almanac, Sport, Studio, Riso, Bloom, Coral, Cobalt, Aurora, Editorial, Carnival, Lumen, Hum) rather than `studied-DNA (source: ...)` — without the user having explicitly pivoted ("use Newsprint instead", "ignore the DNA", "rotate to a different theme")? Auto-fail. The studied DNA was meant to be the system (SKILL.md § 2.6 Condition 0); defaulting back to catalog is the attractor pull. Fix: re-emit using the studied DNA's tokens directly (paper OKLCH, accent OKLCH, named candidate fonts, macrostructure, archetypes) and update the stamp to `theme: studied-DNA (source: <URL or image>)` with the inline values. This gate is trivially passed when no recent study exists in conversation scope.
|
||||
|
||||
70. **Hero content overflows the first viewport.** On a standard laptop viewport — test at **1280×800** (13″), not just 1440×900 — can the hero's essential content (eyebrow, headline, lede, **and the primary CTA**, plus any hero visual's focal point) all be seen **without scrolling**? If not, fail. This is the other half of gate 7: gate 7 caps the hero's *box* (`min-height`); this caps its *content*. A hero can satisfy `min-height: 75dvh` and still overflow because the content is intrinsically too tall — the usual culprits are an **oversized display clamp** (the `clamp()` max is bigger than what fits), **loose line-height** (display at ~1.2 instead of 1.0–1.1), a **lede that runs 3+ lines**, or **bloated `padding-block`** / stacked empty whitespace. Right-size to the fold: pull the display clamp's max down until the headline fits 2–3 lines above the fold, set display line-height 1.0–1.1, hold the hero lede to ~2 lines (≤ ~60 ch), and trim hero padding so the block fits one screen. **Don't overcorrect:** the target is *intentional fit*, not a shrunken or cramped hero — a hero that already fits passes untouched, and this never means tiny type or stripped whitespace. Long-form / art-directed statements (a poem broadside, a scroll-poster) may legitimately run taller than one screen, but even then the **first screen must read as a complete, deliberate composition** — never a headline sliced in half by the fold. Verify on the rendered output at 1280×800.
|
||||
|
||||
The CSS stamp at Step 6 records mobile pass alongside contrast: `· mobile: pass (36, 59, 61–69)`.
|
||||
The CSS stamp at Step 6 records mobile pass alongside contrast: `· mobile: pass (34, 49, 50–57)`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -14,13 +14,13 @@ Where does a section's title live in space? Pick one per page.
|
||||
|
||||
| Pattern | Description | Real-world reference |
|
||||
| --- | --- | --- |
|
||||
| **Left-margin** | ⚠️ **Opt-in only — never default.** Eyebrow / number / label in a narrow left column with heading + body to the right. Reads as a templated-editorial AI tell when applied to SaaS / dev-tool / consumer pages. Permitted ONLY when the user explicitly asks for an editorial / specimen layout AND no eyebrow is paired with the heading (label may sit beside body copy; heading must stay in its own row above). The eyebrow-left / heading-right variant is banned outright by slop-test gate 66. | The New York Times Magazine; our Specimen theme — when the user explicitly requests that voice. |
|
||||
| **Left-margin** | ⚠️ **Opt-in only — never default.** Eyebrow / number / label in a narrow left column with heading + body to the right. Reads as a templated-editorial AI tell when applied to SaaS / dev-tool / consumer pages. Permitted ONLY when the user explicitly asks for an editorial / specimen layout AND no eyebrow is paired with the heading (label may sit beside body copy; heading must stay in its own row above). The eyebrow-left / heading-right variant is banned outright by slop-test gate 54. | The New York Times Magazine; our Specimen theme — when the user explicitly requests that voice. |
|
||||
| **Hanging** | Heading floats in negative space *above* the section, with generous breathing room. | David Airey's portfolio; minimal modernist. |
|
||||
| **Centered display** | Heading dominates centre stage, symmetrical. Formal, welcoming, can feel static if used everywhere. | Apple product pages; Atelier-style runway invitations. |
|
||||
| **Bottom-aligned** | Heading anchors the *base* of a section, content flows above. Inverts hierarchy. | Swiss editorial; Newsprint masthead-below pattern. |
|
||||
| **Overlapping image** | Heading layered atop photography or colour block. Demands strong contrast. | Pentagram project pages; Manifesto posters. |
|
||||
| **Sticky / pinned** | Heading remains visible while content scrolls beneath. Orientation aid. | GSAP ScrollTrigger docs; Almanac-style references. |
|
||||
| **Numbered display** | ⚠️ **Opt-in only — never default.** "01." with a rule line and the heading right beside it. Procedural, sequenced. Banned for default SaaS / consumer / dev-tool pages by slop-test gate 66 (the tag-beside-heading pattern is a templated tell). Permitted only when the user explicitly asks for ordinal / chaptered numbering AND the macrostructure is Long Document, Manifesto, or Catalogue numbered. Even then, prefer the stacked variant: number on its own line above the heading. | Rauno Freiberg's portfolio — when the user explicitly invokes that voice. |
|
||||
| **Numbered display** | ⚠️ **Opt-in only — never default.** "01." with a rule line and the heading right beside it. Procedural, sequenced. Banned for default SaaS / consumer / dev-tool pages by slop-test gate 54 (the tag-beside-heading pattern is a templated tell). Permitted only when the user explicitly asks for ordinal / chaptered numbering AND the macrostructure is Long Document, Manifesto, or Catalogue numbered. Even then, prefer the stacked variant: number on its own line above the heading. | Rauno Freiberg's portfolio — when the user explicitly invokes that voice. |
|
||||
| **Inline with body** | No section break — the heading emerges from the paragraph flow. Conversational. | Medium articles; long-form essays. |
|
||||
|
||||
### 2. Body composition
|
||||
|
||||
@@ -57,7 +57,7 @@ Composed and sparse. The one hero type-in (plays once). Section reveals fade + r
|
||||
- **No warm paper** — warm grey is Coral's. Cobalt is cool (hue ~250–258).
|
||||
- **No pure `#fff` / `#000`.** Cool near-white paper, cool charcoal ink.
|
||||
- **No pill / gradient CTAs.** One solid cobalt button at 6px radius + understated typographic links. Name the destination.
|
||||
- **No centred-everything hero** (gate 53) — left-biased, title-left / demo-right.
|
||||
- **No centred-everything hero** (gate 6) — left-biased, title-left / demo-right.
|
||||
- **No three-equal-icon-tile feature grid** (gate 3) — vary it (one wide code example + narrower points; asymmetric benchmark figures).
|
||||
- **No glassmorphism, no gradient text** (gates), **no aurora/mesh blob, and no background texture/pattern at all** — cool paper + hairlines carry the page; its one dark beat is the graphite band (signature 8), not a background.
|
||||
|
||||
|
||||
@@ -375,7 +375,7 @@ Brand wordmarks — lowercase: `cinder`, `wright`, `stratum`. Legal entity names
|
||||
- **Never colour-emphasise a noun.** The verb landmark is the verb. Always.
|
||||
- **Never more than one accent-coloured word per headline.** Two is a marketing landing page; one is a design move.
|
||||
- **Never invent stats.** If the brief has no real numbers, skip the three-stat row entirely.
|
||||
- **Never re-draw browser chrome.** Real screenshots only (slop-test gate 57).
|
||||
- **Never re-draw browser chrome.** Real screenshots only (slop-test gate 47).
|
||||
- **Never two apparatus objects on one page.** One per build.
|
||||
- **Never a system font for the display.** Instrument Serif or the documented fallback chain (`"Tiempos Headline", ui-serif, Georgia, serif`).
|
||||
- **Never rotate the apparatus.** Night apparatus pulses; Day apparatus is static.
|
||||
|
||||
@@ -221,7 +221,7 @@ Use no more than five sizes on a single page. If you need more hierarchy, use we
|
||||
## Body text rules
|
||||
|
||||
- Minimum 16px. Below 14px is accessibility-hostile.
|
||||
- Line-height 1.5–1.65 on body copy, tighter (1.1–1.3) on display. **Floor for all-caps display heads (`text-transform: uppercase` on `.hero__display` / `.section__title` / `h1` / `h2`) is `1.0` — recommended `1.02–1.08`.** Below 1.0 the cap-tops of line N+1 collide with the baseline of line N (no descenders to cushion the gap); the comma + cap-D on a wrapped "PROMPT, / DIFFERENT" fuse into a single glyph blob. Condensed display faces (Anton, Inter Tight 900, Bebas Neue) make this worse. Gate 67 auto-fails the pattern.
|
||||
- Line-height 1.5–1.65 on body copy, tighter (1.1–1.3) on display. **Floor for all-caps display heads (`text-transform: uppercase` on `.hero__display` / `.section__title` / `h1` / `h2`) is `1.0` — recommended `1.02–1.08`.** Below 1.0 the cap-tops of line N+1 collide with the baseline of line N (no descenders to cushion the gap); the comma + cap-D on a wrapped "PROMPT, / DIFFERENT" fuse into a single glyph blob. Condensed display faces (Anton, Inter Tight 900, Bebas Neue) make this worse. Gate 55 auto-fails the pattern.
|
||||
- Measure 45–75 characters (`max-width: 65ch`).
|
||||
- Never all-caps body copy. Never justified text without hyphenation. Never letter-spacing above 0.05em on body.
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ For each target page:
|
||||
|
||||
### 4. Diversification rule — INVERTED for multi-page
|
||||
|
||||
Across pages of the same app, the diversification rule is *inverted*: consecutive pages MUST share theme, accent, type pairing. They may differ on macrostructure within the family. The 70 slop-test gates that check "differs from previous Hallmark run" are skipped for `designed-as-app` outputs — the system overrides the catalog rotation here.
|
||||
Across pages of the same app, the diversification rule is *inverted*: consecutive pages MUST share theme, accent, type pairing. They may differ on macrostructure within the family. The 57 slop-test gates that check "differs from previous Hallmark run" are skipped for `designed-as-app` outputs — the system overrides the catalog rotation here.
|
||||
|
||||
Pages that drift from `design.md` are slop. The audit verb flags `design.md` drift as a critical structural finding (`stamp-vs-design.md disagreement`).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user