/* Clever Checker styles */

/* Self-hosted, subsetted, preloaded: see fonts/README.md for why and how. */
/* unicode-range must describe what the file ACTUALLY contains. Declare a range
   wider than the subset and the browser downloads the face for a character it
   doesn't have, then renders tofu instead of falling back to a system font that
   would have drawn it. These match static/fonts/README.md's subset commands. */
@font-face {
  font-family: 'Chivo';
  src: url('/static/fonts/chivo-latin.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-display: swap;
  /* Wide: this face renders the visitor's pasted text, not just our copy. */
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122;
}
@font-face {
  font-family: 'Chivo Mono';
  src: url('/static/fonts/chivomono-latin.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-display: swap;
  /* Tight: this face only ever renders our own labels, scores and prices. */
  unicode-range: U+0020-007E, U+00A0, U+00A3, U+00A9, U+00B7, U+00B1, U+00D7,
                 U+20AC, U+2013, U+2018-2019, U+201C-201D, U+2022, U+2026;
}

:root {
  /* LIGHT. Every value below was computed, not picked: see the ratios in the
     comments and the tests that recompute them against every surface.

     Ranked surfaces still, but ranked the way light themes rank: on dark, depth
     came from getting lighter; here the page is the grey one and the panels are
     white, with the lift coming from SHADOW rather than from tone. Note 3 at the
     top of this file still holds; only its mechanism flips. */
  --bg:        #fbfaf7;   /* Paper: the page */
  --surface-1: #ffffff;   /* panels */
  --surface-2: #ffffff;   /* raised: the instrument. Lifted by --shadow-lg */
  --surface-3: #f3f0e7;   /* insets, chips: paper pushed toward --line */

  --ink:       #0f1b2d;   /* Navy: 15.2:1 on the worst surface */
  --ink-2:     #3d4a5e;   /* secondary prose: 7.9:1 on the worst surface */
  /* Labels, axis, fine print. Chosen by computing the ratio against ALL FOUR
     surfaces, not by eye and not by what Lighthouse sampled: the dark theme's
     equivalent once hit 4.47:1 on --bg (which the audit caught) and 3.71:1 on
     --surface-3 (which it never looked at, so it would have shipped). 5.1:1
     everywhere here.
     This is not a score chase: `.fine` is the class carrying the billing fine
     print, and a disclosure nobody can read is not a disclosure. */
  --ink-3:     #5a6678;

  --line:      #e7e4da;   /* Line */
  --line-2:    #d8d2c2;
  --line-3:    #bcb5a2;

  /* Bulb Amber. The one rule that keeps this palette working: NAVY carries all
     the text and structure, and amber is reserved for the bulb, the CTAs, the
     result bar and the badges. That is what keeps it reading as GLOW rather than
     flattening into a brand colour, and it is why nothing here is a text tone.
     #f5a300 is 2.0:1 on paper, so copy never sits in it: it is a FILL and a
     GRAPHIC only, and it carries navy (8.3:1), never white (2.1:1). An accent
     that needs to be read is --ink; an accent that needs to glow is this. */
  /* PayPal's #ffc439, and that is the point: PayPal is the express lane every
     visitor falls back to, so the site's accent IS the default lane. Nothing has
     to change colour for the commonest case, which is what stops the CTA painting
     brand-amber and then flipping the moment detection lands.
     Lighter than the #f5a300 it replaces, so the fill/graphic-only rule below
     binds harder, not less: 1.6:1 on paper (was 2.0), and it carries navy at
     10.9:1 (was 8.3). Verified nothing sets `color: var(--amber)`. */
  --amber:      #ffc439;
  --on-amber:   #0f1b2d;   /* Navy on amber: 10.9:1 */
  --amber-tint: #fff4da;   /* badges, highlights. Navy on it is 15.8:1 */
  /* Readable gold: the amber accent darkened until it can carry TEXT. --amber is a
     fill/graphic only (1.6:1 on paper); this is the same hue held to 5.1:1 on --bg,
     so titles, copy and checkmarks can wear the accent without failing contrast.
     Used by the pricing page's accented type; still not a substitute for --amber
     on fills. */
  --accent-ink: #916000;

  /* THE CTA LANE. Only the critical-path controls (Try, Select, the pay block, each
     checker's Analyse submit) wear these. Deliberately NOT an override of --amber:
     that would drag the dial, the plan flag, the feature checks and every other accent
     along with it, and none of those are on the path.
     Black, always. Every wallet button the path ends on is black now (Apple/Google Pay,
     and PayPal via its white-on-black wordmark), so there is nothing to detect and no
     colour to switch: the CTA the visitor follows already matches the button they press.
     This is why lane.js and the wallet-availability warm-up were removed. */
  --lane:     #000;
  --lane-ink: #fff;

  /* Every control someone is meant to press on the way to paying is this tall and
     this round: Try, Select, the express button, Start, and both login buttons. One
     height and one radius so the funnel reads as one set of controls rather than
     five that nearly match. 26px is exactly half the height, i.e. a true pill; it
     stops being one if the height changes without it.
     The express button is Stripe's, so it gets these same numbers passed to the
     Element as buttonHeight and the borderRadius appearance variable (checkout.html
     mirrors them as JS constants: Stripe cannot read a CSS custom property). */
  --cta-h: 52px;
  --cta-r: 26px;

  /* UI states only. Never used to characterise a score. Both are TEXT (.err,
     button.danger, .status.active), so both are held to 4.5:1 like any other
     text tone, which the dark theme's #ff6b6b and #3ecf8e would fail badly here
     (2.5:1 and 1.9:1 on white). */
  --warn: #b3261e;   /* 5.6:1 */
  --ok:   #12724a;   /* 5.1:1 */

  /* Depth, as tokens rather than a dozen hardcoded rgba(0,0,0,.9) shadows built
     for a black page. On light these have to be far softer or every panel looks
     like it is hovering an inch off the screen. */
  --shadow-sm: 0 1px 2px rgba(15, 27, 45, .05);
  --shadow-md: 0 10px 30px -18px rgba(15, 27, 45, .28);
  --shadow-lg: 0 18px 45px -20px rgba(15, 27, 45, .22);
  --shadow-pop: 0 24px 50px -12px rgba(15, 27, 45, .28), 0 0 0 1px rgba(15, 27, 45, .06);

  /* Two widths, because one was the whole desktop problem. --maxw is a reading
     measure for prose and must stay near 680px (a 1100px paragraph is unreadable
     at any screen size). --wide is the composition width the hero and the grids
     use, so a 27" monitor gets a laid-out page instead of a phone column floating
     in the middle of it. */
  --maxw: 680px;
  --wide: 1120px;
  --r-sm: 6px;
  --r:    10px;
  --r-lg: 16px;
  /* Chunky-button radius: sign-in and pay buttons and the fields beside them. */
  --r-xl: 22px;

  --sans: 'Chivo', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --mono: 'Chivo Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Chivo's figures are already equal-width, so a column of percentages doesn't
     jitter as the digits change. This asks for it explicitly anyway: it costs
     nothing and it's the safety net if the face is ever swapped for one whose
     defaults are proportional. tests/test_landing.py asserts the property (equal
     advance widths) rather than this flag, because the flag is not the point. */
  --fig: 'tnum' 1;
}

* { box-sizing: border-box; }

/* `hidden` MEANS hidden. The browser's own `[hidden] { display: none }` lives in
   the UA stylesheet, which loses to any author `display:` at all, so
   `.meter-note { display: flex }` quietly un-hid a <p hidden> and shipped an
   empty row with its amber bullet sitting under the readout on every single page
   load. `.dz-picked { display: block }` had the same hole waiting.
   This is the one place !important earns its keep: the rule has to beat every
   author display rule, including ones nobody has written yet. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 400 16px/1.6 var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Instrument texture: a hairline grid fading out under the fold. Pure gradient, no image request, no CLS, nothing on the LCP path. */
body::before {
  content: "";
  position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background-image:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 56px 56px;
  opacity: .5;
  mask-image: radial-gradient(ellipse 120% 55% at 50% -5%, #000 0%, transparent 72%);
}
body > * { position: relative; z-index: 1; }

a { color: var(--ink); text-decoration: underline;
    text-underline-offset: 2px; text-decoration-thickness: 1px;
    text-decoration-color: color-mix(in srgb, var(--ink) 45%, transparent); }
a:hover { text-decoration-color: var(--ink); }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 2px; }

main { max-width: var(--maxw); margin: 0 auto; padding: 4px 20px 8px; }

/* Every measured thing. */
.mono, .seg-pct, .price-fig, .eyebrow, code, .gauge-val, .axis, .meta,
.counter, .rangebar-label { font-family: var(--mono); font-feature-settings: var(--fig); }
/* A code chip never breaks across lines. MY-AI-CHECK is the string a cardholder
   compares against their statement, character for character, and one split over
   two lines is one they can't match (COMPLIANCE §4). The same goes for the "---"
   separator on /batch: a rule someone has to retype must survive being read. */
code { white-space: nowrap; }

/* Section label: mono, tracked, with a rule running off to the right. */
.eyebrow {
  font: 500 10.5px/1 var(--mono);
  letter-spacing: .18em; text-transform: uppercase; color: var(--ink);
  margin: 0 0 12px; display: flex; align-items: center; gap: 10px;
}
.eyebrow::after {
  content: ""; flex: 1; height: 1px;
  background: linear-gradient(90deg, var(--line-3), transparent);
}

/* ---------- header ---------- */
header.site {
  max-width: var(--wide); margin: 0 auto;
  padding: 18px 24px; display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
  /* Above <main>, or the language menu can't escape. `body > *` gives header and
     main the same z-index, main paints later, and the readout panel (its own
     stacking context, courtesy of backdrop-filter) drew straight over the open
     dropdown. The menu's own z-index can't fix that: it's trapped inside this
     element's stacking context, so the lift has to happen here. */
  position: relative; z-index: 40;
}
/* One image, mark and wordmark together (static/logo.svg). The lockup can no
   longer drift, because there is nothing left to drift: the wordmark is outlines,
   not text in the body font.
   The trade, stated plainly: an <img> cannot inherit currentColor, so the ink is
   BAKED, at --ink (#0d1017), and the logo is light-background only. When the site
   was dark this note said "regenerate, don't recolour with a filter" if a light
   surface ever needed it. It did, on 2026-07-17, and that is exactly what
   happened: `python tools/make_logo.py`, one constant. A CSS filter would have
   inverted the artwork instead of restating it. */
.brand { display: inline-flex; align-items: center; text-decoration: none; }
.brand:hover { text-decoration: none; }
/* Height drives it and width follows, so the aspect ratio comes from the file
   rather than from two numbers that can disagree with it.
   tools/make_logo.py COMPUTES the viewBox from the ink and rounds up, so the box
   height is dictated by that file: read it off the generated logo.svg, never guess
   it. The box is now 35 (was 30) because the mark was enlarged 10% and the wordmark
   was re-anchored to the bulb's foot. The px height keeps the OLD 33/30 = 1.1 ratio
   (35 x 1.1 = 38.5), so the wordmark renders at exactly the size it always has and
   only the mark grew. Change the box and these three restate: 38.5 x box/35. */
.brand .logo { display: block; height: 38.5px; width: auto; }
header nav { display: flex; gap: 14px; font-size: 13px; align-items: center; }

/* ---------- header lockup: tools drawer + brand ----------
   The corner tools button and the logo ride together in ONE flex group, so the
   header's space-between resolves to [ tools + brand | account + Try Pro ] at every
   width, the same on desktop and mobile. */
.head-lead { display: inline-flex; align-items: center; gap: 10px; min-width: 0; }

/* ---------- tools drawer ----------
   A LEFT-corner hamburger opening a full-height off-canvas panel, modelled on
   justdone.com and identical on desktop and mobile: one control, one behaviour, no
   second layout to keep in step. The old inline "All Checkers" dropdown (and its
   twin inside a right-hand mobile hamburger) are gone. A <details>, so it still
   opens with JS off; the slide and the dimmer are transform/opacity, the close is
   wired in base.html. */
.toolsnav { display: inline-flex; align-items: center; }
/* The corner button. Visible at EVERY width now (it was a phone-only control). Pulled
   a touch left so the bars sit in the corner inside the header's own padding. */
.burger {
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  list-style: none; width: 40px; height: 40px; margin: -8px 0 -8px -8px; border-radius: 9px;
  color: var(--ink-2); transition: color .15s ease, background .15s ease;
}
.burger::-webkit-details-marker { display: none; }
.burger:hover, .toolsnav[open] .burger { color: var(--ink); background: var(--surface-3); }
/* The panel must stay in the DOM while closed so it can SLIDE (a closed <details>
   would pop, not glide); we hide it with transform + visibility instead of letting
   Chrome's ::details-content collapse it. */
.toolsnav::details-content { content-visibility: visible; }

/* The dimmer behind the open drawer, and the tap target that closes it. Fixed to the
   viewport, under the panel, over the page. */
.drawer-backdrop {
  position: fixed; inset: 0; z-index: 60; background: rgba(15, 27, 45, .42);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .28s ease, visibility .28s ease;
}
.toolsnav[open] .drawer-backdrop { opacity: 1; visibility: visible; pointer-events: auto; }

/* The off-canvas panel: full height, slid in from the left edge. dvh so a phone's
   collapsing address bar cannot leave a gap at the foot. */
.drawer {
  position: fixed; top: 0; left: 0; z-index: 61;
  /* align-items: stretch and gap: 0 are RESTATED, not defaults: this is a <nav>
     inside <header>, so `header nav { align-items: center; gap: 14px }` matches it
     and would otherwise shrink the list to its content and centre it (insetting the
     active accent, and floating the labels with whitespace either side). */
  display: flex; flex-direction: column; align-items: stretch; gap: 0;
  width: 240px; max-width: 74vw; height: 100vh; height: 100dvh;
  padding: 14px 10px 18px; overflow-y: auto;
  background: var(--surface-2); border-right: 1px solid var(--line-3);
  box-shadow: var(--shadow-pop);
  transform: translateX(-100%); visibility: hidden;
  transition: transform .28s cubic-bezier(.4, 0, .2, 1), visibility .28s ease;
}
.toolsnav[open] .drawer { transform: none; visibility: visible; }
/* No page scroll behind the open drawer. Class set from JS (base.html) because
   :has() is not universal enough to gate a body rule on the open state. */
body.drawer-open { overflow: hidden; }
/* Reduced-motion: no slide, just appear. */
@media (prefers-reduced-motion: reduce) {
  .drawer, .drawer-backdrop { transition: none; }
}

.drawer-head {
  display: flex; align-items: center; justify-content: flex-end;
  margin: 2px 2px 8px;
}
.drawer-x {
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  width: 32px; height: 32px; margin: -6px -6px -6px 0; padding: 0;
  border: 0; border-radius: 8px; background: transparent; color: var(--ink-2);
  transition: color .15s ease, background .15s ease;
}
.drawer-x:hover { color: var(--ink); background: var(--surface-3); }

.drawer-list { display: flex; flex-direction: column; gap: 1px; }
.drawer-cat {
  font: 500 10px/1.5 var(--mono); letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-3); margin: 12px 8px 4px;
}
.drawer-cat:first-child { margin-top: 2px; }
/* One tool row: shared line icon (from _tool_icons.html) + label, on one line. */
a.drawer-item {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 10px; border-radius: 9px; text-decoration: none;
  color: var(--ink); font: 400 14.5px/1.2 var(--sans);
  border-left: 2px solid transparent;
}
a.drawer-item .tool-ic { flex: none; color: var(--ink-3); transition: color .15s ease; }
a.drawer-item span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
a.drawer-item:hover { background: var(--surface-3); text-decoration: none; }
a.drawer-item:hover .tool-ic { color: var(--ink); }
/* The current tool (on a tool or the home page): brand-amber tint and left rule,
   the label in full ink and heavier. Navy icon, not amber: an amber stroke on white
   is too pale to read as the marked row. aria-current carries it for a screen reader. */
a.drawer-item.is-active {
  background: var(--amber-tint); color: var(--ink); font-weight: 600;
  border-left-color: var(--amber);
}
a.drawer-item.is-active .tool-ic { color: var(--ink); }

/* "All checkers ->" foot link into the hub, set off by a hairline. */
a.drawer-all {
  display: block; margin: 12px 2px 0; padding: 12px 10px 2px;
  border-top: 1px solid var(--line);
  color: var(--ink-2); font: 500 13px/1 var(--mono); text-decoration: none;
}
a.drawer-all:hover { color: var(--ink); text-decoration: none; }
/* The "Add to <browser>" store badge at the foot of the menu (visitor's own browser).
   Left-aligned under the tools; the badge keeps its own pill treatment. Hidden while
   the desktop rail is collapsed to icons (restored in the min-width:900px block). */
.drawer-ext { margin: 12px 2px 2px; padding: 0 8px; }
.drawer-ext .storebadge { width: 100%; justify-content: flex-start; }

/* ---------- profile menu ---------- */
/* Personalised account chip: name/email beside the photo, capped so a long email
   cannot stretch the desktop bar. It shows in full inside its dropdown.
   A LINK now (to the profile), so it has to opt out of the nav's underline-on-hover
   and inherit the chip's colour: inside the summary it should read as one control,
   not as a link glued to an avatar. */
.acct-name { max-width: 128px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
header nav a.acct-name { color: inherit; text-decoration: none; }
header nav a.acct-name:hover { color: inherit; text-decoration: underline; }
/* The plan badge in the account chip: the tool Pro pill reused for Pro, greyed for
   the free tier so it reads as a state rather than a second call to upgrade. */
.acct-tier { flex: none; }
.pro-badge.free { color: var(--ink-3); background: var(--surface-3); border-color: var(--line-2); }
/* Signed-out summary: a generic profile glyph in a round hit area, the same size as
   the signed-in avatar so the corner does not jump when a visitor logs in. */
.acct-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 50%; color: var(--ink-2);
  border: 1px solid var(--line-3); transition: color .15s ease, border-color .15s ease;
}
.acctmenu[open] .acct-icon, .acct-chip:hover .acct-icon { color: var(--ink); border-color: var(--ink); }
/* The account chip's chevron: down when shut, up when open. */
.chev { flex: none; transition: transform .15s ease; }
.acctmenu[open] summary .chev { transform: rotate(180deg); }
/* The account dropdown panel: glass, hairline edge, anchored under the chip. */
.acctpanel {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 30;
  max-width: calc(100vw - 24px); max-height: 78vh; overflow-y: auto;
  border: 1px solid var(--line-3); border-radius: var(--r);
  background: color-mix(in srgb, var(--surface-2) 98%, transparent);
  backdrop-filter: blur(20px) saturate(1.3);
  -webkit-backdrop-filter: blur(20px) saturate(1.3);
  box-shadow: var(--shadow-pop);
}
.acctpanel a, .acctpanel .langpick > summary {
  display: block; padding: 8px 9px; border-radius: 6px; text-decoration: none;
  color: var(--ink); font: 400 14px/1.2 var(--sans);
}
.acctpanel a:hover { background: var(--surface-3); text-decoration: none; }
/* The Language switch as a menu row that expands the locale list INLINE, rather than
   the footer's popover: a second absolute panel anchored to the right edge would
   fall off-screen inside this already-corner-anchored menu. */
.acctpanel .langpick {
  display: block; position: static; width: 100%;
  border-top: 1px solid var(--line); margin-top: 4px; padding-top: 4px;
  /* And a matching hairline BELOW, so the language block (which expands into the
     locale list) is fenced off from Contact rather than running straight into it. */
  border-bottom: 1px solid var(--line); margin-bottom: 4px; padding-bottom: 4px;
}
.acctpanel .langpick > summary {
  display: flex; align-items: center; gap: 8px; cursor: pointer; color: var(--ink);
}
.acctpanel .langpick > summary:hover { background: var(--surface-3); }
.acctpanel .langpick > summary:hover span { text-decoration: none; }
.acctpanel .langpick > summary svg { flex: none; color: var(--ink-3); }
.acctpanel .langpick .langmenu {
  position: static; inset: auto; min-width: 0; width: 100%;
  margin: 2px 0 0; padding: 0; border: 0; background: none; box-shadow: none;
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
.acctpanel .langpick .langmenu-head { margin: 6px 9px 4px; }
.acctpanel .langopt { font-size: 13.5px; }
/* The "Legal and billing pages stay in English." note is dropped from the profile
   menu (it stays in the footer language control, where there is room for it). */
.acctpanel .langpick .langmenu-foot { display: none; }
/* Contact follows the language block, so the divider above Language also sets both
   off from the account rows above. */

/* The language control lives in the footer link row and is dressed as one of those
   links: no border, no pill, the nav's own font (inherited, so it cannot drift from
   the links either side of it), and the same ink and hover. It was a bordered pill,
   which on a centred row of its own read as a form control left in the footer. */
.langpick { position: relative; display: inline-flex; }
.langpick summary {
  display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
  list-style: none; color: var(--ink-2); font: inherit;
  transition: color .15s ease;
}
.langpick summary::-webkit-details-marker { display: none; }
.langpick summary:hover, .langpick[open] summary { color: var(--ink); }
/* Underline the WORD, not the summary: text-decoration set on a flex container does
   not reach its flex items, so a rule on the summary would underline nothing. */
.langpick summary:hover span { text-decoration: underline; }
.langmenu {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 30;
  min-width: 210px; padding: 8px;
  border: 1px solid var(--line-3); border-radius: var(--r);
  /* Near-opaque, not glass. The readout panel behind this one ALSO has a
     backdrop-filter, and nested backdrop-filters don't compose: the dial showed
     straight through the menu and the language names were unreadable. Glass is
     for the surfaces you look at, not the ones you read a list in. The blur is
     kept for the edge treatment where it lands over plain page. */
  background: color-mix(in srgb, var(--surface-2) 98%, transparent);
  backdrop-filter: blur(20px) saturate(1.3);
  -webkit-backdrop-filter: blur(20px) saturate(1.3);
  box-shadow: var(--shadow-pop);
}
/* .langmenu-foot ("Results are English-only for now") was removed on request,
   with its rules. .langmenu keeps its own 8px padding, so the menu still closes
   off cleanly under the last option without it. */
.langmenu-head {
  font: 500 10px/1.5 var(--mono); letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-3); margin: 4px 8px 8px;
}
.langopt {
  width: 100%; margin: 0; padding: 8px 9px; border: 0; border-radius: 6px;
  background: transparent; color: var(--ink); cursor: pointer;
  font: 400 13.5px/1 var(--sans); text-align: left;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.langopt:hover { background: var(--surface-3); filter: none; }
.langopt.is-current { color: var(--ink); }
.langopt .tick { color: var(--ink); flex: none; }
header nav a { color: var(--ink-2); text-decoration: none; }
header nav a:hover { color: var(--ink); text-decoration: underline; }
/* The prominent header CTA into the free funnel: a big filled accent pill, the one
   thing in the header asking for the click.
   Ordered FIRST in the nav (before Tools, Sign in and the account chip) so the ask
   leads the group rather than trailing it. Source order keeps it last, because on
   mobile the orders are restated wholesale in the 720px block below and the nav is
   read there as [chip] [Try Pro] [burger]. */
/* The CTA itself is NOT header-scoped: _cta.html renders the same control on a
   result page, and a rule that only fired inside `header nav` left it there as an
   unstyled link. Only the header's own placement stays scoped, below. */
/* The lane pill, ONE definition worn by the header Try button AND by the primary
   submit on every checker (.primary-cta). Both are the critical action on the page
   they sit on, both are the destination the star points at, so they share the exact
   skin rather than two near-copies drifting apart (DRY). A <button> does not inherit
   the body font, hence `font:` names the family explicitly. */
a.try, button.primary-cta {
  display: inline-flex; align-items: center; gap: 6px;
  /* Hug the label. Without this the global `button { width: 100% }` (styles.css) wins
     and the pill stretches full width the moment it is not inside a shrink-wrapping
     parent (it sits directly in the foot now), which is the "crazy full-width button". */
  width: auto;
  /* Was inherited from `header nav a` and lost the moment this control was used
     outside the header, which underlined the label inside a filled pill. */
  text-decoration: none;
  color: var(--lane-ink); background: var(--lane); border: 1px solid var(--lane);
  /* Height is fixed (--cta-h = 52px), so the padding here is horizontal only and the
     narrow-width rules below trim just that. Less on the left: the star already
     carries whitespace inside its box, so an even 18px each side left too big a
     gap before it. */
  height: var(--cta-h); padding: 0 18px 0 13px;
  border-radius: var(--cta-r);
  font: 700 14.5px/1 var(--sans);
  letter-spacing: -.01em; white-space: nowrap;
}
/* Hover highlight for BOTH lanes. Two bugs fixed here: `header nav a:hover` sets
   color:--ink and an underline, which on the black lane turned the white label dark
   (invisible) and underlined it, so the color and text-decoration are pinned back to
   the lane's own. And brightness() cannot lighten pure black, so the amber lane
   highlighted on hover and the black one did nothing: a translucent white inset
   overlay lightens ANY lane (black -> charcoal, amber -> lighter amber) uniformly. */
a.try:hover, button.primary-cta:hover {
  text-decoration: none; color: var(--lane-ink);
  box-shadow: inset 0 0 0 999px rgba(255, 255, 255, .14), var(--shadow-sm);
}
/* THE SWIPE. A soft light band sweeps across the black path CTAs (the header/pay Try,
   every primary-cta, and checkout's Start) once every 2s, tying them together as one
   clickable family now that they are all the same black. It is a ::after gradient whose
   background-position animates, so the band stays inside the button's own rounded box
   (border-radius: inherit) with no overflow clip on the button, and it never blocks a
   click (pointer-events: none). The prefers-reduced-motion reset below stops it. */
a.try, button.primary-cta, .co-start { position: relative; }
a.try::after, button.primary-cta::after, .co-start::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: linear-gradient(100deg, transparent 42%, rgba(255, 255, 255, .28) 50%, transparent 58%);
  background-size: 220% 100%; background-repeat: no-repeat;
  animation: ctaSwipe 2s ease-in-out infinite;
}
@keyframes ctaSwipe { 0% { background-position: 220% 0; } 40%, 100% { background-position: -220% 0; } }
/* Only the extras a <button> needs on top of the shared skin. */
button.primary-cta:disabled { filter: none; opacity: .55; cursor: default; }
/* The Pro pill on a gated tool's submit: an outline in the button's own ink, so it
   reads on the lane colour whether that is black or amber (same reasoning as the
   pill inside the old inline button). */
.primary-cta .pro-badge { background: transparent; color: inherit; border-color: currentColor; }
/* Header only: lead the nav group rather than trailing it (see the note above). */
header nav a.try { order: -1; }
/* The critical-path star (cta_star in web.py), worn by Try, Select and Start and by
   nothing else. ONE rule for all three, so the mark someone follows from the header
   to the payment form is the same size and the same shape at every step.
   Sized in em, because the three controls are three different font sizes and the
   star has to stay in proportion to the word it leads, not to the last button.
   It takes NO colour of its own: fill="currentColor" inside a control whose color
   is --lane-ink, so it is white on the black lane and black on the yellow one
   without a second rule to keep in step. The hollow cut (.cta-star-open, on the
   alternative plans) strokes the same currentColor for the same reason.
   .9em, not .8em: both cuts share a box padded to stop the stroke clipping, so the
   size is scaled up to land the solid star back on .8em of actual ink. */
.cta-star, .cta-star-open { flex: none; width: .9em; height: .9em; }
/* Hollow AND dimmed, wherever it appears: the alternative plans, and Start, which is
   the alternative to the express button on the same principle. An outline at full ink
   still reads as emphasis; at 55% it settles back to "available". Opacity rather than
   a grey, which would need one value on white and another on any lane colour. */
.cta-star-open { opacity: .55; }

/* The lane is black for everyone now (see the --lane default): every wallet button the
   path ends on is black, so there is no per-visitor colour to switch and no data-express
   attribute to set. The old [data-express="black"|"paypal"] overrides and lane.js are
   gone with the wallet detection they existed to drive. */
/* The control is in the footer, so its menu opens UPWARD: dropping it downward would
   put it off the bottom of the page. It keeps the base rule's right: 0, which anchors
   it to the summary's right edge; the summary is the last item of its row, so the
   210px panel always opens back over the row rather than off the right of a 320px
   screen (which centring it on the summary did). */
footer.site .langmenu { top: auto; bottom: calc(100% + 8px); }
/* The profile menu, top right, present signed in OR out: the chip (a photo/initial
   avatar plus name when signed in, a profile glyph when signed out) and the menu it
   opens (account rows, then Language and Contact). Same disclosure pattern as the
   old tools menu, so it opens with no script; the hover-open in base.html is an
   enhancement on top. Ordered LAST so it sits in the corner, right of the CTA. */
.acctmenu { position: relative; display: inline-flex; order: 3; }
.acct-chip {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  list-style: none; color: var(--ink-2);
}
.acct-chip::-webkit-details-marker { display: none; }
.acct-chip:hover, .acctmenu[open] .acct-chip { color: var(--ink); }
/* Wide enough for the longest row it holds ("Iniciar sesión / Registrarse" on the
   Spanish signed-out menu), still a menu and not a catalogue. */
.acctpanel { width: 224px; padding: 6px; }
/* The 8px between the chip and the panel is a DEAD ZONE: it belongs to the header,
   not to the <details>, so a mouse travelling from the chip down to "Sign out"
   left .acctmenu on the way and the hover-close fired mid-journey. The menu was
   unusable with a pointer even though it was fine on tap.
   .acct-bridge is an invisible strip covering exactly that gap, and it is a real
   child of the menu, so crossing it is not leaving. It lives INSIDE the <details>,
   so it exists only while the menu is open and never eats clicks aimed at the
   header behind it. A ::before on the panel was tried first and does not work:
   Chrome does not hit-test a generated box for pointer-enter/leave, so the menu
   still closed mid-journey (measured, not assumed).
   Paired with the close DELAY in base.html: the strip fixes the straight path
   down, the delay forgives the diagonal one. */
.acct-bridge {
  position: absolute; top: 100%; right: 0; height: 10px; width: 224px;
}
.acct-photo, .acct-avatar {
  width: 24px; height: 24px; border-radius: 50%; object-fit: cover; flex: none;
}
.acct-avatar {
  display: grid; place-items: center; background: var(--ink); color: #fff;
  font-size: 12px; font-weight: 700;
}
/* Cancel is prominent in the header by rule, not by taste (COMPLIANCE §1.4).
   nowrap because a pill that breaks into two lines stops looking like a control
   and starts looking like an accident, and this is the control the whole
   easy-cancel promise rests on. */
header nav a.cancel {
  color: var(--ink); border: 1px solid var(--line-3);
  padding: 6px 11px; border-radius: 999px; font-size: 12.5px;
  white-space: nowrap;
}
header nav a.cancel:hover { border-color: var(--ink); text-decoration: none; }
/* Batch is the paid product and this is its only prominent route in. Filled, not
   outlined: it is the one nav item asking for something. It sits BEFORE Cancel and
   is quieter than nothing else in the header, because Cancel outranking the upsell
   is the point of COMPLIANCE §1.4, not an accident to correct. */
header nav a.batch-cta {
  color: var(--on-amber); background: var(--amber); border: 1px solid var(--amber);
  padding: 6px 11px; border-radius: 999px; font-size: 12.5px;
  white-space: nowrap; font-weight: 600;
}
header nav a.batch-cta:hover { text-decoration: none; filter: brightness(1.12); }

/* ---------- hero ---------- */
/* Mobile first: one column, copy then readout. The desktop composition is at the
   bottom of this file. */
.hero { padding: 14px 0 0; display: grid; gap: 20px; }
/* One line at 320-340px is a hard rule (GOAL §6), which is why the H1 is short
   enough to look blunt. It's also the exact-match keyword, which buys the
   ad-relevance half of Quality Score. Both constraints want the same words. */
.hero h1 {
  font-size: clamp(34px, 10.5vw, 46px);
  line-height: 1.02; letter-spacing: -.045em; font-weight: 900;
  margin: 0 0 14px; text-wrap: balance;
}
.hero .sub {
  color: var(--ink-2); margin: 0 0 24px; font-size: 16.5px; max-width: 44ch;
}
.hero .sub strong { color: var(--ink); font-weight: 600; }

/* ---------- the specimen (input) ---------- */
/* A bay you place something in, not a form field: a labelled rail, a live count,
   and the action inside the panel. Glass rather than a flat box, with the blur
   picking up the instrument grid behind it so the panel sits IN the page instead
   of on top of it. backdrop-filter degrades to the flat background where it isn't
   supported, which is a cosmetic loss and nothing more. */
.specimen {
  border: 1px solid var(--line-2); border-radius: var(--r-lg);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--surface-2) 70%, transparent),
    color-mix(in srgb, var(--surface-1) 82%, transparent));
  backdrop-filter: blur(18px) saturate(1.25);
  -webkit-backdrop-filter: blur(18px) saturate(1.25);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  transition: border-color .18s ease, box-shadow .18s ease;
}
.specimen:focus-within {
  border-color: var(--line-3);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ink) 14%, transparent),
              var(--shadow-md);
}
.specimen-rail, .specimen-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 9px 12px 9px 14px;
}
.specimen-rail { border-bottom: 1px solid var(--line); }
/* The foot can carry four things (Upload, Paste, the example link, the pill). When
   they do not fit on one row it wraps as WHOLE units, dropping the pill to its own
   line, rather than breaking "Try an example" across two lines. row-gap keeps the
   wrapped rows apart; the base `gap: 10px` already supplies it, named here for clarity. */
