fix: regenerate favicon.ico with a transparent ground; tick Pouya's read-through

public/favicon.ico shipped with no transparency: all three frames declared a
32-bit alpha channel and then carried alpha=255 on every one of their 256/1024/
2304 pixels, ground opaque cream rgba(250,247,242,255). Pouya's read-through
finding, confirmed by parsing the ICO container directly.

The render source carries true alpha (2,272,386 transparent px, 20,795 partial),
so this is an export, not a mask derived from the cream ground — R13's harder
branch did not fire and R13 is unchanged on its own terms.

New scripts/icons.mjs + npm run icons re-derives the icon from the committed
master: asserts the source is still the documented crop (R14), verifies a
candidate file and renames on success so a rejected build cannot replace a good
favicon, and runs a boundary-colour halo test. Composition is unchanged —
ink bbox and pixel count identical at all three sizes.

apple-touch-icon.png is byte-identical and stays opaque cream deliberately; the
reason lives in docs/reference/brand-assets.md §The icon set, with the bar and a
pointer in BaseLayout.astro, docs/06 and R13.

docs/06: the read-through is ticked, and the cutover callout drops to ONE
blocker — Q60's waiting period.

Two adversarial review rounds, 15 findings, all resolved, none declined; stopped
at two per D19. claims-auditor correctly deferred to cutover per D20. Gates on
the committed bytes, exit status read: check 0, build 0 (23 pages),
check:claims 0, check:intake 0, og:proof 0, lint 0, lighthouse 0 (worst of 23
99/100/100/100). Nothing deployed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
This commit is contained in:
Pouya Lajevardi
2026-09-02 16:14:35 -04:00
co-authored by Claude Opus 5
parent 4735989f0b
commit 67847d94fa
8 changed files with 891 additions and 22 deletions
+60 -15
View File
@@ -395,8 +395,22 @@ Then invalidate `/*`.
> reversing them puts 22 of 23 pages behind a 403 for as long as a CloudFront
> deployment takes.
> 🛑 **TWO THINGS BLOCK THIS ENTIRE LIST AS AT 2026-09-02: ONE WAITING PERIOD
> AND ONE READ-THROUGH.**
> 🛑 **ONE THING BLOCKS THIS ENTIRE LIST AS AT 2026-09-02, AND IT IS A WAITING
> PERIOD RATHER THAN A TASK: Q60.**
>
> ✅ **THE READ-THROUGH IS COMPLETE — Pouya, 2026-09-02, and it returned ONE
> FINDING WHICH WAS NOT COPY.** `public/favicon.ico` shipped with no
> transparency; fixed and verified the same day (see **Favicon set complete**
> below). His read carried the approvals with it, in terms: the
> `/legal/privacy/` §Who can see it wording, the **SML Company Ltd** consent
> line, and `/med-arb/` **as shipped**. That discharges blocker 2 and every
> wording sign-off that had been routed into it.
>
> ⚠️ **THE COUNT NOW READS ONE AGAIN, AND THE EARLIER ONE WAS A DEFECT — READ
> THE REASON, NOT THE NUMBER.** It said ONE earlier on 2026-09-02 because an
> approval had gone **missing** from the list; it says ONE now because the pass
> that approval was routed into has been **done**. A tally cannot tell those
> apart, which is the point the note below has been making all day.
>
> ⚠️ *(The count has moved repeatedly in one day and the DIRECTION is the only
> part worth reading — the number of moves is deliberately not stated, because a
@@ -421,12 +435,12 @@ Then invalidate `/*`.
> record is written and it does not call failure before **7 days**, so
> **start it before anything else on this page.** It is the one blocker
> that is a waiting period rather than a task.
> 2. **Pouya has not yet read every page against `AGENTS.md` §4.** The human
> pass the other half of D20, and not delegable. **It is also where the
> §Who can see it approval now lands:** Pouya ruled on 2026-09-02 that the
> read-through *is* the approval and that nothing is to be held open waiting
> on a separate wording sign-off. The item under **Copy and claims** below
> carries what to read first and why.
> 2. ✅ **DONE 2026-09-02 — Pouya read every page against `AGENTS.md` §4.** The
> human pass, the other half of D20 and not delegable. It was also where the
> §Who can see it approval was routed, and his ruling that the read-through
> *is* the approval means that sign-off is now discharged rather than
> pending. **Sole finding: the favicon's opaque ground.** No copy finding on
> any of the 23 pages.
>
> ✅ **CLOSED 2026-09-02 — Q64, MOOT.** It asked whether anyone else holds the
> AWS root password or its MFA device, because the page published *"has no
@@ -650,12 +664,25 @@ the decision is re-readable rather than re-litigated.
count has been wrong twice, and this line carried "eight" for a round after
the comment itself had been corrected to nine (`adversarial-reviewer`,
round 2). Read the list, not a number
- [ ] **Pouya has read every page against `AGENTS.md` §4.** The human pass. It is
the other half of D20 and it is not delegable — his reading is what the
per-step audit was traded for.
⚠️ **START WITH `/legal/privacy/` §Who can see it. IT IS BLOCKER 2 IN THE
CALLOUT ABOVE, AND THIS READ *IS* THE APPROVAL** — Pouya ruled on
2026-09-02 that nothing waits on a separate wording sign-off. Every
- [x] **DONE 2026-09-02 — Pouya has read every page against `AGENTS.md` §4.**
The human pass, the other half of D20 and not delegable — his reading is
what the per-step audit was traded for. **It returned one finding across 23
pages and that finding was not copy:** the favicon shipped with an opaque
cream ground. **The `/legal/privacy/` §Who can see it wording, the
SML Company Ltd consent line and `/med-arb/` as shipped are approved by
this read**, per his ruling that the read-through *is* the approval.
⚠️ **This does NOT discharge `claims-auditor`'s cutover pass**, which is a
separate item on this list: D20 traded the per-step machine audit for the
human pass **plus** one machine pass over the finished site, and one of
those two has now happened.
~~⚠️ **START WITH `/legal/privacy/` §Who can see it. IT IS BLOCKER 2 IN THE
CALLOUT ABOVE, AND THIS READ *IS* THE APPROVAL**~~ — **struck 2026-09-02:
the pass is DONE, and an unstruck imperative on a ticked item told an
operator to begin a read this page also records as finished, pointing at a
blocker that no longer exists** (`adversarial-reviewer`, round 2). What it
said remains true of what happened: Pouya ruled on
2026-09-02 that nothing waits on a separate wording sign-off, and he read
§Who can see it first. Every
sentence in it changed three times that day — Q62's ruling, Q63's, then the
ruling that cut it to **four plain statements** — and it is the only
section on the site whose subject lives entirely outside this repository.
@@ -972,7 +999,25 @@ the decision is re-readable rather than re-litigated.
instead. A `Disallow` will not do it: a blocked URL can still be listed.
Found by `adversarial-reviewer`, 2026-08-31
- [ ] Booking link works, including the no-JavaScript fallback — **conditional on R6**; booking is parked and `CONTACT.bookingUrl` is `null`, so nothing renders and this passes vacuously until a tool is chosen. **Nothing on `/contact/` mentions booking**, deliberately
- [ ] Favicon set complete
- [x] ✅ **Favicon set complete, and REGENERATED 2026-09-02 — it had shipped with
no transparency at all.** Pouya's read-through finding. All three frames
(16/32/48) declared a 32-bit alpha channel and then carried `alpha = 255`
on every pixel, the ground opaque cream — so the tab icon showed as a cream
rectangle on any dark tab strip. `public/favicon.ico` is now transparent,
regenerated by `npm run icons` from the committed master and verified
programmatically and by eye, on dark grounds and light.
⚠️ **`public/apple-touch-icon.png` STAYS OPAQUE CREAM AND MUST NOT BE
"FIXED" TO MATCH.** The reason is a platform behaviour — iOS composites a
transparent touch icon onto black — **stated by Pouya on 2026-09-02 and not
re-tested on a handset**; the item directly below is where it would be. The
touch icon is byte-identical across this change.
⚠️ **AND THE CHANGE IS NOT FREE ON DARK.** The maroon half of the ribbon
effectively drops out against a dark tab strip; the champagne half carries
the mark. **The figures are deliberately NOT repeated here** — they live in
`docs/reference/brand-assets.md` §The icon set, with the method, and a copy
on this page had already gone stale within a day by quoting the 32 px row
as if it were the general case (`adversarial-reviewer`, round 2). Read them
there.
- [ ] Tested on iOS Safari, Android Chrome, desktop Safari/Chrome/Firefox
- [ ] Tested at 320 px and at 200% zoom
- [x] ✅ **THE 200%-TEXT NAV OVERFLOW IS FIXED, 2026-09-01 — THIS ITEM IS
+148 -3
View File
@@ -5,9 +5,22 @@ is `CLAUDE.md`'s rule and `AGENTS.md` R14, and this file exists because the
infinity mark was reconstructed wrongly and **two adversarial review passes could
not catch it**, since the real artwork was not in the repo to compare against.
**Every measurement below is `[verified 2026-08-26]`** — computed with `sharp`
against the files in this repository, and re-derivable by anyone from the
commands given. Nothing here is quoted from an external source.
**The measurements here are computed with `sharp` against the files in this
repository**, and are re-derivable by anyone from the commands given.
**Unless a section says otherwise, every figure below is `[verified
2026-08-26]`. Where a section carries its own stamp, that stamp wins** — the
render ladders are `[measured 2026-08-27]` and §The icon set is `[verified
2026-09-02]`. ⚠️ *This line has been wrong in both directions in one week: first
as a blanket 2026-08-26 that was already stale for the ladders, then as
"each section carries its own stamp" when five sections carry none. It is a floor
plus overrides because that is the only shape that covers every section without
mis-dating one* (`adversarial-reviewer`, rounds 1 and 2).
⚠️ **Two things in §The icon set are NOT read off this repository and are
labelled where they appear:** the tab-strip colours used in the contrast figures,
and iOS's handling of a transparent touch icon. Neither is derivable from
anything committed, so neither is stamped as if it were.
## Files
@@ -103,6 +116,138 @@ Passing an explicit `width` is load-bearing: without it Astro emits the
untouched 2668 px master as the `<img src>` fallback — **1,146,406 bytes**
which any client without AVIF or WebP support would actually download.
## The icon set
`[verified 2026-09-02 — every figure below read off the icon files in this
repository, EXCEPT the two external constants flagged inline]`
| File | What it is | Ground |
|---|---|---|
| `public/favicon.ico` | 16, 32 and 48 px frames, each a PNG-encoded 32-bit RGBA image inside the ICO container | **Transparent** |
| `public/apple-touch-icon.png` | 180 × 180 | **Opaque cream `#faf7f2`, and that is deliberate — see below** |
**The two grounds differ on purpose, and this is the line that stops someone
"fixing" it.** ⚠️ *The reason is an external platform behaviour, not a repository
fact, and it is recorded here as what it is:* **iOS does not honour transparency
in a touch icon — it composites it onto black**, so a transparent touch icon
ships a black tile on the home screen. Stated by Pouya in his ruling of
**2026-09-02** and not independently re-tested here; **it has not been checked on
a handset in this repository, and the place that does check it is the unticked
`docs/06` item "Tested on iOS Safari…".** The tab favicon has the opposite
requirement: a tab strip is dark for many readers, and an opaque ground shows
there as a visible rectangle around the mark. So the favicon is transparent, the
touch icon is matted, and **neither should be changed to match the other.**
### Composition
The mark spans **7/8 of the canvas width**, centred on both axes — 14/16, 28/32,
42/48, and 158/180 on the touch icon. That was measured off the icons as they
already shipped, so a regeneration reproduces the composition rather than
restyling the mark.
### Regenerating
```
npm run icons
```
`scripts/icons.mjs` resizes `src/assets/brand/sml-infinity-mark.png` onto a
transparent square canvas and assembles the ICO container itself, because
`sharp` does not write `.ico`. **It writes the favicon only** and never touches
the touch icon. It refuses to run if the render source has no alpha channel, and
it asserts that the source is still byte-identical to the documented crop of
`sml-infinity-mark-master.png` — R14, so the icon stays traceable to committed
artwork. It verifies a **candidate** file and renames it into place only on
success, so a rejected build cannot replace a good favicon.
**It also carries the halo test**, because the check that matters was living
only in prose here while the generator could not run it
(`adversarial-reviewer`, round 2). For each frame it takes every painted pixel
touching a fully transparent one and measures how many sit within 20 of cream:
| | boundary px | near cream | share |
|---|---|---|---|
| correct, 16 / 32 / 48 px | 61 / 146 / 258 | 1 / 2 / 5 | **1.6 / 1.4 / 1.9 %** |
| haloed fixture, 16 px | 56 | 11 | **19.6 %** |
The gate is **10 %**, roughly 5× clear of both. ⚠️ **The first version of this
guard inspected only partial-alpha pixels and MISSED the fixture completely** —
a knockout sets alpha per pixel and leaves **no partial alpha at all**, so there
was nothing for it to look at. The fixture is built by matting the mark on cream
and then knocking the ground out by alpha, which is the defect exactly; it fires
at 19.6 % and leaves `public/favicon.ico` untouched. **A guard that cannot see
the defect it is named for is worse than none**, and this one passed the real
icon while blind, which is the shape that ends a check instead of starting one.
### Why the favicon was regenerated, 2026-09-02
It had **no transparency at all**: all three frames declared a 32-bit alpha
channel and then carried `alpha = 255` on every one of their 256 / 1,024 / 2,304
pixels, with the ground opaque cream `rgba(250,247,242,255)`. Found by Pouya on a
read-through, confirmed by parsing the container directly.
Three measurements stand behind the replacement.
**1. The composition did not change.** Composited back onto cream, the new icon
reproduces the old matted one to within **1 of 255 on every channel of every
pixel** at all three sizes (mean delta 0.010.02, 0 pixels over a delta of 8).
So the regeneration did not restyle, rescale or reposition the mark.
⚠️ **That is NOT a test for a halo, and an earlier draft of this file filed it as
one.** Composite-onto-cream returns ~0 whether the edge is correct *or* is a
cream-matted edge that has merely had its background knocked out — the second
case composites straight back to what it came from. Both branches pass, so the
test cannot discriminate. Found by `adversarial-reviewer`, 2026-09-02.
**2. No halo — the measurement that does discriminate.** Read the RGB the
partial-alpha pixels actually carry. A cream halo means that RGB is near cream; a
correct export means it is the ribbon's own colour. Of the partial-alpha pixels,
**0 of 94 / 300 / 603** are within 12 of `rgb(250,247,242)`; the nearest is 13
away, the mean distance is **174.0 / 178.3 / 177.0**, and the mean colour per
frame is about `rgb(118,86,74)` — maroon-brown, not cream and not black.
**3. Legible on dark and on light, and NOT at the same cost.** ⚠️ *The ground
colours below are external constants, not repository facts: `#202124` is
Chrome's dark tab strip and `#f1f3f4` its light one, both `[observed
2026-09-02]`.* All ratios are computed on **composited** pixels, and the ink
colours are **fully opaque** pixels only — a ratio taken from a raw channel value
is a claim about a colour that is never painted, which is how an earlier draft
came to quote `14.02:1` for a pixel whose alpha is 251.
| | darkest opaque ink | lightest opaque ink | vs `#202124` | vs `#f1f3f4` |
|---|---|---|---|---|
| 16 px | `rgb(70,33,33)` | `rgb(216,188,143)` | champagne **8.82:1** | maroon **12.58:1** |
| 32 px | `rgb(60,29,29)` | `rgb(234,214,172)` | champagne **11.28:1** | maroon **13.62:1** |
| 48 px | `rgb(61,26,26)` | `rgb(238,219,175)` | champagne **11.80:1** | maroon **13.86:1** |
Whichever ground it sits on, one end of the ribbon carries the silhouette. **But
the other end does not merely dim — on dark it goes.** The maroon lobe
composites to **1.041.15:1**, and counting pixels that reach 3:1 against each
ground gives **23 / 87 / 189** on dark against **40 / 168 / 378** on light, at
16 / 32 / 48 px. **So about half as many pixels reach 3:1 on dark as on light.**
⚠️ *Those pixels are not absent — they are painted and fall below 3:1, and the
mark's full outline is still there, dim. An earlier draft said "roughly half the
mark's visible pixels are absent", which is neither what was counted (pixels at
`alpha > 0` are 109 / 388 / 805) nor what a render shows* — `adversarial-reviewer`,
round 2. The previous opaque-cream icon was still more legible on dark.
**That is the price of the change and it was accepted, not overlooked.** What was
bought is the removal of a cream rectangle from every dark tab strip and from
white. Raised by `adversarial-reviewer`, 2026-09-02, against a draft that
recorded the change as costless.
**The obvious alternative is an icon pair keyed on `prefers-color-scheme`, and it
was NOT ruled out on measurement.** ⚠️ *An earlier draft dismissed it on a
mechanism that is wrong: it said the query keys off the page's colour scheme. It
does not — it reports the user's system or browser preference, and this site
declares `color-scheme` nowhere (`git grep -n color-scheme -- src`, exit 1), so
it would resolve to the same preference that makes the tab strip dark.* The real
objections are **untested here** and are recorded as such: `media` on
`<link rel="icon">` is unevenly supported for raster icons, a browser theme can
be set independently of the system preference, and it doubles the artefact
`npm run icons` has to keep in sync. **If this cost is ever revisited, that is
the option to test** — do not re-dismiss it on the reason struck above.
## The colours are the artwork's, not the palette's
`tokens.css` is not involved. The ribbon carries its own gradient and it is