/* ══════════════════════════════════════════════════════════════════════════
   WALLREEL — landing page + privacy page
   Tokens transcribed verbatim from site/ART-DIRECTION.md §1, which inherits
   from wall/WALLCTL-DESIGN.md and src/wall/wall.js GRID_CSS.
   Dark only, by decision (ART-DIRECTION §0). No webfonts, no remote anything.
   ══════════════════════════════════════════════════════════════════════════ */
:root{
  color-scheme: dark;
  /* surfaces */
  --bg:#0e0e0e;                      /* = .vw-root background */
  --bg-raise:#131418;                /* = .vw-pad — panels only */
  --bar-bg:rgba(14,14,14,0.92);      /* = --vwc-bar-bg — the hero rail */
  --hairline:#1f2126;                /* = --vwc-hairline — every rule on the page */
  --hairline-soft:rgba(255,255,255,0.07);  /* = --vwc-group-border */
  --group-bg:rgba(255,255,255,0.03); /* = --vwc-group-bg — seg track, row hover */
  --hover-bg:rgba(255,255,255,0.06); /* = --vwc-hover-bg */
  --active-bg:rgba(255,255,255,0.10);/* = --vwc-active-bg */
  --chip:#26282e;                    /* = --vwc-chip — SELECTED segment only */
  --chip-hover:#3a3d45;              /* = .vw-close:hover */
  /* ink */
  --text-strong:#e5e7eb;             /* 15.59:1 on --bg */
  --text:#cbd5e1;                    /* 13.00:1 on --bg */
  --text-num:rgba(203,213,225,0.85); /*  9.53:1 on --bg */
  --text-dim:rgba(203,213,225,0.62); /*  5.47:1 on --bg · 5.42:1 on --bg-raise */
  --accent:#22d3ee;                  /* 10.68:1 — focus rings + ONE status dot. NEVER a fill. */
  /* primary button */
  --btn-bg:#e5e7eb; --btn-ink:#0b0b0c; --btn-hover:#ffffff; --btn-active:#cbd5e1;
  /* type */
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, "Helvetica Neue", sans-serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  --fs-display: clamp(30px, 5.4vw, 54px);
  --fs-h2:      clamp(23px, 3.1vw, 32px);
  --fs-lede:    clamp(15px, 1.55vw, 19px);
  --fs-body:16px;  --fs-sm:14px;
  --fs-num:13px;
  --fs-micro:11px;
  /* space — 4px base */
  --s1:4px;--s2:8px;--s3:12px;--s4:16px;--s5:20px;--s6:24px;--s7:32px;--s8:40px;--s9:56px;--s10:72px;
  --section: clamp(60px, 8.5vw, 104px);
  --container:1120px; --gutter:24px;
  --hero-w:1680px;                   /* the before/after band. The body stays 1120.
                                        W = (1680-72)/2 = 804, so a 2x display asks for
                                        1608 device px. MEASURED: the masters are 1728 wide,
                                        so the video is never upscaled at any viewport. */
  /* radii */
  --r-xs:4px;  --r-sm:6px;  --r-md:8px;  --r-lg:12px;  --r-xl:14px;
  /* motion */
  --t-fast:150ms;  --t-med:240ms;  --t-slow:400ms;  --ease:cubic-bezier(0.2,0,0,1);
}

*,*::before,*::after{box-sizing:border-box;}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;}
body{
  margin:0;background:var(--bg);color:var(--text);
  font:var(--fs-body)/1.6 var(--sans);
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
  overflow-x:hidden;
}
img{max-width:100%;display:block;}
/* The hairline under a link is drawn with text-decoration rather than
   border-bottom (ART-DIRECTION §4 specifies the border). Same 1px hairline,
   same colour — but it stays glued to the baseline when padding-block widens
   the hit area to the required 24px minimum. A border would drift downward. */
a{color:var(--text-strong);text-decoration:underline;
  text-decoration-color:var(--hairline-soft);text-decoration-thickness:1px;
  text-underline-offset:3px;padding-block:4px;}
a:hover{text-decoration-color:var(--text-dim);}
h1,h2,h3,p,ol,ul,dl,dd,dt,figure,table,blockquote{margin:0;}
ol,ul{padding:0;list-style:none;}

/* one focus rule, everywhere (ART-DIRECTION §4) */
:where(a,button,summary,label,input,[tabindex]):focus-visible{
  outline:2px solid var(--accent); outline-offset:2px; border-radius:var(--r-xs);
}
.sr{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;border:0;}

.skip{position:absolute;left:var(--gutter);top:-100px;z-index:20;
      background:var(--btn-bg);color:var(--btn-ink);border:0;border-radius:var(--r-md);
      padding:12px var(--s5);min-height:44px;display:inline-flex;align-items:center;
      font:600 14px/1 var(--sans);}
.skip:focus{top:12px;}

.wrap{max-width:var(--container);margin:0 auto;padding:0 var(--gutter);}
.ml{font:600 var(--fs-micro)/1 var(--mono);text-transform:uppercase;letter-spacing:.08em;
    color:var(--text-dim);}
.num{font-family:var(--mono);font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1;}

/* ══ [NEEDS ROBERT] — build markers ═══════════════════════════════════════
   Deliberately unmistakable: dashed rule + stripe. Nothing else on the page
   uses a dashed border, so these can never be read as shipped copy.
   Every one is listed in the engineer's report. Robert fills them before deploy. */
.needs{
  display:block;margin:var(--s3) 0;padding:var(--s3) var(--s4);
  border:1px dashed var(--text-dim);border-radius:var(--r-md);
  background:repeating-linear-gradient(135deg,
    rgba(255,255,255,0.03) 0 8px, rgba(255,255,255,0.06) 8px 16px);
  font:var(--fs-num)/1.55 var(--mono);color:var(--text-strong);
}
.needs-tag{display:inline-block;margin-right:var(--s2);padding:4px var(--s2);
  border-radius:var(--r-xs);background:var(--chip);color:var(--text-strong);
  font:600 var(--fs-micro)/1.3 var(--mono);text-transform:uppercase;letter-spacing:.08em;
  vertical-align:baseline;}
.needs-draft{display:block;margin-top:var(--s2);color:var(--text-dim);}
.needs-inline{display:inline-block;margin:0;padding:2px var(--s2);font-size:12px;}

/* ══ MASTHEAD ═════════════════════════════════════════════ */
.masthead{height:64px;display:flex;align-items:center;justify-content:space-between;gap:var(--s4);}
.lockup{display:inline-flex;align-items:center;gap:var(--s2);min-height:24px;text-decoration:none;}
/* The extension icon in the masthead, 2026-08-26. It REPLACES the dot in every lockup
   (index, generated pages, sites, privacy, welcome): the icon carries the accent cyan
   itself, so dot + icon would be two marks for one job. 22px from the 128px PNG is
   sharp at 2x. */
.logo{width:22px;height:22px;border-radius:5px;flex:none;display:block;}
.wordmark{font:500 15px/1 var(--mono);letter-spacing:.01em;color:var(--text-strong);}
.masthead .meta{font:var(--fs-num)/1 var(--mono);color:var(--text-dim);}

