# Task 15e — Retire responsive.css

**Agent**: `design-system-keeper` · **Model**: `agy` (Gemini 3.1 Pro — vision)
**Depends on**: 15d, 16 · **Blocks**: 20 **Worktree**:
`.agents/scripts/worktree.sh start 15e responsive-css`

## Goal

`responsive.css` is 30 KB and mostly served `/full-guide/`. Once 15d and 16 have
landed, port what the Astro pages still need into component styles or
`tokens.css`, prove the remainder dead, and delete it.

**Proof is screenshots, not reading.** A rule that looks unused because no
selector matches at 1600 px may be the only thing holding the 560 px layout
together.

## Method

1. Build. Screenshot every migrated route at 560 / 800 / 1100 / 1600 px.
2. Remove `responsive.css` from the build entirely.
3. Screenshot again. Every diff is a rule you must port.
4. Port it into the owning component's `<style>`, or — if it is a real token —
   into `tokens.css`, which **you own**. No other agent may add tokens.
5. Repeat until the diffs are empty.

## Constraints

- Allowed breakpoints are 560 / 800 / 1100 / 1600 / 2200 px.
  `.agents/scripts/check-tokens.mjs` rejects others.
- Do not delete the file while any legacy page still loads it. Check which
  routes have actually been migrated at the time you run; 20 is the cutover.
- No raw hex, no `font-size: Npx` outside `tokens.css`.
- You are also the owner of the ~190 accumulated `/* token-gap: ... */` markers.
  Resolving them is **not** in this brief — do not start. Report the count so it
  can be scheduled.

## Done when

Superseded by the legacy-page amendment below. `responsive.css` cannot be
deleted before task 20 because `full-guide/index.html` is still the live legacy
route and deliberately links it.

- [ ] Screenshot diffs reviewed at all required widths without an Astro import
      of `responsive.css`
- [x] Ported rules live with the component that needs them, or in `tokens.css`
- [x] No Astro file or layout references `responsive.css`; the legacy page link
      remains unchanged until task 20
- [x] `pnpm run gate` green; assertions intact

## Amendment — the screenshot step now actually works

When this brief was written, `.agents/scripts/visual-regression.mjs` threw
`playwright ... install its project dependency` for everyone: the browsers were
cached in `~/.cache/ms-playwright` but the node package was never a dependency.
Every "screenshots match" checkbox in this plan was unsatisfiable. `playwright`
is now a devDependency (`c2a046d`) and a headless screenshot has been verified
to work. **Take the screenshots.** "I read the rules and they look unused" is
not the proof this brief asks for.

All eight routes are migrated as of `edd03f9` — `index`, `models`, `agents`,
`skills`, `summary`, `rules`, `full-guide`, `skills-review`. Screenshot all of
them, not just `/full-guide/`.

## Amendment — the token-gap queue is already closed

The brief says "~190 accumulated `/* token-gap: ... */` markers … do not start".
Task 02c ran and took them from 154 to **17**. Do not reopen that work either;
the 17 that remain are deliberate.

Two things 02c got wrong that you must not repeat. It closed six markers by
pointing the value at a palette token with a _different_ value — `#5b7098` at
`var(--accent)` (`#7c78a8`), `#9eb0bb` at `var(--muted)` (`#697b89`) — after its
own marker comments had recorded "no token matches". That shipped a silent
redesign the gate could not see. `edd03f9` repaired it by adding exact on-dark
tokens: `--ink-muted`, `--ink-line`, `--ink-code`, `--accent-paper`,
`--accent-paper-active`, `--accent-surface`, and four exact-alpha overlays
`--white-14/23/25/31`. Use those when you need a colour on `--ink` or
`--accent`; the light-background palette is the wrong family there.

If you must port a colour or size that has no token, add an **exact** one to
`tokens.css` — you own it — or leave the raw value under a `token-gap:` marker.
Never round to a near neighbour.

## Amendment — how this gets verified

The gate does not read your output: all 42 assertions in `scripts/verify.mjs`
read the legacy files, so deleting `responsive.css` and breaking every mobile
layout passes it cleanly. Before you claim done, diff the **built** CSS
(`dist/_astro/*.css`) against `main` and enumerate every declaration that
disappeared. Each one is either present elsewhere or a regression.

## Attempt 1 stopped short — and the goal as written is not reachable yet

