Build seamless game loop prototype
This commit is contained in:
80
.od-skills/blog-post-2d43614db1/SKILL.md
Normal file
80
.od-skills/blog-post-2d43614db1/SKILL.md
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
name: blog-post
|
||||
description: |
|
||||
A long-form article / blog post — masthead, hero image placeholder,
|
||||
article body with figures and pull quotes, author byline, related posts.
|
||||
Use when the brief asks for "blog", "article", "post", "essay", or
|
||||
"case study".
|
||||
triggers:
|
||||
- "blog"
|
||||
- "blog post"
|
||||
- "article"
|
||||
- "essay"
|
||||
- "case study"
|
||||
- "newsletter"
|
||||
- "博客"
|
||||
- "文章"
|
||||
od:
|
||||
mode: prototype
|
||||
platform: desktop
|
||||
scenario: marketing
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: true
|
||||
sections: [color, typography, layout, components]
|
||||
craft:
|
||||
requires: [typography, typography-hierarchy, typography-hierarchy-editorial, rtl-and-bidi]
|
||||
---
|
||||
|
||||
# Blog Post Skill
|
||||
|
||||
Produce a single long-form article page — editorial layout, no chrome.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Read the active DESIGN.md** (injected above). Lean into the typography
|
||||
tokens — long-form is 70% type, 20% image, 10% chrome.
|
||||
2. **Pick the topic** from the brief and write a real article — at least 600
|
||||
words across 4–6 H2 sections. No lorem ipsum.
|
||||
3. **Sections**, in order:
|
||||
- **Masthead** — small wordmark + 4–6 nav links, plain.
|
||||
- **Article header** — category eyebrow, headline (display token, large),
|
||||
deck (1–2 sentence subhead), author name + role + date.
|
||||
- **Hero image** — a 16:9 placeholder block using a DS-tinted gradient or
|
||||
solid fill (no external images). Add a 1-line caption underneath.
|
||||
- **Body** — alternating prose paragraphs with at least:
|
||||
- 1 pull quote (large display type, accent rule on the inline-start edge so the layout flips correctly under `dir="rtl"`).
|
||||
- 1 figure (image placeholder + caption).
|
||||
- 1 list (numbered or bulleted).
|
||||
- 1 inline blockquote.
|
||||
- **Author footer** — author avatar (initials in a circle), bio paragraph.
|
||||
- **Related** — 3 cards linking to other posts. Each card: tiny image
|
||||
block, title, 1-line excerpt, date.
|
||||
4. **Write** a single HTML document:
|
||||
- `<!doctype html>` through `</html>`, CSS inline.
|
||||
- Article body uses the DS body font, centered, max-width per DS layout
|
||||
rule (typically 680–720px).
|
||||
- Drop caps (`first-letter`) only if the DS mood is editorial / serif —
|
||||
skip on tech-y DSes.
|
||||
- `data-od-id` on the headline, hero, body, pull quote, related grid.
|
||||
5. **Self-check**:
|
||||
- Type hierarchy is unambiguous — H1 is clearly the headline; H2s are
|
||||
section dividers; pull quotes do not compete with H1.
|
||||
- Line length 60–75 chars for body prose.
|
||||
- Accent appears at most twice (eyebrow + pull-quote rule, or one link).
|
||||
- The page reads like a magazine, not a marketing landing.
|
||||
|
||||
## Output contract
|
||||
|
||||
Emit between `<artifact>` tags:
|
||||
|
||||
```
|
||||
<artifact identifier="post-slug" type="text/html" title="Article Title">
|
||||
<!doctype html>
|
||||
<html>...</html>
|
||||
</artifact>
|
||||
```
|
||||
|
||||
One sentence before the artifact, nothing after.
|
||||
80
.od-skills/blog-post-2d43614db1/example.html
Normal file
80
.od-skills/blog-post-2d43614db1/example.html
Normal file
@@ -0,0 +1,80 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Why we rewrote our sync engine in Rust — Filebase</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: 18px/1.65 Georgia, 'Iowan Old Style', serif; }
|
||||
.wrap { max-width: 680px; margin: 0 auto; padding: 56px 28px 96px; }
|
||||
nav.top { font-family: -apple-system, system-ui, sans-serif; font-size: 13px; color: var(--muted); margin-bottom: 56px; }
|
||||
nav.top a { color: inherit; text-decoration: none; }
|
||||
.eyebrow { font-family: -apple-system, system-ui, sans-serif; font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent); margin-bottom: 14px; }
|
||||
h1 { font-size: clamp(36px, 5vw, 52px); line-height: 1.1; letter-spacing: -0.015em; margin: 0 0 20px; }
|
||||
.byline { font-family: -apple-system, system-ui, sans-serif; font-size: 14px; color: var(--muted); margin: 0 0 40px; display: flex; align-items: center; gap: 12px; }
|
||||
.avatar { width: 32px; height: 32px; border-radius: 50%; background: var(--accent); opacity: 0.18; }
|
||||
.lede { font-size: 22px; line-height: 1.5; color: var(--fg); margin: 0 0 40px; font-style: italic; }
|
||||
.hero-figure { aspect-ratio: 16/9; background: linear-gradient(135deg, var(--accent), #6b6964); border-radius: 8px; margin-bottom: 48px; opacity: 0.85; }
|
||||
p { margin: 24px 0; }
|
||||
p:first-of-type::first-letter { float: left; font-size: 64px; line-height: 0.9; padding: 6px 10px 0 0; font-weight: 600; color: var(--accent); }
|
||||
h2 { font-size: 28px; letter-spacing: -0.01em; margin: 56px 0 12px; line-height: 1.2; }
|
||||
blockquote { margin: 40px 0; padding: 0 32px; font-size: 24px; line-height: 1.4; color: var(--fg); border-left: 3px solid var(--accent); font-style: italic; }
|
||||
code { font-family: ui-monospace, monospace; background: var(--surface); border: 1px solid var(--border); padding: 1px 5px; border-radius: 4px; font-size: 0.85em; }
|
||||
pre { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 16px 18px; overflow-x: auto; font: 14px/1.55 ui-monospace, monospace; }
|
||||
figure.numbers { font-family: -apple-system, system-ui, sans-serif; display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; margin: 40px -24px; padding: 28px 24px; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
|
||||
figure.numbers .stat .value { font-family: Georgia, serif; font-size: 38px; letter-spacing: -0.01em; line-height: 1; }
|
||||
figure.numbers .stat .label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; margin-top: 6px; }
|
||||
.endnote { font-family: -apple-system, system-ui, sans-serif; font-size: 13px; color: var(--muted); margin-top: 64px; padding-top: 24px; border-top: 1px solid var(--border); }
|
||||
.endnote a { color: var(--accent); text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<article class="wrap" data-od-id="article">
|
||||
<nav class="top"><a href="#">← Filebase blog</a></nav>
|
||||
<div class="eyebrow">Engineering</div>
|
||||
<h1>Why we rewrote our sync engine in Rust</h1>
|
||||
<div class="byline">
|
||||
<div class="avatar"></div>
|
||||
<span>By Mira Hassan · April 22, 2026 · 8 min read</span>
|
||||
</div>
|
||||
<p class="lede">For two years our Go sync engine was good enough. Then video editors started joining the customer list, and the GC pauses we'd been politely ignoring turned into bug reports we couldn't ignore.</p>
|
||||
<div class="hero-figure" data-od-id="hero-figure"></div>
|
||||
|
||||
<p>The decision wasn't sudden. We'd been watching the GC pause distribution shift for six months before we admitted what the data was telling us. P50 latency was great. P99 was a horror movie. Customers syncing 30 GB of <code>.psd</code> files in active editing sessions were the ones writing in.</p>
|
||||
|
||||
<p>Rewriting an entire sync engine sounds like the kind of project a startup is told never to do. We did it anyway. Here's how it went, what surprised us, and the parts I'd do differently.</p>
|
||||
|
||||
<h2>The trigger: GC pauses we couldn't fix</h2>
|
||||
<p>Go's garbage collector is brilliant. It is also, fundamentally, a tradeoff. Our hot path allocated short-lived buffer slices on every block diff — and at our scale, on a heavy uploader, the collector ran often enough that the P99 pause crept past 50ms.</p>
|
||||
|
||||
<p>We tried the usual fixes: pooling buffers with <code>sync.Pool</code>, tuning <code>GOGC</code>, reducing allocations in the merge path. They each helped a little. None of them got us under 20ms, and the customers we cared about needed under 5.</p>
|
||||
|
||||
<blockquote>"We can't fix this in Go. We can fix it in something without a GC."</blockquote>
|
||||
|
||||
<p>Our staff engineer Sasha said this in a meeting in October. He was right. The question wasn't whether to leave Go. It was what to leave it for, and how much we could keep.</p>
|
||||
|
||||
<h2>What we kept; what we threw out</h2>
|
||||
<p>The CLI stayed in Go. The control plane stayed in Go. The bit that does block-level diffing in a hot loop on a customer's laptop — that became Rust. The boundary became a single FFI surface with a small, opinionated protocol.</p>
|
||||
|
||||
<figure class="numbers">
|
||||
<div class="stat"><div class="value">38ms → 4ms</div><div class="label">P99 sync latency</div></div>
|
||||
<div class="stat"><div class="value">62%</div><div class="label">Memory drop</div></div>
|
||||
<div class="stat"><div class="value">11 weeks</div><div class="label">From RFC to ship</div></div>
|
||||
</figure>
|
||||
|
||||
<p>The numbers above are real and from production. They are also misleading without context: the Rust port doesn't just remove the GC, it also removes a layer of abstraction we'd been carrying since the Go MVP.</p>
|
||||
|
||||
<h2>What I'd do differently</h2>
|
||||
<p>One thing: the FFI boundary. We chose <code>cgo</code> for symmetry — Go calling Rust feels right when you already have Go everywhere. But the binding ceremony is brittle, and we ate two production incidents from string lifetime mistakes before we wrote a wrapper layer that handled them once.</p>
|
||||
|
||||
<p>If I were starting today, I'd reach for <code>uniffi</code> or generate the bindings from a schema. The lessons isn't <em>don't use cgo</em>; it's <em>treat the boundary like an external API the moment you cross language families</em>.</p>
|
||||
|
||||
<div class="endnote">Filebase is hiring engineers who like writing this kind of post. <a href="#">See open roles →</a></div>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
79
.od-skills/blog-post/SKILL.md
Normal file
79
.od-skills/blog-post/SKILL.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
name: blog-post
|
||||
description: |
|
||||
A long-form article / blog post — masthead, hero image placeholder,
|
||||
article body with figures and pull quotes, author byline, related posts.
|
||||
Use when the brief asks for "blog", "article", "post", "essay", or
|
||||
"case study".
|
||||
triggers:
|
||||
- "blog"
|
||||
- "blog post"
|
||||
- "article"
|
||||
- "essay"
|
||||
- "case study"
|
||||
- "newsletter"
|
||||
- "博客"
|
||||
- "文章"
|
||||
od:
|
||||
mode: prototype
|
||||
platform: desktop
|
||||
scenario: marketing
|
||||
featured: 11
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: true
|
||||
sections: [color, typography, layout, components]
|
||||
---
|
||||
|
||||
# Blog Post Skill
|
||||
|
||||
Produce a single long-form article page — editorial layout, no chrome.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Read the active DESIGN.md** (injected above). Lean into the typography
|
||||
tokens — long-form is 70% type, 20% image, 10% chrome.
|
||||
2. **Pick the topic** from the brief and write a real article — at least 600
|
||||
words across 4–6 H2 sections. No lorem ipsum.
|
||||
3. **Sections**, in order:
|
||||
- **Masthead** — small wordmark + 4–6 nav links, plain.
|
||||
- **Article header** — category eyebrow, headline (display token, large),
|
||||
deck (1–2 sentence subhead), author name + role + date.
|
||||
- **Hero image** — a 16:9 placeholder block using a DS-tinted gradient or
|
||||
solid fill (no external images). Add a 1-line caption underneath.
|
||||
- **Body** — alternating prose paragraphs with at least:
|
||||
- 1 pull quote (large display type, accent rule on the left).
|
||||
- 1 figure (image placeholder + caption).
|
||||
- 1 list (numbered or bulleted).
|
||||
- 1 inline blockquote.
|
||||
- **Author footer** — author avatar (initials in a circle), bio paragraph.
|
||||
- **Related** — 3 cards linking to other posts. Each card: tiny image
|
||||
block, title, 1-line excerpt, date.
|
||||
4. **Write** a single HTML document:
|
||||
- `<!doctype html>` through `</html>`, CSS inline.
|
||||
- Article body uses the DS body font, centered, max-width per DS layout
|
||||
rule (typically 680–720px).
|
||||
- Drop caps (`first-letter`) only if the DS mood is editorial / serif —
|
||||
skip on tech-y DSes.
|
||||
- `data-od-id` on the headline, hero, body, pull quote, related grid.
|
||||
5. **Self-check**:
|
||||
- Type hierarchy is unambiguous — H1 is clearly the headline; H2s are
|
||||
section dividers; pull quotes do not compete with H1.
|
||||
- Line length 60–75 chars for body prose.
|
||||
- Accent appears at most twice (eyebrow + pull-quote rule, or one link).
|
||||
- The page reads like a magazine, not a marketing landing.
|
||||
|
||||
## Output contract
|
||||
|
||||
Emit between `<artifact>` tags:
|
||||
|
||||
```
|
||||
<artifact identifier="post-slug" type="text/html" title="Article Title">
|
||||
<!doctype html>
|
||||
<html>...</html>
|
||||
</artifact>
|
||||
```
|
||||
|
||||
One sentence before the artifact, nothing after.
|
||||
80
.od-skills/blog-post/example.html
Normal file
80
.od-skills/blog-post/example.html
Normal file
@@ -0,0 +1,80 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Why we rewrote our sync engine in Rust — Filebase</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: 18px/1.65 Georgia, 'Iowan Old Style', serif; }
|
||||
.wrap { max-width: 680px; margin: 0 auto; padding: 56px 28px 96px; }
|
||||
nav.top { font-family: -apple-system, system-ui, sans-serif; font-size: 13px; color: var(--muted); margin-bottom: 56px; }
|
||||
nav.top a { color: inherit; text-decoration: none; }
|
||||
.eyebrow { font-family: -apple-system, system-ui, sans-serif; font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent); margin-bottom: 14px; }
|
||||
h1 { font-size: clamp(36px, 5vw, 52px); line-height: 1.1; letter-spacing: -0.015em; margin: 0 0 20px; }
|
||||
.byline { font-family: -apple-system, system-ui, sans-serif; font-size: 14px; color: var(--muted); margin: 0 0 40px; display: flex; align-items: center; gap: 12px; }
|
||||
.avatar { width: 32px; height: 32px; border-radius: 50%; background: var(--accent); opacity: 0.18; }
|
||||
.lede { font-size: 22px; line-height: 1.5; color: var(--fg); margin: 0 0 40px; font-style: italic; }
|
||||
.hero-figure { aspect-ratio: 16/9; background: linear-gradient(135deg, var(--accent), #6b6964); border-radius: 8px; margin-bottom: 48px; opacity: 0.85; }
|
||||
p { margin: 24px 0; }
|
||||
p:first-of-type::first-letter { float: left; font-size: 64px; line-height: 0.9; padding: 6px 10px 0 0; font-weight: 600; color: var(--accent); }
|
||||
h2 { font-size: 28px; letter-spacing: -0.01em; margin: 56px 0 12px; line-height: 1.2; }
|
||||
blockquote { margin: 40px 0; padding: 0 32px; font-size: 24px; line-height: 1.4; color: var(--fg); border-left: 3px solid var(--accent); font-style: italic; }
|
||||
code { font-family: ui-monospace, monospace; background: var(--surface); border: 1px solid var(--border); padding: 1px 5px; border-radius: 4px; font-size: 0.85em; }
|
||||
pre { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 16px 18px; overflow-x: auto; font: 14px/1.55 ui-monospace, monospace; }
|
||||
figure.numbers { font-family: -apple-system, system-ui, sans-serif; display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; margin: 40px -24px; padding: 28px 24px; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
|
||||
figure.numbers .stat .value { font-family: Georgia, serif; font-size: 38px; letter-spacing: -0.01em; line-height: 1; }
|
||||
figure.numbers .stat .label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; margin-top: 6px; }
|
||||
.endnote { font-family: -apple-system, system-ui, sans-serif; font-size: 13px; color: var(--muted); margin-top: 64px; padding-top: 24px; border-top: 1px solid var(--border); }
|
||||
.endnote a { color: var(--accent); text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<article class="wrap" data-od-id="article">
|
||||
<nav class="top"><a href="#">← Filebase blog</a></nav>
|
||||
<div class="eyebrow">Engineering</div>
|
||||
<h1>Why we rewrote our sync engine in Rust</h1>
|
||||
<div class="byline">
|
||||
<div class="avatar"></div>
|
||||
<span>By Mira Hassan · April 22, 2026 · 8 min read</span>
|
||||
</div>
|
||||
<p class="lede">For two years our Go sync engine was good enough. Then video editors started joining the customer list, and the GC pauses we'd been politely ignoring turned into bug reports we couldn't ignore.</p>
|
||||
<div class="hero-figure" data-od-id="hero-figure"></div>
|
||||
|
||||
<p>The decision wasn't sudden. We'd been watching the GC pause distribution shift for six months before we admitted what the data was telling us. P50 latency was great. P99 was a horror movie. Customers syncing 30 GB of <code>.psd</code> files in active editing sessions were the ones writing in.</p>
|
||||
|
||||
<p>Rewriting an entire sync engine sounds like the kind of project a startup is told never to do. We did it anyway. Here's how it went, what surprised us, and the parts I'd do differently.</p>
|
||||
|
||||
<h2>The trigger: GC pauses we couldn't fix</h2>
|
||||
<p>Go's garbage collector is brilliant. It is also, fundamentally, a tradeoff. Our hot path allocated short-lived buffer slices on every block diff — and at our scale, on a heavy uploader, the collector ran often enough that the P99 pause crept past 50ms.</p>
|
||||
|
||||
<p>We tried the usual fixes: pooling buffers with <code>sync.Pool</code>, tuning <code>GOGC</code>, reducing allocations in the merge path. They each helped a little. None of them got us under 20ms, and the customers we cared about needed under 5.</p>
|
||||
|
||||
<blockquote>"We can't fix this in Go. We can fix it in something without a GC."</blockquote>
|
||||
|
||||
<p>Our staff engineer Sasha said this in a meeting in October. He was right. The question wasn't whether to leave Go. It was what to leave it for, and how much we could keep.</p>
|
||||
|
||||
<h2>What we kept; what we threw out</h2>
|
||||
<p>The CLI stayed in Go. The control plane stayed in Go. The bit that does block-level diffing in a hot loop on a customer's laptop — that became Rust. The boundary became a single FFI surface with a small, opinionated protocol.</p>
|
||||
|
||||
<figure class="numbers">
|
||||
<div class="stat"><div class="value">38ms → 4ms</div><div class="label">P99 sync latency</div></div>
|
||||
<div class="stat"><div class="value">62%</div><div class="label">Memory drop</div></div>
|
||||
<div class="stat"><div class="value">11 weeks</div><div class="label">From RFC to ship</div></div>
|
||||
</figure>
|
||||
|
||||
<p>The numbers above are real and from production. They are also misleading without context: the Rust port doesn't just remove the GC, it also removes a layer of abstraction we'd been carrying since the Go MVP.</p>
|
||||
|
||||
<h2>What I'd do differently</h2>
|
||||
<p>One thing: the FFI boundary. We chose <code>cgo</code> for symmetry — Go calling Rust feels right when you already have Go everywhere. But the binding ceremony is brittle, and we ate two production incidents from string lifetime mistakes before we wrote a wrapper layer that handled them once.</p>
|
||||
|
||||
<p>If I were starting today, I'd reach for <code>uniffi</code> or generate the bindings from a schema. The lessons isn't <em>don't use cgo</em>; it's <em>treat the boundary like an external API the moment you cross language families</em>.</p>
|
||||
|
||||
<div class="endnote">Filebase is hiring engineers who like writing this kind of post. <a href="#">See open roles →</a></div>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
97
.od-skills/web-prototype-36dbb042a6/SKILL.md
Normal file
97
.od-skills/web-prototype-36dbb042a6/SKILL.md
Normal file
@@ -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.
|
||||
338
.od-skills/web-prototype-36dbb042a6/assets/template.html
Normal file
338
.od-skills/web-prototype-36dbb042a6/assets/template.html
Normal file
@@ -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>
|
||||
81
.od-skills/web-prototype-36dbb042a6/example.html
Normal file
81
.od-skills/web-prototype-36dbb042a6/example.html
Normal file
@@ -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>
|
||||
132
.od-skills/web-prototype-36dbb042a6/open-design.json
Normal file
132
.od-skills/web-prototype-36dbb042a6/open-design.json
Normal file
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "example-web-prototype",
|
||||
"title": "Web Prototype",
|
||||
"version": "0.1.1",
|
||||
"description": "General-purpose desktop web prototype. Single self-contained HTML file built\nby copying the seed `assets/template.html` and pasting section layouts from\n`references/layouts.md`. Default for any landing / marketing / docs / SaaS\npage when no more specific skill matches.",
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "Open Design",
|
||||
"url": "https://github.com/nexu-io"
|
||||
},
|
||||
"homepage": "https://github.com/nexu-io/open-design/tree/main/plugins/_official/examples/web-prototype",
|
||||
"tags": [
|
||||
"example",
|
||||
"first-party",
|
||||
"prototype",
|
||||
"design",
|
||||
"web",
|
||||
"desktop",
|
||||
"mockup",
|
||||
"landing",
|
||||
"single-page",
|
||||
"marketing-page",
|
||||
"homepage"
|
||||
],
|
||||
"compat": {
|
||||
"agentSkills": [
|
||||
{
|
||||
"path": "./SKILL.md"
|
||||
}
|
||||
]
|
||||
},
|
||||
"od": {
|
||||
"kind": "scenario",
|
||||
"taskKind": "new-generation",
|
||||
"mode": "prototype",
|
||||
"platform": "desktop",
|
||||
"scenario": "design",
|
||||
"surface": "web",
|
||||
"preview": {
|
||||
"type": "html",
|
||||
"entry": "./example.html"
|
||||
},
|
||||
"useCase": {
|
||||
"query": {
|
||||
"en": "Create a premium product-studio {{fidelity}} {{artifactKind}} for {{audience}}: sharp information architecture, elegant visual hierarchy, polished interaction states, and a refined design-system-driven interface that feels shipped by a top-tier product team. Use {{designSystem}} as the design-system direction and start from {{template}}. Build one self-contained HTML file by copying the seed `assets/template.html` and pasting layouts from `references/layouts.md`.",
|
||||
"zh-CN": "用高端产品工作室的完成度,为 {{audience}} 打磨一个 {{fidelity}} 的 {{artifactKind}}:信息架构清晰、视觉层级优雅、交互状态完整,整体像顶级产品团队交付的可演示原型。设计系统方向使用 {{designSystem}},从 {{template}} 开始。使用 `assets/template.html` 种子并从 `references/layouts.md` 粘贴版面,输出单文件 HTML。"
|
||||
},
|
||||
"exampleOutputs": [
|
||||
{
|
||||
"path": "./example.html",
|
||||
"title": "Web Prototype"
|
||||
}
|
||||
]
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"name": "artifactKind",
|
||||
"label": "Artifact kind",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"placeholder": "SaaS landing page",
|
||||
"default": "web prototype"
|
||||
},
|
||||
{
|
||||
"name": "fidelity",
|
||||
"label": "Fidelity",
|
||||
"type": "select",
|
||||
"required": true,
|
||||
"options": [
|
||||
"wireframe",
|
||||
"high-fidelity"
|
||||
],
|
||||
"default": "high-fidelity"
|
||||
},
|
||||
{
|
||||
"name": "audience",
|
||||
"label": "Audience",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"placeholder": "startup founders evaluating an AI CRM",
|
||||
"default": "product evaluators"
|
||||
},
|
||||
{
|
||||
"name": "designSystem",
|
||||
"label": "Design system",
|
||||
"type": "string",
|
||||
"placeholder": "OpenAI, Linear, shadcn, or custom brand notes",
|
||||
"default": "the active project design system"
|
||||
},
|
||||
{
|
||||
"name": "template",
|
||||
"label": "Template",
|
||||
"type": "string",
|
||||
"placeholder": "marketing homepage, dashboard, docs page",
|
||||
"default": "the bundled web prototype seed"
|
||||
}
|
||||
],
|
||||
"context": {
|
||||
"skills": [
|
||||
{
|
||||
"path": "./SKILL.md"
|
||||
}
|
||||
],
|
||||
"designSystem": {
|
||||
"primary": true
|
||||
},
|
||||
"assets": [
|
||||
"./example.html",
|
||||
"./assets/template.html",
|
||||
"./references/checklist.md",
|
||||
"./references/layouts.md"
|
||||
]
|
||||
},
|
||||
"pipeline": {
|
||||
"stages": [
|
||||
{
|
||||
"id": "generate",
|
||||
"atoms": [
|
||||
"file-write",
|
||||
"live-artifact"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"capabilities": [
|
||||
"prompt:inject",
|
||||
"fs:write"
|
||||
]
|
||||
}
|
||||
}
|
||||
44
.od-skills/web-prototype-36dbb042a6/references/checklist.md
Normal file
44
.od-skills/web-prototype-36dbb042a6/references/checklist.md
Normal file
@@ -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.
|
||||
247
.od-skills/web-prototype-36dbb042a6/references/layouts.md
Normal file
247
.od-skills/web-prototype-36dbb042a6/references/layouts.md
Normal file
@@ -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