.specimen-foot { border-top: 1px solid var(--line); flex-wrap: wrap; row-gap: 10px; }
/* Upload/Paste/Example sit left (.foot-actions, margin-right:auto); the submit pill is
   the only other child, pinned right and kept there when it wraps to its own row on a
   narrow column. */
/* The Clear button + submit pill, held together at the RIGHT of the foot (the option
   selects, when present, stay on the left). Clear sits just left of the pill and shows
   only while there is text: `:has` reaches from the panel down to it, since the button
   lives in the foot, a different parent from the textarea whose emptiness governs it.
   Clearing brings the empty state (placeholder + Upload/Paste/Example) back. */
.foot-run { display: inline-flex; align-items: center; gap: 10px; margin-left: auto; }
/* Hidden by default. `.foot-btn.clear-btn` (two classes) out-specifies the `.foot-btn`
   display, which otherwise wins on source order and keeps Clear always shown. The
   :has rule (an id inside) out-specifies this in turn to reveal it when text is present. */
.foot-btn.clear-btn { display: none; }
.specimen:has(#content:not(:placeholder-shown)) .clear-btn { display: inline-flex; }
.specimen-rail .label {
  font: 500 10px/1 var(--mono); letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-3);
}
/* The label and its (i) note travel together at the left of the rail, so the tip
   sits ON the label rather than floating to the centre under space-between. */