/* ══ HERO ═════════════════════════════════════════════════ */
.hero{padding:clamp(28px,4.5vw,56px) var(--gutter) var(--section);text-align:center;}
/* .hero is no longer `.wrap`. The head and foot carry the 1120 cap themselves and
   .ba breaks out to --hero-w. Two elements, so `.wrap{padding:0 var(--gutter)}` (0,1,0)
   and `.hero{padding-*}` (0,1,0) never contest one element again. HERO-V2-DESIGN §9. */
.hero-head,.hero-foot{max-width:var(--container);margin-inline:auto;}
h1{font:600 var(--fs-display)/1.06 var(--sans);letter-spacing:-.022em;color:var(--text-strong);
   max-width:19ch;margin:0 auto;text-wrap:balance;}
.lede{font-size:var(--fs-lede);line-height:1.5;color:var(--text);max-width:56ch;
      margin:var(--s4) auto 0;text-wrap:pretty;}
.hero-eyebrow{display:block;margin:0 auto var(--s4);}   /* .ml, centred above the h1 */
.cta-row{margin-top:var(--s6);display:flex;flex-direction:column;align-items:center;gap:var(--s3);}
.cta-meta{font:var(--fs-num)/1.4 var(--mono);color:var(--text-dim);}
.cta-second{font-size:var(--fs-sm);color:var(--text);cursor:pointer;
  display:inline-flex;align-items:center;min-height:24px;padding-block:4px;
  text-decoration:underline;text-decoration-color:var(--hairline-soft);
  text-decoration-thickness:1px;text-underline-offset:3px;}
.cta-second:hover{text-decoration-color:var(--text-dim);color:var(--text-strong);}

