diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index 6213de6..eef857d 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -8,8 +8,8 @@
{
"name": "ultraweb",
"source": "./",
- "description": "One guided session → first-grade Next.js website: dynamic scoping questions, three fast mockups, your approval, then the full build with sign-off checkpoints on a real-studio cadence. 75 interlocking skills + 3 model-routed subagents: taste constitution, award-canon study library (25 patterns from Awwwards SOTY-tier winners), design-system-first pipeline, real quality gates, current-stack engineering.",
- "version": "1.6.0",
+ "description": "One guided session → first-grade Next.js website: dynamic scoping questions, three fast mockups, your approval, then the full build with sign-off checkpoints on a real-studio cadence — resumable mid-build, scoped by a sketch/standard/flagship dial, reviewable on your own phone via preview URLs. 80 interlocking skills + 3 model-routed subagents: taste constitution enforced at write time by hooks, award-canon study library (25 patterns from Awwwards SOTY-tier winners), client-asset intake, brand-mark identity, design-system-first pipeline, real quality gates, current-stack engineering.",
+ "version": "1.7.0",
"author": {
"name": "Iwan Braun"
},
diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
index 92e29a7..434ade6 100644
--- a/.claude-plugin/plugin.json
+++ b/.claude-plugin/plugin.json
@@ -1,8 +1,8 @@
{
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
"name": "ultraweb",
- "version": "1.6.0",
- "description": "Guided webdesign harness for Next.js: a dynamic scoping interview, a three-candidate mockup round, and human-in-the-loop checkpoints on a real-studio cadence — first-page review on the built homepage, preflight/UAT acceptance before ship, a dial from hands-off to full sign-off (classic one-prompt autonomous mode on request) — 75 interlocking skills with real-world worked examples, model-routed subagents (Opus 5 / Sonnet 5), design taste, an award-canon study library distilled from Awwwards-winning sites, design systems, components, motion — including a DIRECTION-gated anime.js SVG-choreography engine and a DIRECTION-gated site-scale immersive-3D build — Next.js engineering, backend, and quality gates",
+ "version": "1.7.0",
+ "description": "Guided webdesign harness for Next.js: a dynamic scoping interview, a three-candidate mockup round (side-by-side or pairwise tournament), and human-in-the-loop checkpoints on a real-studio cadence — first-page review on the built homepage, preflight/UAT acceptance before ship, preview URLs so you review on your own phone, a dial from hands-off to full sign-off plus a scope dial from sketch to flagship (classic one-prompt autonomous mode on request) — 80 interlocking skills with real-world worked examples, model-routed subagents (Opus 5 / Sonnet 5), design taste enforced at write time by hooks, an award-canon study library distilled from Awwwards-winning sites, client-asset intake, brand-mark identity, design systems, components, motion — including a DIRECTION-gated anime.js SVG-choreography engine and a DIRECTION-gated site-scale immersive-3D build — Next.js engineering, backend, quality gates, a live in-build /studio dashboard, progress + resume across interruptions, and a cross-build taste fingerprint",
"author": {
"name": "Iwan Braun",
"email": "iwan.braun2004@gmail.com"
@@ -28,5 +28,6 @@
],
"skills": [
"./"
- ]
+ ],
+ "hooks": "./hooks/hooks.json"
}
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000..8bcfa25
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,16 @@
+name: corpus-lint
+on:
+ push:
+ branches: ['**']
+ pull_request:
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ - name: Lint the skill corpus invariants
+ run: node scripts/lint-skills.mjs
diff --git a/CAST.md b/CAST.md
new file mode 100644
index 0000000..5001563
--- /dev/null
+++ b/CAST.md
@@ -0,0 +1,146 @@
+# CAST.md — the eight recurring clients
+
+Canonical fact sheet for the eight clients traced across the ~70 skill files' Worked-example sections (`ROSTER.md` line 7). Skills don't invent a client's palette, type, routes, backend, or signature move — they cite this file. A value a skill states must match the row below; if a skill needs a value this sheet lacks, that skill sets it and records it here. Where skills already disagree, the majority/most-detailed value is canonical and the outlier is logged under **Known divergences** — those files are not edited by this pass. Every fact below is extracted (or paraphrased without invention) from `skills/*/SKILL.md` and the root `SKILL.md`/`ROSTER.md`.
+
+---
+
+## Kaffeewerk Ost
+
+Berlin specialty coffee roastery + online shop — single-origin roasts sold one-off and as a recurring **Abo** (subscription).
+
+| Field | Value |
+|---|---|
+| Archetype | **Warm Organic/Humanist**, pushed ~20% past comfortable, + twist: Swiss column discipline on commerce surfaces (`SKILL.md`, `direction`, `mockup`, `checkpoint`) |
+| Ground | `oklch(0.97 0.008 75)` cream |
+| Ink | `oklch(0.24 0.02 60)` |
+| Accent | `oklch(0.62 0.16 45)` rust — light-ground only, fails AA on the dark bar (`identity`) |
+| Type pairing | Fraunces (`opsz`, display) + Work Sans (UI/body) |
+| Routes | `/`, `/shop`, `/shop/[slug]`, `/abo`, `/roesterei`, `/kontakt` |
+| Backend | Stripe checkout + raw-body webhook · Drizzle (`products`/`orders`/`subscriptions`) · Resend receipts · TTDSG-direct consent |
+| Signature move | The roast-profile temperature curve — hand-drawn SVG rise-and-plateau path; hero spine + section divider on `/`; on `/roesterei` three batch profiles trace in sequence with a bean marker |
+
+## Tidepool
+
+B2B analytics SaaS for container-terminal / port-logistics ops managers.
+
+| Field | Value |
+|---|---|
+| Archetype | **Precision Instrument** — Neo-grotesque Minimal, calm, data-forward, dark-mode first-class (`command-palette`, `depth`, `navigation`, `overlays`, `icons`, `pricing`) |
+| Ground (dark, default) | `oklch(0.18 0.015 250)` · card `oklch(0.22 0.015 250)` · popover `oklch(0.26 0.015 250)` |
+| Ground (light) | `oklch(0.985 0.005 240)` — hue 245 |
+| Accent | teal `oklch(0.68 0.12 200)` — no decorative glow (banned by name) |
+| Type pairing | General Sans (UI/display) + JetBrains Mono (data/numerals, `tabular-nums`) |
+| Routes | `/`, `/product`, `/pricing`, `/docs` (+`/docs/api`), `/changelog`, `/login`; `/api/v1/*` handlers (not sitemap pages) |
+| Backend | Better Auth (email+password Growth, OIDC SSO Fleet) · Drizzle+Neon · versioned `GET /api/v1/berths` (zod, session-gated) · Stripe (single recurring price, Growth $490/mo only) |
+| Signature move | Live berth-utilization timeline in the hero — static SVG shell as LCP, live vessel positions stream in behind Suspense, no cache |
+
+## Studio Norra
+
+Oslo agency portfolio — raw editorial authority that still has to sell the studio's craft.
+
+| Field | Value |
+|---|---|
+| Archetype | **Editorial Brutalist** — exposed 12-col grid, oversized uppercase, deliberate rawness with high craft (`award-canon`, `gate-visual`, `layout-grid`, `motion-language`, `theme-worlds`) |
+| Ground | paper `oklch(0.96 0.005 90)` |
+| Ink | `oklch(0.2 0.01 270)` |
+| Accent | signal red `oklch(0.6 0.21 25)` — interaction states ONLY, never at rest |
+| Type pairing | Archivo Expanded (oversized uppercase display) + Inter (body) |
+| Routes | `/`, `/work`, `/work/[slug]`, `/studio`, `/contact` |
+| Backend | unset — no worked example cites a backend skill; first skill to use it decides, then records it here |
+| Signature move | Cursor-proximity case-study image reveals on `/work` — real `
` DOM, `clip-path` + `useSpring` pointer-follower, 0kb WebGL. `/work`→`/work/[slug]` shared-element spring transition. Per-case-study `--world-` accent worlds |
+
+## Casa Verde
+
+Lisbon farm-to-table restaurant, EN/PT bilingual, reservations-driven.
+
+| Field | Value |
+|---|---|
+| Archetype | **Sunlit Rustic** — full-bleed photography carries the emotion, chrome recedes (`imagery`, `wireframe`, `faq`) |
+| Ground | warm cream `oklch(0.97 0.01 85)` |
+| Ink | unset — no skill states a body-ink value; first skill to use it decides, then records it here |
+| Accent | terracotta `oklch(0.66 0.13 45)` |
+| Type pairing | Fraunces (italic on section h2 / success heading) + Karla (labels, UI, body) |
+| Routes | `/en`, `/en/menu`, `/en/story`, `/en/reservations`, mirrored at `/pt/*`; default locale `pt` |
+| Backend | `server-actions` (zod v4 `reservationSchema`, atomic seat claim) + Resend confirmation · `i18n` (zero-library, EN/PT dictionaries) · AI/RAG **rejected** — menu/hours change too fast |
+| Signature move | The day's-harvest strip — horizontally-scrolling row of today's market finds, full-bleed, above the menu preview (native swipe on mobile, static under reduced motion) |
+
+## Ledger & Lane
+
+Boutique two-partner DACH law firm — "Considered Counsel" / Quiet Authority.
+
+| Field | Value |
+|---|---|
+| Archetype | **Quiet Authority** — restraint as credibility; a ruled-line system structures every page like a legal document (`typography`, `retrofit`) |
+| Ground | warm paper `oklch(0.975 0.005 80)` |
+| Ink | ink navy `oklch(0.25 0.02 260)` |
+| Accent | muted gold `oklch(0.72 0.09 85)` — one-gold-per-page rule |
+| Type pairing | Newsreader (`opsz`, display + body, serif) + Public Sans (UI) — Archivo rejected, too grotesque |
+| Routes | `/`, `/practice/[area]`, `/attorneys`, `/insights/[slug]`, `/contact`, `/impressum`, `/mandat` (engagement letter) |
+| Backend | contact flow (implied `server-actions`); MDX `/insights` (`content-cms`, `BlogPosting` JSON-LD); `aiCrawlerPolicy: disallow` per UrhG §44b · AI/RAG **rejected** — legal liability |
+| Signature move | Ruled-line hairline system — full-width hairlines drawn in on scroll (footer's closer); H1 cap-height trimmed to the ruled baseline |
+| Market | German/DACH entity — Amtsgericht Charlottenburg HRB, USt-IdNr; cited as the DACH/BFSG and German-facing example elsewhere. (`footer`'s "NY & CT bar admissions" is the divergent outlier — see below.) |
+
+## Framewalk
+
+Indie game studio — Steam-launch marketing site for "Hollow Cartographer".
+
+| Field | Value |
+|---|---|
+| Archetype | **Atmospheric Dark** — earned by the game's fog-and-lantern art, not a template (`color`, `hero`, `buttons`, `physics`) |
+| Ground | `oklch(0.16 0.02 200)` (fog-tinted, deliberately not the banned AI-startup navy) |
+| Ink / foreground | `oklch(0.92 0.01 190)` · card `oklch(0.20 0.02 200)` · border `oklch(0.24 0.02 205)` · muted-fg `oklch(0.72 0.015 200)` |
+| Accent | phosphor `oklch(0.78 0.15 160)`, primary-fg `oklch(0.16 0.02 200)`; the ONLY filled button anywhere is "Wishlist on Steam" |
+| Type pairing | Space Grotesk (display) + Inter (UI/body), self-hosted via `next/font` |
+| Routes | `/`, `/game`, `/devlog`, `/devlog/[slug]`, `/press` |
+| Backend | `content-cms` (MDX devlog) · `server-actions`+`email` (launch-news capture) · **rejected**: `payments`, `database`/`auth` (no accounts) |
+| Signature move | Three-layer parallax fog in the hero answering the cursor (Trailing spring, stiffness 180/damping 18/mass 1); static composite under reduced motion. Console-signature + playable ASCII-compass `not-found.tsx` |
+| Related property | "Hollow Cartographer: Deepwater" — a separate launch microsite (`/`, `/deepwater`, `/bestiary`, `/bestiary/[slug]`, `/wishlist`), archetype **Art-House Immersive**, WebGL cave-descent scene (`set-design`). Distinct property, not a divergence of the site above. |
+
+## Aldermoor Trust
+
+Community foundation — grants, volunteer stories, donations (UK market).
+
+| Field | Value |
+|---|---|
+| Archetype | **Open Civic** — accessibility-first, all pairings AAA where possible (`gate-accessibility`, `scaffold`) |
+| Ground | warm paper `oklch(0.97 0.008 85)` |
+| Ink | `oklch(0.24 0.02 85)` |
+| Accent | deep green `oklch(0.45 0.1 155)` — AAA on body (13.6:1), 5.2:1 for the link role |
+| Type pairing | Source Serif 4 (prose/body); no display face named anywhere — unset, first skill to use it decides |
+| Routes | `/`, `/grants`, `/stories/[slug]`, `/volunteer`, `/donate`; long-form `/report/2025` |
+| Backend | `content-cms` — MDX via content-collections, volunteer-edited in-repo; headless CMS **rejected** (cadence/volume too low) · `/donate` payment flow: unset — no worked example specifies it yet |
+| Signature move | On `/`, story-card left rule growing into a reading-progress indicator on scroll (must rest visible under reduced motion). On `/report/2025`, Sidenote Gutter + Running Folio + green-tinted Read-o-meter |
+| Craft policy | Trust-critical brand — ships **no** hidden-craft gesture (paired with Ledger & Lane as the two opt-outs) |
+
+## Loop & Thread
+
+Small-batch handmade textiles shop — throws and runners, milled in Donegal, one-off stock.
+
+| Field | Value |
+|---|---|
+| Archetype | **Soft Craft** — tactility through generous radius, close-up photography, unhurried motion (`shape-language`) |
+| Ground | linen `oklch(0.94 0.012 80)` |
+| Ink | walnut `oklch(0.35 0.04 60)` |
+| Accent | indigo `oklch(0.45 0.08 265)` |
+| Type pairing | Fraunces (display) + Karla (body) — variable-axis, self-hosted |
+| Routes | `/`, `/shop`, `/products/[slug]` (e.g. `/products/aran-throw`), `/journal`, `/about` |
+| Backend | Stripe one-time checkout (`mode: 'payment'`, unique-constraint inventory claim) · Better Auth · Drizzle+Neon · Vercel Blob (client-direct review-photo upload, 8MB cap) · Resend |
+| Radius | Round band, shifted up one notch: sm 0.625rem · md 0.875rem · lg 1.25rem (cards) · xl 1.5rem (hero media). Motif: Arc & capsule |
+| Signature move | Flat-lay → in-hand crop hover crossfade (flat-lay is the LCP element and preloads; crop lazy-loads) |
+| Register | **Du** (informal German register), paired with Framewalk as the D2C/playful-register clients |
+
+---
+
+## Known divergences
+
+Outliers only — canonical values are the rows above. The four divergences found by the 2026-08-14 extraction (component-api Tidepool label, footer Ledger & Lane jurisdiction, product-detail Loop & Thread archetype/palette, assets Loop & Thread route) have since been fixed in place; the entries below stay as the audit trail.
+
+- **Kaffeewerk Ost — expected "Warm Workshop" divergence not found.** A repo-wide search (`grep -rin "workshop"`) found no skill naming the archetype "Warm Workshop" — every worked example (`SKILL.md`, `mockup`, `direction`, `cart`, `copywriting`, `identity`, `iterate`, `checkpoint`) uses **Warm Organic/Humanist**. `direction/SKILL.md:155` and `skills/studio/SKILL.md` use the plain word "workshop" only in unrelated prose. Noted for the audit trail; no correction needed.
+
+- **Tidepool — archetype label, minor.** `skills/component-api/SKILL.md:48` titles its example "**(Neo-grotesque Minimal)**" alone, dropping the "Precision Instrument" name every other citation carries (`command-palette/SKILL.md:110`: "Precision Instrument — Neo-grotesque Minimal, dark-first"). Canonical name: **Precision Instrument**.
+
+- **Ledger & Lane — jurisdiction conflict.** `skills/footer/SKILL.md:82` states "NY & CT bar admissions" (a US firm), conflicting with `print-craft/SKILL.md:107` (Amtsgericht Charlottenburg HRB, USt-IdNr), `seo/SKILL.md:157,173` (German-facing, `aiCrawlerPolicy` reasoned from UrhG §44b), and `gate-accessibility/SKILL.md:124` (cited as the DACH/BFSG example). Canonical: DACH/German entity; `footer/SKILL.md:82` is the divergent file.
+
+- **Loop & Thread — archetype name + palette.** `skills/product-detail/SKILL.md:117` names it **"Warm Editorial"** with madder accent `oklch(0.62 0.13 40)` on warm-paper `oklch(0.97 0.01 85)`. Every other citation (`shape-language/SKILL.md:97` — "Soft Craft"; `payments/SKILL.md:168`; `storage/SKILL.md:93` — linen + indigo `oklch(0.45 0.08 265)`; `social-proof/SKILL.md` — walnut/indigo class names) agrees on **Soft Craft** with the linen/walnut/indigo triad. `product-detail/SKILL.md:117` is the divergent file.
+
+- **Loop & Thread — route naming.** `skills/assets/SKILL.md:77` places galleries at `/shop/[slug]`; every other citation (`product-detail/SKILL.md:119`, `payments/SKILL.md:154`, `storage/SKILL.md:89`, `ship/SKILL.md:92`) uses `/products/[slug]`. Canonical: `/products/[slug]`; `assets/SKILL.md:77` is the divergent file.
diff --git a/README.md b/README.md
index 48b5d4b..c31427c 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,34 @@
# ultraweb
-[](https://code.claude.com/docs/en/plugins) [](.claude-plugin/plugin.json) [](ROSTER.md) [](https://ultraweb-site.vercel.app)
+[](https://code.claude.com/docs/en/plugins) [](.claude-plugin/plugin.json) [](ROSTER.md) [](https://ultraweb-site.vercel.app)
-*A Claude Code plugin for AI web design: a guided design session — a few sharp scoping questions, three fast mockups, your approval — then a production-grade Next.js 16 + Tailwind CSS v4 website out: design system, copywriting, motion, backend, SEO, and seven screenshot-verified quality gates.*
+*A Claude Code plugin for AI web design: a guided design session — a few sharp scoping questions, three fast mockups, your approval — then a production-grade Next.js 16 + Tailwind CSS v4 website: design system, brand mark, copywriting, motion, backend, SEO, and seven screenshot-verified quality gates. Sized by a scope dial, reviewable on your own phone, and resumable if life interrupts the build.*
## You hire a design studio. It fits in one session.
Somewhere in a nicer timeline there's a small agency that does this properly. An art director who refuses the purple gradient. A design engineer who ships tokens before components. A critic who screenshots your site at 375px and tells you the truth about it. And before any of them lift a pen, someone sits you down, asks the four questions that actually matter for *your* site, and shows you three sketches to point at. They cost forty thousand euros and they're booked until spring.
-**ultraweb is that studio, as a Claude Code plugin.** 75 skills and 3 subagents that argue with each other on your behalf until something good comes out the other end — a real Next.js site, built, judged, and fixed before you ever see it.
+**ultraweb is that studio, as a Claude Code plugin.** 80 skills and 3 subagents that argue with each other on your behalf until something good comes out the other end — a real Next.js site, built, judged, and fixed before you ever see it.
```text
/ultraweb build me a website for a Berlin specialty coffee roastery with an online shop
```
-Answer a short round of questions about scope (yours will be about subscriptions and checkout — someone else's would be about reservations or case studies), pick one of three mockups, say yes. *Then* go make coffee yourself. It'll be a while.
+Answer a short round of questions about scope (yours will be about subscriptions and checkout — someone else's would be about reservations or case studies), pick one of three mockups, say yes. Then leave. The build tells you — in writing, in `design/PROGRESS.md` — exactly when it will next need you and for how many minutes.
Prefer the classic fire-and-forget? Say **"just build it, no questions"** and the studio decides everything itself, logging each assumption so you can correct it afterward.
----
+## Where it sits
+
+v0, Lovable, and Bolt hand you a page in ninety seconds; plain Claude Code will happily improvise a site from vibes. ultraweb is for the build where that isn't enough: it is the only tool in this row with a **written taste constitution**, an **adversarial critic that screenshots the result and scores it before you see it**, and a **paper trail** (`design/*.md`) that makes every decision inspectable and every future change surgical. The trade is honest: it is slower and it burns more tokens, because verification is the product. Use a prototype tool to explore an idea; use ultraweb to ship the site you'll defend.
+
+And it doesn't force the full price on a small ask: say "landing page" or "quick" and the **sketch tier** runs a thinner pipeline — fewer mockups, one-page structure, single-round gates — at a fraction of the cost. `standard` is the default; `flagship` unlocks the 3D/showpiece budget and a five-round visual critique.
## See it defend itself
**[ultraweb-site.vercel.app](https://ultraweb-site.vercel.app)** — built by this pipeline, from one prompt, with no human touch-ups.
-The whole paper trail is public at [blyatiful1/ultraweb-site](https://github.com/blyatiful1/ultraweb-site): every decision the studio made on the way (brief → direction → system → sitemap → QA), the 58/72 skill-coverage ledger from that build (the harness has since grown to 75, and that run predates the guided session — it was a classic autonomous build), and each gate's receipts. The homepage renders its own report card. If the site were bad, you'd be able to prove it from the repo.
+The whole paper trail is public at [blyatiful1/ultraweb-site](https://github.com/blyatiful1/ultraweb-site): every decision the studio made on the way (brief → direction → system → sitemap → QA), the skill-coverage ledger from that build (58 of the 72 that existed then; the harness has since grown to 80, and that run predates the guided session — it was a classic autonomous build), and each gate's receipts. The homepage renders its own report card. If the site were bad, you'd be able to prove it from the repo.
### The build, measured
@@ -41,17 +45,11 @@ Every number below was counted from the session transcripts of that build — th
| Cache reads | 733.0 million tokens |
| Total tokens processed | 756.0 million |
| Quality gates | 7 of 7 green — after three fix rounds, not on the first try |
-| Skill coverage | 58 of 72 (80.6%) at build time, all 14 exclusions recorded with reasons |
| Lighthouse, mobile, production | 93 performance · 94 accessibility · 94 best practices · 94 SEO |
| Cumulative layout shift | 0.00 |
| Commissioned animation weight | +22.9 KB gzip measured, against a ~23 KB budget set in writing before it was built |
-Read the table twice. Once as an advertisement: one prompt became a gated, documented, production site in a working day. Once as a warning: it took three quarters of a billion processed tokens to get there. Both readings are correct, and both are the point.
-
----
-
-> [!IMPORTANT]
-> **This thing is expensive.** A full build runs twelve phases and loops through screenshot-driven critique until it stops finding problems. It burns far more tokens, and far more minutes, than a normal prompt — the table above is what one full run actually looks like. That's not inefficiency; that's the part that makes it good. [The honest math is below.](#the-bill)
+Read the table twice. Once as an advertisement: one prompt became a gated, documented, production site in a working day. Once as a warning: that was a full-fat `standard` build at maximum thoroughness — the bill is real, and [the honest math is below](#the-bill). Since that run, the harness has grown context discipline, progressive skill loading, and the sketch tier, all aimed at the cost side of this table.
## Getting it
@@ -64,72 +62,82 @@ Read the table twice. Once as an advertisement: one prompt became a gated, docum
Confirm the dialog, run `/reload-plugins`, done. Later: `/plugin marketplace update ultraweb`.
-**Or by hand,** straight into your skills folder:
+**Working offline or from a fork?** Clone it anywhere and add the clone as a local marketplace — same namespacing, same 80 skills:
```bash
-git clone https://github.com/blyatiful1/ultraweb.git ~/.claude/skills/ultraweb
+git clone https://github.com/blyatiful1/ultraweb.git ~/src/ultraweb
```
-Loads itself next session as `ultraweb@skills-dir`. Update with `git pull`, uninstall with `rm -rf`.
+```text
+/plugin marketplace add ~/src/ultraweb
+/plugin install ultraweb@ultraweb
+```
+
+(Don't clone straight into `~/.claude/skills/` — a nested skills tree loads the orchestrator without its 79 specialists, which fails quietly as vague output. The pipeline's own preflight now catches that half-install and says so, but the marketplace path is the one that just works.)
-**Did it work?** `/plugin` lists ultraweb, and `/ultraweb` answers when called.
+**Did it work?** `/plugin` lists ultraweb, and `/ultraweb` answers when called. The build's own Phase 0 preflight re-verifies the install, the toolchain, and Playwright before any expensive work starts.
## Using it
One sentence about what you want — then a short conversation instead of a leap of faith.
-**First, the interview.** The pipeline reads your sentence, works out what kind of site it is, and asks up to four multiple-choice questions about the forks it can't safely guess — scope, features, audience, content. The questions are generated from *your* prompt, not a fixed form: a shop gets asked about subscriptions, a restaurant about reservations, a portfolio about case-study depth. Never about colors or fonts — you shouldn't have to describe taste in words.
+**First, the interview.** The pipeline reads your sentence, works out what kind of site it is, and asks up to four multiple-choice questions about the forks it can't safely guess — scope, features, audience, content. The questions are generated from *your* prompt, not a fixed form; every one carries a "skip — you decide" default, so answering is never homework. Never about colors or fonts — you shouldn't have to describe taste in words. **Have a logo, photos, copy, a brand guide?** Point at the folder: the `assets` skill inventories what exists and the pipeline builds *with* your brand instead of inventing one over it.
-**Then, the mockups.** Three deliberately different directions, each rendered fast as a self-contained static HTML preview — real palette, real type pairing, your copy sketched in, no build step. You pick one, mix elements ("the warm one, but with B's grid"), or send the round back. Nothing expensive happens until you say yes; your approval is written into `design/MOCKUPS.md` and is literally the gate the build waits behind.
+**Then, the mockups.** Three deliberately different directions, rendered fast as self-contained static HTML — real palette, real type pairing, your copy sketched in — plus a one-tab contact sheet so you compare by scrolling, not by juggling tabs. Prefer one decision at a time? Say so and it runs as a **tournament**: A against B, winner against C. You pick, mix elements ("the warm one, but with B's grid"), or send the round back. Nothing expensive happens until you say yes; your approval is written into `design/MOCKUPS.md` and is literally the gate the build waits behind. The moment you approve, you get the **session map**: which phases run next, where the next checkpoint falls, and how many minutes of your time it will want.
-**Then, the build — with you in the loop where it counts.** From your approved direction, the pipeline runs: design system → pages → scaffold → build → backend → copy → motion → findability → gates → ship. Two more checkpoints are on by default, placed where real studios place client reviews: the **first-page review** — the homepage is built completely first and shown to you at phone and desktop widths, so the whole design system gets your sign-off on one real page before it's rolled across every other page — and **preflight/UAT** — after all seven gates are green (you're the acceptance test, never the first QA), you get the gate report, per-page screenshots, and a what-to-click list, and nothing ships until you accept. Every verdict is logged near-verbatim in `design/REVIEWS.md`; feedback runs in consolidated rounds (two per checkpoint, industry standard) and routes through the owning skill so a color complaint fixes the token, not one component.
+**Then, the build — with you in the loop where it counts.** From your approved direction: design system (including your site's own **brand mark** — wordmark, favicon, OG template, drawn from the committed typeface) → pages → scaffold → build → backend → copy → motion → findability → gates → ship. Two more checkpoints are on by default, placed where real studios place client reviews: the **first-page review** — the homepage is built completely first and shown to you at phone and desktop widths, with a throwaway **preview URL** so you check it on your actual phone, not PNGs of localhost — and **preflight/UAT** — after all seven gates are green (you're the acceptance test, never the first QA), you get the gate report, per-page screenshots, a fresh preview URL, and a what-to-click list. Every verdict is logged near-verbatim in `design/REVIEWS.md`; feedback routes through the owning skill so a color complaint fixes the token, not one component.
-**Want more or less of that?** It's a dial. Say *"walk me through it"* and you get the full studio cadence — brief read-back, structure sign-off, and voice review join the default three. Say **"just build it" / "no questions"** for the original hands-off autonomous mode (also the automatic fallback when nobody's around to answer, e.g. scheduled runs — a checkpoint never deadlocks a build; it auto-passes and logs it). Every phase leaves a written record in `design/*.md` inside your project, which is how 75 skills manage to agree with each other three hours later; the mockup files stay behind as reference — the site is re-derived from the decisions, never copy-pasted from a sketch.
+**While it builds, you can watch.** The dev server the pipeline already runs serves a dev-only `/studio` route: phase progress, the gate table going green, the checkpoint ledger, a contact sheet of every screenshot — live off the files the build writes, at zero token cost, and hard-wired to 404 in production. And `design/PROGRESS.md` always answers "where are we, are you waiting on me, when do you need me next" — ask "status" any time, or just read the file.
+
+**If life interrupts the build** — laptop closed, session died — nothing is lost: the artifacts are the memory, the pipeline commits at every phase boundary, and a new session in the same directory resumes from the record, re-presenting any checkpoint you were mid-answer on. It never re-runs a phase you already paid for.
+
+**Want more or less of that?** Both dials are yours, any time. Say *"walk me through it"* for the full studio cadence, **"just build it"** for hands-off autonomous mode (also the automatic fallback when nobody's around to answer — a checkpoint never deadlocks a build), *"stop asking me"* mid-build to move the dial down. Every phase leaves a written record in `design/*.md`, which is how 80 skills manage to agree with each other three hours later.
**Already have an ultraweb site?** Just say what's wrong — *"the hero's too timid"* — and `ultraweb:iterate` scopes the change and re-runs only the gates you actually disturbed.
-**Have some other site?** `ultraweb:retrofit` reads it and hands back a scored, unflattering gap report.
+**Have some other site?** `ultraweb:retrofit` is also the zero-risk first taste: point it at any Next.js site and it hands back a scored, unflattering gap report — no build, no big bill, and every gap names the skill that would fix it.
+
+**Built a few sites?** The studio remembers. Mockup verdicts and review feedback accrue into a **taste fingerprint** (`~/.claude/ultraweb/taste.md`) that breaks ties in your favor on the next build — with a mandatory "heretic seat" in every mockup round arguing against your profile, so it stays a preference, never a rut. Last 10 builds only; taste drifts.
## The bill
-The showcase table above is the receipt: 4,753 API calls, 2.78 million tokens generated, 756 million tokens processed, six hours and six minutes. A full `/ultraweb` run is a studio engagement, not an API call, and here is where those numbers come from:
+The showcase table above is the receipt for a maximum-thoroughness `standard` build: 4,753 API calls, 2.78 million tokens generated, 756 million tokens processed, six hours and six minutes. A full `/ultraweb` run is a studio engagement, not an API call. Where the money goes:
-- **twelve phases**, each one actually loading and following its skills — not vibing them from memory;
-- **an accumulating context** — every phase writes artifacts the later phases read back, so the working set grows as the build does. This is why 96.9 percent of all processed tokens were cache reads: the paper trail gets re-read on nearly every call. Prompt caching prices those far below fresh input, and the expensive habit doubles as the quality mechanism — a pipeline that re-reads its own decisions can't drift;
-- **quality gates that loop** — screenshots at 375 / 768 / 1440, scored against a rubric, fix → re-gate → fix again until it goes green. The showcase needed three rounds;
+- **twelve phases**, each one actually loading and following its skills — now split into decision cores with reference files loaded only on need, under a written context discipline (each artifact read once per context) that targets the single largest line in that receipt: the ~154K-token resident prefix dragged through every call;
+- **quality gates that loop** — screenshots at 375 / 768 / 1440, scored against a rubric, fix → re-gate → fix again until it goes green. The showcase needed three rounds. Hooks now kill banned-list slop at write time — thirty milliseconds of grep instead of an Opus fix round later;
- **and in fan-out mode**, an agent per page group and an agent per gate — 37 of them in the showcase build.
-Model routing keeps it as honest as it can — mechanical sweeps drop to Sonnet 5, judgment stays up on Opus 5 — but cheaper per call is not the same as cheap. It is still, plainly, an order of magnitude beyond an ordinary Claude Code task. Plan for it.
+Model routing keeps it as honest as it can — mechanical sweeps drop to Sonnet 5, judgment stays up on Opus 5 — but cheaper per call is not the same as cheap. Rough sizing: a **sketch**-tier landing page should land an order of magnitude under the showcase numbers; **standard** is the receipt above as the ceiling for a comparable site; **flagship** buys more critique rounds and the 3D budget on top. Plan for it.
-**How to spend less:** build once, then talk to it. Full runs are for new sites and total redesigns. Everything after that is `iterate`, which touches only what your change touched. The interview and mockup round are the cheap part by design — a handful of questions and three static HTML sketches — and they exist precisely so the expensive part runs once, at a direction you already approved, instead of twice because the first guess was wrong.
+**How to spend less:** say what the job really is ("landing page" → sketch tier), build once, then talk to it. Full runs are for new sites and total redesigns; everything after that is `iterate`, which touches only what your change touched. The interview and mockup round are the cheap part by design — they exist precisely so the expensive part runs once, at a direction you already approved, instead of twice because the first guess was wrong.
## Why the output isn't slop
Four things do most of the work:
-**`taste` — the constitution.** A banned list (no purple AI gradient, no untouched shadcn, no "Empower your workflow" copy), a required list (OKLCH palette, a real type pairing, deliberate asymmetry, honored reduced-motion), and the heuristics for deciding everything in between. Every other skill bows to it.
+**`taste` — the constitution.** A banned list (no purple AI gradient, no untouched shadcn, no "Empower your workflow" copy), a required list (OKLCH palette, a real type pairing, deliberate asymmetry, honored reduced-motion), and the heuristics for deciding everything in between. Every other skill bows to it — and a plugin hook now enforces the greppable half of it at write time, on every file, before it can ship.
**`award-canon` — the library.** 32 Awwwards Site-of-the-Year and SOTD-tier winners from 2017 to 2026, studied and rendered down into 25 named, transferable patterns — plus the invariants that survived every era, the jury's own scoring weights, and a list of moves that have visibly aged. Each claim carries its verified award tier; dead sites are marked *reconstructed*, never passed off as inspected. The prime directive: **steal the principle, never the surface.**
-**Seven gates that don't take your word for it.** Code, responsive, visual, accessibility, performance, anti-slop, content — each verified empirically. Real builds. Real Playwright screenshots. Computed contrast. Lighthouse. The site isn't finished until `design/QA.md` is green, and nothing is allowed to fake green.
+**Seven gates that don't take your word for it.** Code, responsive, visual, accessibility, performance, anti-slop, content — each verified empirically. Real builds. Real Playwright screenshots. Computed contrast. Lighthouse. The site isn't finished until `design/QA.md` is green, and nothing is allowed to fake green — including faking it when the tools are missing: no browser means an honest **UNVERIFIED**, declared in Phase 0 before the build spends a cent, never a quiet wave-through discovered five hours in.
-**`STACK.md` — the reality check.** Stack facts checked against live npm and official docs rather than training memory, so skills cite Next 16's `proxy.ts` and `preload`, Tailwind v4's `@theme`, Motion 12's `motion/react`. When the ecosystem moves, one file moves.
+**`STACK.md` — the reality check.** Stack facts checked against live npm and official docs rather than training memory, so skills cite Next 16's `proxy.ts` and `preload`, Tailwind v4's `@theme`, Motion 12's `motion/react`. Version pins live in `stack/versions.json` with a 30-day expiry and a refresh script; corpus invariants are enforced by a lint script in CI, not by memory.
-And underneath all of it: nearly every skill ends with a real decision traced end to end, drawn from a recurring cast of eight fictional clients — a Berlin roastery, a port-logistics SaaS, an Oslo agency, a Lisbon restaurant, a law firm, a game studio, a foundation, a textiles shop. Skills sharing a client agree on its palette, its type, its routes. The examples don't just illustrate the skills; they demonstrate the handoff between them.
+And underneath all of it: nearly every skill ends with a real decision traced end to end, drawn from a recurring cast of eight fictional clients whose canonical facts live in [CAST.md](CAST.md) — a Berlin roastery, a port-logistics SaaS, an Oslo agency, a Lisbon restaurant, a law firm, a game studio, a foundation, a textiles shop. Skills sharing a client agree on its palette, its type, its routes. The examples don't just illustrate the skills; they demonstrate the handoff between them.
## The studio floor
| Department | Who's in it |
|------|--------|
-| **Direction** | `ultraweb` (the pipeline itself), `taste`, `iterate`, `award-canon`, `checkpoint` (the client-review cadence) |
-| **Discovery** | `brief` (with the guided scoping interview), `direction` (12 archetypes), `mockup` (the pick/mix/revise round), `sitemap`, `wireframe`, `copywriting` |
-| **Design system** | `tokens`, `color`, `typography`, `layout-grid`, `depth`, `shape-language`, `icons`, `imagery`, `motion-language`, `theme-worlds` |
+| **Direction** | `ultraweb` (the pipeline itself), `taste`, `iterate`, `award-canon`, `checkpoint` (the client-review cadence), `status` (progress + resume) |
+| **Discovery** | `brief` (with the guided scoping interview), `assets` (client-material intake), `direction` (12 archetypes), `mockup` (the pick/mix/revise round, tournament optional), `sitemap`, `wireframe`, `copywriting` |
+| **Design system** | `tokens`, `color`, `typography`, `identity` (the brand mark), `layout-grid`, `depth`, `shape-language`, `icons`, `imagery`, `motion-language`, `theme-worlds` |
| **Components** | `component-api`, `hero`, `navigation`, `footer`, `feature-sections`, `cards`, `buttons`, `forms`, `data-display`, `pricing`, `social-proof`, `faq`, `ui-states`, `overlays`, `cart`, `product-detail`, `command-palette`, `marginalia` |
| **Motion** | `micro-interactions`, `scroll-motion`, `page-transitions`, `physics`, `showpiece`, `set-design`, `animejs`, `hidden-craft` |
-| **Engineering** | `scaffold`, `app-structure`, `routing`, `data-fetching`, `server-actions`, `media-optimization`, `seo`, `i18n`, `print-craft` |
+| **Engineering** | `scaffold`, `app-structure`, `routing`, `data-fetching`, `server-actions`, `media-optimization`, `seo`, `i18n`, `print-craft`, `studio` (the live build dashboard) |
| **Backend** | `api-design`, `database`, `auth`, `email`, `payments`, `content-cms`, `storage`, `consent`, `analytics` |
| **QA** | `gate-code`, `gate-responsive`, `gate-visual`, `gate-accessibility`, `gate-performance`, `gate-antislop`, `gate-content` |
-| **Delivery** | `ship`, `handoff`, `retrofit` |
+| **Delivery** | `preview` (review URLs at the checkpoints), `ship`, `handoff`, `retrofit` |
Three specialists work outside the main line, each pinned to its own model tier:
@@ -139,14 +147,15 @@ Three specialists work outside the main line, each pinned to its own model tier:
The same policy governs all fan-out work: judgment stays on the lead model, specialist builds and critiques on Opus 5, mechanical sweeps on Sonnet 5.
-Want the full scope of all 75? → [ROSTER.md](ROSTER.md). Want the per-site award study bank? → [skills/award-canon/CANON.md](skills/award-canon/CANON.md).
+Want the full scope of all 80? → [ROSTER.md](ROSTER.md). Want the per-site award study bank? → [skills/award-canon/references/CANON.md](skills/award-canon/references/CANON.md). The shared client cast? → [CAST.md](CAST.md).
## What you need
- **Claude Code** — CLI, desktop, or web.
- **Node + npm** — something has to build the Next.js app.
-- **Playwright MCP** — the eyes. Without it, the visual and responsive gates degrade to an honest *"unverified"* instead of quietly waving your site through.
-- **Token headroom** — see [the bill](#the-bill). The showcase run processed 756 million tokens in six hours. Start it when you have room for it.
+- **Playwright MCP** — the eyes. Without it, the visual, responsive, and accessibility gates record an honest *UNVERIFIED* instead of quietly waving your site through — and the Phase 0 preflight tells you so before the build starts, not five hours in.
+- **Vercel CLI auth** *(optional)* — enables the preview URLs at the review checkpoints. Without it, you review screenshots; nothing blocks.
+- **Token headroom** — see [the bill](#the-bill). Start a `standard` build when you have room for it; say "landing page" when you don't.
---
diff --git a/ROSTER.md b/ROSTER.md
index 365321a..25e401f 100644
--- a/ROSTER.md
+++ b/ROSTER.md
@@ -1,19 +1,23 @@
# ultraweb skill roster
-The complete map of the harness. 75 skills: 1 orchestrator (root `SKILL.md`) + 74 specialist skills in `skills//SKILL.md`. Nearly every skill reads `design/*` artifacts produced upstream and serves the pipeline defined in the root skill (the core references — `taste`, `award-canon` — supply judgment instead of consuming artifacts). `taste` is the constitution; every skill defers to it.
+The complete map of the harness. 80 skills: 1 orchestrator (root `SKILL.md`) + 79 specialist skills in `skills//SKILL.md`. Nearly every skill reads `design/*` artifacts produced upstream and serves the pipeline defined in the root skill (the core references — `taste`, `award-canon` — supply judgment instead of consuming artifacts). `taste` is the constitution; every skill defers to it.
Format: **name** — scope. *(reads → writes)*
-Most skills close with a **Worked example** traced from a shared bank of eight recurring clients — Kaffeewerk Ost (roastery e-commerce), Tidepool (B2B SaaS), Studio Norra (agency portfolio), Casa Verde (restaurant, EN/PT), Ledger & Lane (law firm), Framewalk (game studio), Aldermoor Trust (foundation), Loop & Thread (textiles shop). Skills sharing a client agree on its canonical palette, type, and routes, so reading two related skills shows the same project from both sides of the handoff.
+Most skills close with a **Worked example** traced from a shared bank of eight recurring clients — Kaffeewerk Ost (roastery e-commerce), Tidepool (B2B SaaS), Studio Norra (agency portfolio), Casa Verde (restaurant, EN/PT), Ledger & Lane (law firm), Framewalk (game studio), Aldermoor Trust (foundation), Loop & Thread (textiles shop). Their canonical facts — palette values, type pairing, routes, signature move — live in [CAST.md](CAST.md); a worked example that uses a value must match that sheet or amend it. Skills sharing a client agree on its canon, so reading two related skills shows the same project from both sides of the handoff.
+
+**Progressive disclosure:** a skill's SKILL.md is its decision core; worked examples, compose maps, and catalogs above ~12KB live in `skills//references/` and load only when a build genuinely needs them (`example.md`, `composes.md`; award-canon also carries `ARCHETYPE-MAP.md`, `INVARIANTS.md`, `CANON.md`). New skills follow the same split. Corpus invariants are enforced by `scripts/lint-skills.mjs` (run by CI on every push); stack pins live in `stack/versions.json`, refreshed by `scripts/verify-stack.mjs`.
## Tier 0 — Core
- **taste** — the design constitution: first-grade bar, banned list, required list, heuristics, stack lock. *(— → judgment)*
- **iterate** — targeted revision pipeline for an existing ultraweb site: locate the design/* artifacts, scope the change, touch only affected phases, re-run only affected gates. *(design/* → changed code + QA.md)*
- **award-canon** — the study library: 25 named patterns + per-site bank distilled from Awwwards Site-of-the-Year/SOTD-tier winners 2017-2026; direction consults it for references, design-judge scores against its invariants. *(— → judgment + reference)*
-- **checkpoint** — the client-review mechanic on a real-studio cadence: six named checkpoints (brief read-back, direction approval, structure sign-off, first-page review, voice review, preflight/UAT), consolidated-feedback round discipline (two rounds, then escalate upstream), verdict routing to owning skills, written approvals; which ones block is the engagement level's call (hands-off / guided / studio), and unattended sessions auto-pass instead of stalling. *(phase artifacts → design/REVIEWS.md + routed verdicts)*
+- **checkpoint** — the client-review mechanic on a real-studio cadence: six named checkpoints (brief read-back, direction approval, structure sign-off, first-page review, voice review, preflight/UAT), each with an honest "your time" estimate, consolidated-feedback round discipline (two rounds, then escalate upstream), verdict routing to owning skills, written approvals; which ones block is the engagement level's call (hands-off / guided / studio), and unattended sessions auto-pass instead of stalling. *(phase artifacts → design/REVIEWS.md + routed verdicts)*
+- **status** — the "where are we" surface: design/PROGRESS.md rewritten at every phase boundary and checkpoint transition — current phase, waiting-on-you, next-checkpoint ETA + effort, per-phase durations; answers "is it still going" from the file, never from transcript, and anchors the root skill's resume ladder. *(phase transitions → design/PROGRESS.md)*
## Tier 1 — Discovery
- **brief** — expand one prompt into a full creative brief: site type, audience, goals, tone words, page list, content inventory, backend needs. Guided mode runs a short scoping interview generated from the prompt's open forks (scope and substance only, never aesthetics); autonomous mode decides everything, interviewing nobody. *(user prompt + interview answers → design/BRIEF.md)*
+- **assets** — client-asset intake, run whenever the user names existing material: classify what exists (vector/raster logo, photography, copy docs, brand guide, fonts), map each asset to its consuming slot, extract constraints (sampled OKLCH as candidates, licensable typefaces, logo clear-space) — downstream skills read it before inventing, and "No client assets provided" is an explicit recorded line. *(named files/folders → design/ASSETS.md)*
- **direction** — choose ONE aesthetic archetype from a catalog of 12 named directions (each with type/color/motion stance, when-to-use, signature-move ideas) + ONE signature move + an explicit "we will not" list. Guided mode commits whatever the mockup round's Approved line names. *(BRIEF.md + MOCKUPS.md → design/DIRECTION.md)*
- **mockup** — guided mode's Phase 2 round: renders the three shortlisted archetypes as fast, throwaway, self-contained static HTML previews (hero + 2–3 decision-carrying sections, real OKLCH palette and type, sketched copy), runs the pick/mix/revise loop, and logs each round plus the Approved line that green-lights the build. The build never copies mockup markup. *(BRIEF.md + shortlist → design/mockups/*.html + design/MOCKUPS.md)*
- **sitemap** — information architecture: pages, routes, nav structure, per-page purpose and conversion goal. *(BRIEF.md → design/SITEMAP.md part 1)*
@@ -31,6 +35,7 @@ Most skills close with a **Worked example** traced from a shared bank of eight r
- **imagery** — art direction for images: photo treatment (duotone/grain/overlay), gradient meshes, noise textures, SVG patterns, honest placeholder strategy (generated, on-brand — never gray boxes or stock-photo-cliché). *(DIRECTION.md → SYSTEM.md §imagery + assets)*
- **motion-language** — the motion vocabulary: duration/easing token set, choreography rules (what animates, in what order, what never animates), reduced-motion policy. *(DIRECTION.md → SYSTEM.md §motion)*
- **theme-worlds** — scoped multi-theme "worlds" beyond light/dark: per-route or per-case-study accent worlds via native CSS `@scope` + `data-world`/`data-mode` token re-mapping (no ThemeProvider); every world AA-verified, dark mode re-decided per world. *(DIRECTION.md → scoped @theme overrides)*
+- **identity** — the brand-mark producer the header was waiting for: wordmark set in the committed display face (optical work named), monogram feeding icon.tsx/apple-icon/manifest, the OG template component seo consumes, clear-space and misuse rules; formalizes a client-supplied mark from ASSETS.md, never redraws a real brand. *(DIRECTION+SYSTEM §type → design/IDENTITY.md + public/brand/*.svg)*
## Tier 3 — Components (each: quality bar, 3+ named layout variants, anti-patterns, states, a11y notes)
- **hero** — first-viewport sections: variants (typographic, split, full-bleed media, product-shot, editorial), headline scale, CTA hierarchy, above-fold performance. *(DIRECTION+SYSTEM+SITEMAP → components/sections/hero.tsx)*
@@ -72,6 +77,7 @@ Most skills close with a **Worked example** traced from a shared bank of eight r
- **seo** — Metadata API per route, generateMetadata, ImageResponse OG images, sitemap.ts/robots.ts, JSON-LD structured data, canonical/i18n alternates. *(BRIEF+copy → metadata layer)*
- **i18n** — internationalization when the brief needs it: locale routing strategy, dictionary pattern, hreflang, date/number formatting, RTL awareness. *(BRIEF → i18n layer)*
- **print-craft** — the print stylesheet (`@media print`) as a designed surface: chrome-hiding reset, page-break control, `@page` margins, ink economy; a real DACH angle for print-to-PDF Impressum/AGB/Datenschutz and invoices. *(SYSTEM → print layer)*
+- **studio** — the dev-only `/studio` construction-site route: phase progress, gate table, checkpoint ledger, screenshot contact sheet, and the hook-written activity feed, all read off disk at request time — zero tokens per refresh; hard-gated to 404 in production (ship verifies it). *(design/*.md + studio-log.jsonl → app/(studio)/studio/*)*
## Tier 6 — Backend
- **api-design** — route handlers: REST shape, typed responses, zod-validated input, error envelope convention, status codes, rate-limit hook points. *(BRIEF → app/api/*)*
@@ -94,7 +100,8 @@ Most skills close with a **Worked example** traced from a shared bank of eight r
- **gate-content** — copy and metadata completeness: every page has real title/description, no dead copy patterns, headings tell a story when read alone, links resolve. *(code → QA.md)*
## Tier 8 — Ship
-- **ship** — production readiness: env var audit, build + start smoke test, deploy (Vercel when asked), post-deploy verification of live URL. *(green QA.md → live site)*
+- **preview** — non-production review URLs at CP4 and CP6 (and on request): `vercel deploy` with no `--prod`, preconditioned only on a clean build + secret scan, noindex headers on non-production, the URL logged beside the checkpoint's screenshots; production deploys stay ship's. *(built homepage / green gates → throwaway preview URL in REVIEWS.md)*
+- **ship** — production readiness: env var audit, build + start smoke test (including /studio 404), deploy (Vercel when asked), post-deploy verification of live URL. *(green QA.md → live site)*
- **handoff** — closing docs: README with stack map, how to edit content/tokens, design/* artifacts explained, maintenance notes. *(everything → README.md)*
- **retrofit** — entry point for existing sites: audit any Next.js site against the constitution, produce a scored gap report and a phased upgrade plan mapping each gap to the ultraweb skill that fixes it. *(existing code → design/RETROFIT.md)*
diff --git a/SKILL.md b/SKILL.md
index 41332f9..f1058af 100644
--- a/SKILL.md
+++ b/SKILL.md
@@ -1,6 +1,6 @@
---
name: ultraweb
-description: Build a complete, first-grade Next.js website through a guided design session — a short scoping interview tailored to what the site is, three fast mockup candidates the user picks from, then the full pipeline from design system through components, copy, motion, backend, and quality gates to a shippable site, with human-in-the-loop checkpoints on a studio cadence (first-page review on the built homepage, preflight/UAT acceptance before ship; a dial from hands-off to full sign-off-at-every-milestone). Use when the user asks to build, create, or make a website, landing page, marketing site, portfolio, or web app ("build me a site for X", "create a landing page", "make a website"), or asks for a full redesign. "Just build it" / "no questions" runs the classic one-prompt autonomous mode instead. For targeted changes to an existing site use ultraweb:iterate; for judging an existing site use ultraweb:retrofit.
+description: Build a complete, first-grade Next.js website through a guided design session — a short scoping interview tailored to what the site is, three fast mockup candidates the user picks from, then the full pipeline from design system through components, copy, motion, backend, and quality gates to a shippable site, with human-in-the-loop checkpoints on a studio cadence (first-page review on the built homepage, preflight/UAT acceptance before ship; a dial from hands-off to full sign-off-at-every-milestone) and a scope dial (sketch / standard / flagship) that sizes the pipeline to the ask. Use when the user asks to build, create, or make a website, landing page, marketing site, portfolio, or web app ("build me a site for X", "create a landing page", "make a website"), or asks for a full redesign — and when a previous build was interrupted and should continue ("continue the build", "resume", "where were we": see §Resuming). "Just build it" / "no questions" runs the classic one-prompt autonomous mode instead. For targeted changes to an existing site use ultraweb:iterate; for judging an existing site use ultraweb:retrofit.
---
# ultraweb — one guided session → first-grade website
@@ -19,7 +19,19 @@ How much the user is in the loop is a dial, not a switch — set once, from thei
| **guided** (default) | saying nothing either way | CP2 direction approval (the mockup round) · CP4 first-page review · CP6 preflight/UAT |
| **studio** | "walk me through it", "check in with me at every step", "I want sign-off" | all six: CP1 brief read-back · CP2 · CP3 structure sign-off · CP4 · CP5 voice review · CP6 |
-The cadence copies where real studios put client involvement: heavy at discovery and design approval, thin during production, back for acceptance. A checkpoint that cannot reach a human auto-passes with a logged `Auto-passed (unattended)` — human-in-the-loop upgrades quality, it never deadlocks a build.
+The cadence copies where real studios put client involvement: heavy at discovery and design approval, thin during production, back for acceptance. A checkpoint that cannot reach a human auto-passes with a logged `Auto-passed (unattended)` — human-in-the-loop upgrades quality, it never deadlocks a build. **The dial is re-settable mid-build**: "stop asking me" or "check with me more" at any point moves the level, logged as a new line in REVIEWS.md — the user never has to finish at the involvement they started with. And "unattended" is a session property, never a patience judgment: an interactive session with a slow human WAITS at a checkpoint; only a session that cannot ask (scheduled run, CI, non-interactive) auto-passes.
+
+## Scope tiers
+
+A second dial, independent of engagement: how much pipeline the ask deserves. Set once from the user's own words, recorded next to the engagement level at the top of `design/REVIEWS.md` and in `design/PROGRESS.md`. A one-page landing site must not pay a twelve-phase price.
+
+| Tier | Set by | What changes |
+|------|--------|--------------|
+| **sketch** | "landing page", "one-pager", "quick", "cheap", "rough" | Two mockup candidates, not three · Phase 4 collapses into the brief (one page, sections listed there) · Phases 7 and 10's i18n/print skipped unless the brief demands them · `gate-visual` capped at one judge round, `gate-performance` homepage-only · solo mode forced · no `references/` files loaded |
+| **standard** (default) | saying nothing either way | the full pipeline as written below |
+| **flagship** | "go all out", "best possible", "award-worthy", "money no object" | standard + eligibility for `showpiece`/`set-design` commissions + `gate-visual` ceiling raised to five rounds |
+
+Gates weaken only where a tier row says so, in writing — a sketch build still refuses slop, still passes `gate-code` and `gate-antislop` in full. When a sketch-tier site later grows ("now add a shop"), that is `ultraweb:iterate` at standard depth for the new surface, not a rebuild.
**Before anything else: invoke `ultraweb:taste`.** It is the constitution — every decision in this pipeline is subordinate to it.
@@ -38,43 +50,63 @@ A site is first-grade when ALL of these hold — verified, not assumed:
## Artifact contract
-Every phase writes its decisions to files in the generated project. Later phases READ these — this is how 75 skills stay coherent. Never skip an artifact.
+Every phase writes its decisions to files. Later phases READ these — this is how 80 skills stay coherent. Never skip an artifact.
+
+**Where `design/` lives: at the project root.** Phases 0–4 run before the app exists, so they write to `./design/` in the working directory; `scaffold` step 2 then inits the app subdirectory and **immediately moves `design/` into it** (recorded in PROGRESS.md), then verifies `design/BRIEF.md` resolves from the new project root before any further step. A scaffold that orphans the artifacts is a defect — they contain the only irreplaceable output of the build: the human's answers and approvals.
| File | Written by | Contains |
|------|-----------|----------|
+| `design/PROGRESS.md` | status (rewritten by the Lead at every phase boundary and checkpoint open/close) | Current phase, what's running, whether the build is waiting on the human, next checkpoint ETA + effort, per-phase durations |
| `design/BRIEF.md` | brief | Audience, purpose, tone, content inventory, backend needs — interview answers folded in as decisions |
+| `design/ASSETS.md` | assets | Inventory of client-supplied material mapped to consuming slots, extracted constraints, or the explicit "No client assets provided" line |
| `design/MOCKUPS.md` | mockup (guided mode) | Candidate roster per round, user verdicts, and the Approved line that green-lights Phase 3 |
-| `design/REVIEWS.md` | checkpoint | Engagement level + one block per activated checkpoint: what was presented, verdicts near-verbatim, the Approved/Auto-passed line |
+| `design/REVIEWS.md` | checkpoint | Engagement level + scope tier + one block per activated checkpoint: what was presented, verdicts near-verbatim, the Approved/Auto-passed line |
| `design/mockups/*.html` | mockup (guided mode) | One throwaway static preview per candidate — visual reference only, never source |
| `design/DIRECTION.md` | direction | Archetype, signature move, references, what we will NOT do |
| `design/SYSTEM.md` | foundation phase | Palette, type pairing, spacing rhythm, motion vocabulary + rationale |
+| `design/IDENTITY.md` | identity | The brand mark's construction: lockup, clear-space, minimum sizes, misuse list (SVGs in `public/brand/`) |
| `design/SITEMAP.md` | sitemap + wireframe | Pages, routes, per-page section blueprints |
-| `design/QA.md` | every gate | Gate results, screenshots taken, issues found/fixed |
+| `design/SEO.md` | seo | Findability decisions that need a record: AI-crawler policy + reason, canonical strategy |
+| `design/QA.md` | every gate, appended by iterate re-gates | Gate results, screenshots taken, issues found/fixed |
+| `design/studio-log.jsonl` | the plugin's PostToolUse hook (no model calls) | Timestamped agent/tool activity feed; read by the `/studio` route |
| `app/globals.css` | tokens | The entire design system as Tailwind v4 `@theme` tokens |
+## Context discipline
+
+The largest cost line in a build is re-reading its own paper trail. Three hard rules:
+
+1. **Each `design/*` artifact is read ONCE per context.** A skill's `Reads:` line declares a dependency, not an instruction to re-open the file — if it is already in this context, do not Read it again.
+2. **Re-read an artifact only after something in THIS context wrote to it.**
+3. **Never Read back a file you just wrote.** Write and Edit fail loudly; silence means the content on disk is what you sent.
+
+The same discipline governs skill loading: a skill's `references/` files (worked examples, catalogs, dossiers) are loaded only when the build's case genuinely needs them — the SKILL.md core is the decision material.
+
## Pipeline
-Run the phases in order. Each phase names the skills to invoke — invoke them, don't paraphrase from memory.
+Run the phases in order. Each phase names the skills to invoke — invoke them, don't paraphrase from memory. At every phase boundary the Lead rewrites `design/PROGRESS.md` (`ultraweb:status` owns the format) — twelve small writes that buy the user "where are we" at any moment and buy a fresh session its resume point.
-### Phase 1 — Understand (skills: `brief`)
-Classify the site type from the prompt, then run the **scoping interview** (guided mode): one round of up to four multiple-choice questions, generated from what THIS prompt left open — never a fixed questionnaire. An e-commerce prompt forks on catalog size, subscriptions, and checkout ownership; a restaurant on reservations and languages; a portfolio on depth of case studies. Questions cover scope, features, audience, and content — never colors, fonts, or style (Phase 2 shows those; it does not ask about them). A second round only if an answer opens a genuinely new fork; two rounds is the ceiling. Fold the answers into `design/BRIEF.md` as committed decisions; everything unasked is still decided and logged in §Assumed facts. Autonomous mode: skip the interview — decide everything, as before. Studio level: close the phase with **CP1 brief read-back** (`ultraweb:checkpoint`) — the brief summary and §Assumed facts as a correctable list; corrections land back as committed decisions.
+### Phase 0 — Preflight (no skills — two minutes that prevent a five-hour dead end)
+Before the interview, verify the room: **(1) Skills resolve** — invoke `ultraweb:taste` (the constitution, always first). If it does not resolve, STOP with an install-repair message: the specialist skills did not load and ultraweb is half-installed — reinstall via the marketplace path in README. Never work around a missing constitution. **(2) Toolchain** — node ≥ the version STACK.md's stack demands, npm, git present; target directory writable. **(3) Eyes** — Playwright MCP reachable (ToolSearch `+playwright browser`). If missing, say plainly, BEFORE any expensive work: "the visual, responsive, and accessibility gates will run **UNVERIFIED** — the site still builds and ships, QA.md records the gap" (with the MCP install pointer), and ask once whether to continue; in an unattended session, log it and continue. **(4) Report** — print a one-block capability report and record it in `design/PROGRESS.md`. A missing capability discovered in Phase 11 is a preflight bug, not bad luck.
+
+### Phase 1 — Understand (skills: `brief`; `assets` whenever the user names existing material — a logo, photos, copy docs, a brand guide, a current site)
+Classify the site type from the prompt, then run the **scoping interview** (guided mode): one round of up to four multiple-choice questions, generated from what THIS prompt left open — never a fixed questionnaire. An e-commerce prompt forks on catalog size, subscriptions, and checkout ownership; a restaurant on reservations and languages; a portfolio on depth of case studies. Questions cover scope, features, audience, and content — never colors, fonts, or style (Phase 2 shows those; it does not ask about them). Every question carries a marked default ("skip — I'll decide") so answering is never homework; a skipped question is decided and logged like an unasked one. A second round only if an answer opens a genuinely new fork; two rounds is the ceiling. Fold the answers into `design/BRIEF.md` as committed decisions; everything unasked is still decided and logged in §Assumed facts. When the user pointed at existing material, `assets` runs now and writes `design/ASSETS.md` — downstream skills read it before inventing. Autonomous mode: skip the interview — decide everything, as before. Studio level: close the phase with **CP1 brief read-back** (`ultraweb:checkpoint`) — the brief summary and §Assumed facts as a correctable list; corrections land back as committed decisions.
### Phase 2 — Direction (skills: `direction`, `mockup` in guided mode — `award-canon` consulted for references and signature-move precedent)
-Shortlist THREE deliberately contrasting archetypes for this brief. In guided mode, `mockup` renders each candidate as one fast, self-contained static HTML preview (hero + 2–3 decision-carrying sections, real OKLCH palette, real type pairing, copy sketched in the brief's voice) in `design/mockups/`, presents them side by side, and the user picks one, mixes named elements across candidates, or requests a revised round — looping until an explicit approval, every round logged in `design/MOCKUPS.md` (this round is **CP2** in the checkpoint cadence; REVIEWS.md links here rather than duplicating). Only the approved candidate becomes `design/DIRECTION.md` (a commissioned mix becomes the recorded twist). **The approval is the gate: no Phase 3+ work, no scaffold, no downstream fan-out until the Approved line exists** (the three mockup candidates themselves are the one thing that may fan out before it — they are how the approval gets earned). Mockup code is throwaway — the build re-derives everything from the artifacts and never copies mockup markup. Autonomous mode: skip the mockup round, commit to ONE archetype directly. Either way this is the highest-leverage decision of the build — spend real thought here.
+Shortlist THREE deliberately contrasting archetypes for this brief (two at sketch tier). In guided mode, `mockup` renders each candidate as one fast, self-contained static HTML preview (hero + 2–3 decision-carrying sections, real OKLCH palette, real type pairing, copy sketched in the brief's voice) in `design/mockups/`, presents them side by side — plus a single `design/mockups/index.html` contact sheet so the user compares in one tab instead of holding three in working memory — and the user picks one, mixes named elements across candidates, or requests a revised round — looping until an explicit approval, every round logged in `design/MOCKUPS.md` (this round is **CP2** in the checkpoint cadence; REVIEWS.md links here rather than duplicating). Only the approved candidate becomes `design/DIRECTION.md` (a commissioned mix becomes the recorded twist). **The approval is the gate: no Phase 3+ work, no scaffold, no downstream fan-out until the Approved line exists** (the three mockup candidates themselves are the one thing that may fan out before it — they are how the approval gets earned). Immediately after the Approved line is written, print the **session map** (`ultraweb:status` format): the remaining phases, which are silent, where the next checkpoint falls, roughly when, and how many minutes of the user's time it will want — a time-blind user should never have to guess whether to stay or go. Mockup code is throwaway — the build re-derives everything from the artifacts and never copies mockup markup. Autonomous mode: skip the mockup round, commit to ONE archetype directly. Either way this is the highest-leverage decision of the build — spend real thought here.
-### Phase 3 — Foundation (skills: `color`, `typography`, `layout-grid`, `depth`, `shape-language`, `imagery`, `motion-language`, plus `theme-worlds` if the brief needs per-route/per-case-study worlds — then `tokens` LAST)
-Design the system before any component: OKLCH palette with dark mode, font pairing (never default-Inter-only), spacing rhythm, elevation and shape language, image treatment, easing/duration vocabulary. Each skill writes its `design/SYSTEM.md` section; `tokens` runs last and compiles every decision into `@theme` tokens in `app/globals.css`.
+### Phase 3 — Foundation (skills: `color`, `typography`, `layout-grid`, `depth`, `shape-language`, `imagery`, `motion-language`, plus `theme-worlds` if the brief needs per-route/per-case-study worlds; `identity` after `typography` — then `tokens` LAST)
+Design the system before any component: OKLCH palette with dark mode, font pairing (never default-Inter-only), spacing rhythm, elevation and shape language, image treatment, easing/duration vocabulary. `identity` runs once the display face is committed: the wordmark, the monogram that feeds `icon.tsx`, and the OG template — a site with an unowned logo slot is a template, not a commission (a client-supplied mark from ASSETS.md gets formalized, never redrawn). Each skill writes its `design/SYSTEM.md` section; `tokens` runs last and compiles every decision into `@theme` tokens in `app/globals.css`.
### Phase 4 — Structure (skills: `sitemap`, `wireframe`)
Pages, routes, and a section-by-section blueprint for each page in `design/SITEMAP.md`. Every section names which component skill builds it. Studio level: close with **CP3 structure sign-off** — the page list with one line per section; a missing or extra page caught here costs an edit, caught in Phase 6 it costs a build.
-### Phase 5 — Scaffold (skills: `scaffold`, `app-structure`)
-Init the Next.js app (current stable, App Router, TS strict, Tailwind v4, shadcn/ui, motion, lucide). Wire tokens into `globals.css`. Commit the RSC/client boundary plan.
+### Phase 5 — Scaffold (skills: `scaffold`, `app-structure`, `studio`)
+Init the Next.js app (current stable, App Router, TS strict, Tailwind v4, shadcn/ui, motion, lucide) — moving `design/` to the project root per the artifact-contract location rule, and re-entrantly: scaffold's step 0 detects a partially-built tree and enters at the first incomplete step instead of re-initializing. Wire tokens into `globals.css`. Commit the RSC/client boundary plan. `studio` adds the dev-only `/studio` construction-site route (skipped at sketch tier) — the user's live window into the build, at zero token cost per update.
### Phase 6 — Build (skills: contract — `component-api` (every component obeys it); per section — `hero`, `navigation`, `footer`, `feature-sections`, `cards`, `buttons`, `forms`, `data-display`, `pricing`, `social-proof`, `faq`, `ui-states`, `overlays`; commerce — `cart`, `product-detail`; search — `command-palette`; long-form — `marginalia`; system usage — `icons`; engineering — `routing`, `data-fetching`, `media-optimization`)
Build section by section following `design/SITEMAP.md`. Each section consults its skill for the quality bar and anti-patterns. Desktop AND mobile designed together, not mobile-as-afterthought.
-**Build order: the homepage first, completely, before any inner page.** It exercises the whole system — tokens, hero, navigation, footer, section rhythm — so a system-level defect surfaces on one page instead of being rolled across all of them. At guided/studio level, **CP4 first-page review** runs on the finished homepage (screenshots at 375 and 1440): the user confirms the built reality matches the mockup they approved, feedback routes through the owning skills per `ultraweb:checkpoint`, and only then do inner pages roll out inheriting the fixes. Hands-off: same build order (the rework saving is real regardless), no stop.
+**Build order: the homepage first, completely, before any inner page.** It exercises the whole system — tokens, hero, navigation, footer, section rhythm — so a system-level defect surfaces on one page instead of being rolled across all of them. At guided/studio level, **CP4 first-page review** runs on the finished homepage (screenshots at 375 and 1440, plus a `ultraweb:preview` URL when Vercel auth exists — the user reviews on their own phone, not PNGs of localhost): the user confirms the built reality matches the mockup they approved, feedback routes through the owning skills per `ultraweb:checkpoint`, and only then do inner pages roll out inheriting the fixes. Hands-off: same build order (the rework saving is real regardless), no stop.
### Phase 7 — Backend (skills as needed: `server-actions`, `api-design`, `database`, `auth`, `email`, `payments`, `content-cms`, `storage`, `analytics` whenever the brief's conversion goals need measuring, plus `consent` whenever any third-party tracking/cookies load)
Only what `design/BRIEF.md` demands — a brochure site gets a contact form action, not a database. Whatever is built gets validation (zod), error states, and honest failure UX.
@@ -89,14 +121,26 @@ The choreography pass, applied to the finished layout. Respect `prefers-reduced-
Metadata API, generated OG images, sitemap/robots, JSON-LD where it fits.
### Phase 11 — Gates (skills: `gate-code`, `gate-responsive`, `gate-visual`, `gate-accessibility`, `gate-performance`, `gate-antislop`, `gate-content`)
-Run ALL gates; loop fix→re-gate until green. `gate-visual` and `gate-responsive` require real screenshots (Playwright MCP). Record everything in `design/QA.md`. Do not report done with a red gate.
+Run ALL gates; loop fix→re-gate until green. `gate-visual` and `gate-responsive` require real screenshots (Playwright MCP). When Phase 0 reported no browser, their screenshot halves record **UNVERIFIED** — a third verdict, distinct from PASS and FAIL: everything code-checkable still runs in full (build, types, greps, computed contrast from tokens, link checks), QA.md states exactly what went unseen, and the build ships honest about the gap instead of dead-ending here. Record everything in `design/QA.md`. Do not report done with a red gate — and never write PASS where the truth is UNVERIFIED.
-### Phase 11.5 — Acceptance (skills: `checkpoint` — guided/studio)
-**CP6 preflight/UAT**, strictly AFTER every gate is green: the user reviews a working site with real content — gate summary, per-route screenshots, a what-to-click list. They are the acceptance test, never the smoke test; the client being first QA is the cardinal studio error this ordering exists to prevent. Ship waits for the Approved line (or the logged auto-pass).
+### Phase 11.5 — Acceptance (skills: `checkpoint`, `preview` — guided/studio)
+**CP6 preflight/UAT**, strictly AFTER every gate is green: the user reviews a working site with real content — gate summary, per-route screenshots, a what-to-click list, and a fresh `preview` URL so the click-list is actually clickable on their own devices. They are the acceptance test, never the smoke test; the client being first QA is the cardinal studio error this ordering exists to prevent. Ship waits for the Approved line (or the logged auto-pass).
### Phase 12 — Ship (skills: `ship`, `handoff`)
Production build, env audit, deploy if asked, and a handoff README. `ship`'s own explicit deploy confirmation still applies on top of CP6.
+## Resuming an interrupted build
+
+A six-hour build will sometimes be interrupted — laptop closed, session died, context compacted. When a session starts (or is asked to "continue") in a directory containing `design/`, run this ladder BEFORE any other work:
+
+1. **Read `design/PROGRESS.md` §Now.** If present, it is authoritative: report position in three lines and resume at that phase.
+2. **If absent, reconstruct from artifact presence in pipeline order:** BRIEF.md → P1 done; MOCKUPS.md Approved line → P2 done; `@theme` block in `app/globals.css` → P3 done; SITEMAP.md part 2 → P4 done; `package.json` + dev server starts → P5 done; per-route files under `app/` → P6 partial (name which routes exist); QA.md gate rows → P11 partial. `git log --oneline` is the second ledger — phase-boundary commits confirm the reconstruction.
+3. **Check for open checkpoints.** A REVIEWS.md or MOCKUPS.md block without an `**Approved**`/`**Auto-passed**` line is an OPEN checkpoint: re-present it and say plainly "you were mid-review here" — never assume the approval happened.
+4. **Never re-run a completed phase.** Rewriting DIRECTION.md or SYSTEM.md on resume is a defect, not thoroughness — the artifacts are the memory, and the Approved lines in them are the user's, not yours to re-earn.
+5. Print the reconstruction as a three-line summary, rewrite PROGRESS.md, and continue without asking permission — unless step 3 found an open checkpoint.
+
+Partial artifacts are a resume, never an `iterate` (it requires the full record) and never a `retrofit` (it would overwrite real decisions with guesses — retrofit must refuse any directory that already contains `design/DIRECTION.md`).
+
## Orchestration modes
- **Solo mode** (default): run the pipeline yourself, sequentially. Phases 3 and 6 are where most of the time goes.
@@ -134,6 +178,8 @@ Prompt: *"build me a website for a Berlin specialty coffee roastery with an onli
## Failure discipline
+- **Commit at every phase boundary**: `git commit` with the fixed message form `ultraweb: phase 6 — homepage complete` (create-next-app's `git init` provides the repo; Phases 1–4 artifacts get committed retroactively at Phase 5). `git log --oneline` becomes a second phase ledger that survives anything, `git status` answers "what did a dying agent leave half-written", and every fix pass has a rollback point. Never push or create remotes uninvited — local commits only.
+- **The dev server has one owner: the Lead.** Started once in Phase 5, PID and port recorded in PROGRESS.md; gates and agents are given the URL, they never start their own. After any change to config, tokens, or dependencies, the Lead restarts it deliberately — a stale server makes every screenshot a lie.
- A gate that fails twice on the same issue: stop patching symptoms, re-read the relevant skill, fix the root cause.
- In guided mode, Phase 3+ work without an Approved line in `design/MOCKUPS.md` is a defect, not initiative — stop and get the approval. Three mockup rounds without one means the shortlist is wrong: re-shortlist, don't grind.
- Checkpoint feedback is a consolidated round, max two per checkpoint — a third request means an upstream phase is wrong; escalate per `ultraweb:checkpoint`, never sand the same spot. And feedback routes through the owning skill (color → `color`/`tokens`, copy → `copywriting`), never inline pokes at the complaint site.
diff --git a/STACK.md b/STACK.md
index ec02016..2aac89f 100644
--- a/STACK.md
+++ b/STACK.md
@@ -1,6 +1,8 @@
# STACK.md — verified stack facts
-Verified against live npm registry + official docs on **2026-07-16**; the anime.js and Shiki sections verified **2026-07-28**; the three.js/R3F section verified **2026-07-29**. Every ultraweb skill's code advice must match this file. Versions drift: `scaffold` re-verifies at build time (`npm view version`); when this file and reality disagree, reality wins — then update this file.
+Verified against live npm registry + official docs on **2026-07-16**; the anime.js and Shiki sections verified **2026-07-28**; the three.js/R3F section verified **2026-07-29**. Every ultraweb skill's code advice must match this file.
+
+**Version numbers live in `stack/versions.json` — the machine-readable manifest is the authority; the prose below carries API facts and may lag on digits.** Refresh: `node scripts/verify-stack.mjs` (report) / `--write` (fold in + stamp). **Hard expiry: if the manifest's `verified` date is more than 30 days old, run the script before Phase 5 — do not build against unverified pins.** When this file and reality disagree, reality wins — fold the drift via the script, then reconcile any API fact tied to a MAJOR-drifted package.
## Versions (npm `latest`, 2026-07-16; anime.js/Shiki 2026-07-28; three.js/R3F 2026-07-29)
@@ -170,7 +172,7 @@ The reduce branch lands the FINAL state — a path left at full dashoffset under
Argued once here; every other skill cites "per STACK.md" instead of re-litigating.
-- **GSAP** — free for every use since the Webflow acquisition, so licensing is not the objection. It duplicates anime.js's territory at roughly 3× the weight, and its stock effects are the most-copied on the web — an anti-slop liability under `taste`. `award-canon`'s ladder names anime.js where it used to name GSAP ScrollTrigger. (CANON.md's GSAP references are historical facts about real award-winning sites and stay untouched.) With scroll-scrubbed 3D in scope the ScrollTrigger question closes here too, so nobody re-litigates it per build: a scroll-driven camera reads motion's `useScroll` progress inside `useFrame` and damps it onto a mixer playhead (`ultraweb:set-design`) — three lines, zero dependency — and it keeps the scrollbar, the keyboard and find-in-page that a `pin`+`scrub` removes. The replacement is better on the merits, not merely sanctioned.
+- **GSAP** — free for every use since the Webflow acquisition, so licensing is not the objection. It duplicates anime.js's territory at roughly 3× the weight, and its stock effects are the most-copied on the web — an anti-slop liability under `taste`. `award-canon`'s ladder names anime.js where it used to name GSAP ScrollTrigger. (the canon dossiers' GSAP references are historical facts about real award-winning sites and stay untouched.) With scroll-scrubbed 3D in scope the ScrollTrigger question closes here too, so nobody re-litigates it per build: a scroll-driven camera reads motion's `useScroll` progress inside `useFrame` and damps it onto a mixer playhead (`ultraweb:set-design`) — three lines, zero dependency — and it keeps the scrollbar, the keyboard and find-in-page that a `pin`+`scrub` removes. The replacement is better on the merits, not merely sanctioned.
- **Theatre.js** — an authoring GUI plus a runtime; the studio workflow buys nothing a DIRECTION-commissioned timeline written in code doesn't already have, and adds a second source of truth for motion numbers. The rejection is directly load-bearing now that site-scale 3D exists: Theatre.js is specifically a 3D animation authoring GUI, and a camera clip authored in the DCC tool and scrubbed in code is the sanctioned form — DIRECTION.md and the asset are already the two sources of truth this build allows.
- **react-spring** — duplicates motion's springs exactly. One spring system.
- **Rough Notation** — a fourth animation runtime for one hand-drawn-annotation effect. Codified as a named `animejs` move instead.
diff --git a/agents/design-judge.md b/agents/design-judge.md
index b895d5e..08b98d4 100644
--- a/agents/design-judge.md
+++ b/agents/design-judge.md
@@ -14,7 +14,7 @@ You are a senior art director doing a portfolio review. You are paid to find wha
Screenshot file paths (or a directory), plus the project root containing `design/DIRECTION.md`, `design/SYSTEM.md`, and the ultraweb taste constitution (skills/taste/SKILL.md in the ultraweb plugin, or quoted in your prompt).
## Procedure
-1. Read DIRECTION.md and SYSTEM.md first — you judge against THIS site's stated direction, not your personal preferences.
+1. Read DIRECTION.md and SYSTEM.md first — you judge against THIS site's stated direction, not your personal preferences. Then read the plugin's `skills/award-canon/references/INVARIANTS.md` — the invariants and the jury weighting you score with come from that file verbatim, never from a remembered paraphrase of it.
2. View every screenshot at full attention. Judge each on the rubric below, 1–10 each:
- **Hierarchy** — is there an unmistakable first, second, third thing to read?
- **Typography** — scale contrast, pairing execution, tracking/leading craft
diff --git a/agents/pixel-qa.md b/agents/pixel-qa.md
index 93d688c..1e251c5 100644
--- a/agents/pixel-qa.md
+++ b/agents/pixel-qa.md
@@ -7,7 +7,7 @@ model: sonnet
You drive a real browser against the running site and report only what you observed. You never infer what a page "should" look like — you capture it.
## Procedure
-1. Use ToolSearch with query "+playwright browser" to load the Playwright MCP tools (browser_navigate, browser_resize, browser_take_screenshot, browser_console_messages, browser_snapshot, browser_click).
+1. Use ToolSearch with query "+playwright browser" to load the Playwright MCP tools (browser_navigate, browser_resize, browser_take_screenshot, browser_console_messages, browser_snapshot, browser_click). If the search returns NO Playwright tools, STOP immediately and report exactly: "NO BROWSER — Playwright MCP not available; zero routes verified." Do not fall back to fetching HTML and describing it — a report that looks like a sweep but saw no pixels is worse than no report, and the gates have a defined UNVERIFIED path for this answer.
2. Confirm the dev server URL you were given responds (navigate to it). If it doesn't load, STOP and report that — nothing else you'd report would be trustworthy.
3. For every route you were given, at each breakpoint 375×812, 768×1024, 1440×900:
- resize → navigate → wait for network idle → screenshot (save with names like `qa/-.png` under the project)
diff --git a/hooks/antislop.sh b/hooks/antislop.sh
new file mode 100755
index 0000000..9d71224
--- /dev/null
+++ b/hooks/antislop.sh
@@ -0,0 +1,53 @@
+#!/usr/bin/env bash
+# antislop.sh — PostToolUse hook on Write|Edit.
+# Kills taste banned-list violations at write time (~30ms of grep) instead of
+# letting them survive to a Phase 11 fix round. Exit 2 = blocking feedback to Claude.
+# Scoped hard: only fires inside an ultraweb build (a design/BRIEF.md ancestor),
+# only on source files, never on the design record itself.
+
+set -u
+input="$(cat)"
+
+# Best-effort file_path extraction without jq; bail silently if we can't parse.
+file_path="$(printf '%s' "$input" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*:[[:space:]]*"//; s/"$//')"
+[ -n "${file_path:-}" ] || exit 0
+
+case "$file_path" in
+ *.tsx|*.ts|*.jsx|*.css|*.mdx) ;;
+ *) exit 0 ;;
+esac
+case "$file_path" in
+ */design/*|*/node_modules/*|*/.next/*|*/studio/*) exit 0 ;;
+esac
+[ -f "$file_path" ] || exit 0
+
+# Only police ultraweb builds: walk up looking for design/BRIEF.md.
+dir="$(dirname "$file_path")"
+marker=""
+while [ "$dir" != "/" ] && [ -n "$dir" ]; do
+ if [ -f "$dir/design/BRIEF.md" ]; then marker="$dir"; break; fi
+ dir="$(dirname "$dir")"
+done
+[ -n "$marker" ] || exit 0
+
+violations=""
+check() { # $1 = pattern, $2 = taste-law line
+ if grep -nE "$1" "$file_path" >/dev/null 2>&1; then
+ violations="${violations} line $(grep -nE "$1" "$file_path" | head -1 | cut -d: -f1): $2\n"
+ fi
+}
+
+check 'from-(purple|violet|fuchsia)-[0-9]+.*to-(blue|indigo|violet)-[0-9]+' 'purple-to-blue gradient — the AI-slop signature (taste banned list)'
+check 'bg-clip-text.*text-transparent.*bg-gradient|bg-gradient.*bg-clip-text.*text-transparent' 'gradient text on a headline as a default move (taste banned list)'
+check 'href="#"' 'dead href="#" link — every link resolves or does not ship (taste banned list)'
+check '[Ll]orem ipsum' 'lorem ipsum — zero placeholder anything (definition of done #2)'
+check '>Feature [0-9]<|"Feature [0-9]"' 'stock "Feature N" copy (taste banned list)'
+check 'placeholder\.com|via\.placeholder' 'placeholder.com image (taste banned list)'
+check 'Elevate your|Unlock the power|Empower your|Seamlessly [a-z]' 'dead startup copy (taste banned list)'
+check '✨|🚀|🎉' 'emoji in production copy (taste banned list)'
+
+if [ -n "$violations" ]; then
+ printf 'ultraweb antislop hook — this write violates the taste constitution:\n%b Fix it now, at the source: consult ultraweb:taste (and ultraweb:copywriting for copy). Do not re-write the same content with a workaround.\n' "$violations" >&2
+ exit 2
+fi
+exit 0
diff --git a/hooks/hooks.json b/hooks/hooks.json
new file mode 100644
index 0000000..88a1845
--- /dev/null
+++ b/hooks/hooks.json
@@ -0,0 +1,34 @@
+{
+ "hooks": {
+ "PostToolUse": [
+ {
+ "matcher": "Write|Edit",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/antislop.sh\""
+ }
+ ]
+ },
+ {
+ "matcher": "Task",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/studio-log.sh\""
+ }
+ ]
+ }
+ ],
+ "SubagentStop": [
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/studio-log.sh\""
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/hooks/studio-log.sh b/hooks/studio-log.sh
new file mode 100755
index 0000000..bc37dd0
--- /dev/null
+++ b/hooks/studio-log.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+# studio-log.sh — PostToolUse(Task) + SubagentStop hook.
+# Appends one JSONL line per agent event to design/studio-log.jsonl so the
+# dev-only /studio route (ultraweb:studio) can render a live activity feed
+# at zero token cost. No-op outside an ultraweb build.
+
+set -u
+input="$(cat)"
+
+[ -f "design/BRIEF.md" ] || exit 0
+
+event="$(printf '%s' "$input" | grep -o '"hook_event_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*:[[:space:]]*"//; s/"$//')"
+desc="$(printf '%s' "$input" | grep -o '"description"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*:[[:space:]]*"//; s/"$//')"
+agent="$(printf '%s' "$input" | grep -o '"subagent_type"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*:[[:space:]]*"//; s/"$//')"
+
+ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
+# Values were already JSON-escaped in the source payload; strip stray control chars only.
+printf '{"ts":"%s","event":"%s","agent":"%s","summary":"%s"}\n' \
+ "$ts" "${event:-unknown}" "${agent:-}" "${desc:-}" | tr -d '\000-\010\013\014\016-\037' >> design/studio-log.jsonl
+exit 0
diff --git a/scripts/lint-skills.mjs b/scripts/lint-skills.mjs
new file mode 100755
index 0000000..3b3ece6
--- /dev/null
+++ b/scripts/lint-skills.mjs
@@ -0,0 +1,105 @@
+#!/usr/bin/env node
+// lint-skills.mjs — enforces the corpus invariants that used to live in the author's memory.
+// Usage: node scripts/lint-skills.mjs (exit 1 on any FAIL; WARNs never fail the run)
+// Dependency-free, no network.
+
+import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname, join } from 'node:path';
+
+const root = join(dirname(fileURLToPath(import.meta.url)), '..');
+const read = p => readFileSync(join(root, p), 'utf8');
+
+let fails = 0, warns = 0;
+const fail = m => { fails++; console.log(`FAIL ${m}`); };
+const warn = m => { warns++; console.log(`warn ${m}`); };
+
+const skillDirs = readdirSync(join(root, 'skills')).filter(d => statSync(join(root, 'skills', d)).isDirectory()).sort();
+const expectedCount = skillDirs.length + 1; // specialists + the root orchestrator
+
+// ---- 1. Frontmatter: exactly name + description, name === dirname, sane description length
+for (const d of skillDirs) {
+ const p = `skills/${d}/SKILL.md`;
+ if (!existsSync(join(root, p))) { fail(`${p} missing`); continue; }
+ const src = read(p);
+ const fm = src.match(/^---\n([\s\S]*?)\n---\n/);
+ if (!fm) { fail(`${p}: no frontmatter block`); continue; }
+ const keys = [...fm[1].matchAll(/^([a-zA-Z-]+):/gm)].map(m => m[1]);
+ if (keys.join(',') !== 'name,description') fail(`${p}: frontmatter keys are [${keys}], expected exactly [name, description]`);
+ const name = fm[1].match(/^name:\s*(\S+)/m)?.[1];
+ if (name !== d) fail(`${p}: frontmatter name "${name}" !== directory "${d}"`);
+ const desc = fm[1].match(/^description:\s*([\s\S]*?)(?=^\S|$(?![\s\S]))/m)?.[1]?.replace(/\s+/g, ' ').trim() ?? '';
+ if (desc.length > 1300) fail(`${p}: description ${desc.length} chars (>1300)`);
+ else if (desc.length > 1000) warn(`${p}: description ${desc.length} chars (>1000 — consider trimming)`);
+ if (!/\*\*Stage:\*\*/.test(src)) fail(`${p}: missing the **Stage:** line`);
+}
+
+// ---- 2. Every ultraweb: reference resolves
+const mdFiles = ['SKILL.md', 'README.md', 'ROSTER.md', 'STACK.md', 'CAST.md',
+ ...skillDirs.map(d => `skills/${d}/SKILL.md`),
+ ...readdirSync(join(root, 'agents')).map(f => `agents/${f}`)]
+ .filter(p => existsSync(join(root, p)));
+for (const p of mdFiles) {
+ for (const m of read(p).matchAll(/ultraweb:([a-z0-9-]+)/g)) {
+ if (!skillDirs.includes(m[1])) fail(`${p}: reference "ultraweb:${m[1]}" resolves to no skills/ directory`);
+ }
+}
+
+// ---- 3. Count claims agree everywhere (" skills" prose + README badge)
+const countFiles = ['README.md', 'ROSTER.md', 'SKILL.md', '.claude-plugin/plugin.json', '.claude-plugin/marketplace.json'];
+for (const p of countFiles) {
+ if (!existsSync(join(root, p))) continue;
+ for (const m of read(p).matchAll(/(\d+)\s+(?:interlocking\s+)?skills/gi)) {
+ const n = Number(m[1]);
+ if (n > 20 && n !== expectedCount) fail(`${p}: claims "${m[0]}" but the corpus is ${expectedCount} (${skillDirs.length} specialists + root)`);
+ }
+}
+const badge = read('README.md').match(/skills-(\d+)-/);
+if (badge && Number(badge[1]) !== expectedCount) fail(`README.md: badge says ${badge[1]} skills, corpus is ${expectedCount}`);
+
+// ---- 4. Every skill dir appears in ROSTER.md and README's studio-floor table
+const roster = read('ROSTER.md'), readme = read('README.md');
+for (const d of skillDirs) {
+ if (!roster.includes(`**${d}**`)) fail(`ROSTER.md: skill "${d}" has no roster entry`);
+ if (!readme.includes(`\`${d}\``)) fail(`README.md: skill "${d}" missing from the studio-floor table`);
+}
+
+// ---- 5. Agent model pins match the routing table's story
+for (const [agent, model] of [['design-judge', 'opus'], ['pixel-qa', 'sonnet'], ['stack-doctor', 'opus']]) {
+ const src = read(`agents/${agent}.md`);
+ if (!new RegExp(`^model:\\s*${model}$`, 'm').test(src)) fail(`agents/${agent}.md: model pin is not "${model}"`);
+}
+
+// ---- 6. references/ pointers resolve
+for (const d of skillDirs) {
+ const src = read(`skills/${d}/SKILL.md`);
+ for (const m of src.matchAll(/references\/([a-zA-Z0-9._-]+\.md)/g)) {
+ if (!existsSync(join(root, 'skills', d, 'references', m[1]))) fail(`skills/${d}: points at references/${m[1]} which does not exist`);
+ }
+}
+
+// ---- 7. No bare package-version literals creeping into skills (versions live in stack/versions.json)
+for (const d of skillDirs) {
+ const src = read(`skills/${d}/SKILL.md`);
+ for (const m of src.matchAll(/\b(next|tailwindcss|shadcn|motion|zod|stripe|drizzle-orm|better-auth|resend|animejs|three)[@ ]v?(\d+\.\d+\.\d+)/g)) {
+ warn(`skills/${d}: bare version "${m[0]}" — numbers belong in stack/versions.json`);
+ }
+}
+
+// ---- 8. Worked example presence + CAST.md client
+const cast = existsSync(join(root, 'CAST.md')) ? read('CAST.md') : '';
+const clients = [...cast.matchAll(/^## (?!Known)(.+)$/gm)].map(m => m[1].trim());
+for (const d of skillDirs) {
+ const src = read(`skills/${d}/SKILL.md`);
+ const we = src.match(/^## Worked example[^\n]*/m);
+ if (!we) { if (!['taste', 'award-canon'].includes(d)) warn(`skills/${d}: no ## Worked example section`); continue; }
+ if (clients.length && !clients.some(c => we[0].includes(c.split(' (')[0]))) warn(`skills/${d}: worked example "${we[0].slice(3, 80)}" names no CAST.md client`);
+}
+
+// ---- 9. stack manifest freshness
+const manifest = JSON.parse(read('stack/versions.json'));
+const age = Math.floor((Date.now() - new Date(manifest.verified)) / 86400000);
+if (age > 30) warn(`stack/versions.json: verified ${manifest.verified} is ${age} days old (>30) — run scripts/verify-stack.mjs`);
+
+console.log(`\n${fails} failure(s), ${warns} warning(s) across ${skillDirs.length} skills (+ root = ${expectedCount}).`);
+process.exit(fails ? 1 : 0);
diff --git a/scripts/verify-stack.mjs b/scripts/verify-stack.mjs
new file mode 100755
index 0000000..6587cb0
--- /dev/null
+++ b/scripts/verify-stack.mjs
@@ -0,0 +1,52 @@
+#!/usr/bin/env node
+// verify-stack.mjs — checks stack/versions.json against the live npm registry.
+// Usage:
+// node scripts/verify-stack.mjs # report drift, exit 1 if any
+// node scripts/verify-stack.mjs --write # fold drift into the manifest, stamp 'verified'
+// Dependency-free. Requires network access to the npm registry.
+
+import { readFileSync, writeFileSync } from 'node:fs';
+import { execFileSync } from 'node:child_process';
+import { fileURLToPath } from 'node:url';
+import { dirname, join } from 'node:path';
+
+const root = join(dirname(fileURLToPath(import.meta.url)), '..');
+const manifestPath = join(root, 'stack', 'versions.json');
+const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
+const write = process.argv.includes('--write');
+
+const ageDays = Math.floor((Date.now() - new Date(manifest.verified)) / 86400000);
+console.log(`stack/versions.json verified ${manifest.verified} (${ageDays} days ago)${ageDays > 30 ? ' — STALE (>30d)' : ''}\n`);
+
+let drift = 0;
+const rows = [];
+for (const [pkg, pinned] of Object.entries(manifest.packages)) {
+ let live;
+ try {
+ live = execFileSync('npm', ['view', pkg, 'version'], { encoding: 'utf8', timeout: 30000 }).trim();
+ } catch {
+ rows.push([pkg, pinned, 'UNREACHABLE', '?']);
+ continue;
+ }
+ const same = live === pinned;
+ if (!same) drift++;
+ const majorJump = live.split('.')[0] !== pinned.split('.')[0];
+ rows.push([pkg, pinned, live, same ? 'ok' : majorJump ? 'MAJOR DRIFT' : 'drift']);
+ if (write && !same) manifest.packages[pkg] = live;
+}
+
+const w = [Math.max(...rows.map(r => r[0].length)), 10, 10];
+for (const r of rows) console.log(r[0].padEnd(w[0] + 2) + r[1].padEnd(12) + r[2].padEnd(12) + r[3]);
+
+if (write) {
+ manifest.verified = new Date().toISOString().slice(0, 10);
+ delete manifest.observedDrift;
+ writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
+ console.log(`\nManifest updated, verified stamped ${manifest.verified}.`);
+ console.log('Now reconcile STACK.md prose: any API fact tied to a MAJOR-drifted package must be re-verified against its docs, not assumed.');
+} else if (drift) {
+ console.log(`\n${drift} package(s) drifted. Re-run with --write to fold in (then re-verify MAJOR-drifted API facts in STACK.md).`);
+ process.exit(1);
+} else {
+ console.log('\nAll pinned versions match the live registry.');
+}
diff --git a/skills/analytics/SKILL.md b/skills/analytics/SKILL.md
index cd81de0..f9204dd 100644
--- a/skills/analytics/SKILL.md
+++ b/skills/analytics/SKILL.md
@@ -1,6 +1,6 @@
---
name: analytics
-description: Conversion measurement closing the pipeline's open loop — brief defines the goals, sitemap assigns one per route, this skill counts them: cookieless-first tool choice (EU-hosted Plausible or self-hosted Umami on the locked Postgres, per STACK.md), the §25 TTDSG/TDDDG reasoning that makes a consent banner unnecessary, an event taxonomy transcribed from design/SITEMAP.md's conversion-goal column, a typed track() helper that won't compile an unlisted event, CTA instrumentation named after the goal not the widget, and the rule that anything cookie-based loads behind ultraweb:consent while GA4-by-default stays banned. Invoke in Phase 7 whenever the brief names a conversion worth counting — trigger phrases — "add analytics", "track conversions", "measure the funnel", "set up Plausible", "do we need a cookie banner for analytics".
+description: Conversion measurement closing the pipeline's open loop — brief defines the goals, sitemap assigns one per route, this skill counts them: cookieless-first tool choice (EU-hosted Plausible or self-hosted Umami on the locked Postgres, per STACK.md), the §25 TTDSG/TDDDG reasoning that makes a consent banner unnecessary, an event taxonomy transcribed from design/SITEMAP.md's conversion-goal column, a typed track() helper that won't compile an unlisted event, CTA instrumentation named after the goal not the widget, and the rule that anything cookie-based loads behind ultraweb:consent while GA4-by-default stays banned. Invoke in Phase 7 whenever the brief names a conversion worth counting — trigger phrases — "add analytics", "track conversions", "measure the funnel", "set up Plausible". The consent banner itself — building it, wording it, gating scripts behind it — is ultraweb:consent's; this skill only decides what gets measured and with which tool.
---
# analytics — count the goals you promised
@@ -129,26 +129,8 @@ And the constitutional one: an event you couldn't defend to the visitor in one s
## Worked example — Kaffeewerk Ost, the shop → Abo funnel
-design/SITEMAP.md, Conversion goal column, verbatim: `/` → *"Buy"* (the exit to `/shop` lives in the Purpose column, per `ultraweb:sitemap` step 4), `/shop` → *"Buy"*, `/shop/[slug]` → *"Buy"*, `/abo` → *"Subscribe"*, `/roesterei` → *"Read next"*, `/kontakt` → *"Contact"*.
-
-Decision: **Plausible, EU-hosted**, proxied first-party through a `/stats/*` rewrite — script URL and `data-api` endpoint together. Six routes, four goals, **four events** — `add-to-cart` `{slug}`, `order-complete`, `abo-subscribe` `{plan}`, `kontakt-send`. `/roesterei` gets none. The two `add-to-cart` surfaces — the rust `oklch(0.62 0.16 45)` "In den Warenkorb" button on the `/shop` grid card and the same button on `/shop/[slug]` — fire the *same* event with `{ slug: "roestung-14" }`, so the roast comparison survives a layout change; `{ placement: "grid" | "detail" }` is the one prop added when the client asks which surface converts. `order-complete` and `abo-subscribe` `{ plan: "250g-monatlich" }` fire from the Stripe webhook, so a card decline can never register as a sale. Verification: Application tab empty on a fresh load, the events POST landing on `kaffeewerk-ost.de/stats/event` and not on the vendor's origin, one event per click in realtime.
-
-Rejected: GA4 plus a cookie banner, the agency's default — it sets cookies, so it drags back the banner `ultraweb:consent` just deleted for this exact client, and its US transfers were ruled unlawful (per STACK.md). Honestly conceded: **self-hosted Umami** was the closer call — the Drizzle Postgres is already locked and it costs no per-site fee — and it loses here only because Kaffeewerk has no one to own an upgrade; on any build that already runs a VPS, Umami is the better call.
-
-Handoff: `ultraweb:consent` keeps its single `embeds` category — this skill added nothing to the banner; `ultraweb:gate-performance` counts the deferred script in the page budget; `ultraweb:gate-content` already checks each page's headings argue for the same goal this now counts; the dictionary ships in the `ultraweb:handoff` README.
+Moved to `references/example.md` — read only when this build's case is genuinely ambiguous; the sections above are the decision material.
## Composes with
-- **ultraweb:consent** — the boundary: cookieless tools live outside it, anything writing to the device (GA4, ad pixels, replay) is a category in its context and loads only when granted. Choosing the tool here is how consent's banner stays deleted.
-- **ultraweb:sitemap** — the conversion-goal column is transcribed into the event list; one goal per route, one event per goal, no invention.
-- **ultraweb:brief** — upstream: it decides what counts as a conversion at all, and whether this skill runs.
-- **ultraweb:buttons** — the CTA carrying each goal is where instrumentation lands; the handler goes on the button so keyboard activation counts too.
-- **ultraweb:app-structure** — `track()` callers are `"use client"` leaves, never a layout, never an RSC render body.
-- **ultraweb:server-actions** — form goals fire from the action's success state, never on submit; validation failures are not conversions.
-- **ultraweb:payments** — the Stripe webhook is where `order-complete` / `abo-subscribe` are counted, beside the outbox row it already writes.
-- **ultraweb:database** — hosts self-hosted Umami on the already-locked Postgres when that row of the tool table wins.
-- **ultraweb:copywriting** — writes the /datenschutz analytics paragraph in the site's voice from the facts this skill supplies (tool, data, legal basis, retention).
-- **ultraweb:gate-content** — checks the heading story argues for the route's goal; this skill checks the goal actually happened. Same column, two ends.
-- **ultraweb:gate-performance** — the tag counts against the page transfer budget like any other script; a tag manager fails it.
-- **ultraweb:ship** — env audit covers the server-only stats key, and the launch check confirms the first-party proxy answers in production.
-- **ultraweb:handoff** — the event dictionary is a handoff artifact: what is measured, why, and which SITEMAP goal each event maps to.
+Moved to `references/composes.md` — the handoff map; load it when orchestrating this skill against its neighbors.
diff --git a/skills/analytics/references/composes.md b/skills/analytics/references/composes.md
new file mode 100644
index 0000000..5047d0b
--- /dev/null
+++ b/skills/analytics/references/composes.md
@@ -0,0 +1,15 @@
+## Composes with
+
+- **ultraweb:consent** — the boundary: cookieless tools live outside it, anything writing to the device (GA4, ad pixels, replay) is a category in its context and loads only when granted. Choosing the tool here is how consent's banner stays deleted.
+- **ultraweb:sitemap** — the conversion-goal column is transcribed into the event list; one goal per route, one event per goal, no invention.
+- **ultraweb:brief** — upstream: it decides what counts as a conversion at all, and whether this skill runs.
+- **ultraweb:buttons** — the CTA carrying each goal is where instrumentation lands; the handler goes on the button so keyboard activation counts too.
+- **ultraweb:app-structure** — `track()` callers are `"use client"` leaves, never a layout, never an RSC render body.
+- **ultraweb:server-actions** — form goals fire from the action's success state, never on submit; validation failures are not conversions.
+- **ultraweb:payments** — the Stripe webhook is where `order-complete` / `abo-subscribe` are counted, beside the outbox row it already writes.
+- **ultraweb:database** — hosts self-hosted Umami on the already-locked Postgres when that row of the tool table wins.
+- **ultraweb:copywriting** — writes the /datenschutz analytics paragraph in the site's voice from the facts this skill supplies (tool, data, legal basis, retention).
+- **ultraweb:gate-content** — checks the heading story argues for the route's goal; this skill checks the goal actually happened. Same column, two ends.
+- **ultraweb:gate-performance** — the tag counts against the page transfer budget like any other script; a tag manager fails it.
+- **ultraweb:ship** — env audit covers the server-only stats key, and the launch check confirms the first-party proxy answers in production.
+- **ultraweb:handoff** — the event dictionary is a handoff artifact: what is measured, why, and which SITEMAP goal each event maps to.
diff --git a/skills/analytics/references/example.md b/skills/analytics/references/example.md
new file mode 100644
index 0000000..8fc09e1
--- /dev/null
+++ b/skills/analytics/references/example.md
@@ -0,0 +1,9 @@
+## Worked example — Kaffeewerk Ost, the shop → Abo funnel
+
+design/SITEMAP.md, Conversion goal column, verbatim: `/` → *"Buy"* (the exit to `/shop` lives in the Purpose column, per `ultraweb:sitemap` step 4), `/shop` → *"Buy"*, `/shop/[slug]` → *"Buy"*, `/abo` → *"Subscribe"*, `/roesterei` → *"Read next"*, `/kontakt` → *"Contact"*.
+
+Decision: **Plausible, EU-hosted**, proxied first-party through a `/stats/*` rewrite — script URL and `data-api` endpoint together. Six routes, four goals, **four events** — `add-to-cart` `{slug}`, `order-complete`, `abo-subscribe` `{plan}`, `kontakt-send`. `/roesterei` gets none. The two `add-to-cart` surfaces — the rust `oklch(0.62 0.16 45)` "In den Warenkorb" button on the `/shop` grid card and the same button on `/shop/[slug]` — fire the *same* event with `{ slug: "roestung-14" }`, so the roast comparison survives a layout change; `{ placement: "grid" | "detail" }` is the one prop added when the client asks which surface converts. `order-complete` and `abo-subscribe` `{ plan: "250g-monatlich" }` fire from the Stripe webhook, so a card decline can never register as a sale. Verification: Application tab empty on a fresh load, the events POST landing on `kaffeewerk-ost.de/stats/event` and not on the vendor's origin, one event per click in realtime.
+
+Rejected: GA4 plus a cookie banner, the agency's default — it sets cookies, so it drags back the banner `ultraweb:consent` just deleted for this exact client, and its US transfers were ruled unlawful (per STACK.md). Honestly conceded: **self-hosted Umami** was the closer call — the Drizzle Postgres is already locked and it costs no per-site fee — and it loses here only because Kaffeewerk has no one to own an upgrade; on any build that already runs a VPS, Umami is the better call.
+
+Handoff: `ultraweb:consent` keeps its single `embeds` category — this skill added nothing to the banner; `ultraweb:gate-performance` counts the deferred script in the page budget; `ultraweb:gate-content` already checks each page's headings argue for the same goal this now counts; the dictionary ships in the `ultraweb:handoff` README.
diff --git a/skills/animejs/SKILL.md b/skills/animejs/SKILL.md
index 312afa1..3a86c58 100644
--- a/skills/animejs/SKILL.md
+++ b/skills/animejs/SKILL.md
@@ -119,48 +119,8 @@ And the constitutional one: a second animation engine in the bundle with no DIRE
## Worked example — Kaffeewerk Ost, /roesterei roast-profile sequence
-design/DIRECTION.md (Warm Organic/Humanist) commissions the same motif at two intensities and keeps them apart on purpose: *"the roast-profile temperature curve — a hand-drawn SVG rise-and-plateau path — lives on `/` as the hero's spine and recurs as the section divider. Budget on `/`: ONE reusable path, no per-section variation, scroll draw-in only. On `/roesterei` the same curve becomes the argument: three batch profiles (Yirgacheffe, Huila, Sidamo) trace in sequence as you scroll, a bean marker rides the lead curve, and the first-crack tick morphs into the drop-temperature tick — ultraweb:animejs."*
-
-Decision: the gate clears on `/roesterei` and only there — the moment needs four of the six capabilities (multi-path sequencing, motion path, `d` morph, scroll scrub), DIRECTION.md names the skill, and SYSTEM.md §motion records intensity **3** because the timeline is scrubbed. One timeline, 1400ms of scrubbed range — past motion-language's 700ms ceiling under its carve-out for a DIRECTION-commissioned animejs sequence. Rust `oklch(0.62 0.16 45)` draws the lead Yirgacheffe curve; the two supporting profiles trace in ink at 40% so the accent still means "this one"; Fraunces batch labels sit static, Work Sans axis numbers never animate. Cost: +~19 KB gz for the timeline, ~23 KB once `onScroll` scrubs it (per STACK.md), recorded in SYSTEM.md as a deliberate spend.
-
-```tsx
-// components/motion/roast-profile.tsx — "use client" leaf; the finished SVG is server-rendered above it
-"use client";
-import { useRef } from "react";
-import { createScope, createTimeline, onScroll, stagger, svg, utils, type Scope } from "animejs";
-import { animeEase, animeDur } from "@/lib/motion";
-
-const root = useRef(null); // in the component; everything below is its useEffect
-const scope = useRef(null);
-
-scope.current = createScope({ root, mediaQueries: { reduceMotion: "(prefers-reduced-motion: reduce)" } })
- .add((self) => {
- if (self.matches.reduceMotion) { utils.set(svg.createDrawable(".curve"), { draw: "0 1" }); return; }
- createTimeline({ autoplay: onScroll({ target: root, enter: "bottom top", leave: "top bottom", sync: 0.25 }) })
- .add(svg.createDrawable(".curve"), {
- draw: ["0 0", "0 1"], delay: stagger(120), ease: animeEase.out, duration: animeDur.section,
- })
- .add(".bean", { ...svg.createMotionPath("#lead"), ease: animeEase.inOut, duration: animeDur.section }, "-=200")
- .add(".tick-crack", { d: svg.morphTo("#tick-drop"), duration: animeDur.small }, "-=120");
- });
-return () => scope.current?.revert();
-```
-
-Rejected: motion `pathLength` for the whole thing. It genuinely wins on `/` — the hero's spine is one path, one scroll draw, and `` (or a plain CSS `stroke-dashoffset` transition) does it for zero bytes; that is precisely why the hero is not this skill's territory and why the dependency does not exist until `/roesterei` is built. What `pathLength` cannot do is hold three drawables, a motion-path traveler, and a `d` morph on ONE scrubbed clock: three independent `useScroll` transforms drift out of phase at exactly the moment the reader is comparing batches, and correcting that drift is re-implementing a timeline engine badly. Also rejected: Rough Notation for the hand-drawn underline under each batch label — a fourth runtime for one effect (per STACK.md); the underline is authored as two squiggle passes and drawn by this same timeline at the micro tier.
-
-Handoff: `components/motion/roast-profile.tsx` plus `animeEase`/`animeDur` in `lib/motion.ts`; ultraweb:shape-language authored the SVG (one path per animatable element, stable IDs, matching point counts on the tick morph pair, no baked transforms); ultraweb:gate-performance records the measured gzip delta and the DIRECTION.md citation in design/QA.md; ultraweb:gate-accessibility confirms the reduce branch leaves all three curves drawn and the labels legible.
+Moved to `references/example.md` — read only when this build's case is genuinely ambiguous; the sections above are the decision material.
## Composes with
-- ultraweb:direction — the gate: no DIRECTION.md line naming this skill and its moment, no engine. It is also the only authority that can widen the moment's scope.
-- ultraweb:motion-language — the vocabulary: duration tiers, the one easing family, the intensity dial, and the carve-outs that let a commissioned SVG timeline exceed 700ms and reach intensity 3. This skill consumes values, never invents them.
-- ultraweb:scroll-motion — owns every other scroll effect; `animation-timeline` stays the default engine, and only the commissioned scrubbed-SVG timeline is carved out of its no-JS-scroll-listener rule.
-- ultraweb:physics — gestures, drag, and pointer springs stay on motion at `domMax`; `createDraggable` is never installed, so the two skills never overlap.
-- ultraweb:showpiece — shares the one-signature budget. A moment that is raster, canvas, shader, or 3D in one section is showpiece's; a moment that is inherently vector is this skill's side branch of its cost ladder.
-- ultraweb:set-design — the DIRECTION-gated renderer, not a third engine: SVG choreography is vector and never enters a canvas, `animejs/adapters/three` stays refused per STACK.md, and a site that somehow earned both commissions has two signature moves, which is a `direction` failure to catch.
-- ultraweb:micro-interactions — the hand-rolled text scramble stays its default; `splitText`/`scrambleText` is the escalation ONLY when this engine is already commissioned, never a reason to install it.
-- ultraweb:icons — the lucide line-draw recipe is the CHEAP version of the first named move; a single glyph draw stays there and never pulls this dependency.
-- ultraweb:shape-language — authors every SVG this skill animates: one path per animatable element, stable IDs, `currentColor`, no baked transforms, morph pairs with matching point counts.
-- ultraweb:gate-performance — the bundle entry is its pass bar: named imports only, measured gzip contribution, DIRECTION citation present.
-- ultraweb:gate-accessibility — verifies the reduce branch empirically; a path left undrawn under `prefers-reduced-motion` is invisible content and a hard fail.
-- ultraweb:award-canon — Weight as a Feature is why the byte budget precedes the choreography, and Semantic Motion Only is why the moment must carry the argument, not decorate it. Cite the principle, never a winner's surface.
+Moved to `references/composes.md` — the handoff map; load it when orchestrating this skill against its neighbors.
diff --git a/skills/animejs/references/composes.md b/skills/animejs/references/composes.md
new file mode 100644
index 0000000..4e77a42
--- /dev/null
+++ b/skills/animejs/references/composes.md
@@ -0,0 +1,14 @@
+## Composes with
+
+- ultraweb:direction — the gate: no DIRECTION.md line naming this skill and its moment, no engine. It is also the only authority that can widen the moment's scope.
+- ultraweb:motion-language — the vocabulary: duration tiers, the one easing family, the intensity dial, and the carve-outs that let a commissioned SVG timeline exceed 700ms and reach intensity 3. This skill consumes values, never invents them.
+- ultraweb:scroll-motion — owns every other scroll effect; `animation-timeline` stays the default engine, and only the commissioned scrubbed-SVG timeline is carved out of its no-JS-scroll-listener rule.
+- ultraweb:physics — gestures, drag, and pointer springs stay on motion at `domMax`; `createDraggable` is never installed, so the two skills never overlap.
+- ultraweb:showpiece — shares the one-signature budget. A moment that is raster, canvas, shader, or 3D in one section is showpiece's; a moment that is inherently vector is this skill's side branch of its cost ladder.
+- ultraweb:set-design — the DIRECTION-gated renderer, not a third engine: SVG choreography is vector and never enters a canvas, `animejs/adapters/three` stays refused per STACK.md, and a site that somehow earned both commissions has two signature moves, which is a `direction` failure to catch.
+- ultraweb:micro-interactions — the hand-rolled text scramble stays its default; `splitText`/`scrambleText` is the escalation ONLY when this engine is already commissioned, never a reason to install it.
+- ultraweb:icons — the lucide line-draw recipe is the CHEAP version of the first named move; a single glyph draw stays there and never pulls this dependency.
+- ultraweb:shape-language — authors every SVG this skill animates: one path per animatable element, stable IDs, `currentColor`, no baked transforms, morph pairs with matching point counts.
+- ultraweb:gate-performance — the bundle entry is its pass bar: named imports only, measured gzip contribution, DIRECTION citation present.
+- ultraweb:gate-accessibility — verifies the reduce branch empirically; a path left undrawn under `prefers-reduced-motion` is invisible content and a hard fail.
+- ultraweb:award-canon — Weight as a Feature is why the byte budget precedes the choreography, and Semantic Motion Only is why the moment must carry the argument, not decorate it. Cite the principle, never a winner's surface.
diff --git a/skills/animejs/references/example.md b/skills/animejs/references/example.md
new file mode 100644
index 0000000..4ef86c1
--- /dev/null
+++ b/skills/animejs/references/example.md
@@ -0,0 +1,32 @@
+## Worked example — Kaffeewerk Ost, /roesterei roast-profile sequence
+
+design/DIRECTION.md (Warm Organic/Humanist) commissions the same motif at two intensities and keeps them apart on purpose: *"the roast-profile temperature curve — a hand-drawn SVG rise-and-plateau path — lives on `/` as the hero's spine and recurs as the section divider. Budget on `/`: ONE reusable path, no per-section variation, scroll draw-in only. On `/roesterei` the same curve becomes the argument: three batch profiles (Yirgacheffe, Huila, Sidamo) trace in sequence as you scroll, a bean marker rides the lead curve, and the first-crack tick morphs into the drop-temperature tick — ultraweb:animejs."*
+
+Decision: the gate clears on `/roesterei` and only there — the moment needs four of the six capabilities (multi-path sequencing, motion path, `d` morph, scroll scrub), DIRECTION.md names the skill, and SYSTEM.md §motion records intensity **3** because the timeline is scrubbed. One timeline, 1400ms of scrubbed range — past motion-language's 700ms ceiling under its carve-out for a DIRECTION-commissioned animejs sequence. Rust `oklch(0.62 0.16 45)` draws the lead Yirgacheffe curve; the two supporting profiles trace in ink at 40% so the accent still means "this one"; Fraunces batch labels sit static, Work Sans axis numbers never animate. Cost: +~19 KB gz for the timeline, ~23 KB once `onScroll` scrubs it (per STACK.md), recorded in SYSTEM.md as a deliberate spend.
+
+```tsx
+// components/motion/roast-profile.tsx — "use client" leaf; the finished SVG is server-rendered above it
+"use client";
+import { useRef } from "react";
+import { createScope, createTimeline, onScroll, stagger, svg, utils, type Scope } from "animejs";
+import { animeEase, animeDur } from "@/lib/motion";
+
+const root = useRef(null); // in the component; everything below is its useEffect
+const scope = useRef(null);
+
+scope.current = createScope({ root, mediaQueries: { reduceMotion: "(prefers-reduced-motion: reduce)" } })
+ .add((self) => {
+ if (self.matches.reduceMotion) { utils.set(svg.createDrawable(".curve"), { draw: "0 1" }); return; }
+ createTimeline({ autoplay: onScroll({ target: root, enter: "bottom top", leave: "top bottom", sync: 0.25 }) })
+ .add(svg.createDrawable(".curve"), {
+ draw: ["0 0", "0 1"], delay: stagger(120), ease: animeEase.out, duration: animeDur.section,
+ })
+ .add(".bean", { ...svg.createMotionPath("#lead"), ease: animeEase.inOut, duration: animeDur.section }, "-=200")
+ .add(".tick-crack", { d: svg.morphTo("#tick-drop"), duration: animeDur.small }, "-=120");
+ });
+return () => scope.current?.revert();
+```
+
+Rejected: motion `pathLength` for the whole thing. It genuinely wins on `/` — the hero's spine is one path, one scroll draw, and `` (or a plain CSS `stroke-dashoffset` transition) does it for zero bytes; that is precisely why the hero is not this skill's territory and why the dependency does not exist until `/roesterei` is built. What `pathLength` cannot do is hold three drawables, a motion-path traveler, and a `d` morph on ONE scrubbed clock: three independent `useScroll` transforms drift out of phase at exactly the moment the reader is comparing batches, and correcting that drift is re-implementing a timeline engine badly. Also rejected: Rough Notation for the hand-drawn underline under each batch label — a fourth runtime for one effect (per STACK.md); the underline is authored as two squiggle passes and drawn by this same timeline at the micro tier.
+
+Handoff: `components/motion/roast-profile.tsx` plus `animeEase`/`animeDur` in `lib/motion.ts`; ultraweb:shape-language authored the SVG (one path per animatable element, stable IDs, matching point counts on the tick morph pair, no baked transforms); ultraweb:gate-performance records the measured gzip delta and the DIRECTION.md citation in design/QA.md; ultraweb:gate-accessibility confirms the reduce branch leaves all three curves drawn and the labels legible.
diff --git a/skills/app-structure/SKILL.md b/skills/app-structure/SKILL.md
index edb124f..119936d 100644
--- a/skills/app-structure/SKILL.md
+++ b/skills/app-structure/SKILL.md
@@ -141,28 +141,8 @@ Sections take data as props — pages fetch, sections render. Secrets (`process.
## Worked example — Tidepool, port-logistics SaaS boundary plan
-design/SITEMAP.md part 2 lists six routes (`/`, `/product`, `/pricing`, `/docs`, `/changelog`, `/login`) and the hero's signature: a live berth timeline that streams updates.
-
-The boundary plan (SITEMAP.md part 3) keeps every layout and page server; client leaves stay in the low teens:
-- `components/hero/berth-timeline.tsx` (`"use client"`) — polls `/api/v1/berths` and animates the timeline; the RSC renders the static SVG fallback and passes the seed as a serializable prop, so first paint needs no JS.
-- `components/pricing/billing-toggle.tsx` — monthly/annual lives in the URL (`?billing=annual`, awaited from `searchParams`), not `useState`, so a shared link lands on the annual view with Growth ($490/mo) still featured.
-- `app/login/_components/login-form.tsx` (`"use client"`) — `useActionState` over a `'use server'` Better Auth sign-in action; no handler crosses the boundary.
-- `components/layout/focus-on-navigate.tsx` (`"use client"`) — a `usePathname()` effect mounted once in the root layout; after a nav click from `/` to `/pricing` it moves focus to that page's `` so a keyboard user isn't stranded on the old header link. The skip-first-mount guard keeps a deep-linked `/docs#webhooks` hash target focused.
-- Theme: next-themes `defaultTheme="dark"` — the only context in the root layout (Precision Instrument is dark-first).
-
-Rejected: making `app/pricing/page.tsx` a client component to own the toggle — it would drag the whole tier table into the bundle for one query param. The URL carries the state instead.
-
-Handoff: SITEMAP.md part 3 is the contract ultraweb:navigation (header client leaf) and ultraweb:server-actions (the login action) build against; ultraweb:gate-performance later greps the tree against the low-teens count.
+Moved to `references/example.md` — read only when this build's case is genuinely ambiguous; the sections above are the decision material.
## Composes with
-- ultraweb:scaffold — creates the tree this contract governs; run app-structure immediately after it
-- ultraweb:routing — adds segment files (route groups, loading/error/not-found) under the same rules
-- ultraweb:data-fetching — decides caching and streaming once fetches sit in the right server components
-- ultraweb:server-actions — the mutation row of the state table
-- ultraweb:micro-interactions — produces the components/motion leaves that sections compose
-- ultraweb:gate-performance — audits `"use client"` creep against the low-teens target at Phase 11
-- ultraweb:navigation — builds components/layout/header.tsx against this plan; its mobile-menu toggle is the canonical nav client leaf this skill assigns
-- ultraweb:page-transitions — owns the app/template.tsx re-mount case this skill's layout-vs-template rule defers to, and the aria-live route announcer; app-structure owns the focus-reset half of the same accessible-navigation problem
-- ultraweb:gate-accessibility — audits keyboard and screen-reader flows, including that focus lands on `#main-heading` after client navigation
-- ultraweb:gate-code — greps every layout.tsx for `"use client"` and counts occurrences, empirically enforcing this skill's leaf-placement rule
+Moved to `references/composes.md` — the handoff map; load it when orchestrating this skill against its neighbors.
diff --git a/skills/app-structure/references/composes.md b/skills/app-structure/references/composes.md
new file mode 100644
index 0000000..dc7af22
--- /dev/null
+++ b/skills/app-structure/references/composes.md
@@ -0,0 +1,12 @@
+## Composes with
+
+- ultraweb:scaffold — creates the tree this contract governs; run app-structure immediately after it
+- ultraweb:routing — adds segment files (route groups, loading/error/not-found) under the same rules
+- ultraweb:data-fetching — decides caching and streaming once fetches sit in the right server components
+- ultraweb:server-actions — the mutation row of the state table
+- ultraweb:micro-interactions — produces the components/motion leaves that sections compose
+- ultraweb:gate-performance — audits `"use client"` creep against the low-teens target at Phase 11
+- ultraweb:navigation — builds components/layout/header.tsx against this plan; its mobile-menu toggle is the canonical nav client leaf this skill assigns
+- ultraweb:page-transitions — owns the app/template.tsx re-mount case this skill's layout-vs-template rule defers to, and the aria-live route announcer; app-structure owns the focus-reset half of the same accessible-navigation problem
+- ultraweb:gate-accessibility — audits keyboard and screen-reader flows, including that focus lands on `#main-heading` after client navigation
+- ultraweb:gate-code — greps every layout.tsx for `"use client"` and counts occurrences, empirically enforcing this skill's leaf-placement rule
diff --git a/skills/app-structure/references/example.md b/skills/app-structure/references/example.md
new file mode 100644
index 0000000..99d6636
--- /dev/null
+++ b/skills/app-structure/references/example.md
@@ -0,0 +1,14 @@
+## Worked example — Tidepool, port-logistics SaaS boundary plan
+
+design/SITEMAP.md part 2 lists six routes (`/`, `/product`, `/pricing`, `/docs`, `/changelog`, `/login`) and the hero's signature: a live berth timeline that streams updates.
+
+The boundary plan (SITEMAP.md part 3) keeps every layout and page server; client leaves stay in the low teens:
+- `components/hero/berth-timeline.tsx` (`"use client"`) — polls `/api/v1/berths` and animates the timeline; the RSC renders the static SVG fallback and passes the seed as a serializable prop, so first paint needs no JS.
+- `components/pricing/billing-toggle.tsx` — monthly/annual lives in the URL (`?billing=annual`, awaited from `searchParams`), not `useState`, so a shared link lands on the annual view with Growth ($490/mo) still featured.
+- `app/login/_components/login-form.tsx` (`"use client"`) — `useActionState` over a `'use server'` Better Auth sign-in action; no handler crosses the boundary.
+- `components/layout/focus-on-navigate.tsx` (`"use client"`) — a `usePathname()` effect mounted once in the root layout; after a nav click from `/` to `/pricing` it moves focus to that page's `` so a keyboard user isn't stranded on the old header link. The skip-first-mount guard keeps a deep-linked `/docs#webhooks` hash target focused.
+- Theme: next-themes `defaultTheme="dark"` — the only context in the root layout (Precision Instrument is dark-first).
+
+Rejected: making `app/pricing/page.tsx` a client component to own the toggle — it would drag the whole tier table into the bundle for one query param. The URL carries the state instead.
+
+Handoff: SITEMAP.md part 3 is the contract ultraweb:navigation (header client leaf) and ultraweb:server-actions (the login action) build against; ultraweb:gate-performance later greps the tree against the low-teens count.
diff --git a/skills/assets/SKILL.md b/skills/assets/SKILL.md
new file mode 100644
index 0000000..6fcb106
--- /dev/null
+++ b/skills/assets/SKILL.md
@@ -0,0 +1,94 @@
+---
+name: assets
+description: Ingest the client's existing material before the pipeline invents anything — scan the folder, paths, or files the user named, classify every find (vector logo, raster logo, photography, illustration, copy documents, brand-guide PDF, font files), map each one to the page slot that will consume it and the treatment it must pass through, and extract constraints into design/ASSETS.md: sampled OKLCH values as candidates for color, the existing typeface when its license permits web embedding, logo clear-space and mono-variant facts for identity. Invoke in Phase 1 immediately after brief whenever the user points at material that already exists — "use our logo", "here are our photos", "we have a brand guide", "our current site is X", a named folder, or attached files. When nothing exists it writes the one line that keeps the brief honest.
+---
+
+# assets — ingest what exists before inventing
+
+**Stage:** Phase 1 — Understand (immediately after `brief`) - **Reads:** the paths/folder/files the user named + design/BRIEF.md - **Writes:** design/ASSETS.md
+
+## Standard
+
+A first-grade intake is exhaustive, classified, and honest about its own authority:
+
+- **Exhaustive:** every file in the named source is accounted for — used, superseded, or rejected with a reason. A silently ignored asset becomes the client's first review comment: "why isn't our logo on it?"
+- **Classified by what it can become**, not by extension. A 4000px flat-lay is hero media; the same shot at 380px is a thumbnail. Classification records that ceiling, so `imagery` never plans a full-bleed around a file that cannot carry one.
+- **Slotted:** each kept asset names the page slot that consumes it and the ONE treatment it passes through. An inventory that stops at "we have 40 photos" hands the build a shoebox, not a decision.
+- **Advisory, never sovereign:** extracted palette and type facts are CANDIDATES. `taste` and contrast math still govern; a client hex that fails AA loses. What the client owns is input to the design, not a veto over it.
+- **Honest about absence:** no assets is a finding, not a blank. It gets written down so §Assumed facts stays truthful about what the studio invented.
+
+## Process
+
+1. **Resolve the sources.** Enumerate the folder, paths, or attachments the user named — `find -type f -not -path '*/.*' | sort` — then `file` each hit for its real type. A named URL is not an asset source: hand it to `direction` and move on.
+2. **Classify every find** into one of seven buckets: vector logo (`.svg`, `.ai`, `.eps`, live-path `.pdf`) · raster logo · photography · illustration/pattern · copy documents (`.docx`, `.md`, `.txt`, exported site copy) · brand-guide PDF · font files (`.otf`, `.ttf`, `.woff2`). Anything unclassifiable is listed as `unknown` with its type string — never dropped.
+3. **Record the ceiling per asset:** pixel dimensions and orientation for raster, viewBox for vector, page count for documents. Photography under 1600px on the long edge cannot hold a hero — say so in the row instead of discovering it in Phase 6.
+4. **Assign the slot and the treatment.** Slot comes from BRIEF.md §Pages and §Content inventory (`/shop` grid card, hero, about portrait); when `wireframe` later names the real section, tighten the column to that name. Treatment names what the asset must survive: `imagery`'s single named treatment, `media-optimization`'s pipeline, background removal, re-crop.
+5. **Sample color candidates.** Pull 3–6 values from the vector source or the brand guide, converted to `oklch(L C H)` — never carried forward as hex. They are proposals for `color`'s ramp, labeled as such.
+6. **Contrast pass, non-negotiable.** Compute every candidate proposed as a foreground or surface against its intended pair. Failing AA: keep the hue, move L (cap C if the hue can't hold it), log BOTH values and both ratios. An unreadable client color is a defect with a brand story attached.
+7. **Check the typeface license before committing anything.** A named face enters SYSTEM.md only if web embedding is permitted — open-licensed, or a webfont license the client actually holds. Desktop-only licenses, "we bought it for the logo", and unverifiable claims all resolve the same way: name the nearest licensable alternative on `typography`'s terms and log the substitution with its reason. Never ship an unlicensed `.otf` out of a client folder as a webfont.
+8. **Read the mark, don't redraw it.** Record clear-space in mark-heights, minimum legible size, whether a mono variant exists, and whether it survives knocked out on dark. Vector → `identity` formalizes the lockup around it. Raster-only → flag it for `identity` and stop: **never auto-trace a client mark.** A traced logo is a forgery with wrong curves, and it gets compared against the printed one.
+9. **Mine the copy documents for voice, not paragraphs.** Extract the phrases the client genuinely uses — product names, process terms, how they describe their craft — and hand them to `copywriting` as source. Their existing About page is evidence of voice, never approved copy.
+10. **If nothing exists**, write the file anyway with exactly the line `No client assets provided — everything visual is invented.` and mirror it into BRIEF.md §Assumed facts. That line is why the first review is a correction and not a betrayal.
+11. Write design/ASSETS.md in the format below. Every Foundation-phase skill reads it before inventing.
+
+## ASSETS.md format
+
+```md
+# Assets —
+Source: · files scanned, kept
+
+| Asset | Class | Ceiling | Slot | Treatment |
+|---|---|---|---|---|
+| `label-scan.jpg` | raster logo | 1200×800 | header, footer, favicon | → identity: formalize as vector, never traced |
+| `throw-flatlay-*.jpg` (12) | photography | 3000px | /shop cards, /products/[slug] gallery | linen-ground treatment + blob pipeline |
+| `brand-2019.pdf` | brand guide | 6pp | constraints only | — |
+
+## Color candidates (input to `color`, not law)
+- `oklch(0.42 0.06 148)` — thread green, sampled from label. AA fail on linen (3.1:1) → **use `oklch(0.36 0.07 148)`** (5.4:1), hue held.
+
+## Type
+- Named in guide: — →
+
+## Mark
+- Clear space: × mark height · min size: px · mono variant: yes/no · knockout: holds/fails
+
+## Voice source
+- → phrases handed to `copywriting`
+
+## Not used
+- —
+```
+
+## Anti-patterns
+
+- Auto-tracing a raster logo and calling it the vector — wrong curves, forged authority
+- Adopting a client hex that fails AA because "it's their brand color" — the ratio is not negotiable, the hue is
+- Treating a brand-guide PDF as SYSTEM.md — it is evidence; `color`, `typography`, and `identity` still decide
+- Installing a font found in a client folder without a license check
+- "40 photos in /assets" as an inventory — no ceiling, no slot, no treatment is a shoebox
+- Pasting the client's existing About page in as copy — `copywriting` writes every string on the site
+- Skipping the file when nothing was supplied, leaving the brief silently implying the visuals came from the client
+- Chasing the reference site the user linked instead of handing it to `direction`
+
+## Worked example — Loop & Thread, a shoebox of shop assets
+
+The user says "here's our stuff" and points at `~/loopthread-brand/`. Scan returns 47 files: 31 product photos, a scanned woven label, a 2019 brand PDF, two `.otf` files, an About doc.
+
+- **Photography (31)** — 3000px flat-lays on undyed linen, already the direction's ground. Kept: 18, slotted to `/shop` cards and `/products/[slug]` galleries; the in-hand crops become the hover state `imagery` specs. Rejected: 13 phone shots under 1200px with mixed white balance.
+- **Mark** — `label-scan.jpg`, 1200×800, a photograph of a woven label. Raster-only, so it is flagged for `identity` to formalize as a drawn wordmark referencing the weave, **not traced**. Clear space measures 1× mark height; no mono variant exists; knocked out on dark it dissolves — `identity` inherits both gaps.
+- **Color** — thread green sampled at `oklch(0.42 0.06 148)`. Against the linen ground it computes 3.1:1 — AA fail for body text. Logged as adjusted: `oklch(0.36 0.07 148)`, 5.4:1, hue held, and `color` takes the adjusted value as a candidate for its accent, not as its ramp.
+- **Type** — the 2019 guide names Sofia Pro; the `.otf` pair carries a desktop-only license. Substitution logged, and `typography` commits Fraunces + Karla on its own terms.
+- **Voice** — the About doc yields "small-batch", "undyed", "milled in Donegal"; handed to `copywriting` as vocabulary, and the Du register it already uses is confirmed there.
+
+Rejected alternative: sampling the palette from the product photos instead of the label — the photos are lit warm and would have taught `color` a linen tint that is a lighting accident, not a brand decision.
+
+## Composes with
+
+- ultraweb:brief — upstream; §Pages and §Content inventory supply the slots, and an empty intake writes its line back into §Assumed facts.
+- ultraweb:identity — reads the Mark block: formalizes a client mark, never redraws it, and inherits the missing mono variant and knockout as scoped work.
+- ultraweb:color — reads Color candidates as proposals into its OKLCH ramp; the AA adjustment logged here is already computed, never re-litigated by eye.
+- ultraweb:typography — reads Type: an open-licensed client face may be committed, an unlicensed one arrives as a named substitution with its reason.
+- ultraweb:imagery — reads the photography rows and their ceilings; real client photography replaces generated placeholders and passes the same single treatment.
+- ultraweb:copywriting — reads Voice source for the client's own vocabulary and register; it still writes every string.
+- ultraweb:media-optimization — consumes the Treatment column: dimensions, remote patterns, `blurDataURL`, which shot is the LCP element.
+- ultraweb:direction — owns any reference site the user points at ("our current site is X", "make it like Y"); hand the URL over and let Phase 2 judge it.
diff --git a/skills/award-canon/SKILL.md b/skills/award-canon/SKILL.md
index 62beaac..af30fca 100644
--- a/skills/award-canon/SKILL.md
+++ b/skills/award-canon/SKILL.md
@@ -1,13 +1,13 @@
---
name: award-canon
-description: The study library of the Awwwards record — 25 named patterns distilled from 32 Site-of-the-Year/SOTD-tier dossiers (2017–2026), the invariants that held across every year, the jury scoring model, and the dated-fashions list, with the per-site study bank in CANON.md. A reference cited by principle and always subordinate to taste and the gates — never a mandate. Invoke whenever a build needs award-grade references or signature-move ideas ("which award winners", "reference sites", "how would an Awwwards winner do this", "what do juries reward", "signature-move ideas"), or when any skill cites "the canon" or a named pattern.
+description: The study library of the Awwwards record — 25 named patterns distilled from 32 Site-of-the-Year/SOTD-tier dossiers (2017–2026), the invariants that held across every year, the jury scoring model, and the dated-fashions list, with the per-site study bank and the archetype map in references/ (loaded on demand, never wholesale). A reference cited by principle and always subordinate to taste and the gates — never a mandate. Invoke whenever a build needs award-grade references or signature-move ideas ("which award winners", "reference sites", "how would an Awwwards winner do this", "what do juries reward", "signature-move ideas"), or when any skill cites "the canon" or a named pattern.
---
# award-canon — the award record, distilled to principle
The study library of what the Awwwards record proves. `taste` is what good means for us; this is the evidence. Winners are studied for their principle and never copied on the surface — **steal the principle, never the surface.** Every pattern is a way to express `taste`'s constants; none overrides them.
-**Stage:** consulted by `direction` in Phase 2 (references + signature-move precedent), cited by name from the motion/component skills, scored against by `design-judge` in Phase 11 - **Reads:** `CANON.md` (the per-site bank), design/DIRECTION.md when a citation is resolved - **Writes:** nothing — a reference library like `taste`; its pattern names are cited into DIRECTION.md and the skills.
+**Stage:** consulted by `direction` in Phase 2 (references + signature-move precedent), cited by name from the motion/component skills, scored against by `design-judge` in Phase 11 - **Reads:** `references/ARCHETYPE-MAP.md` (the default Phase-2 load), `references/CANON.md` (per-site dossiers — only when a site is cited by name), `references/INVARIANTS.md` (what design-judge scores against), design/DIRECTION.md when a citation is resolved - **Writes:** nothing — a reference library like `taste`; its pattern names are cited into DIRECTION.md and the skills.
## Standard
@@ -15,11 +15,11 @@ The prime directive: **transfer the principle, never the surface.** The surface
## How to read the canon
-- **Winners** cite the site + the *actual verified* award + year. Many are Category / SOTD / Users'-Choice / Developer wins, not grand prizes — the principle transfers regardless of the trophy. Cite the tier the dossier verifies; never inflate to "Site of the Year," and never retrofit a sub-score (e.g. an accessibility number) onto a site whose dossier doesn't publish one. Per-site scores, live status, and hedges live in `CANON.md`.
-- **Stack** names the cheapest correct tool first: CSS/tokens → Motion for React → CSS scroll-driven → (side branch, not a cheaper rung) a DIRECTION-commissioned anime.js SVG timeline for inherently vector moments (`ultraweb:animejs`) → WebGL/R3F behind `showpiece`'s gate. Only the SVG-timeline and 3D/shader/GPU-particle layers have no React-native equivalent. GSAP is deliberately not a rung here: it duplicates that SVG territory at far greater weight, and its effects are the most-copied on the web — argued once and rejected in `STACK.md`, cited everywhere else. Where a dossier records GSAP, `CANON.md` is reporting what that site shipped — history, not our ladder. **Native CSS scroll-driven animation (`animation-timeline: view()/scroll()`) is progressive enhancement, not a default** — cross-browser support landed late (Safari, late 2025) — so every use must be designed so the no-support state is *already* the correct static layout.
+- **Winners** cite the site + the *actual verified* award + year. Many are Category / SOTD / Users'-Choice / Developer wins, not grand prizes — the principle transfers regardless of the trophy. Cite the tier the dossier verifies; never inflate to "Site of the Year," and never retrofit a sub-score (e.g. an accessibility number) onto a site whose dossier doesn't publish one. Per-site scores, live status, and hedges live in `references/CANON.md`.
+- **Stack** names the cheapest correct tool first: CSS/tokens → Motion for React → CSS scroll-driven → (side branch, not a cheaper rung) a DIRECTION-commissioned anime.js SVG timeline for inherently vector moments (`ultraweb:animejs`) → WebGL/R3F behind `showpiece`'s gate. Only the SVG-timeline and 3D/shader/GPU-particle layers have no React-native equivalent. GSAP is deliberately not a rung here: it duplicates that SVG territory at far greater weight, and its effects are the most-copied on the web — argued once and rejected in `STACK.md`, cited everywhere else. Where a dossier records GSAP, `references/CANON.md` is reporting what that site shipped — history, not our ladder. **Native CSS scroll-driven animation (`animation-timeline: view()/scroll()`) is progressive enhancement, not a default** — cross-browser support landed late (Safari, late 2025) — so every use must be designed so the no-support state is *already* the correct static layout.
- **Discipline** is load-bearing — it is the guardrail that keeps the pattern inside the constitution.
- **Beyond Awwwards.** This bank is Awwwards-shaped, so it inherits Awwwards taste. Two technique-indexed complements keep the diet honest: **Hoverstat.es** (experimental layout and interaction, indexed by what was built rather than who won) and **Godly** (indexed by component — heroes, navs, footers, pricing). Search them for a *mechanic* against a named problem; every rule above still applies — principle not surface, and the gates outrank both.
-- **Reconstructed cohort.** Several pre-2021 winners are dead or replaced (Pioneer, Prometheus, Star Atlas, Umami, MA, Active Theory v4, the Cool Club, Simply Chocolate, Frans Hals, Orano, New Mobile Workforce). Their mechanics are *reconstructed from case studies, not live inspection* — teach specifics as reported, not verified. `CANON.md` flags each.
+- **Reconstructed cohort.** Several pre-2021 winners are dead or replaced (Pioneer, Prometheus, Star Atlas, Umami, MA, Active Theory v4, the Cool Club, Simply Chocolate, Frans Hals, Orano, New Mobile Workforce). Their mechanics are *reconstructed from case studies, not live inspection* — teach specifics as reported, not verified. `references/CANON.md` flags each.
## The canon — 25 patterns
@@ -68,29 +68,9 @@ The prime directive: **transfer the principle, never the surface.** The surface
Audio is a first-class layer in many winners (New Mobile Workforce's synced transition whooshes, Igloo's ambient score, Messenger's per-zone spatial soundscapes). ultraweb **omits sound by default** — autoplay audio is an accessibility, bandwidth, and open-office/public-context hazard. If a brief genuinely demands it: **opt-in only** (a user gesture enables it), **muted by default** with a persistent visible mute control, and it **never gates progress, content, or a transition** — it enhances a site already complete and navigable in silence.
-## The invariants — held across ALL years (2017–2026)
+## The invariants and the jury model
-Timeless; the 25 patterns are ways to express them. `design-judge` scores against them.
-
-1. **ONE committed signature move, executed to an extreme** — never five effects; two moves usually *lower* the score. The load-bearing constant, and the core of `taste`.
-2. **Typographic conviction** — scale contrast, a real face POV, editorial hierarchy; type foundries won SOTY. The cheapest, most era-durable Design signal (judged in seconds).
-3. **Restraint / a tiny palette** — a 2–3 color contract is near-universal (or one authored source for a fuller palette); low density, high moment.
-4. **Motion with a director, not a library** — meaning, pacing, one easing physics; decorative motion is noise.
-5. **Immersive tech in service of a narrative/product** — never tech-for-tech's-sake; the site *performs* the subject.
-6. **Craft-level polish + performance discipline** — lean/fast/accessible is the *stronger* position: it wins the Design tier AND the Developer Award.
-7. **Whole-site coherence** — one material/motif/post-stack/grid unifies everything; 404/contact/archive match the homepage.
-8. **The concept is decided before the pixels** — the highest-leverage, zero-cost move is a creative/content decision made first.
-
-## The jury model — and what costs points
-
-| Criterion | Weight | Covers |
-|---|---|---|
-| **Design** | **40%** | hierarchy, typography, color, spacing, micro-detail, hover states, curves, rhythm |
-| **Usability** | **30%** | nav clarity, responsive/mobile, load speed, no CLS, keyboard, Core Web Vitals |
-| **Creativity** | **20%** | original concept, custom interaction, unconventional nav — *serving* content |
-| **Content** | **10%** | quality/relevance of copy, media, information |
-
-Design + Usability = **70%**; Creativity (where the signature move lives) is 20%. **Never trade a Usability point for a Creativity point.** Aim ~8.0 (SOTD contention), floor 6.5 (Honorable Mention); the 6.5→8.0 delta is one signature move executed *without dropping any Design/Usability points*. Jury: ≥18 jurors, the 3 furthest from the mean auto-dropped — **design for the median juror, not a champion.** Accessibility is the recurring weak axis in the corpus (Pioneer 6.67 is the sourced low-water mark) — the exact gap ultraweb's WCAG 2.2 AA + reduced-motion closes for free on the Design axis.
+Moved to `references/INVARIANTS.md` — `design-judge` and `gate-visual` read that file directly and score against it verbatim; it is the anti-drift copy of record.
## Dated fashions — borrow the mechanic, never the skin
@@ -104,7 +84,7 @@ Design + Usability = **70%**; Creativity (where the signature move lives) is 20%
## Using the canon in the pipeline
-- **`direction` (Phase 2)** consults the per-archetype reference map in `CANON.md` for the committed archetype: it names the reference winners (as *qualities to chase, never URLs*) and picks the canon pattern(s) whose *principle* the ONE signature move will borrow. Citations land in DIRECTION.md's References line by pattern name.
+- **`direction` (Phase 2)** reads `references/ARCHETYPE-MAP.md` — and ONLY that file, ~3KB — for the committed archetype: it names the reference winners (as *qualities to chase, never URLs*) and picks the canon pattern(s) whose *principle* the ONE signature move will borrow. Citations land in DIRECTION.md's References line by pattern name.
- **motion & component skills** cite patterns by name instead of re-deriving them — `hero` → Type as the Image / The Persistent Hero Object; `typography` → Type as Evidence; `color` → Content-Derived Color / Invert the Genre Palette; `imagery` → One Material World; `scroll-motion` → Scroll-as-Journey / Scroll-as-Camera / Fake-Depth; `motion-language` → One Physics / Semantic Motion Only; `physics` → The Prove-It Gesture / The Cursor as Narrator; `page-transitions` → The Masked Cut / The Loader is the Overture; `showpiece` → Weight as a Feature / Progressive Spectacle Tiers; `set-design` → Scroll-as-Camera / The Persistent Hero Object / One Material World / The Loader is the Overture / Weight as a Feature / Progressive Spectacle Tiers; `data-display`/`social-proof` → Framed Data; `wireframe`/`navigation` → The Metaphor Engine / Scroll-as-Journey / Archive-as-Toy.
- **`design-judge` (Phase 11)** scores Distinctiveness against the invariants — ONE signature move executed to an extreme (not five), principle not copied surface — and reads the 70/20 weighting as the reason a janky wow move is a net loss.
@@ -119,14 +99,14 @@ Design + Usability = **70%**; Creativity (where the signature move lives) is 20%
## Worked example — Studio Norra, Oslo agency portfolio
-Studio Norra's brief tension — raw editorial authority that still has to *sell* the studio's craft — shortlists Editorial Brutalist and Art-House Immersive. `direction` reads `CANON.md`'s per-archetype map: Brutalist → Type as the Image + The Prove-It Gesture; Art-House → The Persistent Hero Object + The Cursor as Narrator + Scroll-as-Camera. The signature move — cursor-proximity case-study image reveals on `/work` — borrows the *principle* of The Cursor as Narrator (the pointer communicates and acts on the scene) crossed with The Prove-It Gesture (the reveal rewards deliberate movement), executed at the cheapest rung (`clip-path` + a `useSpring` follower, 0kb WebGL) — exactly as Star Atlas's biggest lever was a *decision*, not a shader.
+Studio Norra's brief tension — raw editorial authority that still has to *sell* the studio's craft — shortlists Editorial Brutalist and Art-House Immersive. `direction` reads `references/ARCHETYPE-MAP.md`: Brutalist → Type as the Image + The Prove-It Gesture; Art-House → The Persistent Hero Object + The Cursor as Narrator + Scroll-as-Camera. The signature move — cursor-proximity case-study image reveals on `/work` — borrows the *principle* of The Cursor as Narrator (the pointer communicates and acts on the scene) crossed with The Prove-It Gesture (the reveal rewards deliberate movement), executed at the cheapest rung (`clip-path` + a `useSpring` follower, 0kb WebGL) — exactly as Star Atlas's biggest lever was a *decision*, not a shader.
What the canon explicitly kept OUT: Igloo's frozen-material world and its SDF-text (surface, not principle — and it breaks a11y); a Scroll-as-Camera dolly (the #1 scroll-jack hazard, and Studio Norra's content is an index, not a scene). DIRECTION.md's References line cites "The Cursor as Narrator (principle), The Prove-It Gesture (principle)"; `physics` owns the spring; `design-judge` scores Distinctiveness against invariant 1.
## Composes with
- ultraweb:taste — the constitution; the canon expresses its constants and never overrides its banned list or the gates. `taste` is what good means for us; `award-canon` is what the award record proves.
-- ultraweb:direction — primary consumer: `CANON.md`'s per-archetype map supplies references + signature-move ideas in Phase 2, cited into DIRECTION.md by pattern name.
+- ultraweb:direction — primary consumer: `references/ARCHETYPE-MAP.md` supplies references + signature-move ideas in Phase 2, cited into DIRECTION.md by pattern name.
- ultraweb:typography, ultraweb:color, ultraweb:imagery — cite the type/color/material patterns by name in the foundation phase.
- ultraweb:hero, ultraweb:scroll-motion, ultraweb:motion-language, ultraweb:physics, ultraweb:page-transitions, ultraweb:showpiece, ultraweb:set-design — cite the motion/interaction/3D patterns; `showpiece` owns the WebGL gate the tier patterns operationalize for one set piece, and `set-design` owns it at site scale, where every tier must hold on every route.
- ultraweb:data-display, ultraweb:social-proof — cite Framed Data for stat rows, KPI tiles, and credibility figures.
diff --git a/skills/award-canon/references/ARCHETYPE-MAP.md b/skills/award-canon/references/ARCHETYPE-MAP.md
new file mode 100644
index 0000000..88194e0
--- /dev/null
+++ b/skills/award-canon/references/ARCHETYPE-MAP.md
@@ -0,0 +1,25 @@
+# Per-archetype reference map
+
+The one award-canon file `direction` opens by default in Phase 2: the reference winners and pattern names for the committed archetype. Full site dossiers: `references/CANON.md`. Full pattern entries: `references/PATTERNS.md`.
+
+
+Reference winners are *qualities to chase*, never URLs to reskin. `direction` reads this in Phase 2 for the committed archetype, cites the winners by name, and borrows the named patterns *by principle*.
+
+| # | Archetype | Reference winners | Canon patterns to mine |
+|---|---|---|---|
+| 1 | Editorial / Magazine | The Other Side of Truth (2022), Pangram Pangram (2021), Mammut Baikal (2020) | Type as Evidence, Type as the Image, Scroll-as-Journey, Three-Token Contract, Framed Data |
+| 2 | Swiss / International | Synchronized Studio (2020), Pangram Pangram (2021) | Type as the Image, Three-Token Contract, The Cursor as Narrator |
+| 3 | Brutalist | KPR/Resn (2022), Chungi Folio (2021) | Type as the Image, The Prove-It Gesture, Kinetic Reveal Type |
+| 4 | Neo-grotesque Minimal | Lusion v3 (2023), Opal Tadpole (2024) | Type as Evidence, One Physics, Semantic Motion Only, Three-Token Contract |
+| 5 | Warm Organic / Humanist | Nomadic Tribe (2019), Koox (2018), Mana Yerba Mate (2023) | One Material World, Invert the Genre Palette, Semantic Motion Only (living idle), The Cursor as Narrator |
+| 6 | Refined Luxury Serif | Mammut Baikal (2020), Synchronized (2020) | Type as Evidence, One Physics (weighted fluid), Three-Token Contract, The Persistent Hero Object |
+| 7 | Playful Geometric | Don't Board Me (2024), Chungi Folio (2021), Simply Chocolate (2017) | One Physics (springy overshoot), The Prove-It Gesture, Interaction as Argument, Archive-as-Toy |
+| 8 | Dark Tech | DARK/Netflix (2020), Active Theory v4 (2018) | Progressive Spectacle Tiers, Content-Derived Color, Semantic Motion Only — **counter-move:** Invert the Genre Palette (Star Atlas went light) is how to escape the cliché |
+| 9 | Retro-Futurist | Prometheus Fuels (2021), Star Atlas (2021) | Invert the Genre Palette, One Material World (analog grain), Fake-Depth Before Real Depth |
+| 10 | Soft Depth | Bruno Simon (2019), New Mobile Workforce (2017) | Fake-Depth Before Real Depth, One Physics — glassmorphism stays banned; Noomo's disciplined glass (One Material World) is the reference |
+| 11 | Data-Dense Utilitarian | Orano (2018), DARK/Netflix SVG graphs (2020) | Type as Evidence, Weight as a Feature, Semantic Motion Only, Framed Data, Three-Token Contract |
+| 12 | Art-House Immersive | Igloo (2024), Lusion v3 (2023), Noomo (2023), Persepolis (2022), Lando (2025), hirotos.com (2026, mechanism + cautionary) | The Persistent Hero Object, One Material World, Scroll-as-Camera, The Loader is the Overture, The Masked Cut, Instant Everything, Shared-Element Lift, Progressive Spectacle Tiers, Weight as a Feature |
+
+---
+
+*32 dossiers, 2017–2026. Award tiers cited at the level the sources verify; reconstructed pre-2021 mechanics are reported, not live-inspected; typefaces, exact stacks, and unshipped reduced-motion paths stay hedged. The patterns these prove live in `SKILL.md`.*
diff --git a/skills/award-canon/CANON.md b/skills/award-canon/references/CANON.md
similarity index 93%
rename from skills/award-canon/CANON.md
rename to skills/award-canon/references/CANON.md
index 50299a4..932f4a0 100644
--- a/skills/award-canon/CANON.md
+++ b/skills/award-canon/references/CANON.md
@@ -269,26 +269,3 @@ The 32 dossiers behind `award-canon`'s 25 patterns, grouped by year (newest firs
- **Anti-lesson:** Usability 7.33 — scroll-jacked linear-only nav kills scanning/deep-linking/back-button; Accessibility 6.75; sound-gated transitions can't block content; 2017 full-screen WebGL scroll-cinema reads as an era.
---
-
-## Per-archetype reference map — `direction`'s 12 → winners + patterns
-
-Reference winners are *qualities to chase*, never URLs to reskin. `direction` reads this in Phase 2 for the committed archetype, cites the winners by name, and borrows the named patterns *by principle*.
-
-| # | Archetype | Reference winners | Canon patterns to mine |
-|---|---|---|---|
-| 1 | Editorial / Magazine | The Other Side of Truth (2022), Pangram Pangram (2021), Mammut Baikal (2020) | Type as Evidence, Type as the Image, Scroll-as-Journey, Three-Token Contract, Framed Data |
-| 2 | Swiss / International | Synchronized Studio (2020), Pangram Pangram (2021) | Type as the Image, Three-Token Contract, The Cursor as Narrator |
-| 3 | Brutalist | KPR/Resn (2022), Chungi Folio (2021) | Type as the Image, The Prove-It Gesture, Kinetic Reveal Type |
-| 4 | Neo-grotesque Minimal | Lusion v3 (2023), Opal Tadpole (2024) | Type as Evidence, One Physics, Semantic Motion Only, Three-Token Contract |
-| 5 | Warm Organic / Humanist | Nomadic Tribe (2019), Koox (2018), Mana Yerba Mate (2023) | One Material World, Invert the Genre Palette, Semantic Motion Only (living idle), The Cursor as Narrator |
-| 6 | Refined Luxury Serif | Mammut Baikal (2020), Synchronized (2020) | Type as Evidence, One Physics (weighted fluid), Three-Token Contract, The Persistent Hero Object |
-| 7 | Playful Geometric | Don't Board Me (2024), Chungi Folio (2021), Simply Chocolate (2017) | One Physics (springy overshoot), The Prove-It Gesture, Interaction as Argument, Archive-as-Toy |
-| 8 | Dark Tech | DARK/Netflix (2020), Active Theory v4 (2018) | Progressive Spectacle Tiers, Content-Derived Color, Semantic Motion Only — **counter-move:** Invert the Genre Palette (Star Atlas went light) is how to escape the cliché |
-| 9 | Retro-Futurist | Prometheus Fuels (2021), Star Atlas (2021) | Invert the Genre Palette, One Material World (analog grain), Fake-Depth Before Real Depth |
-| 10 | Soft Depth | Bruno Simon (2019), New Mobile Workforce (2017) | Fake-Depth Before Real Depth, One Physics — glassmorphism stays banned; Noomo's disciplined glass (One Material World) is the reference |
-| 11 | Data-Dense Utilitarian | Orano (2018), DARK/Netflix SVG graphs (2020) | Type as Evidence, Weight as a Feature, Semantic Motion Only, Framed Data, Three-Token Contract |
-| 12 | Art-House Immersive | Igloo (2024), Lusion v3 (2023), Noomo (2023), Persepolis (2022), Lando (2025), hirotos.com (2026, mechanism + cautionary) | The Persistent Hero Object, One Material World, Scroll-as-Camera, The Loader is the Overture, The Masked Cut, Instant Everything, Shared-Element Lift, Progressive Spectacle Tiers, Weight as a Feature |
-
----
-
-*32 dossiers, 2017–2026. Award tiers cited at the level the sources verify; reconstructed pre-2021 mechanics are reported, not live-inspected; typefaces, exact stacks, and unshipped reduced-motion paths stay hedged. The patterns these prove live in `SKILL.md`.*
diff --git a/skills/award-canon/references/INVARIANTS.md b/skills/award-canon/references/INVARIANTS.md
new file mode 100644
index 0000000..aac3004
--- /dev/null
+++ b/skills/award-canon/references/INVARIANTS.md
@@ -0,0 +1,27 @@
+# award-canon — the invariants and the jury model
+
+What `design-judge` scores against, verbatim — read this file, never paraphrase it.
+
+## The invariants — held across ALL years (2017–2026)
+
+Timeless; the 25 patterns are ways to express them. `design-judge` scores against them.
+
+1. **ONE committed signature move, executed to an extreme** — never five effects; two moves usually *lower* the score. The load-bearing constant, and the core of `taste`.
+2. **Typographic conviction** — scale contrast, a real face POV, editorial hierarchy; type foundries won SOTY. The cheapest, most era-durable Design signal (judged in seconds).
+3. **Restraint / a tiny palette** — a 2–3 color contract is near-universal (or one authored source for a fuller palette); low density, high moment.
+4. **Motion with a director, not a library** — meaning, pacing, one easing physics; decorative motion is noise.
+5. **Immersive tech in service of a narrative/product** — never tech-for-tech's-sake; the site *performs* the subject.
+6. **Craft-level polish + performance discipline** — lean/fast/accessible is the *stronger* position: it wins the Design tier AND the Developer Award.
+7. **Whole-site coherence** — one material/motif/post-stack/grid unifies everything; 404/contact/archive match the homepage.
+8. **The concept is decided before the pixels** — the highest-leverage, zero-cost move is a creative/content decision made first.
+
+## The jury model — and what costs points
+
+| Criterion | Weight | Covers |
+|---|---|---|
+| **Design** | **40%** | hierarchy, typography, color, spacing, micro-detail, hover states, curves, rhythm |
+| **Usability** | **30%** | nav clarity, responsive/mobile, load speed, no CLS, keyboard, Core Web Vitals |
+| **Creativity** | **20%** | original concept, custom interaction, unconventional nav — *serving* content |
+| **Content** | **10%** | quality/relevance of copy, media, information |
+
+Design + Usability = **70%**; Creativity (where the signature move lives) is 20%. **Never trade a Usability point for a Creativity point.** Aim ~8.0 (SOTD contention), floor 6.5 (Honorable Mention); the 6.5→8.0 delta is one signature move executed *without dropping any Design/Usability points*. Jury: ≥18 jurors, the 3 furthest from the mean auto-dropped — **design for the median juror, not a champion.** Accessibility is the recurring weak axis in the corpus (Pioneer 6.67 is the sourced low-water mark) — the exact gap ultraweb's WCAG 2.2 AA + reduced-motion closes for free on the Design axis.
diff --git a/skills/brief/SKILL.md b/skills/brief/SKILL.md
index fed164d..63daa14 100644
--- a/skills/brief/SKILL.md
+++ b/skills/brief/SKILL.md
@@ -98,27 +98,8 @@ Grep a finished BRIEF.md for these:
## Worked example — Framewalk, Steam-launch site for "Hollow Cartographer"
-Prompt read: "site for my indie game Hollow Cartographer, launching on Steam — needs a devlog and a way for people to hear about launch."
-
-Guided-mode interview (one round, three questions — the prompt already fixed the devlog and launch-news forks): press kit? (yes — streamers are the launch channel); devlog imported or fresh? (fresh, MDX); wishlist CTA straight to Steam or an email gate first? (straight to Steam — never gate the primary conversion). Each answer lands below as a decision, attributed "user, interview R1".
-
-- **Site type & energy budget:** product/marketing (one game), *clarity first + one wow* — the wow is the hero, not the chrome.
-- **Audience:** "a 29-year-old atmospheric-exploration fan clearing her Steam discovery queue at 11pm on a laptop — distrusts indie trailers that over-promise and ship vaporware." `copywriting` and `social-proof` build against that distrust with a real devlog cadence, not adjectives.
-- **Conversion:** primary "Wishlist on Steam"; secondary launch-news email capture. Every page serves one or the other.
-- **Tone:** hushed, cartographic, ominous — tension pair *eerie but inviting*. Sample line: "You are the last person to map a place that does not want to be mapped." (Rejected "atmospheric, immersive, polished" — survives the swap test, so worthless.)
-- **Pages:** `/` (hook + wishlist), `/game` (what it is + system reqs), `/devlog` + `/devlog/[slug]` (proof of progress), `/press` (assets + fact sheet).
-- **Backend: needs** → `content-cms` (MDX devlog) · `server-actions` + `email` (launch-news capture). **Rejected** → `payments` (the "buy" is an external Steam link, not our checkout); `database`/`auth` (no accounts, no per-user state).
-
-Rejected alternative: a second filled "Buy on Steam" CTA beside Wishlist — pre-launch there is nothing to buy, and a competing button splits intent; the direction's rule is one primary CTA everywhere.
-
-Handoff: lands in `design/BRIEF.md`; `ultraweb:direction` reads §Site type + the *eerie but inviting* tension to shortlist the Atmospheric-Dark archetype, and `ultraweb:sitemap` expands §Pages into the five routes.
+Moved to `references/example.md` — read only when this build's case is genuinely ambiguous; the sections above are the decision material.
## Composes with
-- ultraweb:taste — invoke first; its site-type → energy-budget heuristic drives step 2.
-- ultraweb:direction — consumes §Site type and the tone tension to shortlist archetypes.
-- ultraweb:mockup — guided mode; reads §Site type to pick each candidate's decision-carrying sections and §Tone for its sketch copy.
-- ultraweb:sitemap — expands §Pages into routes and nav structure.
-- ultraweb:copywriting — writes exclusively from §Content inventory, in §Tone's voice.
-- ultraweb:auth, ultraweb:database, ultraweb:payments, ultraweb:email, ultraweb:content-cms, ultraweb:storage, ultraweb:api-design — enter Phase 7 only as §Backend: needs names them.
-- ultraweb:iterate — user corrections to §Assumed facts route through it, never a rebuild.
+Moved to `references/composes.md` — the handoff map; load it when orchestrating this skill against its neighbors.
diff --git a/skills/brief/references/composes.md b/skills/brief/references/composes.md
new file mode 100644
index 0000000..d9b561f
--- /dev/null
+++ b/skills/brief/references/composes.md
@@ -0,0 +1,9 @@
+## Composes with
+
+- ultraweb:taste — invoke first; its site-type → energy-budget heuristic drives step 2.
+- ultraweb:direction — consumes §Site type and the tone tension to shortlist archetypes.
+- ultraweb:mockup — guided mode; reads §Site type to pick each candidate's decision-carrying sections and §Tone for its sketch copy.
+- ultraweb:sitemap — expands §Pages into routes and nav structure.
+- ultraweb:copywriting — writes exclusively from §Content inventory, in §Tone's voice.
+- ultraweb:auth, ultraweb:database, ultraweb:payments, ultraweb:email, ultraweb:content-cms, ultraweb:storage, ultraweb:api-design — enter Phase 7 only as §Backend: needs names them.
+- ultraweb:iterate — user corrections to §Assumed facts route through it, never a rebuild.
diff --git a/skills/brief/references/example.md b/skills/brief/references/example.md
new file mode 100644
index 0000000..1392376
--- /dev/null
+++ b/skills/brief/references/example.md
@@ -0,0 +1,16 @@
+## Worked example — Framewalk, Steam-launch site for "Hollow Cartographer"
+
+Prompt read: "site for my indie game Hollow Cartographer, launching on Steam — needs a devlog and a way for people to hear about launch."
+
+Guided-mode interview (one round, three questions — the prompt already fixed the devlog and launch-news forks): press kit? (yes — streamers are the launch channel); devlog imported or fresh? (fresh, MDX); wishlist CTA straight to Steam or an email gate first? (straight to Steam — never gate the primary conversion). Each answer lands below as a decision, attributed "user, interview R1".
+
+- **Site type & energy budget:** product/marketing (one game), *clarity first + one wow* — the wow is the hero, not the chrome.
+- **Audience:** "a 29-year-old atmospheric-exploration fan clearing her Steam discovery queue at 11pm on a laptop — distrusts indie trailers that over-promise and ship vaporware." `copywriting` and `social-proof` build against that distrust with a real devlog cadence, not adjectives.
+- **Conversion:** primary "Wishlist on Steam"; secondary launch-news email capture. Every page serves one or the other.
+- **Tone:** hushed, cartographic, ominous — tension pair *eerie but inviting*. Sample line: "You are the last person to map a place that does not want to be mapped." (Rejected "atmospheric, immersive, polished" — survives the swap test, so worthless.)
+- **Pages:** `/` (hook + wishlist), `/game` (what it is + system reqs), `/devlog` + `/devlog/[slug]` (proof of progress), `/press` (assets + fact sheet).
+- **Backend: needs** → `content-cms` (MDX devlog) · `server-actions` + `email` (launch-news capture). **Rejected** → `payments` (the "buy" is an external Steam link, not our checkout); `database`/`auth` (no accounts, no per-user state).
+
+Rejected alternative: a second filled "Buy on Steam" CTA beside Wishlist — pre-launch there is nothing to buy, and a competing button splits intent; the direction's rule is one primary CTA everywhere.
+
+Handoff: lands in `design/BRIEF.md`; `ultraweb:direction` reads §Site type + the *eerie but inviting* tension to shortlist the Atmospheric-Dark archetype, and `ultraweb:sitemap` expands §Pages into the five routes.
diff --git a/skills/cart/SKILL.md b/skills/cart/SKILL.md
index 61328df..070c8a4 100644
--- a/skills/cart/SKILL.md
+++ b/skills/cart/SKILL.md
@@ -103,25 +103,8 @@ const [optimisticCount, bump] = useOptimistic(count, (n, delta: number) => n + d
## Worked example — Kaffeewerk Ost, the cart for a Berlin roastery
-design/BRIEF.md: Warm Organic e-commerce shop + `/abo` subscriptions; free shipping over 39 € is real; signature = the roast-profile temperature curve.
-
-Slide-over drawer is the default — buying a second bag shouldn't cost the collection page. Add-to-cart on `/shop/[slug]` is `