/* ===========================================================================
   Blastline landing page — /blastline  (+ the Blastline band on /)
   ---------------------------------------------------------------------------
   CONTRACT (identical to agent-experience.css, deliberately):
     * every selector starts `.dv2 .bl-*`
     * NO `:root`, NO redefinition of a design-v2 token
     * the `bl-` prefix is reserved for this page; `br-` is the product-UI
       namespace inside the dashboard and must not be reused or overridden here

   LOADED BY TWO TEMPLATES (changed 2026-08-07). public/blastline.html and
   public/home.html both link this sheet, because the homepage band renders the
   SAME reading instrument as the product page. One definition, placed twice —
   the alternative was a second copy of the component that drifts from this one
   and eventually makes the two pages state different figures. Every selector is
   `.dv2 .bl-*` scoped, so loading it on the homepage cannot leak.

   VISUAL THESIS
   -------------
   Light shell, dark instrument — the same relationship the product UI already
   has (`.dv2 .verdict-instrument` is a dark component embedded in a light page,
   and `.dv2 .scanviz` on the home page does the same). Blastline CONTINUES that
   language rather than inventing a third one. A dark <body> was rejected: the
   shared `.dv2 .topnav` is rgba(255,255,255,.78) + backdrop-filter and would
   render as a white bar over a dark page, forcing a fork of the nav.

   THE ONE RULE THIS PAGE OWNS
   ---------------------------
   AN ABSTENTION IS NOT A SEVERITY. It is drawn OFF the red/amber/green axis
   entirely — see the `--bl-unknown-*` block below for the full argument. The
   previous version of this file drew `unknown` in amber at 14% alpha and put
   `opacity: .22` on un-climbed rungs; that rendered the product's most
   deliberate output as "mild problem, greyed out". Both are fixed here and both
   fixes are load-bearing, not cosmetic.
   =========================================================================== */

.dv2 .bl-page {
  /* Scale borrowed from agent-experience.css — the accent axis of this product
     is red/green (reachable / closed), NOT the blue→violet `--grad`, which
     belongs to MySSL scanning. `--grad` survives on this page in exactly two
     places: the logo and .btn-primary, both inherited. */
  --bl-navy: #0b1020;
  --bl-navy-2: #111936;
  --bl-red: #ef6464;
  --bl-green: #15966f;

  /* `--bl-red` is a FILL colour. On white it measures ~3.0:1, which squeaks
     past AA for large text and fails outright at body size — so text that has
     to be red uses the -ink variant and the fill never carries a label at
     small sizes. Same split design-v2.css already makes with --red / --red-ink. */
  --bl-red-ink: #C43C3C;
  --bl-green-ink: #0B6B50;

  /* dark-surface variants (same values the product panel uses) */
  --bl-red-on-dark: #ff8383;
  --bl-green-on-dark: #61dcb0;
  --bl-ink-on-dark: #eef2ff;
  --bl-muted-on-dark: #a9b4d5;

  /* ── ABSTENTION ──────────────────────────────────────────────────────────
     `unknown` is GRAPHITE, and that is a correctness decision rather than a
     taste one.

     Amber means "warn" everywhere else on this site — a C-grade certificate, an
     expiring cert, a header that could be tightened. A visitor arriving from the
     SSL product has already been trained on that scale. Drawing an abstention in
     amber tells them "mild problem". An abstention is not a mild problem and it
     is not a severity at all: it is an ABSENT ANSWER, produced deliberately, and
     it belongs on a different axis from red/green.

     So: red and green answer "what did it find?". Graphite hatching answers a
     different question — "was this settled at all?" — and it is deliberately the
     HIGHEST-contrast state on the page, not the faintest.

     Four rules, all four load-bearing:
       1. TEXTURE, NOT COLOUR. 45° hatch survives greyscale and every form of
          colour-vision deficiency. A hue alone does not.
       2. RECESSED, NOT RAISED. Settled states sit ON the surface; an abstention
          is cut INTO it (inset shadow). Read pre-attentively, before any label.
       3. FULL CONTRAST, NEVER FADED. `opacity` and `filter` are FORBIDDEN on an
          abstention — see the explicit re-assertion in the rule below. Fading it
          renders it as *disabled*, which is the precise misreading this product
          exists to prevent.
       4. ALWAYS LABELLED `unknown`. Never an icon alone, never a colour alone. */
  --bl-unknown-ink: #232B4A;
  --bl-unknown-line: #4E5880;
  --bl-unknown-field: #E2E6F2;

  /* Stripe weight and period are ABSOLUTE px and never scale down. This is the
     rule that keeps an abstention legible at 14px tall, on a phone, in sunlight
     and in print. 3-in-9 ≈ 33% ink coverage; the old rgba(255,200,105,.14) at a
     2px period was ~2% effective coverage, i.e. invisible. */
  --bl-hatch-w: 3px;
  --bl-hatch-p: 9px;
  --bl-hatch: repeating-linear-gradient(45deg,
              var(--bl-unknown-ink) 0 var(--bl-hatch-w),
              transparent var(--bl-hatch-w) var(--bl-hatch-p));
  --bl-hatch-sm: repeating-linear-gradient(45deg,
              var(--bl-unknown-ink) 0 1.5px, transparent 1.5px 5px);

  /* NOT-PRODUCIBLE — a fourth state, and the page's sharpest piece of honesty.
     Distinct from `unknown`: an abstention means "asked, not settled on this
     host"; not-producible means "this engine cannot compute this rung in this
     release AT ALL, on any host". Drawn as a flat void with a dashed edge and
     no hatch, because there is no evidence here to texture — nothing was asked. */
  --bl-void-ink: #6E7590;
  --bl-void-field: #F1F2F6;

  /* EXCLUDED — a decision, not an absence. A slot deliberately left out of the
     watched surface is neither an answer nor an abstention, so it gets a flat
     neutral and never the hatch. */
  --bl-idle: #D8DCE8;

  /* ── type scale ─────────────────────────────────────────────────────────
     `.dv2` sets font-size:15px on itself but nothing sets `html`, so rem = 16px.
     Body moves 14.5px → 17px. That is the single largest readability change on
     the page, and it is also what forces the cut list: copy you cannot afford at
     17px is copy the page should not have been carrying. */
  /* MEASURED, not guessed. At 1440px the container is 1240px and the hero grid
     leaves the copy column ~576px. "Know what's there." is 18 characters; at
     68px that needs ~610px and wrapped to SIX lines again — the exact defect
     this redesign exists to remove. 56px needs ~500px and holds three lines.
     If you widen the copy column, this can go back up; do not raise one without
     re-measuring the other. */
  --bl-fs-h1:   clamp(2.05rem, 1.35rem + 2.60vw, 3.50rem);  /* 32.8 → 56   */
  --bl-fs-deck: clamp(1.05rem, 0.97rem + 0.33vw, 1.30rem);  /* 16.8 → 20.8 */
  --bl-fs-h2:   clamp(1.70rem, 1.28rem + 1.75vw, 2.55rem);  /* 27.2 → 40.8 */
  --bl-fs-body: 1.0625rem;                                  /* 17px flat   */
  --bl-fs-mono: 0.8125rem;                                  /* 13px flat   */
}