/* buttons */
.btn{
  appearance:none;border:0;cursor:pointer;font:600 15px/1.25 var(--sans);
  display:inline-flex;align-items:center;justify-content:center;gap:var(--s2);
  min-height:48px;padding:var(--s3) var(--s6);border-radius:var(--r-md);text-align:center;
  transition:background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
/* BROWSER LOGOS, 2026-08-26. The official marks, inlined: BRIEF.md §8 holds the
   zero-extra-request rule, and an inline mark cannot 404 or land after its button.
   Every gradient id inside them is namespaced wrch-/wrff- because both source files
   shipped ids "a","b","c" — inlined raw, the second logo would render with the first
   one's gradients. flex-shrink:0 because .btn is inline-flex.
   aria-hidden: the button text already names the browser. */
.blogo{flex-shrink:0;width:20px;height:20px;display:block;}
.btn-primary{background:var(--btn-bg);color:var(--btn-ink);}
.btn-primary:hover{background:var(--btn-hover);}
.btn-primary:active{background:var(--btn-active);}
a.btn{text-decoration:none;}
.caret{width:0;height:0;border-left:4.5px solid transparent;border-right:4.5px solid transparent;
       border-top:5px solid currentColor;margin-top:1px;flex:none;}

/* ── THE HERO BEFORE/AFTER PAIR ──────────────────────────
   Two browser mock-ups shown at once. No toggle, no crossfade, no drift, and no
   transform anywhere in the hero. HERO-V2-DESIGN §0-§3. */
.ba{
  width:100%;max-width:var(--hero-w);margin:var(--s8) auto 0;
  display:grid;grid-template-columns:1fr calc(var(--s10) + var(--s6)) 1fr;grid-template-rows:auto auto auto;
  align-items:start;row-gap:var(--s4);
}
.ba-lead-before{grid-column:1;grid-row:1;}
.ba-lead-after {grid-column:3;grid-row:1;}
.bw-before    {grid-column:1;grid-row:2;}
.ba-arrow     {grid-column:2;grid-row:2;}
.bw-after     {grid-column:3;grid-row:2;}
.ba-cap-before{grid-column:1;grid-row:3;}
.ba-cap-after {grid-column:3;grid-row:3;}
/* The lead-in above each window. Set in the h1 face at regular weight, one step
   below the display size, so it reads as a heading for its panel and not as a second
   h1. The size is Robert's ask (~36px) and clamps down with the display type.
   "Before" is dim and "With Wallreel" is strong: the type carries the same contrast
   the two windows carry, and the eye lands on the right-hand panel first. Ratios by
   the WCAG formula on --bg: --text-dim 5.47:1, --text-strong 17+:1. Both clear AA
   for large text with room. `align-self:end` seats both baselines on the window bar
   even when one label wraps. */
.ba-lead{
  font:400 clamp(26px, 2.6vw, 36px)/1.1 var(--sans);letter-spacing:-.015em;
  color:var(--text-strong);text-align:center;margin:0;align-self:end;
  text-wrap:balance;
}
.ba-lead-before{color:var(--text-dim);}

/* The window. Both panels are geometrically identical and only the label differs:
   the comparison's credibility rests on the two windows being the same window.
   No box-shadow — elevation is surface value + hairline (ART-DIRECTION §0). */
.bw{
  border:1px solid var(--hairline);border-radius:var(--r-xl);overflow:hidden;
  background:var(--bg);text-align:left;
}
/* THE CHROME IS A MID-TONE, and both extremes were tried and rejected on sight.
   It shipped as var(--bar-bg) = rgba(14,14,14,0.92) on var(--bg) = #0e0e0e — the same
   colour, so the window had no visible top edge and did not read as a window at all.
   The fix went too far the other way: --btn-bg #e5e7eb is literally the Download
   button's fill, so two bright rounded bars appeared under the CTA and read as buttons.
   A surface must not borrow a control's colour. That is the actual lesson here.
   --chip-hover #3a3d45 is the existing token for the lightest raised surface, and it is
   within a point or two of what Chrome and Firefox paint their own dark toolbars.
   NO NEW TOKEN. Ratios COMPUTED by the WCAG formula:
     bar #3a3d45 on page #0e0e0e ....... 1.78:1  — visible as a surface, not as a control
     .bw-name --text #cbd5e1 on bar .... 7.32:1  — clears ART-DIRECTION §9
     dots --text-dim on bar ............ 3.88:1  — decorative, aria-hidden
   .bw-name must NOT inherit .ml's --text-dim: at 11px it computes 3.88:1 on this bar,
   which fails AA for text that is not large. Hence the explicit --text override. */
.bw-bar{
  display:flex;align-items:center;gap:var(--s4);
  height:var(--s7);padding-inline:var(--s4);
  background:var(--chip-hover);
}
.bw-name{color:var(--text);}
.bw-dots{display:flex;gap:6px;flex:none;}
.bw-dots i{width:8px;height:8px;border-radius:50%;background:var(--text-dim);}
.bw-screen{aspect-ratio:16/9;}
.bw-screen video{width:100%;height:100%;object-fit:cover;object-position:50% 0;display:block;}
.ba-cap{font:12px/1.55 var(--mono);color:var(--text-dim);text-align:left;}

/* The connector. The arrow head is the --fs-micro caret recipe, rotated by swapping
   which borders are transparent — never by transform. So the reduce-motion block has
   nothing to strip here and no chance to strip the wrong thing. */
.ba-arrow{
  align-self:stretch;display:flex;align-items:center;justify-content:center;
  padding-top:var(--s7);   /* centres the circle on the VIDEO box, not on the panel */
}
/* The circle is the raised surface the window bars use (--chip-hover), not the page
   colour. On --bg it vanished: page on page behind a hairline. 2026-08-26, Robert. */
.ba-arrow > span{
  width:var(--s9);height:var(--s9);border-radius:50%;
  background:var(--chip-hover);border:1px solid var(--hairline);color:var(--text-strong);
  display:flex;align-items:center;justify-content:center;
}
.ba-arrow > span::before{content:"";width:26px;height:2px;background:currentColor;}
.ba-arrow > span::after{content:"";width:0;height:0;
  border-top:7px solid transparent;border-bottom:7px solid transparent;
  border-left:10px solid currentColor;}

/* ── THE SEGMENTED SWITCH — install block only ───────────
   The hero's copy of this control died with the before/after toggle. The rules did
   not: #b-chrome / #b-firefox still bind to them. Do not delete .seg or .seg-btn. */
.seg{display:flex;align-items:center;gap:2px;padding:2px;flex:none;
     background:var(--group-bg);border:1px solid var(--hairline-soft);border-radius:var(--r-md);}
.seg-btn{
  display:inline-flex;align-items:center;justify-content:center;
  min-height:28px;min-width:68px;padding:0 var(--s3);border-radius:var(--r-xs);cursor:pointer;
  font:var(--fs-num)/1 var(--mono);color:var(--text-dim);
  transition:color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}
.seg-btn:hover{color:var(--text-strong);}  /* colour only — hover can never read as selected */
/* Selected and focus states use the ADJACENT combinator, never :has(). The input now
   sits directly before its own label, so these work on every browser. Only the
   show/hide below depends on :has(), and that dependency can only ever HIDE. */
input[name="br"]:checked + .seg-btn{background:var(--chip);color:var(--text-strong);}
input[name="br"]:focus-visible + .seg-btn{outline:2px solid var(--accent);outline-offset:-2px;}

/* provenance line — BRIEF §4 makes this non-negotiable on every hero version */
.hero-cap{margin:var(--s4) auto 0;max-width:74ch;
          font:12px/1.55 var(--mono);color:var(--text-dim);}
.rm-note,.nojs-note{margin:var(--s3) auto 0;max-width:74ch;
          font:12px/1.55 var(--mono);color:var(--text-dim);}
.rm-note{display:none;}

/* ══ SECTION FURNITURE ════════════════════════════════════
   section.wrap, not section — `.wrap{padding:0 var(--gutter)}` is (0,1,0) and
   would otherwise win against a bare `section` (0,0,1) and zero the rhythm. */
section.wrap{padding-block:var(--section);}
.sec-head{max-width:64ch;}
h2{font:600 var(--fs-h2)/1.14 var(--sans);letter-spacing:-.018em;color:var(--text-strong);text-wrap:balance;}
.sec-head .ml{display:block;margin-bottom:var(--s4);}
.sec-head p{margin-top:var(--s4);font-size:var(--fs-lede);line-height:1.55;color:var(--text);text-wrap:pretty;}
.privacy-claim{font-weight:500;color:var(--text-strong);}

/* ══ 3 · WHAT YOU GET ═════════════════════════════════════ */
.caps{display:flex;flex-wrap:wrap;margin-top:var(--s8);border-top:1px solid var(--hairline);}
.cap-item{flex:1 1 240px;padding-top:var(--s5);display:flex;flex-direction:column;}
.cap-item:not(:first-child){border-top:1px solid var(--hairline);margin-top:var(--s5);}
.cap-item h3{font:600 16px/1.3 var(--sans);color:var(--text-strong);}
.cap-item p{margin-top:var(--s2);font-size:var(--fs-sm);line-height:1.55;color:var(--text-dim);}
/* the evidence line sits UNDER the claim it proves — see report note.
   margin-top:auto pushes it to the bottom of the flex column so all four
   evidence rules land on one line across the row. */
.cap-item p.cap-stat{margin-top:auto;padding-top:var(--s3);border-top:1px solid var(--hairline);
  font:var(--fs-num)/1.5 var(--mono);font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1;
  color:var(--text-num);}
.caps-foot{margin-top:var(--s7);font-size:var(--fs-sm);line-height:1.6;color:var(--text-dim);max-width:64ch;}

/* ══ 2 · FEATURE ROWS ═════════════════════════════════════
   Two-column rows, alternating sides. `.feat-flip` reverses the columns with
   grid `order` and ONLY at >=900px, so the DOM order is the mobile order and
   every row stacks heading-then-visual. Reversing with `direction:rtl` or with
   `row-reverse` was rejected: both flip the text's own inline direction or need
   an override on each child.
   Column ratio is 1fr 1.15fr — the control replica is the wider half, because it
   is the thing being demonstrated. */
.feat{display:grid;grid-template-columns:1fr;gap:var(--s6);align-items:center;
      padding-block:var(--s9);border-top:1px solid var(--hairline);}
.feat:first-of-type{margin-top:var(--s8);}
.feat-text h3{font:600 clamp(20px,2.4vw,26px)/1.2 var(--sans);letter-spacing:-.015em;
              color:var(--text-strong);text-wrap:balance;}
.feat-text p{margin-top:var(--s3);font-size:var(--fs-lede);line-height:1.55;color:var(--text-dim);}
.feat-stat{margin-top:var(--s4);padding-top:var(--s3);border-top:1px solid var(--hairline);
  font:var(--fs-num)/1.6 var(--mono);font-variant-numeric:tabular-nums;
  font-feature-settings:"tnum" 1;color:var(--text-num);}
.feat-cap{margin-top:var(--s3);font:12px/1.55 var(--mono);color:var(--text-dim);text-align:center;}

/* ── the stage the replica sits on ─────────────────────────
   A panel, not a screenshot. It carries a hint of the wall (six dim tiles) so the
   control has something to be a control OF, then the control sits over it. */
.ctl-stage{container-type:inline-size;position:relative;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:var(--s6);padding:var(--s7) var(--s5);min-height:260px;
  border:1px solid var(--hairline);border-radius:var(--r-lg);background:var(--bg-raise);
  overflow:hidden;}
/* ── the marquee ───────────────────────────────────────────
   THREE elements, and the nesting is the whole fix:
     .ctl-tiles  — the fixed viewport, clips and masks.
     .ctl-track  — the ONE moving element, holds BOTH copies, animates to -50%.
     .ctl-copy   — two identical rows of tiles.
   A translate percentage resolves against the MOVING ELEMENT'S OWN border box.
   The first attempt animated each copy, so -50% was half of one copy: MEASURED a
   285.5px jump against a 576px copy, and the seam was plainly visible. With both
   copies inside the animated element, -50% IS one copy, by construction — there
   is no number to keep in sync and no breakpoint that can desync it.
   (The build before that had a different bug: six tiles moving -72px with nothing
   behind the seam, plus `justify-content:center` re-centring the row every frame.) */
.ctl-tiles{display:flex;width:100%;overflow:hidden;
  /* the strip is a suggestion of the wall, so it runs off both edges */
  mask-image:linear-gradient(to right,transparent,#000 12%,#000 88%,transparent);
  -webkit-mask-image:linear-gradient(to right,transparent,#000 12%,#000 88%,transparent);}
.ctl-track{display:flex;flex:0 0 auto;
  /* promote once, so the browser does not re-raster the row every frame */
  will-change:transform;}
.ctl-copy{display:flex;gap:var(--s2);padding-right:var(--s2);flex:0 0 auto;}
/* A tile has to read as a frame of video, not as an empty box. Two cheap cues do
   it: a lit gradient so each tile has its own value, and a play glyph. No images
   — the page ships no new bytes for a decorative strip. The nth-child values are
   arbitrary and only exist to stop six identical rectangles reading as a table. */
.ctl-tile{flex:0 0 auto;width:64px;height:36px;border-radius:var(--r-sm);
  border:1px solid var(--hairline-soft);position:relative;overflow:hidden;
  background:linear-gradient(150deg,rgba(255,255,255,0.10),rgba(255,255,255,0.02) 60%);}
.ctl-tile:nth-child(2){background:linear-gradient(150deg,rgba(255,255,255,0.05),rgba(255,255,255,0.02) 70%);}
.ctl-tile:nth-child(3){background:linear-gradient(200deg,rgba(255,255,255,0.13),rgba(255,255,255,0.03) 55%);}
.ctl-tile:nth-child(4){background:linear-gradient(120deg,rgba(255,255,255,0.06),rgba(255,255,255,0.02) 65%);}
.ctl-tile:nth-child(5){background:linear-gradient(170deg,rgba(255,255,255,0.11),rgba(255,255,255,0.03) 60%);}
.ctl-tile:nth-child(6){background:linear-gradient(140deg,rgba(255,255,255,0.04),rgba(255,255,255,0.02) 75%);}
/* the play triangle, same CSS-border shape the extension uses for its own */
.ctl-tile::after{content:"";position:absolute;top:50%;left:50%;
  transform:translate(-50%,-50%);width:0;height:0;
  border-left:7px solid rgba(203,213,225,0.38);
  border-top:4.5px solid transparent;border-bottom:4.5px solid transparent;
  margin-left:1px;}
/* The drift row's tiles slide; the rate row's tiles pulse. Both are decorative
   and both are removed by the motion policy at the bottom of this file.
   --drift-dur is set by main.js from the slider value; the fallback keeps the
   animation honest with JS off (40 px/s over the 72px period = 1.8s). */
/* -50% of the DOUBLED track = exactly one copy, so the reset is invisible.
   A px distance cannot work here: the tile width changes at two breakpoints and
   any hardcoded number would desync from the content it is supposed to loop. */
.feat .ctl-track{animation:ctl-drift var(--drift-dur,9s) linear infinite;}
/* The rate row does not drift and does not pulse. It used to pulse the whole
   strip's opacity, .5 to 1 over --rate-dur, and Robert could not see it: the tiles
   are 2-13% white on #0e0e0e, so half of nearly-black is nearly-black. And a pulse
   is not a picture of speed. This is: a progress line along each tile's bottom
   edge, filling left to right and looping, 2s per pass at 1x. --rate-dur is
   2s / rate, so .5x visibly halves the speed of every bar under the click. All
   eight bars run in lockstep, by Robert's call: one fill starting and ending
   together reads as one rate applied to every clip at once, which is the claim
   in the copy beside it. */
.feat .ctl-tiles-rate .ctl-track{animation:none;}
.ctl-tiles-rate .ctl-tile::before{content:"";position:absolute;left:0;right:0;bottom:0;height:3px;
  background:rgba(203,213,225,0.85);transform-origin:left;transform:scaleX(0);
  animation:ctl-sweep var(--rate-dur,2s) linear infinite;}
@keyframes ctl-drift{from{transform:translateX(0);}to{transform:translateX(-50%);}}
@keyframes ctl-sweep{from{transform:scaleX(0);}to{transform:scaleX(1);}}
/* At drift 0 the strip must be STILL, not slow. A paused animation is the only
   honest render of "the wall stops dead": a 0 px/s wall that still creeps would
   contradict the sentence next to it. */
.ctl-stage[data-drift="0"] .ctl-track{animation-play-state:paused;}

/* ── the oversized control replica ─────────────────────────
   Transcribed from src/wall/wall.js GRID_CSS (.vwc-group and friends), scaled by
   --k. The extension's real group is 28px tall; --k:3 puts this at 84px. Every
   value below is the real value times --k, or a token that is already shared.
   `em`-free on purpose: the extension's numbers are px and so are these. */
/* --k IS DERIVED, NOT ASSERTED, AND IT MUST BE A REGISTERED NUMBER.
   Two bugs, both caught by a screenshot and then by reading the computed value:
   1. `--k:3` hardcoded made the drift group 715px inside a 550px column. The
      label and the readout were clipped off both ends.
   2. The first fix divided a length by a bare number inside an UNREGISTERED
      custom property. An unregistered property is a token string, not a value:
      it never computed, so every `calc(28px * var(--k))` was invalid-at-
      computed-value-time and the whole replica silently fell back to its
      unscaled 28px self. MEASURED: --k read back as the literal text
      "min(3, (100cqi - 40px) / 238)" and the label rendered at inherited 16px.
   `@property` with syntax "<number>" is what makes the division resolve.
   MEASURED at --k:3 — drift renders 715px, so one unit of k is 238px of drift
   group. BOTH groups divide by 238, deliberately: row 2's caption says "same
   scale", so the two replicas must resolve to the same --k. Sizing each group to
   its own width made them 1.96 and 1.62 and quietly made that caption false. The
   drift group is the wider of the two, so it is the binding constraint; the clips
   pill just ends up shorter, exactly as it is in the extension's real bar.
   If the drift control gains a child, re-measure this number. */
@property --k{syntax:"<number>";inherits:true;initial-value:3;}
/* --kw is a LENGTH, not a bare number. `(100cqi - 40px) / 238` divides a length
   by a number and yields a LENGTH, so `min(3, <length>)` mixes types, is invalid,
   and falls back to the initial value 3 — which is exactly the overflow this was
   meant to prevent, now silent. Dividing length BY length is what produces the
   unitless ratio `--k` is declared to hold.
   AND IT MUST NOT BE WRITTEN AS `a / b`. Length-by-length division in calc() is
   Chromium-only. MEASURED 2026-08-25 on Firefox 154, clean profile: the division
   was invalid, --k fell back to 3, and the drift group rendered 715px inside a
   579px stage — bug 1 above, back again, on the browser half the buyers use.
   tan(atan2(a, b)) is the portable form: atan2 takes two lengths and returns an
   angle, tan returns the unitless ratio a/b. Chromium 2.09 → 2.09, Firefox 3 → 2.09. */
.vwc-big{--kw:238px;--k:min(3, tan(atan2(100cqi - 40px, var(--kw))));position:relative;z-index:1;
  display:flex;align-items:center;gap:calc(8px * var(--k));
  height:calc(28px * var(--k));padding:0 calc(8px * var(--k));
  background:var(--group-bg);border:1px solid var(--hairline-soft);
  border-radius:calc(8px * var(--k));box-sizing:border-box;
  /* the real bar sits over video, so the replica gets the same backdrop */
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);}
.vwc-label{font:600 calc(9px * var(--k))/1 var(--mono);letter-spacing:.08em;
  text-transform:uppercase;color:var(--text-dim);}
.vwc-toggle{appearance:none;-webkit-appearance:none;border:0;margin:0;padding:0;
  background:transparent;width:calc(24px * var(--k));height:calc(24px * var(--k));
  border-radius:calc(6px * var(--k));color:var(--text);cursor:pointer;
  display:grid;place-items:center;transition:background var(--t-fast);}
.vwc-toggle:hover{background:var(--hover-bg);}
.vwc-toggle:active{background:var(--active-bg);}
.vwc-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:0;}
/* playing -> pause bars. border-box so the 8px is the TOTAL width, exactly as
   the extension does it (3px bar, 2px gap, 3px bar), times --k. */
.vwc-group[data-state="playing"] .vwc-toggle::before{
  content:"";box-sizing:border-box;width:calc(8px * var(--k));height:calc(10px * var(--k));
  border-left:calc(3px * var(--k)) solid currentColor;
  border-right:calc(3px * var(--k)) solid currentColor;}
/* paused -> play triangle */
.vwc-group[data-state="paused"] .vwc-toggle::before{
  content:"";width:0;height:0;border-left:calc(9px * var(--k)) solid currentColor;
  border-top:calc(5.5px * var(--k)) solid transparent;
  border-bottom:calc(5.5px * var(--k)) solid transparent;
  margin-left:calc(2px * var(--k));}
.vwc-slider{-webkit-appearance:none;appearance:none;width:calc(88px * var(--k));
  height:calc(24px * var(--k));margin:0;padding:0;background:transparent;cursor:pointer;}
.vwc-slider:focus-visible{outline:2px solid var(--accent);outline-offset:0;}
/* webkit and moz rules stay SEPARATE — an unknown selector voids a shared rule
   in some engines. This is the extension's own build note, and it still holds. */
.vwc-slider::-webkit-slider-runnable-track{height:calc(2px * var(--k));
  border-radius:calc(1px * var(--k));
  background:linear-gradient(to right,rgba(203,213,225,0.60) var(--p,27%),
                             rgba(255,255,255,0.16) var(--p,27%));}
.vwc-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
  width:calc(10px * var(--k));height:calc(10px * var(--k));border-radius:50%;
  background:var(--text);border:0;margin-top:calc(-4px * var(--k));
  transition:transform var(--t-fast);}
.vwc-slider:hover::-webkit-slider-thumb,
.vwc-slider:active::-webkit-slider-thumb{transform:scale(1.2);}
.vwc-slider::-moz-range-track{height:calc(2px * var(--k));border-radius:calc(1px * var(--k));
  background:rgba(255,255,255,0.16);}
.vwc-slider::-moz-range-progress{height:calc(2px * var(--k));border-radius:calc(1px * var(--k));
  background:rgba(203,213,225,0.60);}
.vwc-slider::-moz-range-thumb{width:calc(10px * var(--k));height:calc(10px * var(--k));
  border-radius:50%;background:var(--text);border:0;transition:transform var(--t-fast);}
.vwc-slider:hover::-moz-range-thumb,
.vwc-slider:active::-moz-range-thumb{transform:scale(1.2);}
.vwc-readout{font:calc(11px * var(--k))/1.2 var(--mono);font-variant-numeric:tabular-nums;
  color:var(--text-num);min-width:8ch;text-align:right;}
.vwc-seg{appearance:none;-webkit-appearance:none;border:0;margin:0;background:transparent;
  min-width:calc(30px * var(--k));height:calc(24px * var(--k));padding:0 calc(5px * var(--k));
  border-radius:calc(4px * var(--k));font:calc(11px * var(--k))/1 var(--mono);
  color:var(--text-dim);cursor:pointer;transition:color var(--t-fast);}
/* hover brightens COLOUR ONLY. No hover background, so the selected chip is the
   only lit segment and hover can never be misread as selected. The extension
   makes the same choice for the same reason. */
/* mono tracking that is correct at 11px is loose at 33px, so the scaled-up
   segments pull it back in. The extension is unchanged; only this replica is
   bigger than the type was drawn for. */
.vwc-big .vwc-seg{letter-spacing:-.02em;}
.vwc-seg:hover{color:var(--text);}
.vwc-seg[aria-pressed="true"]{background:var(--chip);color:var(--text-strong);}
.vwc-seg:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;}