.rail-lead { display: inline-flex; align-items: center; gap: 6px; }
/* THE instant tooltip, ONE mechanism sitewide (the collapsed tools rail uses the
   same look). Native `title` waits ~1s; this shows the hint the instant the control
   is hovered or focused, no transition and no delay. The text rides on data-tip.
   Opens ABOVE by default, which is right for a control at the BOTTOM of a panel that
   clips its overflow (the foot's Upload/Paste); `.tip-below` opens under it, for a
   control at the TOP where opening up would be clipped (the rail's (i)). Put `.tip`
   plus `data-tip="..."` on any control that needs a hover hint. */
.tip { position: relative; }
.tip[data-tip]:hover::after, .tip[data-tip]:focus-visible::after {
  content: attr(data-tip);
  position: absolute; z-index: 70; left: 0; bottom: calc(100% + 8px);
  width: max-content; max-width: 240px;
  padding: 6px 10px; border-radius: 8px;
  background: var(--ink); color: var(--surface-2);
  font: 500 12px/1.35 var(--sans); letter-spacing: 0; text-transform: none;
  white-space: normal; text-align: left; pointer-events: none;
  box-shadow: var(--shadow-pop);
}
.tip-below[data-tip]:hover::after, .tip-below[data-tip]:focus-visible::after {
  bottom: auto; top: calc(100% + 8px);
}
/* The (i) privacy note on the input rail: a small round icon button that IS a
   .tip.tip-below (see _specimen.html rail_lead). The UPPERCASE 10px mono label sits low
   in its line-box (empty ascender space above the caps), so align-items:center leaves
   the dot sitting BELOW the caps. Nudge it UP onto the caps' optical centre. One rule,
   shared by both input boxes. */
