Reflex gets its own grade, the Floor gets a second door, and the shots are true again

Four things the last pass left open.

Reflex findings no longer masquerade as Floor failures. They report at a new
REFLEX grade which still blocks, so nothing drifts silently, but "0 FAIL" is now
a true statement about the floor even while a build owes an argument above it.
Across every committed page that distinction turns 230 reported failures into 56
real Floor findings and 174 arguable ones, which is the number that was hiding.

The waiver cap is gone. Three-per-artifact was the restraint the tier existed to
remove: a colour-field poster that wants a black stage, achromatic neutrals, a
long measure and one face doing everything is right, and it was being blocked for
being expressive. Guards, reason quality and the log are what keep waivers honest.
Past three the build gets a NOTE saying what it now is, a house style rather than
a set of exceptions, and pointing at design.md.

The Floor has two admission criteria instead of one embarrassed exception. Door 1
is "no admired page breaks this on purpose". Door 2 is a named house rule where
admired pages do break it but a language model reaches for it unprompted on every
section. Gate 54 is the only member, listed in a table that any future addition
has to join explicitly. The apology is gone; the reasoning is in the file.

Finish findings gained a record form. `answered <check> · <reason>` parses, so a
build that reasons its way past gate 38 leaves something the next run can read
instead of prose in a comment.

Also: the skill stops mandating em and en dashes in page copy, which had it
arguing with its own house rule. The Picks block and the Step 5 preview merge into
one recital, since the same decisions were being serialised four times. The font
catalog and the per-tone voice samples become skip-unless-needed, roughly 200
lines a build. Two waiver bugs found by a cold build are fixed: "intentional
design choice" was passing the boilerplate test, and a malformed two-field waiver
was being ignored in silence.

The 18 hero captures and 14 gallery thumbnails are regenerated against the live
pages, so the README and the homepage stop advertising eyebrows the skill bans.
This commit is contained in:
Youssef
2026-08-03 13:27:37 +01:00
parent 869d6be23e
commit 5b4bf7e443
42 changed files with 153 additions and 73 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ Made by Together AI.
Hallmark holds a hard floor and then gets out of the way. It dresses the brief in one of twenty-four themes, runs a fifty-eight-gate slop test plus a pre-emit self-critique, and refuses the on-distribution defaults every LLM was trained into.
Thirty-four of those gates are a **floor**: contrast, focus order, reduced motion, invented metrics, layout that breaks on a phone, and the eyebrow above the heading. Nothing ships through them. Eighteen are **reflex** gates, the defaults a language model falls into rather than laws, and a build with an argument can waive one on the record: three at most, reason named in the CSS stamp, guard checked mechanically. The last six are polish and bookkeeping and never block a ship.
Thirty-four of those gates are a **floor**: contrast, focus order, reduced motion, invented metrics, layout that breaks on a phone, and the eyebrow above the heading. Nothing ships through them. Eighteen are **reflex** gates, the defaults a language model falls into rather than laws, and a build with an argument can overrule one on the record, with the reason named in the CSS stamp and the guard re-derived by the checker. There is no cap on how many: a colour-field poster that wants a black stage, achromatic neutrals and one face doing everything is allowed to say so and ship. The last six are polish and bookkeeping and never block a ship.
Two pages by Hallmark for two different briefs feel like different sites, not colour-swaps of the same template. The skill is built to stop a model looking generic, not to stop it being good.
+3 -9
View File
@@ -2,21 +2,15 @@
What's next. (v1.2 shipped: four new themes with the image hook, spec files for all 24 themes, the edit-time lint hook, the Tier A conformance harness, variants v2 with progressive reveal + grafts + thumbnails + scoped injection and the corner dock, and the harness pivot - one core with per-harness adapters for Claude Code / Codex / OpenCode plus the multi-harness installer.)
**Since v1.2: the floor / library split.** The eyebrow is banned outright, in every geometry, with a DOM-shape detector behind it and twelve replacement openings in `section-entry.md`. The 58 gates gained tiers: 34 carry a Floor clause that nothing ships through, 18 are Reflex gates a build can waive on the record (three per artifact, guard re-derived mechanically), 6 never block. Themes were cut to a written contract and are material rather than page plans, which took them from 4,070 lines to 2,861. The catalogs stopped being a mandatory pick and became a place to look.
**Since v1.2: the floor / library split.** The eyebrow is banned outright, in every geometry, with a DOM-shape detector behind it and twelve replacement openings in `section-entry.md`. The 58 gates gained tiers: 34 carry a Floor clause that nothing ships through, 18 are Reflex gates a build can waive on the record with a mechanically re-derived guard and no cap, 6 never block. `FAIL` and `REFLEX` are separate grades, so "0 FAIL" is a true claim about the floor even while a build still owes an argument above it. Themes were cut to a written contract and are material rather than page plans, which took them from 4,070 lines to 2,861. The catalogs stopped being a mandatory pick and became a place to look.
---
## Now
**Regenerate the marketing screenshots.** The 18 hero captures in `docs/screenshots/` and the gallery thumbnails under `site/_tests/_thumbs/` still show the eyebrows that were removed from the example pages. They are stale evidence on the README and the homepage until they are recaptured.
**Pay down the floor debt in the committed corpus.** Now that the gates are tiered, the real number is visible: across every committed page there are **56 Floor findings**, not the 230 the old flat linter reported. They cluster as 18 token improvisations (gate 48), 9 layout animations (14), 8 pages missing `overflow-x: clip` (34), 4 missing reduced-motion fallbacks (27), 4 missing stamps (20), 4 purple gradients (2), 3 long-word wraps (51), 3 missing focus rings (26), 2 double-sticky (56) and one placeholder name (19). Several are access failures and none of them is taste. This is the highest-value cleanup left.
**Give the Finish tier a stamp form.** A Reflex gate can be waived on the record and the linter reads it. A Finish finding is supposed to be "read and answered", but there is no syntax for an answered-and-dismissed one, so a build that reasons its way past gate 38 writes freeform prose the linter cannot see and the next run cannot parse. Either an `answered` stamp line with the same three-field shape, or an explicit statement that Finish findings need no record.
**Decide how many times a build restates its own decisions.** The Picks block, the Step 5 preview, the CSS stamp and the log entry carry substantially the same content. None of it is design work, and it is the largest remaining ceremony cost. The preview block is the likeliest cut: the Picks block already carries everything except the enrichment answer.
**`copy.md` still mandates em and en dashes** (§ Punctuation) while the repo's own house rule bans them, so a build that follows the skill faithfully reintroduces them into page copy. Worth resolving in one direction or the other rather than leaving the skill arguing with its owner.
**Two more index-then-pick candidates.** `copy.md` is ~184 lines of which most are voice samples for tones a given build is not using, and `typography.md`'s font catalog is dead weight whenever a catalog theme has already named its three faces. Both are on the every-build list; scoping them would save roughly 200 lines per run.
**A Floor violation sits on the flagship example.** `site/examples/tally` italicises one word inside its roman hero headline, which is gate 38a-i, and that page is the first card in the README table. Either fix the headline or stop leading with it.
**Conformance on all three harnesses.** Tier A (`gen-cli.mjs`) drives Claude Code today; run it in a logged-in terminal. Next: the same conformance pass through `codex exec` and OpenCode's non-interactive run, asserting each harness loads its adapter (`harnesses/codex.md` / `harnesses/opencode.md`), holds the load-order discipline, and lands 0-FAIL sloplint. (The old Tier B bare-API open-model matrix is retired from the roadmap; the tooling stays in `eval/` as an archive.)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 KiB

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