/* ══ 4 · COVERAGE TABLE ═══════════════════════════════════ */
.tablewrap{margin-top:var(--s8);border:1px solid var(--hairline);border-radius:var(--r-lg);
           background:var(--bg-raise);overflow:hidden;}
table{width:100%;border-collapse:collapse;}
thead th{padding:var(--s4) var(--s5);text-align:left;
         font:600 var(--fs-micro)/1.3 var(--mono);text-transform:uppercase;letter-spacing:.08em;
         color:var(--text-dim);border-bottom:1px solid var(--hairline);}
thead th.b{text-align:center;width:88px;}
thead th.b-ff{width:140px;}
.grp th{padding:var(--s5) var(--s5) var(--s2);
        font:600 var(--fs-micro)/1.3 var(--mono);text-transform:uppercase;letter-spacing:.08em;
        color:var(--text-dim);text-align:left;}
/* the group label IS the divider: rule above the label, none under it */
tbody + tbody .grp th{border-top:1px solid var(--hairline);padding-top:var(--s6);}
tbody td{padding:var(--s3) var(--s5);border-top:1px solid var(--hairline);vertical-align:middle;}
.grp + tr td{border-top:0;}
tbody tr:not(.grp):hover td{background:var(--group-bg);}
tbody tr:not(.grp) td{transition:background-color var(--t-fast) var(--ease);}
.site{font:var(--fs-num)/1.4 var(--mono);color:var(--text-strong);}
.mark{text-align:center;width:88px;}
.mark-ff{width:140px;}
.yes,.no{display:inline-block;vertical-align:middle;}
.yes{width:12px;height:7px;border-left:1.5px solid var(--text-strong);border-bottom:1.5px solid var(--text-strong);
     transform:rotate(-45deg) translateY(-2px);}