/* `.dv2` has no visually-hidden helper of its own, so this page defines one
   rather than assuming a global that does not exist. */
.dv2 .bl-page .visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.dv2 .bl-page a:focus-visible,
.dv2 .bl-page button:focus-visible {
  outline: 3px solid rgba(59, 107, 255, .28);
  outline-offset: 3px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE STATE SYSTEM — one definition, every state on the page.
   Used by: the hero reading bars, the legend swatches, the axis bars, the
   evidence ladder, and the homepage band. If a state ever needs to look
   different in one of those places, that is a bug in the state, not in the place.
   ═══════════════════════════════════════════════════════════════════════════ */

/* settled — on the surface, flat, no texture */
.dv2 .bl-page [data-state="allow"],
.dv2 .bl-page .is-open {
  background: var(--bl-red); color: #fff;
  border: 1.5px solid transparent;
}
.dv2 .bl-page [data-state="deny"],
.dv2 .bl-page .is-closed {
  background: var(--bl-green); color: #fff;
  border: 1.5px solid transparent;
}

/* abstained — cut into the surface, hatched, and at FULL contrast */
.dv2 .bl-page [data-state="unknown"],
.dv2 .bl-page .is-unknown {
  color: var(--bl-unknown-ink);
  background-color: var(--bl-unknown-field);
  background-image: var(--bl-hatch);
  border: 1.5px solid var(--bl-unknown-line);
  box-shadow: inset 0 2px 6px -1px rgba(18, 24, 51, .34),
              inset 0 0 0 1px rgba(255, 255, 255, .5);
  /* Rule 3 of the abstention contract, written as code so it cannot be lost to
     a later "let's tone that down". An abstention is never faded. */
  opacity: 1;
  filter: none;
}

/* not producible in this release — a void, not an answer and not an abstention */
.dv2 .bl-page [data-state="void"],
.dv2 .bl-page .is-void {
  color: var(--bl-void-ink);
  background: var(--bl-void-field);
  border: 1.5px dashed var(--bl-void-line, #B6BBCB);
  box-shadow: none;
  opacity: 1;
}

/* excluded by a decision — flat neutral, never hatched */
.dv2 .bl-page [data-state="idle"],
.dv2 .bl-page .is-idle {
  background: var(--bl-idle); color: var(--ink-2);
  border: 1.5px solid transparent;
}

/* The abstention label. A SOLID chip sitting ON the hatch — the contrast
   between the two is itself the message: the label is legible, the answer is
   absent. */
.dv2 .bl-page .bl-utag {
  display: inline-flex; align-items: center; gap: 7px; vertical-align: middle;
  padding: 3px 10px 3px 8px; border-radius: 7px;
  background: var(--surface);
  border: 1.5px solid var(--bl-unknown-line);
  color: var(--bl-unknown-ink);
  font: 700 11.5px/1.6 'JetBrains Mono', monospace;
}
.dv2 .bl-page .bl-utag::before {
  content: ""; flex: 0 0 11px; width: 11px; height: 11px; border-radius: 2px;
  border: 1px solid var(--bl-unknown-line);
  background: var(--bl-unknown-field) var(--bl-hatch-sm);
}

/* ── Dark surfaces (hero instrument, homepage band, CTA) ──────────────────
   ⚠ GOTCHA. `--bl-hatch` is substituted at COMPUTED-VALUE time on `.bl-page`.
   Descendants inherit the finished gradient string, so redefining
   `--bl-unknown-ink` alone will NOT flip the stripes — `--bl-hatch` and
   `--bl-hatch-sm` MUST be re-declared in the same block. This has bitten twice;
   do not "simplify" it away. */
.dv2 .bl-page .bl-dark {
  --bl-unknown-ink: #C9D3F2;
  --bl-unknown-line: #7886B6;
  --bl-unknown-field: rgba(9, 13, 31, .78);
  --bl-void-ink: #6B769C;
  --bl-void-field: rgba(255, 255, 255, .035);
  --bl-void-line: rgba(255, 255, 255, .18);
  --bl-idle: rgba(255, 255, 255, .09);
  --bl-hatch: repeating-linear-gradient(45deg,
              var(--bl-unknown-ink) 0 var(--bl-hatch-w),
              transparent var(--bl-hatch-w) var(--bl-hatch-p));
  --bl-hatch-sm: repeating-linear-gradient(45deg,
              var(--bl-unknown-ink) 0 1.5px, transparent 1.5px 5px);
}
.dv2 .bl-page .bl-dark [data-state="unknown"],
.dv2 .bl-page .bl-dark .is-unknown {
  box-shadow: inset 0 2px 7px -1px rgba(0, 0, 0, .6),
              inset 0 0 0 1px rgba(255, 255, 255, .09);
}
.dv2 .bl-page .bl-dark .bl-utag { background: rgba(255, 255, 255, .07); }
.dv2 .bl-page .bl-dark [data-state="idle"],
.dv2 .bl-page .bl-dark .is-idle { color: var(--bl-muted-on-dark); }

/* ── Breadcrumb ────────────────────────────────────────────────────────── */
.dv2 .bl-breadcrumb {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  padding: 18px 0 0; color: var(--muted); font-size: 12px; font-weight: 700;
}
.dv2 .bl-breadcrumb a:hover { color: var(--blue); }
.dv2 .bl-breadcrumb svg { opacity: .6; }

/* ── Hero ──────────────────────────────────────────────────────────────── */
.dv2 .bl-hero {
  position: relative; padding: 34px 0 78px; overflow: hidden;
  background:
    radial-gradient(800px 380px at 82% -30%, rgba(139, 92, 246, .14), transparent 60%),
    radial-gradient(700px 340px at 8% -20%, rgba(59, 107, 255, .14), transparent 60%),
    linear-gradient(180deg, #F4F6FF 0%, var(--bg) 100%);
}
.dv2 .bl-hero-grid {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.04fr);
  gap: clamp(32px, 4.5vw, 64px); align-items: center;
}

/* THE HEADLINE. Three authored blocks, not one balanced paragraph.
   The previous H1 was a single question that computed to SIX ragged lines at
   1440px. Authoring the breaks makes that structurally impossible: a line can
   still wrap inside itself at a narrow width, but the three units can never
   merge or re-shuffle. `-.045em` is `.vi-headline`'s tracking, NOT
   `.hero-headline`'s (-.035em) — the Blastline hero deliberately echoes the
   product's VERDICT heading, because the promise of the page is a verdict. */
.dv2 .bl-h1 {
  margin: 20px 0 20px;
  font-size: var(--bl-fs-h1);
  line-height: .98; letter-spacing: -.045em; font-weight: 800;
  color: var(--ink);
}
.dv2 .bl-h1 > span { display: block; text-wrap: balance; }
.dv2 .bl-h1 > span:last-child { color: var(--bl-red-ink); }

.dv2 .bl-deck {
  max-width: 34em; margin: 0 0 26px;
  color: var(--ink-2); font-size: var(--bl-fs-deck); line-height: 1.62;
}
.dv2 .bl-deck code {
  padding: 1px 5px; border-radius: 6px; color: #3b4772; background: var(--bg-2);
  font: 600 .78em/1.5 'JetBrains Mono', monospace;
}
.dv2 .bl-hero-actions { display: flex; gap: 12px; flex-wrap: wrap; }
.dv2 .bl-hero-notes {
  display: grid; gap: 10px; margin: 26px 0 0; padding: 0; list-style: none;
  max-width: 34em;
}
.dv2 .bl-hero-notes li {
  display: grid; grid-template-columns: 17px 1fr; gap: 10px;
  color: var(--ink-2); font-size: 14px; line-height: 1.55;
}
.dv2 .bl-hero-notes svg { margin-top: 3px; color: var(--green); }

/* ── The instrument (dark) ─────────────────────────────────────────────── */
/* Background recipe copied from `.dv2 .verdict-instrument` so the marketing page
   and the product screen are visibly the same object. */
.dv2 .bl-instrument {
  position: relative; isolation: isolate; overflow: hidden;
  padding: clamp(20px, 3vw, 28px);
  border: 1px solid rgba(126, 144, 213, .18);
  border-radius: 26px;
  color: var(--bl-ink-on-dark);
  background:
    radial-gradient(620px 300px at 92% -5%, rgba(105, 91, 255, .26), transparent 64%),
    radial-gradient(440px 260px at 8% 110%, rgba(24, 178, 224, .15), transparent 70%),
    linear-gradient(145deg, var(--bl-navy-2), var(--bl-navy));
  box-shadow: var(--sh-pop);
}
.dv2 .bl-instrument:before {
  content: ""; position: absolute; z-index: -1; inset: 0; opacity: .16;
  background-image:
    linear-gradient(rgba(255, 255, 255, .08) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, .08) 1px, transparent 1px);
  background-size: 38px 38px;
  mask-image: linear-gradient(to right, transparent, #000 45%, #000);
  -webkit-mask-image: linear-gradient(to right, transparent, #000 45%, #000);
}
.dv2 .bl-inst-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 18px;
}
.dv2 .bl-inst-kicker {
  color: #8f9abb; font-size: 9.5px; font-weight: 800;
  letter-spacing: .12em; text-transform: uppercase;
}
.dv2 .bl-inst-subject {
  overflow-wrap: anywhere;
  color: #c7d2f5; font: 600 11.5px/1.5 'JetBrains Mono', monospace;
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE READING — the motif that makes four axes one product.
   Every axis produces one reading, and every reading is drawn on the same
   strip: a numeral, a question, a proportional bar of what was settled, and —
   always, at full contrast — the share that was not. The hero shows the whole
   instrument (four readings stacked); each body section shows ONE ROW OF IT,
   enlarged. The body sections do not "echo" the hero; they are literally a
   piece of it pulled out and magnified. That is what makes four things one.

   Every proportion below is rendered from static HTML (inline flex-basis
   computed from a real count). There is no JS in this component at all, which
   is how the LCP constraint stops being a constraint.
   ═══════════════════════════════════════════════════════════════════════════ */
.dv2 .bl-readings { display: grid; gap: 2px; margin: 0; padding: 0; list-style: none; }
/* TWO ROWS, not four columns. The first attempt put numeral / question / bar /
   counts on one line; inside a ~600px instrument that left the question ~178px
   and every one of them wrapped to four lines. Question and counts now share
   the top row and the bar spans beneath them, which keeps each question on one
   line and gives the bar its full width — the bar is the thing being read. */
.dv2 .bl-reading {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) auto;
  column-gap: 12px; row-gap: 9px; align-items: center;
  padding: 13px 4px; border-top: 1px solid rgba(255, 255, 255, .08);
}
.dv2 .bl-reading:first-child { border-top: 0; }
/* Explicit placement, not auto. The bar spans cols 2..3, so auto-placement
   would push the counts onto a third row instead of parking them beside the
   question. DOM order stays numeral → question → bar → counts, which is also
   the reading order for assistive tech. */
.dv2 .bl-reading .bl-ax { grid-row: 1; grid-column: 1; }
.dv2 .bl-reading-q { grid-row: 1; grid-column: 2; }
.dv2 .bl-reading-n { grid-row: 1; grid-column: 3; }
.dv2 .bl-reading .bl-bar { grid-row: 2; grid-column: 2 / -1; }
.dv2 .bl-ax {
  font: 700 12px/1 'JetBrains Mono', monospace;
  color: #7f8bb4; letter-spacing: .06em;
}
.dv2 .bl-reading-q { color: #cfd8f7; font-size: 13.5px; line-height: 1.45; }
.dv2 .bl-reading-n {
  color: #9aa5c9; font: 500 11.5px/1.5 'JetBrains Mono', monospace; text-align: right;
}
.dv2 .bl-reading-n b { color: #eef2ff; font-weight: 700; }
.dv2 .bl-reading-n code { color: inherit; font: inherit; }

.dv2 .bl-bar {
  display: flex; height: 22px; border-radius: 7px; overflow: hidden;
  background: rgba(255, 255, 255, .05);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .08);
}
/* Segments inherit the state system wholesale. `min-width` keeps a 1-count
   segment from vanishing entirely; `border-radius: 0` because the segment edges
   inside a bar are joins, not ends. */
.dv2 .bl-bar .bl-seg { min-width: 5px; border-radius: 0; border: 0; box-shadow: none; }
.dv2 .bl-bar .bl-seg.is-unknown {
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .14);
}
.dv2 .bl-inst-foot {
  margin: 16px 2px 0; color: var(--bl-muted-on-dark);
  font-size: 12.5px; line-height: 1.6;
}
.dv2 .bl-inst-foot strong { color: #eef2ff; }

/* ── shared section furniture ──────────────────────────────────────────── */
.dv2 .bl-sec { padding: 78px 0; }
.dv2 .bl-sec.alt { background: var(--bg-2); }
.dv2 .bl-sec-head { max-width: 780px; margin-bottom: 34px; }
.dv2 .bl-sec-head h2 {
  margin: 14px 0 10px; font-size: var(--bl-fs-h2);
  line-height: 1.08; letter-spacing: -.035em; font-weight: 800; text-wrap: balance;
}
/* Body copy moves off `--muted`. `--muted` (#6B7090) on `--bg-2` (#EEF1FA) is
   ~4.3:1 — borderline AA, and it is the direct cause of the "small, grey, hard
   to read, and there is a LOT of it" complaint. `--muted` is now reserved for
   labels, kickers and captions; anything that is a SENTENCE uses --ink-2. */
.dv2 .bl-sec-head p { margin: 0; color: var(--ink-2); font-size: var(--bl-fs-deck); line-height: 1.62; }

.dv2 .bl-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
.dv2 .bl-grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; }

.dv2 .bl-card {
  padding: 24px; border: 1px solid var(--line); border-radius: 20px;
  background: var(--surface); box-shadow: var(--sh-2);
}
.dv2 .bl-card h3 { margin: 0 0 8px; font-size: 17px; font-weight: 800; letter-spacing: -.015em; }
.dv2 .bl-card p { margin: 0 0 10px; color: var(--ink-2); font-size: 15px; line-height: 1.62; }
.dv2 .bl-card p:last-child { margin-bottom: 0; }
.dv2 .bl-card code,
.dv2 .bl-prose code {
  overflow-wrap: anywhere; padding: 1px 5px; border-radius: 6px;
  color: #3b4772; background: var(--bg-2);
  font: 600 12.5px/1.5 'JetBrains Mono', monospace;
}
.dv2 .bl-card .bl-kicker {
  display: block; margin-bottom: 9px; color: var(--muted);
  font-size: 9.5px; font-weight: 800; letter-spacing: .11em; text-transform: uppercase;
}
.dv2 .bl-card.tone-danger { border-left: 4px solid var(--bl-red); }
.dv2 .bl-card.tone-good { border-left: 4px solid var(--bl-green); }

/* A sentence the page quotes in order to REJECT it. Styled as a quotation so a
   reader skimming cannot mistake it for our own claim, and marked with this
   class so the claim gate in tests/test_blastline_page.py can exclude it from
   the banned-phrase scan. See the honesty ledger in public/blastline.html. */
.dv2 .bl-not-our-claim {
  color: var(--muted); text-decoration: line-through;
  text-decoration-color: rgba(218, 79, 79, .55); text-decoration-thickness: 1px;
}
.dv2 .bl-limit .bl-not-our-claim { color: #7d705a; }

.dv2 .bl-prose { max-width: 780px; }
.dv2 .bl-prose p { margin: 0 0 15px; color: var(--ink-2); font-size: var(--bl-fs-body); line-height: 1.7; }
.dv2 .bl-prose p:last-child { margin-bottom: 0; }
.dv2 .bl-prose strong { color: var(--ink); }

/* ── the legend (§2) — teaches the state system before the page uses it ──── */
.dv2 .bl-swatches { display: grid; gap: 12px; margin: 30px 0 0; padding: 0; list-style: none; }
.dv2 .bl-swatches li {
  display: grid; grid-template-columns: 58px 172px minmax(0, 1fr);
  gap: 18px; align-items: center;
  padding: 16px 18px; border-radius: 16px;
  background: var(--surface); border: 1px solid var(--line);
}
.dv2 .bl-sw { height: 34px; border-radius: 9px; }
.dv2 .bl-swatches .bl-sw-label { font: 700 12.5px/1.5 'JetBrains Mono', monospace; color: var(--ink); }
.dv2 .bl-swatches .bl-sw-copy { color: var(--ink-2); font-size: 15px; line-height: 1.6; }
.dv2 .bl-swatches .bl-sw-copy strong { color: var(--ink); }

/* ── the four axes (§4) ──────────────────────────────────────────────────
   The continuous left gutter is the second binder: one uninterrupted spine
   threads all four axis sections, so "core" and "frame" sections are legible at
   a glance and the eye finally has an anchor across 3000px. */
.dv2 .bl-axis { padding: 46px 0; }
.dv2 .bl-axis-grid { display: grid; grid-template-columns: 74px minmax(0, 1fr); }
.dv2 .bl-axis-mark { position: relative; }
.dv2 .bl-axis-mark::before {
  content: ""; position: absolute; left: 13px; top: -46px; bottom: -46px;
  width: 1px; background: var(--line);
}
.dv2 .bl-axis:first-of-type .bl-axis-mark::before { top: 14px; }
.dv2 .bl-axis:last-of-type .bl-axis-mark::before { bottom: calc(100% - 14px); }
.dv2 .bl-axis-mark .bl-ax {
  position: relative; display: grid; place-items: center;
  width: 27px; height: 27px; border-radius: 50%;
  background: var(--surface); border: 1px solid var(--line);
  color: var(--ink); font: 700 11px/1 'JetBrains Mono', monospace;
}
.dv2 .bl-axis-body h2 {
  margin: 0 0 12px; font-size: clamp(1.4rem, .95rem + 1.5vw, 2rem);
  line-height: 1.14; letter-spacing: -.03em; font-weight: 800;
}
.dv2 .bl-axis-q { margin: 0 0 18px; }
.dv2 .bl-axis-q code {
  display: inline-block; padding: 5px 10px; border-radius: 8px;
  background: var(--bg-2); color: var(--ink-2);
  font: 600 12.5px/1.6 'JetBrains Mono', monospace;
}
.dv2 .bl-axis-body > p { color: var(--ink-2); font-size: var(--bl-fs-body); line-height: 1.68; margin: 0 0 14px; }
.dv2 .bl-axis-body > p code,
.dv2 .bl-axis-not code {
  padding: 1px 5px; border-radius: 6px; color: #3b4772; background: var(--bg-2);
  font: 600 12.5px/1.5 'JetBrains Mono', monospace; overflow-wrap: anywhere;
}
.dv2 .bl-bar-lg { height: 46px; border-radius: 12px; margin: 24px 0 10px; }
.dv2 .bl-bar-lg .bl-seg {
  display: grid; place-items: center;
  font: 700 14px/1 'JetBrains Mono', monospace;
}
/* A label sitting ON the hatch has to bring its own ground. The abstention ink
   and the stripes are the same colour by construction, so `route_coverage:
   partial` rendered dark-on-dark and was unreadable. Same solution as
   `.bl-utag`: a solid chip on the texture, where the contrast between the two
   IS the message — the label is legible, the answer is absent. */
.dv2 .bl-bar-lg .bl-seg.is-unknown b,
.dv2 .bl-bar-lg .bl-seg.is-void b {
  padding: 5px 11px; border-radius: 7px;
  background: var(--surface); border: 1.5px solid var(--bl-unknown-line);
  color: var(--bl-unknown-ink); font-size: 12.5px; white-space: nowrap;
}
.dv2 .bl-bar-key { color: var(--ink-2); font-size: 14.5px; line-height: 1.6; margin: 0; }
.dv2 .bl-bar-key code { font-size: 12.5px; }

/* Every axis ends with the same admission, in the same box, in the same place.
   Four axes, four identical shapes — nothing else in the category does this and
   it costs six lines of CSS. */
.dv2 .bl-axis-not {
  margin-top: 22px; padding: 16px 18px; border-radius: 14px;
  border-left: 3px solid var(--bl-unknown-line);
  background: var(--surface-2); color: var(--ink-2);
  font-size: 14.5px; line-height: 1.65;
}
.dv2 .bl-axis-not strong { color: var(--ink); }

/* ── the evidence ladder — now STATIC and honest ─────────────────────────
   Rebuilt 2026-08-07. The previous ladder was driven by a rotating carousel
   that painted every rung below the stop as `reached`. With the all-legs case
   stopping at rung 5, that marked `suspicious_change` (3) and
   `externally_reachable` (4) as climbed — two rungs the engine states it
   CANNOT PRODUCE in this release (agent/src/myssl_agent/access/wri.py:52-61,
   "ULAŞILABİLİRLİK BİR PREFİX DEĞİLDİR"; reachable rungs are a SET, not a
   prefix). The page was animating engine states that do not exist.

   The fix is not to hide those rungs. It is to draw them in the fourth state —
   `void`, "no evidence source in this release" — which turns the defect into
   the clearest available demonstration of what the product is for. */
.dv2 .bl-ladder {
  display: grid; gap: 4px; margin: 22px 0 0; padding: 0; list-style: none;
}
.dv2 .bl-rung {
  position: relative;
  display: grid; grid-template-columns: 26px minmax(0, 190px) minmax(0, 1fr);
  gap: 14px; align-items: center;
  padding: 10px 12px 10px 26px; border-radius: 10px;
}
/* ⚠ THE ROW NEVER TAKES THE STATE FILL ACROSS ITS FULL WIDTH.
   Measured: it did, and the hatch ran underneath the labels — which made the
   ABSTAINED rungs the only unreadable ones on the ladder, exactly inverting the
   hierarchy this page is built on. The row stays flat and the state moves to a
   texture band on the left edge, so the texture is still pre-attentive and the
   text is still text. Override must come AFTER the global state system. */
.dv2 .bl-page .bl-rung[data-state] {
  background-color: rgba(255, 255, 255, .045);
  background-image: none;
  border: 1px solid rgba(255, 255, 255, .07);
  box-shadow: none;
  color: var(--bl-ink-on-dark);
}
.dv2 .bl-page .bl-rung[data-state]::before {
  content: ""; position: absolute; left: 9px; top: 9px; bottom: 9px;
  width: 7px; border-radius: 4px;
}
.dv2 .bl-page .bl-rung[data-state="deny"]::before { background: var(--bl-green-on-dark); }
.dv2 .bl-page .bl-rung[data-state="allow"]::before { background: var(--bl-red-on-dark); }
.dv2 .bl-page .bl-rung[data-state="unknown"]::before {
  background-color: var(--bl-unknown-field);
  background-image: var(--bl-hatch-sm);
  box-shadow: inset 0 0 0 1px var(--bl-unknown-line);
}
.dv2 .bl-page .bl-rung[data-state="void"]::before {
  background: transparent; box-shadow: inset 0 0 0 1px var(--bl-void-line);
}
.dv2 .bl-rung-n {
  display: grid; place-items: center; width: 22px; height: 22px; border-radius: 6px;
  background: rgba(255, 255, 255, .06); color: #9aa5c9;
  font: 700 10px/1 'JetBrains Mono', monospace;
}
.dv2 .bl-rung code { overflow-wrap: anywhere; font: 600 12px/1.4 'JetBrains Mono', monospace; color: #cdd7f7; }
.dv2 .bl-rung .bl-rung-say { font-size: 13px; line-height: 1.45; color: #aab5d6; }
.dv2 .bl-rung[data-state="deny"] code,
.dv2 .bl-rung[data-state="allow"] code { color: #fff; }
/* The void rungs are struck through and dimmed — the ONLY place on this page
   where dimming is correct, because nothing was measured here to report. An
   abstention is never dimmed; see the abstention contract at the top. */
.dv2 .bl-rung[data-state="void"] code {
  color: var(--bl-void-ink); text-decoration: line-through; text-decoration-thickness: 1px;
}
.dv2 .bl-rung[data-state="void"] .bl-rung-say { color: #78829f; }
.dv2 .bl-rung-flag {
  display: inline-block; margin-left: 8px; padding: 2px 7px; border-radius: 5px;
  border: 1px solid currentColor; font: 700 9.5px/1.5 'JetBrains Mono', monospace;
  letter-spacing: .04em; text-transform: uppercase; vertical-align: middle;
  text-decoration: none;
}

/* ── measurement table (evidence) ───────────────────────────────────────── */
.dv2 .bl-measure {
  overflow: hidden; border: 1px solid var(--line); border-radius: 20px;
  background: var(--surface); box-shadow: var(--sh-2);
}
.dv2 .bl-measure-row {
  display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.9fr);
  gap: 18px; padding: 18px 22px;
}
.dv2 .bl-measure-row + .bl-measure-row { border-top: 1px solid var(--line-2); }
.dv2 .bl-measure-row .k { color: var(--ink); font-size: 13.5px; font-weight: 800; }
.dv2 .bl-measure-row .k small { display: block; margin-top: 3px; color: var(--muted); font-size: 11px; font-weight: 600; }
.dv2 .bl-measure-row .v { color: var(--ink-2); font-size: 14.5px; line-height: 1.6; }
.dv2 .bl-measure-row .v em {
  font-style: normal; color: var(--ink);
  font: 700 15px/1.4 'JetBrains Mono', monospace;
}

/* ── the limits section — deliberately NOT collapsible ─────────────────── */
.dv2 .bl-limits { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.dv2 .bl-limit {
  display: grid; grid-template-columns: 28px minmax(0, 1fr); gap: 14px;
  padding: 19px 21px;
  border: 1px solid #e3c98f; border-left-width: 4px; border-radius: 15px;
  background: #fffaef;
}
.dv2 .bl-limit-no {
  display: grid; place-items: center; width: 26px; height: 26px;
  border-radius: 8px; color: #7a5500; background: #f4e3bf;
  font: 700 11px/1 'JetBrains Mono', monospace;
}
.dv2 .bl-limit h3 { margin: 2px 0 6px; color: #6b4a00; font-size: 15px; font-weight: 800; }
.dv2 .bl-limit p { margin: 0; color: #4c4335; font-size: 14px; line-height: 1.65; }
.dv2 .bl-limit p + p { margin-top: 8px; }
.dv2 .bl-limit code {
  overflow-wrap: anywhere; padding: 1px 5px; border-radius: 6px;
  color: #5b4409; background: #f6ecd6;
  font: 600 12.5px/1.5 'JetBrains Mono', monospace;
}

/* ── privacy ledger ────────────────────────────────────────────────────── */
.dv2 .bl-ledger { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
.dv2 .bl-ledger-col {
  padding: 22px 24px; border: 1px solid var(--line); border-radius: 20px;
  background: var(--surface); box-shadow: var(--sh-2);
}
/* The "leaves" column is no longer amber — amber is a severity and this is not
   a warning, it is a fact about a boundary. It takes the abstention ink, which
   on this page means "read this carefully", not "something is wrong". */
.dv2 .bl-ledger-col.leaves { border-top: 4px solid var(--bl-unknown-line); }
.dv2 .bl-ledger-col.stays { border-top: 4px solid var(--bl-green); }
.dv2 .bl-ledger-col h3 { margin: 0 0 4px; font-size: 16px; font-weight: 800; }
.dv2 .bl-ledger-col > p { margin: 0 0 14px; color: var(--muted); font-size: 13px; }
.dv2 .bl-ledger-col ul { margin: 0; padding: 0; list-style: none; display: grid; gap: 10px; }
.dv2 .bl-ledger-col li {
  display: grid; grid-template-columns: 18px minmax(0, 1fr); gap: 10px;
  color: var(--ink-2); font-size: 14.5px; line-height: 1.55;
}
.dv2 .bl-ledger-col li svg { margin-top: 3px; }
.dv2 .bl-ledger-col.leaves li svg { color: var(--bl-unknown-line); }
.dv2 .bl-ledger-col.stays li svg { color: var(--bl-green); }
.dv2 .bl-ledger-cost {
  grid-column: 1 / -1; padding: 18px 21px;
  border: 1px solid var(--line); border-radius: 16px;
  background: var(--surface-2); color: var(--ink-2);
  font-size: 14.5px; line-height: 1.68;
}
.dv2 .bl-ledger-cost strong { color: var(--ink); }

/* ── install steps ─────────────────────────────────────────────────────── */
.dv2 .bl-steps { display: grid; gap: 14px; }
.dv2 .bl-step {
  display: grid; grid-template-columns: 34px minmax(0, 1fr); gap: 16px;
  padding: 20px 22px; border: 1px solid var(--line); border-radius: 18px;
  background: var(--surface); box-shadow: var(--sh-2);
}
.dv2 .bl-step-n {
  display: grid; place-items: center; width: 32px; height: 32px;
  border-radius: 10px; color: var(--blue-ink); background: var(--blue-soft);
  font: 700 13px/1 'JetBrains Mono', monospace;
}
.dv2 .bl-step h3 { margin: 4px 0 6px; font-size: 16px; font-weight: 800; }
.dv2 .bl-step p { margin: 0; color: var(--ink-2); font-size: 14.5px; line-height: 1.6; }
.dv2 .bl-step .code-block { margin: 12px 0 0; }

/* ── CTA ───────────────────────────────────────────────────────────────── */
.dv2 .bl-cta {
  position: relative; overflow: hidden;
  padding: clamp(34px, 5vw, 54px);
  border: 1px solid rgba(126, 144, 213, .18); border-radius: 26px;
  color: var(--bl-ink-on-dark);
  background:
    radial-gradient(620px 300px at 92% -5%, rgba(105, 91, 255, .26), transparent 64%),
    linear-gradient(145deg, var(--bl-navy-2), var(--bl-navy));
  box-shadow: var(--sh-pop);
}
.dv2 .bl-cta h2 {
  max-width: 720px; margin: 0 0 12px; color: #fff;
  font-size: clamp(26px, 3.4vw, 38px); line-height: 1.1; letter-spacing: -.04em;
}
.dv2 .bl-cta p { max-width: 640px; margin: 0 0 24px; color: var(--bl-muted-on-dark); font-size: 15.5px; line-height: 1.65; }
.dv2 .bl-cta-actions { display: flex; gap: 11px; flex-wrap: wrap; }
.dv2 .bl-cta .btn-primary { color: #10162d; background: #fff; border-color: #fff; box-shadow: 0 15px 30px -16px rgba(0, 0, 0, .72); }
.dv2 .bl-cta .bl-ghost { color: #d8def4; border: 1px solid rgba(255, 255, 255, .16); background: rgba(255, 255, 255, .06); }
.dv2 .bl-cta .bl-ghost:hover { color: #fff; background: rgba(255, 255, 255, .11); }
.dv2 .bl-cta-foot { margin: 22px 0 0; color: #8995b8; font-size: 12.5px; line-height: 1.6; }

/* ═══════════════════════════════════════════════════════════════════════════
   THE HOMEPAGE BAND — /  (public/home.html)
   One dark interrupt in a 3000px light field. The homepage is a continuous
   light page below its hero, so a single dark band is worth more than any
   headline size — and it declares "different product, different place" before
   the copy has to. Reuses .bl-instrument + .bl-readings verbatim: the visitor
   who clicks through lands on visual continuity rather than a new page.
   ═══════════════════════════════════════════════════════════════════════════ */
.dv2 .bl-home-band {
  padding: clamp(56px, 7vw, 96px) 0;
  background:
    radial-gradient(760px 380px at 88% -10%, rgba(105, 91, 255, .30), transparent 64%),
    radial-gradient(560px 300px at 6% 108%, rgba(24, 178, 224, .16), transparent 70%),
    linear-gradient(150deg, var(--bl-navy-2), var(--bl-navy));
  color: var(--bl-ink-on-dark);
}
.dv2 .bl-home-band .pill {
  background: rgba(255, 255, 255, .08); border-color: rgba(255, 255, 255, .16); color: #dfe6fb;
}
.dv2 .bl-home-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(32px, 5vw, 66px); align-items: center;
}
.dv2 .bl-home-h {
  margin: 18px 0 16px; font-size: clamp(1.95rem, 1.15rem + 3.1vw, 3.35rem);
  line-height: 1.02; letter-spacing: -.042em; font-weight: 800; color: #fff;
}
.dv2 .bl-home-h > span { display: block; text-wrap: balance; }
.dv2 .bl-home-h > span:last-child { color: #FF9B9B; }
.dv2 .bl-home-band .bl-deck { color: var(--bl-muted-on-dark); }
.dv2 .bl-home-band .bl-deck code { background: rgba(255, 255, 255, .09); color: #e6ecff; }
/* The band is already dark, so the instrument inside it drops its own drop
   shadow and sits as a panel rather than a floating card. */
.dv2 .bl-home-inst {
  box-shadow: none; border-color: rgba(255, 255, 255, .10);
  background: rgba(255, 255, 255, .035);
}
.dv2 .bl-home-inst:before { display: none; }
.dv2 .bl-home-actions { display: flex; gap: 11px; flex-wrap: wrap; margin: 24px 0 0; }
.dv2 .bl-home-band .btn-primary {
  background: #fff; color: #10162d; border-color: #fff;
  box-shadow: 0 15px 30px -16px rgba(0, 0, 0, .72);
}
.dv2 .bl-home-band .bl-ghost {
  color: #d8def4; border: 1px solid rgba(255, 255, 255, .16); background: rgba(255, 255, 255, .06);
}
.dv2 .bl-home-band .bl-ghost:hover { color: #fff; background: rgba(255, 255, 255, .11); }
.dv2 .bl-home-foot { margin: 22px 0 0; color: #8995b8; font-size: 12.5px; line-height: 1.65; }
.dv2 .bl-home-foot code { color: #b8c2e0; font: 600 11.5px/1.5 'JetBrains Mono', monospace; }

/* ═══════════════════════════════════════════════════════════════════════════
   THE SECOND DOOR — / hero, above the fold  (public/home.html)

   WHY IT EXISTS. The band above is 2,000px down the page: a visitor who scans
   a domain and leaves never learns the second product exists. This is the
   teaser that does, and it is the only Blastline surface a first-time visitor
   is guaranteed to see.

   WHY IT IS *HERE*, MEASURED. `.hero-grid` is `align-items: center` and the
   right column held one 546px card inside a 794px row — i.e. 124px of dead
   space above the terminal and 124px below it. `.bl-door-col` re-anchors that
   column to `start`, so the door lands in space that was already empty and the
   `.scanviz` terminal below it moves down by ~65px instead of being displaced
   off the fold. THE LEFT COLUMN IS NOT TOUCHED: the h1, the sub and the scan
   card — the primary conversion path — keep their exact pixel positions. If you
   grow this card, re-measure; every px above ~230 starts eating the terminal.

   WHY IT IS LIGHT, on a page whose Blastline band is dark. `.scanviz` directly
   below it is already a dark navy card. A second dark card in the same column
   fuses with it and reads as one component — the precise misreading this
   element exists to prevent. Light shell / hatched spine keeps them two
   objects, and the hatch is the one texture on this page that appears nowhere
   in the SSL product.

   IT IS A TEASER, NOT A REPORT. Every figure is quoted verbatim from the band
   and from /blastline (axis IV: 3 deny / 12 unknown), and the card says
   `illustration` on its face so the strip cannot be read as a live measurement
   of the visitor's own server. Do not put a number here that is not already on
   /blastline under the honesty ledger.
   ═══════════════════════════════════════════════════════════════════════════ */
.dv2 .bl-door-col { align-self: start; display: grid; gap: 13px; }

.dv2 .bl-door {
  position: relative;
  display: grid;
  gap: 8px;
  padding: 13px 17px 12px;
  border-radius: 18px;
  overflow: hidden;
  text-decoration: none;
  color: var(--ink);
  background: linear-gradient(180deg, #FFFFFF 0%, #F8F9FE 100%);
  border: 1px solid #DADEED;
  box-shadow: 0 1px 0 rgba(15, 17, 26, .04), 0 14px 34px -18px rgba(28, 32, 75, .34);
}
/* The identity tab — deliberately the SAME geometry as `.dv2 .scancard::before`
   (design-v2.css), the tab on the scan form directly to the left of this card.
   Same family, one difference: the scan form's tab is the solid blue→violet
   `--grad` and this one is hatched. Read side by side that is the whole pitch —
   same house, different instrument.

   A full-height spine down the left edge was tried first and rejected on the
   render: an 18px corner radius tapers a 5px strip at both ends, and the result
   read as a detached stripe floating beside the card rather than part of it. */
.dv2 .bl-door::before {
  content: "";
  position: absolute; top: -1px; left: 21px;
  width: 92px; height: 4px;
  border-radius: 0 0 999px 999px;
  background-color: var(--bl-unknown-line);
  background-image: var(--bl-hatch);
}
.dv2 .bl-door:hover { border-color: #C3CAE4; }
.dv2 .bl-door:focus-visible {
  outline: 3px solid rgba(59, 107, 255, .32);
  outline-offset: 3px;
}

.dv2 .bl-door-top { display: flex; align-items: center; gap: 8px; min-width: 0; }
.dv2 .bl-door-new {
  flex: 0 0 auto;
  padding: 2px 8px; border-radius: 999px;
  background: var(--bl-navy); color: #fff;
  font: 800 9.5px/1.7 'Plus Jakarta Sans', sans-serif;
  letter-spacing: .1em; text-transform: uppercase;
}
.dv2 .bl-door-kicker {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--muted);
  font: 700 10.5px/1.7 'Plus Jakarta Sans', sans-serif;
  letter-spacing: .1em; text-transform: uppercase;
}

.dv2 .bl-door-h { display: grid; gap: 1px; }
.dv2 .bl-door-h > span { display: block; text-wrap: balance; }
.dv2 .bl-door-q {
  color: var(--muted); font-size: 15.5px; font-weight: 600; letter-spacing: -.012em;
}
/* Stays `--ink`. `--bl-red-ink` was tried here to echo the band's
   `.bl-home-h > span:last-child` (#FF9B9B on its dark ground) and rejected on
   the render: on a white card, sitting 40px above a green bar, red stops being
   a brand accent and starts reading as an alert. The card differentiates itself
   from the blue→violet hero beside it through the hatch and the navy chip, not
   through a hue on the headline. */
.dv2 .bl-door-a {
  color: var(--ink); font-size: 17.5px; font-weight: 800; letter-spacing: -.024em;
}

/* The strip. `.bl-bar` is authored for the dark instrument, so the two rules it
   carries for a dark ground are re-stated here for a light one — the STATE
   fills themselves are untouched and still come from the one state system. */
.dv2 .bl-door-inst { display: grid; gap: 5px; }
.dv2 .bl-door-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
}
.dv2 .bl-door-lab {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ink-2); font-size: 12.5px; font-weight: 600;
}
.dv2 .bl-door-n {
  flex: 0 0 auto; color: var(--muted);
  font: 600 11.5px/1.5 'JetBrains Mono', monospace;
}
.dv2 .bl-door-n b { color: var(--ink); font-weight: 700; }

.dv2 .bl-door-bar {
  position: relative;
  height: 16px; border-radius: 5px;
  background: #EDEFF7;
  box-shadow: inset 0 0 0 1px rgba(28, 32, 75, .08);
}
.dv2 .bl-door-bar .bl-seg.is-unknown {
  box-shadow: inset 0 2px 5px -1px rgba(18, 24, 51, .30),
              inset 0 0 0 1px rgba(255, 255, 255, .45);
}
/* The pass. Positioned, therefore NOT a flex item of `.bl-bar`; clipped by the
   bar's own `overflow: hidden`. Invisible at rest, so with motion off there is
   nothing to freeze. */
.dv2 .bl-door-bar::after {
  content: "";
  position: absolute; top: 0; bottom: 0; left: 0; width: 58px;
  opacity: 0;
  /* Peak alpha is capped so the hatch stays legible THROUGH the glint. Rule 3
     of the abstention contract forbids `opacity` and `filter` on an abstention;
     an overlay that briefly obscured one would be the same defect by another
     route. Measured at .78: the stripes read at every frame of the pass. */
  background: linear-gradient(90deg,
              rgba(255, 255, 255, 0) 0%,
              rgba(255, 255, 255, .78) 50%,
              rgba(255, 255, 255, 0) 100%);
  pointer-events: none;
}

.dv2 .bl-door-foot {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
}
.dv2 .bl-door-meta { color: var(--muted-2); font-size: 11.5px; font-weight: 600; }
.dv2 .bl-door-go {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--blue-ink); font-size: 13px; font-weight: 800; letter-spacing: -.008em;
}
.dv2 .bl-door-go svg { flex: 0 0 auto; }

/* ═══ MOTION ════════════════════════════════════════════════════════════
   Base state carries NO animation and NO opacity:0 — with JS off or an
   IntersectionObserver that never fires, the page must still be complete.
   Motion is additive, inside `prefers-reduced-motion: no-preference` only.

   THE HATCH NO LONGER MOVES. The previous file drifted the hatch on a 3s loop.
   That was the one piece of pure decoration on the page and it was on the wrong
   element: a MOVING texture implies activity on the one thing whose meaning is
   "nothing was observed here". Cut on principle, not for performance.

   THAT RULE ALSO GOVERNS `.bl-door` BELOW, and is the reason its animation is
   shaped the way it is. The hero card must catch an eye, so it has motion the
   band does not — but the moving thing is a PASS crossing the whole strip, not
   the hatch, and it crosses the settled segment and the abstained one at the
   same speed and the same brightness. It is also 88% idle: 0.95s of travel in a
   7.4s cycle, because the product collects HOURLY and a continuously shimmering
   strip would imply a live feed the agent does not provide. A faster loop here
   would be a claim, not a style choice.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: no-preference) {
  /* THE ARRIVAL. `backwards`, not `both`, and that is load-bearing: `both`
     pins the `to` transform forever, and an animation-held transform outranks
     the `:hover` rule below in the cascade — the card would stop lifting. With
     `backwards` the delay shows the `from` frame and the element returns to its
     own base value the moment the animation ends. */
  .dv2 .bl-door {
    animation: bl-door-in .5s cubic-bezier(.2, .75, .3, 1) .2s backwards;
    transition: transform .22s ease, box-shadow .22s ease, border-color .22s ease;
  }
  .dv2 .bl-door:hover {
    transform: translateY(-3px);
    box-shadow: 0 1px 0 rgba(15, 17, 26, .04), 0 22px 44px -20px rgba(28, 32, 75, .42);
  }
  .dv2 .bl-door-go svg { transition: transform .22s ease; }
  .dv2 .bl-door:hover .bl-door-go svg { transform: translateX(4px); }

  .dv2 .bl-door-bar::after {
    animation: bl-door-pass 7.4s cubic-bezier(.42, 0, .28, 1) 1.4s infinite;
  }

  /* Reuse the product's existing entrance keyframe BY NAME AND VALUE
     (agent-experience.css :92 `br-rise`), rather than minting a second one
     that drifts from it.

     NOTE the missing `.bl-reveal { opacity: 0 }` — its absence is the whole
     point. Hiding sections up front and waiting for JS to un-hide them yields a
     BLANK PAGE whenever the script is blocked, the observer never fires, or a
     crawler renders without executing JS. The base state is fully visible; the
     animation only decorates the moment a section scrolls into view. */
  .dv2 .bl-reveal.is-in { animation: br-rise .42s ease both; }
}

/* `br-rise` is DEFINED in agent-experience.css, which this page does not load.
   Re-declared here with the SAME name and the SAME values so the two surfaces
   cannot drift apart: if one is ever retuned, the other must be edited to match. */
@keyframes br-rise {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Deliberately NOT `br-rise`: this one must not end on a pinned transform (see
   the `backwards` note above), and it travels 11px rather than 8 because the
   card is above the fold with no scroll cue to explain its arrival. */
@keyframes bl-door-in {
  from { opacity: 0; transform: translateY(11px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* One pass, then 6.4s of nothing. Percentages, not seconds, so retuning the
   duration above keeps the ratio: the strip is idle 88% of the time. */
@keyframes bl-door-pass {
  0%    { transform: translateX(-120%); opacity: 0; }
  1.5%  { opacity: .95; }
  11%   { opacity: .95; }
  12.8% { transform: translateX(680%); opacity: 0; }
  100%  { transform: translateX(680%); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .dv2 .bl-reveal,
  .dv2 .bl-reveal.is-in { opacity: 1; animation: none !important; }
  /* The card's base state is fully visible and untransformed, so removing the
     animation is enough — nothing here needs an opacity reset. The sweep is
     `opacity: 0` at rest, so stopping it leaves the strip legible rather than
     leaving a bright band parked on top of a reading. */
  .dv2 .bl-door { animation: none; transition: none; }
  .dv2 .bl-door-bar::after { animation: none; }
}

/* ═══ RESPONSIVE ════════════════════════════════════════════════════════
   The hatch period is deliberately absent from every block below: --bl-hatch-w
   and --bl-hatch-p are absolute px and NEVER scale down. A percentage-based
   hatch would vanish on a phone, which is exactly where an abstention most
   needs to survive.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 1080px) {
  .dv2 .bl-hero-grid,
  .dv2 .bl-home-grid { grid-template-columns: 1fr; }
  .dv2 .bl-grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 860px) {
  .dv2 .bl-sec { padding: 58px 0; }
  .dv2 .bl-grid-2,
  .dv2 .bl-grid-3,
  .dv2 .bl-limits,
  .dv2 .bl-ledger { grid-template-columns: 1fr; }
  .dv2 .bl-measure-row { grid-template-columns: 1fr; gap: 6px; }
  .dv2 .bl-swatches li { grid-template-columns: 50px minmax(0, 1fr); }
  .dv2 .bl-swatches li > .bl-sw-label { grid-column: 2; }
  .dv2 .bl-swatches li > .bl-sw-copy { grid-column: 1 / -1; }
}
@media (max-width: 720px) {
  /* The reading strip regrids to a stack: label line, full-width bar, counts.
     Arguably better than desktop — four labelled bars, nothing truncated. */
  .dv2 .bl-reading {
    grid-template-columns: 22px minmax(0, 1fr);
    row-gap: 9px; padding: 15px 2px;
  }
  .dv2 .bl-reading .bl-bar { grid-row: 2; grid-column: 1 / -1; height: 18px; }
  .dv2 .bl-reading-n { grid-row: 3; grid-column: 1 / -1; text-align: left; }
  /* A 5px segment cannot carry a label, so the inline counts move out to the
     key below rather than rendering at an ambiguous width. */
  .dv2 .bl-bar-lg { height: 34px; }
  .dv2 .bl-bar-lg .bl-seg b { display: none; }
  .dv2 .bl-axis-grid { grid-template-columns: minmax(0, 1fr); }
  .dv2 .bl-axis-mark::before { display: none; }
  .dv2 .bl-axis-mark { margin-bottom: 14px; }
  .dv2 .bl-rung { grid-template-columns: 26px minmax(0, 1fr); row-gap: 4px; }
  .dv2 .bl-rung .bl-rung-say { grid-column: 2; }
}
@media (max-width: 560px) {
  .dv2 .bl-hero { padding: 22px 0 52px; }
  .dv2 .bl-instrument { padding: 18px 15px; border-radius: 22px; }
  .dv2 .bl-step { grid-template-columns: 1fr; gap: 10px; }
  .dv2 .bl-limit { grid-template-columns: 1fr; gap: 10px; }
  .dv2 .bl-cta { padding: 26px 20px; }
  /* The door's two nowrap runs (the kicker and the reading label) are the only
     things on it that could push the page wide; both already ellipsise, but at
     this width the reading is worth more whole than the truncation is worth
     tidy, so the row stacks instead. */
  .dv2 .bl-door { padding: 13px 15px 12px 19px; border-radius: 16px; }
  .dv2 .bl-door-row { flex-direction: column; align-items: flex-start; gap: 3px; }
  .dv2 .bl-door-lab { white-space: normal; }
  .dv2 .bl-door-a { font-size: 16.5px; }
  .dv2 .bl-door-q { font-size: 15px; }
}
/* 360px floor: nothing may force horizontal page scroll — long paths wrap
   (`overflow-wrap: anywhere` above) and the code blocks scroll inside
   themselves (design-v2.css `.code-block pre { overflow-x: auto }`). */
@media (max-width: 380px) {
  .dv2 .bl-inst-head { flex-direction: column; align-items: flex-start; }
  .dv2 .bl-rung code { font-size: 11px; }
}