.info-dot {
  display: inline-flex; align-items: center; justify-content: center; line-height: 0;
  width: 15px; height: 15px; padding: 0; border: 0; background: none; color: var(--ink-3);
  /* MEASURED, not eyeballed: the rail's inherited line-height leaves the flex row taller
     than the controls, so align-items:center drops the 15px dot ~6px below the 10px mono
     caps. This lifts its centre exactly onto theirs (verified: the svg mid and the caps
     mid coincide to 0.0px). Retune only against the rail's real geometry, never by feel. */
  transform: translateY(-6px);
  transition: color .15s ease;
}
.info-dot:hover, .info-dot:focus-visible { color: var(--ink); }
/* Upload and Paste: the two ways to fill the box, bottom-left on every checker.
   Quiet bordered controls with a leading icon, so they read as controls without
   competing with the primary star pill on the right. `margin-right: auto` on the
   group is what pushes "Try an example" and the submit to the far side. */
.foot-actions { display: inline-flex; align-items: center; gap: 8px; margin-right: auto; }
.foot-btn {
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  padding: 7px 11px; border-radius: 8px; text-decoration: none;
  background: var(--surface-1); color: var(--ink-2); border: 1px solid var(--line-2);
  font: 600 12.5px/1 var(--sans);
  transition: color .18s ease, border-color .18s ease, box-shadow .18s ease;
}
.foot-btn:hover { color: var(--ink); border-color: var(--line-3); box-shadow: var(--shadow-sm); }
.foot-btn svg { flex: none; }
/* The run cluster: the example link and the submit pill, pinned to the RIGHT of the
   foot. margin-left:auto holds it there even when it wraps to its own line under
   Upload/Paste on a narrow column, so the pill is always bottom-right of the box. */
/* A <button> that reads as a text link: the site's <a> underline on a real control,
   so the auth resend keeps its form semantics without a slab. Defined HERE, in the
   always-loaded sheet, rather than tools.css, so any page can use it (the checker
   foot no longer does: its example is a foot-btn now, but auth pages still are). */
.linkish {
  width: auto; margin: 0; padding: 0; border: 0; background: none;
  color: var(--ink); font: inherit; cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, var(--ink) 45%, transparent);
}
.linkish:hover { filter: none; text-decoration-color: var(--ink); }
.linkish:active { transform: none; }
.counter { font-size: 11.5px; color: var(--ink-3); transition: color .15s ease; }
/* Push the word count (and the "More" upsell right after it) to the right of the rail,
   so the rail reads [TEXT (i) ....... 55 words More] instead of leaving the count
   marooned in the centre under space-between. */
.specimen-rail .counter { margin-left: auto; }
.counter.ready { color: var(--ink); }
/* The Pro marker on a gated tool. amber-tint is the documented badge/highlight
   tone (navy on it is 15.8:1) and is deliberately NOT the lane amber, which only
   wallet controls wear. One line, one word: it is a plan name, not a sentence, so
   it holds at 320px in every locale untouched. */
.pro-badge {
  font: 600 9.5px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase;
  color: var(--on-amber); background: var(--amber-tint);
  border: 1px solid #f0dfae; border-radius: 999px; padding: 3px 7px;
}
/* On the input rail it packs beside the Text label and pushes the counter to the
   far right, so the rail stays label | badge ......... counter, not a third even
   column. Scoped here so the same badge inside the rewrite CTA is a plain inline
   pill, not a spacer. */
.specimen-rail .pro-badge { margin-right: auto; }
/* Inside the submit button the Pro pill has to read on both button states: the
   resting surface and the amber it turns when ready. The amber-tint fill would
   vanish on the amber button, so in here the pill is an outline in the button's
   own text colour, which contrasts in either state. */
button.inline-cta .pro-badge {
  background: transparent; color: inherit; border-color: currentColor;
}

/* The rewrite hero on a high detector score. A bordered panel in the badge tone,
   stacked so each line keeps the one-line rule in the narrow readout column. The
   Pro pill vanishes for a visitor who already pays (selling Pro to a subscriber
   reads as having lost their subscription). Not the lane amber: this is a tool
   link, not a wallet control. */
/* The AI-rewrite offer's eyebrow. The pill it sits over reuses button.primary-cta,
   so it wears the exact header-Try skin (lane colour + the critical-path star) with
   no second copy of the treatment. Layout (centering, margins) is with the readout
   modal styles below; this is just the small mono label. */
.rewrite-lead {
  display: block; font: 500 10px/1 var(--mono); letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-3); margin-bottom: 8px;
}
/* ALWAYS visible, and that is the point of denominating the counter in words: the
   counter states the allowance ("12 / 2,000 words") and this is the answer to the
   question it raises. Revealing it only once someone is already over means the
   upgrade appears exclusively to people who have hit a wall, which is the one
   moment it reads as a toll rather than an offer. `over` still amber-flags it, so
   it gains emphasis at the relevant moment instead of appearing from nowhere. */
