# CRITIQUE — self-critique log

## Redesign note

The print-editorial version below (v1) shipped, then got direct
feedback: "boring... make it look graphical and out of the world."
That's not a note you iterate your way out of with another editorial
pass — it's a different brief. `BRIEF.md` was rewritten (concept
**THROUGHPUT**, dark neon palette, a generative canvas network as the
signature technique) and the site was rebuilt. The three passes below
are against that rebuild (v2), which is what's currently live. The
original v1 log is kept at the bottom of this file for the record —
it's real work, it just isn't the shipped direction anymore.

Screenshots via `tools/shot.js` at desktop (1440×900) and mobile
(390×844), top/mid/bottom scroll, plus per-section and per-breakpoint
captures for close reading. Console-error count is quoted from the
harness output each pass.

---

## v2 — Pass 1

**Console errors: 0** throughout this pass.

### What I found, looking at the pixels — and the numbers

1. **Contrast failure, again, in a new palette.** Same lesson as v1,
   different hex: I computed the actual luminance instead of
   eyeballing and `--text-faint` (`#6B7488`) measured **4.30:1**
   against `--void` — just under 4.5:1. Fixed to `#78839A` (5.29:1).
   I now treat "compute every text/background pair before calling a
   palette done" as a standing step, not a one-off fix.
2. **The same dead-zone problem, in graphical clothing.** The
   Projects and FAQ intros left 300+px of empty void on the right of
   a 1440px viewport — the exact failure from v1, just without margin
   notes to blame this time. A wide two-column lede with nothing in
   the second column is a mistake regardless of visual style.
3. **A ~250px void between Engagements and Projects** from stacked
   92-110px section padding, same shape as the v1 finding.
4. **Muddy gradient text.** The three-stop `--ai → --ecom → --supply`
   gradient looks vivid at the word level but slightly washed at the
   letter-level transition zones (particularly through the middle of
   short words like "for fun").

### What I changed

- Fixed `--text-faint` and re-verified every pair with a luminance
  script (`--text-faint` 5.3:1, others 6.2–18.6:1, gradient headline
  text checked at its darkest mid-blend point too — worst case ~7:1).
- Introduced a `.lede-grid` + `.signal-card` pattern: the empty second
  column now carries a real graphical stat (flagship count, workshop
  repo count, FAQ answer count) instead of sitting empty.
- Tightened section padding 110px → 92px.
- Added a subtle colored `drop-shadow` glow behind all gradient text,
  which both fits the neon aesthetic and disguises the muddier
  mid-transition letters.
- Rewrote the `@media print` block to redefine the CSS custom
  properties (`--text-primary`, `--text-soft`, etc.) for print instead
  of patching individual selectors — the dark theme was rendering
  near-white text on white paper before this fix, illegible.

### The upgrade

The hero network is now **mouse-reactive** — nodes within ~170px of
the cursor drift toward it with an eased pull, dragging their
connected routes with them, then settle back when the cursor leaves.
The network hero is the site's signature technique; it should feel
alive, not like a looping video. Verified with a synthetic
`page.mouse.move()` test that node positions actually shift, that it
produces zero console errors, and that it's skipped entirely under
`prefers-reduced-motion` (no listeners attached, single static frame).

---

## v2 — Pass 2

**Console errors: 0** throughout this pass.

### What I found

1. **Engagement tiles read flat.** Eight plain-text tiles in a grid,
   no color at all, sitting directly below a page that otherwise
   leans hard into a vivid three-color signal system. Next to the
   hero, About, and Timeline, this section looked like it belonged to
   a different, more boring site.
2. Re-checked the print stylesheet after the pass-1 variable-override
   fix by actually rendering it (`emulateMediaType('print')`, waited
   for the transition-timing artifact I got burned by in the prior
   redesign) — confirmed it holds up: legible ink, no near-white
   text, the accent CTA no longer prints as a filled block.

### What I changed

- Added a 2px top accent bar to each engagement tile, cycling through
  the three signal colors, so the grid reads as part of the same
  system as the rest of the page instead of a plain data table.

### The upgrade