Preserved as `rejected/15e-attempt-1` (`d2312d7`). Start by reading that diff:
it ported rules into `ChapterHero`, `FleetDiagram`, `RouteTable`, `WorktreeMap`,
`LanguageToggle`, `ReadingProgress`, `agents.astro`, `full-guide.astro` and
`tokens.css`, and it added `pixelmatch`, `pngjs` and `postcss` plus a working
screenshot-compare harness. Reuse all of it. It never finished the comparison
loop and never committed on its own.

It also deleted `responsive.css` while **`full-guide/index.html` still links
it**. That page is the live site until task 20 cuts over; deleting the
stylesheet strips it. `src/components/islands/GuideSelector.astro` still
references it too.

So the "delete it" checkbox in this brief cannot be honestly ticked before the
cutover. Do not tick it, and do not delete the file. **The deliverable is
everything up to the deletion:**

- Every rule `responsive.css` provides to an Astro route lives with the
  component that needs it, or in `tokens.css`.
- No `.astro` file and no Astro layout links `responsive.css` any more.
- `full-guide/index.html` still links it, unchanged, and still renders exactly
  as it does today.
- A list, in this file, of the rules that remain in `responsive.css` solely for
  the legacy page — that list is the deletion checklist task 20 will execute.

### Task 20 deletion checklist

`responsive.css` has no Astro consumer after this task. Its remaining consumer
is the untouched `<link rel="stylesheet" href="../responsive.css">` in
`full-guide/index.html`. Task 20 must retire that page before deleting the
stylesheet. Until then, retain these legacy-only sections exactly as they are:

- The root guide shell and all interactive section selectors (`.topbar` through
  `.copy-status`), including their dark-surface colour values.
- The responsive blocks at 1600px, 2200px, 1050px, 800px, and 600px, plus the
  reduced-motion block. They continue to style the legacy HTML only.
- The legacy page's `.route-meter span { height: var(--score) }` transition.
  Astro uses the component-safe transform equivalent; do not modify this legacy
  copy while the legacy document is live.

Amend the "Done when" boxes to match before you start, and say in your final
report that the file is intentionally still present.

### One rule to port properly while you are in there

`.route-meter span` animates `height: var(--score)` — a layout property, which
task 18 was meant to eliminate. 18 changed it in `responsive.css` and that
change was reverted (`0bca43c`) because it belongs in the component, not in a
legacy file. When you port this rule, port it as
`transform: scaleY(var(--score)); transform-origin: bottom` with `height: 100%`.
`--score` is set inline as a percentage (`--score:92%`) by `GuideSelector.astro`
and `full-guide.astro`; a percentage in `scaleY()` is valid and was verified
rendering correctly at `matrix(1, 0, 0, 0.92, 0, 0)`. Leave the legacy
`responsive.css` copy of the rule alone.

## Attempt 3 rejected: two breakpoints were replaced with dead tokens

Tagged `rejected/15e-attempt-3` (`d5f5514`). The gate passed with 84 assertions
intact, and the work is still wrong. Recover anything reusable from the tag; do
not build on it.

**The disqualifying defect.** `responsive.css` carries
`@media (max-width: 880px)` — which collapses `.verify-layers` and
`.verify-cta-grid` to one column and `.verify-intro` to a single column — and
`@media (max-width: 1050px)`, which narrows `.tree-node` to 145px, puts
`.tree-detail` on two columns, and collapses `.skill-explorer`. Neither was
ported. Both are absent from the built CSS:

```
grep -oh 'max-width:[0-9]*px' dist/_astro/*.css | sort -u
```

returns every other breakpoint and not those two. On the built site the
verification section stays multi-column down to 560px and the worktree tree
never narrows.

What made it look finished is worse than the omission. `audit-ui.mjs` compares
the built CSS against `.agents/snapshots/built-css-values.json`, so dropping
those two media queries fails the value contract. Attempt 3 answered that by
adding

```css
/* Retained as exact legacy dimensions for the built-CSS value contract;
   neither is used as a media query. */
--legacy-audit-width-880: 880px;
--legacy-audit-width-1050: 1050px;
```

Two variables, referenced by nothing, whose only function is to put the strings
`880px` and `1050px` back into the built sheet so the check that exists to catch
this exact loss reports success. The comment says so outright. **Port the media
queries. Never satisfy a value contract with a value nothing uses** — if a check
is in the way, the check is telling you something.

**Three more things to fix on the next attempt.**

