Skip to content

ar5iv-css modernisation: tokens, themes, a11y, three-engine harness - #39

Merged
dginev merged 67 commits into
mainfrom
glowup
Jul 5, 2026
Merged

ar5iv-css modernisation: tokens, themes, a11y, three-engine harness#39
dginev merged 67 commits into
mainfrom
glowup

Conversation

@dginev

@dginev dginev commented May 15, 2026

Copy link
Copy Markdown
Owner

Summary

  • Five iteration cycles of CSS modernisation, ending at best-in-class for the scholarly-document scope: 47-paper corpus, three engines (Chromium / Firefox / WebKit), AAA contrast on all themes (light / dark / sepia), one real user-reported bug fixed (Color issues (\rowcolor) arXiv/html_feedback#3642).
  • Design-token surface in css/ar5iv/tokens.css + docs/TOKENS.md; light-dark()-driven theming; sepia theme; prefers-contrast: more mode.
  • Accessibility layer (css/ar5iv/a11y.css): :focus-visible, keyboard footnote popovers, prefers-reduced-motion, :target, ::selection, forced-colors.
  • RTL-ready logical properties throughout ar5iv.css (margin-inline-*, inset-inline-*, text-align: start/end, etc.).
  • Visual-regression harness in tools/visual.mjs — Playwright across Chromium/Firefox/WebKit, paginated rendering for long math papers (single fullPage for short, viewport-tall chunks for over-limit), per-engine baselines under tools/baseline/<engine>/ (gitignored), 320 + 1280 viewport × {light, dark} matrix.
  • Real bug fix: arXiv:2403.11784 Table 3 black-on-dark headers (Color issues (\rowcolor) arXiv/html_feedback#3642). Re-enabled the legacy style=\"color:#000000\" dark-mode rescue after verifying arxiv-browse's downstream theme only covers the modern --ltx-fg-color:#000000 form.
  • CI lint pipeline at .github/workflows/ci.yml: stylelint + TOKENS.md ↔ tokens.css drift check + lightningcss build.

Notable trade-offs

  • Visual harness is per-developer (each runs --update locally). Shared-baseline tarball deferred until same-machine AA-drift determinism is pinned down — current pixelTolerance is 400.
  • arXiv:2105.10386 (~50k DOM nodes, ~400k px tall) crashes the renderer on all three engines around chunk 140-170; harness reports skip-renderer-crash with partial coverage of the first ~57 %.
  • :has() and light-dark() are Newly-Available (not yet Widely-Available), policy-exempted per docs/BASELINE_AUDIT.md. Projected un-gated Widely-Available milestone: Nov 2026. oklch(from …) Jan 2027 (currently @supports-gated with HSL fallback).

Test plan

  • npm ci
  • npm run lint — stylelint + drift check both pass
  • npm run build — produces dist/ar5iv.min.css
  • node tools/visual.mjs --update then node tools/visual.mjs — Chromium baseline + verify pass
  • (Optional cross-engine) --engine=firefox and --engine=webkit after npx playwright install firefox webkit (WebKit also needs sudo apt-get install libavif16 on Linux)
  • Open `examples/ar5iv-2403.11784.html#S3.T3` in Chrome, toggle `document.documentElement.setAttribute('data-theme','dark')`, verify Table 3 headers render white (the fix from commit e383182)
  • CI workflow runs green on this PR's first push

🤖 Generated with Claude Code

dginev and others added 30 commits May 13, 2026 22:00
Modernise the ar5iv theme without changing any rendered output for
the existing in-tree arXiv-ID regression corpus. The starting-point
audit is preserved verbatim in docs/GLOWUP_AUDIT_START.md; the
decisions and surprises behind each phase are recorded in
docs/GLOWUP_WISDOM.md.

Structural moves
- Introduce @layer order (reset, tokens, base, structure, components,
  math, fixes); leave existing rules un-layered (implicit highest
  priority, so cascade behaviour is preserved).
- Extract design tokens to css/ar5iv/tokens.css. New tokens:
  --border-hairline / --border-double, --duration-fast / --ease-out,
  --z-below / --z-page / --z-popover, --focus-ring*, --surface-subtle,
  --text-color-author-black-dark. Theme-aware tokens now use
  light-dark() with color-scheme rules on :root[data-theme].
- New css/ar5iv/a11y.css: :focus-visible ring, keyboard-accessible
  footnote popovers (:focus-within), prefers-reduced-motion guard,
  :target pulse, ::selection, forced-colors mapping.
- New css/ar5iv/print.css: force light, inline footnote content,
  external-URL annotation, break-inside discipline, @page margin.
- Fix the @supports probe to actually test relative-colour syntax
  (oklch(from white l c h)) rather than calc-in-arguments.
- Mirror image-filter / svg padding under @media (prefers-color-scheme:
  dark) so OS-dark users without a JS-set data-theme still get the
  matching behavioural overrides.

Code hygiene
- Replace pre-flow-root clearfix with display: flow-root.
- All media queries converted to modern range syntax with a
  consistent < / <= / > / >= boundary discipline; 0.01rem-offset
  edges removed.
- Replace 100vw with 100dvw for author-block absolute centring.
- Wrap epigraph width / margin in min(100%, …) so they stop
  overflowing at narrow viewports.
- Drop text-justify: inter-word at all 6 sites (default text-justify:
  auto is correct per script).
- Remove dead vendor prefixes (-webkit-/-moz-/-ms-/-o- on transition,
  transform, columns).
- Replace opacity: 100 (clamps to 1) with opacity: 1 throughout.
- Remove dead z-index: 100 on a static element, dead box-sizing on a
  single class, dead align-self: normal, commented-out B2 rule.
- Substitute named CSS colour `whitesmoke` with --surface-subtle
  token (theme-aware).
- Replace `border-width: thin` keyword with the tokenised hairline.
- Frame the transformed-wrappers !important cluster (~50 lines, ~6
  !importants) as one feature flag with a banner comment, not six
  independent decisions.

Distribution
- Add &display=swap to the three Google Fonts @import URLs.
- package.json: exports map, style field, sideEffects allowlist,
  files allowlist, https repository URL, author-name typo fix.

Docs
- docs/TOKENS.md — token reference.
- CONTRIBUTING.md — layer-order, :is/:where, regression corpus
  discipline, what-not-to-change.
- README.md refreshed with cross-links.
- Cross-link rfc_latexml_custom_properties.md from the top of
  ar5iv.css.

Deferred (catalogued in GLOWUP_WISDOM.md): spacing/type/breakpoint
scales, container-query pilot, logical-property sweep, sepia / high-
contrast themes, OS-preference mirror for the OKLCH author-colour
inversion, stylelint config, font-face unicode-range subsetting, and
upstream LaTeXML changes for focusable footnote marks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…, a11y depth

CSS substance only; docs sync follows in a separate commit.

* dark-mode.css extracted as its own sub-file; implements the RFC's
  --fn-{fg,bg,border,fill,stroke}-color-to-dark-mode public override
  surface so downstream themes redefine transforms without forking.
* OKLCH/HSL inversion now fires under both [data-theme="dark"] and
  @media (prefers-color-scheme: dark) — closes the OS-preference gap
  that left os-dark users with un-inverted author colours.
* OKLCH supports branch unified: fill uses the bg scale (0.8237) and
  stroke uses the fg/border scale (0.7), matching the HSL fallback's
  107/100 split.
* Dark-mode WCAG AA contrast audit: --email-link-color darkcyan
  (4.48:1) -> #009999 (~5.3:1); --info-text-color #01719d (3.39:1) ->
  light/dark split with #3a9bcc (~5.9:1); --error-text-color #d52f36
  (3.77:1) -> #e85a60 (~4.8:1).
* a11y: [hidden] defensive reset; .ltx_note_mark touch-target inflated
  to ~24x24 px via transparent ::before (WCAG 2.5.8); MathML focus
  rule narrowed to keep the ring on interactive descendants.
* .ltx_no_dark_filter opt-out on the global image dark-mode filter.
* a11y.css breakpoint pair aligned with ar5iv.css's < 96rem / >= 96rem
  discipline (drops the residual 95.99rem split).
* 5-deep :not() chain on inline-image sizing rewritten as
  :not(:is(...)) — same semantics, comment-acknowledged ugliness
  resolved.
* Code-smell residue: four stale "untested" / "this is debatable"
  TODOs deleted (10 substantive TODOs retained); --fo_width legacy
  fallback dropped after verifying no current upstream consumer.
* Iteration-1 regression caught: display: flow-root on
  .ltx_page_content had pushed the title ~32 px lower than before
  by suppressing the parent-first-child margin-collapse the old
  :after-only clearfix preserved. Fix: dropped margin-top: 2rem from
  .ltx_document.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Docs-only follow-up to the previous commit; describes code that just
landed.

* rfc_latexml_custom_properties.md: corrected the worked example's
  selector (the prior [style*="color:"] would also match
  background-color: and border-color:); per-property gates added.
  Documented the foreground/border 0.7 scale and the HSL fallback's
  107/100 asymmetry — only the background 0.8237 was justified before.
* GLOWUP_WISDOM.md: appended iteration-2 findings — the flow-root
  margin-collapse correction, the YAGNI re-pass that deferred most
  of the iteration-2 audit plan, the .ltx_sr_only retraction, the
  --fn-* implementation note (with the var()-in-light-dark() pattern
  we chose not to ship), the OKLCH/HSL fill/stroke cleanup, and a
  final iteration-2 tally.
* GLOWUP_PHASE2_AUDIT.md: new frozen snapshot of the pre-iteration-2
  audit (the GLOWUP_PROGRESS.md content as it stood after three
  critique passes), mirroring the GLOWUP_AUDIT_START.md pattern.
* GLOWUP_PROGRESS.md: rewritten as the iteration-3 punch list with a
  16-row dimension checklist defining "best-in-class" concretely; the
  flat 10-item priority order and a hard-vs-soft dependency diagram.
* .gitignore: examples/ — local working directory for fetched arXiv
  pages with rewritten stylesheet links (manual eyeball harness while
  iteration-3 item #1 is pending).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Manual `npm publish` for now; jsDelivr / unpkg auto-mirror once
published. GH Actions auto-publish on tag deferred until the manual
flow is proven.

* package.json: version 0.8.4 -> 0.9.0 (matches the iteration-2
  contract additions: --fn-* API is a new public surface). Add
  `lightningcss-cli` devDependency, `build` script, and a
  `prepublishOnly` hook so `npm publish` always ships a fresh bundle.
  Add `dist` to `files` and a new `./min` export.
* `npm run build` -> dist/ar5iv.min.css (~46 KB, ~37 % smaller than
  the unminified source) + sourcemap. lightningcss inlines the four
  local @imports (tokens, a11y, dark-mode, print) into a single file;
  the @imports in ar5iv-fonts.css (remote Google Fonts URLs) are
  intentionally not inlined.
* .gitignore: dist/ (built locally, shipped via npm-publish only),
  node_modules/.
* README.md rewritten:
  - CDN recipe with jsDelivr / unpkg URL patterns
  - self-hosted-from-checkout recipe
  - local-build recipe
  - maintainer release flow (git tag + npm publish)
* package-lock.json committed for reproducible dev installs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Status table flips 8❌/2⚠️/6✅ → 7❌/2⚠️/7✅. Item #7 stays in the
priority list with a strikethrough/landed note so the numbering
through #8/#9/#10 (and the `#4#8` dependency cross-reference)
stays stable as more items land. Change-log entry added.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iteration-3 item #5 landed.

* css/ar5iv.css: wrap the bulk of rules in @layer components, plus
  the B1/B3 known-bug patches at the bottom in @layer fixes.
  The transformed-wrappers feature flag stays un-layered so its
  !important rules keep maximum priority over LaTeXML's inline
  style="transform:…" declarations (per the cross-layer !important
  inversion: un-layered !important beats every layered !important).
* `reset`, `structure`, `math` declared but empty — reserved as
  override slots for downstream themes that want to slot rules
  between ours.
* Build verification: lightningcss bundles correctly; 11/11
  .ltx_transformed_outer references end up un-layered in the
  minified output; B1 selector confirmed inside @layer fixes.
* CONTRIBUTING.md updated with the new layer placements and an
  explicit note on what stays un-layered.
* GLOWUP_PROGRESS.md: status table 7❌/2⚠️/7✅ → 7❌/1⚠️/8✅.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iteration-3 backlog is unchanged in scope; doc gains operability.

* New "How to pick this up" section at the top: standing workflow
  for each item (build verify -> open demos -> change -> build/eye
  verify -> status-table + change-log update -> commit), plus the
  YAGNI / !important-discipline / visual-changes-must-be-intentional
  house rules.
* Each unfinished item (#1 #2 #3 #4 #6 #8 #9 #10) gets a "Next move"
  paragraph: tool choices, concrete first action, conventions and
  pitfalls. No item now requires re-discovery from the frozen
  GLOWUP_PHASE2_AUDIT archive — that doc stays as deeper rationale
  but isn't load-bearing for tomorrow's first commit.
* Doc grew 250 -> 413 lines from the additions. Will shrink as the
  remaining items land.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A third phantom-margin finding in the same family as the iteration-1
flow-root regression. Iter-2 caught one (.ltx_document margin-top:
2rem), tonight catches the next level deeper.

.ltx_title.ltx_title_document had `margin-top: 1rem`, which
pre-iter-1 collapsed outward and was absorbed by .ltx_page_content's
4rem outer margin. Post-flow-root, the BFC blocks that collapse,
adding ~1rem visible drift.

First instinct was to delete the declaration. That made it worse:
Chrome's UA `h1 { margin-block-start: 0.83em }` re-asserts when the
author rule disappears, and at 1.7rem font-size that's ~1.4rem —
more than the 1rem the author rule had been masking. Correct fix
is `margin-top: 0` explicitly, not deletion.

Wisdom doc updated with both lessons:
- Audit every margin on the first-in-flow-descendant chain when
  introducing a BFC, not just the parent's first child.
- Deleting a CSS declaration is not equivalent to setting it to
  "natural" zero — for elements with non-zero UA styling, you
  have to write zero.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes iteration-3 item #10. Recipes cover the override patterns
authors actually need: re-decorate a single palette token; change
the dark-mode inversion strategy (no-inversion, stronger, or via
color-mix); add a third data-theme value (sepia + high-contrast
walk-throughs); ship a downstream npm package with @layer
ordering. Pitfalls section documents the light-dark(var(),var())
caveat, the color-scheme requirement per data-theme, @layer
first-appearance ordering, and the !important inversion rule.

Path-(b) of iteration-3 item #9 (demonstrated extensibility) is
now covered by the cookbook — the recipes are the worked example.
Path-(a) (a first-party alt theme) stays available if a user need
surfaces. Status table: 7 ❌ / 1 ⚠️ / 8 ✅ → 6 ❌ / 1 ⚠️ / 9 ✅.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes iteration-3 item #3. ~60 sites in ar5iv.css converted from
`margin-left/right`, `padding-left/right`, `left:`, `border-left/right`,
`text-align: left/right`, and `float: left/right` to their logical
equivalents — so a `<html dir="rtl">` document mirrors correctly
without requiring a separate stylesheet.

Stays-physical decisions (with a clarifying comment in the
borders section): LaTeXML-internal `.ltx_border_l/r/ll/rr/L/R/r_dashed`,
`.ltx_framed_left/right/leftright`, `.ltx_nopad_l/r`, and
`.ltx_align_left/right` keep `left/right` because LaTeXML emits
physical-side semantics in its class names. Intrinsically-LTR
content streams (listing line-number gutter, SVG `<text>`,
verbatim, conversion-report, math-overflow alignment,
`.ltx_INFO/WARNING/ERROR/FATAL`) also keep physical alignment.

The `.ltx_eqn_eqno.ltx_align_right .ltx_tag { float: right }` rule
stays physical because its selector is gated on the physical
`.ltx_align_right` class — author intent, not flow direction.

Float on `.ltx_note_outer` and `.ltx_bibliography dt` /
`.ltx_description dt` converted to `float: inline-end` /
`float: inline-start`. These are baseline-since-mid-2024
(Chrome 118, Firefox 121, Safari 17.5). On older browsers the
float degrades to static positioning — acceptable graceful
degradation.

Synthetic RTL demo at `examples/ar5iv-1910.06709-rtl.html`
(gitignored) for local verification.

Status table: 6 ❌ / 1 ⚠️ / 9 ✅ → 5 ❌ / 1 ⚠️ / 10 ✅.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes iteration-3 item #4 (partial — font-size scale deferred).

Spacing anchors: `--space-xs/sm/md/lg/xl` map to 0.5/1/1.5/2/4 rem,
the five values that the audit histogram showed accounting for
~70 % of margin declarations. Property-agnostic — same anchors
intended for padding/gap/inset when the value matches. Long-tail
literals (0.1, 0.2, 0.25, 0.3, 0.66, 0.75 rem) stay raw because
each was hand-tuned for a specific typographic context; snapping
them retroactively to a scale step would erase intent.

Line-height: `--line-height-prose: 1.5rem` — the dominant value
(7 consumers) at the body's 1rem font-size. Other line-height
values (`1rem`, `1.1rem`, `1.15rem`, `1.4rem`) stay literal —
each is a one-off hand-tune, not a scale step.

Font-size: deferred. Histogram is mostly singletons (12 distinct
values for ~24 sites). Defining anchor tokens would either
invent anchors or freeze hand-tuned values into a fake ladder.
Re-evaluate after a second sweep.

Migration: 45 margin sites + 7 line-height sites converted in
this commit. TOKENS.md updated with the new tokens and three
stale colour values brought current (`--email-link-color`,
`--info-text-color`, `--error-text-color` — iteration-2 had
bumped these for contrast but TOKENS.md still listed the old
values; computed contrast ratios on light/dark backgrounds now
spelled out in the table).

Status table: 5 ❌ / 1 ⚠️ / 10 ✅ → 4 ❌ / 2 ⚠️ / 10 ✅.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes iteration-3 item #8. `npm run lint` extends
`stylelint-config-standard` with allowlists for the LaTeXML-driven
naming conventions and the codebase's known `!important` clusters
(transformed-wrappers, per-rule inline-style defeats, `[hidden]`
reset, print stack). 45 surviving warnings are all `!important`
in those allowlisted clusters — kept advisory rather than promoted
to errors.

First run also surfaced 10 hard errors, all real cleanups:
- four deprecated `word-wrap: …` → `overflow-wrap: …` substitutions
  (modern spec name; same behaviour)
- one deprecated `word-break: break-word` keyword (the legacy
  alias for `overflow-wrap: break-word`)
- four single-colon `:before` / `:after` pseudo-elements promoted
  to the level-3 double-colon notation
- one dead rule: `span.ltx_personname span:first { font-size: … }`
  — `:first` is not a valid pseudo-class outside `@page`, so this
  has been silently inert for the lifetime of the file. Removed
  rather than guessing at the intended pseudo-class.

Bare-rem rule deferred. Now that #4 has tokenised the spacing
scale, a custom plugin could enforce "prefer `--space-*` over
bare rem", but it's ~80 LoC of churn-risk and the migration is
fresh — defer until a second offender ships.

Status table: 4 ❌ / 2 ⚠️ / 10 ✅ → 3 ❌ / 2 ⚠️ / 11 ✅.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iteration-3 item #2 partial — these are the bugs a code audit
can find; a full DevTools walk for content-driven overflow
(long URLs in footnotes, wide formulas, TikZ figures with big
embedded text) is still pending. The fixes:

- Author-block fly-out (.ltx_role_address/.ltx_role_affiliation,
  position: absolute) used `width: var(--main-width)` plus an
  unclamped centering offset. On a 20rem viewport the formula
  `calc((100dvw - 52rem) / 2)` evaluates to -16rem, pushing the
  block off-canvas. Now `width: min(--main-width, 100dvw)` and
  `inset-inline-start: max(0px, …)`.

- Conversion-report panel forced a literal `width: var(--main-width)`,
  overflowing any viewport narrower than 52rem. Switched to
  `max-width` + `width: 100%` + `box-sizing: border-box`.

- Title-page image cap was `max-width: 30rem`, which at 320
  CSS-px viewport still overflowed. Now `max-width: min(30rem, 100%)`.

- Footnote hover popovers — both the small-screen variant
  (gated by `(width < 96rem)`) and the wide-screen
  table/figcaption variant — declared `width: 20rem` plus
  `padding: 2rem`. Under default `content-box` that's a 24rem
  visual box inside a 20rem viewport. Added `box-sizing:
  border-box` so the padding eats into the declared width
  instead of adding to it.

- Epigraph fly-out math (blockquote.ltx_epigraph) used
  `min(100%, …)` for width and `min(50%, …)` for
  margin-inline-start — at 320 CSS-px those summed to 27rem in
  an 18rem column. Now both use the same 50/45 ratio with the
  same rem cap, so geometry stays inside the column at any
  width while preserving the wide-viewport intent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The hypothesis was: in a narrow iframe embedding inside a wide
host viewport, the sidenote ladder media query
(@media (width >= 96rem)) might pick the wrong band. Synthetic
test at examples/embedded-iframe.html (gitignored) — a 600
CSS-px iframe inside a maximised browser host — confirms the
opposite: the media query evaluates against the iframe's own
viewport when the iframe declares
<meta name="viewport" content="width=device-width">. Every
LaTeXML HTML output declares that. So the predicted gap doesn't
exist.

Container queries would still be useful in scenarios that don't
exist today (direct embedding without iframe isolation;
side-by-side dual-article reader). Per YAGNI, defer until a
real consumer surfaces. Test page retained for future
re-verification.

Status table: 3 ❌ / 2 ⚠️ / 11 ✅ → 2 ❌ / 2 ⚠️ / 12 ✅.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes iteration-3 item #9, both paths.

Path (a) — first-party sepia variant. `:root[data-theme="sepia"]`
declared in css/ar5iv/tokens.css with a cream/brown palette
(background #f4ecd8, text #5b4636 ≈ 7.4:1, link #6b4423 ≈ 7.3:1)
and `color-scheme: only light`. The `--fn-*` application rules
don't fire for sepia because their gate is `[data-theme="dark"]`
or `:root:not([data-theme="light"])` inside
`prefers-color-scheme: dark` — neither matches `data-theme="sepia"`,
so author-supplied colours render as-is on the sepia background.
Validates the palette-override pathway on every build.

Path (b) — cross-reference the production arxiv-browse vendor
theme as the real-world consumer. The cross-check surfaced one
real cookbook gap: the upstream `--text-color-author-black-dark`
token (added in iteration 2) was the exact answer to a TODO
comment in the downstream's stylesheet about wanting a dedicated
token for the dark-mode `\color{black}` rescue case. The cookbook
now calls this out in recipe 2 with a one-line override example.

Also added a new "Extending the token surface" section between
recipes 3 and 4 covering net-new downstream tokens for chrome
elements (headers, footers, nav, modal forms). The arxiv-browse
theme exercises this pattern heavily; the cookbook had only
implied it.

Also disabled `custom-property-empty-line-before` in stylelintrc
(the rule fires inside selector bodies that mix `color-scheme`
with custom-property declarations — true for the new sepia
block and not worth a layout split).

Status table: 2 ❌ / 2 ⚠️ / 12 ✅ → 1 ❌ / 2 ⚠️ / 13 ✅.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes iteration-3 item #1.

`tools/visual.mjs` renders the three corpus demos in
`examples/` (gitignored — populated via `examples/fetch.sh`) at
1280 CSS-px × {light, dark} using Playwright's Chromium, diffs
against the committed PNG baseline in `tools/baseline/` using
pixelmatch (per-pixel YIQ threshold 0.1, per-image pixel-count
tolerance 400). `npm test` runs the diff;
`node tools/visual.mjs --update` refreshes baseline after an
intentional change.

The retrospective evidence for this harness (the iteration-2
flow-root title shift, the post-flow-root UA-default margin
re-assertion, both bugs that survived multiple critique passes
before someone noticed) all manifests in the first viewport, so
the script renders first-viewport-only rather than fullPage.
That keeps the committed baseline at 1.4 MB total (vs 25 MB for
the fullPage variant — the 2407.16893 paper alone was 8 MB).
One-line change in the script flips to `fullPage: true` and the
baseline migrates to an out-of-tree tar.zst blob if a class of
below-the-fold regression starts slipping through.

One-viewport coverage (1280) was a similar judgement call:
the structural breakpoints sit at 52rem and 96rem media queries,
both visible from a mid-range viewport. Adding 320/768/1920
quadruples the baseline size for marginal coverage. Reflow-
specific regressions at the narrow edge are covered separately
by iteration-3 item #2 (the structural audit) and would benefit
from a dedicated narrow-viewport harness rather than fork the
visual-regression budget.

With this commit every iteration-2 regression class has a
mechanical guard: stylelint catches syntax/idiom drift,
visual.mjs catches geometric changes. Status table:
1 ❌ / 2 ⚠️ / 13 ✅ → 0 ❌ / 2 ⚠️ / 14 ✅.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A critique pass on the eight iteration-3 commits surfaced these:

1. Cookbook recipe 1 used `light-dark(#literal, var(--same-name))`
   to "preserve the dark branch" — that's a CSS cyclic dependency
   (Custom Properties L2 §5.1), the property becomes `unset`. The
   recipe didn't work as written. Rewritten to either inline both
   branches (look up the dark value in TOKENS.md) or override
   per-`data-theme` and per `@media (prefers-color-scheme: light)`
   separately. Added a "Why you can't var()-reference the original"
   explainer.

2. Cookbook recipe 2(b) described `oklch(from … calc(1 - 0.85 * l) …)`
   as "more contrast on near-black inputs". Wrong direction:
   near-black inputs map to near-white outputs at any scale.
   The actual effect is at mid-tone and near-white author inputs,
   where the larger scale spreads them across a wider output
   range — at the cost of mid-tone outputs landing closer to
   black, potentially below the contrast floor on a dark
   background. Renamed to "more dramatic inversion" with the
   trade-off spelled out.

3. Sepia contrast ratios in tokens.css were committed as ~7.4:1
   and ~7.3:1 without computing. Recomputed via the WCAG 2.x
   relative-luminance formula: 7.48 : 1 (text) and 7.19 : 1
   (link). Updated to the precise values with a brief note on the
   formula used.

4. GLOWUP_WISDOM.md augmented with six iteration-3 entries:
   stylelint surfacing a 6-year-dead `:first` rule; the
   arxiv-browse cross-reference closing a real token gap;
   "falsified by hypothesis test" framing for the container-query
   close; visual-harness scope as ship-working-iterate-later;
   the `light-dark(literal, var(--same))` cyclic-dependency
   mistake; the logical-property "stays-physical" judgment call
   on LaTeXML-internal classes; and the epigraph fix's
   moderate-viewport side-effect.

5. The iteration-2 wisdom entry on `--fo_width` reframed: the
   *hyphen-named* `--ltx-fo-width` is the LaTeXML contract
   (emitted from `lib/LaTeXML/Engine/TeX_Box.pool.ltxml:385`);
   the *underscore-named* `--fo_width` was a legacy fallback
   with no current producer. The iter-2 commit dropped the
   right one; the wisdom-doc framing as "undefined fallback is a
   real mystery" was slightly off.

6. CONTRIBUTING.md gained a Provenance section (ar5iv-css forked
   from LaTeXML.css; the `--ltx-*` and class-name surface is an
   upstream contract) and a "how to inspect a failed npm test"
   paragraph pointing at the three artefacts (fresh snapshot,
   diff PNG, baseline).

7. Container-query close on #6 reframed from "hypothesis
   falsified" (claims-empirically-disproved) to "spec-based
   reasoning plus a falsifiable artifact" (the test page exists
   and would catch a spec change). Closer to what actually
   happened.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds 320 × {light, dark} to the three-demo corpus, six new
baselines totalling ~210 KB. Baseline grew 1.4 MB → 1.6 MB.

The 320 viewport mechanically closes part of iteration-3 #2's
pending in-browser reflow walk — any future structural overflow
regression at the narrow edge now fails `npm test`. The five
reflow fixes already landed in `7e3fc18` (author-block clamp,
conversion-report max-width, title-page image clamp, footnote
popover box-sizing, epigraph width/margin ratio) are now
captured in the baseline; their failure modes would all show up
as horizontal-scroll-induced page-width changes that pixelmatch
catches trivially.

What's still in the ⚠️ partial state: the 400% zoom walk at
viewport 1280, and content-driven overflow that wraps fine at
baseline but might not after edits (long URLs, wide formulas).
Those are now small-scope tasks well-suited to a contributor
walk-through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous first-viewport-only scope was correctly called out as
misleading — it captured only the frontmatter (title, authors,
abstract, maybe first paragraph), missing everything below the
fold in every paper. A regression in the bibliography rules or in
a mid-paper figure had zero coverage. The earlier wisdom-doc
justification ("retrospective bugs all manifested in the first
viewport, so first-viewport is enough") was selection bias: we
caught those bugs *because* they were in the part of the page we
were looking at; bugs below the fold survive unnoticed, which is
exactly the class a harness exists to surface.

Changes:
- `tools/visual.mjs` now renders `fullPage: true` over every
  arXiv ID cited in `ar5iv.css` comments (47 papers — list
  duplicated in `examples/fetch-corpus.sh` for the bulk fetch).
- `examples/fetch-corpus.sh` (new): bulk-fetches the corpus via
  the existing `fetch.sh`, skips already-present, reports
  failures (one paper, 2105.10386, currently 503/404 across
  both sources).
- 1280 × {light, dark} only; 320 dropped from the matrix because
  fullPage at narrow viewports doubles disk cost without
  proportional regression coverage. WCAG-1.4.10 narrow-viewport
  reflow checks live under item #2 (a separate manual audit).
- Baselines moved out of git history. fullPage × 46 papers × 2
  themes is ~440 MB locally — too large to commit. Each developer
  generates with `--update`; CI's shared-truth strategy is a
  release-artifact tarball at `tools/snapshots-baseline.tar.zst`
  (deferred until a CI pipeline lands). Removed the six PNGs
  that were previously committed.
- `.gitignore` now uses `examples/*` instead of `examples/`,
  with `!examples/fetch.sh` and `!examples/fetch-corpus.sh`
  exceptions — `examples/<dir>` ignores the directory in a way
  that prevents !exceptions from working at all; `examples/*`
  ignores the contents but leaves the directory itself
  unignored, so the !exceptions resolve correctly. Bonus: both
  fetch driver scripts are now tracked in the repo (previously
  they were gitignored alongside the demos they fetched, which
  was a real onboarding hazard).

Render times: --update ~3 min, npm test ~5 min for the full
corpus (Playwright fullPage screenshots dominate). Both verify
clean: 92 snapshot(s) OK across 46 papers (the missing one
gracefully skipped).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 47-paper list was duplicated as a bash array in
examples/fetch-corpus.sh and a JS array in tools/visual.mjs.
Drift hazard — adding a paper in one place wouldn't pick it up
in the other.

Both scripts now read from tools/corpus.txt (one ID per line,
shell-style # comments and blank lines ignored). To add a paper
to the regression corpus: append a line. Both fetch and render
pick it up automatically on the next run.

Sync-with-source-of-source instructions (the comments in
ar5iv.css) remain documented at the top of corpus.txt for the
case when ar5iv.css grows new cited IDs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User confirmed no embedding plans for ar5iv-rendered papers (no
direct CMS-sidebar embedding, no side-by-side dual-article
reader). The earlier "deferred per YAGNI" close of iteration-3
#6 is therefore durable rather than speculative-revisit — there
is no future consumer the close is waiting on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ext-decoration

Per the iteration-3 critique-pass action on item Q3: the
arxiv-browse downstream theme replaces ar5iv-css's
`border-bottom: var(--border-hairline) dotted` with the standard
`text-decoration: underline dotted` idiom. The downstream pattern
is a clear upstream win:

- `text-decoration` is the semantically correct primitive for
  link underlines. CSS layout primitives like `border-bottom`
  shouldn't carry text-decoration meaning.
- `text-decoration-skip-ink: auto` (default in current browsers)
  causes the underline to skip text descenders. The old
  `border-bottom` approach drew straight across descenders,
  visibly clipping g, p, y, q, j.
- `text-decoration-thickness: from-font` matches the font's
  intrinsic underline-thickness metric — better typographic fit
  than a hard-coded `--border-hairline` (0.063rem) across all
  fonts. Old behaviour was acceptable on Noto Serif but no
  longer self-tuning for a downstream font swap.
- Removes the awkward `border-bottom: none` undo-sites and the
  cascade subtlety where ".ltx_graphics inside an .ltx_ref get
  an unwanted underline through them" was always implicit.

Sites changed:
- `.ltx_page_logo > a` — eLife-like link in the page-logo line
- `.ltx_ref` — the catch-all link
- `.ltx_ref > .ltx_graphics` — `inline` → `inline-block` so the
  underline doesn't leak into inline icons (ORCID etc.;
  retested 1707.04393, no regression)
- Four `.ltx_tocentry_*` rules — three `border-bottom: none`
  undo sites become `text-decoration: none`, one positive
  `.ltx_tag_ref` site becomes `text-decoration: underline dotted`

Harness behaviour: SIZE-mismatch on 80 of 92 snapshots (page
heights shrank by 16-30 px each — the old `border-bottom`
contributed a hairline to every link's inline-box, slightly
inflating line-heights; the new `text-decoration` draws within
existing bounds and recovers that height). The 12 papers
without `.ltx_ref` instances show only the residual
underline-position drift (45-54 pixels of diff each, well
below the 400-pixel tolerance). Baseline regenerated;
12-snapshot re-run confirms clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Project owner named this category during iteration 3: a class of
bugs where source content was originally typeset for A4 or Letter
paper and the fixed-width-print assumptions break under elastic
screen geometry. Examples: code listings overflowing on phone
viewports, oversized figures crowding a narrow column,
horizontally-scrolling tables.

The category clarifies the design space — reflow (fix on mobile,
break formatting), contain overflow (preserve format, add
per-element scrollbar), or constrain at source (re-emission).
Each axis has a different cost; the right answer varies by
content type and its load-bearing properties (a listing's
line-number column is load-bearing in a way prose isn't).

Includes an anti-lesson on `overflow-x: auto` as a tactic — it's
a one-line fix that frequently produces unwanted in-content
scrollbars at desktop viewports (where the content happens to be
slightly wider than `--main-width`). Always cross-browser
visual-verify before landing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Algorithm listings inside `<figure class="ltx_figure">` inherit
`text-align: center` from the figure rule (line 1780). Without
an explicit override, that centring cascades down through
`.ltx_listing` → `.ltx_listingline` (display: block) → each
line's inline content. The visible result is a middle-aligned
ragged-edge listing — line numbers, indentation columns, and
the leftmost character of each line all sit at different x
positions row to row.

Pin to `text-align: left` on `.ltx_listing`. Source code is
intrinsically LTR (line numbers, monospace columns, indentation
all assume left-to-right flow) so physical `left` is the right
semantic — `start` would change behaviour in a future RTL
document context, which is wrong for code.

Caught by the iteration-3 visual-regression harness in fullPage
mode (per the iteration-3 critique pass: first-viewport-only
would have missed every algorithm listing in the corpus,
including the screenshot example the project owner shared from
arxiv:1712.01103). Six papers in the corpus carry algorithm
listings; pixel diff ranged 25k–205k against the previous
mis-centred baseline. Manual cross-check in Chrome confirmed
the new layout matches the project owner's reference rendering.

The earlier-staged `overflow-x: auto` change to the same rule
was a tangent to this fix; rolled back pending separate visual
verification per the impedance-mismatch wisdom entry's
anti-lesson. Listings that overflow narrow viewports still push
horizontal scroll to the page for now — that's the standing
behaviour, not a regression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iteration sequence after the iteration-3 #9 follow-up identified
this as a real upstream win from the arxiv-browse stack. Four
visual rounds converged on the right shape:

1. Tried `width: 100%; box-sizing: border-box` on `.ltx_bibblock`
   to fill the remaining cell width. Worked for long keys; left a
   wide gap for numeric keys because `.ltx_tag` was locked at
   `width: 14%`.
2. Tried `width: max-content` on the tag column under
   `table-layout: fixed`. Browsers ignored or mis-interpreted the
   value; gap got wider, not narrower.
3. Tried `table-layout: auto` so the tag column would size to its
   actual key width. Worked per-bibitem but produced a *jagged*
   tag column across the bibliography — `display: table` is
   per-row, and table-auto distributed leftover width
   proportionally to max-content, giving short-content rows a
   wider tag column than long-content rows.
4. Refactored to a parent CSS grid with subgrid per bibitem.
   `.ltx_biblist` defines two tracks: `max-content` for the tag
   column, `minmax(0, 1fr)` for the entry content. Each bibitem
   participates via `grid-template-columns: subgrid` and
   `grid-column: 1 / -1`, so the *widest tag in the whole list*
   sets the column width and every entry lines up to it.

A bibitem has one `.ltx_tag` and *one or more* `.ltx_bibblock`
children (LaTeXML emits one bibblock per logical entry line —
title, authors, journal, etc.). Explicit placement: tag at
`grid-column: 1; grid-row: 1`; every bibblock at `grid-column: 2`,
auto-rows so they stack vertically. Earlier subgrid attempt
without explicit placement let bibblocks 2+ flow back into the
tag column — "completely broken" per the user.

Also two cosmetic adjustments confirmed visually:
- `padding-inline-start: 0` on `.ltx_biblist` to drop the <ul>
  UA-default 40px so `[1]` aligns under the "R" of "References"
  rather than indenting.
- `column-gap: 3em` for breathing room between the key and the
  entry text.

Removed the `inset-inline-start: -2.5rem` hanging-tag offset
that no longer makes sense under grid placement; tags now sit
flush at the column-1 edge, matching the user's reference
screenshot.

Subgrid is Baseline 2024 (Chrome 117, Firefox 71, Safari 16) —
narrower than the project's other Baseline-since-2023 deps
(OKLCH relative, light-dark()) but the visual fallback on
older browsers is gracefully degraded (the bibitem just renders
as its own grid).

Narrow viewport (<=52rem) falls back to a single-column stack
via `grid-column: auto; grid-row: auto` resets, so the
tag-then-blocks order falls out from source-order placement.

Visual harness: 92 snapshots regenerated. Manual cross-checks
on 2109.04981 (numeric keys) and 1708.02728 (BFR+13 author-year
keys) confirmed uniform alignment in both cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iteration-3 item Q4 answered. The rule pinned `.ltx_p` and
`.ltx_item` containing math with 7+ mrow descendants to
`text-align: left`, working around a 2023-era browser behaviour
where justification around a wide unbreakable formula produced
visibly large whitespace wells.

Obsolescence verified with the canonical trigger paper
(arXiv:2105.10386 "Analysis of Boolean Functions") loaded into
the corpus from a local cache (the paper is currently 503/404
upstream). With the rule disabled, the harness shows 0 pixels of
diff across the entire 47-paper corpus at 1280 × {light, dark} ×
fullPage — including 2105.10386 itself, which exercises the
selector. Current Chromium handles wide-formula justification
cleanly without the override.

Cross-engine verification (Firefox, Safari) deferred — Playwright
supports them via `firefox.launch()` / `webkit.launch()`, but the
harness today is Chromium-only. Will revisit when multi-engine
support lands.

Two wisdom entries added:
- "Defensive rules earn their keep by failing the test, not by
  surviving fear" — the test that retires a defensive rule isn't
  "does it still trigger" but "does removing it reintroduce the
  failure". The conservative bias toward keeping defensive rules
  is reasonable when the test is infeasible; "infeasible" deserves
  a hard look.
- Practical mechanism note: dropping a local LaTeXML output into
  `examples/ar5iv-<id>.html` substitutes for an unreachable
  upstream fetch — useful for testing against canonical trigger
  papers that have rotted out of public mirrors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verdict on best-in-class question: substantively yes for the
CSS capabilities a scholarly theme is expected to cover. Every
row of the 16-row dimension checklist has at least a strong
answer; 14 ✅, 2 ⚠️, 0 ❌. The two ⚠️ rows have well-defined
remaining work that doesn't block shipping.

Remaining open work, captured as iteration-4, is engineering
polish + real-world validation, not capability gaps:

  1. Cross-engine harness (Firefox + WebKit via Playwright)
  2. Parallelize harness rendering (5min → ~75s)
  3. CI pipeline + shared baseline tarball
  4. Triage `black-on-black-list.md` (15 user-reported issues)
  5. Two-column corpus expansion (waiting on user-supplied IDs)
  6. 400 % zoom DevTools walk for content-driven reflow
  7. Audit other dated workarounds for obsolescence (same shape
     as the Q4 MathML retirement that just landed)

No hard dependencies between items. Picks by appetite: #4 for
direct user impact; #1 + #3 for confidence; #2 for inner loop;
#7 for deletion-as-refactor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same disable-and-diff pattern that retired the Q4 MathML rule
(5e0521c). Each candidate was disabled, the harness run across
the 47-paper corpus, and the rule restored or deleted based on
whether any paper showed visible diff.

Removed (0 pixels of diff across corpus):

- `.ltx_quote .ltx_break + .ltx_break + .ltx_break { display: none }`
  Original "paper over" for arXiv:1910.06706 where a quote
  contained a child <p> with many trailing <br>s. The current
  LaTeXML rendering of that paper has zero `<br/>` tags — the
  defensive rule has no consumer.

- `.ltx_TOC > h6` selector from the title-styled list. When
  LaTeXML emits `<h6>` inside `.ltx_TOC` it carries
  `class="ltx_title ltx_title_abstract"`, which already matches
  the `.ltx_title_abstract` selector in the same rule list.
  The h6 selector was a redundant alias.

Confirmed load-bearing (visible diff under the test):

- `.ltx_cite .ltx_font_italic { font-style: normal }` — 4 corpus
  papers diff substantially when removed (italic spans inside
  cites still emit). Comment updated to reflect *why* the rule
  exists rather than the unhelpful "paper over" framing.

- `.ltx_para > .ltx_break:first-child:last-child { display: none }`
  — 4 corpus papers grow by 48px each (one extra blank line)
  when removed. Bare-break paragraphs are still in LaTeXML's
  emission. Comment updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iteration-4 item #2. Drains a flat task queue (47 papers × 2
themes = 94 tasks) with N concurrent workers sharing the single
Chromium browser instance via separate Playwright contexts.
Default concurrency 4, override via `CONCURRENCY=N npm test`.

Wall time on this machine: ~5:30 serial → 4:13 at 4-way
concurrency (~25 % improvement). Smaller win than the 4x naive
expectation — Playwright contexts are competing on screenshot
encoding rather than render-bound, so the bottleneck shifts to
PNG serialization once a few renders are in flight. Higher
concurrency (CONCURRENCY=8) might gain marginally on 8+ core
machines; 4 is the safe default.

Also a stale corpus.txt dedup: an earlier accidental append
duplicated 2105.10386 and was deduplicated via awk, which
collapsed some empty `#`-comment separators in the same pass.
Cosmetic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ll-row hover

Three refinements to the iteration-3 subgrid bibliography (3513fb0)
driven by per-screenshot project-owner feedback during iteration-4.

1. Tag column cap. `grid-template-columns: max-content` let very
   long author-year keys (e.g. "Chandler and Kerswell [2013]")
   push the column wide enough to leave very little room for the
   entry. Switched to `fit-content(12em)` — short keys (numeric
   [1], short author-year [Wil16]) get their natural width;
   keys longer than ~12em (about 20 body-text chars) wrap inside
   the cap rather than starving the entry column. Removed the
   `white-space: nowrap` on the tag for the wide-viewport rule —
   the cap means we *want* long keys to wrap. Replaced with
   `overflow-wrap: break-word`.

2. Baseline alignment. `align-items: baseline` on the bibitem
   grid container so the tag's "[1]" baseline lines up with the
   bibblock's first-line baseline. Default `align-items: stretch`
   was making the bibblock content render a hair below the tag's
   visual top — barely perceptible but the project owner spotted
   it.

3. Whole-bibitem hover highlight. The previous rule
   `.ltx_tag_bibitem ~ *:hover` only highlighted siblings of the
   tag — hovering the bibblock left the tag unhighlighted, and
   vice versa. `.ltx_bibitem:is(:hover, :active) > *` propagates
   the highlight to both children when hovering anywhere in the
   bibitem.

Baseline regenerated 2026-05-14.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dginev and others added 28 commits May 15, 2026 12:10
After `sudo apt-get install libavif16`, `playwright.webkit` runs
through the existing `--engine=webkit` path with zero harness
changes. A partial WebKit baseline (5491 PNGs across 30 papers,
~67 % of full) was generated before host memory pressure cut the
unbounded run short.

The primary-task validation rationale for landing item #2 — the
Chromium-only deletion of the 7-mrow MathML `text-align: left`
workaround (commit 5e0521c) — was rechecked on arXiv:1502.04633
chunks p003 and p008 (dense MathML, displayed equations,
observation environments). Both Chromium and WebKit justify the
surrounding paragraphs cleanly; the original "large whitespace
wells" the workaround was designed to prevent do not reappear in
WebKit. The deletion stands cross-engine.

Doc touch-ups:
- CONTRIBUTING.md: Firefox screenshot-height-limit caveat replaced
  by an engine-agnostic note pointing at the paginated path that
  landed yesterday (a9d9177).
- GLOWUP_PROGRESS.md item #2 marked landed; pickup banner refreshed
  with finish-the-baseline-with-guards as the recommended next pick.
- tools/visual.mjs: trivial comment cleanup ("TBD when libavif16
  ships" no longer accurate).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Today's guarded WebKit-baseline retry hung at 0 % CPU on the
first missing paper (2106.15835) for 7+ minutes before being
killed. Root cause: the HTML's `<meta property="og:image">` and
`<meta name="twitter:image">` reference a remote URL; WebKit's
`waitUntil: 'networkidle'` blocks on it under headless-no-network,
while Chromium handles social-card image fetching differently.
Resuming the bare run would hang again.

Pickup banner now carries a six-step recovery plan:
1. Block external URLs via `context.route()` (root cause)
2. Switch `waitUntil` from 'networkidle' to 'load' (defensive)
3. Add a `timeout: 15000` to `page.goto` (safety net)
4. Spot-check one short paper first; treat any pixel-diffs the
   same as the 2026-05-15 AA-drift playbook
5. Then re-baseline the 17 missing WebKit papers under guards
6. Acceptance: full 47-paper WebKit coverage with no PASS-fails

No code change committed — the fix is the next session's first
work item. Carries forward the system-instability caveat
(ulimit + timeout + CONCURRENCY=1 stay required).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three small changes to `tools/visual.mjs` `renderAndDiff`:

1. Add `context.route('**/*', ...)` that aborts ONLY remote image
   requests. Originally the WebKit run hung on a remote
   `<meta property="og:image">` fetch under `waitUntil:
   'networkidle'`. A blanket abort (any non-`file:` URL) was the
   first attempt, but spot-check on 0708.2787 surfaced an 85 px
   page-height shift — the archived HTML references
   `https://ar5iv.labs.arxiv.org/assets/ar5iv-site.0.2.2.css`, a
   third stylesheet that does affect layout. The surgical
   `resourceType() === 'image'` filter blocks the OG/twitter
   image fetches (the actual hang trigger) while letting
   stylesheets, scripts, fonts, and document fetches through.
2. `waitUntil: 'networkidle'` → `'load'`. The page's own load
   event is enough for static archived HTML; networkidle waited
   on the trickle of side-channel requests we now mostly avoid.
3. `timeout: 15000` on the `page.goto` call. Safety net so any
   future similar hang fails fast and surfaces as ERROR instead
   of stalling the whole worker.

Verified:
- WebKit on arXiv:0708.2787 produces 0 px diff against the
  existing baseline (image block is layout-neutral for WebKit).
- Chromium on arXiv:0708.2787 shows the same same-machine AA
  drift signature as the pre-change bisect — environmental, not
  code-caused.
- WebKit on the previously-hanging arXiv:2106.15835 now
  completes cleanly in ~3 min (172 snapshots produced).
- The 17 missing-WebKit papers re-baselined under guards
  (CONCURRENCY=1 timeout 1500): 4557 snapshots OK, exit 0.

`tools/baseline/webkit/` now covers all 47 papers. Closes
iteration-5 item #2 fully; banner refreshed; iteration-5 item #1
(CI pipeline + release-artifact baseline tarball) becomes the
next pick.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cross-engine audit pass on the iteration-3 #7 paper-over candidates,
now that WebKit is wired up alongside Chromium.

Substantive finding:

  `.ltx_cite > .ltx_ref { display: inline-block; line-height: 1.4rem }`
  (line 246) is *still* load-bearing in 2026. Disable-and-diff on
  arXiv:2006.09882 (the paper's Introduction `[10, 24, 44]` cite
  cluster, named in the rule's original comment) shows:
   - Chromium: 2544 px diff at chunk p012
   - WebKit: 8753 px diff at chunk p004, downstream reflow cascade
  Removed the open-question wording ("Maybe a better approach
  exists?") and replaced it with a date-stamped verification record
  anchored to the specific corpus paper and diff magnitudes.

Untestable candidates:

  `.ltx_item > .ltx_theorem` (line 1097) — no `.ltx_item` element
  with `.ltx_theorem` direct child exists in the 47-paper corpus.
  Selector never matches. Preserved defensively.

  Lines 1818 / 2090 / 2093 — these defend against inaccurate LaTeXML
  output (width estimations, transform: scale()). Disable-and-diff
  can't verify them: the existing baselines also use these overrides,
  so the test would tautologically pass. Real verification needs
  ground-truth (the source PDF) which the harness lacks. Documented
  as out of scope.

Net: one declarative update in CSS, zero deletions. Audit produced
confidence, not code reduction. The cite-ref documentation now anchors
to the cross-engine evidence (matching the 5e0521c Q4 methodology) so
the question doesn't need to be re-asked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `.github/workflows/ci.yml` covering the regressions that don't
need browsers:
  - stylelint against `stylelint-config-standard` with project
    overrides
  - TOKENS.md ↔ tokens.css drift via `tools/check-tokens.mjs`
  - lightningcss bundle build (catches brace mismatch and broken
    @-rules)

Job runs on `ubuntu-latest`, Node 22, with npm caching. Built
`dist/` uploaded as a 14-day artefact. Concurrent PR runs cancel
in-progress.

Phase 2 — running the visual-regression harness on CI with shared
baselines — deferred until two questions are answered: where to
host the release-artifact tarball (GitHub Releases attachment
vs. equivalent), and how to handle AA drift (same-machine surfaces
24-85 px page-height variance between `--update` calls; across CI
runners the noise floor will be larger and either pin OS/fonts or
accept-and-document is needed). Documented in the iteration-5 #1
entry so the question doesn't get re-asked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eight commits today on `glowup`; iteration-5 items #3, #2, and
#1-phase-1 all closed; paper-over audit done. Banner now lists all
eight commits, points at the two open questions for #1 phase 2
(hosting + AA-drift) as the next decision-point, and notes a
primary-task-aligned alternative (deeper paper-over audit pass)
for anyone who'd rather keep doing CSS-confidence work than
tooling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Issue #3642 (arXiv:2403.11784) reports that some text remains black
in dark mode. Verified: Table 3 ("FTENTh Hardware Architecture")
emits three `<span style="color:#000000;">` header cells
("Component", "Manufacturer", "Model") that render black-on-dark
and become invisible. arxiv-browse's downstream theme
(arxiv-html-papers-theme-{20250131,20250916}.css) rescues only the
*modern* `--ltx-fg-color:#000000` form, not the *legacy*
`color:#000000` form — so legacy emissions fall through both
layers.

This rescue rule existed in `css/ar5iv/dark-mode.css` and was
disabled in a previous audit on the assumption that arxiv-browse
covered the legacy case. The evidence (inspection of both
downstream stylesheet versions) shows the assumption was wrong.
Re-enabling fixes the bug on both arxiv-html and ar5iv-labs
consumer paths.

Selector pattern unchanged:
  [data-theme="dark"] :is(
    [style^="color:#000000"],
    [style*=";color:#000000"]
  ) { color: var(--text-color-author-black-dark) !important; }
Two attribute selectors so neither `background-color:#000000` nor
`border-color:#000000` get false-positively rescued.

Spot-check 2403.11784: chunk 1280-dark-p007 shows 1287 px diff
(Table 3 headers now legible), every other chunk 0 px PASS —
surgical fix. Verified cross-engine; both Chromium and WebKit
baselines refreshed for the six corpus papers with legacy
emissions (2409.12111: 2192 sites, 1909.02255: 69, 2006.13760: 34,
1910.06706: 17, 2403.11784: 3, 2105.00613: 2).

Iteration-5 item #4 (triage user-tracked bug backlog) closed.
13 of 15 issues in `black-on-black-list.md` were already closed
by the project owner (paper-by-paper regenerations). The two
remaining: #3461 stale (production v1 now has 0 legacy emissions),
#3642 fixed by this commit. The `\rowcolor darkgray` literal-leak
half of #3642 is a LaTeXML upstream emission (`<span
class="ltx_ERROR undefined">\rowcolor</span>`) — out of CSS scope.

Closes #3642 (CSS half), supersedes wontfix on #3461.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ten commits today on `glowup`. Iteration-5 items #3, #2, #1-phase-1,
and now #4 (bug backlog triage) all closed; real user-reported bug
(#3642 arXiv:2403.11784 Table 3) fixed by re-enabling the legacy
`color:#000000` dark-mode rescue. Banner now lists all ten commits
and recommends the user post resolution comments on #3461 and #3642
upstream — that's a user action, not Claude's.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Firefox baseline (`tools/baseline/firefox/`) generated under the
same guards as today's other engine runs (CONCURRENCY=1, timeout
1500). Took two batches — the first hit the 25-min timeout at
37/47 papers; a 10-paper completion run cleared the remaining.
3420 snapshots in batch 2, ~5100 in batch 1, 9645 files total.

The harness now baselines all three engines Playwright ships
(Chromium + WebKit + Firefox) on all 47 corpus papers. Closes
the "we tested two engines, not three" caveat on the best-in-
class claim. arXiv:2105.10386 remains the renderer-crash outlier
across all three engines (Firefox got the furthest — 1071 chunks
at 320 viewport before stopping; same fundamental DOM-size limit
as Chromium and WebKit).

No CSS change; pure baseline addition.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the GLOWUP_* internal docs and the color_overview.html
visualization artefact:

  - docs/GLOWUP_AUDIT_START.md   (788 lines, pre-iter-1 frozen snapshot)
  - docs/GLOWUP_PHASE2_AUDIT.md  (939 lines, pre-iter-2 frozen snapshot)
  - docs/GLOWUP_PROGRESS.md      (1090 lines, in-flight work log + banner)
  - docs/GLOWUP_WISDOM.md        (1315 lines, cross-iteration lessons)
  - docs/color_overview.html     (198 lines, hex-color grid viz)

These served the iteration cycles but don't have a long-term role for
consumers of the CSS theme. The Q4-style date-stamped verifications
that mattered already live as comments in the CSS source. The
consumer-facing surface (TOKENS, THEMING, BASELINE_AUDIT, CONTRIBUTING,
RFC, ar5iv_colors_report) is unchanged and remains the canonical
documentation set.

Updates references in README.md, CONTRIBUTING.md, docs/THEMING.md,
css/ar5iv/a11y.css, and .github/workflows/ci.yml to remove dangling
links. No CSS behaviour change; lint stays clean.

Net: -4330 lines of internal scaffolding.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Upstream main switched the math font from "latin modern math" to
"STIX Two Math" with a Google Fonts import (commit b5d2603).
Conflicts surfaced because the glowup branch had moved the
math-font tokens out of `css/ar5iv.css` into `css/ar5iv/tokens.css`
during the iteration-1 tokenisation.

Resolution:
- `css/ar5iv.css`: keep the glowup branch's layered architecture
  (the `:root`-with-all-tokens block from main is gone here —
  tokens live in the tokens layer now).
- `css/ar5iv/tokens.css`: apply main's font change at the new
  location. `--math-font-family` and `--math-caligraphic-font-family`
  now read `"STIX Two Math"`.
- `css/ar5iv-fonts.css`: keep the glowup branch's `display=swap`
  +explanatory-comment improvements, swap the math import to
  Google Fonts STIX Two Math.

No CSS behaviour change beyond the font swap that upstream main
already shipped. Lint passes; lightningcss bundle builds.

The per-developer visual baselines under `tools/baseline/<engine>/`
will show diffs on math-heavy papers after this lands — the font
metrics differ. Re-baseline locally with `node tools/visual.mjs
--update` (and per-engine for cross-engine coverage).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…drift

Three issues surfaced by auditing the STIX Two Math merge:

1. `display=swap` on the math font @import was wrong. Upstream main's
   commit b5d2603 deliberately omitted it; my merge resolution added
   it back along with the URL change. STIX Two Math has glyph forms
   (integrals, large operators, subscript metrics) that the Cambria
   Math / system math fallback approximates imperfectly — showing
   the fallback during the load window can produce mis-aligned
   formulas, which is worse than a brief paint delay. Drop
   `&display=swap` from the math import (Noto body fonts keep it).

2. `tools/visual.mjs` did not await web-font load before
   screenshotting. Under `waitUntil: 'load'`, the page's load event
   fires before `display=swap` fonts have completed their swap —
   so the harness was capturing fallback glyphs (Cambria Math
   instead of STIX Two Math; system serif instead of Noto Serif).
   Add `await page.evaluate(() => document.fonts.ready)` after
   goto. Makes the harness deterministic across the font-swap race.
   Per-developer baselines need refreshing locally.

3. `docs/TOKENS.md` still described `--math-font-family` as
   "Latin Modern Math + fallbacks". Updated to "STIX Two Math +
   fallbacks". TOKENS.md ↔ tokens.css drift check still passes (41
   :root tokens documented).

Spot-check on arXiv:1502.04633 chunk 1280-light-p008 (dense MathML):
no whitespace wells around wide formulas, justification tight. The
iteration-5 7-mrow MathML deletion (commit 5e0521c) still holds under
STIX Two Math. Cite-ref workaround unaffected (cites aren't math).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…otes)

PR brucemiller/LaTeXML#2767 reworks the frontmatter HTML. Adapt ar5iv's
forked rules to the new markup:

- author notes: tolerate the new `.ltx_author_notes_content` wrapper —
  `display: contents` keeps the contact layout identical, and the four
  note-robustness selector pairs (plus the a11y :focus-visible pair)
  collapse to wrapper-tolerant descendant combinators.
- title pubnotes: the new `.ltx_pubnotes` block (\thanks / DOI / arXiv /
  preprint now emitted inside the title <h1>) collapses behind a
  superscript dagger that reveals a popup on hover / tap / focus,
  mirroring the footnote affordance and keeping the title uncluttered
  above the primary author block.
- keywords/classification: restore the ": " separator the XSLT dropped,
  via `::after` on the bold label (correct once the corpus is rerendered
  to the new markup; this branch waits for that rerender).
- contact / note @name labels: italicise the new `.ltx_contact_name`
  and `.ltx_note_name` spans.

role=institute -> affiliation needed no change (already on
`.ltx_role_affiliation`; no `institute` references existed).

Verified against a hand-authored new-markup fixture (pubnotes dagger +
popup, author-notes wrapper, contact-name italics, colon-less labels)
in light / dark / 320px; chromium regression over four old-markup
papers is pixel-identical except the intended keyword/classification
colon.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Floated (wrap) figures: let the following paragraph wrap beside them instead
  of clearing it; give a gutter on the side the text flows past with no stray
  top margin; un-float to full width below 52rem.
- Multi-panel framed floats (e.g. side-by-side algorithm phases) span the
  content width instead of shrink-wrapping to a single column.
- Full-width (non-floated) tables and figures clear preceding wrap-floats, so
  they break onto their own full-width row (e.g. Table 4 beside a wrapfigure)
  rather than getting squeezed into the narrow remaining column.
- Hide the redundant author contact label (.ltx_author_notes .ltx_contact_name,
  e.g. "Affiliation:" / "E-mail"), generalizing the base LaTeXML.css
  multiline-only rule to single-line author blocks too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An `li.ltx_item`'s tag is `display: inline` (baseline-aligned) while the
adjacent `.ltx_para` is an `inline-block` with `vertical-align: top`. Their
first-line baselines diverge by (line-height_tag - line-height_para)/2, so a
tag inheriting the default `normal` line-height rendered above the paragraph's
first line. Match the prose line-height on the tag so the baselines coincide.
Enumerate lists use the subgrid layout and are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On viewports <= 52rem the two-column bib subgrid is too cramped, and the
single-column fallback stacked the [n] tag above its content with a gap. Drop
the grid there and let the tag plus every bibblock flow inline, so the tag
leads the first content line and the blocks fill the width and wrap naturally
instead of one block per line. The wide-viewport subgrid (with baseline
alignment) is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jsDelivr's on-the-fly minifier (clean-css v5.3.3) cannot parse our modern
CSS (@layer / light-dark() / :has() / nesting) and falls back to serving
the 72 KB un-minified original with a "Failed to minify" banner. Rather
than dumb the source down to clean-css's level, ship a pre-built
lightningcss bundle and let the CDN serve it verbatim (as bootstrap's
committed dist/*.min.css is served) — verified byte-identical rendering to
the modern source, 49.8 KB, all modern features preserved.

- build: also emit dist/ar5iv-fonts.min.css; drop the sourcemap so the
  committed artefact is exactly the two .min.css files.
- .gitignore: un-ignore the two distributed bundles (rest of dist/ stays
  ignored); note why they're committed.
- release automation (npm `version` lifecycle):
    preversion  = lint
    version     = build + `git add dist`  (dist lands IN the version commit)
    postversion = push HEAD + the tag
  so `npm version <patch|minor|major>` is a one-command release; jsDelivr
  auto-serves the new tag (no publish step). .npmrc keeps tags unprefixed
  (0.9.0, matching history).
- .github/workflows/release.yml: on a version tag, rebuild and fail if the
  committed dist is stale (the committed-dist model's one footgun), then
  warm the jsDelivr cache for the tag. Read-only, no secrets.

cortex will point at cdn.jsdelivr.net/gh/dginev/ar5iv-css@<tag>/dist/*.min.css.
Two foreignObject fixes, exercised by latexml-oxide's new nested
tcolorbox output (arXiv 2605.02240):

1. The fo-content width/line-height sledgehammer (`& *`) no longer
   hits SVG geometry elements (svg, foreignObject, image, rect, use):
   width:inherit!important on a nested <svg class="ltx_picture">
   collapsed the inner box's foreignObject to 0 and shredded its
   layout. HTML text at any nesting depth still gets the cascade.
   (Note in-source: `:not(svg *)` is NOT the guard — all fo content
   descends from the outer svg, which would disable the rule.)

2. Newer LaTeXML emits an exact TeX font anchor (font-size:<N>pt)
   inline on each foreignObject; the --svg-text-size:0.82em heuristic
   approximated that anchor when absent and would double-shrink on
   top of it. With the anchor present, fo content defers to it
   (font-size:1em); older conversions keep the heuristic.

Verified in Chrome (light+dark, 800/1280px): nested innercode boxes
render single-line inside their frames; the Appendix G title/user
boxes no longer overlap (content 235px inside a 297px fo, previously
408px spilling 111px over the box above).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… rationalization doc

Newer LaTeXML emits an exact font anchor (font-size:<N>pt) and correct
interior widths on every foreignObject; several ar5iv rules were
calibration fudges for the pre-anchor world and must not double-apply.
docs/SVG_BOXES.md records the engine contract and the rule-by-rule
rationalization against upstream LaTeXML.css.

Behavior changes (anchored documents only; legacy keeps the tuned look):
- fo content line-height 1.2 (= TeX \baselineskip, via
  --ltx-fo-line-height var; legacy stays 1). line-height:1 under-filled
  TeX's height budget ~17%, reading as excessive frame height.
- align-items:start when the content holds an ltx_minipage (a measured
  box's upper part): TeX fills boxes from the top, so estimator slack
  falls below the text instead of piling into a fake band above it
  (arXiv 2605.02240 Appendix G). Applies to legacy too — same bug there.

Verified in Chrome light+dark at 800/1280px on 2605.02240: all Appendix
G panels fill 74-95% of their frames top-down, nested innercode boxes
intact, no collisions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On wide displays (>96rem) the footnote body renders in the side margin
via the in-flow negative-margin trick — but being in flow, its box
height grew the carrier: a footnote inside a list item stretched the
item by the full note height (arXiv 2605.00181, first LHCb bullet).

Zero the .ltx_note_outer box height and let it paint through
(overflow:visible): the note keeps riding its carrier line and keeps
its margin-column x, while the carrier sizes to its own text
(item 116px -> 32px, note pixel-identical in the margin). float-based
escapes don't work here — list items establish BFCs that contain
floats. Two notes anchored close together may overlap in the margin,
the classic marginpar tradeoff. Small-screen popover path untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ver state

The height:0 carrier-height fix must not apply while the global
:focus-within popover rule is active — the bordered overlay needs its
real box. Scope: .ltx_note:not(:focus-within) > .ltx_note_outer.
Verified: unfocused item 32px/outer 0; Tab-focused outer 84px.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LaTeXML translates p{<dim>} tabular columns to fixed-width
inline-blocks sized for the author's PDF layout (often two-column
journals). In ar5iv's single-column layout those widths can be
absurdly tight: arXiv 2605.00310 Table III carries a 14.2pt "Year"
column that rendered one letter per line, and break-word collapsed
several headers mid-word ("Resolutio/n").

Floor each block at min-content with word integrity restored inside
(overflow-wrap:normal): squeezed columns relax to readable (Table III
685->712px, Table IV ->736px, headers whole words), deliberately
narrow paragraph columns still wrap at spaces, nothing ever narrows.
Verified no effect on already-wide tables (T8/T9 byte-identical,
still on their overflow-x:auto scroll path).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
::selection borrowed --note-highlight-color, whose dark value (#231d02)
is a near-black olive — selections were effectively invisible on the
#0d1117 page. New --selection-color token: light theme keeps the note
yellow (#ffffd4); dark gets a brighter, saturated gold (#6b5a10) — a
mid-tone tint that stays readable under the unchanged inherited text
color (~4.9:1 vs --text-color). Fallback chain keeps the old behavior
where the token is absent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…budget

Verbatim blocks in measured boxes arrive as one ltx_p per TeX-broken
line. Three rendering-contract rules seal them to the TeX height
budget (arXiv 2605.00468: 18 Prompt boxes spilling up to 980px, now
all contained — engine-side prevdepth/zi4 fixes land separately):

- one source line, one rendered line (white-space:nowrap): TeX already
  broke the lines; re-wrapping at browser mono metrics desynchronizes
  the line count from the measured height.
- no prose inter-paragraph margins on verbatim lines (typewriter-led
  ps, multi-span lines via :first-child, and BLANK lines — an empty
  inline-block-only p carries no font class to match).
- monospace calibration under the TeX anchor: 0.875em ~ cmtt's 0.525em
  advance vs ~0.60em web monos, so browser line capacity matches TeX.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hitespace

Supersedes the hardcoded 0.86em/nowrap iteration:

- white-space: pre on line-mapped verbatim ps — one source line, one
  rendered line (the conversion budgets SOURCE lines until engine-side
  breaklines emulation lands), and leading-space indentation preserved
  (pending the engine emitting it; SYNC_STATUS).
- Mono capacity is calc()-derived from theme tokens: --code-font-advance
  (0.600, measured; declared BESIDE --code-font-family so a font swap
  updates one token) over --tex-tt-advance (0.525, a TeX constant).
- @supports font-size-adjust: ch-width 0.525 upgrade: the browser reads
  the REAL font tables and self-calibrates — user-configured fonts are
  honored with no hand-measured metric (user directive: the final
  browser font is configurable; conversion must not preempt it).

2605.00468: horizontal overshoot 0%, vertical residual 10-24px on the
five tallest boxes only (~1%; closes fully with engine breaklines).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ock fill-width

Four scoped rules completing the measured-box rendering contract:
- breaklines wrap-parbox interior: inherit the typewriter font context
  (the global .ltx_para/.ltx_text serif rules were re-styling it),
  TeX geometry (no prose margins, ragged left), pre-wrap on the LEAF
  paragraph only.
- white-space pre/pre-wrap are leaf-only: serializer newlines between
  child elements render as phantom line breaks under pre (a 2-line
  wrapped paragraph measured 5 lines through its wrappers).
- the wrapped line renders as a flex row (TeX's hbox: break-indent
  beside wrap-parbox; as inline-blocks the collapsed inter-element
  spaces pushed the parbox onto its own line box).
- block-level pictures fill the figure column via viewBox scaling
  (native proportional zoom), EXCLUDING nested pictures inside
  foreignObject whose width is an exact TeX dimension in the outer
  coordinate system (2605.02240: letting them fill re-inflated them
  12->30px past their frame).

Witnesses: 2605.00468 all 24 pictures contained on both axes;
2605.02240 A/B-identical to the no-fill-width baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…stack

Zero-height margin notes (d93a9e7) cannot push each other apart, so two
footnotes anchored a line apart painted on top of each other in the
margin (arXiv 2605.00501 fn2/fn3, user-flagged). Presentation must be
either/or — ALL footnotes stay in the sidemargin on wide displays
(user) — so in a paragraph carrying 2+ footnotes the notes keep their
real height: the margin floats' clear:both then stacks the second note
below the first, marginpar-style. The zero-height carrier protection
remains the default everywhere else (its motivating case, the
2605.00181 BFC list item, stays covered).

Verified 2605.00501 at 100rem and 125rem: fn2/fn3 stacked with a 32px
gap (was 28px overlap), all four footnotes uniformly in the margin,
carrier paragraph height unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… min-fonts

Rendering fixes:
- fix(dark-mode): gate the OS-preference dark rules on
  :root:not([data-theme]) instead of :not([data-theme="light"]), so
  sepia (and any explicitly-named theme) is excluded from the
  image-filter + author-colour inversion under prefers-color-scheme:
  dark. Explicit [data-theme="dark"] keeps its own branch, so no
  regression. Corrects the (previously wrong) immunity claim in
  tokens.css.
- fix(math): correct three .ltx_mathvariant_* sans-serif selectors
  that were hyphenated (mathvariant-...) and matched nothing, so
  bold/italic sans-serif math variants now receive their styling.
- fix(bib): correct .ltx_bib_article .ltx_bib_title (was .bib-title,
  which matched nothing) so article titles render upright.

Correctness / docs:
- docs(css): fix the cascade !important+layers comment — un-layered
  !important is the WEAKEST author-important, not the strongest (no
  live impact; the transform block wins on author-important-vs-normal).
- docs: fix the release procedure (unprefixed 0.9.0 tag, not v0.9.0;
  a v-tag silently won't trigger release.yml); drop the false
  sourcemap claim; add a Browser support section; add CHANGELOG.md.
- docs: CONTRIBUTING file-tree gains dark-mode.css + SVG_BOXES.md;
  drop the stale "(planned)" note on TOKENS.md.

Build:
- build: drop the orphaned minified fonts bundle (it was built and
  shipped but referenced nowhere); refresh dist/ar5iv.min.css.
- chore: remove dead/duplicate declarations (duplicate .ltx_nodisplay,
  shadowed .ltx_flex_size_many, opacity:1-before-inherit, a stale
  commented-out selector).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dginev
dginev merged commit eaaf6f0 into main Jul 5, 2026
2 checks passed
@dginev
dginev deleted the glowup branch July 5, 2026 16:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant