detector: hover-state contrast + color-mix/compositing, radial-halo rule, top/bottom stripe variant, file:// browser scans

Four changes driven by human design review of eval artifacts:

1. Static engine contrast fidelity (nav-CTA cascade miss):
   - parseAnyColor evaluates color-mix() (premultiplied sRGB mix; exact
     for the dominant `color-mix(in oklab, C n%, transparent)` chip form)
   - extractStaticColor captures color-mix() balanced instead of plucking
     "transparent" out of the expression
   - resolveBackground composites translucent layers over the opaque base
     in both engines instead of skipping (static) or returning them
     as-if-opaque (browser)
   - NEW hover pass in the static cascade: :hover rules are matched via
     state-stripped selectors, merged per-property against the resting
     cascade with real specificity, and checked for WCAG contrast on
     styled controls (checkHoverContrast). Catches the recurring miss
     where a broader selector (.nav-links a:hover) beats the CTA's own
     hover color and drops the pair below AA.

2. New `radial-halo` slop rule: chromatic radial-gradient wash (visible
   saturated center -> transparent) as a decorative background on a dark
   page. Exempts achromatic vignettes, opaque-end sheens, px-stop dot
   textures, url() photo layers, and translucent (<0.7 alpha) staged-
   light washes. Separate id from dark-glow so dashboards track the
   gradient-drawn variant independently.

3. side-tab horizontal variant: 3-12px chromatic border-top/bottom (and
   top/bottom-anchored full-width pseudo stripes) on cards/badges flag as
   side-tab. Exempt: tablist/nav/aria-selected underlines, link/button
   affordances, table cells, hr, state-conditional pseudo stripes, and
   >12px bands. Badge-shaped spans (own visible background) participate.

4. CLI: file:// URLs route to the Puppeteer browser engine (~2s on a
   50KB page), and detect --json findings now carry the registry
   `category` field so downstream QA loops can separate mechanical slop
   tells from judgment calls.

Fixture policy update: flat 3px top-accent cards moved from should-pass
to flag columns; tablist-underline and 16px-band pass cases added.
Browser bundle regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-07-12 18:18:33 -07:00
co-authored by Claude Fable 5
parent be03fe734a
commit 1a4b5c2fa2
12 changed files with 1043 additions and 106 deletions
@@ -18,6 +18,7 @@ import {
checkElementGlow,
checkElementGptBorderShadow,
checkElementHeroEyebrow,
checkElementHoverContrast,
checkElementIconTile,
checkElementItalicSerif,
checkElementMotion,
@@ -90,8 +91,9 @@ function checkElementBrokenImage(el) {
}
const STATIC_ELEMENT_RULES = [
{ id: 'border-rules', selector: '*', run: (el, tag, style, window, customPropMap) => checkElementBorders(tag, style, null, resolveBorderRadiusPx(el, style, parseFloat(style.width) || 0, window)) },
{ id: 'border-rules', selector: '*', run: (el, tag, style, window, customPropMap) => checkElementBorders(tag, style, null, resolveBorderRadiusPx(el, style, parseFloat(style.width) || 0, window), el) },
{ id: 'color-rules', selector: '*', run: (el, tag, style, window, customPropMap) => checkElementColors(el, style, tag, window, customPropMap, false) },
{ id: 'hover-color-rules', selector: '*', run: (el, tag, style, window) => checkElementHoverContrast(el, style, tag, window) },
{ id: 'dark-glow', selector: '*', run: (el, tag, style, window, customPropMap) => checkElementGlow(tag, style, resolveBackground(el.parentElement || el, window, customPropMap)) },
{ id: 'motion-rules', selector: '*', run: (el, tag, style) => checkElementMotion(tag, style) },
{ id: 'icon-tile-stack', selector: 'h1,h2,h3,h4,h5,h6', run: (el, tag, _style, window) => checkElementIconTile(el, tag, window) },