* feat(design-lint): copy tier reports each tell at its line; empty-mount-node; content-realism ids at ask
The copy tier holds its own patterns (from lib/design-craft.ts) and reads
visible text, script bodies, inline handlers and parsed tags, so payloads,
commented markup and script text no longer count. A text-less body that
loads a script reports empty-mount-node. placeholder-name, placeholder-brand,
lorem, round-number and needs-proof move to ask so a page-specific waiver
answers them (Garry 2026-10-26, option a).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(design-html,design-review): run gstack-design-lint; waivers replace craft_exceptions; remove gstack-design-craft
/design-html Step 4 and /design-review's craft audit call the lint and read
its exit code. A waiver comment in the page replaces --accepted and
finalized.json craft_exceptions (supersedes #3422's persisted exceptions).
bin/gstack-design-craft, lib/design-craft.ts and its test are removed; the
handoff tests move to test/design-craft-resolvers.test.ts. The slop-gate paid
case and its touchfiles follow the lint.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(design-lint): the content-realism tells keep one checker, credited in NOTICE.md
Names the removed lib/design-craft.ts and test/design-craft.test.ts so pregate
selects a lane for the deletions (PREGATE_NO_LANE at 17635b6a7).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix: pre-landing review fixes
- /design-review files only craft-gate lint lines on a saved page, whose
linked CSS the lint cannot load (false missing-focus-visible blocks)
- missing-aria-current counts route links in one pass (unclosed navs were
superlinear); inline-handler tells report at the value's line; a shell
with no <body> tag is an empty mount node
- /design-html says the copy tier reads HTML only, and that a supplied
figure may also need an invented-proof waiver
- tests pin the five content-realism ids at ask and a waived copy tell
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(changelog): the saved-page craft audit files copy-tier lines only
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(design-lint): forward references say a later phase, not Phase 2
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(carve-guards): re-pin design-review and design-html parity to the merged tree's measured sizes
design-review skeleton 137735, union 144802, ratio 1.3681 (all lower);
design-html skeleton 55834, union 78626 (caps unchanged, comments updated).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
9.3 KiB
Craft: Persuade pages and the craft floor
Persuade pages applies when the page exists to get one action from a visitor: a landing, marketing, pricing, waitlist, campaign or comparison page. Write Surface: Persuade or Surface: other as the spec's first line; on other, skip to Craft floor, which applies to every page. An approved mockup wins on structure and visuals; this section fills only what it leaves open.
Persuade pages
One offer → one audience → one primary action.
Intake. When the context does not settle them, ask one AskUserQuestion with at most three multiple-choice questions: the primary action, the traffic source, and published or design reference. Assume the rest in one Assumed: line the user corrects in the refinement loop:
- the conversion and the offer: exactly what they get
- who it is for, the problem they came with, their top three objections, what they already know
- the proof the user can supply (named customers, attributed quotes, real numbers) and the risk terms the business offers
Archetype. When approved.json carries an archetype, the user already chose it on the /design-shotgun board: take it and its sections outline as given, name both in the spec's layout type, and do not re-pick or re-ask. Otherwise pick one and say why there: classic hero + sections (classic-hero, default; the product reads from one screenshot), long-form story (long-form-story, the visitor needs educating or is skeptical), minimal conversion (minimal-conversion, high-intent traffic or a small offer), comparison (comparison, search intent is "X vs Y").
Structure. Above the fold at 375×667 and on desktop, inside the hero budget: a headline naming outcome and audience, one sentence on how, one primary CTA, one proof signal only when real proof exists (named logos or one attributed quote; a metric row stays banned), one product visual. Below it, choose modules and order from the archetype and the objections. The long-form menu: problem → solution, three to five benefits, how it works in three steps, proof, an FAQ of six to twelve real objections, the risk reversal, a final CTA identical to the top. A minimal page stays minimal; a comparison page leads with the comparison.
Conversion rules.
- A page behind an ad repeats the ad's promise and tone in the hero.
- One primary CTA; nothing competes with it above the fold.
- Benefit first and specific: "Cut weekly reporting from 4 hours to 15 minutes", never "save time".
- Show the risk reversal the business offers (free plan, trial, no card, cancel anytime). Never invent one.
- Proof sits beside the claim it supports. A high-friction offer moves the FAQ up.
The primary action works. A real destination (supplied URL or form endpoint) and every state: invalid (inline error, input kept), pending (control disabled so it cannot submit twice, announced through aria-live), failure (what happened, input kept, retry) and success (what happens next). With no backend, simulate the states and say so in a code comment and your summary.
Copy. A CTA is a verb plus what they get ("Start free trial"). Draft two or three headlines in the product's voice and keep the most specific one that names outcome and audience; "{outcome} without {pain}" is a test, not a template.
Indexing. Record the publication decision; keep the site's policy when known. A page the user keeps out of search gets <meta name="robots" content="noindex">; an indexed page gets a real title and description, and its FAQ ships as FAQPage JSON-LD.
Build order. One section at a time, top down; a refinement edits one section.
Craft floor (every page)
Claims come from the user, never from you. Testimonials, customer names and logos, results, uptime, prices and guarantees appear only when supplied. Missing proof: leave the slot out, or mark it [Needs proof: …] in the warning color so it cannot ship by accident. A believable invented claim is worse than an obvious placeholder.
Illustrative content looks real. Sample data in product shots and UI (table rows, chart values, people in a demo inbox):
- No placeholder people or brands (John Doe, Jane Doe, Acme, Nexus, SmartFlow); distinct avatars; varied dates.
- No round sample numbers (99.99%, 50%, 1,000+ customers, 10x). In a claim, a round number signals it was made up: use the supplied figure or drop it.
- No buzzwords: elevate, seamless, effortless, supercharge, streamline, revolutionize, unlock, empower, unleash, next-gen, game-changer, delve, tapestry, "in the world of".
- Sentence-case headings. Errors say what happened and what to do, never "Oops!". No exclamation marks in success messages.
Ship floor. Head tags apply to a full HTML document, not a component or fragment. Every full document has <title>, a meta description, a favicon (an inline SVG data URI from the brand mark when no file is supplied), and on a page meant for sharing og:title and og:description; og:image only from a supplied URL. A "Skip to content" link is the first focusable element, hidden until focused, targeting the id of <main>. Landmarks (<header>, <nav>, <main>, <footer>); alt text on meaningful images, alt="" on decorative ones. A marketing footer links privacy and terms at supplied URLs. Required fields and email format validate inline, wired with aria-invalid and aria-describedby. Every link goes somewhere real: no href="#", no made-up path; a control with no target renders disabled. Navigation to other pages marks the current one aria-current="page"; in-page section links do not.
Values come from the scale. Every font size, space and radius is a step of the system's scale (DESIGN.md, else the spec), chosen by role. An off-scale value snaps to the nearest step that keeps its role, usually the one below, with that step's line height; never below 16px body text or a 44px touch target, and an approved mockup's measured values stand. A shape nested in another with a gap under 32px gets inner radius = outer − gap when that exceeds 2px.
Line breaks. Browser-wrapped headings get text-wrap: balance, body text-wrap: pretty. Break display lines where the thought breaks; never one word alone on the last line.
Motion. These govern the one authored motion moment and state transitions; they license no more motion.
- Content is visible by default; script adds the hidden start state only after the observer is set up (
.motion-readyon<html>), so a script failure or a crawler sees everything and the CTA never waits on an animation. - Reveal with
IntersectionObserver(threshold: 0, bottomrootMarginabout −20%, once). Never an unthrottledwindow.addEventListener('scroll'); scroll-linked effects read scroll insiderequestAnimationFrame. - Animate
transform,opacity,filter,clip-pathonly; blur 14px or less. - No object grows from
scale(0)(start at 0.85 or more); only lines and bars grow from zero. A data bar fills as a left-to-rightclip-pathwipe, 0.7–1.1s. - Stagger in reading order inside one section (30–80ms), never across the page.
- Curves are named tokens: DESIGN.md's, else
cubic-bezier(0.16, 1, 0.3, 1)for entrances andcubic-bezier(0.65, 0, 0.35, 1)for wipes;linearonly for constant motion. - Loops pause off-screen.
prefers-reduced-motion: reduceshows the final state. Full-height sections usemin-height: 100dvh, not100vh.
Design lint
At Step 4, after the Slop Gate and before the screenshots, run the free lint on the finalized page (--scope component for a component file alone; its copy tier reads HTML only, so judge those tells by eye there):
~/.claude/skills/gstack/bin/gstack-design-lint <path-to-finalized.html> --render
It prints one LINT: <grade> <rule-id> <file>:<line> <evidence> | fix: <fix> line per tell (the realism rules above among them), then LINT_SUMMARY: blocks=N ask=N waived=N polish=N render=…. Read the exit code: 2, fix every blocks line (never waivable); 1, fix each ask line or waive it; 0, list the polish lines as notes. No LINT_SUMMARY line: say the lint was unavailable and continue. A render line or render=skipped: say the page was not measured; the static lines stand. Same contract as the Slop Gate: one surgical fix pass, one rerun. Rerun once when the refinement loop exits; report only new lines.
Waivers live in the page. A tell that is right for this page (a real customer named Nexus, a supplied 50% figure, which invented-proof may also flag) gets a comment per rule, <!-- gstack-lint-disable <rule-id>: <reason about this page, 24+ chars> -->, and prints as waived with its reason on every later run, an evolve included. Waive only what the user supplied or confirmed, never a line you could fix.
Before this can ship. Open the Step 4 hand-off, and repeat before Step 5's "Copy to project": every [Needs proof: …] marker, the dependencies the page cannot own (legal URLs, form endpoint, og:image, indexing, analytics and cookie consent where required), every waiver, and every claim the lint made you change.
Adapted from Elaya's ai-design-skills and redesign-skill (MIT, © 2026 Elaya), rewritten for gstack. See NOTICE.md.