.no{width:11px;height:1px;background:var(--text-dim);}
.nodetail{display:block;margin-top:var(--s2);
  font:600 var(--fs-micro)/1.35 var(--mono);text-transform:uppercase;letter-spacing:.06em;
  color:var(--text-dim);}
tfoot td{padding:var(--s4) var(--s5) var(--s5);border-top:1px solid var(--hairline);
         font-size:var(--fs-sm);line-height:1.55;color:var(--text);}
.elsewhere{margin-top:var(--s6);max-width:64ch;font-size:var(--fs-sm);line-height:1.6;color:var(--text-dim);}
.elsewhere + .elsewhere{margin-top:var(--s4);}

/* ══ 5 · PRIVACY ══════════════════════════════════════════ */
.ledger{margin-top:var(--s7);border-top:1px solid var(--hairline);max-width:680px;}
.ledger li{padding:var(--s4) 0;border-bottom:1px solid var(--hairline);
           font-size:var(--fs-sm);line-height:1.55;color:var(--text-strong);}
.after-note{margin-top:var(--s6);max-width:64ch;font-size:var(--fs-sm);line-height:1.6;color:var(--text-dim);}

/* ══ 6 · GET IT ═══════════════════════════════════════════ */
.get{border-top:1px solid var(--hairline);}
.get-body{margin-top:var(--s8);}
/* PANEL — the page has exactly two raised panels, both holding verifiable facts */
.panel{border:1px solid var(--hairline);border-radius:var(--r-lg);
       background:var(--bg-raise);overflow:hidden;}