After

Width:  |  Height:  |  Size: 385 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 KiB

After

Width:  |  Height:  |  Size: 767 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 537 KiB

After

Width:  |  Height:  |  Size: 735 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

After

Width:  |  Height:  |  Size: 525 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 773 KiB

After

Width:  |  Height:  |  Size: 690 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 483 KiB

After

Width:  |  Height:  |  Size: 718 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 511 KiB

After

Width:  |  Height:  |  Size: 759 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 437 KiB

After

Width:  |  Height:  |  Size: 674 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

After

Width:  |  Height:  |  Size: 567 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 814 KiB

After

Width:  |  Height:  |  Size: 740 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 955 KiB

After

Width:  |  Height:  |  Size: 524 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 559 KiB

After

Width:  |  Height:  |  Size: 816 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 437 KiB

After

Width:  |  Height:  |  Size: 662 KiB

+18 -23
View File
@@ -31,19 +31,19 @@ These bind on every output, in every verb, on every model, and **none of them is
9. Never ship an eyebrow, kicker, or overline: short inert type before a heading that announces what the heading is about. Not stacked, not beside, not as a badge pill, not with an ordinal. Open the section another way ([`references/section-entry.md`](references/section-entry.md)). (gate 54)
10. The hero's headline, lede, and primary CTA are all visible at 1280x800 without scrolling. (gate 44b)
11. The first non-empty line of emitted CSS is the `/* Hallmark · macrostructure: ... */` stamp; the pre-emit critique comment sits directly under it, then any waiver lines, then a custom build's direction contract. (gate 20)
12. If Node is available, run `node <skill-dir>/scripts/sloplint.mjs <output>` before handing back and fix every FAIL.
12. If Node is available, run `node <skill-dir>/scripts/sloplint.mjs <output>` before handing back. Fix every `FAIL`; fix or waive every `REFLEX`.
**Above the Floor, the skill is advice.** A banned display font, an accent that fills the page, a pure-black stage, an italic display *system*, four type families: these are **Reflex** gates. They are the defaults a language model falls into, not laws, and a build with a real reason may waive one on the record. The tiers, the waiver syntax, and the caps live in [`references/slop-test.md`](references/slop-test.md) § Tiers.
**Above the Floor, the skill is advice.** A banned display font, an accent that fills the page, a pure-black stage, an italic display *system*, four type families: these are **Reflex** gates. They are the defaults a language model falls into, not laws, and a build with a real reason may waive one on the record. There is no cap on how many a build may waive; the guard, the reason, and the log are what keep it honest. The tiers, the two doors into the Floor, the grades and the waiver syntax live in [`references/slop-test.md`](references/slop-test.md) § Tiers.
## Flow at a glance
0. Pre-flight scan of the existing project.
1. Ask Audience / Use case / Tone once; detect genre; note custom signals.
2. Design the page's shape, nav, footer, hero, and section entry; run the Rotation rules; say the Picks block. (Catalogs available, never required.)
2. Design the page's shape, nav, footer, hero, and section entry; run the Rotation rules. (Catalogs available, never required.)
2.6. Theme route: studied-DNA, catalog, or custom (custom runs the ritual in [`references/custom-theme.md`](references/custom-theme.md)).
3. Load ONLY the listed reference files.
4. Enrichment decision (scene sentence first; most pages are typography-only).
5. Preview block, then pause a beat before code.
5. The Picks block (the run's only prose recital), then pause a beat before code.
6. Build: tokens, stamp, `tokens.css`, log append.
7. Slop test: sloplint script first, then the judged gates.
@@ -177,7 +177,7 @@ Two non-default signals firing (rare): ask one short either/or. State the genre
### 2. Structure and rotation
**Do:** design the shape this brief wants, check it against what you built last time, and say the Picks block before writing any code.
**Do:** design the shape this brief wants, and check it against what you built last time. Hold the decisions; they are said once at Step 5.
**Design the page, then name what you designed.** Decide the page's shape, how it opens, how sections separate, what the nav and footer are doing, how tall the hero stands and what carries it. State those decisions in your own words. You are a capable designer; this step is not a form to fill in.
@@ -207,18 +207,9 @@ Two things the catalogs are not: a checklist you owe, and a rotation you must wa
The `axes` and `fingerprint` fields are how Rotation and gate 32 become checkable instead of remembered; older entries without them are treated as unconstrained. `waived` records any Reflex gate this build waived: the same gate waived three runs running is not an exception any more, it is an unstated house style, and it belongs in a `design.md`. First run for a project: no constraint, note it in one line. User explicitly re-orders the same shape: same shape, different values, and say the deltas.
**The mirror test, before the block.** Would this exact combination have come out for a NEIGHBOURING brief (same category, different product)? If yes, at least one decision must change, and the Picks block names which one in its parenthetical. A choice that survives the mirror is a decision; one that doesn't was a default.
**The mirror test.** Would this exact combination have come out for a NEIGHBOURING brief (same category, different product)? If yes, at least one decision must change, and the Picks block names which one in its parenthetical. A choice that survives the mirror is a decision; one that doesn't was a default.
**The Picks block.** One compact fenced block, said once, replacing all narration. Name a catalog code where you used one, plain words where you did not:
```
Picks · genre: modern-minimal · structure: Workbench (last 3: Bento, Long Document, Manifesto)
· theme: Cobalt (differs on paper band + display style) · hero: Stage/H2 (prev Settled)
· nav: N13 (prev N5) · footer: a single justified colophon line · section entry: rule + change of paper
· deck: -logos +worked-example (SaaS decks only) · enrichment: tbd (Step 4) · custom: no
```
The enrichment cell may read `tbd (Step 4)`; the decision lands there and the Step 5 preview carries the final value. On a custom run the Picks block emits after the 2.6 ritual instead, so it can carry the draw line.
**Do not narrate any of this yet.** Decide, hold the decisions, and say them once at Step 5. Every build used to state the same set four times over (a Picks block here, a preview block at Step 5, the CSS stamp, the log entry); the stamp and the log are machine-readable records with different readers, but two prose recitals of the same list is ceremony, not design. One block, at the point where every cell has a real value.
### 2.6. Theme route
@@ -245,6 +236,8 @@ A custom system is complete (palette + pairing + axes), never a colour swap; its
**Every build:** [`references/typography.md`](references/typography.md) · [`references/color.md`](references/color.md) · [`references/layout-and-space.md`](references/layout-and-space.md) · [`references/motion.md`](references/motion.md) · [`references/copy.md`](references/copy.md) · [`references/anti-patterns.md`](references/anti-patterns.md) · [`references/section-entry.md`](references/section-entry.md) · [`references/finish.md`](references/finish.md).
Two of those carry sections you should skip rather than read whole: `typography.md` § The font catalog is dead weight when a catalog theme has already named the faces, and `copy.md` § Voice samples per tone is seven blocks of which six are not your tone. Skipping both saves roughly 200 lines a build.
**Conditionally (be honest, no defensive pre-loads):** [`references/microinteractions.md`](references/microinteractions.md) when anything is interactive (most pages); [`references/interaction-and-states.md`](references/interaction-and-states.md) for stateful UI; [`references/responsive.md`](references/responsive.md) when mobile is in scope; [`references/structure.md`](references/structure.md) only when deviating from a named macrostructure; [`references/assets.md`](references/assets.md) only when an enrichment needs an external asset; [`references/texture.md`](references/texture.md) only when the picked theme earns texture (Riso, Carnival, Arcade, faint Newsprint) or a custom draw has print lineage; [`references/scroll-choreography.md`](references/scroll-choreography.md) only when the brief asks for scroll story / cinematic pacing or the macro is Feature-stack / Narrative Workflow; [`references/dark-mode.md`](references/dark-mode.md) only when the user asks for both modes; [`references/data-viz.md`](references/data-viz.md) when the brief involves charts / data / dashboards or the macro is Stat-Led / Workbench; [`references/brand-truth.md`](references/brand-truth.md) when the brief names a real brand or company to build for; [`references/custom-theme.md`](references/custom-theme.md) only on the custom route; [`references/design-md.md`](references/design-md.md) only when the user asks to lock the system; [`references/preview-examples.md`](references/preview-examples.md) only if the Step 5 spec is not scaffolding enough.
**At the end only:** [`references/slop-test.md`](references/slop-test.md) strictly at Step 7 (pre-loading it costs thousands of tokens for nothing; `anti-patterns.md` is the pre-emit list); [`references/contract.md`](references/contract.md) at handoff; [`references/export-formats.md`](references/export-formats.md) only on `design.md` projects.
@@ -263,21 +256,23 @@ Then run the image-need table at [`references/hero-enrichment.md` § Image-need
### 5. Preview
**Do:** emit the preview block, then stop for a beat before writing code. This is the user's five-second redirect window.
**Do:** emit the Picks block, once, then stop for a beat before writing code. This is the only prose recital of the build's decisions and it is the user's five-second redirect window. Everything decided at Steps 2, 2.6 and 4 lands here together, which is why nothing was narrated earlier.
```markdown
**Hallmark · v1.2.0**
- **Macrostructure** · Stat-Led
- **Theme** · Coral (near-white paper · quiet neutrals · coral accent)
- **Structure** · Stat-Led (last 3: Bento, Long Document, Manifesto) — genre modern-minimal
- **Theme** · Coral (near-white paper · quiet neutrals · coral accent; differs from Newsprint on paper band + display style)
- **Chrome** · nav N13 (prev N5) · footer: a single justified colophon line
- **Hero** · Stage/H2 (prev Settled)
- **Section entry** · a hairline rule, repeated, plus one change of paper
- **Enrichment** · none (typography only)
- **Sections** · Hero · Logos · Stats · Features · Pricing · FAQ · CTA · Footer
- **Motion** · counter · pricing-lift · pulse-once
- **Slop test** · Floor 34/34 ✓ · Reflex 18 · Finish clean (run after Build)
- **Diversification** · differs from Newsprint on paper band + display style
```
Custom builds add two bullets: `**Direction** · <name> (draw n/7, wildcard yes/no)` and `**Posture** · <Restrained | Committed | Full palette | Drenched>`. Any page may add `**Signature** · <the one move>`.
Name a catalog code where you took one, plain words where you designed it. SaaS deck runs add `**Deck** · -logos +worked-example`. Custom builds add `**Direction** · <name> (draw n/7, wildcard yes/no)` and `**Posture** · <Restrained | Committed | Full palette | Drenched>`. Any page may add `**Signature** · <the one move>`.
The Slop test row must reflect the real Step 7 outcome; a fabricated `Floor 34/34` is itself slop. Any waived Reflex gate is named in the row (`Reflex 18 (1 waived: 23)`). If gates fail at Step 7, fix and emit a **one-line delta** (`Slop test · Floor 34/34 after 2 fixes: gates 41, 44`), not the whole block again.
@@ -322,9 +317,9 @@ Component scope runs the component sweep named in `slop-test.md`. Update the pre
Triggers: the user passes `--fast` / says "fast", or the brief itself says quick / rough / demo / prototype / throwaway. A short brief alone is NOT a trigger; a 10-word real-product brief still gets the Step 1 ask.
Behaviour: `--fast` counts as "go ahead" at the Step 1 gate (infer + one-line disclosure); total narration caps at three lines (inference line · Picks block · done line); the preview emits once with no CTA line and no re-emit; the pre-flight cache is reused silently.
Behaviour: `--fast` counts as "go ahead" at the Step 1 gate (infer + one-line disclosure); total narration caps at three lines (inference line · Picks block · done line); the block emits once with no CTA line and no re-emit; the pre-flight cache is reused silently.
Not relaxed: **the Floor**, the stamp, `tokens.css`, the log append, contrast checks. Fast mode may waive Reflex gates without the three-per-artifact cap, and skips the Finish tier. It cuts ceremony and argument, never the Floor.
Not relaxed: **the Floor**, the stamp, `tokens.css`, the log append, contrast checks. Fast mode may waive Reflex gates with a one-line reason and skips the Finish tier. It cuts ceremony and argument, never the Floor.
---
+1 -1
View File
@@ -154,7 +154,7 @@ Straight `"` and `'` in rendered text. Fix: curly quotes and apostrophes. (F1)
### Three periods instead of ellipsis
`...` in body copy. Fix: the ellipsis character (U+2026). (F2)
### Double-hyphen dashes
`--` in body copy where a dash belongs. Fix: a true em dash (U+2014).
`--` in body copy. Fix: recast with a comma, colon, semicolon, or parentheses. Hallmark does not reach for the em dash (copy.md § Proper typography).
### Spinners that flash
A spinner visible 50ms on a fast action. Fix: delay-show 150ms, or minimum 300ms visible.
### Collapsing helper-text slot
+3 -1
View File
@@ -56,7 +56,7 @@ Three beats:
## Proper typography
- Curly quotes: `"Hello"`, `'word'`.
- Em-dash for interruption: `` (U+2014). En-dash for ranges: `1020` (U+2013). Never `--`.
- **Dashes: prefer punctuation that is not a dash.** A comma, a colon, a semicolon, or parentheses will carry an interruption, and `to` will carry a range (`10 to 20`). Where a range must be typographic, a hyphen reads correctly at small sizes. **Never `--`.** *(House rule, and a deliberate departure from standard practice: the em dash has become one of the loudest machine-written tells in prose, and a page that never reaches for one never trips it. If a project's own style guide wants em dashes, that guide wins; this is the default in their absence.)*
- Ellipsis: `…` (U+2026). Never `...`.
- Apostrophe: ``. Never the prime `'`.
- Non-breaking space before units: `10 kg`, `5 min` (use `&nbsp;` or U+00A0).
@@ -67,6 +67,8 @@ If the text is loaded from a CMS, configure Smart Quotes in the CMS. If it's har
## Voice samples per tone
**Read only your tone's block.** These are seven independent sets and six of them are noise on any given build; scrolling all of them is ~100 lines you cannot act on.
The skill bends toward distribution-default copy ("Built for the modern team", "Unleash your X", "Where A meets B") unless given non-default voices to imitate. The samples below are *real opening lines* from sites that defy that distribution. **Imitate the kind of specificity** — named places, named dates, named verticals, refusal of metaphor, refusal of the verb — not the wording. The tone column maps to the seven tones the design-context gate (see [`SKILL.md`](../SKILL.md) Step 1) commits the user to.
### Editorial
@@ -1,46 +1,43 @@
# Preview-block worked examples
Four sample Step 5 preview blocks for the model to imitate, varied across macrostructure types. Load this file only when picking an unusual macrostructure or custom theme and the bullet-list spec in `SKILL.md § 5. Preview` doesn't give enough scaffolding on its own. Most builds don't need to read this file.
Four sample Step 5 Picks blocks for the model to imitate, varied across page types. Load this file only when the bullet-list spec in `SKILL.md § 5` doesn't give enough scaffolding on its own. Most builds don't need to read this file.
---
*Long Document (editorial, motion-cut):*
> **Hallmark · v1.2.0**
>
> - **Macrostructure** · Long Document
> - **Structure** · Long Document
> - **Theme** · Newsprint (cool slate paper · steel-blue accent · geometric sans)
> - **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** · Floor 34/34 ✓ · Reflex 18 · Finish clean (run after Build)
> - **Diversification** · first run for this project
*Bento Grid (SaaS, motion-on):*
> **Hallmark · v1.2.0**
>
> - **Macrostructure** · Bento Grid
> - **Structure** · Bento Grid
> - **Theme** · Newsprint (cool slate paper · steel-blue accent · geometric sans)
> - **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** · Floor 34/34 ✓ · Reflex 18 · Finish clean (run after Build)
> - **Diversification** · differs from Coral on paper hue (light-cool vs pure-white) + accent (indigo vs ink-blue)
*Manifesto (declarative, no enrichment):*
> **Hallmark · v1.2.0**
>
> - **Macrostructure** · Manifesto
> - **Structure** · Manifesto
> - **Theme** · Manifesto (dark · Inter Tight 900 · single red bleed)
> - **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** · Floor 34/34 ✓ · Reflex 18 · Finish clean (run after Build)
> - **Diversification** · differs from Newsprint on paper band (dark vs light) + display style (display-heavy vs geometric-sans)
*Custom (Coffeebox archival café):*
> **Hallmark · v1.2.0**
>
> - **Macrostructure** · Long Document
> - **Structure** · Long Document
> - **Direction** · the postal frank (draw 6/7, wildcard no)
> - **Posture** · Full palette (kraft field · frank red · ink · airmail-blue seal)
> - **Theme** · custom (vibe: "archival warmth, hand-set, no varnish" · paper oklch(95% 0.015 80) · accent oklch(55% 0.17 30) frank-red · Bricolage Grotesque display + Source Serif 4 body, both roman)
@@ -48,4 +45,3 @@ Four sample Step 5 preview blocks for the model to imitate, varied across macros
> - **Sections** · Masthead · Letter · Three Notes · Visit · Colophon
> - **Motion** · breathing-loop on bean (with reduced-motion fallback)
> - **Slop test** · Floor 34/34 ✓ · Reflex 18 · Finish clean (run after Build)
> - **Diversification** · custom axes: light / geometric-sans / warm - differs from previous catalog Newsprint on display style
+47 -7
View File
@@ -2,7 +2,7 @@
Run this list before handing back any output. Update the Step 5 preview block's `Slop test` row to reflect the actual outcome of this run.
Thirty-four of the fifty-eight gates carry a **Floor** clause and nothing ships through them. Eighteen are **Reflex** only: the defaults a language model falls into, which a build with an argument may waive on the record. The last six never block a ship (four **Finish**, two bookkeeping). The tiers, and the question that sorts them, are below.
Thirty-four of the fifty-eight gates carry a **Floor** clause and nothing ships through them. Eighteen are **Reflex** only: the defaults a language model falls into, which a build with an argument may waive on the record, with no cap on how many. The last six never block a ship (four **Finish**, two bookkeeping). The tiers, the two doors into the Floor, and the grades are below.
Some gates are **universal** (apply to every genre); some are **genre-scoped** (apply only when the active genre is editorial, atmospheric, modern-minimal, or playful). Genre overrides are noted inline. Where a gate has *no* genre note, treat it as universal.
@@ -30,6 +30,36 @@ Every gate carries a **tier**, which says how hard it binds. The question that s
- **[a11y]** A tag, not a tier, on the nine gates that are also an access requirement. It exists so `audit` can group them and so no later pass quietly re-tiers them.
- **[Ledger]** A tag on the two gates that are skill bookkeeping rather than design quality (32, 57). They report at grade `NOTE`, never set the exit code, and never count toward the Floor verdict. A page is not slop for repeating last month's archetype; it is just a repeat.
### The second door into the Floor
That question sorts design rules, and Hallmark is not a general design authority: it is an anti-slop skill for language models. So the Floor has a second, narrower admission criterion, and a gate needs only one of the two:
> **1.** No admired page breaks it on purpose. (Correctness, access, honesty, and the bans.)
> **2. House rule.** Admired pages *do* break it, but it is a reflex so reliably machine-made that Hallmark chooses to be stricter than the field, and says so out loud.
Door 2 is deliberately hard to walk through, because every rule that uses it is the skill overruling good designers. The membership is small, explicit, and listed here in full:
| Gate | House rule | Why it is stricter than the field |
| --- | --- | --- |
| **54** | No eyebrow, kicker, or overline, in any geometry | Plenty of admired pages ship one. A language model ships one on *every section*, unprompted, and it is the single most reliable tell in the set. Removing the option outright is worth more than the pattern is. |
Anything added to this table has to name the reflex it is displacing and accept that it is a house preference, not a fact about design. Nothing else gets in through door 2.
### Grades
The tier says whether you are allowed to disagree. The **grade** says what the checker found and what it costs:
| Grade | Meaning |
| --- | --- |
| `FAIL` | A Floor finding. Nothing ships. Not waivable. |
| `REFLEX` | An unanswered Reflex finding. Also blocks, but it is not a Floor failure: fix it or waive it on the record. |
| `WAIVED` | A Reflex finding the build waived, guard satisfied. Visible, never silent. |
| `WARN` | The checker suspects but cannot prove; confirm or dismiss by judgment. |
| `ANSWERED` | A Finish finding the build recorded an answer for. |
| `NOTE` | Bookkeeping. Never blocks, never counts toward the verdict. |
`FAIL` and `REFLEX` are reported separately on purpose, so **"0 FAIL" stays a true statement about the floor** even while a build still owes an argument somewhere above it.
**Split gates keep their number and gain two clauses** (`7a` Floor, `7b` Reflex). That is how a gate can hold a real floor without also enforcing a taste threshold, and it is why the count is still 58.
The tier table here is the single source of truth. `anti-patterns.md`'s groups and `sloplint.mjs`'s `TIERS` map are derived from it; when they disagree, this file wins.
@@ -43,7 +73,7 @@ Every gate below also carries a class tag, which says **who can check it**:
- **[R/J]** Render-verifiable: confirm on the rendered page when rendering is available, else judge.
- **[J]** Judged: the model verifies these at Step 7; no script can.
Tier and class are independent. A Floor gate can emit WARN (the script suspects but cannot prove); a Reflex gate can emit FAIL (the script is certain). The grade says how sure the checker is; the tier says whether you are allowed to disagree.
Tier and class are independent: a Floor gate can emit `WARN` when the script suspects but cannot prove, and a Reflex gate emits `REFLEX` when it is certain. Class says who can check it, grade says what was found, tier says whether you are allowed to disagree.
When sloplint has run, walk the J and R/J gates, plus the judged halves of M/R gates when --render did not run, and confirm or dismiss every WARN; re-litigating M gates the script already passed is wasted judgment. Verification is budgeted: one batched inspection round, one fix batch, at most one confirm round, then stop.
@@ -60,12 +90,22 @@ Three fields, in order: **the gate**, **the guard evidence**, **the reason**. Th
The rules:
- **Floor is never waivable.** Naming one is rejected and the gate fires normally.
- **Three per artifact directory, one per gate.** A fourth waiver is rejected, and **its gate then runs as if unwaived** — so waiving twelve gates leaves you strictly worse off than waiving none.
- **The reason has to be about this page.** Under 24 characters, or boilerplate ("design choice", "intentional", "looks better", "the brief requires it"), or shared word-for-word with another waiver, and it is rejected.
- **Waivers are logged.** They append to `.hallmark/log.json`. The same gate waived on three consecutive builds is not an exception any more, it is an unstated house style, and it belongs in a `design.md`.
- **One per gate.** A second waiver on the same gate is rejected.
- **No hard cap.** A genuinely expressive page can overrule four or five reflexes and still be right: a colour-field poster on a pure-black stage, achromatic by intent, long measure, one display face doing everything. Blocking it at an arbitrary count would be exactly the restraint this tier exists to remove. Past three, the build gets a `NOTE` saying what it now is: **a house style, not a set of exceptions**, which belongs in a `design.md` so later pages inherit it instead of re-arguing it.
- **The guard is what keeps this honest**, not a count. Guards are mechanical preconditions the linter re-derives; on the gates most tempting to waive (23, 37, 42, 43, 47, 7) the guard is exactly the thing a lazy build will not have done.
- **The reason has to be about this page.** Under 24 characters, or boilerplate once filler and stock phrases are stripped, or shared word-for-word with another waiver, and it is rejected. A waiver that does not parse is reported as malformed rather than silently ignored.
- **Waivers are logged.** They append to `.hallmark/log.json`. The same gate waived on three consecutive builds is not an exception any more.
An honoured waiver reports at grade `WAIVED` and is visible in the output. Silent waiving would be free waiving.
**Answering a Finish finding.** Finish findings are read and answered, and the answer has a record form too, so the next run can parse it instead of finding freeform prose in a comment:
```css
/* Hallmark · answered F3 · the display head is one word and cannot wrap, so balance has nothing to balance */
```
Two fields: the check, and why it does not apply here. Same reason-quality rules. Only Finish findings are answered; a Reflex finding is waived and a Floor finding is fixed.
---
## Pre-emit self-critique (six axes)
@@ -264,7 +304,7 @@ Universal. Every emitted page must render flawlessly at 320 px, 375 px, 414 px,
**The fix is never just deletion.** [`section-entry.md`](section-entry.md) carries twelve other ways to open a section, ten of which are already built elsewhere in the skill. When the sequence is genuine, reach for a real `<ol>` with `counter()` or set the numeral at graphic scale. One honest edge case: when a label is a section's *only* heading (`aria-labelledby` pointing at it, no `<h1>-<h6>` anywhere), the gate WARNs instead, and the fix is promoting it to `<h2>`.
**This one is a house rule, and it is stricter than the field.** Plenty of admired pages ship eyebrows; Hallmark does not, because it is the tell the skill is named for. It is the one Floor gate that is not defended by "no good designer would do this", and it is never waivable.
**This is a house rule** and it enters the Floor through door 2 (§ Tiers, The second door into the Floor), the only gate that does. Plenty of admired pages ship eyebrows; a language model ships one on every section, unprompted, which is what makes removing the option worth more than the pattern. Never waivable.
55. **[Floor]** **[M]** **55a. Cap collision on wrap.** Does a display-size element (`.hero__display`, `.section__title`, `h1`, `h2`, anything `≥ --text-2xl`) declare both `text-transform: uppercase` AND a `line-height` below `1.0` **while being able to wrap** (it renders on more than one line at 375 px and carries no `white-space: nowrap`)? Uppercase glyphs have no descenders and their cap-tops sit at the very top of the line box, so at `0.94` the cap-tops of line N+1 collide with line N's baseline and a trailing comma fuses into the cap beside it. Condensed faces (Anton, Inter Tight 900, Bebas Neue) make it worse. A rendering defect, not a preference. Bump `--lh-tight` to ≥ 1.0, or drop the uppercase.
**[Reflex]** **[M]** **55b. Tight all-caps lockups.** Does a single-line-guaranteed all-caps element (a one-word wordmark, a masthead, a stat) run below the recommended `1.02-1.08`? *Waivable:* a nowrap wordmark at 0.85 is standard lockup practice and cannot collide with a line that does not exist. *Guard:* the element cannot wrap at 320 px.
@@ -289,4 +329,4 @@ Rules live in [`finish.md`](finish.md) (F1-F6), [`layout-and-space.md`](layout-a
---
**Nothing ships with an open Floor finding.** Reflex findings are either fixed or waived on the record. Finish findings are read and answered, never ignored.
**Nothing ships with an open `FAIL` or an open `REFLEX`.** A Floor finding is fixed; a Reflex finding is fixed or waived on the record; a Finish finding is read and answered. The difference between the first two is not whether they block, it is whether you are allowed to disagree.
+2
View File
@@ -44,6 +44,8 @@ If the user insists on one, do it. Otherwise pick from the allowlist below.
## The font catalog
**Skip this section when a catalog theme is in play.** The theme file has already named the display, body and label faces, and picking a different one breaks the theme. Read it when the route is custom or studied-DNA, when the pre-flight found no font stack, or when a face here needs its source and licence checked.
Three sources, in priority order:
- **Google Fonts** — free, served via CDN, works everywhere. The default source.
@@ -1,22 +1,25 @@
<!doctype html>
<!--
Waiver fixture. Five waivers are claimed; three are honoured and two are refused,
and one of the refusals leaves its gate failing. That last part is the mechanism:
an over-cap waiver does not fall back to silence, it falls back to the gate.
Waiver fixture. Five waivers are claimed on a deliberately expressive page:
four are honoured, one is refused because it names a Floor gate, and the build
ships. There is no cap, so the page is not blocked for being expressive; what
it gets instead is a NOTE saying four waivers is a house style, not exceptions.
Assertion (run from this directory):
node ../../sloplint.mjs . --json | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const j=JSON.parse(s);console.log(JSON.stringify(j.summary))})"
# expected: fails 2, waived 3, rejected 2
# (counts are findings, not gates: gate 1 fires on both the token and the h1
# rule, and gate 7 on both the token and the body background)
# expected: fails 0, reflex 0, waived 5, rejected 2, answered 1, notes 1
# (waived counts findings, not gates: gate 1 fires on both the token and the
# h1 rule, and gate 7 on both the token and the body background)
Case by case:
waive 40 refused, Floor is never waivable
waive 7 honoured; the guard (surface: absolute in the stamp) holds
waive 22 honoured
waive 25 honoured (no finding to downgrade here); fills the three-gate cap
waive 1 → refused on the cap, and gate 1 then fails on its own merits
waive 40 -> refused, Floor is never waivable, and gate 40 still binds
waive 7 -> honoured; the guard (surface: absolute in the stamp) holds
waive 22 -> honoured
waive 25 -> honoured (no finding to downgrade on this page)
waive 1 -> honoured, and trips the >3 house-style NOTE
answered F3 -> honoured; the Finish finding reports ANSWERED instead of WARN
answered 40 -> refused; 40 is Floor, and Floor findings are fixed, not answered
-->
<html lang="en">
<head>
@@ -31,6 +34,8 @@
/* Hallmark · waive 22 · neutrals achromatic throughout · the manifesto rules are pure grey by intent */
/* Hallmark · waive 25 · measure runs to 96ch · the manifesto sets long declarative lines */
/* Hallmark · waive 1 · two families declared · the manifesto pairs a display serif with a grotesque */
/* Hallmark · answered F3 · the manifesto head is one word and cannot wrap, so balance has nothing to balance */
/* Hallmark · answered 40 · contrast is fine here honestly */
:root {
--color-paper: #000;
--color-ink: oklch(96% 0 250);
+57 -11
View File
@@ -590,22 +590,30 @@ function reasonIsBoilerplate(reason) {
return r.length < 12;
}
/* Honoured waivers for the group currently being checked. */
/* Honoured waivers and answered Finish checks for the group being checked. */
let WAIVED = new Set();
let ANSWERED = new Set();
const findings = [];
function report(gate, grade, file, line, evidence, fix) {
const g = String(gate);
if (opts.scope === 'component' && PAGE_ONLY.has(g)) return;
const tier = tierOf(g);
if (tier === 'ledger' && grade === 'FAIL') grade = 'NOTE';
if (grade === 'FAIL' && tier === 'reflex' && WAIVED.has(baseGate(g))) grade = 'WAIVED';
const b = baseGate(g);
if (grade === 'FAIL') {
/* Bookkeeping never blocks a ship, and a Reflex finding is not a Floor
finding: it gets its own blocking grade so "Floor clean" stays a true
statement even while the build still owes an answer somewhere. */
if (tier === 'ledger') grade = 'NOTE';
else if (tier === 'reflex') grade = WAIVED.has(b) ? 'WAIVED' : 'REFLEX';
}
if (tier === 'finish' && ANSWERED.has(b)) grade = 'ANSWERED';
findings.push({ gate: g, grade, tier, file: rel(file), line: line || 1, evidence: trunc(String(evidence)), fix });
}
/** Parse, judge, and apply the `waive` lines in a group's stamp. */
function applyWaivers(ctx, dir) {
WAIVED = new Set();
WAIVED = new Set(); ANSWERED = new Set();
const re = /\/\*\s*Hallmark\s*·\s*waive\s+(\d{1,2}[a-z]?(?:-[a-z]+)?|F\d+)\s*·\s*([^·*]+?)\s*·\s*([^*]+?)\s*\*\//gi;
const claims = [...ctx.stampText.matchAll(re)].map((m) => ({
id: m[1], evidence: m[2].trim(), reason: m[3].trim(), raw: m[0],
@@ -647,7 +655,6 @@ function applyWaivers(ctx, dir) {
const tier = tierOf(c.id);
if (tier !== 'reflex') { rejectAt(c, tier === 'ledger' ? 'bookkeeping gate' : 'Floor is never waivable'); continue; }
if (WAIVED.has(b)) { rejectAt(c, 'one waiver per gate'); continue; }
if (WAIVED.size >= WAIVER_MAX) { rejectAt(c, `cap: ${WAIVER_MAX} per artifact`); continue; }
if (c.reason.length < 24) { rejectAt(c, 'reason under 24 characters'); continue; }
if (reasonIsBoilerplate(c.reason)) { rejectAt(c, 'boilerplate reason'); continue; }
if (reasons.get(c.reason.toLowerCase()) > 1) { rejectAt(c, 'reason reused on another waiver'); continue; }
@@ -658,9 +665,40 @@ function applyWaivers(ctx, dir) {
if (verdict !== true) { rejectAt(c, `guard: ${verdict}`); continue; }
WAIVED.add(b);
}
/* Past the soft cap the waivers still stand: a genuinely expressive page can
legitimately overrule four or five reflexes, and blocking it there would be
the restraint this tier exists to remove. What it earns instead is a name.
Guards, reason quality and the log are what keep this honest, not a count. */
if (WAIVED.size > WAIVER_MAX) {
findings.push({
gate: 'waive', grade: 'NOTE', tier: 'ledger', file: rel(file), line: 1,
evidence: trunc(`${WAIVED.size} reflex gates waived (${[...WAIVED].join(', ')}): that is a house style, not a set of exceptions`),
fix: 'Lock it into a design.md so later pages inherit it instead of re-arguing it',
});
}
/* Finish findings are "read and answered". This is the record form, so an
answer is parseable by the next run instead of freeform prose in a comment. */
const ansRe = /\/\*\s*Hallmark\s*·\s*answered\s+(\d{1,2}[a-z]?(?:-[a-z]+)?|F\d+)\s*·\s*([^*]+?)\s*\*\//gi;
for (const m of ctx.stampText.matchAll(ansRe)) {
const id = baseGate(m[1]);
const reason = m[2].trim();
const bad = tierOf(id) !== 'finish' ? 'only Finish findings are answered; Reflex is waived and Floor is fixed'
: reason.length < 24 ? 'reason under 24 characters'
: reasonIsBoilerplate(reason) ? 'boilerplate reason' : null;
if (bad) {
findings.push({
gate: id, grade: 'WAIVER', tier: tierOf(id), file: rel(file), line: 1,
evidence: trunc(`answer on ${m[1]} REJECTED (${bad})`),
fix: 'Two · -separated fields: /* Hallmark · answered <check> · <reason> */',
});
continue;
}
ANSWERED.add(id);
}
}
const GRADE_RANK = { FAIL: 0, WAIVER: 1, WARN: 2, WAIVED: 3, NOTE: 4 };
const GRADE_RANK = { FAIL: 0, REFLEX: 1, WAIVER: 2, WARN: 3, WAIVED: 4, ANSWERED: 5, NOTE: 6 };
function gateSortKey(g) {
if (g === '38a') return 38.5;
@@ -2274,24 +2312,32 @@ unique.sort((a, b) => {
const count = (g) => unique.filter((f) => f.grade === g).length;
const fails = count('FAIL');
const reflex = count('REFLEX');
const warns = count('WARN');
const waived = count('WAIVED');
const rejected = count('WAIVER');
const answered = count('ANSWERED');
const notes = count('NOTE');
const tally = [`${fails} FAIL`, `${warns} WARN`];
/* Floor and Reflex both block, and both are reported, but they are not the same
claim: "0 FAIL" means nothing got past the floor, which stays true even while
the build still owes an argument on a reflex. */
const tally = [`${fails} FAIL`];
if (reflex) tally.push(`${reflex} REFLEX`);
tally.push(`${warns} WARN`);
if (waived) tally.push(`${waived} WAIVED`);
if (rejected) tally.push(`${rejected} REJECTED`);
if (answered) tally.push(`${answered} ANSWERED`);
if (notes) tally.push(`${notes} NOTE`);
if (opts.json) {
console.log(JSON.stringify({
summary: { fails, warns, waived, rejected, notes, files: files.length },
summary: { fails, reflex, warns, waived, rejected, answered, notes, files: files.length },
findings: unique,
}, null, 2));
} else {
if (!unique.length) {
console.log(`sloplint: clean. 0 FAIL, 0 WARN across ${files.length} file(s).`);
console.log(`sloplint: clean. 0 FAIL, 0 REFLEX, 0 WARN across ${files.length} file(s).`);
} else {
const wGate = Math.max(4, ...unique.map((f) => String(f.gate).length));
const wGrade = Math.max(5, ...unique.map((f) => f.grade.length));
@@ -2299,11 +2345,11 @@ if (opts.json) {
console.log(`${'GATE'.padEnd(wGate)} ${'GRADE'.padEnd(wGrade)} ${'WHERE'.padEnd(wWhere)} EVIDENCE`);
for (const f of unique) {
console.log(`${String(f.gate).padEnd(wGate)} ${f.grade.padEnd(wGrade)} ${`${f.file}:${f.line}`.padEnd(wWhere)} ${f.evidence}`);
if (f.grade !== 'WAIVED') {
if (f.grade !== 'WAIVED' && f.grade !== 'ANSWERED') {
console.log(`${''.padEnd(wGate)} ${''.padEnd(wGrade)} ${''.padEnd(wWhere)} fix: ${f.fix}`);
}
}
console.log(`\n${tally.join(', ')} across ${files.length} file(s).`);
}
}
process.exit(fails > 0 ? 1 : 0);
process.exit(fails > 0 || reflex > 0 ? 1 : 0);