.counter-more { font-size: 11.5px; margin-left: 8px; color: var(--ink-3); }
.counter-more:hover { color: var(--ink); }
.counter.over ~ .counter-more { color: var(--ink); font-weight: 600; }
textarea {
  display: block; width: 100%; background: transparent; color: var(--ink);
  border: 0; padding: 15px 14px; font: 400 15.5px/1.65 var(--sans);
  resize: vertical; min-height: 168px;
}
textarea:focus { outline: none; }
textarea::placeholder { color: #626c80; }

/* The empty-state overlay INSIDE the input box: the placeholder on line one, the
   Upload / Paste / Example buttons on line two. It shows only while the box is empty
   and disappears the instant there is text, with NO script: the textarea keeps its
   placeholder (rendered transparent, so the visible one is the overlay's) purely so
   `:placeholder-shown` can track emptiness, and the sibling rule below hides the
   overlay when the placeholder is not shown. Clearing the box brings it back. */
.specimen-input { position: relative; }
/* The real placeholder is transparent; the overlay draws the visible one aligned to
   it. Scoped to the overlayed boxes so other textareas (batch) keep their own. */
.specimen-input textarea::placeholder { color: transparent; }
.input-overlay {
  position: absolute; inset: 0; padding: 15px 14px;
  display: flex; flex-direction: column; align-items: flex-start; gap: 12px;
  /* Clicks fall through to the textarea to type; only the buttons take pointer events. */
  pointer-events: none;
}
/* Line one: the visible placeholder, matched to the textarea's own type and inset so
   it sits exactly where typed text begins. */
.input-ph { margin: 0; color: #626c80; font: 400 15.5px/1.65 var(--sans); }
.input-overlay .foot-actions { pointer-events: auto; margin: 0; }
/* Text present -> the overlay (placeholder AND buttons) is gone. Comes back on clear. */
.specimen-input textarea:not(:placeholder-shown) ~ .input-overlay { display: none; }

/* The inline action. A real <button> for keyboard and screen readers, styled as
   a control rather than a slab.
   It reads as a BUTTON from the first paint: bordered, on its own surface. The
   old resting state was transparent with no border and no hover response, which
   looked like a text link that had lost its underline, so nothing about it said
   "click" until enough text turned it gold. Colour is now the READY signal on top
   of a control that always looked like one, instead of the only thing making it a
   control at all. */
button.inline-cta {
  width: auto; margin: 0; flex: none;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 12px; border-radius: 8px;
  background: var(--surface-1); color: var(--ink);
  border: 1px solid var(--line-2);
  font: 600 13.5px/1 var(--sans);
  transition: background .18s ease, color .18s ease, border-color .18s ease,
              box-shadow .18s ease;
}
button.inline-cta:hover { border-color: var(--line-3); box-shadow: var(--shadow-sm); }
button.inline-cta svg { transition: transform .18s ease; }
button.inline-cta:hover { filter: none; }
/* :not(.ghost) is load-bearing. `ready` means "there is enough text to analyse",
   which is a statement about the ANALYSE button and nothing else. Without this,
   the upload gate sitting in the same panel lit up amber at 40 characters too,
   as though typing had enabled it: it is a paid feature and typing does not buy
   it. The state belongs to the action it describes. */
.specimen.ready button.inline-cta:not(.ghost) {
  background: var(--amber); color: var(--on-amber); border-color: var(--amber);
}
.specimen.ready button.inline-cta:not(.ghost):hover svg { transform: translateX(2px); }
/* HOUSE RULE: every link and button shows the hand, always. A control that looks
   clickable under an arrow cursor reads as broken, and "not enough text yet" is a
   state to say in colour and copy, never in the cursor. This is where that rule
   used to be broken: the Analyse button carried `cursor: default` below the
   minimum length, which is exactly the moment someone is trying to work out
   whether the thing is alive. */
a, button, summary, [role="button"], .linkish { cursor: pointer; }

input[type=email], input[type=text] {
  width: 100%; background: var(--surface-1); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--r);
  padding: 13px 14px; font: inherit;
}
label { display: block; margin-top: 14px; font-size: 14px; color: var(--ink-2); }

/* ---------- buttons ---------- */
button {
  width: 100%; margin-top: 12px; padding: 15px 18px;
  font: 700 15.5px/1 var(--sans); letter-spacing: -.01em;
  color: var(--on-amber); background: var(--amber); border: 0;
  border-radius: var(--r); cursor: pointer;
  transition: filter .12s ease, transform .08s ease;
}
button:hover { filter: brightness(1.07); }
button:active { transform: translateY(1px); }
button.ghost {
  background: transparent; color: var(--ink); border: 1px solid var(--line-3);
}
button.danger { background: transparent; color: var(--warn); border: 1px solid var(--warn); }
.err { color: var(--warn); margin-top: 10px; font-size: 14.5px; }
/* The Turnstile challenge is interaction-only (see _turnstile.html): it is
   invisible until Cloudflare wants a human, then a fixed-width box appears. Left
   aligned it hangs off the corner of the specimen card. Centring the flex line
   puts it under the middle of the text input on desktop, and under the middle of
   the page on mobile, since the form is the page width there. */
.cf-turnstile { margin-top: 12px; display: flex; justify-content: center; }

/* ---------- generic panels (report/account/legal/cancel) ---------- */
.report, .account, .legal, .cancel-page {
  background: var(--surface-1); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 22px; margin-top: 20px;
}
.legal h1, .report h1, .account h1, .cancel-page h1 {
  font-size: 27px; line-height: 1.15; letter-spacing: -.035em;
  margin: 0 0 10px; font-weight: 800; text-wrap: balance;
}
.legal .sub, .report .sub, .cancel-page .sub { color: var(--ink-2); margin: 0 0 18px; }

/* ---------- the readout (teaser) ---------- */
/* The raised surface: this is the instrument, and it should sit above the page
   rather than beside it. */
.teaser {
  border: 1px solid var(--line-2); border-radius: var(--r-lg);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--surface-2) 80%, transparent),
    color-mix(in srgb, var(--surface-1) 90%, transparent));
  backdrop-filter: blur(18px) saturate(1.25);
  -webkit-backdrop-filter: blur(18px) saturate(1.25);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
/* Only the FILLED readout animates in. The idle one is there from first paint,
   so animating it would be motion for its own sake and a CLS risk. */
.teaser:not(.is-idle) { animation: rise .3s cubic-bezier(.2,.7,.3,1); }
@keyframes rise { from { opacity: .6; transform: translateY(6px); } }

/* ---------- the idle readout ---------- */
/* Visible before anything is submitted, so a cold visitor can see what they're
   about to get instead of a form and a wall of text. It is EMPTY, never fake:
   every value is a dash, the dial reads "··" rather than 0, and nothing here is
   a number we did not produce. A greyed-out placeholder score would still be a
   fabricated result. */
/* The idle panel is NOT dimmed with `opacity` any more, and the reason is worth
   keeping: opacity on a CONTAINER quietly re-computes the contrast of every
   glyph inside it, and no palette test can see that, because the token is still
   correct and only the composite is wrong. `.teaser.is-idle { opacity: .92 }`
   rendered --ink-3 (#5b6474) as #676f7e over the page, which is 4.34:1 on this
   surface: a WCAG failure Lighthouse caught and every colour test here passed
   straight over. On the dark theme the same 8% happened to blend the other way
   and cost nothing, which is why it survived this long.
   The idle state is carried by the DIAL instead, which is a graphic and owns its
   own dimming, and by the copy. Dim the thing you mean to dim. */
.teaser.is-idle .dial { opacity: .5; }
.teaser.is-idle .gauge-val { fill: var(--ink-3); }
.idle-note {
  margin: 0; padding: 0 16px 18px; text-align: center;
  font-size: 14px; color: var(--ink-3); line-height: 1.55; text-wrap: balance;
}
.teaser:not(.is-idle) .idle-note { display: none; }
.segments li.skeleton .seg-bar { background: var(--line); }
.segments li.skeleton .seg-pct,
.segments li.skeleton .seg-i { color: var(--line-3); }
.segments li.skeleton .seg-text {
  height: 8px; border-radius: 3px; background: var(--line);
  align-self: center; opacity: .8;
}
.segments li.skeleton:nth-child(2) .seg-text { width: 76%; }
.segments li.skeleton:nth-child(3) .seg-text { width: 54%; }
.teaser:not(.is-idle) .segments li.skeleton { display: none; }

.readout-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 14px; border-bottom: 1px solid var(--line);
  background: var(--surface-3);
}
.readout-head .label {
  font: 500 10px/1 var(--mono); letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-3);
}
.meta { font-size: 11px; color: var(--ink-3); }

.dial-wrap { padding: 18px 14px 4px; display: grid; place-items: center; }
.dial { width: 100%; max-width: 260px; height: auto; display: block; }
/* The arc is one hue at every score. A dial that greens at 5% and reds at 95%
   has delivered a verdict without writing one. */
.dial .track { stroke: var(--line-2); }
/* The arc is the biggest accent shape on the page. It carries no text and the score
   is printed in full at its centre, so it rides the bright graphic gold. The sweep is
   slow (tuned to the number's count-up in app.js) so the score reads as arriving. */
.dial .value { stroke: var(--amber); transition: stroke-dashoffset 1.4s cubic-bezier(.2,.7,.3,1); }
/* The whole instrument eases in when the readout opens: a gentle rise-and-settle so
   the dial itself arrives, then the arc sweeps and the number counts up within it. */
.readout-modal[open] .dial { animation: dialIn .55s cubic-bezier(.2, .7, .3, 1) both; }
@keyframes dialIn { from { opacity: 0; transform: translateY(8px) scale(.94); } }
@media (prefers-reduced-motion: reduce) {
  .readout-modal[open] .dial { animation: none; }
  .dial .value { transition: none; }
}
.dial .tick  { stroke: var(--line-3); }
.dial .tick.major { stroke: var(--ink-3); }
.dial .axis  { fill: var(--ink-3); font-size: 9px; letter-spacing: .06em; }
.gauge-val {
  fill: var(--ink); font-size: 38px; font-weight: 700; letter-spacing: -.04em;
}
.gauge-pct { fill: var(--ink-3); font-size: 15px; font-weight: 500; }
.gauge-cap {
  fill: var(--ink-3); font-size: 8px; letter-spacing: .16em; font-weight: 500;
}

.verdict {
  font-size: 17px; font-weight: 600; text-align: center; margin: 2px 0 4px;
  letter-spacing: -.02em; line-height: 1.35; text-wrap: balance;
  padding: 0 14px;
}
.hook { color: var(--ink-2); text-align: center; margin: 0; font-size: 14.5px;
        padding: 0 14px 16px; }

/* Real spread, from the real sentence distribution, never an invented ±. */
.rangebar {
  margin: 0 14px 16px; padding: 11px 12px; border-radius: var(--r-sm);
  background: var(--surface-3); border: 1px solid var(--line);
}
.rangebar-label {
  font-size: 10px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 8px; display: flex;
  justify-content: space-between; gap: 8px;
}
.rangebar-track {
  position: relative; height: 4px; border-radius: 2px; background: var(--line-2);
}
.rangebar-fill {
  position: absolute; top: 0; height: 4px; border-radius: 2px;
  background: linear-gradient(90deg, var(--amber-tint), var(--amber));
  transition: width .8s cubic-bezier(.2, .7, .3, 1);
}

/* ---------- per-sentence chart ---------- */
/* A chart, not a list of cards: aligned index, bar, number, text. The eye should
   be able to run down the bars and find the signal without reading. */
.segments { list-style: none; padding: 0 14px 6px; margin: 0; }
.segments li {
  display: grid; grid-template-columns: 20px 46px 40px 1fr;
  align-items: baseline; gap: 10px;
  padding: 9px 0; border-top: 1px solid var(--line);
}
.seg-i { font: 400 10.5px/1.6 var(--mono); color: var(--ink-3);
         font-feature-settings: var(--fig); }
.seg-bar { height: 5px; border-radius: 1px; background: var(--line-2);
           position: relative; top: -2px; overflow: hidden; }
.seg-bar i { display: block; height: 100%; background: var(--amber);
             transition: width .7s cubic-bezier(.2, .7, .3, 1); }
.segments li.flag .seg-bar i { background: var(--amber); }
.seg-pct { font-size: 12.5px; font-weight: 500; color: var(--ink-2); }
.segments li.flag .seg-pct { color: var(--ink); }
.seg-text { color: var(--ink-2); font-size: 14px; line-height: 1.5; }
.segments li.flag .seg-text { color: var(--ink); }
/* There are no locked rows any more. Every row is a real, scored row: the free
   result is the whole result, and the gate meters how much and how often
   (GOAL §3). What survives is the meter note, which says how much of a long
   submission was actually read. */
.meter-note {
  font: 400 12.5px/1.5 var(--mono); color: var(--ink-3);
  padding: 10px 14px 0; margin: 0; border-top: 1px solid var(--line);
  display: flex; align-items: baseline; gap: 8px;
}
.meter-note::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: var(--ink); flex: none; position: relative; top: -3px;
}