.price-row{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s2) var(--s4);padding:var(--s6);}
.price{font:600 40px/1 var(--mono);font-variant-numeric:tabular-nums;color:var(--text-strong);letter-spacing:-.02em;}
.price-terms{font:var(--fs-num)/1.6 var(--mono);color:var(--text-dim);}
.price-body{padding:0 var(--s6) var(--s6);max-width:64ch;font-size:var(--fs-sm);line-height:1.6;color:var(--text);}
.recap{padding:var(--s5) var(--s6);border-top:1px solid var(--hairline);
       font-size:var(--fs-sm);line-height:1.6;color:var(--text);}

.install-head{margin-top:var(--s9);}
.install-head h3{font:600 20px/1.3 var(--sans);color:var(--text-strong);}
/* ── THE BROWSER QUESTION ──────────────────────────────────────────────────
   Not a .panel. The page has exactly two raised panels and both hold verifiable
   facts; a question is not a fact. Two hairlines on the page background instead —
   the page's own separating device, doubled, which reads as a distinct unit with
   no new surface colour, radius or border weight. */
.ask{margin-top:var(--s9);padding:var(--s6) 0;border:0;border-top:1px solid var(--hairline);
     border-bottom:1px solid var(--hairline);}
.ask-legend{padding:0;float:none;width:100%;}
.ask-legend .ml{display:block;margin-bottom:var(--s3);}
.ask-q{display:block;font:600 20px/1.3 var(--sans);letter-spacing:-.015em;color:var(--text-strong);}
.ask-why{margin-top:var(--s3);max-width:56ch;font-size:var(--fs-sm);line-height:1.6;color:var(--text-dim);}
/* max-content: .seg is a block child of the fieldset, so without this it stretches
   to the full column and the two buttons sit in the left corner of an empty strip. */
.ask .seg{margin-top:var(--s5);width:max-content;}
/* 44px is the WCAG 2.5.8 target minimum. The old 28px was below it, and this is now
   the page's primary decision control. */
.ask .seg-btn{min-height:44px;min-width:140px;padding:var(--s2) var(--s5);
  flex-direction:column;gap:2px;font:600 15px/1.2 var(--sans);letter-spacing:0;text-transform:none;}
.ask .seg-btn .sub{font:var(--fs-micro)/1.3 var(--mono);color:var(--text-dim);
  text-transform:none;letter-spacing:0;}
.ask-hint{margin-top:var(--s3);font:var(--fs-num)/1.5 var(--mono);color:var(--text-dim);}
.ask-hint:empty{display:none;}

/* The unanswered state. Plain dim text in the position the steps will occupy — a
   boxed empty state would be a bigger visual event than the answer replacing it. */

/* The mismatch line. Information, not an alert — the page's convention is no amber,
   no red. --accent is focus rings only and is not available here. */
.dl-mismatch{margin-bottom:var(--s4);padding-left:var(--s4);
  border-left:2px solid var(--hairline-soft);font:var(--fs-num)/1.55 var(--mono);
  color:var(--text-strong);max-width:60ch;}
.dl-mismatch[hidden]{display:none;}

.switch-row{display:flex;align-items:center;gap:var(--s3);margin-top:var(--s6);flex-wrap:wrap;}
.timebadge{margin-left:auto;font:600 var(--fs-micro)/1.3 var(--mono);text-transform:uppercase;
           letter-spacing:.08em;color:var(--text-dim);
           border:1px solid var(--hairline-soft);border-radius:999px;padding:7px 12px;}

.steps{display:none;margin-top:var(--s7);}
/* The escape hatches are hidden until there is something to escape FROM. QA MEASURED
   both showing in the unanswered state — an offer to switch away from two browsers the
   reader had not chosen. */
/* THE MAPPING IS CROSSED ON PURPOSE. `.other-firefox` reads "Using Firefox instead?"
   so it must appear to a reader who picked CHROME. QA MEASURED the uncrossed version:
   it showed "Using Chrome instead?" to someone already on Chrome, and main.js then set
   Chrome again — a double inversion that cancelled to a no-op. This is the recovery
   path for the original bug, so it has to point AWAY from the current answer. */
/* Both counts hide by default. Before an answer the badge reads "About 90 seconds."
   and nothing more. QA MEASURED the old form rendering "Four steps. Two steps." in
   the unanswered state, directly above a prompt saying nothing was picked. */
.tb-chrome,.tb-firefox{display:none;}
/* :has() ONLY hides the prompt. Never let a :has() rule be what reveals a download. */

.steps > p{max-width:64ch;font-size:var(--fs-sm);line-height:1.6;color:var(--text-dim);}
.steps h3.steps-title{font:600 18px/1.3 var(--sans);color:var(--text-strong);margin-bottom:var(--s3);}
/* The four step heads on the front door. The steps themselves live on /install/chrome
   only (INSTALL-PAGE-PLAN.md §3.3); this list says they exist and are short. */
.step-heads{margin:var(--s3) 0 0;padding-left:1.2em;font-size:var(--fs-sm);line-height:1.8;color:var(--text);}
ol.rail-steps{position:relative;padding-left:44px;margin-top:var(--s7);}
ol.rail-steps::before{content:"";position:absolute;left:13px;top:8px;bottom:16px;width:1px;background:var(--hairline);}
ol.rail-steps > li{position:relative;padding-bottom:var(--s7);}
ol.rail-steps > li:last-child{padding-bottom:0;}
.node{position:absolute;left:-44px;top:-3px;width:28px;height:28px;border-radius:50%;
      background:var(--bg);border:1px solid var(--hairline);
      display:grid;place-items:center;
      font:var(--fs-num)/1 var(--mono);font-variant-numeric:tabular-nums;color:var(--text-num);}
.step-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s3);}
.step-head h4{font:600 17px/1.35 var(--sans);color:var(--text-strong);margin:0;}
.tag{font:600 var(--fs-micro)/1.3 var(--mono);text-transform:uppercase;letter-spacing:.08em;color:var(--text-dim);}
.step p{margin-top:var(--s2);max-width:60ch;font-size:var(--fs-sm);line-height:1.6;color:var(--text);}

.copychip{
  display:inline-flex;align-items:center;gap:var(--s3);margin-top:var(--s3);
  min-height:36px;padding:var(--s1) var(--s2) var(--s1) var(--s3);
  border:1px solid var(--hairline-soft);
  background:var(--group-bg);border-radius:var(--r-md);cursor:pointer;
  font:var(--fs-num)/1.3 var(--mono);color:var(--text-strong);text-align:left;
  transition:background-color var(--t-fast) var(--ease);
}
.copychip:hover{background:var(--hover-bg);}
.copychip .act{font:600 var(--fs-micro)/1.3 var(--mono);text-transform:uppercase;letter-spacing:.08em;
               color:var(--text-dim);background:var(--chip);border-radius:var(--r-xs);padding:6px 8px;
               flex:none;transition:opacity var(--t-fast) var(--ease);}