1. **No `--raw-<hex>` tokens.** Attempt 3 added roughly fifty of them:
   `--raw-9eabb4: #9eabb4`, `--raw-ffffff2d: #ffffff2d`, and so on. A token
   named after its own value carries no meaning, so it is not a token — it is a
   lookup table that satisfies `check-tokens.mjs` while defeating the point of
   having one. If a ported rule needs a colour that the palette has no name for,
   either it is one of the existing tokens (use it) or it is a genuine gap (name
   it for its role, or leave the literal and add a token-gap marker — see
   `.agents/rules/theming.md`).

2. **Leave `--white-31` alone.** Attempt 3 rewrote it from
   `rgb(255 255 255 / 31.3725%)` to `#ffffff50` and added a second
   `--white-31-alpha` holding the original. The two are numerically equal; the
   churn reverses task 02c, which set these six on-dark tokens to their exact
   values on purpose, and leaves a duplicate behind.

3. **Do not commit screenshots.** Attempt 3 added 128 PNGs under
   `.agents/snapshots/15e-before/` and `15e-after/`, 35 MB, to a repository
   whose entire pack is under 400 KB. Take them, compare them, report the
   comparison, and leave them out of the commit.

**Acceptance, restated.** Every breakpoint in `responsive.css` that serves an
Astro route appears in the built CSS, in a rule that does the same thing it did
before:

```
diff <(grep -oh '(max\|min)-width:[0-9]*px' responsive.css | sort -u) \
     <(grep -ohE '(max|min)-width:[0-9]*px' dist/_astro/*.css | sort -u)
```

Anything only the legacy page needs may be missing from the built sheet — say
which, and why, in the final report.

## Attempt 4 rejected: the ported rules are in the sheet and do nothing

Tagged `rejected/15e-attempt-4` (`b6b6200`). This attempt fixed everything
attempt 3 got wrong — the 880px and 1050px media queries are ported, the tokens
are named for their roles rather than their hex values, `--white-31` is
untouched, `responsive.css` is unchanged and still linked by
`full-guide/index.html`, no `.astro` file imports it, the route-meter is
`transform: scaleY()`, the four off-scale breakpoints carry honest token-gap
markers, no screenshots were committed, and the gate passes with 84 assertions.
The acceptance test in this brief passes on it.

**It is still wrong, and the acceptance test was the problem.**

Astro scopes a component's styles. `WorktreeMap.astro`'s rules compile to
`.tree-node[data-astro-cid-lsutp3lb] { width: 180px }`, specificity 0,2,0. A
rule ported verbatim out of `responsive.css` arrives as
`@media (max-width: 1050px) { .tree-node { width: 145px } }`, specificity 0,1,0.
The breakpoint is in the built sheet, the media query matches, the selector
matches — and the declaration loses. At 1050px the node stays 180px wide.
Measured:

```
node .agents/scripts/computed-style-diff.mjs full-guide --widths 880,1050
```

56 differences on attempt 4. Diffing breakpoints between `responsive.css` and
`dist/_astro/*.css` cannot see any of it, which is my error, not the agent's: I
wrote that test.

**Port rules into the component that owns the selector**, so they compile with
the same scope as the base rule they are overriding. A rule that has no owning
component belongs in a global sheet, but then the base rule it overrides has to
be global too.

### The baseline is not zero, and that is the real finding

The same command reports **52 differences on `main`**, before this task changes
anything. `full-guide.astro` currently imports `responsive.css`, and that import
has never fully worked, for the identical reason: a global stylesheet cannot
override scoped component styles. The responsive layer has been partly inert in
the Astro build for as long as it has been imported, on `.tree-node`, `.branch`,
`.worker-card`, `.tree-stage`, `.tree-lab`, `.hero`, `.fleet`, `.chapter-links`
and more.

So this task is not "keep parity". Parity is already broken, and porting the
rules properly is what fixes it.

### Acceptance, restated again

```
node .agents/scripts/computed-style-diff.mjs full-guide
```

- Must report **fewer than 52** differences at 880px and 1050px — that is the
  `main` baseline, and anything above it is a regression.
- Drive it to **zero** where you can. Every difference you leave must be listed
  in your final report with the reason it is not fixable inside this task's
  scope.
- Keep everything attempt 4 got right; the list above is not a set of problems,
  it is the standard to match.
- The breakpoint diff against the built CSS stays as a _necessary_ check. It is
  no longer a _sufficient_ one.