/* ---------- the readout as a centered modal ---------- */
/* app.js opens this with showModal(), so it lands centered in the top layer over a
   blurred page. The dialog is the box; the teaser inside is just content and scrolls
   if the breakdown is long, so the readout never grows the page (up or down) and is
   never on screen beside the Analyse pill. */
/* Both popups (the readout, and the AI-rewrite result stacked above it) share the
   box. A flex column with a fixed header and a scrolling body, so the body always
   scrolls to its LAST line with room to spare, rather than the old fixed max-height
   that clipped the bottom of a long breakdown. */
.readout-modal, .rewrite-modal {
  width: min(430px, 92vw); max-height: min(88vh, 780px);
  padding: 0; border: 1px solid var(--line-2); border-radius: var(--r-lg);
  background: var(--surface-1); color: var(--ink); box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.rewrite-modal { width: min(480px, 92vw); }
.readout-modal[open], .rewrite-modal[open] {
  display: flex; flex-direction: column;
  animation: pop .22s cubic-bezier(.2, .7, .3, 1);
}
.readout-modal::backdrop, .rewrite-modal::backdrop {
  background: rgba(10, 18, 30, .34);
  backdrop-filter: blur(6px) saturate(1.1);
  -webkit-backdrop-filter: blur(6px) saturate(1.1);
}
.readout-modal .readout-head, .rewrite-modal .readout-head { flex: none; }
/* The scrolling body of each popup. padding-bottom guarantees the last line clears
   the rounded corner instead of sitting flush against the scroll edge. */
.readout-modal .teaser, .rewrite-modal .rewrite-body {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  border: 0; background: none; box-shadow: none; border-radius: 0; margin: 0;
  padding-bottom: 22px;
}
@keyframes pop { from { opacity: 0; transform: translateY(8px) scale(.985); } }
.readout-head .meta { font-size: 11px; color: var(--ink-3); }
.readout-close {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0; border: 0; border-radius: 8px;
  background: transparent; color: var(--ink-3); cursor: pointer;
}
.readout-close:hover { background: var(--surface-3); color: var(--ink); }

/* The AI-rewrite popup's content: the humaniser fragment (h1 + the output panel).
   Padded so it reads as a tidy card, with the output panel filling the width. */
.rewrite-body { padding: 16px 16px 0; }
.rewrite-body h1 { font-size: 20px; margin: 0 0 4px; letter-spacing: -.02em; }
.rewrite-body .sub { color: var(--ink-2); font-size: 14px; margin: 0 0 14px; }
.rewrite-body .output-panel { margin-top: 12px; }
.rewrite-err { color: var(--warn); font-size: 14px; margin: 8px 0 16px; }

/* Score colours (product rule): a score over 70 reads as concerning (red), under 30
   as clear (green), the middle stays neutral gold. Higher = more AI / more overlap
   on both score tools, so high maps to red and low to green. Set by app.js as a
   class on the teaser (dial) and per row on each segment. */
.teaser.score-high .dial .value { stroke: var(--warn); }
.teaser.score-high .gauge-val { fill: var(--warn); }
.teaser.score-low .dial .value { stroke: var(--ok); }
.teaser.score-low .gauge-val { fill: var(--ok); }
.segments li.score-high .seg-bar i { background: var(--warn); }
.segments li.score-high .seg-pct { color: var(--warn); font-weight: 600; }
.segments li.score-low .seg-bar i { background: var(--ok); }
.segments li.score-low .seg-pct { color: var(--ok); font-weight: 600; }
/* The range bar follows the same scheme: red when the scores are high, green when
   low, gold in the middle. It fills 0 -> top of the range, so it reads "mostly
   filled, in the score's colour" rather than a thin amber sliver at the far right. */
.teaser.score-high .rangebar-fill {
  background: linear-gradient(90deg, color-mix(in srgb, var(--warn) 42%, white), var(--warn));
}
.teaser.score-low .rangebar-fill {
  background: linear-gradient(90deg, color-mix(in srgb, var(--ok) 42%, white), var(--ok));
}

/* The AI-rewrite offer, centered with room below it. It is revealed LAST (app.js
   adds .rewrite-in after the score has counted up), rising in so it reads as the
   next step rather than being there from the start. */
.rewrite-offer { margin: 6px 14px 20px; text-align: center; }
.rewrite-offer .rewrite-btn { margin: 0 auto; }
.rewrite-offer.rewrite-in { animation: rewriteRise .42s cubic-bezier(.2, .7, .3, 1); }
@keyframes rewriteRise { from { opacity: 0; transform: translateY(10px); } }
.rewrite-result { margin: 0 14px 16px; }

/* Why the pay block is on screen. Sits ABOVE the disclosure, because "why am I
   being asked to pay" comes before "here are the terms". */
.upgrade-why {
  margin: 0 0 12px; font-size: 14.5px; font-weight: 600; color: var(--ink);
}

/* ---------- pay block ---------- */
.pay { margin: 14px; padding: 16px; border-radius: var(--r);
       background: var(--bg); border: 1px solid var(--line-2); }
/* What the money buys, above the button. The offer leads and the terms follow it,
   which is the order a person reads in; the terms are still directly adjacent and
   still land before any card entry, which happens on Stripe's page after this. */
.pay-head { margin: 0 0 8px; font-size: 15px; font-weight: 700; color: var(--ink); }
.pay-buys {
  margin: 0 0 14px; padding: 0; list-style: none;
  font-size: 13.5px; line-height: 1.55; color: var(--ink-2);
}
.pay-buys li { display: flex; align-items: baseline; gap: 8px; }
.pay-buys li::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%; flex: none;
  background: var(--ink); position: relative; top: -2px;
}
/* The last buy is a link out to the full plan comparison. Amber to read as the
   one actionable item in the list, underlined on hover like the rest of the copy. */
.pay-buys a { color: var(--ink); font-weight: 600; text-decoration: none; }
.pay-buys a:hover { text-decoration: underline; }
/* Flex so the star sits on the text baseline-centre and the pair centres as one.
   Chunky and generously rounded, matching the pay button on /pay/checkout. */
.pay-btn {
  width: 100%; display: flex; align-items: center; justify-content: center; gap: 7px;
  height: var(--cta-h); padding: 0 18px; border-radius: var(--cta-r); font-size: 16px;
  /* Same step as the header Try, so the same lane colour (the base `button` rule
     hands out plain amber, which is right for every OTHER button on the site). */
  background: var(--lane); color: var(--lane-ink);
}
/* The pay block, in a dialog, for the upload gate. Same block, same disclosures. */
.pay-dialog {
  border: 1px solid var(--line-3); border-radius: var(--r); padding: 0;
  background: var(--surface-2); color: var(--ink);
  max-width: 380px; width: calc(100vw - 32px);
}
.pay-dialog::backdrop { background: rgb(8 10 14 / .5); }
.pay-dialog .pay { margin: 0; border: 0; background: transparent; }
/* In the upload-gate popup the CTA hugs its label and centres like the header pill,
   instead of stretching the dialog full width. Scoped to the dialog: the inline offer
   under the teaser keeps its slab. A flex box is block-level, so fit-content is what
   shrinks it; margin auto then centres the shrunk box. */
.pay-dialog .pay-btn { width: fit-content; margin-left: auto; margin-right: auto; }
.pay-dialog-title {
  margin: 0; padding: 16px 16px 0; font-size: 15px; font-weight: 700;
}
.pay-dialog-close { position: absolute; top: 8px; right: 8px; margin: 0; }
.pay-dialog-close button {
  background: transparent; border: 0; color: var(--ink-3); cursor: pointer;
  padding: 4px; line-height: 0; border-radius: 6px;
}
.pay-dialog-close button:hover { color: var(--ink); background: var(--surface-3); }
/* The upload gate: an affordance, deliberately quieter than Analyse. It is the
   paid path, and out-shouting the free action people came for would be the
   bait this product's position is against. */
button.inline-cta.ghost {
  color: var(--ink-2); font-weight: 500; margin-right: auto;
}
button.inline-cta.ghost:hover { color: var(--ink); }
/* Room to breathe. This line is four separate facts run together ("43c a day",
   "$0.99 today", "7-day trial", "then $39 every 3 months") and at 1.5 they set as
   a grey wall that reads as boilerplate to be skipped. It is the single most
   important sentence on the page: COMPLIANCE §1.1 wants it clear AND conspicuous,
   and conspicuous is a typographic property, not just a placement one. Looser
   leading, a little letter-spacing off the tight default, and space around it. */
.disclose {
  font-size: 14.5px; line-height: 1.75; letter-spacing: .006em;
  margin: 4px 0 14px;
}
code, .disclose code {
  font-size: 12.5px; background: var(--surface-3); padding: 2px 6px;
  border-radius: 4px; border: 1px solid var(--line-2); color: var(--ink);
  letter-spacing: .02em;
}
/* The billing disclosure is legally load-bearing, never shrink it below the
   body text around it, and never grey it out (COMPLIANCE §1.1 "conspicuous"). */
.disclose-list { font-size: 14px; line-height: 1.65; margin: 0; padding-left: 18px;
                 color: var(--ink-2); }
.disclose-list li { margin-bottom: 5px; }
.disclose-list strong, .disclose strong { color: var(--ink); }
.pay input[type=email] { margin-top: 14px; }
.pay .fine { margin: 10px 0 0; }
.fine { font-size: 12.5px; color: var(--ink-3); line-height: 1.55; }
/* Copyright: one line at 320px (house rule). At 12.5px it lands exactly on the
   ~265px content width and tips into two lines, so it is sized down a touch,
   which also reads correctly as the finest print in the footer. */
.copyright { font-size: 11px; }

/* ---------- protected legal identifiers (app/templates/_legal.html) ----------
   The company name, registered address and contact email ship as Chivo PNGs,
   never as indexable text (COMPLIANCE §7). Each image is a font em-box:
   ascent+descent = 1.195em tall, glyph baseline 0.25em up from the bottom. So
   height:1.195em makes the picture the exact size of the surrounding text and
   ONE vertical-align seats it on the shared baseline, whether or not the word
   has descenders. Regenerate images + manifest with tools/make_legal_images.py. */
.legalmark { height: 1.195em; width: auto; vertical-align: -0.25em; }
.email-link { cursor: pointer; text-decoration: none; }
/* The address is a multi-line block, sized to body prose by the template's
   inline em_h height (width follows the intrinsic ratio) and never wider than
   its column on a narrow screen. */
.legalmark--block { display: block; width: auto; height: auto; max-width: 100%;
                    margin: 8px 0; }

/* ---------- report ---------- */
.report .segments { padding-left: 0; padding-right: 0; }
.report-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 8px; }
.report-actions button, .btn-link { width: auto; display: inline-block; margin-top: 0; }
/* White on a hairline border, the same surface as the store badges and the install
   download CTA: a download reads the same way wherever the site offers one. */
.btn-link { padding: 13px 18px; border: 1px solid var(--line-3);
            background: var(--surface-1);
            border-radius: var(--r); color: var(--ink); text-decoration: none; }