/* schematic of Chrome's Developer-mode toggle — neutral, never cyan, never blue */
.toggle-demo{display:flex;align-items:center;gap:var(--s3);margin-top:var(--s3);flex-wrap:wrap;
             border:1px solid var(--hairline-soft);background:var(--group-bg);
             border-radius:var(--r-md);padding:10px var(--s3);width:max-content;max-width:100%;}
.tg{width:34px;height:18px;border-radius:9px;background:var(--chip-hover);position:relative;flex:none;}
.tg::after{content:"";position:absolute;top:2px;right:2px;width:14px;height:14px;border-radius:50%;background:var(--text-strong);}
.toggle-demo .lab{font:13px/1.3 var(--sans);color:var(--text-strong);}


.stuck{margin-top:var(--s7);font-size:var(--fs-sm);line-height:1.6;color:var(--text);}
.offstore{margin-top:var(--s8);padding-top:var(--s7);border-top:1px solid var(--hairline);max-width:64ch;}
.offstore h3{font:600 18px/1.3 var(--sans);color:var(--text-strong);}
.offstore p{margin-top:var(--s3);font-size:var(--fs-sm);line-height:1.6;color:var(--text-dim);}

.buy{margin-top:var(--s8);padding-top:var(--s7);border-top:1px solid var(--hairline);}
/* BOTH BUTTONS, ALWAYS. 2026-08-26 — the browser question was removed and each block
   carries its own steps, so there is nothing to select between and nothing to hide.
   No :has() rule governs a download button any more, which retires the hazard the
   @supports fallback at the foot of this file was written to catch. */
.buy-block{display:block;}
.buy-block + .buy-block{margin-top:var(--s9);padding-top:var(--s7);border-top:1px solid var(--hairline);}
/* BROWSER-AWARE, NEVER A GATE. HANDOFF-PLAN §2.5, 2026-08-27. main.js block 2 sets ONE
   attribute, data-browser, on .buy. With JS off, or an unknown engine, the attribute is
   absent and both blocks render in full exactly as above. With it set, the visitor's own
   block stays first and full; the OTHER block collapses to its .handoff line. `order`
   reorders; `display` collapses; the DOM order never changes. Never display:none on a
   .buy-block itself — the handoff line lives inside it. */
.buy{display:flex;flex-direction:column;}
.buy-sub{order:3;}
.handoff{display:none;}                                   /* JS off, unknown: no handoff line */
.buy[data-browser="chrome"]  .buy-firefox,
.buy[data-browser="firefox"] .buy-chrome{order:2;}         /* the other browser goes second */
.buy[data-browser="chrome"]  .buy-firefox > .btn,
.buy[data-browser="chrome"]  .buy-firefox > .dl-meta,
.buy[data-browser="chrome"]  .buy-firefox > .steps,
.buy[data-browser="firefox"] .buy-chrome > .btn,
.buy[data-browser="firefox"] .buy-chrome > .dl-meta,
.buy[data-browser="firefox"] .buy-chrome > .steps{display:none;}
.buy[data-browser="chrome"]  .buy-firefox > .handoff,
.buy[data-browser="firefox"] .buy-chrome > .handoff{display:flex;gap:var(--s3);align-items:center;flex-wrap:wrap;}
.handoff > .copychip{margin-top:0;}                       /* the chip's own top margin would sit it 12px low in the row */
/* THE HAIRLINE keys on DOM order (`+`), and `order` does not change `:first-child`. So
   with data-browser set, every block carries the rule and the visually-first one gives
   it back. Four lines. */
.buy[data-browser] .buy-block{margin-top:var(--s9);padding-top:var(--s7);border-top:1px solid var(--hairline);}
.buy[data-browser="chrome"]  .buy-chrome,
.buy[data-browser="firefox"] .buy-firefox{margin-top:0;padding-top:0;border-top:0;}
.buy-block .steps{display:block;margin-top:var(--s6);}
.buy .btn{min-height:52px;}
.dl-meta{margin-top:var(--s3);font:var(--fs-num)/1.5 var(--mono);color:var(--text-dim);}
.buy-sub{margin-top:var(--s4);font-size:var(--fs-sm);line-height:1.6;color:var(--text-dim);}
.buy-other{margin-top:var(--s4);font-size:var(--fs-sm);}

/* ══ 7 · FAQ ══════════════════════════════════════════════ */
.faq{margin-top:var(--s8);border-top:1px solid var(--hairline);}
details{border-bottom:1px solid var(--hairline);}
summary{display:flex;align-items:center;gap:var(--s5);padding:var(--s5) 0;cursor:pointer;
        list-style:none;font:500 16px/1.4 var(--sans);color:var(--text);
        transition:color var(--t-fast) var(--ease);}
summary::-webkit-details-marker{display:none;}
summary:hover{color:var(--text-strong);}
details[open] summary{color:var(--text-strong);}
summary:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;border-radius:var(--r-xs);}
.pm{position:relative;margin-left:auto;width:12px;height:12px;flex:none;}
.pm::before,.pm::after{content:"";position:absolute;inset:0;margin:auto;background:var(--text-dim);}
.pm::before{width:12px;height:1px;}
.pm::after{width:1px;height:12px;transition:transform var(--t-fast) var(--ease);}
details[open] .pm::after{transform:scaleY(0);}
details > .ans{padding:0 0 var(--s5);max-width:68ch;font-size:15px;line-height:1.65;color:var(--text-dim);}

/* ══ 8 · FOOTER ═══════════════════════════════════════════ */
footer{border-top:1px solid var(--hairline);padding-block:var(--s8);}
.foot{display:flex;flex-wrap:wrap;gap:var(--s3) var(--s7);align-items:center;
      font:var(--fs-num)/1.6 var(--mono);color:var(--text-dim);}
.foot a{color:var(--text-dim);}
.foot .sp{margin-left:auto;}
.foot-built{margin-top:var(--s4);font:var(--fs-num)/1.6 var(--mono);color:var(--text-dim);}

/* ══ PRIVACY PAGE ═════════════════════════════════════════ */
/* the doc column is left-aligned inside the 1120 container, so its left edge
   lines up with the masthead wordmark exactly as the landing page's body does */
.doc{max-width:72ch;padding-block:var(--section);}
main.wrap{padding-block:0;}
.doc h1{font:600 clamp(28px,4vw,40px)/1.1 var(--sans);letter-spacing:-.02em;
        color:var(--text-strong);max-width:none;margin:0;text-align:left;}
.doc .eff{margin-top:var(--s4);font:var(--fs-num)/1.6 var(--mono);color:var(--text-dim);}
.doc .intro{margin-top:var(--s6);font-size:var(--fs-lede);line-height:1.55;color:var(--text);}
.doc section{margin-top:var(--s9);}
.doc h2{font:600 20px/1.3 var(--sans);letter-spacing:-.01em;}
.doc section p{margin-top:var(--s4);font-size:var(--fs-body);line-height:1.7;color:var(--text-dim);}
.doc .back{margin-top:var(--s9);padding-top:var(--s7);border-top:1px solid var(--hairline);
           font:var(--fs-num)/1.6 var(--mono);}

