first-commit
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
---
|
||||
name: web-prototype
|
||||
description: |
|
||||
General-purpose desktop web prototype. Single self-contained HTML file built
|
||||
by copying the seed `assets/template.html` and pasting section layouts from
|
||||
`references/layouts.md`. Default for any landing / marketing / docs / SaaS
|
||||
page when no more specific skill matches.
|
||||
triggers:
|
||||
- "prototype"
|
||||
- "mockup"
|
||||
- "landing"
|
||||
- "single page"
|
||||
- "marketing page"
|
||||
- "homepage"
|
||||
od:
|
||||
mode: prototype
|
||||
platform: desktop
|
||||
scenario: design
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: true
|
||||
sections: [color, typography, layout, components]
|
||||
---
|
||||
|
||||
# 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-review
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
### 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 — Copy the seed
|
||||
|
||||
Copy `assets/template.html` to the project root as `index.html`. 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 your `index.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 `index.html` in `<artifact>` tags. 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-img` class — 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>
|
||||
```
|
||||
|
||||
One sentence before the artifact. Nothing after.
|
||||
@@ -0,0 +1,338 @@
|
||||
<!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>
|
||||
@@ -0,0 +1,81 @@
|
||||
<!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>
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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 a `color-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)` or `var(--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-2` collapse to one column at ≤920px (the default media query in `template.html` does 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: 60ch` on `.lead` enforces 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>` and `h*` in seed).
|
||||
- [ ] **`color-mix()` for derived tones.** No additional `--accent-50` / `--accent-300` Bootstrap-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.
|
||||
@@ -0,0 +1,247 @@
|
||||
# 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`)
|
||||
|
||||
> `section` `container` `hero` `hero-center` `hero-split` `hero-cta` `eyebrow` `lead` `h1` `h2` `h3` `meta` `num` `btn` `btn-primary` `btn-secondary` `btn-ghost` `btn-arrow` `card` `card-flat` `card-rule` `feature` `feature-mark` `stat` `stat-num` `stat-label` `stat-unit` `quote` `quote-mark` `quote-author` `pill` `tag` `field` `input` `textarea` `ds-table` `num-col` `ph-img` `square` `portrait` `wide` `rule` `rule-strong` `grid-2` `grid-3` `grid-4` `grid-2-1` `grid-1-2` `row` `row-between` `stack` `log-row` `pull` `topnav` `pagefoot`
|
||||
|
||||
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.
|
||||
|
||||
```html
|
||||
<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.
|
||||
|
||||
```html
|
||||
<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.
|
||||
|
||||
```html
|
||||
<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.
|
||||
|
||||
```html
|
||||
<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.
|
||||
|
||||
```html
|
||||
<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.
|
||||
|
||||
```html
|
||||
<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.
|
||||
|
||||
```html
|
||||
<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".
|
||||
|
||||
```html
|
||||
<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.
|
||||
Reference in New Issue
Block a user