.btn-link:hover { border-color: var(--ink); text-decoration: none; }

/* ---------- landing bands ---------- */
.band { margin-top: 52px; }
/* Clamped, because the one-line rule (GOAL §6) binds section headings too and
   26px x ~22 chars does not fit 280px of usable width at 320px. The copy is kept
   inside ~22 chars as well. The type and the words have to agree, and relying on
   only one of them is how a heading silently wraps on the cheapest phone. */
.band h2 {
  font-size: clamp(22px, 6.6vw, 26px); line-height: 1.12; letter-spacing: -.035em;
  margin: 0 0 10px; font-weight: 800; text-wrap: balance;
}
.band > p { color: var(--ink-2); margin: 0 0 18px; }

/* benefit grid: hairline-ruled cells, not floating cards */
.grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
  background: var(--line); border: 1px solid var(--line);
  border-radius: var(--r); overflow: hidden;
}
.card { background: var(--surface-1); padding: 16px; }
.card h3 {
  font: 500 10px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink); margin: 0 0 9px;
}
.card p { margin: 0; font-size: 14px; color: var(--ink-2); line-height: 1.55; }
.card p strong { color: var(--ink); font-weight: 600; }

/* how it works */
.steps { list-style: none; padding: 0; margin: 0; counter-reset: step; }
.steps li {
  display: flex; gap: 14px; padding: 15px 0; border-top: 1px solid var(--line);
  counter-increment: step;
}
.steps li::before {
  content: counter(step, decimal-leading-zero);
  font: 500 11px/1.7 var(--mono); color: var(--ink); flex: none;
  font-feature-settings: var(--fig);
}
.steps p { margin: 0; font-size: 14.5px; color: var(--ink-2); }
.steps strong { color: var(--ink); font-weight: 600; display: block; margin-bottom: 2px; }

/* methodology. The honesty band, deliberately the plainest thing here */
.honest {
  border: 1px solid var(--line-2); border-left: 2px solid var(--ink);
  border-radius: 0 var(--r) var(--r) 0;
  background: var(--surface-1); padding: 18px 18px 18px 20px;
}
.honest > p:first-child { margin-top: 0; color: var(--ink-2); }
.honest ul { margin: 12px 0 0; padding-left: 18px; }
.honest li { font-size: 14.5px; color: var(--ink-2); margin-bottom: 8px; line-height: 1.55; }
.honest li strong { color: var(--ink); font-weight: 600; }

/* pricing */
.prices { display: grid; gap: 1px; background: var(--line);
          border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; }
.price-card {
  background: var(--surface-1); padding: 16px;
  display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap;
}
.price-card.now { background: var(--surface-2); }
.price-fig {
  font-size: 20px; font-weight: 700; letter-spacing: -.03em;
  flex: none; min-width: 70px; color: var(--ink-2);
}
.price-card.now .price-fig { color: var(--ink); }
.price-body { flex: 1 1 190px; }
.price-body h3 { margin: 0 0 3px; font-size: 14.5px; font-weight: 700; }
.price-body p { margin: 0; font-size: 13.5px; color: var(--ink-2); line-height: 1.5; }
/* A note under a band's content (e.g. the pricing cards). */
.band-foot { margin-top: 14px; }
/* Page-title h1 inside a band section (the /pricing page reuses the landing's band
   composition but leads with an h1, not an h2). */
.band > h1 {
  font-size: clamp(30px, 7vw, 40px); line-height: 1.08; letter-spacing: -.03em;
  margin: 0 0 10px; font-weight: 800; text-wrap: balance;
}
/* Primary call-to-action link (filled amber pill), reusable across content pages. */
a.cta {
  display: inline-flex; align-items: center; gap: 8px; text-decoration: none;
  background: var(--amber); color: var(--on-amber); border: 1px solid var(--amber);
  padding: 11px 18px; border-radius: 999px; font-weight: 600; font-size: 15px;
}
a.cta:hover { text-decoration: none; filter: brightness(1.08); }

/* FAQ: <details>, so it works with JS off and costs nothing to render */
.faq { border-top: 1px solid var(--line); }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  cursor: pointer; padding: 15px 0; font-weight: 600; font-size: 15px;
  list-style: none; display: flex; justify-content: space-between; gap: 12px;
  letter-spacing: -.01em;
}
.faq summary::-webkit-details-marker { display: none; }
/* A rotated "+" rather than a minus glyph: U+2212 is not in Chivo, so it
   rendered as tofu. This needs no glyph that might not exist and animates. */
.faq summary::after {
  content: "+"; color: var(--ink); font-family: var(--mono); font-weight: 400;
  flex: none; transition: transform .18s ease; transform-origin: 50% 50%;
}
.faq details[open] summary::after { transform: rotate(45deg); }
.faq details p { margin: 0 0 15px; font-size: 14.5px; color: var(--ink-2);
                 max-width: 60ch; line-height: 1.6; }

/* ---------- cancel / account / legal ---------- */
.cancel-page h2 { font-size: 16px; margin: 24px 0 6px; }
.status { font-size: 17px; font-weight: 700; font-family: var(--mono);
          font-feature-settings: var(--fig); }
.status.active { color: var(--ok); }
.status.canceled { color: var(--ink-3); }
.legal h2 { font-size: 16.5px; margin: 24px 0 6px; letter-spacing: -.01em; }
.legal p, .legal li { color: var(--ink-2); }
.legal li { margin-bottom: 5px; }
.legal ul { padding-left: 20px; }
/* The effective-date / intro line under the h1 on the legal pages: a quiet caption,
   not body copy. */
.legal-updated { font-size: 13.5px; color: var(--ink-3); margin: 0 0 16px; }

/* ---------- footer ---------- */
/* Full-bleed, with the reading measure moved onto the children, so the footer can
   carry an OPAQUE background and end the grid texture.
   body::before (the grid) is position: fixed, so it cannot fade out at a point in
   the DOCUMENT: whatever its mask does, it does relative to the viewport, and at the
   bottom of a page that means its lines run straight through the footer. /pay/checkout
   is the worst of it, because body.co-page::before re-masks the grid to full strength
   over the lower half of the viewport, and the footer's own rules then read as more
   lines crossing them. An opaque footer is what stops it, and it has to reach both
   edges: capped at a measure, the cut would show as a clean rectangle with grid still
   running down either side of it. */
footer.site {
  position: relative; margin: 56px 0 0; padding: 24px 20px 44px;
  text-align: center; color: var(--ink-3);
  background: var(--bg); border-top: 1px solid var(--line);
}
/* The measure the content used to get from the footer's own max-width. */
footer.site > * { max-width: var(--maxw); margin-left: auto; margin-right: auto; }
/* ...and the grid dissolves into the footer rather than stopping dead on its border,
   across the 56px of margin above it. */
footer.site::before {
  content: ""; position: absolute; left: 0; right: 0; height: 56px;
  /* +1px because a percentage bottom resolves against the PADDING box: at a flat
     100% the gradient's solid end lands on top of the 1px border-top and paints
     it out. */
  bottom: calc(100% + 1px);
  pointer-events: none; background: linear-gradient(to bottom, transparent, var(--bg));
}
/* align-items: center for the one item that is not a bare link: the language
   disclosure is a flex ITEM, so it would otherwise stretch and sit its summary at the
   top of the row, half a line above the links beside it. */
footer nav { display: flex; gap: 8px 18px; justify-content: center; align-items: center;
             flex-wrap: wrap; margin-bottom: 14px; font-size: 13.5px; }
/* A full-width, zero-height flex item forces the footer nav's second row (primary
   links, then contact / sign-in). A bare <br> is inert here. */
.nav-break { flex: 0 0 100%; width: 100%; height: 0; }
footer nav a { color: var(--ink-2); text-decoration: none; }
footer nav a:hover { color: var(--ink); text-decoration: underline; }
/* The second row carries FOUR items now, and at 320px the last of them (the language
   control) fell to a line of its own: the lonely centred row this change exists to
   remove. Measured, not guessed: at 18px the row wants 276px of a 265px line, at 12px
   it wants 258px. The gap is the thing to give up, not the item. */
@media (max-width: 400px) {
  footer nav { gap: 8px 12px; }
}

/* Mobile: the tools menu is a left-corner button opening a full-height off-canvas
   panel (on desktop it is instead a persistent right rail; see the min-width: 900px
   block). The burger sits flush in the left corner so it costs the logo as little
   width as possible; the right of the bar is the profile menu and the Try CTA. */
@media (max-width: 720px) {
  /* Flush the burger into the left corner: kill the lockup gap and pull it out to
     the header's own padding edge, so it does not push the logo inward. */
  .head-lead { gap: 3px; }
  .burger { margin-left: -11px; }
  /* Right-hand nav order: the Try CTA, then the profile menu, so the profile is the
     rightmost control in the corner (the tools button is in the left lockup). */
  header nav a.try { order: 1; }
  .acctmenu { order: 2; }
  /* Avatar only in the bar on a phone: the name has nowhere to go beside the CTA
     at 320px, and the menu the avatar opens carries the name's job anyway. The
     chevron goes with it, and not only to save its 11px (which the header does not
     have at 320px, measured: it pushed the page to 323). Next to a bare avatar it
     is pointing at nothing a reader can name. */
  .acctmenu .acct-name, .acctmenu .chev { display: none; }
}

@media (max-width: 420px) {
  .grid { grid-template-columns: 1fr; }
  .segments li { grid-template-columns: 16px 34px 36px 1fr; gap: 8px; }
  header.site { padding: 14px 12px; gap: 8px; }
  /* The lockup shrinks a touch for balance on smaller screens. Same 0.8533 ratio
     to the box as before (25.6/30), restated for the new 35-tall box. */
  .brand .logo { height: 29.9px; }
  header nav { gap: 8px; }
  /* At 320px the foot carries Upload + Paste on the left and the run cluster (the
     example link + the star pill) on the right. It wraps as two units: Upload/Paste
     stay on the top row, the run cluster drops to its own row and margin-left:auto
     (.foot-run) keeps it right-aligned, so the pill sits bottom-right and stays sized
     to its own label rather than stretching full width. */
  .specimen-foot { flex-wrap: wrap; }
  /* Keep the full "star Try Pro {price}"; trim padding/size so burger + logo + CTA +
     profile hold one line. Font shrinks (never a dropped word) to fit. */
  header nav a.try { padding: 0 12px; font-size: 12.5px; gap: 5px; }
  /* The profile control is now ALWAYS in the corner (signed in or out), so the header
     always carries the extra ~26px it used to carry only when signed in: the lockup
     and gaps give it back here for everyone, not just body.signed-in. */
  .brand .logo { height: 26px; }
  header nav { gap: 6px; }
  .acct-photo, .acct-avatar { width: 22px; height: 22px; }
  .acct-icon { width: 26px; height: 26px; }
}
/* ---------- desktop: the tools drawer becomes a persistent LEFT rail ----------
   On desktop (>=900px, where the hero also goes two-column) the tools menu is not a
   hidden off-canvas panel but a justdone-style icon rail pinned to the LEFT edge and
   shown at ALL times, with the hamburger at the top-left corner above it. Clicking the
   hamburger EXPANDS the same panel rightward to reveal the labels; the rail itself is
   always there. It is ONE element (.drawer): CSS collapses it to icons and expands it
   to labels, so there is a single tool list, never two. The page reserves the rail's
   width (a left gutter) so nothing hides under it. Placed late in the file, after the
   base .drawer / .drawer-item rules, so these overrides win on source order (a media
   query adds no
   specificity of its own). */