/* ══ RESPONSIVE — breakpoints 1000 / 720 / 560 / 420 ══════ */
/* ── feature rows go two-up, and every second row flips ────
   `order` on the grid children, so the DOM order (text, then visual) is
   untouched and the mobile stack below this breakpoint needs no rule at all. */
@media (min-width:900px){
  .feat{grid-template-columns:minmax(0,0.78fr) minmax(0,1fr);gap:var(--s8);}
  .feat-flip .feat-text{order:2;}
  .feat-flip .feat-visual{order:1;}
  /* `order` moves the boxes but NOT the track sizes: on a flipped row the visual
     landed in the 0.78fr track and the text in the 1fr one. MEASURED: the two
     stages came out 580px and 452px, so the two replicas resolved to --k 2.09 and
     1.56 and row 2's "same scale" caption was false. Flipping the TRACKS as well
     keeps the wide track under the visual in both rows. */
  .feat-flip{grid-template-columns:minmax(0,1fr) minmax(0,0.78fr);}
}
/* Below 900 the replica must still fit. --k drops so the group narrows with the
   column instead of forcing a horizontal scrollbar on the page.
   MEASURED at 390px viewport: --k:1.6 gives a 320px group inside a 350px column. */
@media (max-width:899px){
  .feat{padding-block:var(--s8);}
  .vwc-big{--k:min(2.2, (100cqi - 32px) / var(--kw));}
  .ctl-stage{padding:var(--s6) var(--s4);gap:var(--s5);min-height:200px;}
}
@media (max-width:559px){
  /* The cap rises rather than falls: --k is already container-derived, so the
     limit at 390px is the stage width (about 1.2), not this ceiling. A 260px
     stage around a 34px pill is mostly empty, so the box tightens too. */
  .vwc-big{--k:min(2.4, (100cqi - 20px) / var(--kw));}
  .ctl-stage{min-height:150px;padding:var(--s5) var(--s3);gap:var(--s4);}
  .vwc-big{gap:calc(6px * var(--k));padding:0 calc(6px * var(--k));}
  .ctl-tile{width:52px;height:30px;}
}
@media (min-width:1000px){
  .cap-item{padding:var(--s5) var(--s6) 0;}
  .cap-item:first-child{padding-left:0;}
  .cap-item:last-child{padding-right:0;}
  .cap-item:not(:first-child){border-top:0;margin-top:0;border-left:1px solid var(--hairline);}
}
@media (max-width:999px) and (min-width:560px){
  .cap-item{flex:1 1 45%;padding-right:var(--s6);}
  .cap-item:nth-child(2){border-top:0;margin-top:0;}
}
/* ── the pair stacks. One column, DOM order, no `order` property. ───────────
   The box widens to 2/1 rather than narrowing: two boxes and a connector now share
   the fold, so ART-DIRECTION §7's zoom-in crop would make the fold worse, not better.
   The connector's negative margin buys back 16px of it. HERO-V2-DESIGN §3, §7. */
@media (max-width:999px){
  .ba{grid-template-columns:1fr;row-gap:var(--s3);}
  .ba-lead-before,.bw-before,.ba-arrow,.ba-lead-after,.bw-after,.ba-cap-before,.ba-cap-after{grid-column:1;grid-row:auto;}
  .ba-lead{width:100%;max-width:640px;margin-inline:auto;}
  /* `width:100%` is load-bearing. An auto inline margin on a grid item cancels
     stretch alignment, so without it the panel shrink-wraps to the video's
     intrinsic width. MEASURED at 390x844: 300px panel, not 350px. */
  .bw{width:100%;max-width:640px;margin-inline:auto;}
  .ba-cap{width:100%;max-width:640px;margin-inline:auto;}
  .bw-screen{aspect-ratio:2/1;}
  .ba-arrow{padding-top:0;margin-block:calc(-1 * var(--s2));}
  .ba-arrow > span{width:var(--s7);height:var(--s7);flex-direction:column;}
  .ba-arrow > span::before{width:2px;height:16px;}
  .ba-arrow > span::after{border-left:6px solid transparent;border-right:6px solid transparent;
    border-top:8px solid currentColor;border-bottom:0;}
}
@media (max-width:719px){
  :root{--gutter:20px;}
  .masthead{height:56px;}
  .masthead .meta{display:none;}
  .hero{padding-top:var(--s5);}
  h1{max-width:none;}
  .lede{margin-top:var(--s3);}
  .cta-row{margin-top:var(--s5);}
  .cta-row .btn{width:100%;}
  .ba{margin-top:var(--s6);}
  .price{font-size:34px;}
  .price-row{padding:var(--s5);}
  .price-body{padding:0 var(--s5) var(--s5);}
  .recap{padding:var(--s4) var(--s5);}
  .timebadge{margin-left:0;flex:1 0 100%;width:max-content;max-width:100%;}
  .buy .btn{width:100%;}
  .foot .sp{margin-left:0;flex:1 0 100%;}
}
@media (max-width:459px){
  thead th,tbody td,.grp th,tfoot td{padding-left:var(--s3);padding-right:var(--s3);}
  thead th.b,.mark{width:52px;}
  thead th.b-ff,.mark-ff{width:96px;}
  .site{font-size:12px;}
  .seg-btn{min-width:60px;}
  .ask .seg{width:100%;}
  .ask .seg-btn{flex:1 1 0;min-width:0;}
  ol.rail-steps{padding-left:38px;}
  .node{left:-38px;width:24px;height:24px;font-size:12px;}
  ol.rail-steps::before{left:11px;}
  .nodetail{font-size:10px;letter-spacing:.03em;}
}
@media (max-width:419px){
  .bw-bar{padding-inline:var(--s3);gap:var(--s3);}
}

/* ══ MOTION POLICY (ART-DIRECTION §6) ═════════════════════
   Keep colour/opacity feedback. Remove every transform and animation. */
@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto;}
  *,*::before,*::after{animation:none !important;}
  .pm::after{transition:none !important;}
  /* The tile strips are decorative motion on a page that sells motion. The
     controls keep working; only the animation stops. A reduce-motion reader can
     still drag the slider and read the number it lands on. */
  .ctl-track{animation:none !important;}
  .vwc-slider::-webkit-slider-thumb{transition:none;}
  .vwc-slider:hover::-webkit-slider-thumb,
  .vwc-slider:active::-webkit-slider-thumb{transform:none;}
  .vwc-slider::-moz-range-thumb{transition:none;}
  .vwc-slider:hover::-moz-range-thumb,
  .vwc-slider:active::-moz-range-thumb{transform:none;}
  details[open] .pm::after{transform:scaleY(0);}
  .rm-note{display:block;}
}

/* THE :has() FALLBACK IS RETIRED, 2026-08-26. It existed because .steps and
   .buy-block shipped display:none and only :has() rules reversed them, so an engine
   without :has() showed no download button at all. The browser question is gone, both
   are visible in the base state, and no :has() rule governs either. If a conditional
   download button ever returns, restore this block WITH its ordering warning: it must
   be the LAST rule in the file, or a later rule of equal specificity silently wins and
   leaves the fallback inert. */
.tb-chrome, .tb-firefox{display:inline;}
