/* v4 shared stylesheet.
 *
 * Lifted verbatim out of home.html on 2026-08-16, where it was inlined and
 * then duplicated byte-for-byte into voice-ai.html. Every v4 page links it.
 *
 * Page-specific CSS stays in that page, in its own <style> block, and should
 * be a handful of rules. Anything that turns up in a SECOND page belongs
 * here instead.
 *
 * Cache-bust by bumping ?v= on the <link>, not by editing a header. A
 * no-cache header does not help a file the browser already has.
 */
*, *::before, *::after { box-sizing: border-box; }
  /* 🔴 The clip must be on BOTH html and body. Full-bleed decoration overshoots
     the viewport by half a scrollbar, because calc((100% - 100vw)/2) uses 100vw
     which includes the scrollbar while 100% does not. On the LEFT that is
     invisible, since leftward overflow is not scrollable. On the RIGHT it is a
     horizontal scrollbar, and body's clip alone does not stop it: the document
     scrolls at the html level. Found when the KCR decoration moved right. */
  html { scroll-behavior: smooth; overflow-x: clip; }
  body {
    /* Full-bleed decoration (the ribbon, the knot) deliberately extends past
       the viewport edges. Clip here, once, so nothing can produce a horizontal
       scrollbar. `clip` not `hidden`: hidden makes body a scroll container and
       breaks position:sticky further down the page. */
    overflow-x: clip;
    margin: 0; background: var(--ground); color: var(--ink);
    font-family: var(--font); font-weight: var(--w-regular);
    font-size: var(--t-body); line-height: var(--lh-body); letter-spacing: var(--ls);
    -webkit-font-smoothing: antialiased; text-wrap: pretty;
  }
  @media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }
  :focus-visible { outline: 3px solid var(--ink); outline-offset: 4px; }

  /* two insets, measured: panels 50, text 150 */
  .panel-w   { max-width: var(--maxw); margin-inline: auto; padding-inline: var(--panel-inset); }
  .content-w { max-width: var(--maxw); margin-inline: auto; padding-inline: var(--content-inset); }

  h1,h2,h3,p { margin: 0; font-weight: var(--w-regular); }
  h1,h2,h3 { text-wrap: balance; }
  /* letter-spacing MUST be restated per size, or the em inherits as a length */
  .display,.h1,.h2,.ghost,.num { letter-spacing: var(--ls); }
  .display { font-size: var(--t-display); line-height: var(--lh-display); }
  .h1      { font-size: var(--t-h1);      line-height: var(--lh-h1); }
  .h2      { font-size: var(--t-h2);      line-height: var(--lh-h2); }
  .lead    { color: var(--ink-soft); }

  section { padding-block: var(--gap-section); }

  /* ---- outlined display: the signature, weight 800, used sparingly ------- */
  .ghost { font-size: var(--t-display); line-height: var(--lh-display);
           font-weight: var(--w-outline); color: transparent;
           -webkit-text-stroke: 2px var(--ink); }
  .ghost-sm { font-size: var(--t-h1); line-height: 1; }

  /* ---- buttons ----------------------------------------------------------- */
  .btn { display: inline-flex; align-items: center; justify-content: center;
         min-height: 48px; padding: clamp(14px,1.8vw,34px) clamp(28px,4vw,76px);
         border-radius: var(--r-pill); border: 1px solid transparent;
         font-size: var(--t-body); letter-spacing: var(--ls);
         background: var(--panel); color: #000; text-decoration: none; cursor: pointer;
         transition: background-color var(--dur) var(--ease), scale var(--dur-fast) var(--ease); }
  .btn:active { scale: .96; }
  .btn-dark  { background: var(--dark); color: #fff; }
  .btn-light { background: #fff; color: #000; }
  /* The OUTLINED secondary. Takes its ink from the surface rather than being
     hardcoded white, so one variant works on white pages and on dark ones:
     .on-dark / .on-image-dark already remap --ink to #FFF, which is exactly
     what this used to be hardcoded to. Before this it was white-only, so a
     light page had no outlined secondary and fell back to the grey .btn,
     which reads as a wireframe placeholder. */
  .btn-ghost { background: transparent; color: var(--ink); border-color: var(--ink); }
  .btn-ghost:hover { background: color-mix(in srgb, var(--ink) 8%, transparent); }
  /* Accent fill. White label measures 5.36:1 on #0F6FB0, so it clears AA for
     body-size text, not just large. Inherits var(--accent), so inside .on-dark
     it becomes the lighter blue automatically. */
  .btn-accent { background: var(--accent); color: #fff; }
  .btn-accent:hover { background: #0B5D95; }
  .on-dark .btn-accent { color: #0B1620; }
  .btn svg { width: 1em; height: 1em; flex: none; }

  /* ---- 01 HERO ----------------------------------------------------------- */
  /* ---- navigation ---------------------------------------------------------
     Three parts: wordmark, links, actions. No bar, no shadow, no border. v4's
     nav sits on the white ground the same way every other block does, and the
     hero panel underneath provides the edge, so a chrome bar would be a second
     edge doing the same job.

     The dropdown is built from the page's own panel language: white, 20px
     radius, 1px ink outline, exactly like the outlined pricing tiers. Not a
     floating shadowed menu, which would be the only shadow on the entire page. */
  .nav { min-height: clamp(44px,4.2vw,80px); display: grid;
         grid-template-columns: auto 1fr auto; align-items: center;
         gap: clamp(16px,2vw,40px);
         padding-inline: clamp(20px,2.6vw,50px);
         margin-top: clamp(10px,1.3vw,25px); position: relative; z-index: 60; }
  .nav-brand { display: block; line-height: 0; }
  .nav-brand img { height: clamp(22px,2.08vw,40px); width: auto; display: block; }

  .nav-links { display: flex; align-items: center; justify-content: center;
               gap: clamp(6px,1vw,20px); }
  .nav-item { position: relative; }
  .nav-trigger, .nav-link {
    font: inherit; font-size: 0.86em; letter-spacing: var(--ls);
    color: var(--ink); background: none; border: 0; cursor: pointer;
    text-decoration: none; padding: 10px clamp(8px,0.8vw,16px);
    border-radius: var(--r-pill); min-height: 44px;
    display: inline-flex; align-items: center; gap: 7px;
    transition: color var(--dur-fast) var(--ease); }
  .nav-trigger:hover, .nav-link:hover { color: var(--accent); }
  .nav-trigger .caret { width: 9px; height: 9px; flex: none;
    transition: transform var(--dur) var(--ease); }
  .nav-trigger[aria-expanded="true"] { color: var(--accent); }
  .nav-trigger[aria-expanded="true"] .caret { transform: rotate(180deg); }

  .dropdown { position: absolute; top: calc(100% + 12px); left: 50%;
    translate: -50% 0; background: var(--ground);
    border: 1px solid var(--ink); border-radius: var(--r-panel);
    padding: clamp(14px,1.2vw,22px); display: grid; gap: 4px;
    grid-auto-flow: column; grid-template-rows: repeat(3, auto);
    min-width: max-content; }
  .dropdown[hidden] { display: none; }
  .dropdown a { display: block; white-space: nowrap; text-decoration: none;
    color: var(--ink); font-size: 0.86em; letter-spacing: var(--ls);
    padding: 9px 14px; border-radius: 12px;
    transition: background-color var(--dur-fast) var(--ease); }
  .dropdown a:hover { background: #F1F4F7; color: var(--accent); }

  .nav-actions { display: flex; align-items: center; gap: clamp(6px,0.8vw,14px);
                 justify-self: end; }
  .nav-actions .btn { font-size: 0.86em; min-height: 44px;
                      padding: 11px clamp(16px,1.5vw,28px); }
  .nav-toggle { display: none; background: none; border: 0; cursor: pointer;
                color: var(--ink); padding: 10px; min-height: 44px; min-width: 44px; }
  .nav-toggle svg { width: 22px; height: 22px; }

  @media (max-width: 1100px) {
    /* Below this the three-part row stops fitting, so links collapse behind the
       toggle and the dropdowns become plain stacked groups rather than popovers. */
    .nav { grid-template-columns: auto 1fr; }
    .nav-toggle { display: inline-flex; align-items: center; justify-content: center; }
    .nav-links { display: none; }
    .nav.open .nav-links { display: grid; position: absolute; top: calc(100% + 10px);
      left: clamp(20px,2.6vw,50px); right: clamp(20px,2.6vw,50px);
      background: var(--ground); border: 1px solid var(--ink);
      border-radius: var(--r-panel); padding: 14px; gap: 2px;
      justify-content: stretch; }
    .nav.open .nav-item { position: static; }
    .nav.open .dropdown { position: static; translate: none; border: 0;
      padding: 0 0 6px clamp(12px,2vw,20px); grid-auto-flow: row;
      grid-template-rows: none; min-width: 0; }
    .nav.open .dropdown[hidden] { display: none; }
    .nav-actions .nav-verify { display: none; }
  }

  .hero { background: var(--panel); border-radius: var(--r-panel);
          min-height: clamp(360px,35.4vw,680px); display: flex; align-items: center;
          padding: clamp(28px,5.2vw,100px); margin-top: clamp(12px,1.04vw,20px);
          position: relative; overflow: hidden; isolation: isolate; }
  /* Shared image-as-background layer: sits behind the copy, cropped by its
     parent. Parent needs position:relative + overflow:hidden + isolation. */
  .img-bg { position: absolute; inset: 0; z-index: -1; }
  .img-bg img { width: 100%; height: 100%; object-fit: cover; display: block; }
  /* Hero-specific crop: keeps the arch's crown in frame, since a centred crop
     on a 2.68 panel from a 1.78 source decapitates it. */
  .hero-bg img { object-position: 50% 22%; }
  /* 🔴 A SCRIM, AT PHONE WIDTH ONLY. On desktop the hero copy sits over the
     dark left third of the photo and white text is legible with no help. At
     390px the copy spans the FULL image, so it lands on the bright centre and
     the body paragraphs became unreadable. Verified in a 390px iframe, which
     is the only way to fire the real mobile media queries.
     Scoped to the breakpoint on purpose: the desktop hero is signed off and
     this must not change it. */
  @media (max-width: 900px) {
    .on-image-dark .img-bg::after {
      content: ""; position: absolute; inset: 0;
      background: linear-gradient(180deg, rgba(15,20,26,.82), rgba(15,20,26,.58));
    }
  }
  /* 🔴 52ch, not 46ch. Ansh, 2026-08-19: the H1 should set in 3 lines on
     desktop, not 4. Measured by sweeping the column 680 -> 1120px: the H1 is
     4 lines at the old 672px, drops to 3 at ~700px, and holds 3 until 910px
     where it becomes 2. 52ch resolves to 759px, mid-band, so a font-metric
     shift in either direction cannot flip the line count.
     Only the H1 widens: both .lead paragraphs carry their own 46ch cap, so
     the body copy keeps the measure it was signed off at. */
  .hero-copy { max-width: 52ch; display: grid; gap: var(--gap-inner); position: relative; }
  .hero-copy .lead { max-width: 46ch; }
  .hero-cta { display: flex; gap: var(--gap-tight); flex-wrap: wrap; }
  /* 🔴 HOMEPAGE HERO ONLY: one button, offer demoted to a text link under it.
     Ansh, 2026-08-19: "before people start, they need to know how it all
     works, so that CTA should be prominent." So the button jumps to #kcr on
     the same page and the offer stops competing with it. Stacked rather than
     side by side, which also retires the padding workaround the two long
     labels needed. Scoped to `.hero`: the internal-page hero is
     .split > .hero-copy > .hero-cta with no .hero ancestor, so it keeps the
     stock horizontal pair and this cannot leak onto a signed-off page. */
  .hero .hero-cta { flex-direction: column; align-items: flex-start; }
  /* Inherits --ink, so it flips with .on-image-dark like the rest of the hero
     rather than carrying its own colour. */
  .hero-link { color: var(--ink); font-size: var(--t-body); line-height: 1.2;
               text-underline-offset: 6px; text-decoration-thickness: 1px; }
  .hero-link:hover { text-decoration-thickness: 2px; }

  /* 35/35/30. The third column was `auto`, so it sized to its content instead of
     taking a share of the row, which read as roughly 40/40/20. */
  .hero-row { display: grid; grid-template-columns: 35fr 35fr 30fr; gap: clamp(12px,1.04vw,20px);
              margin-top: clamp(12px,1.04vw,20px); align-items: end;
              position: relative; z-index: 2; }
  .hero-card { background: var(--panel); border-radius: var(--r-panel);
               min-height: clamp(180px,19.8vw,380px); display: flex; align-items: flex-end;
               padding: clamp(20px,2.6vw,50px);
               position: relative; overflow: hidden; isolation: isolate; }
  .hero-card .h2 { position: relative; }
  .hero-stat { padding-left: clamp(12px,1.6vw,30px); }
  @media (max-width: 900px) { .hero-row { grid-template-columns: 1fr; } }

  /* ---- the ribbon --------------------------------------------------------
     Bleeds off the left edge, runs across, and its purple riser (which exits
     the TOP-RIGHT of the artwork) passes BEHIND card 2 before section 2 starts.

     Three things make it work and all three are load-bearing:
     1. z-index: the card row is 2, the ribbon is 1. Without this the ribbon
        paints ON TOP of the cards, because it comes later in the DOM.
     2. negative margin-top pulls it up INTO the card row so the riser is
        occluded. In vw so the overlap scales with everything else.
     3. overflow-x: clip on the wrapper. The art extends past the left edge by
        design, and without clipping that is a horizontal scrollbar. `clip`
        rather than `hidden` so it never becomes a scroll container.

     Requires a source with a real alpha channel. A PNG flattened onto white
     looks identical here and shows a hard rectangle the moment it crosses the
     card. Verified: alpha survives the webp/avif pipeline intact. */
  /* 🔴 -3.5vw is measured, not chosen. Scanning the artwork's alpha channel:
     the purple riser reaches the very top edge (y=0) but the cyan loop on the
     left does not start until y=124 of 874, i.e. 14.2% down. Rendered at 58vw
     that is 74px. Overlap more than that and card 1 slices the cyan loop flat,
     which is exactly what -10vw was doing. 3.85vw is break-even; 3.5 leaves a
     margin. The riser is still occluded because it starts at the top edge. */
  /* Full-bleed separator. The artwork's opaque pixels run to BOTH edges
     (x=0 and x=1918 of 1920), so it spans the viewport rather than the 1820px
     panel, and needs no mirroring. Negative block margins pull the two
     neighbouring sections in: back to back they carry --gap-section on each
     side, which left the blob floating in roughly 400px of nothing. */
  .divider-blob { width: 100%; overflow-x: clip; pointer-events: none;
                  margin-block: clamp(-90px, -5vw, -24px); }
  /* The Voice AI section sits directly under the separator, which is pulled up
     by a negative margin. Without its own top gap the heading would collide
     with the blob's lower curve. */
  .voiceai-section { padding-top: clamp(3rem, 7.8vw, 9.375rem); }
  /* The staircase is not centred in its frame: the subject spans x 846-1620 of
     1920, midpoint 64.2%. A default 50% crop would slice the right-hand steps
     off entirely. 73% centres it in the 717px the portrait blob can show.
     ⚠️ The subject is 774px wide against a 717px crop, so ~57px is lost no
     matter what; 73% splits that evenly rather than dumping it on one side. */
  .blob.stairs img { object-position: 73% 50%; }
  .divider-blob img { width: 100%; height: auto; display: block; }

  .ribbon-wrap { position: relative; z-index: 1; overflow-x: clip;
                 margin-top: -3.5vw; pointer-events: none; }
  .ribbon { display: block; width: 58vw; margin-left: -4vw; }
  /* The section after the ribbon carries less top padding than a normal
     section: the ribbon already occupies that space, so a full --gap-section
     on top of it reads as a hole. */
  /* No overflow clip here: it would clip the knot back to the section's own
     edge, which is the very thing we are pulling it out of. body clips instead. */
  .ribbon-wrap + section { padding-top: clamp(2rem, 6.25vw, 7.5rem);
                           position: relative; }

  /* ---- the knot, bottom-left of section 02 -------------------------------
     Absolute so it can sit at the VIEWPORT's left edge rather than the 150px
     content inset, and z-index -1 so it stays behind the copy.

     🔴 scaleX(-1) is not decoration. The artwork is cropped hard on its RIGHT
     edge (opaque pixels run to x=1188 of 1189) and has ~15% transparent
     padding on its LEFT. Placed unflipped at the left edge you would get a gap
     of nothing before the shape starts, then a straight vertical cut ending
     mid-page. Mirrored, the hard cut lands on the viewport edge where a crop
     is invisible, and the organic side faces into the page. One line to
     revert: delete the transform. */
  /* 🔴 left is NOT 0. The section is capped at --maxw (1920) and centred, so
     above 1920px of viewport `left: 0` is the SECTION's edge, not the screen's,
     and the knot drifts inward: 133px at 2200, 313px at 2560, 753px at 3440.
     It looked correct only on windows narrower than 1920.
     calc((100% - 100vw)/2) is the section's own centring offset, negated, so
     the knot lands on the viewport edge at every width. 100vw includes the
     scrollbar, so this overshoots by a few px; body's overflow-x: clip eats it,
     and overshooting is the safe direction because a gap would be visible. */
  .gap-knot { position: absolute; left: calc((100% - 100vw) / 2); bottom: 0;
              z-index: -1; width: 32vw; pointer-events: none;
              transform: scaleX(-1); }
  .gap-knot img { width: 100%; height: auto; display: block; }
  @media (max-width: 900px) { .gap-knot { display: none; } }
  /* the <img> must be sized too: width on <picture> alone does not size it */
  .ribbon img { width: 100%; height: auto; display: block; }
  @media (max-width: 900px) {
    /* Cards stack here, so "behind card 2" stops meaning anything. It reads as
       a divider between the row and section 2 instead. */
    .ribbon-wrap { margin-top: -6vw; }
    /* 🔴 150/-25, not 118/-18. At 118vw the ribbon spanned x -70 to 390 in a
       390px viewport, so its right edge landed EXACTLY on the screen edge and
       the artwork's hard right crop read as the shape being chopped off.
       Ansh: "increase the size of that blob a bit more so it goes out of the
       viewport, so that hanging edge cuts off". At 150vw it runs -97 to 487,
       so the crop happens ~97px off-screen and the shape simply bleeds.
       Safe: .ribbon-wrap clips overflow-x and body clips again, so nothing
       here can produce a horizontal scrollbar. */
    .ribbon { width: 150vw; margin-left: -25vw; }
  }

  /* ---- 02 the gap: divided list ------------------------------------------ */
  .split { display: grid; grid-template-columns: 720fr 700fr; gap: clamp(24px,7.8vw,150px); }
  /* Image-right variant. The fr values swap too, so the image column stays the
     wider of the two exactly as it was on the left; reordering the DOM alone
     would have handed the image the narrower 700 column and shrunk it. */
  .split-rev { grid-template-columns: 700fr 720fr; }
  /* ⛔ Tried and reverted 2026-08-15: `.split > .h1 { align-self: center }`
     dropped section 02's headline into the gap above the decoration. Ansh:
     "looks kinda weird now". If the gap needs closing later, do it with an
     explicit margin-top on that one headline rather than centring, which moves
     it by an amount that depends on how tall the column beside it happens to
     be. */
  @media (max-width: 950px) { .split { grid-template-columns: 1fr; } }
  .items { display: grid; }
  .item { display: grid; grid-template-columns: auto 1fr; gap: var(--gap-inner);
          padding-block: var(--gap-inner); }
  .item + .item { border-top: 2px solid var(--line-strong); }
  .item .dot { width: clamp(48px,5.2vw,100px); aspect-ratio: 1; border-radius: 50%;
               background: var(--panel); }
  /* A bullet holding artwork drops the grey disc and the circular clip: the
     asset carries its own silhouette, and clipping it to a circle would shear
     off the outer bobbles that make it read as a blob. display:block because
     <picture> is inline by default and would ignore the sizing above. */
  .item .dot.art { background: none; border-radius: 0; display: block; }
  .item .dot.art img { width: 100%; height: 100%; object-fit: contain; display: block; }
  .item h3 { font-size: var(--t-h2); line-height: var(--lh-h2); letter-spacing: var(--ls);
             margin-bottom: 12px; }

  /* ---- marquee statement -------------------------------------------------- */
  .statement { padding-block: var(--gap-block); }
  /* The close-CTA row. Two .btn siblings sat in the statement paragraph with no
     gap at all: measured 5px on an HTML page, which is a collapsed whitespace
     text node rather than a designed gap, and 0px on the React blog, because
     JSX strips whitespace that contains a newline. Same primitive as .hero-cta,
     not a margin hack, so the buttons also wrap correctly on a narrow screen
     instead of carrying a stranded left margin onto the second line. */
  .statement p:has(> .btn) { display: flex; gap: var(--gap-tight); flex-wrap: wrap; }

  /* ---- scroll-driven reveal ------------------------------------------------
     Words fade up left-to-right as the block crosses the viewport.

     🔴 FAIL-SAFE BY DESIGN: the base .tw state is fully opaque. JS adds
     .tw-on to START dimming. If the script throws, never runs, or the browser
     blocks it, the statement renders as normal readable text rather than
     invisible. The reverse (hidden by default, revealed by JS) is the usual
     way this is written and it silently eats content when JS fails.

     One custom property is written per frame on the container; each word
     derives its own opacity in CSS from --p, --n and --i, so a 14-word line
     costs one style write per frame rather than fourteen. */
  /* pre-WRAP, not pre. pre preserves the spaces but also forbids the word from
     breaking, so a word wider than the column runs off the right edge instead
     of wrapping. Measured on /compare/nice/ at 390px: "Enterprise-grade" is
     400px inside a 314px column. pre-wrap keeps the spaces AND lets an
     over-long word break, and overflow-wrap handles one with no hyphen to
     break at. Neither changes anything at a width where the word already fits. */
  .tw w { display: inline-block; white-space: pre-wrap; overflow-wrap: break-word; }
  .tw-on w { opacity: clamp(0.06, calc((var(--p) * var(--n)) - var(--i)), 1); }
  @media (prefers-reduced-motion: reduce) { .tw-on w { opacity: 1; } }

  /* ---- 03 KCR: blob + ghost ---------------------------------------------- */
  /* The notched silhouette is kept and the photo is clipped INTO it, so the
     signature motif survives and the image is still a real <picture> with a
     srcset. The clipPath uses clipPathUnits="objectBoundingBox" (path
     normalised 0-1) so it scales with the element; a CSS clip-path: path()
     would be fixed pixels and would not. aspect-ratio must stay 850/1280 or
     the normalised corner radii distort. */
  .blob { width: 100%; max-width: 850px; aspect-ratio: 850/1280; display: block;
          clip-path: url(#kcr-blob-clip); background: var(--panel); }
  .blob img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .kcr-section { position: relative; }
  .kcr-def { border-left: 2px solid var(--line-strong); padding-left: var(--gap-inner); }
  /* Same shape as the FAQ list: a heading at h2 size, body under it, divided by
     a rule. One pattern for "a list of things worth reading properly". */
  .kcr-list { margin: 0; padding: 0; list-style: none; }
  .kcr-list li { border-bottom: 2px solid var(--line-strong);
                 padding-block: var(--gap-inner); }
  .kcr-list b { display: block; font-size: var(--t-h2); line-height: var(--lh-h2);
                letter-spacing: var(--ls); font-weight: var(--w-regular);
                color: var(--ink); margin-bottom: 10px; }
  .kcr-list span { color: var(--ink-soft); max-width: 62ch; display: block; }

  /* blob 4: bottom-RIGHT of the KCR section, under the staircase image, hugging
     the viewport's right edge. The calc negates the section's own centring
     offset, which is needed because the section caps at --maxw and centres, so
     a plain `right: 0` would be the SECTION's edge above 1920px, not the
     screen's.
     🔴 NO scaleX(-1) here, and that is the point: the artwork is cropped hard on
     its RIGHT (opaque to x=1198 of 1200) with ~32px of transparent padding on
     the left. Unflipped, that hard crop sits on the right viewport edge where a
     crop cannot be seen. It carried a flip only while it lived on the left. */
  .kcr-blob { position: absolute; right: calc((100% - 100vw) / 2); bottom: 0;
              z-index: -1; width: 26vw; pointer-events: none; }
  .kcr-blob img { width: 100%; height: auto; display: block; }
  @media (max-width: 900px) { .kcr-blob { display: none; } }

  /* ---- 04 stages: numbered 2x2 ------------------------------------------- */
  .stages { display: grid; grid-template-columns: 1fr 1fr;
            column-gap: clamp(24px,5.7vw,110px); row-gap: var(--gap-block); }
  @media (max-width: 900px) { .stages { grid-template-columns: 1fr; } }
  .stage { display: grid; grid-template-columns: auto 1fr; gap: var(--gap-inner); }
  .num { font-size: var(--t-display); line-height: 1; font-weight: var(--w-outline);
         color: transparent; -webkit-text-stroke: 2px var(--ink);
         writing-mode: vertical-rl; transform: rotate(180deg); height: max-content; }

  /* ---- 05 cards with overlapping caption --------------------------------- */
  .cards { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap-inner); align-items: start; }
  @media (max-width: 900px) { .cards { grid-template-columns: 1fr; } }
  /* display:block because <picture> is inline by default; overflow:hidden so
     the photo is actually clipped by the 50px radius rather than squaring it. */
  .card .img { aspect-ratio: 700/719; background: var(--panel); display: block;
               border: 1px solid var(--ink); border-radius: var(--r-card);
               overflow: hidden; }
  /* Both card images are 1920x2597 portrait (0.739) in a 0.974 near-square box,
     so width governs and ~24% of the height is cropped. Both are the same
     colonnade series with the vanishing arch sitting slightly above centre, so
     45% lifts the crop to keep the arch and drop empty floor. The SAME value on
     both is what makes them read as a pair rather than two arbitrary crops. */
  .card .img img { width: 100%; height: 100%; object-fit: cover; display: block;
                   object-position: 50% 45%; }
  /* 🔴 position:relative is what keeps the white card ON TOP of the photo.
     CSS paints block backgrounds (step 4) BEFORE inline/replaced content
     (step 7), so an in-flow <img> renders over the background of a later
     sibling block. It worked while .img was an empty div and broke the moment
     it held a real photo. Positioning the caption moves it to step 8. */
  .card .cap { margin-top: -28%; background: var(--ground); border: 1px solid var(--ink);
               border-radius: var(--r-card); padding: clamp(20px,2.6vw,50px);
               position: relative; z-index: 1; }
  .card .cap h3 { font-size: var(--t-h2); line-height: var(--lh-h2);
                  letter-spacing: var(--ls); margin-bottom: 12px; }
  .offset { margin-top: clamp(24px,7.8vw,150px); }

  /* ---- 06 pricing --------------------------------------------------------- */
  .tiers { display: grid; gap: clamp(24px,3.9vw,75px); }
  .tier { border: 1px solid var(--ink); border-radius: var(--r-panel);
          padding: clamp(24px,2.8vw,54px); display: grid;
          grid-template-columns: 1fr auto; gap: var(--gap-inner); position: relative; }
  @media (max-width: 800px) { .tier { grid-template-columns: 1fr; } }
  /* The FREE tier is the dark one now: it is the headline offer, so it carries
     the weight. The paid tiers are outline-only and sit beside the copy. */
  .tier-hero { background: var(--dark); border-color: var(--dark); }
  /* The tab used to sit on a dark card, so it was white-on-dark. On an
     outlined white card that is invisible; it takes the accent instead. */
  .tier .tab { background: var(--accent); color: #fff; }
  /* Paid tiers, stacked under the headline in the left column. */
  .tiers-paid { display: grid; gap: clamp(16px,1.6vw,30px);
                margin-top: var(--gap-block); }
  /* ---- pricing column order ----------------------------------------------
     The paid tiers are a SIBLING of the copy, not nested inside it, purely so
     that mobile can reorder them independently. Desktop is unchanged and is
     held by explicit placement: copy top-left, paid tiers under it, the free
     block filling the right column across both rows. */
  @media (min-width: 951px) {
    #pricing .pricing-copy { grid-column: 1; grid-row: 1; }
    #pricing .tiers-paid   { grid-column: 1; grid-row: 2; }
    #pricing .tiers        { grid-column: 2; grid-row: 1 / span 2; }
  }
  /* Ansh, 2026-08-21: "the free dark block should appear first and then the
     priced 2 below it." It is the headline offer, so on a phone it leads. */
  @media (max-width: 950px) {
    #pricing .split { display: flex; flex-direction: column; }
    #pricing .tiers      { order: 1; }
    #pricing .tiers-paid { order: 2; }
  }
  .tiers-paid .tier { grid-template-columns: 1fr; gap: clamp(12px,1vw,18px); }
  .tiers-paid .price { font-size: var(--t-h2); line-height: 1.15; }
  .tier ul { margin: 0; padding-left: 1.4em; display: grid; gap: 6px; }
  .tier .price { font-size: var(--t-h1); line-height: var(--lh-h1); letter-spacing: var(--ls); }

  /* The free tier holds three volume rows, so it runs as one column rather
     than the copy/features split the paid tiers use. */
  .tier-stack { grid-template-columns: 1fr; }
  .tier-rows { display: grid; }
  .tier-row { display: grid; grid-template-columns: 1fr auto; align-items: center;
              gap: var(--gap-inner); padding-block: clamp(16px,1.6vw,30px); }
  .tier-row + .tier-row { border-top: 1px solid var(--line); }
  .tier-row .amt { font-size: var(--t-h2); line-height: 1.1; letter-spacing: var(--ls);
                   display: block; }
  .tier-row .lead { font-size: 0.86em; }
  @media (max-width: 620px) { .tier-row { grid-template-columns: 1fr; } }

  .btn-sm { min-height: 40px; padding: 10px clamp(18px,1.6vw,30px);
            font-size: 0.82em; }
  .tier .tab { position: absolute; top: 0; left: clamp(24px,5vw,100px); translate: 0 -50%;
               background: #fff; color: #000; border-radius: var(--r-card);
               padding: 6px clamp(16px,2vw,40px); font-size: var(--t-body); }

  /* ---- 07 dark band ------------------------------------------------------- */
  /* The band keeps its own, larger padding. It is a filled block, so this is
     internal breathing room rather than a gap between two things, and it does
     not double up against a neighbour the way section padding does. */
  .band { background: var(--dark); padding-block: clamp(3rem, 7.8vw, 9.375rem); }
  /* ---- verifier tabs -------------------------------------------------------
     Pills select which panel shows. Resting pill is a lifted shade of the band
     itself so it reads as part of the surface; the active one goes solid white
     so "which is selected" is unmissable rather than a subtle tint. */
  .pills { display: flex; flex-wrap: wrap; gap: clamp(10px,1vw,18px);
           margin-top: var(--gap-block); }
  /* 0.86em, not 0.9em. 0.9 rendered 19.8px, a SEVENTH size on a six-size
     scale, on every page with pills. 0.86em is the scale step the site's other
     small buttons already use (.nav-actions .btn). */
  .pill { font: inherit; font-size: 0.86em; letter-spacing: var(--ls);
          color: var(--ink-onDark); background: var(--dark-lift);
          border: 1px solid var(--dark-lift); border-radius: var(--r-pill);
          padding: clamp(10px,0.9vw,16px) clamp(18px,1.7vw,32px);
          min-height: 44px; cursor: pointer; display: inline-flex; align-items: center;
          gap: 10px; transition: background-color var(--dur) var(--ease); }
  .pill:hover { background: var(--dark-lift-2); }
  .pill[aria-selected="true"] { background: #fff; border-color: #fff; color: #0B1620; }
  .pill i { width: 8px; height: 8px; border-radius: 50%; background: currentColor;
            flex: none; opacity: .5; }
  .pill[aria-selected="true"] i { opacity: 1; background: var(--accent); }

  /* 🔴 All three panels occupy the SAME grid cell, so the container is always as
     tall as the tallest one and switching tabs never shifts the page. The
     inactive ones stay in flow with visibility:hidden rather than display:none,
     which is what makes them still contribute their height.
     A fixed height would have been the obvious alternative and it is worse: at
     a narrower viewport the copy reflows longer and overflows the box. This
     adapts to whatever the copy turns out to be.
     visibility:hidden also removes them from the accessibility tree and from
     tab order, so it is not display:none in a costume. */
  .panels { margin-top: clamp(20px,2vw,40px); display: grid; }
  .vpanel { grid-area: 1 / 1; }
  .vpanel[hidden] { display: grid; visibility: hidden; pointer-events: none; }
  /* 🔴 THE CONTAINER IS CONSTANT AND THE IMAGE ADAPTS TO IT, never the reverse.
     min-height on the PANEL fixes the box; the three sources are 1920x1440,
     1920x2743 and 1920x1371, so anything that lets the image drive height makes
     the section jump every time a tab is switched.

     .visual must be display:block: it is a <picture>, which is inline by
     default, and an inline box ignores min-height and gives height:100% nothing
     to resolve against. That is exactly what broke it.

     The img is absolutely positioned so it cannot contribute to layout height
     at all, then object-fit:cover crops it to whatever the box turned out to be. */
  .vpanel { display: grid; grid-template-columns: 700fr 927fr;
            min-height: clamp(320px, 33vw, 620px);
            background: var(--ground); border-radius: var(--r-card); overflow: hidden; }
  @media (max-width: 900px) { .vpanel { grid-template-columns: 1fr; } }
  .vpanel .visual { display: block; position: relative; background: var(--mid);
                    overflow: hidden; }
  @media (max-width: 900px) { .vpanel .visual { min-height: 240px; } }
  .vpanel .visual img { position: absolute; inset: 0;
                        width: 100%; height: 100%; object-fit: cover; display: block; }
  /* The panel body is a LIGHT island inside a dark band, so it opts out of the
     inherited .on-dark ink rather than fighting it declaration by declaration. */
  .vpanel .body { padding: clamp(24px,4.2vw,80px); display: grid; gap: var(--gap-inner);
                  align-content: center;
                  --ink: #000; --ink-soft: #565A62; --accent: #0F6FB0;
                  color: var(--ink); }
  /* The panel is a light island, so it must also opt out of `.on-dark
     .btn-accent`, which paints the label dark for use on the light blue. Here
     the fill is the DEEP blue, so the label goes back to white (5.36:1). */
  .vpanel .body .btn-accent { color: #fff; }

  .chip { background: var(--panel); color: #000; border-radius: var(--r-card);
          min-height: clamp(88px,10.4vw,200px); display: flex; align-items: center;
          gap: var(--gap-inner); padding: clamp(20px,2.6vw,50px); }
  .chip i { width: clamp(44px,5.2vw,100px); aspect-ratio: 1; border-radius: 50%;
            background: var(--dark); flex-shrink: 0; }
  .bigstats { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap-inner);
              margin-top: var(--gap-block); }
  @media (max-width: 800px) { .bigstats { grid-template-columns: 1fr; } }

  /* ---- 08 FAQ ------------------------------------------------------------- */
  /* blob-10 is cropped hard on its LEFT (opaque from x=0) and clean on the
     right, so it is built to bleed off the left edge rather than sit standalone
     the way blob-9 did. Stays in normal flow under the headline and is pulled
     out to the viewport edge with a negative margin.

     🔴 The margin deliberately avoids the calc((100% - 100vw)/2) form used
     elsewhere. That expression overshoots by half a scrollbar, because 100vw
     includes the scrollbar and 100% does not, and it caused a real horizontal
     scrollbar when the KCR decoration moved to the right edge. This version
     subtracts the content inset and the section's own centring offset directly,
     so it lands exactly on the edge with nothing to clip. */
  .faq-rings { display: block; width: 30vw; max-width: 800px;
               margin-top: clamp(28px,3.1vw,60px);
               margin-left: calc(-1 * var(--content-inset)
                                 - max(0px, (100vw - var(--maxw)) / 2)); }
  .faq-rings img { width: 100%; height: auto; display: block; }
  @media (max-width: 950px) { .faq-rings { display: none; } }

  .faq details { border-bottom: 2px solid var(--line-strong); padding-block: var(--gap-inner); }
  .faq summary { font-size: var(--t-h2); line-height: var(--lh-h2); letter-spacing: var(--ls);
                 cursor: pointer; list-style: none; display: flex; justify-content: space-between;
                 gap: 24px; min-height: 44px; align-items: center; }
  .faq summary::-webkit-details-marker { display: none; }
  .faq summary::after { content: "+"; font-weight: var(--w-regular); }
  .faq details[open] summary::after { content: "\2013"; }
  .faq .a { margin-top: var(--gap-inner); max-width: 62ch; color: var(--ink-soft); }

  /* ---- footer ------------------------------------------------------------- */
  /* ---- footer -------------------------------------------------------------
     Two zones inside one dark panel, exactly as Ansh drew it: the statement and
     the link columns on dark, then a white strip inset at the bottom carrying
     the wordmark, legal and social.

     The white strip keeps the notch shape it already had (rounded top corners,
     inset from the panel's sides) rather than becoming a plain bar. That notch
     is the same concave-corner idea as the KCR blob, so the page closes on a
     shape it already used rather than introducing a new one at the very end. */
  .foot { background: var(--dark); color: #fff; border-radius: var(--r-panel);
          padding: clamp(40px,5.2vw,100px) clamp(24px,5.2vw,100px) 0;
          margin-bottom: var(--panel-inset); overflow: hidden; }
  .foot-statement { max-width: 900px; }

  .foot-cols { display: grid; grid-template-columns: repeat(6, 1fr);
               gap: clamp(20px,2.6vw,50px);
               margin-top: clamp(32px,4.2vw,80px); }
  @media (max-width: 1100px) { .foot-cols { grid-template-columns: repeat(3, 1fr); } }
  @media (max-width: 620px)  { .foot-cols { grid-template-columns: repeat(2, 1fr); } }
  .foot-col h4 { margin: 0 0 14px; font-size: 0.78em; font-weight: var(--w-regular);
                 letter-spacing: 0.08em; text-transform: uppercase;
                 color: var(--accent-onDark); }
  .foot-col a { display: block; text-decoration: none; color: var(--ink-onDark-soft);
                font-size: 0.86em; letter-spacing: var(--ls); padding: 7px 0;
                transition: color var(--dur-fast) var(--ease); }
  .foot-col a:hover { color: #fff; }

  /* The white strip. Inset and round-topped so it reads as a plate the dark
     panel sits behind, which is how the notch already behaved. */
  /* 🔴 The plate is a LIGHT island inside .foot.on-dark, so it must opt out of
     the inherited on-dark ink or every token resolves to a pale grey meant for
     a near-black background. Redeclaring the three tokens here fixes the legal
     links, the copyright and the social icons in one place, rather than
     patching each rule with a hardcoded colour. */
  .foot-plate { background: var(--ground);
                --ink: #000; --ink-soft: #565A62; --ink-faint: #8A8F97;
                --accent: #0F6FB0;
                color: var(--ink);
                border-radius: var(--r-card) var(--r-card) 0 0;
                margin-top: clamp(32px,4.2vw,80px);
                margin-inline: clamp(0px,5.2vw,100px);
                padding: clamp(20px,2.1vw,40px) clamp(20px,3.1vw,60px);
                display: grid; grid-template-columns: auto 1fr auto;
                align-items: center; gap: clamp(16px,2vw,40px); }
  @media (max-width: 900px) {
    .foot-plate { grid-template-columns: 1fr; justify-items: center;
                  text-align: center; gap: 18px; }
  }
  .foot-plate img { height: clamp(18px,1.5vw,28px); width: auto; display: block; }
  .foot-legal { display: flex; flex-wrap: wrap; justify-content: center;
                gap: clamp(10px,1.4vw,26px); }
  .foot-legal a { text-decoration: none; color: var(--ink);
                  font-size: 0.78em; letter-spacing: var(--ls);
                  transition: color var(--dur-fast) var(--ease); }
  .foot-legal a:hover { color: var(--accent); }
  .foot-social { display: flex; gap: 12px; align-items: center; }
  .foot-social a { color: var(--ink); display: inline-flex; padding: 8px;
                   transition: color var(--dur-fast) var(--ease); }
  .foot-social a:hover { color: var(--accent); }
  .foot-social svg { width: 18px; height: 18px; }
  .foot-copy { font-size: 0.78em; color: var(--ink-soft); }