@media (min-width: 900px) {
  :root { --rail: 62px; }
  /* Reserve the strip so page content never slides under the fixed rail. The payment
     pages carry no rail (they drop header_tools), so they carry no gutter either. */
  body { padding-left: var(--rail); }
  body.no-rail { padding-left: 0; }
  body.no-rail .toolsnav { display: none; }

  /* The hamburger pins to the very top-LEFT corner, centred in the rail strip, above
     the icons; the logo sits just right of the rail (the body's left gutter). */
  .head-lead { gap: 0; }
  .burger { position: fixed; z-index: 62; top: 15px; left: 11px; margin: 0; }

  /* The panel IS the rail: docked left, full height, always shown, icons only.
     overflow is VISIBLE (not hidden) so the instant hover tooltips below can escape
     the 62px strip; the icon set is short enough to fit a desktop height without a
     scrollbar. */
  .drawer {
    right: auto; left: 0; top: 0; bottom: 0; height: 100vh; height: 100dvh;
    width: var(--rail); max-width: none;
    transform: none; visibility: visible;
    padding: 60px 0 14px; gap: 2px;
    border-left: 0; border-right: 1px solid var(--line-3); box-shadow: none;
    overflow: visible;
    transition: width .26s cubic-bezier(.4, 0, .2, 1), box-shadow .26s ease;
  }
  /* Open = expanded: widen RIGHTWARD over the page (the left edge and the icons stay
     put, so nothing jumps) and reveal the labels. Width is trimmed close to the
     longest label so the panel is not a wide box with the text floating in it. */
  .toolsnav[open] .drawer { width: 236px; box-shadow: var(--shadow-pop); overflow-y: auto; }

  /* Collapsed rail: hide everything but the icons. The hamburger is the toggle, so the
     in-panel close (drawer-head) is not needed on desktop, and the hub link only makes
     sense with its label. The store badge STAYS, collapsed to the browser's own mark. */
  .drawer-head { display: none; }
  .drawer-cat, a.drawer-all { display: none; }
  a.drawer-item > span { display: none; }
  a.drawer-item {
    justify-content: center; gap: 0; padding: 12px 0;
    border-left: 0; border-radius: 0;
  }
  /* Active tool at icon-only width: amber tint plus an inset left bar (a 2px border on
     a centred row would read as stray). */
  a.drawer-item.is-active { background: var(--amber-tint); box-shadow: inset 3px 0 0 var(--amber); }

  /* The store badge stays in the collapsed rail as the browser's OWN colourful logo
     (the tool icons above are monochrome; this one is the real Chrome/Firefox/Safari
     mark), set off by a hairline, so "get the extension" is one tap from the rail. Its
     pill chrome and "Add to <browser>" label drop; only the mark shows, centred. The
     expanded/mobile full badge is the base .drawer-ext rule and is scoped back below. */
  .toolsnav:not([open]) .drawer-ext {
    margin-top: 6px; padding: 6px 0 0; border-top: 1px solid var(--line);
  }
  .toolsnav:not([open]) .drawer-ext .storebadge {
    width: 100%; justify-content: center; gap: 0; padding: 10px 0;
    border: 0; background: none; box-shadow: none; border-radius: 0;
  }
  .toolsnav:not([open]) .drawer-ext .storebadge:hover { background: var(--surface-3); }
  .toolsnav:not([open]) .drawer-ext .storebadge-txt { display: none; }
  .toolsnav:not([open]) .drawer-ext .bmark { width: 24px; height: 24px; }

  /* Instant tooltips on the collapsed rail. The native `title` tip waits ~1s, so the
     label rides on aria-label instead and is shown IMMEDIATELY from CSS on hover/focus
     (no transition = no delay), escaping the rail because it is overflow: visible. */
  .toolsnav:not([open]) .drawer-item,
  .toolsnav:not([open]) .drawer-ext .storebadge { position: relative; }
  .toolsnav:not([open]) .drawer-item:hover::after,
  .toolsnav:not([open]) .drawer-item:focus-visible::after,
  .toolsnav:not([open]) .drawer-ext .storebadge:hover::after,
  .toolsnav:not([open]) .drawer-ext .storebadge:focus-visible::after {
    content: attr(aria-label);
    position: absolute; left: calc(100% + 9px); top: 50%; transform: translateY(-50%);
    z-index: 70; padding: 6px 10px; border-radius: 8px;
    background: var(--ink); color: var(--surface-2);
    font: 500 12px/1 var(--sans); white-space: nowrap; pointer-events: none;
    box-shadow: var(--shadow-pop);
  }

  /* Expanded: labels back, category heads back, rows left-aligned with their icon. */
  .toolsnav[open] .drawer-list { padding: 0 10px; }
  .toolsnav[open] .drawer-cat { display: block; }
  .toolsnav[open] a.drawer-all { display: block; margin: 12px 12px 2px; }
  .toolsnav[open] a.drawer-item > span { display: block; }
  .toolsnav[open] a.drawer-item {
    justify-content: flex-start; gap: 11px; padding: 9px 12px; border-radius: 9px;
  }
  .toolsnav[open] a.drawer-item.is-active { box-shadow: none; border-left: 2px solid var(--amber); }

  /* Desktop expand does not lock the page (it is an overlay, not a takeover); the
     mobile off-canvas still does, below. */
  body.drawer-open { overflow: auto; }
}

/* Smallest phones: shrink the CTA font a touch more (still the full words) and the
   lockup, so logo + "star Try Pro {price}" + burger hold one line at 320px, with a
   ~12px margin (measured). Font shrinks; no word is ever dropped. */
@media (max-width: 360px) {
  header nav a.try { padding: 0 9px; font-size: 11px; gap: 4px; }
  /* The profile control is always in the corner now, so the tightest lockup (the one
     that used to be signed-in only) applies to everyone at 320px. */
  .brand .logo { height: 22px; }
  .acct-icon { width: 24px; height: 24px; }
  /* The star is decoration, not a word, so it is what goes at 320px. Every word
     of "Try Pro {price}" still ships. */
  .try .cta-star { display: none; }
}

/* ---------- desktop ----------
   Everything above is the phone. Without this block the site was a 680px column
   centred in a 1440px window, which is what "feels like a mobile site on a
   desktop" actually means: no composition, just a narrow strip of leftovers.

   The hero becomes two columns and the readout moves up beside the input, so the
   instrument is visible next to the thing that feeds it and the fold does real
   work. Prose bands keep the 680px reading measure regardless of window width,
   because a 1100px line length is unreadable at any screen size. */
@media (min-width: 900px) {
  main { max-width: var(--wide); padding: 12px 24px 8px; }

  .hero {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 40px; align-items: end; padding: 32px 0 8px;
  }
  .hero-copy { padding-top: 8px; }
  /* The readout bottom-aligns with the input box rather than the page title: the
     instrument sits beside the thing that feeds it, not floating up level with the
     H1 with empty space under it. Score heroes only (index + plagiarism); the
     grammar workspace re-asserts start below so its corrections rail tops out with
     the text. */
  /* Tool pages (every tool except the AI-detect flagship) and the checker hub have
     no readout in the second column, so the two-column hero left their content
     stranded at half width against an empty column. Collapse to a single
     FULL-WIDTH column so the specimen, live stats and goal row (and the hub's own
     intro line) line up edge to edge with the bands above and below, not inset in
     a strip. The hub was missing from this list, which is why its one-sentence
     intro wrapped: the sentence was never too long, the column was too narrow. */
  .hero-tool, .hero-hub { grid-template-columns: minmax(0, 1fr); }
  /* Score tools (AI detector + plagiarism) open their result as a centered modal,
     so they have no second column either: one column, the input constrained to a
     readable measure rather than stretched across the full width. */
  .hero-modal { grid-template-columns: minmax(0, 1fr); }
  .hero-modal .hero-copy { max-width: 640px; }
  .hero h1 { font-size: clamp(46px, 4.2vw, 58px); }
  .hero .sub { font-size: 17.5px; margin-bottom: 28px; }
  .dial { max-width: 300px; }
  textarea { min-height: 210px; }

  /* Prose keeps its measure; the page does not. */
  .band { max-width: var(--maxw); margin-left: auto; margin-right: auto; }
  .band.full { max-width: var(--wide); }
  .band.full .grid { grid-template-columns: repeat(4, 1fr); }
  .band.full .prices { grid-template-columns: repeat(3, 1fr); }
  .band.full .steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
  .band.full .steps li { border-top: 1px solid var(--line-3); padding-top: 14px; }
  /* Methodology + FAQ span the wide width like every other full band, but their
     prose splits into two columns so the reading measure stays short instead of
     running the whole 1120px. The methodology intro paragraph spans both columns. */
  .band.full .honest ul { columns: 2; column-gap: 40px; }
  .band.full .honest li { break-inside: avoid; }
  .band.full .faq { display: grid; grid-template-columns: 1fr 1fr; column-gap: 40px; border-top: 0; }
  .band.full .faq details { border-top: 1px solid var(--line); }

  footer.site > * { max-width: var(--wide); }
  header.site { padding: 22px 24px; }
}

/* The reveal is decorative. Honor the OS setting. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

@media print {
  header.site, footer.site, .report-actions, .pay, body::before { display: none; }
  body { background: #fff; color: #000; }
  .report { border: 0; background: #fff; }
  .seg-text, .segments li, .seg-pct { color: #000; }
}

/* Arrow links. The arrows are inline SVG rather than characters: U+2192/U+2190
   are not in Chivo at any weight, so they rendered as tofu. */
.arrow-link a { display: inline-flex; align-items: center; gap: 6px; }
.arrow-link a svg { flex: none; transition: transform .18s ease; }
.arrow-link a:hover svg { transform: translateX(2px); }
.arrow-link.back a:hover svg { transform: translateX(-2px); }

/* Dial: idle vs live centre. Swapped as whole groups so the idle state needs no
   placeholder number and no stand-in glyph. */
.teaser.is-idle .dial .on-live { display: none; }
.teaser:not(.is-idle) .dial .on-idle { display: none; }
.gauge-idle {
  fill: var(--ink-3); font-family: var(--mono); font-size: 10px;
  font-weight: 500; letter-spacing: .18em;
}

/* The line under the pay button. Sits BELOW it, never between the disclosure
   and the button: nothing may come between the terms and the control that
   consents to them (COMPLIANCE §1.1). */
.pay-note { margin: 10px 0 0; text-align: center; }

