
Web Prototype
- 303 installs
- 83.7k repo stars
- Updated August 5, 2026
- nexu-io/open-design
web-prototype is an Open Design agent skill that generates single-file HTML landing and marketing prototypes by composing bundled layout skeletons and DESIGN.md tokens for developers who need fast clickable mocks.
About
web-prototype is a prototype-mode skill in nexu-io/open-design that produces general-purpose desktop web prototypes as a single self-contained HTML file. Agents copy the bundled assets/template.html seed—which encodes six :root design tokens and a class system—then paste section layouts from references/layouts.md containing 8 paste-ready skeletons for hero, features, stats, pricing, and CTA rhythms. The skill reads the active DESIGN.md for color, typography, layout, and components before writing index.html for sandboxed preview in Open Design. Developers reach for web-prototype when they need a landing, marketing, docs, or SaaS page mockup faster than scaffolding React or Next.js. A P0/P1/P2 checklist in references/checklist.md gates output quality, and data-od-id attributes enable comment-mode targeting. Default page rhythms cover landing, editorial, pricing, and docs index flows without writing CSS from scratch.
- General web mocks
- Multi-page exploration
- Fast iteration
- Stakeholder demos
- Pre-build alignment
Web Prototype by the numbers
- 303 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #828 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/nexu-io/open-design --skill web-prototypeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 303 |
|---|---|
| repo stars | ★ 83.7k |
| Last updated | August 5, 2026 |
| Repository | nexu-io/open-design ↗ |
How do you prototype a landing page without a frontend stack?
Create general-purpose web prototypes for new features, dashboards, or marketing pages when teams need fast clickable mocks without bootstrapping a full frontend stack.
Who is it for?
Developers and designers who need fast single-file HTML landing or SaaS mocks inside Open Design before production frontend work.
Skip if: Developers who already need a production React or Next.js component library rather than a disposable HTML composition prototype.
When should I use this skill?
A developer asks for a landing page prototype, marketing mockup, or single-page homepage without bootstrapping a full frontend framework.
What you get
Self-contained index.html prototype with DESIGN.md tokens, section layouts, and sandboxed HTML preview
- index.html prototype
- section layout plan
- checklist-passed HTML mock
By the numbers
- Bundles 8 paste-ready section skeletons in references/layouts.md
- Seed template.html defines 6 :root CSS design-token variables
- Includes P0, P1, and P2 checklist tiers in references/checklist.md
Files
Web Prototype Skill
Produce a single, self-contained HTML prototype using the bundled seed and layout library — not by writing CSS from scratch. The seed already encodes good defaults (typography, spacing, accent budget). Your job is to compose it.
Resource map
web-prototype/
├── SKILL.md ← you're reading this
├── assets/
│ └── template.html ← seed: tokens + class system + chrome (READ FIRST)
└── references/
├── layouts.md ← 8 paste-ready section skeletons
└── checklist.md ← P0/P1/P2 self-reviewWorkflow
Step 0 — Pre-flight (do this once before writing anything)
1. Read `assets/template.html` end-to-end — at minimum through the <style> block. The class inventory at the top of references/layouts.md lists every class that must be defined there; if one is missing, add it to <style> rather than re-defining it inline on every section. 2. Read `references/layouts.md` so you know which section skeletons exist. Don't write a section type that isn't covered — pick the closest layout and adapt. 3. Read the active DESIGN.md (already injected into your system prompt). Map its colors to the six :root variables in the seed; don't introduce new tokens.
Step 1 — Prepare the artifact from the seed
Choose one kebab-case artifact slug before composing the page. Use assets/template.html as the seed for the final artifact HTML.
Do not write a project-root HTML draft with file-write before emitting the final <artifact>. The live-artifact output is the canonical HTML file for this generation turn; an extra index.html, cast.html, or brief-derived draft can be stranded beside it as an orphan.
Replace the six :root variables with the active design system's tokens. Replace the page <title> and the topnav brand.
Step 2 — Plan the section list
Pick layouts before writing copy. Default rhythms (from layouts.md):
| Page kind | Default rhythm |
|---|---|
| Landing | 1 hero → 3 features → 4 stats or 5 quote → custom split → 6 cta |
| Marketing / editorial | 1 hero-center → 7 log list → 6 cta |
| Pricing | 1 hero-center → 8 comparison table → 6 cta |
| Docs index | 1 hero-center → 7 log list (sections of docs) → 6 cta |
State the chosen list in one sentence to the user before writing — they can redirect cheaply now and not after 200 lines of HTML.
Step 3 — Paste and fill
For each chosen layout, copy the <section> block from layouts.md into <main id="content"> of the artifact HTML. Replace bracketed [REPLACE] strings with real, specific copy from the user's brief. No filler — if a slot is empty, the section is the wrong choice; pick a different layout.
Step 4 — Self-check
Run through references/checklist.md top to bottom. Every P0 item must pass before you move on. P1 items should pass; P2 are bonus.
Step 5 — Emit the artifact
Wrap the completed HTML in <artifact> tags using the artifact slug as the identifier. One sentence before describing what's there. Stop after </artifact>.
Hard rules (the seed protects most of these — don't fight it)
- Single accent, used at most twice per screen. Eyebrow + primary CTA is the default budget.
- Display font is serif (Iowan Old Style / Charter / Georgia in the seed). Sans for body. Mono for numerics, captions, eyebrows.
- Image placeholders, not external URLs. Use the
.ph-imgclass — never link to a stock photo CDN. - Mobile reflow already works via the seed's media query at 920px. Don't break it by adding fixed widths.
- `data-od-id` on every `<section>` so comment mode can target it.
Output contract
<artifact identifier="kebab-case-slug" type="text/html" title="Human Title">
<!doctype html>
<html>...</html>
</artifact>Open Design derives the canonical HTML artifact from this identifier. Do not also write another root HTML file for the same generation turn.
One sentence before the artifact. Nothing after.
<!doctype html>
<!--
OD web-prototype seed.
Copy this file to <project>/index.html, then fill `<main id="content">` by
pasting blocks from `references/layouts.md`. Every class referenced in
layouts.md is defined here — DO NOT remove unused ones unless you also
remove their callers, and DO NOT invent new global classes (use inline
style="…" for one-off tweaks).
Theme tokens at the top of `<style>` are the *only* place to set palette
and type. They map cleanly onto a DESIGN.md — when an active design system
is injected, swap these six variables and everything reflows.
-->
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>[REPLACE] Page title · brand</title>
<style>
/* ─── tokens ─────────────────────────────────────────────────────────
Six variables. Bind them to the active DESIGN.md and stop.
Do not introduce raw hex anywhere else in this file. */
:root {
--bg: #fafaf7; /* page background — never #fff */
--surface: #ffffff; /* cards, modals, raised areas */
--fg: #1a1916; /* primary text — never #000 */
--muted: #6b6964; /* secondary text, captions */
--border: #e8e5df; /* hairlines, dividers */
--accent: #c96442; /* one accent — used at most 2× per screen */
/* derived — do not change */
--accent-soft: color-mix(in oklch, var(--accent) 14%, transparent);
--fg-soft: color-mix(in oklch, var(--fg) 6%, transparent);
/* type — display = serif (default), body = sans, mono for numerics */
--font-display: 'Iowan Old Style', 'Charter', Georgia, 'Times New Roman', serif;
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
--font-mono: ui-monospace, 'JetBrains Mono', 'SF Mono', Menlo, monospace;
/* scale — clamp() so it works at 1280, 1440, 1920 without media queries */
--fs-h1: clamp(44px, 6vw, 76px);
--fs-h2: clamp(32px, 4vw, 48px);
--fs-h3: 22px;
--fs-lead: 19px;
--fs-body: 16px;
--fs-meta: 13px;
/* spacing — 8-point grid */
--gap-xs: 8px;
--gap-sm: 12px;
--gap-md: 20px;
--gap-lg: 32px;
--gap-xl: 56px;
--gap-2xl: 96px;
--container: 1120px;
--gutter: 32px;
--radius: 10px;
--radius-lg: 16px;
}
/* ─── reset & base ──────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
background: var(--bg);
color: var(--fg);
font-family: var(--font-body);
font-size: var(--fs-body);
line-height: 1.55;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
}
img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; cursor: pointer; }
p { text-wrap: pretty; }
h1, h2, h3, h4 { text-wrap: balance; }
/* ─── layout primitives ─────────────────────────────────────────── */
.container {
max-width: var(--container);
margin-inline: auto;
padding-inline: var(--gutter);
}
.section {
padding-block: clamp(48px, 8vw, var(--gap-2xl));
}
.section + .section { border-top: 1px solid var(--border); }
.stack { display: flex; flex-direction: column; }
.stack > * + * { margin-top: var(--gap-md); }
.row { display: flex; align-items: center; gap: var(--gap-md); }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: var(--gap-md); }
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gap-lg); }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gap-lg); }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--gap-md); }
.grid-2-1 { display: grid; grid-template-columns: 2fr 1fr; gap: var(--gap-xl); align-items: start; }
.grid-1-2 { display: grid; grid-template-columns: 1fr 2fr; gap: var(--gap-xl); align-items: start; }
@media (max-width: 920px) {
.grid-2, .grid-3, .grid-4, .grid-2-1, .grid-1-2 { grid-template-columns: 1fr; }
}
/* ─── type ──────────────────────────────────────────────────────── */
.h1, h1 { font-family: var(--font-display); font-size: var(--fs-h1); line-height: 1.04; letter-spacing: -0.02em; margin: 0; }
.h2, h2 { font-family: var(--font-display); font-size: var(--fs-h2); line-height: 1.1; letter-spacing: -0.015em; margin: 0; }
.h3, h3 { font-size: var(--fs-h3); font-weight: 600; line-height: 1.3; letter-spacing: -0.005em; margin: 0; }
.lead { font-size: var(--fs-lead); line-height: 1.55; color: var(--muted); max-width: 60ch; margin: 0; }
.eyebrow {
font-family: var(--font-mono);
font-size: 12px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--accent);
margin: 0 0 var(--gap-md);
}
.meta { font-family: var(--font-mono); font-size: var(--fs-meta); color: var(--muted); }
.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
/* ─── chrome: nav + footer ──────────────────────────────────────── */
.topnav {
position: sticky; top: 0; z-index: 10;
background: color-mix(in oklch, var(--bg) 92%, transparent);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border);
}
.topnav-inner { display: flex; align-items: center; justify-content: space-between; padding-block: 14px; }
.topnav .logo { font-family: var(--font-display); font-size: 19px; font-weight: 600; letter-spacing: -0.01em; }
.topnav nav { display: flex; gap: var(--gap-lg); }
.topnav nav a { font-size: 14px; color: var(--muted); }
.topnav nav a:hover { color: var(--fg); }
.pagefoot { padding-block: var(--gap-xl); color: var(--muted); font-size: 13px; border-top: 1px solid var(--border); }
.pagefoot .row-between { flex-wrap: wrap; gap: var(--gap-md); }
/* ─── buttons ───────────────────────────────────────────────────── */
.btn {
display: inline-flex; align-items: center; gap: 8px;
padding: 11px 20px;
border-radius: var(--radius);
border: 1px solid transparent;
font-size: 15px;
font-weight: 500;
letter-spacing: -0.005em;
transition: transform 0.05s ease, background 0.15s ease, border-color 0.15s ease;
}
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--accent); color: var(--surface); border-color: var(--accent); }
.btn-primary:hover { background: color-mix(in oklch, var(--accent) 88%, black); }
.btn-secondary { background: transparent; color: var(--fg); border-color: var(--border); }
.btn-secondary:hover { border-color: var(--fg); }
.btn-ghost { background: transparent; color: var(--fg); border-color: transparent; padding-inline: 8px; }
.btn-ghost:hover { color: var(--accent); }
.btn-arrow::after { content: '→'; transition: transform 0.15s ease; }
.btn-arrow:hover::after { transform: translateX(2px); }
/* ─── card / surface ────────────────────────────────────────────── */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 28px;
}
.card-flat { background: transparent; border: 0; padding: 0; }
.card-rule { background: transparent; border: 0; border-top: 1px solid var(--fg); padding: 24px 0 0; border-radius: 0; }
/* ─── feature cell (icon + h3 + p) ──────────────────────────────── */
.feature .feature-mark {
width: 36px; height: 36px;
display: grid; place-items: center;
border: 1px solid var(--border);
border-radius: 10px;
color: var(--accent);
margin-bottom: var(--gap-md);
}
.feature .feature-mark svg { width: 18px; height: 18px; }
.feature h3 { margin-bottom: 6px; }
.feature p { margin: 0; color: var(--muted); font-size: 15px; }
/* ─── stat (big number + label) ─────────────────────────────────── */
.stat .stat-num {
font-family: var(--font-display);
font-size: clamp(56px, 8vw, 96px);
line-height: 0.95;
letter-spacing: -0.04em;
color: var(--accent);
font-weight: 600;
}
.stat .stat-label { color: var(--muted); font-size: 14px; margin-top: 8px; max-width: 24ch; }
.stat .stat-unit { font-size: 0.5em; opacity: 0.7; margin-left: 2px; }
/* ─── quote / testimonial ───────────────────────────────────────── */
.quote {
font-family: var(--font-display);
font-size: clamp(24px, 2.6vw, 32px);
line-height: 1.32;
letter-spacing: -0.01em;
max-width: 28ch;
}
.quote-author { color: var(--muted); font-size: 14px; margin-top: var(--gap-md); }
.quote-mark {
font-family: var(--font-display);
font-size: 140px; line-height: 0.7;
color: var(--accent); opacity: 0.18;
margin-bottom: -28px;
}
/* ─── pill / badge / tag ────────────────────────────────────────── */
.pill {
display: inline-flex; align-items: center; gap: 6px;
padding: 4px 10px;
background: var(--accent-soft);
color: var(--accent);
border-radius: 999px;
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.tag {
display: inline-flex; align-items: center;
padding: 4px 10px;
background: transparent;
color: var(--muted);
border: 1px solid var(--border);
border-radius: 999px;
font-size: 12px;
}
/* ─── form field ────────────────────────────────────────────────── */
.field { display: flex; flex-direction: column; gap: 6px; }
.field label { font-size: 13px; color: var(--muted); }
.input, .textarea {
width: 100%;
padding: 11px 14px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
color: var(--fg);
font: inherit;
font-size: 15px;
}
.input:focus, .textarea:focus {
outline: 2px solid var(--accent-soft);
border-color: var(--accent);
}
.textarea { min-height: 96px; resize: vertical; line-height: 1.55; }
/* ─── table (data-style, no chrome) ─────────────────────────────── */
.ds-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.ds-table th, .ds-table td { padding: 12px 14px; text-align: left; border-bottom: 1px solid var(--border); }
.ds-table th { color: var(--muted); font-weight: 500; font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.04em; text-transform: uppercase; }
.ds-table tbody tr:hover { background: var(--fg-soft); }
.ds-table .num-col { font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; }
/* ─── image placeholder (DS-tinted block, never broken <img>) ──── */
.ph-img {
background:
linear-gradient(135deg, var(--accent-soft), var(--fg-soft)),
var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
aspect-ratio: 16 / 10;
display: grid; place-items: center;
color: var(--muted);
font-family: var(--font-mono);
font-size: 12px;
letter-spacing: 0.04em;
}
.ph-img.square { aspect-ratio: 1 / 1; }
.ph-img.portrait { aspect-ratio: 3 / 4; }
.ph-img.wide { aspect-ratio: 16 / 9; }
/* ─── divider ───────────────────────────────────────────────────── */
.rule { border: 0; border-top: 1px solid var(--border); margin: 0; }
.rule-strong { border: 0; border-top: 1px solid var(--fg); margin: 0; }
/* ─── hero variants used by layouts.md ──────────────────────────── */
.hero { padding-block: clamp(80px, 12vw, 160px); }
.hero-center { text-align: center; max-width: 32ch; margin-inline: auto; }
.hero h1 { margin-bottom: var(--gap-md); }
.hero .lead { margin-bottom: var(--gap-lg); }
.hero-cta { display: inline-flex; gap: var(--gap-sm); flex-wrap: wrap; }
.hero-center .hero-cta { justify-content: center; }
.hero-split { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap-2xl); align-items: center; }
@media (max-width: 920px) { .hero-split { grid-template-columns: 1fr; } }
/* ─── log row (newsletter, blog list, changelog) ────────────────── */
.log-row { display: grid; grid-template-columns: 120px 1fr 100px; gap: var(--gap-lg); padding: 22px 0; border-top: 1px solid var(--border); align-items: baseline; }
.log-row .meta { color: var(--muted); }
.log-row h3 { font-size: 19px; }
.log-row .pull { text-align: right; }
</style>
</head>
<body>
<header class="topnav" data-od-id="topnav">
<div class="container topnav-inner">
<span class="logo">[REPLACE] Brand</span>
<nav>
<a href="#">[REPLACE] Link 1</a>
<a href="#">[REPLACE] Link 2</a>
<a href="#">[REPLACE] Link 3</a>
</nav>
<button class="btn btn-primary">[REPLACE] CTA</button>
</div>
</header>
<main id="content">
<!--
┌─────────────────────────────────────────────────────────────────┐
│ PASTE LAYOUTS FROM references/layouts.md HERE. │
│ ► Each layout block is a self-contained `<section>` — │
│ drop in 3–6 of them per page. │
│ ► Always lead with one hero (Layout 1 or 2). │
│ ► End with a CTA strip + footer (Layout 6 / footer below). │
└─────────────────────────────────────────────────────────────────┘
-->
<section class="section hero" data-od-id="hero">
<div class="container hero-center">
<p class="eyebrow">[REPLACE] Eyebrow</p>
<h1>[REPLACE] One sharp sentence about what this is.</h1>
<p class="lead">[REPLACE] One subhead sentence — concrete value, not a tagline.</p>
<div class="hero-cta">
<button class="btn btn-primary">[REPLACE] Primary CTA</button>
<button class="btn btn-secondary">[REPLACE] Secondary</button>
</div>
</div>
</section>
</main>
<footer class="pagefoot" data-od-id="footer">
<div class="container row-between">
<span>© [REPLACE] Brand · [REPLACE] Year</span>
<span class="meta">[REPLACE] tagline · contact@example.com</span>
</div>
</footer>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Tomato — focused work timer</title>
<style>
:root {
--bg: #fafaf9; --fg: #1c1b1a; --muted: #6b6964; --border: #e6e4e0;
--accent: #c96442; --surface: #ffffff;
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--fg); font: 16px/1.55 -apple-system, system-ui, sans-serif; }
header, main, footer { max-width: 1080px; margin: 0 auto; padding: 0 32px; }
header { display: flex; justify-content: space-between; align-items: center; padding-top: 20px; }
.logo { font-weight: 600; font-size: 17px; letter-spacing: -0.01em; }
nav a { color: var(--fg); text-decoration: none; margin-left: 24px; font-size: 14px; }
nav a:hover { color: var(--accent); }
.hero { padding: 96px 0 80px; text-align: center; }
.hero h1 { font-size: clamp(44px, 6vw, 76px); line-height: 1.05; letter-spacing: -0.02em; margin: 0 0 20px; max-width: 18ch; margin-inline: auto; }
.hero p { color: var(--muted); font-size: 19px; max-width: 52ch; margin: 0 auto 32px; }
.cta { display: inline-flex; gap: 12px; }
button { font: inherit; cursor: pointer; padding: 12px 22px; border-radius: 8px; }
.btn-primary { background: var(--accent); color: white; border: 1px solid var(--accent); font-weight: 500; }
.btn-secondary { background: transparent; color: var(--fg); border: 1px solid var(--border); }
.features { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; padding: 56px 0 96px; }
@media (max-width: 720px) { .features { grid-template-columns: 1fr; } }
.feature { padding: 28px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; }
.feature .icon { width: 36px; height: 36px; border-radius: 8px; background: var(--accent); margin-bottom: 16px; opacity: 0.12; }
.feature h3 { margin: 0 0 8px; font-size: 17px; letter-spacing: -0.01em; }
.feature p { margin: 0; color: var(--muted); font-size: 14px; }
.closing { padding: 64px 0 96px; text-align: center; border-top: 1px solid var(--border); }
.closing h2 { font-size: 32px; margin: 0 0 12px; letter-spacing: -0.01em; }
.closing p { color: var(--muted); margin: 0 0 28px; }
footer { padding: 32px; color: var(--muted); font-size: 13px; text-align: center; }
</style>
</head>
<body>
<header data-od-id="topnav">
<span class="logo">🍅 Tomato</span>
<nav>
<a href="#features">Features</a>
<a href="#pricing">Pricing</a>
<a href="#login">Sign in</a>
</nav>
</header>
<main>
<section class="hero" data-od-id="hero">
<h1>Twenty-five minutes at a time.</h1>
<p>The pomodoro timer that actually keeps your hands off Slack. Block notifications, log every cycle, ship more before lunch.</p>
<div class="cta">
<button class="btn-primary">Start a session</button>
<button class="btn-secondary">See how it works</button>
</div>
</section>
<section class="features" id="features">
<div class="feature" data-od-id="feature-block">
<div class="icon"></div>
<h3>Block on, not off</h3>
<p>Slack and email go quiet for 25 minutes. They come back loud at the break, with a digest.</p>
</div>
<div class="feature" data-od-id="feature-stats">
<div class="icon"></div>
<h3>Stats that don't lie</h3>
<p>Weekly review tells you which days you actually shipped versus which you only seemed busy.</p>
</div>
<div class="feature" data-od-id="feature-team">
<div class="icon"></div>
<h3>Team-friendly silences</h3>
<p>Your status auto-updates so teammates know when to ask, when to wait, and when you're done.</p>
</div>
</section>
<section class="closing" data-od-id="closing">
<h2>Stop measuring meetings. Start measuring focus.</h2>
<p>Free for solo. $4/mo per teammate after that.</p>
<button class="btn-primary">Try Tomato free</button>
</section>
</main>
<footer>© Tomato Labs · Made for people who'd rather be making.</footer>
</body>
</html>
Web prototype checklist
Run this before emitting <artifact>. P0 = must pass; P1 = should pass; P2 = nice to have.
P0 — must pass
- [ ] No raw hex outside `:root` token block. Every color is
var(--bg)/var(--fg)/var(--muted)/var(--border)/var(--accent)/var(--surface)(or acolor-mix()of those). Grep#[0-9a-fA-F]{3,8}outside:root{}should return nothing. - [ ] All headings use `var(--font-display)`. No sans-serif
<h1>/<h2>. Inter / Roboto / system-sans never serve as a display face. - [ ] Accent appears at most twice per screen. Count: eyebrow color, primary CTA fill, anything else? If three or more, demote one to
var(--fg)orvar(--muted). - [ ] No purple/violet gradient backgrounds. No
linear-gradient(... #a855f7 / #8b5cf6 / purple ...). The seed template has no gradients on backgrounds — keep it that way. - [ ] No emoji used as feature icons. Use the inline SVG monoline marks shipped in Layout 3, or a tasteful single-character glyph in
--font-mono. ✨ 🚀 🎯 are out. - [ ] No invented metrics. Every number on the page came from the user, the brief, or is clearly labelled as a placeholder (e.g.
[REPLACE] · 38×). "10× faster", "99.9% uptime" without source = remove. - [ ] No filler copy. Zero "Feature One / Feature Two", lorem ipsum, "Lorem ipsum dolor". If a section feels empty, delete it; do not pad.
- [ ] `data-od-id` on every top-level `<section>`. Used by comment mode to target sections.
- [ ] Mobile reflow works. All
grid-2,grid-3,grid-4,grid-2-1,grid-1-2collapse to one column at ≤920px (the default media query intemplate.htmldoes this). Verify by mentally narrowing — no horizontal scroll. - [ ] No `scrollIntoView()` calls. Breaks the OD preview iframe. Use
scrollTo({...})if you need scroll behaviour.
P1 — should pass
- [ ] One decisive flourish. A pull quote, a striking stat, a real-feeling photograph, one micro-animation on the hero. One. Not three.
- [ ] Section rhythm alternates. No two stat rows in a row. No two feature triplets in a row. No two quote blocks in a row.
- [ ] Headlines under 14 words. If longer, the writing is doing the design's job.
- [ ] Lead text under 56 ch / two sentences.
max-width: 60chon.leadenforces this; don't override. - [ ] CTA buttons say what happens. "Start free" beats "Get Started". "Read the story" beats "Learn More".
- [ ] Hover states present for all
<a>and.btn. Seed template covers this. - [ ] Numerics use `.num` (mono, tabular). Prices, stats, version numbers, dates.
- [ ] One image style per page. Don't mix square portrait headshots with widescreen product hero with vertical phone mock — pick a lane.
P2 — nice to have
- [ ] `text-wrap: pretty` / `balance` on long paragraphs / headings (already on
<p>andh*in seed). - [ ] `color-mix()` for derived tones. No additional
--accent-50/--accent-300Bootstrap-style tokens — derive on the spot. - [ ] Sticky topnav has frosted glass (already in seed via
backdrop-filter: blur()). - [ ] Loaded fonts are system-first. Iowan Old Style / Charter for serif, system stack for sans. Only pull a Google Font if DESIGN.md specifies one.
Anti-slop spot-check
Look at the page for two seconds. If your gut says any of:
- "looks like every Cursor / Linear / Vercel ripoff I've seen this month"
- "this could be any AI startup's homepage"
- "the feature row has an icon, a heading, and three lines of vague benefit copy"
…go back, replace one feature cell with something more specific to this product (a screenshot, a concrete example, a sample of the actual output), and remove one accent.
Web prototype layouts
8 paste-ready section skeletons. Drop into <main id="content"> of assets/template.html. Don't write sections from scratch — pick the closest layout, paste, swap copy.
Pre-flight (do this once before pasting anything)
1. Read `assets/template.html` through the end of the <style> block. Every class used below must exist there. If one is missing, add it to <style> rather than inlining it on each section. 2. Pick a section list before writing copy. Default rhythms:
- Landing: 1 hero → 2 features → 3 stat-row OR quote → 4 split → 6 cta-strip → footer
- Marketing / editorial: 1 hero-center → 7 log-list → 4 split → 6 cta-strip
- Pricing / docs: 1 hero-center → table-driven → 6 cta-strip
3. One accent per screen, used at most twice. The hero eyebrow and the primary button already use it; budget any third usage carefully.
Class inventory (must exist in template.html)
sectioncontainerherohero-centerhero-splithero-ctaeyebrowleadh1h2h3metanumbtnbtn-primarybtn-secondarybtn-ghostbtn-arrowcardcard-flatcard-rulefeaturefeature-markstatstat-numstat-labelstat-unitquotequote-markquote-authorpilltagfieldinputtextareads-tablenum-colph-imgsquareportraitwiderulerule-stronggrid-2grid-3grid-4grid-2-1grid-1-2rowrow-betweenstacklog-rowpulltopnavpagefoot
If you reach for a class not on this list, define it in <style> first or use style="…" inline. Never invent a global class on a <section> that isn't backed by CSS.
---
Layout 1 — Hero, centered
Use when the page leads with a thesis sentence (most landings, most marketing pages). One eyebrow, one h1 (≤14 words), one lead sentence, two CTAs.
<section class="section hero" data-od-id="hero">
<div class="container hero-center">
<p class="eyebrow">EYEBROW · CONTEXT</p>
<h1>One sharp sentence about what this is.</h1>
<p class="lead">One concrete-value subhead — what changes for the reader.</p>
<div class="hero-cta">
<button class="btn btn-primary">Primary action</button>
<button class="btn btn-secondary">Secondary</button>
</div>
</div>
</section>Layout 2 — Hero, split (text + visual)
Use when there is a real product visual (product UI, screenshot, photograph). Left half copy, right half a ph-img placeholder the user replaces.
<section class="section" data-od-id="hero-split">
<div class="container hero-split">
<div>
<p class="eyebrow">EYEBROW · ROLE</p>
<h1>Headline that names the change.</h1>
<p class="lead" style="margin-top: 20px;">A short subhead — concrete, not corporate. Two sentences max.</p>
<div class="hero-cta" style="margin-top: 28px;">
<button class="btn btn-primary">Primary action</button>
<button class="btn btn-ghost btn-arrow">Read the story</button>
</div>
</div>
<div class="ph-img wide" aria-label="Hero visual placeholder">[ Hero visual · 16:9 ]</div>
</div>
</section>Layout 3 — Feature triplet
Three feature cells. Lead with a small <h2> framing the row. Don't put an icon on every heading — one tasteful mark per cell, monoline.
<section class="section" data-od-id="features">
<div class="container stack" style="gap: 56px;">
<div style="max-width: 36ch;">
<p class="eyebrow">WHAT'S DIFFERENT</p>
<h2>Three things you'll notice in the first ten minutes.</h2>
</div>
<div class="grid-3">
<div class="feature card-flat">
<div class="feature-mark">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M12 3v18M3 12h18"/></svg>
</div>
<h3>Specific feature one</h3>
<p>Two-sentence description that names the user value, not the technology.</p>
</div>
<div class="feature card-flat">
<div class="feature-mark">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="12" cy="12" r="8"/><path d="M12 8v4l3 2"/></svg>
</div>
<h3>Specific feature two</h3>
<p>Two-sentence description that names the user value, not the technology.</p>
</div>
<div class="feature card-flat">
<div class="feature-mark">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M4 7h16M4 12h10M4 17h16"/></svg>
</div>
<h3>Specific feature three</h3>
<p>Two-sentence description that names the user value, not the technology.</p>
</div>
</div>
</div>
</section>Layout 4 — Stat row (data billboard)
Use when there are real numbers. Three stats max — four feels like a brochure. Don't invent metrics. If you don't have a number, use a different layout.
<section class="section" data-od-id="stats">
<div class="container">
<p class="eyebrow" style="margin-bottom: 40px;">BY THE NUMBERS · 2026</p>
<div class="grid-3">
<div class="stat">
<div class="stat-num num">38<span class="stat-unit">×</span></div>
<p class="stat-label">less data moved over the wire vs. naive sync, on real customer workloads.</p>
</div>
<div class="stat">
<div class="stat-num num">3,184</div>
<p class="stat-label">paying teams, including 14 of the YC W26 batch.</p>
</div>
<div class="stat">
<div class="stat-num num">$0.04<span class="stat-unit">/GB</span></div>
<p class="stat-label">average egress saved — typical $1,800/mo bill drops to $200.</p>
</div>
</div>
</div>
</section>Layout 5 — Pull quote (testimonial)
A single decisive quote with attribution. Use sparingly — one per page, never two in a row.
<section class="section" data-od-id="quote">
<div class="container" style="max-width: 800px;">
<div class="quote-mark">"</div>
<blockquote class="quote">Filebase pays for itself in the first month. We were going to hire a dedicated DevOps person to babysit our sync — instead we just switched.</blockquote>
<p class="quote-author">— Mira Hassan, CTO at Northwind Studios</p>
</div>
</section>Layout 6 — CTA strip (closing)
End the page on one decisive ask. Centered, generous whitespace, one primary button. No secondary unless the page has zero other buttons.
<section class="section" data-od-id="cta-strip" style="text-align: center;">
<div class="container" style="max-width: 600px;">
<h2>Stop measuring meetings. Start measuring focus.</h2>
<p class="lead" style="margin: 16px auto 32px;">Free for solo. $4/mo per teammate after that.</p>
<button class="btn btn-primary">Start free</button>
</div>
</section>Layout 7 — Log list (changelog / blog index / posts)
Editorial layout for a list of dated entries. Date in mono on the left, title + dek in the middle, optional pull stat on the right. Borders on top, never around — boxes feel like a brochure.
<section class="section" data-od-id="log">
<div class="container">
<div class="row-between" style="margin-bottom: 32px;">
<h2>Recent changes</h2>
<a class="btn btn-ghost btn-arrow" href="#">View all</a>
</div>
<div>
<article class="log-row">
<span class="meta">Apr 27, 2026</span>
<div>
<h3>Sync engine v3 — half the wire bytes</h3>
<p style="margin: 4px 0 0; color: var(--muted); font-size: 14px;">A new content-defined chunker that produces 38× fewer post-edit changes on Final Cut projects.</p>
</div>
<span class="pull meta">Engineering</span>
</article>
<article class="log-row">
<span class="meta">Apr 19, 2026</span>
<div>
<h3>Per-folder bandwidth budgets</h3>
<p style="margin: 4px 0 0; color: var(--muted); font-size: 14px;">Cap how much a single project can pull each month — useful for archive folders.</p>
</div>
<span class="pull meta">Product</span>
</article>
<article class="log-row">
<span class="meta">Apr 04, 2026</span>
<div>
<h3>S3 + R2 dual-region replication</h3>
<p style="margin: 4px 0 0; color: var(--muted); font-size: 14px;">Two providers, automatic failover. Enterprise tier only for now.</p>
</div>
<span class="pull meta">Infra</span>
</article>
</div>
</div>
</section>Layout 8 — Comparison table (pricing, plan matrix, before/after)
Hairline borders, mono numerics, one column highlighted via an accent border. Don't put the whole row in surface-color — that screams "table".
<section class="section" data-od-id="pricing">
<div class="container">
<div style="text-align: center; max-width: 36ch; margin: 0 auto 56px;">
<p class="eyebrow">PRICING</p>
<h2>One row of features. Three lines of pricing.</h2>
</div>
<table class="ds-table">
<thead>
<tr>
<th>Feature</th>
<th class="num-col">Solo</th>
<th class="num-col">Team</th>
<th class="num-col">Enterprise</th>
</tr>
</thead>
<tbody>
<tr><td>Sync engine v3</td><td class="num-col">✓</td><td class="num-col">✓</td><td class="num-col">✓</td></tr>
<tr><td>Per-folder budgets</td><td class="num-col">—</td><td class="num-col">✓</td><td class="num-col">✓</td></tr>
<tr><td>SAML / SCIM</td><td class="num-col">—</td><td class="num-col">—</td><td class="num-col">✓</td></tr>
<tr><td>Dedicated infra</td><td class="num-col">—</td><td class="num-col">—</td><td class="num-col">✓</td></tr>
<tr style="border-top: 1px solid var(--fg);">
<td><strong>Monthly</strong></td>
<td class="num-col"><strong>$0</strong></td>
<td class="num-col"><strong>$4 / seat</strong></td>
<td class="num-col"><strong>Talk to us</strong></td>
</tr>
</tbody>
</table>
</div>
</section>---
Section rhythm — when in doubt
For a 5-section landing: 1. Hero (Layout 1 or 2) 2. Features (Layout 3) 3. Stats or quote (Layout 4 or 5) 4. Split detail (custom, using grid-2-1 / grid-1-2) 5. CTA + footer (Layout 6)
For a 4-section docs/marketing index: 1. Hero center (Layout 1) 2. Log list (Layout 7) 3. CTA + footer (Layout 6)
Two stat rows in a row, two quote blocks in a row, two feature triplets in a row — all visual fatigue. Alternate.
Related skills
FAQ
What file does web-prototype produce?
web-prototype writes a single self-contained index.html file composed from the bundled template.html seed and layout skeletons. Open Design renders the file as a sandboxed HTML preview without requiring a frontend framework.
How many layout skeletons does web-prototype include?
web-prototype includes 8 paste-ready section skeletons in references/layouts.md. Agents pick hero, features, stats, pricing, or CTA layouts and map six :root tokens from the active DESIGN.md before writing index.html.