/* ══════════════════════════════════════════════════════════════════════
   BAGENGINE VISUAL DIGESTIBILITY LAYER (.article-wrap)
   Teal #6d5aef + sky #10b981 port of the light system (matches the
   Prescription Savings Finder tool). Same component class vocabulary
   network-wide; per-article bespoke graphics add CONTENT, this provides
   STYLE + motion + grids. Chassis port — canonized 2026-07-01.
   var(--x, fallback) syntax so it is robust to overhaul-v3 token gaps.
   ══════════════════════════════════════════════════════════════════════ */
.article-wrap{--rx-a:#6d5aef;--rx-b:#10b981}

/* hero illustration */
.article-wrap .lc-illus{max-width:560px;margin:1.7rem auto .7rem;display:block}
.article-wrap .lc-illus svg{width:100%;height:auto;display:block;filter:drop-shadow(0 12px 26px rgba(109,90,239,.18))}

/* accent-bar headings */
.article-wrap h2{position:relative;padding-left:1.05rem}
.article-wrap h2::before{content:"";position:absolute;left:0;top:.18em;bottom:.18em;width:5px;border-radius:3px;background:linear-gradient(180deg,var(--rx-a),var(--rx-b))}

/* lede */
.article-wrap .article-intro,.article-wrap p.lede{font-size:1.14rem;line-height:1.75;color:var(--text,#44403c)}

/* pull-quote */
.article-wrap .pq{margin:2.2rem 0;padding:.3rem 0 .3rem 1.5rem;border-left:4px solid var(--rx-a);font-family:var(--display,'Instrument Serif',Georgia),serif;font-weight:700;font-size:clamp(1.2rem,2.5vw,1.6rem);line-height:1.38;color:var(--dark,#1c1917);letter-spacing:-.01em}
.article-wrap .pq .hl{background:linear-gradient(90deg,var(--rx-a),var(--rx-b));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.article-wrap .pq .src{display:block;margin-top:.55rem;font-family:var(--mono,monospace),monospace;font-size:.68rem;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--muted,#a8a29e)}

/* concept split */
.article-wrap .lc-split{display:grid;grid-template-columns:1fr 1fr;margin:1.7rem 0;border:1px solid var(--border,#e7e5e4);border-radius:14px;overflow:hidden;background:#fff}
.article-wrap .lc-split figcaption{grid-column:1/-1;order:2;font-size:.79rem;color:var(--text-light,#78716c);line-height:1.55;padding:.85rem 1.2rem;background:var(--paper,#faf6ef);border-top:1px solid var(--border,#e7e5e4)}
.article-wrap .lc-split .pane{padding:1.3rem 1.35rem}
.article-wrap .lc-split .a{background:linear-gradient(160deg,#f5f3ff,#fff)}
.article-wrap .lc-split .b{background:linear-gradient(160deg,#ecfdf5,#fff);border-left:1px solid var(--border,#e7e5e4)}
.article-wrap .lc-split .ph{font-family:var(--mono,monospace),monospace;font-size:10px;letter-spacing:.14em;text-transform:uppercase;margin:0 0 .2rem;font-weight:700}
.article-wrap .lc-split .a .ph{color:#4c3bd4}
.article-wrap .lc-split .b .ph{color:#047857}
.article-wrap .lc-split .sub{font-size:.76rem;color:var(--text-light,#78716c);margin:0 0 .85rem}
.article-wrap .lc-split .chips{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.85rem}
.article-wrap .lc-split .chip{font-family:var(--mono,monospace),monospace;font-size:10px;color:var(--text,#44403c);background:#fff;border:1px solid var(--border,#e7e5e4);border-radius:5px;padding:.22rem .52rem}
.article-wrap .lc-split .big{font-family:var(--display,'Instrument Serif',Georgia),serif;font-weight:800;font-size:1.16rem;margin:.1rem 0 .55rem;letter-spacing:-.02em}
.article-wrap .lc-split .a .big{color:#4c3bd4}
.article-wrap .lc-split .b .big{color:#047857}
.article-wrap .lc-split .tags{margin:0;font-size:.84rem;color:var(--text,#44403c);line-height:1.85}
.article-wrap .lc-split .tags .t{display:flex;align-items:flex-start;gap:.45rem}
.article-wrap .lc-split .tags .t::before{content:"";width:5px;height:5px;border-radius:50%;flex:none;margin-top:.5em}
.article-wrap .lc-split .a .tags .t::before{background:var(--rx-a)}
.article-wrap .lc-split .b .tags .t::before{background:var(--rx-b)}

/* key callout */
.article-wrap .key{display:flex;gap:.9rem;align-items:flex-start;margin:1.8rem 0;padding:1.05rem 1.2rem;background:linear-gradient(135deg,#f5f3ff,#ecfdf5);border:1px solid #c4b8fb;border-radius:13px}
.article-wrap .key .ic{flex:none;width:30px;height:30px;border-radius:9px;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,var(--rx-a),var(--rx-b));font-family:var(--display,'Instrument Serif',Georgia),serif;font-weight:800;color:#fff;font-size:1rem}
.article-wrap .key .kt{margin:0;color:var(--text,#44403c);font-size:.94rem;line-height:1.62}
.article-wrap .key .kt b{color:var(--dark,#1c1917)}

/* screenshot frame */
.article-wrap .shot{margin:1.8rem 0;border:1px solid var(--border,#e7e5e4);border-radius:13px;overflow:hidden;background:#fff;box-shadow:0 8px 28px rgba(24,24,27,.08)}
.article-wrap .shot .bar{display:flex;align-items:center;gap:.5rem;padding:.55rem .85rem;background:var(--paper,#faf6ef);border-bottom:1px solid var(--border,#e7e5e4)}
.article-wrap .shot .bar i{width:9px;height:9px;border-radius:50%;background:#d4d4d8;display:inline-block}
.article-wrap .shot .bar .u{margin-left:.6rem;font-family:var(--mono,monospace),monospace;font-size:11px;color:var(--muted,#a8a29e)}
.article-wrap .shot img{display:block;width:100%;height:auto}
.article-wrap .shot figcaption{font-size:.8rem;color:var(--text-light,#78716c);line-height:1.5;padding:.7rem .95rem;border-top:1px solid var(--border,#e7e5e4)}

/* data dashboard */
.article-wrap .crm-dash{display:grid;grid-template-columns:repeat(3,1fr);gap:.8rem;margin:1.9rem 0 .6rem}
.article-wrap .crm-dash .dcard{background:#fff;border:1px solid var(--border,#e7e5e4);border-radius:14px;padding:1.05rem 1.1rem 1.15rem;box-shadow:0 8px 24px rgba(24,24,27,.06);display:flex;flex-direction:column}
.article-wrap .crm-dash .dh{font-family:var(--mono,monospace),monospace;font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted,#a8a29e);margin:0 0 .7rem}
.article-wrap .crm-dash .dsub{font-size:.73rem;color:var(--text-light,#78716c);margin:.7rem 0 0;line-height:1.4}
.article-wrap .gauge{position:relative;width:104px;height:104px;margin:.1rem auto .1rem}
.article-wrap .gauge svg{transform:rotate(-90deg)}
.article-wrap .gauge .ring{fill:none;stroke:#e6e2fd;stroke-width:10}
.article-wrap .gauge .prog{fill:none;stroke:url(#gaugeg);stroke-width:10;stroke-linecap:round;stroke-dasharray:283;stroke-dashoffset:283;animation:gFill 1.9s cubic-bezier(.2,.8,.2,1) forwards}
@keyframes gFill{to{stroke-dashoffset:var(--goff,23)}}
.article-wrap .gauge .num{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center}
.article-wrap .gauge .num b{font-family:var(--display,'Instrument Serif',Georgia),serif;font-size:1.75rem;font-weight:800;color:#4c3bd4;line-height:1}
.article-wrap .gauge .num em{font-family:var(--mono,monospace),monospace;font-size:7.5px;letter-spacing:.12em;color:var(--muted,#a8a29e);text-transform:uppercase;font-style:normal;margin-top:.15rem}
.article-wrap .pbars{display:flex;flex-direction:column;gap:.5rem;padding:.55rem 0;flex:1;justify-content:center}
.article-wrap .pbar{height:13px;border-radius:7px;background:linear-gradient(90deg,var(--rx-a),var(--rx-b));transform-origin:left;transform:scaleX(0);animation:pGrow 1.3s cubic-bezier(.2,.8,.2,1) forwards}
@keyframes pGrow{to{transform:scaleX(1)}}
.article-wrap .echips{display:flex;flex-wrap:wrap;gap:.4rem;padding:.5rem 0;flex:1;align-content:center}
.article-wrap .echip{font-family:var(--mono,monospace),monospace;font-size:10px;color:#15803d;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:6px;padding:.26rem .55rem;display:flex;align-items:center;gap:.32rem;opacity:0;animation:eIn .5s ease forwards}
.article-wrap .echip::before{content:"";width:5px;height:5px;border-radius:50%;background:#22c55e;flex:none}
@keyframes eIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}

/* verdict grid */
.article-wrap .verdict-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.8rem;margin:1.9rem 0}
.article-wrap .vcard{background:#fff;border:1px solid var(--border,#e7e5e4);border-radius:13px;padding:1.1rem 1.15rem;border-top:3px solid var(--rx-a);transition:transform .2s,box-shadow .2s}
.article-wrap .vcard:hover{transform:translateY(-4px);box-shadow:0 16px 34px -14px rgba(109,90,239,.4)}
.article-wrap .vcard.top{background:linear-gradient(160deg,#f5f3ff,#fff)}
.article-wrap .vcard .vt{font-family:var(--mono,monospace),monospace;font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted,#a8a29e);margin-bottom:.4rem}
.article-wrap .vcard .vn{font-family:var(--display,'Instrument Serif',Georgia),serif;font-weight:800;font-size:1.18rem;color:#4c3bd4;letter-spacing:-.02em;margin-bottom:.35rem}
.article-wrap .vcard .vd{font-size:.85rem;color:var(--text,#44403c);line-height:1.5}

/* scroll reveal */
.article-wrap .reveal{opacity:0;transform:translateY(18px);transition:opacity .6s ease,transform .6s ease}
.article-wrap .reveal.in{opacity:1;transform:none}
@media(prefers-reduced-motion:reduce){.article-wrap .reveal{opacity:1;transform:none;transition:none}}

/* ── FLOATING CHAPTER-RAIL (scroll-spy TOC in the right whitespace) ── */
.rx-rail{position:fixed!important;top:50%!important;transform:translateY(-50%)!important;right:max(1.1rem,calc((100vw - 1180px)/2 - 172px))!important;left:auto!important;bottom:auto!important;width:172px!important;z-index:40;display:block!important;margin:0!important;font-family:var(--body,system-ui),sans-serif}
.rx-rail .rt{font-family:var(--mono,monospace),monospace;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted,#a8a29e);margin:0 0 .7rem;padding-left:.85rem}
.rx-rail ul{list-style:none;margin:0;padding:0;border-left:1px solid var(--border,#e7e5e4)}
.rx-rail li{margin:0}
.rx-rail a{display:block;padding:.28rem 0 .28rem .85rem;margin-left:-1px;border-left:2px solid transparent;font-size:.76rem;line-height:1.35;color:var(--text-light,#78716c);text-decoration:none;transition:color .16s,border-color .16s;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rx-rail a:hover{color:var(--rx-a)}
.rx-rail a.on{color:var(--rx-a);border-left-color:var(--rx-a);font-weight:600}
/* ⛔ `!important` IN THE BASE RULE DEFEATED THE MEDIA QUERY WRITTEN TO HIDE THIS (RENDER BIBLE §9).
   `.rx-rail` above declares `display:block!important`, so this rule — correct, present, and
   targeting the right breakpoint — LOST every time and the rail never hid on a phone. Measured
   2026-08-25 by `mobile-check.mjs` at 390px: `div.rx-rail w=172 right=534`, a 172px element parked
   144px off the right edge of every bagengine article, contributing to a horizontal scroll on the
   property's entire article corpus. The rule was not missing and was not wrong; it was outranked.
   ⭐ A media query cannot beat `!important` no matter how specific the breakpoint is. */
@media(max-width:1300px){.rx-rail{display:none!important}}

@media(max-width:760px){.article-wrap .crm-dash,.article-wrap .verdict-grid{grid-template-columns:1fr}}
@media(max-width:680px){.article-wrap .lc-split{grid-template-columns:1fr}.article-wrap .lc-split .b{border-left:0;border-top:1px solid var(--border,#e7e5e4)}}

/* drug-page price bars + eyebrow spacing (added 2026-07-01d) */
.article-tags .article-tag:not(:last-child){margin-right:.5rem}
.article-wrap .rxdbar rect.b{transform:scaleY(0);transform-box:fill-box;transform-origin:bottom;animation:rxdbarGrow .9s cubic-bezier(.2,.8,.2,1) forwards}
@keyframes rxdbarGrow{to{transform:scaleY(1)}}
.article-wrap .rxdbar text{font-family:'JetBrains Mono',monospace}

.article-wrap .bebar rect.b{transform:scaleY(0);transform-box:fill-box;transform-origin:bottom;animation:bebarGrow .9s cubic-bezier(.2,.8,.2,1) forwards}
@keyframes bebarGrow{to{transform:scaleY(1)}}
@media(prefers-reduced-motion:reduce){.article-wrap .bebar rect.b{animation:none;transform:none}}
.article-tags .article-tag:not(:last-child){margin-right:.5rem}

/* ── WCAG AA CONTRAST REPAIR (2026-08-25) ────────────────────────────────────────────────────
   Measured on the live homepage, three separate causes:

   1. `--dim` resolves to #7c79a5 here and carries the small uppercase labels
      (.tb-label, .sb-section-label). On this property's dark surfaces that is 1.98:1 and 2.04:1
      against a 4.5 floor — the worst contrast measured anywhere on the network. 165 colour uses,
      ZERO background uses, so lifting the value is safe. #7c79a5 gives 4.68 / 4.83.
      ⚠️ `--dim` is declared with THREE different values across this property (#71717f, #7c79a5,
      #454c57). Only the failing one is changed; a blanket token rewrite would have flattened a
      distinction somebody made deliberately.

   2. `.nav-cta` is white on var(--accent) #7c6af7 = 3.99:1. ⛔ The accent is NOT retuned: it is used
      122 times as TEXT and 9 times as a FILL, and those need OPPOSITE directions (lighter for text
      on dark, darker for white-on-fill). A token change cannot serve both, so only the fill role
      moves. #7160e1 gives white-on-it 4.71.

   3. Nav links resolve to #6e6e76 at 4.01:1 — that is the engine's `var(--text-light, #6e6e76)`
      FALLBACK firing because the token is out of scope on this page.
      ⭐ THE FALLBACK CANNOT BE FIXED GLOBALLY: no single grey clears 4.5 on both a near-white paper
        (#faf6f0) and a near-black surface (#060608). A shared default that must serve opposite
        themes is structurally unable to; the honest fix is to define the token per property, which
        is what the line below does. */
/* ⛔ THE FIX HAD TO COME BACK HERE. It was moved to bagengine-modern.css as "the source", but the
   HOMEPAGE DOES NOT LOAD THAT FILE — measured: `/` and `/sponsored` load only be-digest.css, while
   `/articles` and `/seller-tools/*` load no external CSS at all. With the token out of scope the
   engine's `var(--text-light, #6e6e76)` FALLBACK fired and measured 4.01:1 on rgb(6,6,8).
   ⭐ "Fix it at the source" is only right when every consumer READS that source. Check which file the
     failing page actually loads before deciding where the source is. */
:root { --text-light: #7d7d87; }   /* 4.97 / 4.85 / 4.70 on this property's dark surfaces */

/* ⚠️ `/sponsored` is the ONE light page here (rgb(250,250,249)) and it carries
   `<main class="sponsored-page">`, which is the only reliable hook to scope light-theme values to.
   On it the same tokens must go the other way: dark text, and the raw accent darkened for
   text-on-white (#7c6af7 measures 3.82:1 there). */
/* (the `.sponsored-page` descendant rules that stood here were replaced by the `body:has()` scope
   below, which is the only form that reaches the nav — see that block for why.) */
/* ⚠️ Higher specificity than the plain `.nav-cta` the engine injects at runtime, which otherwise
   wins by source order and restores the 3.99:1 fill. */
/* ⚠️ Higher specificity than the plain `.nav-cta` the engine injects at runtime, which otherwise
   wins by source order and restores the 3.99:1 fill.
   ⭐ AND EVERY OTHER ACCENT-FILLED CONTROL, enumerated rather than discovered one failure at a time.
     Patching `.nav-cta` alone left `.sb-cta` failing on the same page; grepping for every selector
     that fills with var(--accent)/#7c6af7 found ten. Fixing the class beats fixing the instance. */
nav.nav .nav-cta, header .nav-cta, a.nav-cta,
.sb-cta, .am-cta, .hero-cta, .btn-accent, .btn-primary,
.deal-btn, .email-micro-btn, .email-strip-btn, .primary,
/* ⚠️ `.mobile-sidebar a.sb-cta` is specificity (0,2,1) in assets/style.css; a bare `.sb-cta` (0,1,0)
   never beat it. Matched at equal-or-higher specificity rather than reaching for !important, which
   would have hidden the collision instead of resolving it. */
.mobile-sidebar a.sb-cta { background: #7160e1; }

/* ⛔ THOSE RULES SET A FILL AND NOT A COLOUR, AND THAT MADE ONE VARIANT FAR WORSE (2026-08-25).
   The note above reasons about a CTA that is "white on var(--accent)", and for the engine-injected
   button that is true. But on ARTICLE pages `bagengine-modern.css:50` styles `.nav-cta` as an
   OUTLINE button: `color: var(--accent)`, an accent border, no background. Filling that with
   #7160e1 while leaving the text at #7c6af7 produced **purple text on purple, measured live at
   1.18:1** — the primary nav CTA on every bagengine article, effectively unreadable.

   ⭐ THE LESSON: a contrast fix that moves ONE side of a foreground/background pair must be
     re-measured against EVERY variant of that component, not the variant it was written for. This
     went from 3.99 (failing but legible) to 1.18 (invisible) while reading, in the diff, as a
     repair. `instrument-integrity-doctrine` §6 already says trading a false green for a false red
     is not an improvement; this is the same error one level down, in the fix rather than the gate.
   White on #7160e1 measures 4.71, and the border moves with the fill so the outline variant still
   reads as a button. */
nav.nav .nav-cta, header .nav-cta, a.nav-cta,
.sb-cta, .am-cta, .hero-cta, .btn-accent, .btn-primary,
.deal-btn, .email-micro-btn, .email-strip-btn, .primary,
.mobile-sidebar a.sb-cta { color: #fff; }
nav.nav .nav-cta, header .nav-cta, a.nav-cta { border-color: #7160e1; }

/* `--muted` measured 2.56:1 on white and drives the breadcrumbs, section eyebrows and footer note
   across this property. #71717a (zinc-500) gives 4.83 and keeps the palette's hue.

   ⚠️ IT IS NOT A TEXT-ONLY TOKEN, AND THE FIRST VERSION OF THIS COMMENT SAID IT WAS. The `--dim`
   note above prescribes checking for background uses before moving a colour token; that check was
   run AFTER the claim was written, and it found one: `styles.css:265`
   `.tier-header .tier-badge.t3 { background: var(--muted) }`. Recorded rather than quietly deleted,
   because a comment asserting a check it did not perform is worse than no comment.
   The move is still safe, for reasons that are measured rather than assumed: `styles.css` declares
   its OWN `--muted:#a8a29e` at line 16, only ONE article links it, and the affected use is a badge
   FILL whose text is light — so a darker token raises that contrast rather than lowering it. */
   ⛔⛔ AND THE MOVE WAS STILL WRONG, FOR A REASON NEITHER VERSION OF THIS COMMENT CHECKED: it asks
   what the token is USED for (text vs fill) but never what it is used ON. #71717a was tuned against
   WHITE and measured 4.83 there. **This property is DARK on three of its four page types** — `/`,
   `/articles` and `/seller-tools/*` are rgb(6,6,8) / rgb(15,15,20) / rgb(12,10,20); only `/sponsored`
   is light. On the dark home page the same token drives `.mobile-sidebar a` and measured **4.09:1**.
   ⭐ A SHARED DEFAULT CANNOT SERVE OPPOSITE BACKGROUNDS. Tuning one token against one backdrop and
     promoting it to `:root` moves the failure to the other theme instead of removing it. The
     question is not "is this token text or fill", it is "how many DIFFERENT BACKDROPS does it land
     on" — and if the answer is more than one, it needs a scope, not a value. */

/* DARK is the default here (3 of 4 page types). #8884a8 is this property's OWN dark-surface value,
   taken from `styles.css:13` rather than newly invented — 5.56:1 on rgb(10,10,14). */
:root { --muted: #8884a8; }

/* ⚠️ LIGHT-PAGE SCOPE MUST BE ON THE ROOT, NOT ON `<main>`. `/sponsored` marks itself with
   `<main class="sponsored-page">`, but the NAV AND FOOTER ARE SIBLINGS OF `<main>`, so a
   `.sponsored-page …` descendant rule cannot reach them — which is exactly why the first attempt at
   this left `a.nav-brand` failing at 3.82:1 while the body copy went clean. `:has()` lifts the hook
   to an ancestor of the whole document, so the light values reach chrome as well as content. */
/* ⭐ GENERALISED FROM ONE PAGE TO A MARKER. This began as `/sponsored` only, then the 404 page
   turned out to be a SECOND light context — and it was mistaken for a dark "seller-tools page type"
   for most of a day, because `/seller-tools/*` does not exist (the real path is `/tools/*`) so every
   probe of it was silently being served the 404. A page type inferred from a URL that 404s is not a
   page type. `.lattice-light` lets any future light page opt in without another selector here. */
/* ⛔ INDIVIDUAL ARTICLE PAGES ARE LIGHT AND WERE MATCHING NONE OF THESE HOOKS (added 2026-08-26).
   The note above states the property is dark on three of four page types and lists "articles
   rgb(15,15,20)". That is true of the ARTICLES INDEX — `/articles/` measures rgb(15,15,20) live and
   its dark tokens are correct. **It is not true of an individual article**:
   `/articles/best-amazon-fba-tools` measures `body` at **rgb(250,250,249)**, light, and there are
   ~85 of them against one index. They matched no light hook, so every one was rendering
   dark-tuned tokens on a light ground: breadcrumbs and the footer affiliate note measured **2.45:1**.

   ⭐ THE HOOK THAT WAS THERE COULD NOT FIRE. `body[style*="250, 250, 249"]` (line ~273) only matches
     if that colour is an INLINE style ATTRIBUTE on <body>. These pages take their background from a
     stylesheet, so `body.getAttribute('style')` is null and the selector can never match. A
     selector that tests for markup the page does not emit is decoration, the same shape as a gate
     whose predicate the defect never satisfies.
   ⭐ `article.article-wrap` is the honest marker: present on every individual article and on NONE of
     `/`, `/articles/`, `/tools.html` or `/sponsored` (all four checked). It uses the same `:has()`
     lift the note below endorses, so the light values reach nav and footer chrome too. */
body:has(article.article-wrap),
body:has(main.sponsored-page), body.lattice-light {
  --muted: #71717a;        /* 4.83 on rgb(250,250,249) — the value the note above measured */
  --text-light: #6e6e76;
  --accent-ink: #5b47d6;
  /* ⭐ THE SPLIT THE `--accent-ink` NOTE INTRODUCED IS ONLY NEEDED ON DARK PAGES. That note says the
     accent's TEXT role (needs lighter) and FILL role (needs darker) pull in opposite directions, so
     one token cannot serve both — true on a dark backdrop. **On a LIGHT backdrop both roles pull the
     SAME way**: darker text raises contrast against white, and a darker fill raises contrast with the
     white label on it. So here the split collapses and `--accent` can simply move with `--accent-ink`.
     Fixes the page's inline `a { color: var(--accent) }`, which put the network cross-links
     (RxGrab, OmniRx, DeskDeploy…) at 3.82:1. */
  --accent: #5b47d6;       /* 6.33 on rgb(250,250,249) */
}
body:has(main.sponsored-page) a.nav-brand,
body:has(main.sponsored-page) .nav-brand,
body.lattice-light a.nav-brand,
body.lattice-light .nav-brand { color: #5b47d6; }

/* ── ACCENT INK: THE SPLIT THE NOTE ABOVE SAID COULD NOT BE MADE ───────────────────────────────
   The `.nav-cta` note reasons: *"the accent is used 122 times as TEXT and 9 times as a FILL, and
   those need OPPOSITE directions (lighter for text on dark, darker for white-on-fill). A token
   change cannot serve both, so only the fill role moves."* That is correct about ONE token and it
   is the reason the text role was left failing: `#7c6af7` measures 3.99:1 on white, so every small
   accent-coloured label on this property sits below AA.

   ⭐ ONE TOKEN CANNOT SERVE TWO ROLES. TWO TOKENS CAN. The fill role already moved to #7160e1; this
     gives the TEXT role its own value, #5b47d6, measured at 6.33:1 on white. Same hue family, so
     nothing looks off-brand, and the two roles can now be tuned independently forever.
   ⚠️ Applied to the stylesheet-driven small labels only. `.bluf-title` carries `color:#7c6af7` as an
     INLINE style on 70 articles and inline beats any stylesheet without `!important`; that is an
     HTML sweep, deliberately not forced from here. */
:root { --accent-ink: #785eff; }   /* 4.67 on #060608 */
/* ⛔ INVERTED 2026-08-25. --accent-ink was set to #5b47d6, measured 6.33:1 ON WHITE — correct for a
   light property, and this one is DARK on three of its four page types (homepage rgb(6,6,8),
   articles rgb(15,15,20), seller-tools rgb(12,10,20)); only /sponsored is light. On the dark pages
   #5b47d6 measured 3.20:1 and failed on 22 elements.
   ⭐ THE VALUE WAS TUNED AGAINST THE WRONG DEFAULT SURFACE. The default token now serves the majority
     theme and the MINORITY case is the one that gets scoped, which is the way round that leaves the
     fewest pages wrong when someone adds a new one. */
/* `body:has(article.article-wrap)` added for the same reason as above: individual articles are light
   and the inline-style hook beside it cannot fire on a stylesheet-painted background. */
body:has(article.article-wrap),
body[style*="250, 250, 249"], .light, .sponsored-page { --accent-ink: #5b47d6; }
.related-card-tag, .related-card-arrow, .section-eyebrow,
.article-tag, .pill, .faq-item > summary { color: var(--accent-ink); }
/* In-body prose links also render in the raw accent (3.82:1 at 16px). Same split applies: they are
   TEXT, so they take the ink value. Scoped to .article-wrap so nav/footer chrome is untouched. */
.article-wrap p a, .article-wrap li a, .article-wrap td a { color: var(--accent-ink); }

/* ── LIGHT CARDS ON DARK PAGES ─────────────────────────────────────────────────────────────────
   ⛔ `.lattice-capture` PAINTS ITS OWN NEAR-WHITE CARD AND INHERITED THE PAGE'S DARK-THEME TEXT.
   Measured live on /articles/ (body rgb(15,15,20)): the aside's own background is rgb(250,250,250)
   while its text computed to rgb(232,232,240) — **1.08:1, invisible**. Same on the newsletter and
   "Own this category on BagEngine" variants: 1.17:1.
   ⚠️ THESE ARE THE COMMERCIAL SURFACES. The invisible strings are "Own this category on BagEngine",
   "Run a seller tool that belongs here" and "Get our research updates" — the email capture and the
   advertise-with-us ask, unreadable on every dark page type.
   ⭐ THE GENERAL CASE, AND IT IS THE THIRD FORM OF IT ON THIS PROPERTY: a component that brings its
     OWN background cannot inherit colour from the page theme. Page-level light/dark tokens are
     scoped by ANCESTOR, and this element breaks that chain by repainting. It needs its own colour,
     not a better token. `.featured-partner-cta` already does this correctly (dark text, passes) —
     which is why only one of the two was broken. */
.lattice-capture, .lattice-capture strong, .lattice-capture p,
.lattice-capture label, .lattice-capture span { color: #18181b; }

/* ═══ IX GROUND LAYER · BagEngine light articles · 2026-08-27 ═══════════════════════════════════
 * INFORMATION-DESIGN §5. BagEngine's 88 light articles rendered on a flat rgb(250,250,249) with no
 * ground at all, which is the exact condition §5 opens on: a near-white page gives white cards
 * nothing to lift off, and no component set rescues it.
 *
 * ⛔ THE GROUND TONE IS DELIBERATELY NOT CHANGED, AND THAT IS THE WHOLE POINT OF §5.
 *   nesyona uses --ground:#f8f7f4. Porting that here would DARKEN BagEngine's base, and the baseline
 *   measured before this change is: .breadcrumbs 4.63, .breadcrumbs a 4.84, nav links 4.84. That is
 *   0.13 of margin above AA. §5 records the identical move breaking AA on TWELVE elements at once
 *   (nav links, breadcrumbs, meta bar, disclosure summaries) because body copy and chrome sit
 *   directly ON the ground rather than on a card.
 *   ⭐ THE GROUND IS THE WRONG LEVER FOR DEPTH. Depth comes from the implied LIGHT SOURCE, the
 *     washes and the vignette. Those sit behind text; the base tone sits under it.
 *
 * ⛔ NO GRAIN LAYER. One was built, shipped and removed the same day on nesyona: invisible at 3.5%,
 *   television static at any level you can perceive. There is no useful window on a light,
 *   text-dense editorial page, and the depth was never coming from the grain anyway.
 *
 * ⭐ SUBTLETY IS NOT THE SAME AS RESTRAINT. Washes are at .10 / .085 / .055, the strengths that
 *   READ rather than merely measure. A layer you can only find with getComputedStyle is a rounding
 *   error, not a design.
 *
 * ⚠️ The drift carries NO information, which is the only reason it may animate at all under the IX
 *   rule. prefers-reduced-motion stops it completely.
 */
:root{
  --wash-a:rgba(124,106,247,.10);   /* brand violet */
  --wash-b:rgba(91,71,214,.085);    /* deeper violet, the ink token */
  --wash-c:rgba(180,83,9,.055);     /* secondary amber, warmth in the lower field */
}
/* LIGHT + ATMOSPHERE. inset:-12% so the drift never exposes an edge. */
body::before{
  content:"";position:fixed;inset:-12%;z-index:-2;pointer-events:none;
  background:
    radial-gradient(120% 70% at 50% -18%, rgba(255,255,255,.95), rgba(255,255,255,0) 62%),
    radial-gradient(46rem 33rem at 8% -4%,  var(--wash-a), transparent 60%),
    radial-gradient(42rem 30rem at 96% 6%,  var(--wash-b), transparent 58%),
    radial-gradient(50rem 34rem at 74% 92%, var(--wash-c), transparent 62%),
    radial-gradient(38rem 26rem at 18% 88%, var(--wash-b), transparent 60%);
  animation:ix-drift 42s ease-in-out infinite alternate;
}
/* VIGNETTE. Gives the reading column edges to sit inside. Transparent to 46%, so it never reaches
   the centred text column; only the outer field darkens. */
body::after{
  content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
  background:radial-gradient(125% 85% at 50% 2%, transparent 46%, rgba(24,24,27,.075) 100%);
}
@keyframes ix-drift{
  0%{transform:translate3d(0,0,0) scale(1)}
  50%{transform:translate3d(1.4%,-1%,0) scale(1.05)}
  100%{transform:translate3d(-1%,1.2%,0) scale(1.03)}
}
@media(prefers-reduced-motion:reduce){body::before{animation:none}}

/* ⛔ .btn-accent WAS RENDERING VIOLET TEXT ON A VIOLET BUTTON, 1.35:1, ON 13 ARTICLES.
 * The rule that gives it `color:white` lives only in `assets/style.css`, which article pages do not
 * load. With no rule setting a colour, the button INHERITED the article's link colour, which is the
 * same violet as its own background. Measured live: color rgb(91,71,214) on background
 * rgb(113,96,225). A call to action nobody can read.
 * ⭐ Same class as a shared component whose CSS ships in a sheet the page never links: the markup is
 *   right, the rule is right, and they are not in the same room. Found by sweeping every text
 *   element on a page rather than a hand-picked list.
 * ⚠️ Two of the fifteen elements that sweep flagged were probe artifacts, not defects: it cannot
 *   parse `color(srgb ...)` and does not composite alpha. Both were checked before acting.
 */
.btn-accent{background:var(--accent-fill,var(--accent));color:#fff;position:relative;overflow:hidden}
.btn-accent:hover{filter:brightness(1.15);color:#fff}

/* ── NAMED ENTITIES ARE REAL ESTATE (2026-08-27) ───────────────────────────────────────────────
 * Standard: LATTICE-INFORMATION-DESIGN-STANDARD.md §10, LATTICE-CONTENT-OS.md §5b.
 * Every named company in a positioned row carries its REAL mark and a destination that EXISTS.
 * Rendering lives in _scripts/_entity.mjs so both census generators agree.
 * ⚠️ Neutral tile behind the image: many favicons are dark-on-transparent and vanish on a dark cell.
 */
.ent{display:inline-flex;align-items:center;gap:.5rem;min-width:0;vertical-align:-.1em}
/* ⛔ vertical-align:-.1em IS MEASURED, NOT CHOSEN. An inline-flex box takes its baseline from its
   first flex item, which here is the 22px mark, not the name. In prose lists ("A, B and C") that
   put every separating comma 1.59px BELOW the entity name it follows: subtle, and it made a
   deliberately enterprise-grade row look mis-set. Candidates measured live against the comma's own
   text-range box: baseline +1.59, middle -2.41, -.1em 0.00, -.2em -1.59, -.3em -3.20.
   ⭐ A typographic offset is a quantity. Measure the thing you are aligning TO. */
.ent-logo{flex:none;width:22px;height:22px;border-radius:6px;background:#fff;border:1px solid var(--border,#e4e4e7);
  display:inline-flex;align-items:center;justify-content:center;overflow:hidden}
.ent-logo img{width:16px;height:16px;object-fit:contain;display:block}
.ent-mark{flex:none;width:22px;height:22px;border-radius:6px;display:inline-flex;align-items:center;justify-content:center;
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.62rem;font-weight:700;letter-spacing:.02em}
.ent-name{font-weight:600;color:var(--text,#3f3f46);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
a.ent-name{color:var(--accent,#5b47d6);text-decoration:none}
a.ent-name:hover{text-decoration:underline}
/* ⚠️ 42 of the 54 tools have neither a review article nor an approved affiliate programme, so they
   render as PLAIN TEXT. Before this, all 53 unconfigured ones linked to /go/<slug>, which the
   worker answers with 410 Gone. A link to an error is worse than no link. */
.ent-name.no-dest{color:var(--text,#3f3f46)}
.ent-sub{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.7rem;color:var(--text-light,#6e6e76);white-space:nowrap}
td.ent-lead{background:linear-gradient(90deg,rgba(91,71,214,.06),transparent)}
