design-ship-gate · git:20260906.c2334b3 · 2026-09-06 · sha256 82001735fd36fec9

design-ship-gate git:20260906.c2334b3A

Immutable. This exact content is served forever at /api/v1/blob/82001735fd36fec9.

---
name: design-ship-gate
description: "Done-gate for a web page built by a small model. Run it before saying a landing page is finished: brief coverage, then eight executable checks, one per defect that recurred across nine Qwen3.8-27B pages (fonts, OG tags, dark mode, 390 px overflow, number wrapping, hero collisions, tracking, language). Commands and pass conditions, no design theory."
triggers:
  - /design-ship-gate
  - ship gate
  - design done gate
  - before I say the page is done
  - ตรวจก่อนส่งหน้าเว็บ
  - ก่อนบอกว่าหน้าเสร็จ
  - เช็คหน้า landing ก่อนส่ง
---

# Ship gate (`design-ship-gate`)

**Run this before you say a page is finished.** Read the brief again, then run every check below and paste each command's output. A check you did not run is a check that failed. Fix, re-run, then report `GATE: 8/8` with the outputs.

Why a gate and not more rules: the design rules were loaded and the pages still broke them. Nine pages from one brief, audited with real renders: more than two font families **5 / 9**, no Open Graph tags **6 / 9**, hero elements colliding **3 / 9**, a number split from its unit **3 / 9**. Every one of those was found by a command, never by re-reading the rule.

## 0. Brief coverage — write the list first

Before the checks, write this table in your answer and fill every row:

| the brief names | element(s) that deliver it |
|---|---|
| each style word in the brief (e.g. "Liquid Glass") | selector or section |
| dark mode | `prefers-color-scheme` block or toggle — **a deliverable even when the brief does not say it** (4 of 5 pages skipped it unasked) |
| Open Graph tags | `<meta property="og:...">` — same, a deliverable when unnamed |
| the brief's language | the page copy is in it (2 / 9 answered a Thai brief in English) |

A row with an empty right-hand cell is a missing deliverable, not a stylistic choice. A page in one generic direction (a SaaS card grid for an editorial brief) fails this table before any check runs.

## 1–8. The checks

Set `P=index.html` (the page) and `G=<this skill's directory>` (where `gate-dark.js`, `gate-390.js`, `gate-hero.js` live). Node with Playwright is needed for 4–6 (`npm i -D playwright` in the page's folder, or `NODE_PATH` to an existing install); if it is absent say so in the report instead of skipping silently.

**1. At most two font families.** Count what is *loaded and primary*: every family in a Google Fonts `family=` link, plus the FIRST name of each `font-family` / `--font-*` declaration. Fallbacks after the comma (`Segoe UI`, `SF Mono`, `Helvetica Neue`, `system-ui`) are not families — a first draft of this check counted them and called a two-font page a five-font page. Pass: the list has `<= 2` entries.
```sh
{ grep -oE "family=[A-Za-z+]+" "$P" | sed 's/family=//; s/+/ /g'; grep -oiE "(font-family|--font[a-z0-9-]*|--ff[a-z0-9-]*)[[:space:]]*:[^;},]+" "$P" | sed -E "s/^[^:]+:[[:space:]]*//; s/['\"]//g; s/[[:space:]]+$//" | grep -viE "^(var\(|sans-serif|serif|monospace|system-ui|ui-|inherit|initial|-apple)" ; } | sort -u
```

**2. Open Graph present.** Pass: all three lines print.
```sh
grep -oE 'property="og:(title|description|image)"' "$P" | sort -u
```

**3. Dark mode exists.** Pass: at least one match.
```sh
grep -cE "prefers-color-scheme|data-theme|\.dark\b|theme-toggle" "$P"
```

**4. In dark mode every stat is visible.** The failure was `color: var(--paper)` on a stats band after dark mode was added later — in the dark theme `--paper` went dark too and the numbers vanished. The script reads text nodes, because a stat is written `9.4<span>B</span>` and an element-level test skips it; "invisible" is a luminance distance under 60 to the effective background, which is what the audit saw. Pass: `invisible: 0`.
```sh
node "$G/gate-dark.js" "$P"
```

**5. No horizontal overflow at 390 px — with `overflow-x:hidden` removed for the test.** One page hid a 56 px overflow behind that rule. Pass: `overflow: 0`.
```sh
node "$G/gate-390.js" "$P"
```

**6. No text collides with the headline.** A column ruler `01–12` sat across the `h1`; a glass lens sat over "EST. 1998". Both were text over text, and the ruler was even below the headline in z-order, so the script does not look at z-index: any positioned element carrying its own visible text whose box overlaps the `h1` box is a collision. Decorative blobs and glows carry no text and pass. Pass: `colliding: 0`. The fix is to move the text (the ruler into the gutter, the lens off the tagline), not to hide it.
```sh
node "$G/gate-hero.js" "$P"
```
Known miss: a glass lens (no text of its own) over a tagline that is not the `h1` is not caught — 2 of the 3 audited collisions are. Look at the hero once with your eyes as well.

**7. A number stays on one line with its unit.** `100B+` split into `100` / `B+` because `.big span{display:block}` also matched the unit's `<span>`. Pass: nothing prints (no descendant `span` rule under a numeric block), and `≈5B+/day`-style stats use `white-space:nowrap`.
```sh
grep -nE "\.(big|stat|num|n|value)[a-z-]* +span *\{" "$P"
```
Use `.big > span` for a deliberate line break, and put the unit inside the same `nowrap` element as the number.

**8. Nothing under 24 px is tracked tighter than −0.025em.** A wordmark at 22 px with `-0.03em` read "Goole". The command lists every tighter value, `em` and `px` alike (the first version stopped at `-0.09em`, so `-0.1em` and `-1px` — the tightest values there are — were invisible to the check that exists to find them; review 2026-09-06); grep cannot see the font size, so for each line printed, name the selector's font size in the report. Pass: every printed line is a heading or display number at ≥ 24 px (the tracking hack is for those only); anything smaller goes back to `letter-spacing: 0`.
```sh
grep -nE "letter-spacing: *-(0\.(0(2[6-9]|[3-9])|[1-9])[0-9]*em|[0-9.]+px)" "$P"
```

## Report

```
GATE: n/8  (fonts 2 · og 3/3 · dark yes · invisible 0 · overflow 0 · colliding 0 · span-rule none · tracking ok)
brief table: k rows, all filled
```
Paste the command outputs under it. `GATE: 8/8` with no outputs is not a pass.

## What this does not touch

Design choices, copy, colour, layout — `design-rules` and `design-audit` own those. This file only says whether the page may be called finished. It was calibrated on nine pages from one brief and one model; the counts above are what was seen, not a rate.