The three pillar chips in the hero ("AI," "E-Commerce," "OMS · WMS ·
Supply Chain") now **highlight the matching-colored nodes in the live
network** on hover/focus — matching nodes grow and brighten, everything
else dims. It ties the one piece of static UI copy in the hero
directly to the generative visual underneath it, so the network reads
as literally representing those three domains rather than just
decorating them. Verified with a synthetic hover test and confirmed
zero console errors; also wired to `focus`/`blur` (not just mouse) so
it's reachable by keyboard, and forces a redraw under
`prefers-reduced-motion` so it still responds to the interaction
instead of silently doing nothing.

---

## v2 — Pass 3

**Console errors: 0** throughout this pass.

### What I found

1. **Real constitution violation: imagery wasn't WebP or under
   200KB.** `assets/portrait@2x.jpg` is 216KB and both portraits were
   JPG — the constitution is explicit ("Any imagery ≤ 200 KB WebP")
   and I'd missed it in both the v1 build and the v2 rebuild. Checked
   file sizes directly instead of assuming the original assets were
   fine because they'd shipped before.
2. **Self-inflicted bug while adding the pass-3 upgrade.** I added a
   magnetic-hover handler gated on `if (!reducedMotion)`, but declared
   the `reducedMotion` variable *later* in the same script, in the
   stat-counter block. Because of `var` hoisting the check ran against
   `undefined` — meaning the effect would have run for
   reduced-motion users regardless of their preference, silently
   defeating the exact accessibility guarantee this build is supposed
   to make. Caught it by grepping variable declaration order, not by
   visual inspection — this class of bug doesn't show up in a
   screenshot.

### What I changed

- Converted both portraits to WebP via `ffmpeg` (`portrait.webp` 32KB,
  `portrait@2x.webp` 110KB, both under the limit) and pointed the
  `<img>` `src`/`srcset` at them. Left the OG/Twitter/JSON-LD image
  tags on the `.jpg` versions deliberately — social-preview crawlers
  have inconsistent WebP support, and the constitution's size rule is
  about page-weight, not link-preview metadata.
- Moved the `reducedMotion` declaration to the top of the script,
  before anything reads it, and deleted the stale second declaration.
  Verified with a synthetic test: normal motion produces a non-empty
  `transform` on mousemove, reduced motion produces none, in both
  cases zero console errors.

### The upgrade

**Magnetic hover** on the primary hero CTA — the button drifts a few
pixels toward the cursor as it approaches, spring-returns on
mouseleave. A small, well-worn but genuinely satisfying interaction
that reinforces "this page is alive," matching the network hero
instead of competing with it. Confirmed inert under
`prefers-reduced-motion`.

---
---

# v1 log (retired direction — print-editorial)

*(Kept for the record. This is the critique log from the print-grade
editorial build that preceded the graphical rebuild above.)*

## Pass 1

**Console errors at start of pass: 3** — `frame-ancestors` CSP directive
warned via `<meta>` (invalid delivery method), and a 404 on
`/favicon.ico`. Fixed immediately (removed the meta-incompatible
directive, added an inline SVG favicon matching the doc-stamp mark).
**Console errors after fix: 0.**

### What I found, looking at the pixels

1. **Dead zones on every wide section.** Experience, FAQ, and the
   Projects intro paragraph all cap their text at a reading measure
   and then leave 40–50% of a 1440px viewport completely empty — no
   rule, no note, nothing. For a site whose entire premise is "every
   margin earns its keep," an empty margin is the one thing it cannot
   have. This was the single biggest failure of the first draft.
2. **Drop-cap bug.** `.about-prose p:first-of-type::first-letter` was
   supposed to style only the opening paragraph. Because the second
   paragraph is wrapped in its own `.annotated` grid container, it
   becomes "first of its type" *relative to that new parent* — so
   both paragraphs rendered with an oversized drop cap.
3. **Section-boundary void.** Between the Engagements table and the
   Projects heading, two stacked 104px paddings plus hairline rules
   produced a ~250px band of nothing on the mid-scroll capture.
4. **Orphaned trailing rule** under the last certification.
5. Contact/colophon section: colophon paragraph sits alone with the
   whole right two-thirds of the screen empty.

### What I changed

- Rebuilt each Experience entry as a three-column row (date/location,
  body, margin note) so the annotation rail is persistent, not a
  one-off. Notes pull only facts already stated elsewhere.
- Gave FAQ a sticky rail note explaining why the section exists.
- Added a margin note to the Projects intro paragraph.
- Fixed the drop-cap bug with an explicit `.dropcap` class.
- Tightened section padding (104px → 88px).
- Removed `border-bottom` from the last child in education/cert lists.
- Added a closing stamp — "AS-BUILT · VERIFIED" — to the footer.

### The upgrade

A hidden terminal, reachable by pressing <kbd>`</kbd> or clicking
"Open the terminal." Answers `whoami`, `ls`, `stack`, `contact`.

---

## Pass 2

**Console errors: 0** throughout this pass.

### What I found

1. **Real contrast failure.** `--ink-faint` (`#8A8272`) came out to
   3.4:1 against paper — fails WCAG AA. Fixed to `#655F52` (4.8:1).
2. **Masthead wraps at tablet width.** Never checked 768px until this
   pass. Fixed with a breakpoint that shortens the wordmark.
3. **Print stylesheet had a real defect.** The primary CTA printed as
   a solid black-filled box.

### What I changed

- Darkened `--ink-faint`, recomputed every contrast pair.
- Added a tablet breakpoint dropping the "— As-Built" suffix.
- Added `text-decoration: none` to `.mast-brand` for consistency.
- Fixed the print button to render outlined instead of filled.

### The upgrade

A "Print this record" trigger wired to `window.print()`.

---

## Pass 3

**Console errors: 0** throughout this pass.

### What I found

1. **Real horizontal-scroll bug at 375/390px.** `scrollWidth` 475px
   vs. `clientWidth` 375px — caused by an unwrapped decorative
   `.field-strip` under the hero.
2. Confirmed via automated check: single `<h1>`, clean heading
   sequence, zero images missing `alt`, correct landmark counts, zero
   links without accessible text, visible focus ring, skip link is
   the first tab stop.

### What I changed

- Gave `.field-strip` `flex-wrap: wrap` with proper gap. Re-verified
  zero overflow at 375, 390, 768, and 1440.

### The upgrade

**Redline mode** — a toggle highlighting every margin note and
footnote marker at once, since as-built drawings are literally
produced by redlining the blueprint by hand. (Retired along with the
editorial concept it was built for.)
