From 8ac3886a9c15e00f3f502a38d50009b557f912f3 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Thu, 3 Sep 2026 12:24:54 -0700 Subject: [PATCH] Port: Fix detector URL scans and advisory handling (#709) Upstream sha fa44839f7289fced3f51946684656a28775638cc. Advisory handling. `severity` becomes the canonical registry field: the `advisory` bool leaves `Antipattern`, `advisory_rule_ids` filters on `severity == "advisory"`, and `derive_advisory_flag` stamps the finding's `advisory: true` from the effective severity, so a per-finding promotion or demotion carries the flag. The html and browser engines call it after their severity override; the detect CLI and the hook accept either spelling; the driver's serializer and the wasm registry exports derive it the same way. em-dash-overuse moves from `advisory: true` to `severity: "advisory"`. URL scans. `expand_joined_url_targets` splits an argv value that is entirely whitespace-separated URLs and leaves paths with spaces alone. The browser driver reads the readable linked-stylesheet corpus into the HTML pattern corpora and resolves a finding's selector with `selector_nodes_for_live_dom` / `pseudo_element_host_selector`, so an unresolvable selector drops the finding instead of keeping it page-level. The CSSOM walk itself is page JS: `browser-bundle/15-snapshot.js` gains `__snapLinkedStylesheetText` (grouping rules flattened, container-query probes, effective keyframes) and puts it in the snapshot as `linkedCss`; `10-probe.js` exposes the same for the in-page route, and the Dom trait carries `linked_stylesheet_text`. Also `enclosing_css_selector` blanks comments before hunting the previous declaration delimiter, and `check_typography` reports the uniquely most-used family instead of every family over a 15% share. Verified: `impeccable detect --no-config --json tests/fixtures/antipatterns` is now byte-identical to `node cli/bin/cli.js` on an origin/main worktree over the shared corpus (432 findings). The two changed lines in tests/oracle/vectors/calls/rules.checks/checkHtmlPatterns.jsonl were re-recorded by running origin/main's `checkHtmlPatterns` over the frozen args; only the comment-polluted selector changed. Goldens re-recorded for the advisory partition (config-*, fixture gemini/gpt-tells, numbered-section-labels, scoped-ignore, shape-assembled-illustration, color, em-dash-entities) and the help text, each cross-checked against the JS on origin/main. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY --- browser-bundle/10-probe.js | 5 + browser-bundle/15-snapshot.js | 322 +++++++++++++++++ crates/browser/src/lib.rs | 1 + crates/core/src/browser/driver.rs | 229 +++++++++++- crates/core/src/browser/page_checks.rs | 41 ++- crates/core/tests/rule_pack.rs | 2 - crates/detect/src/cli.rs | 48 ++- crates/detect/tests/rule_pack.rs | 1 - crates/foundation/src/browser/dom.rs | 6 + crates/foundation/src/browser/snapshot.rs | 7 + crates/foundation/src/css/scan.rs | 13 +- crates/foundation/src/findings.rs | 21 +- crates/foundation/src/registry.rs | 85 +---- crates/hook/src/hook_lib.rs | 8 +- crates/html/src/engine.rs | 1 + crates/html/tests/rule_pack.rs | 1 - .../assets/detect-antipatterns-browser.js | 338 +++++++++++++++++- crates/wasm/src/exports_pure.rs | 3 - crates/wasm/src/js_dom.rs | 4 + crates/wasm/src/lib.rs | 2 +- crates/wasm/tests/detect_exports.rs | 1 - docs/CLI-CONTRACT.md | 10 +- .../golden/detect-config-cross-project.json | 4 +- .../oracle/golden/detect-config-css-json.json | 2 +- .../oracle/golden/detect-config-css-text.json | 2 +- .../oracle/golden/detect-config-dir-dot.json | 2 +- .../oracle/golden/detect-config-dir-json.json | 2 +- .../oracle/golden/detect-config-dir-text.json | 2 +- .../golden/detect-config-from-subdir.json | 2 +- .../golden/detect-config-inline-disabled.json | 2 +- .../golden/detect-config-inline-json.json | 4 +- .../golden/detect-config-page-json.json | 2 +- .../golden/detect-config-page-text.json | 2 +- .../detect-fixture-json-color-html.json | 2 +- ...ct-fixture-json-em-dash-entities-html.json | 2 +- ...detect-fixture-json-gemini-tells-html.json | 4 +- .../detect-fixture-json-gpt-tells-html.json | 4 +- ...ure-json-numbered-section-labels-html.json | 4 +- ...etect-fixture-json-scoped-ignore-html.json | 2 +- ...son-shape-assembled-illustration-html.json | 4 +- ...detect-fixture-text-gemini-tells-html.json | 4 +- .../detect-fixture-text-gpt-tells-html.json | 4 +- ...ure-text-numbered-section-labels-html.json | 4 +- ...etect-fixture-text-scoped-ignore-html.json | 2 +- ...ext-shape-assembled-illustration-html.json | 4 +- tests/oracle/golden/detect-help.json | 2 +- tests/oracle/golden/detect-unknown-flag.json | 2 +- .../rules.checks/checkHtmlPatterns.jsonl | 4 +- 48 files changed, 1045 insertions(+), 178 deletions(-) diff --git a/browser-bundle/10-probe.js b/browser-bundle/10-probe.js index 2e8029b87..890bac3a4 100644 --- a/browser-bundle/10-probe.js +++ b/browser-bundle/10-probe.js @@ -101,6 +101,11 @@ const __impeccableDom = { } return undefined; }, + linked_stylesheet_text() { + // The CSSOM walk lives in 15-snapshot.js so the standalone snapshot + // producer carries it too; both routes read the same corpus. + return __snapLinkedStylesheetText(); + }, document_html_for_patterns() { const docClone = document.documentElement.cloneNode(true); for (const node of docClone.querySelectorAll('[id^="impeccable-live-"]')) node.remove(); diff --git a/browser-bundle/15-snapshot.js b/browser-bundle/15-snapshot.js index 908cefd4f..98d282baf 100644 --- a/browser-bundle/15-snapshot.js +++ b/browser-bundle/15-snapshot.js @@ -90,6 +90,327 @@ function __snapDirectTextRect(node) { return [left, top, right - left, bottom - top]; } +// ─── Linked stylesheet corpus (JS: injected/index.mjs #709) ──────────────── + +// JS: injected/index.mjs#pseudoElementHostSelector +function __snapPseudoElementHostSelector(selector) { + const raw = String(selector || ''); + const legacyNames = new Set(['before', 'after', 'first-letter', 'first-line']); + const isNameChar = char => /[a-zA-Z0-9_-]/.test(char || ''); + const consumeFunction = (start) => { + let depth = 0; + let quote = ''; + for (let i = start; i < raw.length; i += 1) { + const char = raw[i]; + if (char === '\\') { i += 1; continue; } + if (quote) { if (char === quote) quote = ''; continue; } + if (char === '"' || char === "'") { quote = char; continue; } + if (char === '(') depth += 1; + if (char === ')' && --depth === 0) return i + 1; + } + return raw.length; + }; + + let output = ''; + let found = false; + for (let i = 0; i < raw.length;) { + const char = raw[i]; + if (char === '\\') { + output += raw.slice(i, Math.min(raw.length, i + 2)); + i += 2; + continue; + } + if (char === '"' || char === "'") { + const quote = char; + const start = i; + i += 1; + while (i < raw.length) { + if (raw[i] === '\\') { i += 2; continue; } + const value = raw[i]; + i += 1; + if (value === quote) break; + } + output += raw.slice(start, i); + continue; + } + if (char !== ':') { output += char; i += 1; continue; } + + let end = i + 1; + let isPseudoElement = false; + if (raw[end] === ':') { + end += 1; + const nameStart = end; + while (isNameChar(raw[end])) end += 1; + isPseudoElement = end > nameStart; + } else { + const nameStart = end; + while (isNameChar(raw[end])) end += 1; + isPseudoElement = legacyNames.has(raw.slice(nameStart, end).toLowerCase()); + } + if (!isPseudoElement) { output += char; i += 1; continue; } + if (raw[end] === '(') end = consumeFunction(end); + found = true; + if (!output || /[\s>+~,]/.test(output[output.length - 1])) output += '*'; + i = end; + } + if (!found) return null; + return output.trim().replace(/,\s*(?=,|$)/g, ''); +} + +// JS: injected/index.mjs#selectorNodesForLiveDom +function __snapSelectorNodesForLiveDom(root, selector) { + const raw = String(selector || '').trim(); + if (!raw) return null; + const fallback = __snapPseudoElementHostSelector(raw); + if (fallback == null) { + // An empty result from a valid full selector is authoritative. In + // particular, do not broaden inactive :hover/:focus/:not() rules to + // their host element by stripping pseudo-classes. + try { return Array.from(root.querySelectorAll(raw)); } + catch { return null; } + } + // Resolve pseudo-elements to their originating live elements. An attached + // pseudo-element (`.card::before`) belongs to the element before it, while + // a hostless pseudo-element after a combinator (`main > ::before`) belongs + // to a matching element at that position (`main > *`). + if (!fallback || /^[,\s]*$/.test(fallback)) return null; + try { return Array.from(root.querySelectorAll(fallback)); } + catch { return null; } +} + +let __snapContainerProbeSequence = 0; + +function __snapIsContainerCssRule(rule) { + return rule?.constructor?.name === 'CSSContainerRule' + || /^\s*@container\b/i.test(rule?.cssText || ''); +} + +function __snapStyleRuleAppliesToLiveMatches(rule, matches) { + const style = rule?.style; + if (!style || !matches?.length || typeof getComputedStyle !== 'function') return false; + const sequence = ++__snapContainerProbeSequence; + const property = `--impeccable-container-probe-${sequence}-${Math.random().toString(36).slice(2)}`; + const value = `impeccable-container-active-${sequence}`; + const previousValue = style.getPropertyValue(property); + const previousPriority = style.getPropertyPriority(property); + try { style.setProperty(property, value, 'important'); } + catch { return false; } + + const pseudoElements = [...new Set( + String(rule.selectorText || '').match(/::[a-zA-Z-]+(?:\([^)]*\))?/g) || [], + )]; + try { + return matches.some(el => [null, ...pseudoElements].some(pseudo => { + try { + const computed = pseudo ? getComputedStyle(el, pseudo) : getComputedStyle(el); + return computed.getPropertyValue(property).trim() === value; + } catch { return false; } + })); + } finally { + if (previousValue) style.setProperty(property, previousValue, previousPriority); + else style.removeProperty(property); + } +} + +function __snapConditionalCssRuleIsActive(rule) { + const type = Number(rule?.type); + const constructorName = rule?.constructor?.name || ''; + if (constructorName === 'CSSMediaRule' || type === 4) { + const condition = rule.conditionText || rule.media?.mediaText || ''; + if (!condition || typeof window.matchMedia !== 'function') return true; + try { return window.matchMedia(condition).matches; } + catch { return true; } + } + if (constructorName === 'CSSSupportsRule' || type === 12) { + const condition = rule.conditionText || ''; + if (!condition || typeof CSS === 'undefined' || typeof CSS.supports !== 'function') return true; + try { return CSS.supports(condition); } + catch { return true; } + } + return true; +} + +function __snapSplitCssCommaList(value) { + const parts = []; + let current = ''; + let quote = ''; + let escaped = false; + for (const char of String(value || '')) { + if (escaped) { current += char; escaped = false; continue; } + if (char === '\\') { current += char; escaped = true; continue; } + if (quote) { current += char; if (char === quote) quote = ''; continue; } + if (char === '"' || char === "'") { quote = char; current += char; continue; } + if (char === ',') { parts.push(current); current = ''; continue; } + current += char; + } + parts.push(current); + return parts; +} + +function __snapNormalizeAnimationName(value) { + const name = String(value || '').trim(); + if (name.length >= 2 && name[0] === name[name.length - 1] && (name[0] === '"' || name[0] === "'")) { + return name.slice(1, -1); + } + return name; +} + +function __snapAnimationNamesDeclaredByRule(rule) { + const style = rule?.style; + if (!style) return []; + let value = ''; + try { + value = style.animationName + || style.getPropertyValue?.('animation-name') + || style.webkitAnimationName + || style.getPropertyValue?.('-webkit-animation-name') + || ''; + } catch { return []; } + return __snapSplitCssCommaList(value) + .map(__snapNormalizeAnimationName) + .filter(name => name && name.toLowerCase() !== 'none'); +} + +function __snapKeyframesRuleName(rule, cssText) { + const constructorName = rule?.constructor?.name || ''; + const type = Number(rule?.type); + const isKeyframes = constructorName === 'CSSKeyframesRule' + || constructorName === 'WebKitCSSKeyframesRule' + || type === 7 + || /^\s*@(?:-webkit-)?keyframes\b/i.test(cssText); + if (!isKeyframes) return ''; + const match = String(cssText || '').match(/^\s*@(?:-webkit-)?keyframes\s+([^\s{]+)/i); + return __snapNormalizeAnimationName(rule?.name || match?.[1] || ''); +} + +function __snapCssPropertyName(property) { + if (property.startsWith('--')) return property; + return property.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`); +} + +function __snapResolvedAnimationKeyframes(candidateNames) { + if (typeof document.getAnimations !== 'function') return null; + let animations; + try { animations = document.getAnimations(); } + catch { return null; } + + const resolved = new Map(); + const metadata = new Set(['offset', 'computedOffset', 'easing', 'composite']); + for (const animation of animations) { + const name = __snapNormalizeAnimationName(animation?.animationName || ''); + if (!name || !candidateNames.has(name) || resolved.has(name)) continue; + let frames; + try { frames = animation.effect?.getKeyframes?.() || []; } + catch { continue; } + const blocks = []; + for (const frame of frames) { + const rawOffset = Number.isFinite(frame.computedOffset) ? frame.computedOffset : frame.offset; + if (!Number.isFinite(rawOffset)) continue; + const offset = Math.round(rawOffset * 1000000) / 10000; + const declarations = Object.entries(frame) + .filter(([property, value]) => !metadata.has(property) && value != null && value !== '') + .map(([property, value]) => `${__snapCssPropertyName(property)}: ${value};`); + const easing = String(frame.easing || '').trim(); + if (easing && easing.toLowerCase() !== 'linear') { + declarations.push(`animation-timing-function: ${easing};`); + } + if (declarations.length === 0) continue; + blocks.push(`${offset}% { ${declarations.join(' ')} }`); + } + if (blocks.length > 0) resolved.set(name, `@keyframes ${name} { ${blocks.join(' ')} }`); + } + return resolved; +} + +// Read CSS that is absent from document.outerHTML. Inline \n\n\n
\n
\n

Upload your film and add a narrator

\n

Paste a link or drop a file. We transcribe it, translate, and one narrator reads over the top.

\n

The original stays audible underneath, just quieter.

\n
\n \n
\n\n
\n
\n

Left column with a normal amount of copy that fits comfortably within the opening viewport.

\n

Two short paragraphs, nothing that runs long.

\n
\n
\n

Right column, also short. Both sides sit inside the first screen.

\n

No column stretches the fold.

\n
\n
\n\n
\n
\n
\n\n\n",{"styleText":"\n * { margin: 0; box-sizing: border-box; }\n body { font-family: system-ui, sans-serif; color: #111; background: #fff; }\n p { margin: 0 0 14px; line-height: 1.6; }\n\n /* ── FLAG: two-column opening section, right column runs far past the fold\n while the left column fits inside one viewport ── */\n .flag { display: grid; grid-template-columns: 1.4fr 1fr; gap: 40px; padding: 40px; }\n .flag .short { }\n .flag .short h1 { font-size: 40px; margin-bottom: 16px; }\n .flag .tall .filler { height: 1600px; background: #eee; border-radius: 8px; padding: 16px; }\n\n /* ── PASS: balanced two-column section, both fit ── */\n .pass-balanced { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; padding: 40px; }\n .pass-balanced .col { }\n\n /* ── PASS: single full-page hero (no sibling column) ── */\n .pass-hero { padding: 40px; }\n .pass-hero .art { height: 1400px; background: #f3f3f3; }\n","classText":"flag\nshort\ntall\nfiller\npass-balanced\ncol\ncol\npass-hero\nart"}],"result":[]} {"args":["\n\n\n \n \n Flush Against Border; Should Flag vs Should Pass\n \n\n\n
\n\n \n
\n

Should flag

\n\n

Border + zero side-padding

\n
\n section: full border, padding 28px 0 0; label and pills touch the left/right border lines.\n
\n Supported dev servers\n
    \n
  • Vite
  • \n
  • Next.js
  • \n
  • SvelteKit
  • \n
  • Astro
  • \n
\n
\n
\n\n

All sides flush against a border

\n
\n card: 1px border on all sides, padding 0; heading and body touch every side.\n
\n

Card flush against the borders

\n

Body copy sits flush against every visible border line.

\n
\n
\n\n

Background color, zero padding

\n
\n no border, but a non-transparent bg; text flush against the bg's edge counts too.\n
\n

Title flush against the bg edge

\n

Body copy with zero padding around a colored fill.

\n
\n
\n\n

Outline (not border), zero padding

\n
\n outline draws outside the box; content at the box edge sits next to the outline.\n
\n

Title flush against the outline

\n

The outline counts as a visible boundary too.

\n
\n
\n\n

Left-side flush, other sides fine

\n
\n card: padding 12px 16px 12px 0; text touches the left border, other sides are inset.\n
\n

Title flush only on the left

\n

Padding-left zero with a full border.

\n
\n
\n
\n\n \n
\n

Should pass

\n\n

No boundary at all

\n
\n no bg, no border, no outline; zero padding is fine.\n
\n Supported dev servers\n
    \n
  • Vite
  • \n
  • Next.js
  • \n
  • SvelteKit
  • \n
\n
\n
\n\n

Top-only border, no side borders

\n
\n border-top only, padding-top 24px; no left/right border to be flush against.\n
\n Supported dev servers\n
    \n
  • Vite
  • \n
  • Next.js
  • \n
  • SvelteKit
  • \n
\n
\n
\n\n

Bordered card with proper inset

\n
\n card: 1px border + padding 18px 20px; comfortable inset on every side.\n
\n

Card with proper inset

\n

Body copy sits comfortably away from the borders.

\n
\n
\n\n

Background card with proper inset

\n
\n non-transparent bg + padding 14px 18px; content has room to breathe.\n
\n

Title with inset on bg

\n

Body copy is comfortably inset from the bg edge.

\n
\n
\n\n

Outline with proper inset

\n
\n outline + padding 14px 18px; content is comfortably inside.\n
\n

Title with inset and outline

\n

Outline boundary with proper inset works.

\n
\n
\n\n

Image-only container, no text

\n
\n card: padding 0, border all sides; no text means nothing can be flush.\n
\n
\n
\n
\n\n

Child margins provide the inset

\n
\n card: parent padding 0, but text children have margins that create visible inset.\n
\n

Title inset by child margin

\n

Body copy is not visually flush even though the parent has zero padding.

\n
\n
\n\n

Inner shell provides the inset

\n
\n card: parent padding 0, but an inner shell is offset from every edge.\n
\n
\n

Title inset by inner shell

\n

The direct child creates the breathing room.

\n
\n
\n
\n\n

Same background as parent

\n
\n child padding 0, but it has the same background as the parent, so no child boundary is visible.\n
\n
\n

Same-surface visual shell

\n

The text belongs to the parent surface, not to a distinct zero-padding card.

\n
\n
\n
\n\n

Full-bleed marquee shell

\n
\n marquee shell clips animated cards intentionally; the cards, not the shell, own text padding.\n
\n
\n

Quote card with its own comfortable padding.

\n

Another moving card, also padded inside.

\n
\n
\n
\n\n

Inner text surface

\n
\n outer shell has a scene background; the inner window owns the actual text padding.\n
\n
\n

Build step running

\n

Detector output belongs to the mock window.

\n
\n
\n
\n
\n
\n \n\n\n",{"styleText":"\n /* Fixture for the flush-against-border rule.\n\n The rule is more general than its name suggests: it fires when a\n container has any visible boundary (border, outline, OR a\n non-transparent background) AND zero inline padding on the\n bounded side, AND text-bearing children land flush against that\n boundary.\n\n Distinct from cramped-padding (which fires when an element has\n its OWN direct text and the padding/font-size ratio is wrong).\n Here the container has NO direct text; only children; so\n cramped-padding gives it a pass. */\n\n body { font-family: system-ui, sans-serif; background: #fafafa; padding: 24px; margin: 0; color: #0f172a; line-height: 1.5; }\n .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; max-width: 1400px; margin: 0 auto; }\n .col h2 { font-size: 22px; font-weight: 700; margin: 0 0 16px; color: #0f172a; line-height: 1.2; }\n .col h3 { font-size: 13px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; margin: 28px 0 10px; color: #64748b; }\n .case { margin-bottom: 14px; }\n .case-label { display: block; font-size: 12px; color: #64748b; margin-bottom: 8px; font-style: italic; max-width: 60ch; }\n\n /* ────────────────────────────────────────────────────────────\n SHOULD FLAG\n ──────────────────────────────────────────────────────────── */\n\n /* ── FLAG: the /live-mode-frameworks bug; border + zero side-padding ── */\n .flag-frameworks {\n padding: 28px 0 0;\n background: #fffbe6;\n border: 1px solid #d4a017;\n border-radius: 8px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n .flag-frameworks-label {\n font-family: ui-monospace, monospace;\n font-size: 11px;\n letter-spacing: 0.18em;\n text-transform: uppercase;\n color: #8b6914;\n }\n .flag-frameworks-list {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n list-style: none;\n padding: 0;\n margin: 0;\n }\n .flag-frameworks-list li {\n font-family: ui-monospace, monospace;\n font-size: 13px;\n padding: 6px 12px;\n background: #fef3c7;\n border: 1px solid #d4a017;\n border-radius: 4px;\n color: #78350f;\n }\n\n /* ── FLAG: child text flush against the four borders of a card ── */\n .flag-card-borders {\n padding: 0;\n background: white;\n border: 1px solid #cbd5e1;\n border-radius: 8px;\n }\n .flag-card-borders h4 { margin: 0; font-size: 14px; color: #0f172a; }\n .flag-card-borders p { margin: 4px 0 0; font-size: 13px; color: #475569; }\n\n /* ── FLAG: non-transparent background, zero padding ── */\n .flag-bg-only {\n padding: 0;\n background: #fde68a;\n border-radius: 6px;\n border: 0;\n }\n .flag-bg-only h4 { margin: 0; font-size: 14px; color: #78350f; }\n .flag-bg-only p { margin: 4px 0 0; font-size: 13px; color: #78350f; }\n\n /* ── FLAG: outline (no border), zero padding ── */\n .flag-outline-only {\n padding: 0;\n background: transparent;\n border: 0;\n outline: 2px solid #94a3b8;\n outline-offset: 0;\n }\n .flag-outline-only h4 { margin: 0; font-size: 14px; color: #0f172a; }\n .flag-outline-only p { margin: 4px 0 0; font-size: 13px; color: #475569; }\n\n /* ── FLAG: border-left + zero left-padding, other sides fine ── */\n .flag-asym-leftflush {\n padding: 12px 16px 12px 0;\n background: white;\n border: 1px solid #cbd5e1;\n border-radius: 4px;\n }\n .flag-asym-leftflush h4 { margin: 0; font-size: 14px; color: #0f172a; }\n .flag-asym-leftflush p { margin: 4px 0 0; font-size: 13px; color: #475569; }\n\n /* ────────────────────────────────────────────────────────────\n SHOULD PASS\n ──────────────────────────────────────────────────────────── */\n\n /* ── PASS: no boundary at all (no bg, no border, no outline) ── */\n .pass-no-boundary {\n padding: 0;\n background: transparent;\n border: 0;\n outline: 0;\n }\n .pass-no-boundary-label {\n font-family: ui-monospace, monospace;\n font-size: 11px;\n letter-spacing: 0.18em;\n text-transform: uppercase;\n color: #475569;\n margin-bottom: 10px;\n }\n .pass-no-boundary-list {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n list-style: none;\n padding: 0;\n margin: 0;\n }\n .pass-no-boundary-list li {\n font-size: 13px;\n padding: 6px 12px;\n background: #f5f5f7;\n border: 1px solid #e2e8f0;\n border-radius: 4px;\n color: #0f172a;\n }\n\n /* ── PASS: top-only border, no left/right border, padding-top fine ── */\n .pass-top-rule {\n padding: 24px 0 0;\n background: transparent;\n border: 0;\n border-top: 1px solid #cbd5e1;\n }\n .pass-top-rule-label {\n font-family: ui-monospace, monospace;\n font-size: 11px;\n letter-spacing: 0.18em;\n text-transform: uppercase;\n color: #475569;\n margin-bottom: 10px;\n }\n .pass-top-rule-list {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n list-style: none;\n padding: 0;\n margin: 0;\n }\n .pass-top-rule-list li {\n font-size: 13px;\n padding: 6px 12px;\n background: #f5f5f7;\n border: 1px solid #e2e8f0;\n border-radius: 4px;\n color: #0f172a;\n }\n\n /* ── PASS: bordered card with comfortable inset on all sides ── */\n .pass-bordered-padded {\n padding: 18px 20px;\n background: white;\n border: 1px solid #cbd5e1;\n border-radius: 8px;\n }\n .pass-bordered-padded h4 { margin: 0; font-size: 14px; color: #0f172a; }\n .pass-bordered-padded p { margin: 4px 0 0; font-size: 13px; color: #475569; }\n\n /* ── PASS: background card with comfortable inset ── */\n .pass-bg-padded {\n padding: 14px 18px;\n background: #fde68a;\n border-radius: 6px;\n border: 0;\n }\n .pass-bg-padded h4 { margin: 0; font-size: 14px; color: #78350f; }\n .pass-bg-padded p { margin: 4px 0 0; font-size: 13px; color: #78350f; }\n\n /* ── PASS: outline with comfortable inset ── */\n .pass-outline-padded {\n padding: 14px 18px;\n outline: 2px solid #94a3b8;\n outline-offset: 0;\n border: 0;\n background: transparent;\n }\n .pass-outline-padded h4 { margin: 0; font-size: 14px; color: #0f172a; }\n .pass-outline-padded p { margin: 4px 0 0; font-size: 13px; color: #475569; }\n\n /* ── PASS: image-only container, no text means no flush issue ── */\n .pass-image-only {\n padding: 0;\n background: white;\n border: 1px solid #cbd5e1;\n border-radius: 8px;\n width: 200px;\n overflow: hidden;\n }\n .pass-image-only .placeholder-img {\n width: 100%;\n height: 80px;\n background: linear-gradient(135deg, #cbd5e1, #94a3b8);\n }\n\n /* ── PASS: parent has zero padding, but child margins create the visible inset ── */\n .pass-margin-inset {\n padding: 0;\n background: white;\n border: 1px solid #cbd5e1;\n border-radius: 8px;\n }\n .pass-margin-inset h4 { margin: 16px 18px 4px; font-size: 14px; color: #0f172a; }\n .pass-margin-inset p { margin: 0 18px 16px; font-size: 13px; color: #475569; }\n\n /* ── PASS: an inner shell, not the parent, owns the inset spacing ── */\n .pass-inner-shell {\n padding: 0;\n background: white;\n border: 1px solid #cbd5e1;\n border-radius: 8px;\n }\n .pass-inner-shell .inner-shell {\n margin: 18px;\n }\n .pass-inner-shell h4 { margin: 0 0 4px; font-size: 14px; color: #0f172a; }\n .pass-inner-shell p { margin: 0; font-size: 13px; color: #475569; }\n\n /* ── PASS: same background as parent, so the child draws no visible boundary ── */\n .pass-same-bg-parent {\n padding: 16px;\n background: #eef2f7;\n border-radius: 8px;\n }\n .pass-same-bg-child {\n padding: 0;\n background: #eef2f7;\n border: 0;\n border-radius: 8px;\n }\n .pass-same-bg-child h4 { margin: 0; font-size: 14px; color: #0f172a; }\n .pass-same-bg-child p { margin: 4px 0 0; font-size: 13px; color: #475569; }\n\n /* ── PASS: full-bleed marquee/surface, not a text card missing padding ── */\n .pass-marquee-shell {\n padding: 0;\n background: #172033;\n color: white;\n overflow: hidden;\n border: 0;\n }\n .pass-marquee-track {\n display: flex;\n gap: 12px;\n width: max-content;\n }\n .pass-marquee-card {\n width: 220px;\n padding: 14px;\n background: #23304a;\n border: 1px solid rgba(255, 255, 255, 0.16);\n }\n .pass-marquee-card p { margin: 0; font-size: 13px; }\n\n /* ── PASS: outer scene owns background; inner text surface owns padding ── */\n .pass-inner-text-surface {\n padding: 0;\n background: #0f172a;\n color: white;\n overflow: hidden;\n border: 0;\n }\n .pass-inner-text-surface-window {\n display: grid;\n gap: 6px;\n min-height: 92px;\n padding: 14px;\n background: #111827;\n border: 1px solid rgba(255, 255, 255, 0.16);\n }\n .pass-inner-text-surface-window p { margin: 0; font-size: 13px; }\n ","classText":"grid\ncol\ncase\ncase-label\nflag-frameworks\nflag-frameworks-label\nflag-frameworks-list\ncase\ncase-label\nflag-card-borders\ncase\ncase-label\nflag-bg-only\ncase\ncase-label\nflag-outline-only\ncase\ncase-label\nflag-asym-leftflush\ncol\ncase\ncase-label\npass-no-boundary\npass-no-boundary-label\npass-no-boundary-list\ncase\ncase-label\npass-top-rule\npass-top-rule-label\npass-top-rule-list\ncase\ncase-label\npass-bordered-padded\ncase\ncase-label\npass-bg-padded\ncase\ncase-label\npass-outline-padded\ncase\ncase-label\npass-image-only\nplaceholder-img\ncase\ncase-label\npass-margin-inset\ncase\ncase-label\npass-inner-shell\ninner-shell\ncase\ncase-label\npass-same-bg-parent\npass-same-bg-child\ncase\ncase-label\npass-marquee-shell\npass-marquee-track\npass-marquee-card\npass-marquee-card\ncase\ncase-label\npass-inner-text-surface\npass-inner-text-surface-window"}],"result":[]} {"args":["\n\n\n \n Image hover-transform fixture\n \n\n\n
\n \n
\n
\n \"CSS\n
\n \"Tailwind\n
\n\n \n
\n \"Static\n
A card that lifts on hover, which is fine because it is not an image.
\n
\n
\n\n\n",{"styleText":"\n body { font-family: system-ui, sans-serif; margin: 0; color: #1a1a1a; background: #fff; }\n .cols { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding: 24px; }\n .col { padding: 16px; }\n img { display: block; width: 160px; height: 120px; margin: 0 0 24px; background: #ddd; }\n .flag-css-hover img { transition: transform 0.3s; }\n .flag-css-hover img:hover { transform: scale(1.05); }\n .card:hover { transform: translateY(-4px); }\n ","classText":"cols\ncol flag\nflag-css-hover\nflag-tw-hover hover:scale-105 transition-transform\ncol pass\npass-static\ncard pass-non-img-hover"}],"result":[{"id":"image-hover-transform","snippet":"img:hover { transform } rule","selector":".flag-css-hover img:hover"},{"id":"image-hover-transform","snippet":"Tailwind hover transform on "}]} -{"args":["\n\n\n \n \n Dark Glow Anti-Patterns — Should Flag vs Should Pass\n \n\n\n
\n \n
\n

Should flag

\n\n

CSS colored glows on dark background

\n
\n
\n

Blue glow

\n

box-shadow: 0 0 20px rgba(59, 130, 246, 0.4)

\n
\n
\n

Purple glow

\n

box-shadow: 0 0 25px rgba(139, 92, 246, 0.35)

\n
\n
\n

Cyan glow

\n

box-shadow: 0 0 15px rgba(6, 182, 212, 0.5)

\n
\n
\n

Multi-shadow with colored glow

\n

Normal shadow + purple glow combined.

\n
\n
\n\n

Inline-style glow (regex path)

\n
\n
\n

Inline pink glow

\n

Inline style for regex detection path.

\n
\n
\n\n

Modern color formats, var() refs, text-shadow

\n
\n
\n

OKLCH glow

\n

box-shadow: 0 0 18px oklch(0.72 0.15 195 / 0.5)

\n
\n
\n

Hex glow

\n

box-shadow: 0 0 16px #3b82f6

\n
\n
\n

HSL glow

\n

box-shadow: 0 0 22px hsl(280, 80%, 60%)

\n
\n
\n

Glow via var()

\n

box-shadow: 0 0 10px var(--fixture-glow)

\n
\n
\n

Glowing text

\n

text-shadow: 0 0 12px #22d3ee on the heading.

\n
\n
\n\n

Zero-offset halo on LIGHT background

\n
\n

Chromatic halo on light page

\n

box-shadow: 0 0 20px oklch(0.65 0.2 300 / 0.45)

\n
\n\n

Zero-offset halo on unreadable (image) surface

\n
\n
\n

Halo over photo background

\n

box-shadow: 0 0 26px rgba(217, 70, 239, 0.5)

\n
\n
\n\n

Colored glow under an opaque gradient over an image (browser)

\n
\n
\n

Opaque dark gradient covers the photo

\n

box-shadow: 0 6px 22px rgba(249, 115, 22, 0.5)

\n
\n
\n
\n\n \n
\n

Should pass

\n\n

Light page + colored shadow

\n
\n

Colored shadow on light background

\n

Not dark mode, colored shadow is fine.

\n
\n\n

Dark cards with neutral shadows

\n
\n
\n

Normal gray shadow on dark card

\n

Gray/black shadow is not a glow.

\n
\n
\n

Focus ring (spread, no blur)

\n

Functional ring, not decorative glow.

\n
\n
\n

Tiny blur (<5px)

\n

Too subtle to be \"glowing\".

\n
\n
\n

Small dark elevation

\n

Subtle shadow on dark card.

\n
\n
\n

Medium dark elevation

\n

Standard dark card shadow.

\n
\n
\n

Large dark elevation

\n

Prominent shadow on dark card.

\n
\n
\n

Inset shadow

\n

Inner shadow, not a glow.

\n
\n
\n

Multi-shadow dark card

\n

Layered gray shadows, no color glow.

\n
\n
\n\n

Medium-gray background (not dark enough)

\n
\n

Not dark enough background

\n

Medium gray doesn't qualify as \"dark mode\".

\n
\n\n

Colored drop shadow / neutral halo on light background

\n
\n

Offset colored drop shadow

\n

Real drop shadow with a color tint — not a halo.

\n
\n
\n

Soft neutral halo

\n

Achromatic zero-offset shadow is ambient elevation.

\n
\n\n

Offset chromatic shadow on unreadable (image) surface

\n
\n
\n

Offset colored shadow over photo background

\n

Surface unknown, offset shadow — abstain, not flag.

\n
\n
\n\n

Offset chromatic shadow under a translucent gradient over an image

\n
\n
\n

The wash blends with unknowable image pixels

\n

Surface unknown — abstain, never score the wash stops as dark.

\n
\n
\n\n

Neutral text-shadow on dark card

\n
\n
\n

Offset neutral text-shadow

\n

Legibility aid, not a glow.

\n
\n
\n\n

Light elevated cards

\n
\n

Small elevation (Tailwind shadow-sm)

\n

Standard subtle card shadow.

\n
\n
\n

Medium elevation (Tailwind shadow-md)

\n

Standard card shadow.

\n
\n
\n

Warm deep shadow

\n

Large spread neutral shadow.

\n
\n
\n
\n \n\n\n",{"styleText":"\n /* Two-column fixture: left = should flag, right = should pass.\n The dark-glow rule fires when a colored box-shadow appears on a\n child whose ancestor has a dark background. Each column wraps\n its dark-context cases in its own .dark-context section so the\n parent-bg lookup behaves predictably regardless of layout. */\n body { font-family: system-ui, sans-serif; background: #f9fafb; padding: 24px; margin: 0; }\n .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; max-width: 1200px; margin: 0 auto; }\n .col h2 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 16px; color: #475569; }\n .col h3 { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; margin: 24px 0 8px; color: #64748b; }\n .dark-context { background: #111827; padding: 16px; border-radius: 12px; }\n .dark-context + .dark-context { margin-top: 12px; }\n /* Card variants: explicit dark vs light pairings, both WCAG AA compliant */\n .card { padding: 14px 16px; border-radius: 10px; margin-bottom: 10px; }\n .card h4 { font-weight: 600; font-size: 13px; margin: 0; }\n .card p { font-size: 12px; margin: 4px 0 0; }\n .card-dark { background: #1f2937; }\n .card-dark h4 { color: #f9fafb; }\n .card-dark p { color: #cbd5e1; }\n .card-light { background: white; }\n .card-light h4 { color: #0f172a; }\n .card-light p { color: #475569; }\n\n /* ── FLAG: dark background + colored glow ── */\n .glow-blue { box-shadow: 0 0 20px rgba(59, 130, 246, 0.4); }\n .glow-purple { box-shadow: 0 0 25px rgba(139, 92, 246, 0.35); }\n .glow-cyan { box-shadow: 0 0 15px rgba(6, 182, 212, 0.5); }\n .glow-multi { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3), 0 0 30px rgba(168, 85, 247, 0.3); }\n\n /* ── FLAG: modern color formats, var() refs, text-shadow ── */\n :root { --fixture-glow: oklch(0.78 0.14 155); }\n .glow-oklch { box-shadow: 0 0 18px oklch(0.72 0.15 195 / 0.5); }\n .glow-hex { box-shadow: 0 0 16px #3b82f6; }\n .glow-hsl { box-shadow: 0 0 22px hsl(280, 80%, 60%); }\n .glow-var { box-shadow: 0 0 10px var(--fixture-glow); }\n .glow-text h4 { text-shadow: 0 0 12px #22d3ee; }\n /* Zero-offset chromatic halo is slop on light backgrounds too */\n .glow-light-oklch { box-shadow: 0 0 20px oklch(0.65 0.2 300 / 0.45); }\n /* Unreadable surface: url() image ancestor. The zero-offset halo tell\n holds on ANY background, so an unknown surface must not suppress it.\n Both element loops must agree here (the browser adapter once returned\n [] for the whole element on an unresolved surface). */\n .photo-context { background-image: url('/fixtures/antipatterns/missing-photo.png'); padding: 16px; border-radius: 12px; }\n .glow-photo-halo { box-shadow: 0 0 26px rgba(217, 70, 239, 0.5); }\n /* Opaque gradient atop a url() layer: every stop is opaque, so the\n gradient provably covers the image — the surface IS the dark\n gradient, and the dark-background glow tell may score against it\n (browser hunt; the static loop has no gradient hunt). */\n .photo-opaque-grad { background: linear-gradient(#111827, #0b1220), url('/fixtures/antipatterns/missing-photo.png'); padding: 16px; border-radius: 12px; }\n .glow-photo-opaque { box-shadow: 0 6px 22px rgba(249, 115, 22, 0.5); }\n\n /* ── PASS: same dark/light backgrounds, but neutral or no glow ── */\n .light-colored-shadow { box-shadow: 0 2px 4px rgba(59, 130, 246, 0.15); }\n .dark-normal-shadow { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); }\n .dark-focus-ring { box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5); }\n .dark-subtle-shadow { box-shadow: 0 1px 3px rgba(59, 130, 246, 0.2); }\n .dark-elevated-sm { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); }\n .dark-elevated-md { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.4); }\n .dark-elevated-lg { box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5); }\n .dark-elevated-inset { box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3); }\n .dark-elevated-multi { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2); }\n .elevated-sm-light { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); }\n .elevated-md-light { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }\n .elevated-warm-light { box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.15); }\n /* Medium-gray card: lighter than dark mode, used to demonstrate \"not dark enough\" for glow detection. White text for AA contrast. */\n .card-medium { background: #6b7280; }\n .card-medium h4 { color: #ffffff; }\n .card-medium p { color: #ffffff; }\n .medium-gray-shadow { box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3); }\n /* Offset chromatic drop shadow on a light background: a real drop\n shadow, not a halo — must NOT flag under the zero-offset rule.\n (Element path; the page-level text scan caps at one finding.) */\n .light-offset-colored { box-shadow: 0 8px 20px rgba(59, 130, 246, 0.25); }\n /* Achromatic zero-offset shadow: soft ambient elevation, stays legal */\n .light-soft-neutral { box-shadow: 0 0 24px rgba(0, 0, 0, 0.15); }\n /* Offset chromatic shadow under the same unreadable url() surface:\n the dark-background glow tell needs a surface we can read — abstain. */\n .photo-offset-colored { box-shadow: 0 6px 20px rgba(16, 185, 129, 0.35); }\n /* Translucent gradient atop a url() layer: the image shows through the\n 20% wash, so the real surface is a blend with unknowable pixels (a\n white photo composites to ~#cccccc, not black). Averaging the wash\n stops as if they were the surface scored this \"dark\" — abstain. */\n .photo-translucent-grad { background: linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), url('/fixtures/antipatterns/missing-photo.png'); padding: 16px; border-radius: 12px; }\n .photo-translucent-offset { box-shadow: 0 6px 22px rgba(244, 63, 94, 0.45); }\n /* Offset neutral text-shadow on dark card: legibility aid, not a glow */\n .dark-text-offset h4 { text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); }\n \nstyle=\"box-shadow: 0 0 20px rgba(236, 72, 153, 0.5);\"","classText":"grid\ncol\ndark-context\ncard card-dark glow-blue\ncard card-dark glow-purple\ncard card-dark glow-cyan\ncard card-dark glow-multi\ndark-context\ncard card-dark\ndark-context\ncard card-dark glow-oklch\ncard card-dark glow-hex\ncard card-dark glow-hsl\ncard card-dark glow-var\ncard card-dark glow-text\ncard card-light glow-light-oklch\nphoto-context\ncard card-light glow-photo-halo\nphoto-opaque-grad\ncard card-dark glow-photo-opaque\ncol\ncard card-light light-colored-shadow\ndark-context\ncard card-dark dark-normal-shadow\ncard card-dark dark-focus-ring\ncard card-dark dark-subtle-shadow\ncard card-dark dark-elevated-sm\ncard card-dark dark-elevated-md\ncard card-dark dark-elevated-lg\ncard card-dark dark-elevated-inset\ncard card-dark dark-elevated-multi\ncard card-medium medium-gray-shadow\ncard card-light light-offset-colored\ncard card-light light-soft-neutral\nphoto-context\ncard card-light photo-offset-colored\nphoto-translucent-grad\ncard card-light photo-translucent-offset\ndark-context\ncard card-dark dark-text-offset\ncard card-light elevated-sm-light\ncard card-light elevated-md-light\ncard card-light elevated-warm-light"}],"result":[{"id":"buried-raster","snippet":"raster under a near-opaque gradient wash: linear-gradient(#111827, #0b1220), url('/fixtures/antipatterns/missing-photo.png')","selector":"the static loop has no gradient hunt). */ .photo-opaque-grad"},{"id":"dark-glow","snippet":"Zero-offset box-shadow glow (#3b82f6)","selector":".glow-blue"}]} +{"args":["\n\n\n \n \n Dark Glow Anti-Patterns — Should Flag vs Should Pass\n \n\n\n
\n \n
\n

Should flag

\n\n

CSS colored glows on dark background

\n
\n
\n

Blue glow

\n

box-shadow: 0 0 20px rgba(59, 130, 246, 0.4)

\n
\n
\n

Purple glow

\n

box-shadow: 0 0 25px rgba(139, 92, 246, 0.35)

\n
\n
\n

Cyan glow

\n

box-shadow: 0 0 15px rgba(6, 182, 212, 0.5)

\n
\n
\n

Multi-shadow with colored glow

\n

Normal shadow + purple glow combined.

\n
\n
\n\n

Inline-style glow (regex path)

\n
\n
\n

Inline pink glow

\n

Inline style for regex detection path.

\n
\n
\n\n

Modern color formats, var() refs, text-shadow

\n
\n
\n

OKLCH glow

\n

box-shadow: 0 0 18px oklch(0.72 0.15 195 / 0.5)

\n
\n
\n

Hex glow

\n

box-shadow: 0 0 16px #3b82f6

\n
\n
\n

HSL glow

\n

box-shadow: 0 0 22px hsl(280, 80%, 60%)

\n
\n
\n

Glow via var()

\n

box-shadow: 0 0 10px var(--fixture-glow)

\n
\n
\n

Glowing text

\n

text-shadow: 0 0 12px #22d3ee on the heading.

\n
\n
\n\n

Zero-offset halo on LIGHT background

\n
\n

Chromatic halo on light page

\n

box-shadow: 0 0 20px oklch(0.65 0.2 300 / 0.45)

\n
\n\n

Zero-offset halo on unreadable (image) surface

\n
\n
\n

Halo over photo background

\n

box-shadow: 0 0 26px rgba(217, 70, 239, 0.5)

\n
\n
\n\n

Colored glow under an opaque gradient over an image (browser)

\n
\n
\n

Opaque dark gradient covers the photo

\n

box-shadow: 0 6px 22px rgba(249, 115, 22, 0.5)

\n
\n
\n
\n\n \n
\n

Should pass

\n\n

Light page + colored shadow

\n
\n

Colored shadow on light background

\n

Not dark mode, colored shadow is fine.

\n
\n\n

Dark cards with neutral shadows

\n
\n
\n

Normal gray shadow on dark card

\n

Gray/black shadow is not a glow.

\n
\n
\n

Focus ring (spread, no blur)

\n

Functional ring, not decorative glow.

\n
\n
\n

Tiny blur (<5px)

\n

Too subtle to be \"glowing\".

\n
\n
\n

Small dark elevation

\n

Subtle shadow on dark card.

\n
\n
\n

Medium dark elevation

\n

Standard dark card shadow.

\n
\n
\n

Large dark elevation

\n

Prominent shadow on dark card.

\n
\n
\n

Inset shadow

\n

Inner shadow, not a glow.

\n
\n
\n

Multi-shadow dark card

\n

Layered gray shadows, no color glow.

\n
\n
\n\n

Medium-gray background (not dark enough)

\n
\n

Not dark enough background

\n

Medium gray doesn't qualify as \"dark mode\".

\n
\n\n

Colored drop shadow / neutral halo on light background

\n
\n

Offset colored drop shadow

\n

Real drop shadow with a color tint — not a halo.

\n
\n
\n

Soft neutral halo

\n

Achromatic zero-offset shadow is ambient elevation.

\n
\n\n

Offset chromatic shadow on unreadable (image) surface

\n
\n
\n

Offset colored shadow over photo background

\n

Surface unknown, offset shadow — abstain, not flag.

\n
\n
\n\n

Offset chromatic shadow under a translucent gradient over an image

\n
\n
\n

The wash blends with unknowable image pixels

\n

Surface unknown — abstain, never score the wash stops as dark.

\n
\n
\n\n

Neutral text-shadow on dark card

\n
\n
\n

Offset neutral text-shadow

\n

Legibility aid, not a glow.

\n
\n
\n\n

Light elevated cards

\n
\n

Small elevation (Tailwind shadow-sm)

\n

Standard subtle card shadow.

\n
\n
\n

Medium elevation (Tailwind shadow-md)

\n

Standard card shadow.

\n
\n
\n

Warm deep shadow

\n

Large spread neutral shadow.

\n
\n
\n
\n \n\n\n",{"styleText":"\n /* Two-column fixture: left = should flag, right = should pass.\n The dark-glow rule fires when a colored box-shadow appears on a\n child whose ancestor has a dark background. Each column wraps\n its dark-context cases in its own .dark-context section so the\n parent-bg lookup behaves predictably regardless of layout. */\n body { font-family: system-ui, sans-serif; background: #f9fafb; padding: 24px; margin: 0; }\n .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; max-width: 1200px; margin: 0 auto; }\n .col h2 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 16px; color: #475569; }\n .col h3 { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; margin: 24px 0 8px; color: #64748b; }\n .dark-context { background: #111827; padding: 16px; border-radius: 12px; }\n .dark-context + .dark-context { margin-top: 12px; }\n /* Card variants: explicit dark vs light pairings, both WCAG AA compliant */\n .card { padding: 14px 16px; border-radius: 10px; margin-bottom: 10px; }\n .card h4 { font-weight: 600; font-size: 13px; margin: 0; }\n .card p { font-size: 12px; margin: 4px 0 0; }\n .card-dark { background: #1f2937; }\n .card-dark h4 { color: #f9fafb; }\n .card-dark p { color: #cbd5e1; }\n .card-light { background: white; }\n .card-light h4 { color: #0f172a; }\n .card-light p { color: #475569; }\n\n /* ── FLAG: dark background + colored glow ── */\n .glow-blue { box-shadow: 0 0 20px rgba(59, 130, 246, 0.4); }\n .glow-purple { box-shadow: 0 0 25px rgba(139, 92, 246, 0.35); }\n .glow-cyan { box-shadow: 0 0 15px rgba(6, 182, 212, 0.5); }\n .glow-multi { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3), 0 0 30px rgba(168, 85, 247, 0.3); }\n\n /* ── FLAG: modern color formats, var() refs, text-shadow ── */\n :root { --fixture-glow: oklch(0.78 0.14 155); }\n .glow-oklch { box-shadow: 0 0 18px oklch(0.72 0.15 195 / 0.5); }\n .glow-hex { box-shadow: 0 0 16px #3b82f6; }\n .glow-hsl { box-shadow: 0 0 22px hsl(280, 80%, 60%); }\n .glow-var { box-shadow: 0 0 10px var(--fixture-glow); }\n .glow-text h4 { text-shadow: 0 0 12px #22d3ee; }\n /* Zero-offset chromatic halo is slop on light backgrounds too */\n .glow-light-oklch { box-shadow: 0 0 20px oklch(0.65 0.2 300 / 0.45); }\n /* Unreadable surface: url() image ancestor. The zero-offset halo tell\n holds on ANY background, so an unknown surface must not suppress it.\n Both element loops must agree here (the browser adapter once returned\n [] for the whole element on an unresolved surface). */\n .photo-context { background-image: url('/fixtures/antipatterns/missing-photo.png'); padding: 16px; border-radius: 12px; }\n .glow-photo-halo { box-shadow: 0 0 26px rgba(217, 70, 239, 0.5); }\n /* Opaque gradient atop a url() layer: every stop is opaque, so the\n gradient provably covers the image — the surface IS the dark\n gradient, and the dark-background glow tell may score against it\n (browser hunt; the static loop has no gradient hunt). */\n .photo-opaque-grad { background: linear-gradient(#111827, #0b1220), url('/fixtures/antipatterns/missing-photo.png'); padding: 16px; border-radius: 12px; }\n .glow-photo-opaque { box-shadow: 0 6px 22px rgba(249, 115, 22, 0.5); }\n\n /* ── PASS: same dark/light backgrounds, but neutral or no glow ── */\n .light-colored-shadow { box-shadow: 0 2px 4px rgba(59, 130, 246, 0.15); }\n .dark-normal-shadow { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); }\n .dark-focus-ring { box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5); }\n .dark-subtle-shadow { box-shadow: 0 1px 3px rgba(59, 130, 246, 0.2); }\n .dark-elevated-sm { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); }\n .dark-elevated-md { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.4); }\n .dark-elevated-lg { box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5); }\n .dark-elevated-inset { box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3); }\n .dark-elevated-multi { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2); }\n .elevated-sm-light { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); }\n .elevated-md-light { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }\n .elevated-warm-light { box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.15); }\n /* Medium-gray card: lighter than dark mode, used to demonstrate \"not dark enough\" for glow detection. White text for AA contrast. */\n .card-medium { background: #6b7280; }\n .card-medium h4 { color: #ffffff; }\n .card-medium p { color: #ffffff; }\n .medium-gray-shadow { box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3); }\n /* Offset chromatic drop shadow on a light background: a real drop\n shadow, not a halo — must NOT flag under the zero-offset rule.\n (Element path; the page-level text scan caps at one finding.) */\n .light-offset-colored { box-shadow: 0 8px 20px rgba(59, 130, 246, 0.25); }\n /* Achromatic zero-offset shadow: soft ambient elevation, stays legal */\n .light-soft-neutral { box-shadow: 0 0 24px rgba(0, 0, 0, 0.15); }\n /* Offset chromatic shadow under the same unreadable url() surface:\n the dark-background glow tell needs a surface we can read — abstain. */\n .photo-offset-colored { box-shadow: 0 6px 20px rgba(16, 185, 129, 0.35); }\n /* Translucent gradient atop a url() layer: the image shows through the\n 20% wash, so the real surface is a blend with unknowable pixels (a\n white photo composites to ~#cccccc, not black). Averaging the wash\n stops as if they were the surface scored this \"dark\" — abstain. */\n .photo-translucent-grad { background: linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), url('/fixtures/antipatterns/missing-photo.png'); padding: 16px; border-radius: 12px; }\n .photo-translucent-offset { box-shadow: 0 6px 22px rgba(244, 63, 94, 0.45); }\n /* Offset neutral text-shadow on dark card: legibility aid, not a glow */\n .dark-text-offset h4 { text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); }\n \nstyle=\"box-shadow: 0 0 20px rgba(236, 72, 153, 0.5);\"","classText":"grid\ncol\ndark-context\ncard card-dark glow-blue\ncard card-dark glow-purple\ncard card-dark glow-cyan\ncard card-dark glow-multi\ndark-context\ncard card-dark\ndark-context\ncard card-dark glow-oklch\ncard card-dark glow-hex\ncard card-dark glow-hsl\ncard card-dark glow-var\ncard card-dark glow-text\ncard card-light glow-light-oklch\nphoto-context\ncard card-light glow-photo-halo\nphoto-opaque-grad\ncard card-dark glow-photo-opaque\ncol\ncard card-light light-colored-shadow\ndark-context\ncard card-dark dark-normal-shadow\ncard card-dark dark-focus-ring\ncard card-dark dark-subtle-shadow\ncard card-dark dark-elevated-sm\ncard card-dark dark-elevated-md\ncard card-dark dark-elevated-lg\ncard card-dark dark-elevated-inset\ncard card-dark dark-elevated-multi\ncard card-medium medium-gray-shadow\ncard card-light light-offset-colored\ncard card-light light-soft-neutral\nphoto-context\ncard card-light photo-offset-colored\nphoto-translucent-grad\ncard card-light photo-translucent-offset\ndark-context\ncard card-dark dark-text-offset\ncard card-light elevated-sm-light\ncard card-light elevated-md-light\ncard card-light elevated-warm-light"}],"result":[{"id":"buried-raster","snippet":"raster under a near-opaque gradient wash: linear-gradient(#111827, #0b1220), url('/fixtures/antipatterns/missing-photo.png')","selector":".photo-opaque-grad"},{"id":"dark-glow","snippet":"Zero-offset box-shadow glow (#3b82f6)","selector":".glow-blue"}]} {"args":["\n\n\n \n Recurring generated-UI tell fixture\n \n\n\n
\n \n
\n
\n Thin hairline border paired with a wide diffuse shadow.\n
\n
\n
\n

We retired the growth theater and shipped something that actually works.

\n
\n\n \n
\n
\n Hairline border with a tight, purposeful shadow.\n
\n
\n A quiet low-contrast hairline with a broad but restrained ambient shadow.\n
\n
\n OKLCH shadow hue values should not be mistaken for shadow blur.\n
\n
\n
\n
\n
\n
\n

We measured real outcomes for the people who use this every day.

\n
\n
\n\n\n",{"styleText":"\n body { font-family: system-ui, sans-serif; margin: 0; color: #1a1a1a; background: #fff; }\n .cols { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding: 24px; }\n .col { padding: 16px; }\n .card { padding: 24px; margin: 0 0 24px; border-radius: 12px; }\n .stripes { height: 80px; margin: 0 0 24px; border-radius: 12px; }\n .grid { height: 80px; margin: 0 0 24px; border-radius: 12px; }\n \nstyle=\"border: 1px solid #e5e7eb; box-shadow: 0 0 24px rgba(0,0,0,0.18)\"\nstyle=\"background: repeating-linear-gradient(45deg, #eee, #eee 10px, #fafafa 10px, #fafafa 20px)\"\nstyle=\"background: linear-gradient(90deg, oklch(96% 0.012 82 / 0.055) 1px, transparent 1px), linear-gradient(180deg, oklch(96% 0.012 82 / 0.05) 1px, transparent 1px); background-size: 78px 78px, 78px 78px;\"\nstyle=\"border: 1px solid #e5e7eb; box-shadow: 0 1px 4px rgba(0,0,0,0.08)\"\nstyle=\"border: 1px solid rgba(148, 163, 184, 0.16); box-shadow: 0 18px 48px -16px rgba(15, 23, 42, 0.18)\"\nstyle=\"border: 1px solid oklch(78% 0 0 / 0.16); box-shadow: 0 16px 36px -12px oklch(2% 0.004 95 / 0.18)\"\nstyle=\"background: linear-gradient(180deg, #f3f4f6, #ffffff)\"\nstyle=\"background: linear-gradient(180deg, rgba(15, 23, 42, 0.06) 1px, transparent 1px); background-size: 100% 32px\"\nstyle=\"background: linear-gradient(90deg, #f3f4f6, #e5e7eb), linear-gradient(180deg, #eef2ff, #ffffff)\"\nstyle=\"background: linear-gradient(90deg, rgba(15, 23, 42, 0.06) 1px, transparent 1px), linear-gradient(180deg, rgba(15, 23, 42, 0.06) 1px, transparent 1px)\"\nstyle=\"background: linear-gradient(90deg, rgba(15, 23, 42, 0.06) 1px, transparent 1px); background-size: 32px 32px; mask-image: linear-gradient(180deg, #000 1px, transparent 1px)\"","classText":"cols\ncol flag\ncard flag-thin-border-wide-shadow\nstripes flag-repeating-stripes\ngrid flag-codex-grid\nflag-theater\ncol pass\ncard pass-tight-shadow\ncard pass-soft-border-wide-shadow\ncard pass-oklch-soft-shadow\nstripes pass-plain-gradient\ngrid pass-single-axis-rule\ngrid pass-two-color-blend\ngrid pass-crosshair-no-tiling\ngrid pass-bg-plus-mask-hairline\npass-no-theater"}],"result":[{"id":"repeating-stripes-gradient","snippet":"repeating-gradient decorative stripes"},{"id":"codex-grid-background","snippet":"two-axis grid-line gradient background"},{"id":"theater-slop-phrase","snippet":"\"growth theater\""}]} {"args":["\n\n\n\nHeading rhythm fixture\n\n\n\n\n

Pass First Heading

\n

Opening paragraph after the first heading on the page. The first heading has\n nothing above it, so it is exempt from the rhythm comparison entirely.

\n\n

A closing paragraph of the previous block of content,\n sitting directly above the crowded heading with no gap at all between them.

\n

Flag Crowded One

\n

The content this heading introduces sits forty pixels away while the previous\n block touches the heading, so the heading reads as a caption for the text above.

\n\n

Another closing paragraph of a previous content block, again flush against\n the heading that follows it in the document flow.

\n

Flag Crowded Two

\n

Second crowded case. Two or more headings must violate the principle before\n the rule fires, because a single occurrence is treated as noise.

\n\n

A third closing paragraph that the next heading will visually attach to,\n which is exactly the inversion this rule measures.

\n

Flag Crowded Three

\n

Third crowded case, this time an h3, to confirm the rule reads h2 through h4.

\n\n

Pass Generous Above

\n

Sixty-four pixels above and sixteen below. The heading clearly binds to this\n paragraph, which is the healthy shape.

\n\n

Pass Near Equal

\n

Twenty pixels above and twenty-four below. Ambiguous rather than inverted,\n so the rule leaves it alone.

\n\n

Chapter Four

\n

Pass Eyebrow Cluster

\n

The tiny label above belongs to the heading cluster. Space is measured from\n the top of the cluster, where seventy-two pixels separate it from this text.

\n\n
\n

Pass Card Heading

\n

A heading inside a small card. The card boundary provides the separation,\n so the rule exempts headings inside panels shorter than two hundred pixels.

\n
\n\n
\n

Pass Band Heading

\n

This heading is the first content inside a section that draws its own\n background, so the band edge is the separator above it.

\n
\n\n\n\n",{"styleText":"\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: Georgia, serif; font-size: 16px; line-height: 1.5; width: 720px; }\n h2 { font-size: 28px; line-height: 1.2; }\n h3 { font-size: 20px; line-height: 1.2; }\n p { max-width: 60ch; }\n\n /* FLAG: zero space above the heading, clear space below it */\n .flag-crowded { margin: 0 0 40px; }\n .flag-crowded + p { margin: 0 0 24px; }\n\n /* PASS: generous space above, tight below */\n .pass-generous { margin: 64px 0 16px; }\n\n /* PASS: near-equal spacing is ambiguous, not inverted */\n .pass-near-equal { margin: 20px 0 24px; }\n\n /* PASS: eyebrow label bound to the heading; real space sits above the cluster */\n .pass-eyebrow-label { font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase; margin: 72px 0 8px; }\n .pass-eyebrow-heading { margin: 0 0 24px; }\n\n /* PASS: small card, its boundary provides the separation */\n .pass-card { box-shadow: 0 1px 4px rgba(0,0,0,0.2); background: #f4f4f4; padding: 16px; margin: 48px 0; width: 320px; }\n .pass-card h3 { margin: 0 0 20px; }\n\n /* PASS: heading first inside a banded section; the band edge separates */\n .pass-band { background: #ececec; padding: 8px 0 32px; margin-top: 8px; }\n .pass-band h2 { margin: 0 0 24px; }\n\nstyle=\"margin-top: 24px;\"","classText":"pass-first\nflag-crowded\nflag-crowded\nflag-crowded\npass-generous\npass-near-equal\npass-eyebrow-label\npass-eyebrow-heading\npass-card\npass-band"}],"result":[]} {"args":["\n\n\n \n Hero-Eyebrow-Chip — Should Flag vs Should Pass\n \n\n\n
\n\n \n
\n

Should flag

\n\n
\n
AI-NATIVE WORKFLOWS
\n

Eyebrow Above Hero

\n

Classic uppercase tracked div eyebrow above an 88px hero.

\n
\n\n
\n NEW IN 2026\n

Span Eyebrow Above Hero

\n

Span variant with the same uppercase + tracking + small-size shape.

\n
\n\n
\n FEATURED\n

Pill Chip Above Hero

\n

Pill-shaped chip with background, border-radius, padding.

\n
\n\n
\n NEW\n

Already Uppercase Text

\n

Text typed uppercase, no text-transform, but matching tracking and size.

\n
\n\n
\n
A VERY LONG UPPERCASE TABLE OF CONTENTS HEADER
\n

Long Uppercase Sentence Above Hero

\n

46-char uppercase tracked label — under the 60-char eyebrow ceiling, so it still reads as an oversized eyebrow.

\n
\n
\n\n \n
\n

Should pass

\n\n
\n
UPPERCASE LABEL
\n

Eyebrow With Normal Tracking

\n

Uppercase label above a hero but with letter-spacing: normal.

\n
\n\n
\n
CARD CAPTION
\n

Plain body text below the caption.

\n

Uppercase Caption Far From Hero

\n

Hero exists, but its preceding sibling is a paragraph, not the eyebrow.

\n
\n\n
\n

Hero With No Eyebrow

\n

Bare h1 at 64px, nothing above it.

\n
\n\n
\n

SECTION HEADING

\n

Heading Above Heading

\n

An h2 styled like an eyebrow above an h1 — heading-tag exclusion must skip this.

\n
\n\n
\n
SECTION KICKER
\n

Body-Sized Heading Below Eyebrow

\n

A compact product/page heading is not an oversized marketing hero.

\n
\n\n
\n
NEXT FROM
\n

Application Panel Heading

\n

Context labels inside tab panels describe application state rather than decorating a marketing hero.

\n
\n\n
\n\n
\n\n\n",{"styleText":"\n /* ─── Layout convention: two-column fixture ─────────────────────────\n Left column = patterns the rule SHOULD flag.\n Right column = legitimate patterns the rule should NOT flag.\n Each test case is annotated with a unique

text so the test\n can match snippets back to expectations.\n ──────────────────────────────────────────────────────────────── */\n body { font-family: system-ui, sans-serif; margin: 0; padding: 24px; }\n .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; max-width: 1200px; margin: 0 auto; }\n .col h2.col-label { font-size: 14px; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 16px; color: #475569; }\n .case { margin-bottom: 32px; padding: 24px; border: 1px solid #e2e8f0; border-radius: 12px; background: #fff; }\n .case p { font-size: 14px; margin: 12px 0 0; color: #64748b; }\n .case h1 { margin: 0; font-family: 'Inter', sans-serif; line-height: 1.05; }\n\n /* Hero size used by most cases */\n .hero { font-size: 88px; font-weight: 600; }\n\n /* ── FLAG cases ── */\n\n .eyebrow-classic {\n text-transform: uppercase;\n letter-spacing: 0.15em;\n font-size: 12px;\n color: #64748b;\n font-weight: 600;\n margin: 0 0 16px;\n }\n\n .eyebrow-span {\n display: inline-block;\n text-transform: uppercase;\n letter-spacing: 0.16em;\n font-size: 11px;\n color: #475569;\n font-weight: 600;\n margin: 0 0 16px;\n }\n\n .eyebrow-pill {\n display: inline-block;\n text-transform: uppercase;\n letter-spacing: 0.14em;\n font-size: 12px;\n background: #f1f5f9;\n color: #334155;\n border-radius: 999px;\n padding: 4px 12px;\n font-weight: 600;\n margin: 0 0 16px;\n }\n\n .eyebrow-already-uppercase {\n letter-spacing: 0.18em;\n font-size: 11px;\n color: #b45309;\n font-weight: 700;\n margin: 0 0 16px;\n }\n\n /* ── PASS cases ── */\n\n .pass-eyebrow-no-tracking {\n text-transform: uppercase;\n letter-spacing: normal;\n font-size: 12px;\n color: #64748b;\n font-weight: 600;\n margin: 0 0 16px;\n }\n\n .pass-body-heading {\n font-size: 24px;\n font-weight: 600;\n font-family: 'Inter', sans-serif;\n margin: 0;\n }\n\n .pass-card-caption {\n text-transform: uppercase;\n letter-spacing: 0.18em;\n font-size: 11px;\n color: #94a3b8;\n font-weight: 600;\n margin: 0 0 8px;\n }\n\n .pass-bare-hero {\n font-size: 64px;\n }\n\n .pass-h2-above-h1 {\n font-size: 14px;\n text-transform: uppercase;\n letter-spacing: 0.18em;\n font-weight: 700;\n color: #475569;\n margin: 0 0 16px;\n font-family: 'Inter', sans-serif;\n }\n\n .pass-long-uppercase {\n text-transform: uppercase;\n letter-spacing: 0.14em;\n font-size: 12px;\n color: #475569;\n font-weight: 600;\n margin: 0 0 16px;\n }\n \nstyle=\"margin-top:32px\"","classText":"grid\ncol\ncol-label\ncase\neyebrow-classic\nhero\ncase\neyebrow-span\nhero\ncase\neyebrow-pill\nhero\ncase\neyebrow-already-uppercase\nhero\ncase\npass-long-uppercase\nhero\ncol\ncol-label\ncase\npass-eyebrow-no-tracking\nhero\ncase\npass-card-caption\nhero\ncase\npass-bare-hero\ncase\npass-h2-above-h1\nhero\ncase\neyebrow-classic\npass-body-heading\ncase\neyebrow-classic\nhero"}],"result":[]} @@ -86,7 +86,7 @@ {"args":["\n\n\n \n Hero-Eyebrow-Chip — Should Flag vs Should Pass\n \n\n\n
\n\n \n
\n

Should flag

\n\n
\n
AI-NATIVE WORKFLOWS
\n

Eyebrow Above Hero

\n

Classic uppercase tracked div eyebrow above an 88px hero.

\n
\n\n
\n NEW IN 2026\n

Span Eyebrow Above Hero

\n

Span variant with the same uppercase + tracking + small-size shape.

\n
\n\n
\n FEATURED\n

Pill Chip Above Hero

\n

Pill-shaped chip with background, border-radius, padding.

\n
\n\n
\n NEW\n

Already Uppercase Text

\n

Text typed uppercase, no text-transform, but matching tracking and size.

\n
\n\n
\n
A VERY LONG UPPERCASE TABLE OF CONTENTS HEADER
\n

Long Uppercase Sentence Above Hero

\n

46-char uppercase tracked label — under the 60-char eyebrow ceiling, so it still reads as an oversized eyebrow.

\n
\n
\n\n \n
\n

Should pass

\n\n
\n
UPPERCASE LABEL
\n

Eyebrow With Normal Tracking

\n

Uppercase label above a hero but with letter-spacing: normal.

\n
\n\n
\n
CARD CAPTION
\n

Plain body text below the caption.

\n

Uppercase Caption Far From Hero

\n

Hero exists, but its preceding sibling is a paragraph, not the eyebrow.

\n
\n\n
\n

Hero With No Eyebrow

\n

Bare h1 at 64px, nothing above it.

\n
\n\n
\n

SECTION HEADING

\n

Heading Above Heading

\n

An h2 styled like an eyebrow above an h1 — heading-tag exclusion must skip this.

\n
\n\n
\n
SECTION KICKER
\n

Body-Sized Heading Below Eyebrow

\n

A compact product/page heading is not an oversized marketing hero.

\n
\n\n
\n
NEXT FROM
\n

Application Panel Heading

\n

Context labels inside tab panels describe application state rather than decorating a marketing hero.

\n
\n\n
\n\n
\n\n\n",{"styleText":"\n /* ─── Layout convention: two-column fixture ─────────────────────────\n Left column = patterns the rule SHOULD flag.\n Right column = legitimate patterns the rule should NOT flag.\n Each test case is annotated with a unique

text so the test\n can match snippets back to expectations.\n ──────────────────────────────────────────────────────────────── */\n body { font-family: system-ui, sans-serif; margin: 0; padding: 24px; }\n .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; max-width: 1200px; margin: 0 auto; }\n .col h2.col-label { font-size: 14px; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 16px; color: #475569; }\n .case { margin-bottom: 32px; padding: 24px; border: 1px solid #e2e8f0; border-radius: 12px; background: #fff; }\n .case p { font-size: 14px; margin: 12px 0 0; color: #64748b; }\n .case h1 { margin: 0; font-family: 'Inter', sans-serif; line-height: 1.05; }\n\n /* Hero size used by most cases */\n .hero { font-size: 88px; font-weight: 600; }\n\n /* ── FLAG cases ── */\n\n .eyebrow-classic {\n text-transform: uppercase;\n letter-spacing: 0.15em;\n font-size: 12px;\n color: #64748b;\n font-weight: 600;\n margin: 0 0 16px;\n }\n\n .eyebrow-span {\n display: inline-block;\n text-transform: uppercase;\n letter-spacing: 0.16em;\n font-size: 11px;\n color: #475569;\n font-weight: 600;\n margin: 0 0 16px;\n }\n\n .eyebrow-pill {\n display: inline-block;\n text-transform: uppercase;\n letter-spacing: 0.14em;\n font-size: 12px;\n background: #f1f5f9;\n color: #334155;\n border-radius: 999px;\n padding: 4px 12px;\n font-weight: 600;\n margin: 0 0 16px;\n }\n\n .eyebrow-already-uppercase {\n letter-spacing: 0.18em;\n font-size: 11px;\n color: #b45309;\n font-weight: 700;\n margin: 0 0 16px;\n }\n\n /* ── PASS cases ── */\n\n .pass-eyebrow-no-tracking {\n text-transform: uppercase;\n letter-spacing: normal;\n font-size: 12px;\n color: #64748b;\n font-weight: 600;\n margin: 0 0 16px;\n }\n\n .pass-body-heading {\n font-size: 24px;\n font-weight: 600;\n font-family: 'Inter', sans-serif;\n margin: 0;\n }\n\n .pass-card-caption {\n text-transform: uppercase;\n letter-spacing: 0.18em;\n font-size: 11px;\n color: #94a3b8;\n font-weight: 600;\n margin: 0 0 8px;\n }\n\n .pass-bare-hero {\n font-size: 64px;\n }\n\n .pass-h2-above-h1 {\n font-size: 14px;\n text-transform: uppercase;\n letter-spacing: 0.18em;\n font-weight: 700;\n color: #475569;\n margin: 0 0 16px;\n font-family: 'Inter', sans-serif;\n }\n\n .pass-long-uppercase {\n text-transform: uppercase;\n letter-spacing: 0.14em;\n font-size: 12px;\n color: #475569;\n font-weight: 600;\n margin: 0 0 16px;\n }\n \nstyle=\"margin-top:32px\"","classText":"grid\ncol\ncol-label\ncase\neyebrow-classic\nhero\ncase\neyebrow-span\nhero\ncase\neyebrow-pill\nhero\ncase\neyebrow-already-uppercase\nhero\ncase\npass-long-uppercase\nhero\ncol\ncol-label\ncase\npass-eyebrow-no-tracking\nhero\ncase\npass-card-caption\nhero\ncase\npass-bare-hero\ncase\npass-h2-above-h1\nhero\ncase\neyebrow-classic\npass-body-heading\ncase\neyebrow-classic\nhero"}],"result":[]} {"args":["\n\n\n \n Kicker Above Heading Fixture\n \n\n\n
\n
\n

Should flag

\n\n
\n Invitation protocol\n

\"A Single Kicker Still Flags\"

\n

One tracked uppercase label above one heading is enough. No repetition required.

\n
\n\n
\n Output model\n

\"Standard Tracking Kicker\"

\n

Uppercase at 12px with 0.08em tracking is the most common authored kicker and must flag.

\n
\n\n
\n

Preview sequence

\n

\"Kicker Above An H3\"

\n

Subsection headings get no eyebrow either.

\n
\n\n
\n
Access window
\n

\"Kicker Above An H4\"

\n

The ban runs down to h4.

\n
\n\n
\n Platform notes\n

\"Sub Hero Heading\"

\n

A 32px h1 is below hero scale, so this pair belongs to the general ban.

\n
\n\n
\n materials briefing\n

\"Small Caps Kicker\"

\n

Small-caps styling is the same label in a different costume.

\n
\n\n
\n
Field studies
\n
\"Heading Role Kicker\"
\n

Elements carrying a heading role are headings for this rule.

\n
\n
\n\n
\n

Should pass

\n\n \n\n
\n Home / Journal / Archive\n

\"Breadcrumb Trail Outside Nav\"

\n

A path with separators is wayfinding, not a kicker, even without a nav wrapper.

\n
\n\n
\n News · Jan 12, 2026\n

\"Dateline Above Headline\"

\n

Editorial category-and-date meta lines are content metadata.

\n
\n\n
\n Field report\n

\"Editorial Card Meta\"

\n

Category labels inside an article card are structured metadata for scanning.

\n
\n\n
\n \n \n

\"Form Heading Is Separate\"

\n
\n\n
\n
Plate study
\n

\"Figure Caption Label\"

\n
\n\n
\n Section 4.2\n

\"Limitation Of Liability\"

\n

Legal and document numbering identifies the clause; it is not decoration.

\n
\n\n
\n § 12.3\n

\"Indemnification Clause\"

\n

Statutory references keep their marker.

\n
\n\n
\n Chapter Four\n

\"Chapter Numbering Passes\"

\n

Spelled-out chapter numbering is document structure, like legal numbering.

\n
\n\n
\n Next from\n

\"Application Panel Context\"

\n

Context labels inside application panels describe state, not decoration.

\n
\n\n
\n \n

\"Page Title After Nav\"

\n

A navigation landmark preceding the page title is chrome, not a kicker.

\n
\n\n
\n

\"48%\"

\n Growth yoy\n

Stat callouts put the small label below the number. That direction is legal.

\n
\n\n
\n A quiet lead-in sentence\n

\"Sentence Case Lead In\"

\n

Not uppercase, not small-caps: an ordinary standfirst.

\n
\n\n
\n New arrivals\n

\"Untracked Caps Label\"

\n

Uppercase without tracking lacks the kicker styling signature.

\n
\n\n
\n Archive code\n

\"Intentional Brand Label\"

\n

Deliberate brand systems can opt out with an explicit marker.

\n
\n\n
\n Platform overview\n

\"Hero Owned By Hero Rule\"

\n

Hero-scale h1 eyebrows stay with hero-eyebrow-chip; this rule stands down.

\n
\n\n
\n
\n Suite\n

\"Garden Suite\"

\n

Category labels inside sibling cards are metadata for comparison.

\n
\n
\n Suite\n

\"Sea Loft\"

\n

The label is useful because the user is scanning sibling cards.

\n
\n
\n\n
    \n
  1. \n Step 01\n

    \"Step Indicator\"

    \n

    Step indicators in ordered flows are sequence markers.

    \n
  2. \n
\n\n
\n
\n Amalfi Coast\n

\"Mockup Hero Variant One\"

\n
\n
\n
\n
\n\n\n",{"styleText":"\n body {\n margin: 0;\n font-family: Arial, sans-serif;\n background: #f6f2ec;\n color: #171411;\n }\n main {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 32px;\n padding: 32px;\n }\n .col {\n min-width: 0;\n border: 1px solid #c9bfb2;\n padding: 24px;\n }\n .case {\n min-height: 120px;\n margin: 0 0 24px;\n padding: 16px;\n background: #fffaf3;\n }\n .kicker,\n .pass-kicker,\n .pass-kicker-caps {\n display: block;\n width: 240px;\n min-height: 16px;\n margin: 0 0 8px;\n font-size: 12px;\n line-height: 16px;\n letter-spacing: 0.11em;\n color: #5b5046;\n }\n .kicker,\n .pass-kicker-caps {\n text-transform: uppercase;\n }\n /* The wild's most common kicker recipe: 0.08em tracking at a sub-13px\n size computes to under 1px, which the old absolute floor excluded. */\n .tight-tracked-kicker {\n display: block;\n width: 240px;\n min-height: 16px;\n margin: 0 0 8px;\n font-size: 12px;\n line-height: 16px;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: #5b5046;\n }\n .smallcaps-kicker {\n display: block;\n width: 240px;\n min-height: 16px;\n margin: 0 0 8px;\n font-size: 12px;\n line-height: 16px;\n letter-spacing: 0.12em;\n font-variant: small-caps;\n color: #5b5046;\n }\n .untracked-caps {\n display: block;\n width: 240px;\n min-height: 16px;\n margin: 0 0 8px;\n font-size: 12px;\n line-height: 16px;\n letter-spacing: normal;\n text-transform: uppercase;\n color: #5b5046;\n }\n h1,\n h2 {\n margin: 0 0 12px;\n font-size: 32px;\n line-height: 38px;\n font-weight: 700;\n }\n h3 {\n margin: 0 0 12px;\n font-size: 24px;\n line-height: 30px;\n font-weight: 700;\n }\n h4 {\n margin: 0 0 12px;\n font-size: 20px;\n line-height: 26px;\n font-weight: 700;\n }\n .role-heading {\n margin: 0 0 12px;\n font-size: 28px;\n line-height: 34px;\n font-weight: 700;\n }\n .hero-h1 {\n margin: 0 0 12px;\n font-size: 64px;\n line-height: 70px;\n font-weight: 700;\n }\n .hero-eyebrow {\n display: block;\n width: 240px;\n min-height: 16px;\n margin: 0 0 8px;\n font-size: 12px;\n line-height: 16px;\n letter-spacing: 2px;\n text-transform: uppercase;\n color: #5b5046;\n }\n p,\n label,\n figcaption,\n a,\n li {\n font-size: 16px;\n line-height: 24px;\n }\n nav,\n form,\n figure,\n ol {\n margin: 0 0 24px;\n padding: 16px;\n border: 1px solid #d8cec0;\n min-height: 80px;\n }\n .card-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n margin: 0 0 24px;\n }\n .suite-card {\n display: block;\n min-height: 120px;\n padding: 16px;\n border: 1px solid #d8cec0;\n background: #fff;\n }\n .taxonomy-label {\n display: block;\n margin: 0 0 8px;\n font-size: 12px;\n line-height: 16px;\n letter-spacing: 0.11em;\n text-transform: uppercase;\n color: #5b5046;\n }\n .suite-card h3 {\n font-size: 24px;\n line-height: 30px;\n }\n .stat {\n min-height: 100px;\n padding: 16px;\n background: #fffaf3;\n }\n .stat h3 {\n font-size: 40px;\n line-height: 46px;\n }\n .hidden-mockup {\n display: grid;\n gap: 12px;\n margin: 0 0 24px;\n }\n .mockup-hero {\n min-height: 120px;\n padding: 16px;\n border: 1px solid #d8cec0;\n background: #fff;\n }\n .mockup-hero h2 {\n font-size: 24px;\n line-height: 30px;\n }\n \nstyle=\"width: 220px; height: 40px;\"","classText":"col\ncase\nkicker\ncase\ntight-tracked-kicker\ncase\nkicker\ncase\nkicker\ncase\nkicker\nsub-hero\ncase\nsmallcaps-kicker\ncase\nkicker\nrole-heading\ncol\ncase\npass-kicker\ncase\npass-kicker-caps\ncase\npass-kicker-caps\ncase\npass-kicker-caps\ncase\npass-kicker\ncase\npass-kicker-caps\ncase\npass-kicker-caps\ncase\npass-kicker-caps\ncase\npass-kicker-caps\ncase\npass-kicker-caps\ncase\nstat\npass-kicker-caps\ncase\npass-kicker\ncase\nuntracked-caps\ncase\npass-kicker-caps\ncase\nhero-eyebrow\nhero-h1\ncard-grid\nsuite-card\ntaxonomy-label\nsuite-card\ntaxonomy-label\ncase\npass-kicker-caps\nhidden-mockup\nmockup-hero\ntaxonomy-label"}],"result":[]} {"args":["\n\n\n \n \n Motion Anti-Patterns — Should Flag vs Should Pass\n \n\n\n
\n \n
\n

Should flag

\n\n

Bounce / elastic easing

\n
\n

CSS bounce animation

\n

animation: bounce 1s infinite

\n
\n
\n

Elastic cubic-bezier

\n

cubic-bezier(0.68, -0.55, 0.265, 1.55)

\n
\n\n

Layout property transitions

\n
\n

transition: width

\n

Animating width causes layout thrash.

\n
\n
\n

transition: height

\n

Animating height causes layout thrash.

\n
\n
\n

transition: padding

\n

Animating padding causes layout thrash.

\n
\n
\n

transition: margin

\n

Animating margin causes layout thrash.

\n
\n
\n

transition: max-height

\n

Use grid-template-rows instead.

\n
\n
\n

transition: width, height

\n

Multiple layout properties.

\n
\n
\n

transition: width, opacity

\n

Layout property mixed with OK property.

\n
\n
\n

transition-property: width

\n

Longhand form.

\n
\n
\n\n \n
\n

Should pass

\n\n

Good easing

\n
\n

Smooth fade in

\n

animation: fadeIn with exponential ease-out

\n
\n
\n

Ease-out quart

\n

cubic-bezier(0.25, 1, 0.5, 1) — smooth deceleration

\n
\n
\n

Ease-out expo

\n

cubic-bezier(0.16, 1, 0.3, 1) — natural feel

\n
\n\n

Safe transitions (transform / opacity / color only)

\n
\n

transition: transform

\n

Transform is GPU-accelerated and safe.

\n
\n
\n

transition: opacity

\n

Opacity is GPU-accelerated and safe.

\n
\n
\n

transition: color, background-color

\n

Color transitions are paint-only, no layout.

\n
\n
\n

transition: box-shadow

\n

Shadow transitions are paint-only.

\n
\n
\n

transition: all

\n

Too common to flag — usually paired with transform/opacity.

\n
\n
\n

transition: transform, opacity, box-shadow

\n

Multiple safe properties combined.

\n
\n
\n
\n \n\n\n",{"styleText":"\n /* ─── Two-column fixture convention ─────────────────────────────\n Left column = should flag. Right column = should pass.\n Tests assert (a) every flag-column rule fires and (b) the total\n finding count matches the number of flag cases (no false\n positives leaking from the pass column).\n ──────────────────────────────────────────────────────────── */\n body { font-family: system-ui, sans-serif; background: #f9fafb; padding: 24px; margin: 0; }\n .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; max-width: 1200px; margin: 0 auto; }\n .col h2 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 16px; color: #475569; }\n .col h3 { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; margin: 24px 0 8px; color: #64748b; }\n .demo { padding: 14px 16px; background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); margin-bottom: 10px; }\n .demo h4 { font-weight: 600; font-size: 13px; margin: 0; }\n .demo p { font-size: 12px; color: #6b7280; margin: 4px 0 0; }\n\n /* ── FLAG: bounce / elastic easing ── */\n @keyframes bounce-keyframe {\n 0%, 100% { transform: translateY(0); }\n 50% { transform: translateY(-10px); }\n }\n .bounce-animation { animation: bounce-keyframe 1s infinite; }\n .elastic-transition { transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); }\n\n /* ── FLAG: layout property transitions ── */\n .width-transition { transition: width 0.3s ease; }\n .height-transition { transition: height 0.4s ease-out; }\n .padding-transition { transition: padding 0.2s linear; }\n .margin-transition { transition: margin 0.3s ease-in; }\n .max-height-transition { transition: max-height 0.5s ease; }\n .multi-layout-transition { transition: width 0.3s ease, height 0.3s ease; }\n .mixed-transition { transition: width 0.3s ease, opacity 0.3s ease; }\n .transition-property-width { transition-property: width; transition-duration: 0.3s; }\n\n /* ── PASS: good easing ── */\n @keyframes fade-in-keyframe {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .fade-in-good { animation: fade-in-keyframe 0.4s cubic-bezier(0.16, 1, 0.3, 1); }\n .ease-out-quart { transition: transform 0.4s cubic-bezier(0.25, 1, 0.5, 1); }\n .ease-out-expo { transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1); }\n\n /* ── PASS: safe transitions ── */\n .transform-transition { transition: transform 0.3s ease-out; }\n .opacity-transition { transition: opacity 0.2s ease; }\n .color-transition { transition: color 0.15s ease, background-color 0.15s ease; }\n .shadow-transition { transition: box-shadow 0.2s ease; }\n .all-transition { transition: all 0.3s ease; }\n .multi-safe-transition { transition: transform 0.3s ease, opacity 0.3s ease, box-shadow 0.2s ease; }\n ","classText":"grid\ncol\ndemo bounce-animation\ndemo elastic-transition\ndemo width-transition\ndemo height-transition\ndemo padding-transition\ndemo margin-transition\ndemo max-height-transition\ndemo multi-layout-transition\ndemo mixed-transition\ndemo transition-property-width\ncol\ndemo fade-in-good\ndemo ease-out-quart\ndemo ease-out-expo\ndemo transform-transition\ndemo opacity-transition\ndemo color-transition\ndemo shadow-transition\ndemo all-transition\ndemo multi-safe-transition"}],"result":[{"id":"bounce-easing","snippet":"animation: bounce-keyframe","selector":".bounce-animation"},{"id":"bounce-easing","snippet":"cubic-bezier(0.68, -0.55, 0.265, 1.55)","selector":".elastic-transition"},{"id":"layout-transition","snippet":"transition: width"}]} -{"args":["\n\n\n \n \n Dark Glow Anti-Patterns — Should Flag vs Should Pass\n \n\n\n
\n \n
\n

Should flag

\n\n

CSS colored glows on dark background

\n
\n
\n

Blue glow

\n

box-shadow: 0 0 20px rgba(59, 130, 246, 0.4)

\n
\n
\n

Purple glow

\n

box-shadow: 0 0 25px rgba(139, 92, 246, 0.35)

\n
\n
\n

Cyan glow

\n

box-shadow: 0 0 15px rgba(6, 182, 212, 0.5)

\n
\n
\n

Multi-shadow with colored glow

\n

Normal shadow + purple glow combined.

\n
\n
\n\n

Inline-style glow (regex path)

\n
\n
\n

Inline pink glow

\n

Inline style for regex detection path.

\n
\n
\n\n

Modern color formats, var() refs, text-shadow

\n
\n
\n

OKLCH glow

\n

box-shadow: 0 0 18px oklch(0.72 0.15 195 / 0.5)

\n
\n
\n

Hex glow

\n

box-shadow: 0 0 16px #3b82f6

\n
\n
\n

HSL glow

\n

box-shadow: 0 0 22px hsl(280, 80%, 60%)

\n
\n
\n

Glow via var()

\n

box-shadow: 0 0 10px var(--fixture-glow)

\n
\n
\n

Glowing text

\n

text-shadow: 0 0 12px #22d3ee on the heading.

\n
\n
\n\n

Zero-offset halo on LIGHT background

\n
\n

Chromatic halo on light page

\n

box-shadow: 0 0 20px oklch(0.65 0.2 300 / 0.45)

\n
\n\n

Zero-offset halo on unreadable (image) surface

\n
\n
\n

Halo over photo background

\n

box-shadow: 0 0 26px rgba(217, 70, 239, 0.5)

\n
\n
\n\n

Colored glow under an opaque gradient over an image (browser)

\n
\n
\n

Opaque dark gradient covers the photo

\n

box-shadow: 0 6px 22px rgba(249, 115, 22, 0.5)

\n
\n
\n
\n\n \n
\n

Should pass

\n\n

Light page + colored shadow

\n
\n

Colored shadow on light background

\n

Not dark mode, colored shadow is fine.

\n
\n\n

Dark cards with neutral shadows

\n
\n
\n

Normal gray shadow on dark card

\n

Gray/black shadow is not a glow.

\n
\n
\n

Focus ring (spread, no blur)

\n

Functional ring, not decorative glow.

\n
\n
\n

Tiny blur (<5px)

\n

Too subtle to be \"glowing\".

\n
\n
\n

Small dark elevation

\n

Subtle shadow on dark card.

\n
\n
\n

Medium dark elevation

\n

Standard dark card shadow.

\n
\n
\n

Large dark elevation

\n

Prominent shadow on dark card.

\n
\n
\n

Inset shadow

\n

Inner shadow, not a glow.

\n
\n
\n

Multi-shadow dark card

\n

Layered gray shadows, no color glow.

\n
\n
\n\n

Medium-gray background (not dark enough)

\n
\n

Not dark enough background

\n

Medium gray doesn't qualify as \"dark mode\".

\n
\n\n

Colored drop shadow / neutral halo on light background

\n
\n

Offset colored drop shadow

\n

Real drop shadow with a color tint — not a halo.

\n
\n
\n

Soft neutral halo

\n

Achromatic zero-offset shadow is ambient elevation.

\n
\n\n

Offset chromatic shadow on unreadable (image) surface

\n
\n
\n

Offset colored shadow over photo background

\n

Surface unknown, offset shadow — abstain, not flag.

\n
\n
\n\n

Offset chromatic shadow under a translucent gradient over an image

\n
\n
\n

The wash blends with unknowable image pixels

\n

Surface unknown — abstain, never score the wash stops as dark.

\n
\n
\n\n

Neutral text-shadow on dark card

\n
\n
\n

Offset neutral text-shadow

\n

Legibility aid, not a glow.

\n
\n
\n\n

Light elevated cards

\n
\n

Small elevation (Tailwind shadow-sm)

\n

Standard subtle card shadow.

\n
\n
\n

Medium elevation (Tailwind shadow-md)

\n

Standard card shadow.

\n
\n
\n

Warm deep shadow

\n

Large spread neutral shadow.

\n
\n
\n
\n \n\n\n",{"styleText":"\n /* Two-column fixture: left = should flag, right = should pass.\n The dark-glow rule fires when a colored box-shadow appears on a\n child whose ancestor has a dark background. Each column wraps\n its dark-context cases in its own .dark-context section so the\n parent-bg lookup behaves predictably regardless of layout. */\n body { font-family: system-ui, sans-serif; background: #f9fafb; padding: 24px; margin: 0; }\n .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; max-width: 1200px; margin: 0 auto; }\n .col h2 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 16px; color: #475569; }\n .col h3 { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; margin: 24px 0 8px; color: #64748b; }\n .dark-context { background: #111827; padding: 16px; border-radius: 12px; }\n .dark-context + .dark-context { margin-top: 12px; }\n /* Card variants: explicit dark vs light pairings, both WCAG AA compliant */\n .card { padding: 14px 16px; border-radius: 10px; margin-bottom: 10px; }\n .card h4 { font-weight: 600; font-size: 13px; margin: 0; }\n .card p { font-size: 12px; margin: 4px 0 0; }\n .card-dark { background: #1f2937; }\n .card-dark h4 { color: #f9fafb; }\n .card-dark p { color: #cbd5e1; }\n .card-light { background: white; }\n .card-light h4 { color: #0f172a; }\n .card-light p { color: #475569; }\n\n /* ── FLAG: dark background + colored glow ── */\n .glow-blue { box-shadow: 0 0 20px rgba(59, 130, 246, 0.4); }\n .glow-purple { box-shadow: 0 0 25px rgba(139, 92, 246, 0.35); }\n .glow-cyan { box-shadow: 0 0 15px rgba(6, 182, 212, 0.5); }\n .glow-multi { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3), 0 0 30px rgba(168, 85, 247, 0.3); }\n\n /* ── FLAG: modern color formats, var() refs, text-shadow ── */\n :root { --fixture-glow: oklch(0.78 0.14 155); }\n .glow-oklch { box-shadow: 0 0 18px oklch(0.72 0.15 195 / 0.5); }\n .glow-hex { box-shadow: 0 0 16px #3b82f6; }\n .glow-hsl { box-shadow: 0 0 22px hsl(280, 80%, 60%); }\n .glow-var { box-shadow: 0 0 10px var(--fixture-glow); }\n .glow-text h4 { text-shadow: 0 0 12px #22d3ee; }\n /* Zero-offset chromatic halo is slop on light backgrounds too */\n .glow-light-oklch { box-shadow: 0 0 20px oklch(0.65 0.2 300 / 0.45); }\n /* Unreadable surface: url() image ancestor. The zero-offset halo tell\n holds on ANY background, so an unknown surface must not suppress it.\n Both element loops must agree here (the browser adapter once returned\n [] for the whole element on an unresolved surface). */\n .photo-context { background-image: url('/fixtures/antipatterns/missing-photo.png'); padding: 16px; border-radius: 12px; }\n .glow-photo-halo { box-shadow: 0 0 26px rgba(217, 70, 239, 0.5); }\n /* Opaque gradient atop a url() layer: every stop is opaque, so the\n gradient provably covers the image — the surface IS the dark\n gradient, and the dark-background glow tell may score against it\n (browser hunt; the static loop has no gradient hunt). */\n .photo-opaque-grad { background: linear-gradient(#111827, #0b1220), url('/fixtures/antipatterns/missing-photo.png'); padding: 16px; border-radius: 12px; }\n .glow-photo-opaque { box-shadow: 0 6px 22px rgba(249, 115, 22, 0.5); }\n\n /* ── PASS: same dark/light backgrounds, but neutral or no glow ── */\n .light-colored-shadow { box-shadow: 0 2px 4px rgba(59, 130, 246, 0.15); }\n .dark-normal-shadow { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); }\n .dark-focus-ring { box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5); }\n .dark-subtle-shadow { box-shadow: 0 1px 3px rgba(59, 130, 246, 0.2); }\n .dark-elevated-sm { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); }\n .dark-elevated-md { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.4); }\n .dark-elevated-lg { box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5); }\n .dark-elevated-inset { box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3); }\n .dark-elevated-multi { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2); }\n .elevated-sm-light { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); }\n .elevated-md-light { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }\n .elevated-warm-light { box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.15); }\n /* Medium-gray card: lighter than dark mode, used to demonstrate \"not dark enough\" for glow detection. White text for AA contrast. */\n .card-medium { background: #6b7280; }\n .card-medium h4 { color: #ffffff; }\n .card-medium p { color: #ffffff; }\n .medium-gray-shadow { box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3); }\n /* Offset chromatic drop shadow on a light background: a real drop\n shadow, not a halo — must NOT flag under the zero-offset rule.\n (Element path; the page-level text scan caps at one finding.) */\n .light-offset-colored { box-shadow: 0 8px 20px rgba(59, 130, 246, 0.25); }\n /* Achromatic zero-offset shadow: soft ambient elevation, stays legal */\n .light-soft-neutral { box-shadow: 0 0 24px rgba(0, 0, 0, 0.15); }\n /* Offset chromatic shadow under the same unreadable url() surface:\n the dark-background glow tell needs a surface we can read — abstain. */\n .photo-offset-colored { box-shadow: 0 6px 20px rgba(16, 185, 129, 0.35); }\n /* Translucent gradient atop a url() layer: the image shows through the\n 20% wash, so the real surface is a blend with unknowable pixels (a\n white photo composites to ~#cccccc, not black). Averaging the wash\n stops as if they were the surface scored this \"dark\" — abstain. */\n .photo-translucent-grad { background: linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), url('/fixtures/antipatterns/missing-photo.png'); padding: 16px; border-radius: 12px; }\n .photo-translucent-offset { box-shadow: 0 6px 22px rgba(244, 63, 94, 0.45); }\n /* Offset neutral text-shadow on dark card: legibility aid, not a glow */\n .dark-text-offset h4 { text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); }\n \nstyle=\"box-shadow: 0 0 20px rgba(236, 72, 153, 0.5);\"","classText":"grid\ncol\ndark-context\ncard card-dark glow-blue\ncard card-dark glow-purple\ncard card-dark glow-cyan\ncard card-dark glow-multi\ndark-context\ncard card-dark\ndark-context\ncard card-dark glow-oklch\ncard card-dark glow-hex\ncard card-dark glow-hsl\ncard card-dark glow-var\ncard card-dark glow-text\ncard card-light glow-light-oklch\nphoto-context\ncard card-light glow-photo-halo\nphoto-opaque-grad\ncard card-dark glow-photo-opaque\ncol\ncard card-light light-colored-shadow\ndark-context\ncard card-dark dark-normal-shadow\ncard card-dark dark-focus-ring\ncard card-dark dark-subtle-shadow\ncard card-dark dark-elevated-sm\ncard card-dark dark-elevated-md\ncard card-dark dark-elevated-lg\ncard card-dark dark-elevated-inset\ncard card-dark dark-elevated-multi\ncard card-medium medium-gray-shadow\ncard card-light light-offset-colored\ncard card-light light-soft-neutral\nphoto-context\ncard card-light photo-offset-colored\nphoto-translucent-grad\ncard card-light photo-translucent-offset\ndark-context\ncard card-dark dark-text-offset\ncard card-light elevated-sm-light\ncard card-light elevated-md-light\ncard card-light elevated-warm-light"}],"result":[{"id":"buried-raster","snippet":"raster under a near-opaque gradient wash: linear-gradient(#111827, #0b1220), url('/fixtures/antipatterns/missing-photo.png')","selector":"the static loop has no gradient hunt). */ .photo-opaque-grad"},{"id":"dark-glow","snippet":"Zero-offset box-shadow glow (#3b82f6)","selector":".glow-blue"}]} +{"args":["\n\n\n \n \n Dark Glow Anti-Patterns — Should Flag vs Should Pass\n \n\n\n
\n \n
\n

Should flag

\n\n

CSS colored glows on dark background

\n
\n
\n

Blue glow

\n

box-shadow: 0 0 20px rgba(59, 130, 246, 0.4)

\n
\n
\n

Purple glow

\n

box-shadow: 0 0 25px rgba(139, 92, 246, 0.35)

\n
\n
\n

Cyan glow

\n

box-shadow: 0 0 15px rgba(6, 182, 212, 0.5)

\n
\n
\n

Multi-shadow with colored glow

\n

Normal shadow + purple glow combined.

\n
\n
\n\n

Inline-style glow (regex path)

\n
\n
\n

Inline pink glow

\n

Inline style for regex detection path.

\n
\n
\n\n

Modern color formats, var() refs, text-shadow

\n
\n
\n

OKLCH glow

\n

box-shadow: 0 0 18px oklch(0.72 0.15 195 / 0.5)

\n
\n
\n

Hex glow

\n

box-shadow: 0 0 16px #3b82f6

\n
\n
\n

HSL glow

\n

box-shadow: 0 0 22px hsl(280, 80%, 60%)

\n
\n
\n

Glow via var()

\n

box-shadow: 0 0 10px var(--fixture-glow)

\n
\n
\n

Glowing text

\n

text-shadow: 0 0 12px #22d3ee on the heading.

\n
\n
\n\n

Zero-offset halo on LIGHT background

\n
\n

Chromatic halo on light page

\n

box-shadow: 0 0 20px oklch(0.65 0.2 300 / 0.45)

\n
\n\n

Zero-offset halo on unreadable (image) surface

\n
\n
\n

Halo over photo background

\n

box-shadow: 0 0 26px rgba(217, 70, 239, 0.5)

\n
\n
\n\n

Colored glow under an opaque gradient over an image (browser)

\n
\n
\n

Opaque dark gradient covers the photo

\n

box-shadow: 0 6px 22px rgba(249, 115, 22, 0.5)

\n
\n
\n
\n\n \n
\n

Should pass

\n\n

Light page + colored shadow

\n
\n

Colored shadow on light background

\n

Not dark mode, colored shadow is fine.

\n
\n\n

Dark cards with neutral shadows

\n
\n
\n

Normal gray shadow on dark card

\n

Gray/black shadow is not a glow.

\n
\n
\n

Focus ring (spread, no blur)

\n

Functional ring, not decorative glow.

\n
\n
\n

Tiny blur (<5px)

\n

Too subtle to be \"glowing\".

\n
\n
\n

Small dark elevation

\n

Subtle shadow on dark card.

\n
\n
\n

Medium dark elevation

\n

Standard dark card shadow.

\n
\n
\n

Large dark elevation

\n

Prominent shadow on dark card.

\n
\n
\n

Inset shadow

\n

Inner shadow, not a glow.

\n
\n
\n

Multi-shadow dark card

\n

Layered gray shadows, no color glow.

\n
\n
\n\n

Medium-gray background (not dark enough)

\n
\n

Not dark enough background

\n

Medium gray doesn't qualify as \"dark mode\".

\n
\n\n

Colored drop shadow / neutral halo on light background

\n
\n

Offset colored drop shadow

\n

Real drop shadow with a color tint — not a halo.

\n
\n
\n

Soft neutral halo

\n

Achromatic zero-offset shadow is ambient elevation.

\n
\n\n

Offset chromatic shadow on unreadable (image) surface

\n
\n
\n

Offset colored shadow over photo background

\n

Surface unknown, offset shadow — abstain, not flag.

\n
\n
\n\n

Offset chromatic shadow under a translucent gradient over an image

\n
\n
\n

The wash blends with unknowable image pixels

\n

Surface unknown — abstain, never score the wash stops as dark.

\n
\n
\n\n

Neutral text-shadow on dark card

\n
\n
\n

Offset neutral text-shadow

\n

Legibility aid, not a glow.

\n
\n
\n\n

Light elevated cards

\n
\n

Small elevation (Tailwind shadow-sm)

\n

Standard subtle card shadow.

\n
\n
\n

Medium elevation (Tailwind shadow-md)

\n

Standard card shadow.

\n
\n
\n

Warm deep shadow

\n

Large spread neutral shadow.

\n
\n
\n
\n \n\n\n",{"styleText":"\n /* Two-column fixture: left = should flag, right = should pass.\n The dark-glow rule fires when a colored box-shadow appears on a\n child whose ancestor has a dark background. Each column wraps\n its dark-context cases in its own .dark-context section so the\n parent-bg lookup behaves predictably regardless of layout. */\n body { font-family: system-ui, sans-serif; background: #f9fafb; padding: 24px; margin: 0; }\n .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; max-width: 1200px; margin: 0 auto; }\n .col h2 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 16px; color: #475569; }\n .col h3 { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; margin: 24px 0 8px; color: #64748b; }\n .dark-context { background: #111827; padding: 16px; border-radius: 12px; }\n .dark-context + .dark-context { margin-top: 12px; }\n /* Card variants: explicit dark vs light pairings, both WCAG AA compliant */\n .card { padding: 14px 16px; border-radius: 10px; margin-bottom: 10px; }\n .card h4 { font-weight: 600; font-size: 13px; margin: 0; }\n .card p { font-size: 12px; margin: 4px 0 0; }\n .card-dark { background: #1f2937; }\n .card-dark h4 { color: #f9fafb; }\n .card-dark p { color: #cbd5e1; }\n .card-light { background: white; }\n .card-light h4 { color: #0f172a; }\n .card-light p { color: #475569; }\n\n /* ── FLAG: dark background + colored glow ── */\n .glow-blue { box-shadow: 0 0 20px rgba(59, 130, 246, 0.4); }\n .glow-purple { box-shadow: 0 0 25px rgba(139, 92, 246, 0.35); }\n .glow-cyan { box-shadow: 0 0 15px rgba(6, 182, 212, 0.5); }\n .glow-multi { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3), 0 0 30px rgba(168, 85, 247, 0.3); }\n\n /* ── FLAG: modern color formats, var() refs, text-shadow ── */\n :root { --fixture-glow: oklch(0.78 0.14 155); }\n .glow-oklch { box-shadow: 0 0 18px oklch(0.72 0.15 195 / 0.5); }\n .glow-hex { box-shadow: 0 0 16px #3b82f6; }\n .glow-hsl { box-shadow: 0 0 22px hsl(280, 80%, 60%); }\n .glow-var { box-shadow: 0 0 10px var(--fixture-glow); }\n .glow-text h4 { text-shadow: 0 0 12px #22d3ee; }\n /* Zero-offset chromatic halo is slop on light backgrounds too */\n .glow-light-oklch { box-shadow: 0 0 20px oklch(0.65 0.2 300 / 0.45); }\n /* Unreadable surface: url() image ancestor. The zero-offset halo tell\n holds on ANY background, so an unknown surface must not suppress it.\n Both element loops must agree here (the browser adapter once returned\n [] for the whole element on an unresolved surface). */\n .photo-context { background-image: url('/fixtures/antipatterns/missing-photo.png'); padding: 16px; border-radius: 12px; }\n .glow-photo-halo { box-shadow: 0 0 26px rgba(217, 70, 239, 0.5); }\n /* Opaque gradient atop a url() layer: every stop is opaque, so the\n gradient provably covers the image — the surface IS the dark\n gradient, and the dark-background glow tell may score against it\n (browser hunt; the static loop has no gradient hunt). */\n .photo-opaque-grad { background: linear-gradient(#111827, #0b1220), url('/fixtures/antipatterns/missing-photo.png'); padding: 16px; border-radius: 12px; }\n .glow-photo-opaque { box-shadow: 0 6px 22px rgba(249, 115, 22, 0.5); }\n\n /* ── PASS: same dark/light backgrounds, but neutral or no glow ── */\n .light-colored-shadow { box-shadow: 0 2px 4px rgba(59, 130, 246, 0.15); }\n .dark-normal-shadow { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); }\n .dark-focus-ring { box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5); }\n .dark-subtle-shadow { box-shadow: 0 1px 3px rgba(59, 130, 246, 0.2); }\n .dark-elevated-sm { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); }\n .dark-elevated-md { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.4); }\n .dark-elevated-lg { box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5); }\n .dark-elevated-inset { box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3); }\n .dark-elevated-multi { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2); }\n .elevated-sm-light { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); }\n .elevated-md-light { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }\n .elevated-warm-light { box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.15); }\n /* Medium-gray card: lighter than dark mode, used to demonstrate \"not dark enough\" for glow detection. White text for AA contrast. */\n .card-medium { background: #6b7280; }\n .card-medium h4 { color: #ffffff; }\n .card-medium p { color: #ffffff; }\n .medium-gray-shadow { box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3); }\n /* Offset chromatic drop shadow on a light background: a real drop\n shadow, not a halo — must NOT flag under the zero-offset rule.\n (Element path; the page-level text scan caps at one finding.) */\n .light-offset-colored { box-shadow: 0 8px 20px rgba(59, 130, 246, 0.25); }\n /* Achromatic zero-offset shadow: soft ambient elevation, stays legal */\n .light-soft-neutral { box-shadow: 0 0 24px rgba(0, 0, 0, 0.15); }\n /* Offset chromatic shadow under the same unreadable url() surface:\n the dark-background glow tell needs a surface we can read — abstain. */\n .photo-offset-colored { box-shadow: 0 6px 20px rgba(16, 185, 129, 0.35); }\n /* Translucent gradient atop a url() layer: the image shows through the\n 20% wash, so the real surface is a blend with unknowable pixels (a\n white photo composites to ~#cccccc, not black). Averaging the wash\n stops as if they were the surface scored this \"dark\" — abstain. */\n .photo-translucent-grad { background: linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), url('/fixtures/antipatterns/missing-photo.png'); padding: 16px; border-radius: 12px; }\n .photo-translucent-offset { box-shadow: 0 6px 22px rgba(244, 63, 94, 0.45); }\n /* Offset neutral text-shadow on dark card: legibility aid, not a glow */\n .dark-text-offset h4 { text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); }\n \nstyle=\"box-shadow: 0 0 20px rgba(236, 72, 153, 0.5);\"","classText":"grid\ncol\ndark-context\ncard card-dark glow-blue\ncard card-dark glow-purple\ncard card-dark glow-cyan\ncard card-dark glow-multi\ndark-context\ncard card-dark\ndark-context\ncard card-dark glow-oklch\ncard card-dark glow-hex\ncard card-dark glow-hsl\ncard card-dark glow-var\ncard card-dark glow-text\ncard card-light glow-light-oklch\nphoto-context\ncard card-light glow-photo-halo\nphoto-opaque-grad\ncard card-dark glow-photo-opaque\ncol\ncard card-light light-colored-shadow\ndark-context\ncard card-dark dark-normal-shadow\ncard card-dark dark-focus-ring\ncard card-dark dark-subtle-shadow\ncard card-dark dark-elevated-sm\ncard card-dark dark-elevated-md\ncard card-dark dark-elevated-lg\ncard card-dark dark-elevated-inset\ncard card-dark dark-elevated-multi\ncard card-medium medium-gray-shadow\ncard card-light light-offset-colored\ncard card-light light-soft-neutral\nphoto-context\ncard card-light photo-offset-colored\nphoto-translucent-grad\ncard card-light photo-translucent-offset\ndark-context\ncard card-dark dark-text-offset\ncard card-light elevated-sm-light\ncard card-light elevated-md-light\ncard card-light elevated-warm-light"}],"result":[{"id":"buried-raster","snippet":"raster under a near-opaque gradient wash: linear-gradient(#111827, #0b1220), url('/fixtures/antipatterns/missing-photo.png')","selector":".photo-opaque-grad"},{"id":"dark-glow","snippet":"Zero-offset box-shadow glow (#3b82f6)","selector":".glow-blue"}]} {"args":["\n\n\n \n \n Flush Against Border; Should Flag vs Should Pass\n \n\n\n
\n\n \n
\n

Should flag

\n\n

Border + zero side-padding

\n
\n section: full border, padding 28px 0 0; label and pills touch the left/right border lines.\n
\n Supported dev servers\n
    \n
  • Vite
  • \n
  • Next.js
  • \n
  • SvelteKit
  • \n
  • Astro
  • \n
\n
\n
\n\n

All sides flush against a border

\n
\n card: 1px border on all sides, padding 0; heading and body touch every side.\n
\n

Card flush against the borders

\n

Body copy sits flush against every visible border line.

\n
\n
\n\n

Background color, zero padding

\n
\n no border, but a non-transparent bg; text flush against the bg's edge counts too.\n
\n

Title flush against the bg edge

\n

Body copy with zero padding around a colored fill.

\n
\n
\n\n

Outline (not border), zero padding

\n
\n outline draws outside the box; content at the box edge sits next to the outline.\n
\n

Title flush against the outline

\n

The outline counts as a visible boundary too.

\n
\n
\n\n

Left-side flush, other sides fine

\n
\n card: padding 12px 16px 12px 0; text touches the left border, other sides are inset.\n
\n

Title flush only on the left

\n

Padding-left zero with a full border.

\n
\n
\n
\n\n \n
\n

Should pass

\n\n

No boundary at all

\n
\n no bg, no border, no outline; zero padding is fine.\n
\n Supported dev servers\n
    \n
  • Vite
  • \n
  • Next.js
  • \n
  • SvelteKit
  • \n
\n
\n
\n\n

Top-only border, no side borders

\n
\n border-top only, padding-top 24px; no left/right border to be flush against.\n
\n Supported dev servers\n
    \n
  • Vite
  • \n
  • Next.js
  • \n
  • SvelteKit
  • \n
\n
\n
\n\n

Bordered card with proper inset

\n
\n card: 1px border + padding 18px 20px; comfortable inset on every side.\n
\n

Card with proper inset

\n

Body copy sits comfortably away from the borders.

\n
\n
\n\n

Background card with proper inset

\n
\n non-transparent bg + padding 14px 18px; content has room to breathe.\n
\n

Title with inset on bg

\n

Body copy is comfortably inset from the bg edge.

\n
\n
\n\n

Outline with proper inset

\n
\n outline + padding 14px 18px; content is comfortably inside.\n
\n

Title with inset and outline

\n

Outline boundary with proper inset works.

\n
\n
\n\n

Image-only container, no text

\n
\n card: padding 0, border all sides; no text means nothing can be flush.\n
\n
\n
\n
\n\n

Child margins provide the inset

\n
\n card: parent padding 0, but text children have margins that create visible inset.\n
\n

Title inset by child margin

\n

Body copy is not visually flush even though the parent has zero padding.

\n
\n
\n\n

Inner shell provides the inset

\n
\n card: parent padding 0, but an inner shell is offset from every edge.\n
\n
\n

Title inset by inner shell

\n

The direct child creates the breathing room.

\n
\n
\n
\n\n

Same background as parent

\n
\n child padding 0, but it has the same background as the parent, so no child boundary is visible.\n
\n
\n

Same-surface visual shell

\n

The text belongs to the parent surface, not to a distinct zero-padding card.

\n
\n
\n
\n\n

Full-bleed marquee shell

\n
\n marquee shell clips animated cards intentionally; the cards, not the shell, own text padding.\n
\n
\n

Quote card with its own comfortable padding.

\n

Another moving card, also padded inside.

\n
\n
\n
\n\n

Inner text surface

\n
\n outer shell has a scene background; the inner window owns the actual text padding.\n
\n
\n

Build step running

\n

Detector output belongs to the mock window.

\n
\n
\n
\n
\n
\n \n\n\n",{"styleText":"\n /* Fixture for the flush-against-border rule.\n\n The rule is more general than its name suggests: it fires when a\n container has any visible boundary (border, outline, OR a\n non-transparent background) AND zero inline padding on the\n bounded side, AND text-bearing children land flush against that\n boundary.\n\n Distinct from cramped-padding (which fires when an element has\n its OWN direct text and the padding/font-size ratio is wrong).\n Here the container has NO direct text; only children; so\n cramped-padding gives it a pass. */\n\n body { font-family: system-ui, sans-serif; background: #fafafa; padding: 24px; margin: 0; color: #0f172a; line-height: 1.5; }\n .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; max-width: 1400px; margin: 0 auto; }\n .col h2 { font-size: 22px; font-weight: 700; margin: 0 0 16px; color: #0f172a; line-height: 1.2; }\n .col h3 { font-size: 13px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; margin: 28px 0 10px; color: #64748b; }\n .case { margin-bottom: 14px; }\n .case-label { display: block; font-size: 12px; color: #64748b; margin-bottom: 8px; font-style: italic; max-width: 60ch; }\n\n /* ────────────────────────────────────────────────────────────\n SHOULD FLAG\n ──────────────────────────────────────────────────────────── */\n\n /* ── FLAG: the /live-mode-frameworks bug; border + zero side-padding ── */\n .flag-frameworks {\n padding: 28px 0 0;\n background: #fffbe6;\n border: 1px solid #d4a017;\n border-radius: 8px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n .flag-frameworks-label {\n font-family: ui-monospace, monospace;\n font-size: 11px;\n letter-spacing: 0.18em;\n text-transform: uppercase;\n color: #8b6914;\n }\n .flag-frameworks-list {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n list-style: none;\n padding: 0;\n margin: 0;\n }\n .flag-frameworks-list li {\n font-family: ui-monospace, monospace;\n font-size: 13px;\n padding: 6px 12px;\n background: #fef3c7;\n border: 1px solid #d4a017;\n border-radius: 4px;\n color: #78350f;\n }\n\n /* ── FLAG: child text flush against the four borders of a card ── */\n .flag-card-borders {\n padding: 0;\n background: white;\n border: 1px solid #cbd5e1;\n border-radius: 8px;\n }\n .flag-card-borders h4 { margin: 0; font-size: 14px; color: #0f172a; }\n .flag-card-borders p { margin: 4px 0 0; font-size: 13px; color: #475569; }\n\n /* ── FLAG: non-transparent background, zero padding ── */\n .flag-bg-only {\n padding: 0;\n background: #fde68a;\n border-radius: 6px;\n border: 0;\n }\n .flag-bg-only h4 { margin: 0; font-size: 14px; color: #78350f; }\n .flag-bg-only p { margin: 4px 0 0; font-size: 13px; color: #78350f; }\n\n /* ── FLAG: outline (no border), zero padding ── */\n .flag-outline-only {\n padding: 0;\n background: transparent;\n border: 0;\n outline: 2px solid #94a3b8;\n outline-offset: 0;\n }\n .flag-outline-only h4 { margin: 0; font-size: 14px; color: #0f172a; }\n .flag-outline-only p { margin: 4px 0 0; font-size: 13px; color: #475569; }\n\n /* ── FLAG: border-left + zero left-padding, other sides fine ── */\n .flag-asym-leftflush {\n padding: 12px 16px 12px 0;\n background: white;\n border: 1px solid #cbd5e1;\n border-radius: 4px;\n }\n .flag-asym-leftflush h4 { margin: 0; font-size: 14px; color: #0f172a; }\n .flag-asym-leftflush p { margin: 4px 0 0; font-size: 13px; color: #475569; }\n\n /* ────────────────────────────────────────────────────────────\n SHOULD PASS\n ──────────────────────────────────────────────────────────── */\n\n /* ── PASS: no boundary at all (no bg, no border, no outline) ── */\n .pass-no-boundary {\n padding: 0;\n background: transparent;\n border: 0;\n outline: 0;\n }\n .pass-no-boundary-label {\n font-family: ui-monospace, monospace;\n font-size: 11px;\n letter-spacing: 0.18em;\n text-transform: uppercase;\n color: #475569;\n margin-bottom: 10px;\n }\n .pass-no-boundary-list {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n list-style: none;\n padding: 0;\n margin: 0;\n }\n .pass-no-boundary-list li {\n font-size: 13px;\n padding: 6px 12px;\n background: #f5f5f7;\n border: 1px solid #e2e8f0;\n border-radius: 4px;\n color: #0f172a;\n }\n\n /* ── PASS: top-only border, no left/right border, padding-top fine ── */\n .pass-top-rule {\n padding: 24px 0 0;\n background: transparent;\n border: 0;\n border-top: 1px solid #cbd5e1;\n }\n .pass-top-rule-label {\n font-family: ui-monospace, monospace;\n font-size: 11px;\n letter-spacing: 0.18em;\n text-transform: uppercase;\n color: #475569;\n margin-bottom: 10px;\n }\n .pass-top-rule-list {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n list-style: none;\n padding: 0;\n margin: 0;\n }\n .pass-top-rule-list li {\n font-size: 13px;\n padding: 6px 12px;\n background: #f5f5f7;\n border: 1px solid #e2e8f0;\n border-radius: 4px;\n color: #0f172a;\n }\n\n /* ── PASS: bordered card with comfortable inset on all sides ── */\n .pass-bordered-padded {\n padding: 18px 20px;\n background: white;\n border: 1px solid #cbd5e1;\n border-radius: 8px;\n }\n .pass-bordered-padded h4 { margin: 0; font-size: 14px; color: #0f172a; }\n .pass-bordered-padded p { margin: 4px 0 0; font-size: 13px; color: #475569; }\n\n /* ── PASS: background card with comfortable inset ── */\n .pass-bg-padded {\n padding: 14px 18px;\n background: #fde68a;\n border-radius: 6px;\n border: 0;\n }\n .pass-bg-padded h4 { margin: 0; font-size: 14px; color: #78350f; }\n .pass-bg-padded p { margin: 4px 0 0; font-size: 13px; color: #78350f; }\n\n /* ── PASS: outline with comfortable inset ── */\n .pass-outline-padded {\n padding: 14px 18px;\n outline: 2px solid #94a3b8;\n outline-offset: 0;\n border: 0;\n background: transparent;\n }\n .pass-outline-padded h4 { margin: 0; font-size: 14px; color: #0f172a; }\n .pass-outline-padded p { margin: 4px 0 0; font-size: 13px; color: #475569; }\n\n /* ── PASS: image-only container, no text means no flush issue ── */\n .pass-image-only {\n padding: 0;\n background: white;\n border: 1px solid #cbd5e1;\n border-radius: 8px;\n width: 200px;\n overflow: hidden;\n }\n .pass-image-only .placeholder-img {\n width: 100%;\n height: 80px;\n background: linear-gradient(135deg, #cbd5e1, #94a3b8);\n }\n\n /* ── PASS: parent has zero padding, but child margins create the visible inset ── */\n .pass-margin-inset {\n padding: 0;\n background: white;\n border: 1px solid #cbd5e1;\n border-radius: 8px;\n }\n .pass-margin-inset h4 { margin: 16px 18px 4px; font-size: 14px; color: #0f172a; }\n .pass-margin-inset p { margin: 0 18px 16px; font-size: 13px; color: #475569; }\n\n /* ── PASS: an inner shell, not the parent, owns the inset spacing ── */\n .pass-inner-shell {\n padding: 0;\n background: white;\n border: 1px solid #cbd5e1;\n border-radius: 8px;\n }\n .pass-inner-shell .inner-shell {\n margin: 18px;\n }\n .pass-inner-shell h4 { margin: 0 0 4px; font-size: 14px; color: #0f172a; }\n .pass-inner-shell p { margin: 0; font-size: 13px; color: #475569; }\n\n /* ── PASS: same background as parent, so the child draws no visible boundary ── */\n .pass-same-bg-parent {\n padding: 16px;\n background: #eef2f7;\n border-radius: 8px;\n }\n .pass-same-bg-child {\n padding: 0;\n background: #eef2f7;\n border: 0;\n border-radius: 8px;\n }\n .pass-same-bg-child h4 { margin: 0; font-size: 14px; color: #0f172a; }\n .pass-same-bg-child p { margin: 4px 0 0; font-size: 13px; color: #475569; }\n\n /* ── PASS: full-bleed marquee/surface, not a text card missing padding ── */\n .pass-marquee-shell {\n padding: 0;\n background: #172033;\n color: white;\n overflow: hidden;\n border: 0;\n }\n .pass-marquee-track {\n display: flex;\n gap: 12px;\n width: max-content;\n }\n .pass-marquee-card {\n width: 220px;\n padding: 14px;\n background: #23304a;\n border: 1px solid rgba(255, 255, 255, 0.16);\n }\n .pass-marquee-card p { margin: 0; font-size: 13px; }\n\n /* ── PASS: outer scene owns background; inner text surface owns padding ── */\n .pass-inner-text-surface {\n padding: 0;\n background: #0f172a;\n color: white;\n overflow: hidden;\n border: 0;\n }\n .pass-inner-text-surface-window {\n display: grid;\n gap: 6px;\n min-height: 92px;\n padding: 14px;\n background: #111827;\n border: 1px solid rgba(255, 255, 255, 0.16);\n }\n .pass-inner-text-surface-window p { margin: 0; font-size: 13px; }\n ","classText":"grid\ncol\ncase\ncase-label\nflag-frameworks\nflag-frameworks-label\nflag-frameworks-list\ncase\ncase-label\nflag-card-borders\ncase\ncase-label\nflag-bg-only\ncase\ncase-label\nflag-outline-only\ncase\ncase-label\nflag-asym-leftflush\ncol\ncase\ncase-label\npass-no-boundary\npass-no-boundary-label\npass-no-boundary-list\ncase\ncase-label\npass-top-rule\npass-top-rule-label\npass-top-rule-list\ncase\ncase-label\npass-bordered-padded\ncase\ncase-label\npass-bg-padded\ncase\ncase-label\npass-outline-padded\ncase\ncase-label\npass-image-only\nplaceholder-img\ncase\ncase-label\npass-margin-inset\ncase\ncase-label\npass-inner-shell\ninner-shell\ncase\ncase-label\npass-same-bg-parent\npass-same-bg-child\ncase\ncase-label\npass-marquee-shell\npass-marquee-track\npass-marquee-card\npass-marquee-card\ncase\ncase-label\npass-inner-text-surface\npass-inner-text-surface-window"}],"result":[]} {"args":["\n\n\n \n Oversized H1 fixture\n \n\n\n
\n \n
\n

A sweeping product headline that runs on far too long to sit at this size

\n

Everything your whole team needs to launch quickly and finally rest at night

\n
\n\n \n
\n

Bold.

\n

Ship faster

\n

A perfectly ordinary headline that is reasonably long but set at a sane, readable size

\n
\n
\n\n\n",{"styleText":"\n body { font-family: Georgia, serif; margin: 0; color: #1a1a1a; background: #fff; }\n .cols { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding: 24px; }\n .col { padding: 16px; }\n h1 { margin: 0 0 24px; line-height: 1.05; }\n \nstyle=\"font-size: 96px\"\nstyle=\"font-size: 80px\"\nstyle=\"font-size: 168px\"\nstyle=\"font-size: 120px\"\nstyle=\"font-size: 40px\"","classText":"cols\ncol flag\ncol pass"}],"result":[]} {"args":["\n\n\n \n Extreme negative tracking fixture\n \n\n\n
\n \n
\n

Tracking crushed em paragraph copy that should still read as a sentence.

\n

Tracking crushed pixels heading line

\n

Tracking crushed heading variant text

\n
\n\n \n
\n

Optical tighten display

\n

Plain paragraph copy with default tracking that reads comfortably.

\n
\n
\n\n\n",{"styleText":"\n body { font-family: Georgia, serif; margin: 0; color: #1a1a1a; background: #fff; }\n .cols { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding: 24px; }\n .col { padding: 16px; }\n p, h2 { margin: 0 0 20px; }\n \nstyle=\"letter-spacing: -0.06em\"\nstyle=\"letter-spacing: -4px; font-size: 64px\"\nstyle=\"letter-spacing: -0.07em\"\nstyle=\"letter-spacing: -0.02em; font-size: 72px\"\nstyle=\"letter-spacing: normal\"","classText":"cols\ncol flag\ncol pass"}],"result":[]}