/* --------------------------------------------------------------------------
   ui.css — waiting, on the marketing site.

   The console has the same two things in console.css; this is the version that
   speaks the kit's tokens rather than the console's. Both are driven by the
   same file, console.js, mirrored into this docroot on first request.

   Everything here is decoration. With the script blocked or broken, every
   button still submits and every link still navigates — they simply do it
   without saying so.
   -------------------------------------------------------------------------- */

/* Every page here is a full load - no script router to animate between them.
   The browser's own Cross-Document View Transitions crossfade the old page
   into the new one on navigation, a stylesheet declaration with no script
   involved; unsupported browsers simply navigate as they always have. Off
   under reduced motion, because a fast crossfade is still motion. */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

/* The bar across the top. It does not know how long the request will take, so
   it eases towards nine tenths and waits there: arriving at 100% would be a
   lie, and stalling at 30% reads as broken. */
/* ⚠ STILL NEVER CLAIMS TO KNOW HOW LONG THE REQUEST TAKES - that honesty is
   the whole design and it does not change here: eases toward nine tenths
   and waits, arriving at 100% only once the page actually has. Richer is
   the gradient fill, the glow, and a slow pulse while it is genuinely
   waiting - none of it says anything the bar didn't already say, it just
   says it with more presence. */
.pageload {
  position: fixed; top: 0; left: 0; height: 3px; width: 100%;
  transform-origin: 0 50%; transform: scaleX(0);
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--color-primary-500, #864ffe) 55%, #fff) 0%,
    var(--color-primary-500, #864ffe) 100%);
  box-shadow: 0 0 10px -1px color-mix(in srgb, var(--color-primary-500, #864ffe) 75%, transparent);
  z-index: 2000; pointer-events: none;
}
.pageload-on {
  transform: scaleX(.9);
  transition: transform 8s cubic-bezier(.05,.7,.1,1);
  animation: pageload-pulse 1.4s ease-in-out infinite;
}
.pageload-done { transform: scaleX(1); opacity: 0; animation: none;
                 transition: transform .2s linear, opacity .35s .15s linear; }
@keyframes pageload-pulse {
  0%, 100% { box-shadow: 0 0 10px -1px color-mix(in srgb, var(--color-primary-500, #864ffe) 75%, transparent); }
  50%      { box-shadow: 0 0 18px 1px color-mix(in srgb, var(--color-primary-500, #864ffe) 95%, transparent); }
}

@keyframes rl-spin { to { transform: rotate(360deg); } }

/* The control keeps its label and its width. A spinner that replaces the text
   makes the button jump, and a control that resizes under the cursor is worse
   than one that says nothing at all. */
.is-busy { pointer-events: none; opacity: .75; position: relative; }
button.is-busy::after, a.is-busy::after, .is-busy > .btn-inner::after {
  content: ""; display: inline-block; vertical-align: -.15em;
  width: 1em; height: 1em; margin-left: .5em;
  border-radius: 50%; border: 2px solid currentColor; border-top-color: transparent;
  animation: rl-spin .7s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  button.is-busy::after, a.is-busy::after, .is-busy > .btn-inner::after
       { animation: none; opacity: .5; }
  .pageload-on { transition: none; transform: scaleX(.9); animation: none; }
}

/* ══ the finish ══════════════════════════════════════════════════════════════
   The kit gives the site its bones — the type scale, the container, the
   button component — and the sections are ours. What the kit does not do is
   the last ten per cent: the places where the page is technically correct and
   still reads flat.

   Everything below is a gradient, a shadow or a transform. No images, no
   requests, nothing that can fail to load and leave a hole, and nothing that
   moves unless somebody caused it or the reduced-motion switch allows it.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── the v3 button: a label, and a door in its own colour ─────────────────
   ⚠ REPLACES A FLAT FILLED PILL WITH A SUFFIX ARROW CHARACTER. That
   version worked and is gone anyway: every primary action on the site was
   the same brand-coloured rectangle, and "this is a considered piece of the
   product" was carried entirely by a sheen and a lift. main.css already
   ships the bones of something better and unused - btn-v3-primary/
   btn-v3-secondary and a [data-button-v3-icon] chip, sized by btn-v3-lg -
   nothing here ever wrote the markup that would have used them. kbtn() now
   does: a label on a plain body, and "this goes somewhere" living in its
   own small chip with its own colour, which is what makes it read as a
   built thing rather than a coloured rectangle that happens to be clickable.

   The base shape - flex row, gap, radius, padding - is ours; main.css has
   no unqualified .btn-v3 rule to inherit. The fills, the chip's gradient
   and every bit of motion below are ours too; the kit only hands over a
   flat background-color for each tone and the chip's footprint. */
/* ⚠ THE BOUNCE, ONE TOKEN. A spring overshoot (the curve peaks past 1 before
   settling) rather than an ease-out that only ever approaches its target -
   the difference between a button that stops and one that lands. Shared so
   every hover on the site answers a cursor the same way. */
:root { --bounce: cubic-bezier(.34, 1.56, .64, 1); }

.btn-v3 {
  display: inline-flex; align-items: center; justify-content: center; gap: .85rem;
  border-radius: 999px; padding: 6px 6px 6px 26px;
  text-decoration: none; white-space: nowrap; cursor: pointer; border: none;
  transition: transform .4s var(--bounce), box-shadow .3s ease;
  will-change: transform;
}
.btn-v3:hover, .btn-v3:focus-visible { transform: translateY(-3px) scale(1.035); }
.btn-v3:active { transform: translateY(-1px) scale(.98); transition-duration: .1s; }
.btn-v3[disabled] { opacity: .55; cursor: not-allowed; transform: none; }

/* The chip. Its own small surface, not just an icon sitting in the flow -
   which is the entire difference between "a button with an arrow on it"
   and "a button with a door on it". */
[data-button-v3-icon] {
  flex: none; display: grid; place-items: center;
  width: 2.35rem; height: 2.35rem; border-radius: 999px;
  transition: transform .4s var(--bounce);
}
[data-button-v3-icon] svg { width: 1rem; height: 1rem; transition: transform .4s var(--bounce); }
/* The chip turns and the arrow inside it slides further than the chip does -
   two motions at different rates reading as one thing with real weight to
   it, rather than the whole pill moving as a single rigid unit. */
.btn-v3:hover [data-button-v3-icon] { transform: rotate(16deg) scale(1.15); }
.btn-v3:hover [data-button-v3-icon] svg { transform: translateX(3px); }

/* ── primary: a dark body, the brand colour lives in the chip ───────────
   ⚠ NOT var(--color-primary-500) as the button's own fill. Filling the
   whole pill with the brand colour is what this replaced; putting the
   colour ONLY in the chip is the entire idea, and it is also what makes
   this survive a rebrand better than the old version did - a workspace
   whose brand colour is pale or light now still gets a body with contrast,
   because the body's colour never depended on the brand colour being dark
   enough to read white text on. */
.btn-v3-primary {
  background: var(--color-secondary, #14161a);
  color: #fff;
  box-shadow: 0 6px 18px -10px color-mix(in srgb, var(--color-secondary, #14161a) 65%, transparent);
}
.btn-v3-primary:hover, .btn-v3-primary:focus-visible {
  box-shadow: 0 16px 32px -12px color-mix(in srgb, var(--color-primary-500) 55%, transparent),
              0 4px 10px -4px color-mix(in srgb, var(--color-secondary, #14161a) 45%, transparent);
}
/* The chip: a small radial mesh rather than a flat fill, so it reads as its
   own lit surface rather than a coloured dot pasted on. Two colour-mixes
   off the ONE brand variable - no second colour to keep in step with a
   rebrand, just the same one sampled twice at different strengths.
   Shared with .btn-v3-ghost below - the same chip on every tone is what
   says "one button system" across a card where they sit side by side. */
.btn-v3-primary [data-button-v3-icon], .btn-v3-ghost [data-button-v3-icon] {
  background:
    radial-gradient(120% 130% at 24% 22%,
      color-mix(in srgb, var(--color-primary-500) 55%, #fff) 0%,
      var(--color-primary-500) 45%,
      color-mix(in srgb, var(--color-primary-500) 78%, var(--color-secondary, #14161a)) 100%);
  color: #fff;
  box-shadow: inset 0 1px 1px rgba(255,255,255,.4),
              0 2px 10px -3px color-mix(in srgb, var(--color-primary-500) 75%, transparent);
}
/* A slow idle pulse on the primary chip only - the one button on a busy
   page that is allowed to say "start here" without a cursor asking it to. */
.btn-v3-primary [data-button-v3-icon] { animation: btn-chip-pulse 2.6s ease-in-out infinite; }
@keyframes btn-chip-pulse {
  0%, 100% { box-shadow: inset 0 1px 1px rgba(255,255,255,.4),
                          0 2px 10px -3px color-mix(in srgb, var(--color-primary-500) 75%, transparent); }
  50%      { box-shadow: inset 0 1px 1px rgba(255,255,255,.4),
                          0 2px 16px 0 color-mix(in srgb, var(--color-primary-500) 90%, transparent); }
}

/* ── ghost: kbtn_ghost(), the site's one secondary button ────────────────
   ⚠ REPLACES .btn.btn-white (main.css's plain flat pill) EVERYWHERE this
   function is called - the pricing cards' second and third "Get started",
   "Sign in" on the signup page, "See how it works", "Talk to us", every
   home-page section's secondary CTA. One pale, sometimes-invisible-on-white
   pill with no chip and no motion, reported live as looking unfinished next
   to the primary button's chip and hover-lift. Same .btn-v3 shape, same
   chip (above) - dark text and a border-as-inset-shadow (not a real border,
   so it never adds a pixel .btn-v3-primary doesn't have, and the two stay
   pixel-aligned in a row) are the only tells that this one is secondary. */
.btn-v3-ghost {
  background: #fff; color: var(--color-secondary, #14161a);
  box-shadow: inset 0 0 0 1px var(--color-stroke-1, #dfe1e6);
}
.btn-v3-ghost:hover, .btn-v3-ghost:focus-visible {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-secondary, #14161a) 30%, transparent),
              0 12px 26px -16px rgba(16,24,40,.32);
}

/* ── the light tone, for a dark band ─────────────────────────────────────
   kbtn($tone = 'light'). Body goes white, chip keeps the brand mesh - the
   one thing that must never change tone, since it is the constant that
   says "primary action" everywhere this button appears. */
.btn-v3-white {
  background: #fff; color: var(--color-secondary, #14161a);
  box-shadow: 0 6px 18px -10px rgba(16,24,40,.35);
}
.btn-v3-white [data-button-v3-icon] {
  background:
    radial-gradient(120% 130% at 24% 22%,
      color-mix(in srgb, var(--color-primary-500) 55%, #fff) 0%,
      var(--color-primary-500) 45%,
      color-mix(in srgb, var(--color-primary-500) 78%, var(--color-secondary, #14161a)) 100%);
  color: #fff;
}

@media (prefers-reduced-motion: reduce) {
  .btn-v3, [data-button-v3-icon], [data-button-v3-icon] svg { transition: none; }
  .btn-v3:hover, .btn-v3:focus-visible, .btn-v3:active { transform: none; }
  .btn-v3:hover [data-button-v3-icon] { transform: none; }
  .btn-v3:hover [data-button-v3-icon] svg { transform: none; }
  .btn-v3-primary [data-button-v3-icon] { animation: none; }
}

/* ── the Google button ────────────────────────────────────────────────────
   google_marketing_btn() in inc/shell.php. Not built on the kit's .btn: that
   class lowercases its label by house style (see kbtn()'s own comment on
   first-letter:uppercase), which a brand mark's own name cannot survive —
   "continue with google" under a four-colour G reads as a mistake, not a
   style. Deliberately plain rather than brand-tinted: this button's whole
   job is to look like the one Google's own product uses, everywhere, so a
   visitor recognises it before they read it. */
/* ⚠ SIZED OFF .btn-lg/.btn-xl, not guessed. This button stands next to
   kbtn()'s own output everywhere it appears, and the two were built from
   different numbers - close, not equal, which is exactly the kind of
   mismatch that reads as "not properly aligned" without anybody being able
   to say what's wrong. padding-block/inline copied from the kit's own
   tokens (main.css .btn-lg, .btn-xl at the md breakpoint) so the two
   buttons share a baseline and a height at every width. */
.btn-google {
  display: inline-flex; align-items: center; justify-content: center; gap: .65rem;
  width: 100%; box-sizing: border-box;
  padding-block: .625rem; padding-inline: 1.5rem; border-radius: 999px;
  border: 1px solid var(--color-stroke-1, #dfe1e6); background: #fff;
  color: var(--color-secondary); font-size: 1rem; font-weight: 500;
  text-decoration: none; white-space: nowrap;
  transition: border-color .2s ease, box-shadow .2s ease, transform .2s var(--bounce);
}
@media (min-width: 768px) {
  .btn-google { padding-block: .875rem; padding-inline: 2rem; }
}
.btn-google svg { flex: none; width: 18px; height: 18px; }
/* The signed-in "Continue as :name" state's initial dot - same look as the
   header's own .acct-dot, sized to sit where the Google mark otherwise does. */
.btn-google-dot {
  display: grid; place-items: center; width: 18px; height: 18px; flex: none;
  border-radius: 50%; font-size: .68rem; font-weight: 700; line-height: 1;
  color: #fff; background: var(--color-primary-500, #864ffe);
}
.btn-google:hover, .btn-google:focus-visible {
  border-color: color-mix(in srgb, var(--color-secondary) 25%, transparent);
  box-shadow: 0 6px 16px -8px rgba(16, 24, 40, .28);
  transform: translateY(-2px);
}
.btn-google:active { transform: translateY(0); }

/* ── the divider above it ─────────────────────────────────────────────────
   Without this the Google button just sat under (or beside) the primary
   action with nothing saying they were alternatives rather than two steps
   of the same thing - press one, then the other. */
.cta-or {
  position: relative; width: 100%; max-width: 22rem; margin: .35rem auto;
  text-align: center; font-size: .78rem; font-weight: 500;
  color: color-mix(in srgb, var(--color-secondary) 45%, transparent);
}
.cta-or::before, .cta-or::after {
  content: ""; position: absolute; top: 50%; width: calc(50% - 1.4rem);
  height: 1px; background: var(--color-stroke-1, #e6e8ec);
}
.cta-or::before { left: 0; }
.cta-or::after  { right: 0; }

@media (prefers-reduced-motion: reduce) {
  .btn-google { transition: border-color .2s ease; }
  .btn-google:hover, .btn-google:focus-visible { transform: none; }
}

/* ── the case for it ─────────────────────────────────────────────────────
   One dark card, argued rather than counted. It carries .dark-wash, so the
   drifting light and the dot grid come from the block above; everything here
   is the layout and the type on top of it.

   (The .proof-strip rules that used to sit here went with the section. A
   stylesheet keeping rules for markup nothing emits is how a sheet gets to
   three thousand lines nobody dares touch.) */
/* ⚠ White text and no background. .dark-wash only draws the glow and the
   grid — the DARK came from the kit's bg-secondary class on the two sections
   that had it, and this card was written without one. So the whole block
   rendered as near-white text on a near-white page: present, selectable,
   and invisible. The background belongs to the component that assumes it. */
.about-card {
  background: var(--color-secondary);
  border-radius: 24px; padding: 3rem; color: rgba(255,255,255,.68);
}
@media (max-width: 640px) { .about-card { padding: 2rem 1.5rem; border-radius: 20px; } }
/* ⚠ center, not start. The two columns are rarely the same natural height -
   four checkmarks against a heading, a blurb and a button - and align-items:
   start pinned both to the TOP of the row, so the shorter one just left a
   gap of dead card underneath it instead of sitting level with the other. */
.about-grid { display: grid; gap: 2.5rem; align-items: center; }
/* ⚠ A grid child defaults to min-width: auto, which refuses to shrink below
   its longest unbreakable word — the classic way a heading pushes a two-column
   grid wider than its container and puts a scrollbar on the page. */
.about-say { min-width: 0; }
@media (min-width: 1024px) {
  .about-card { padding: 3.75rem; }
  .about-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 4rem; }
}
.about-badge {
  display: inline-block; padding: .4rem 1rem; border-radius: 999px;
  background: rgba(255,255,255,.10); border: 1px solid rgba(255,255,255,.14);
  color: rgba(255,255,255,.82); font-size: .8rem;
}
.about-title {
  color: #fff; margin: 1.25rem 0 0; max-width: 14ch;
  font-size: clamp(2rem, 3.4vw, 3rem); line-height: 1.1; letter-spacing: -.03em; font-weight: 500;
}
/* ⚠ The same trap as the footer, one section earlier: the kit's build colours
   paragraphs directly, so `color` on the card does not reach them and the one
   paragraph on a dark ground rendered near-black on near-black. Say it on the
   element. Twice in one file is a pattern, not an accident — anything of ours
   that inverts the ground states its own text colour. */
.about-blurb { margin-top: 1.1rem; max-width: 42ch; line-height: 1.65;
               color: rgba(255,255,255,.68); }
.about-points li, .about-points li > span { color: rgba(255,255,255,.78); }
.about-cta {
  display: inline-flex; align-items: center; margin-top: 2rem;
  padding: .8rem 1.6rem; border-radius: 999px;
  background: rgba(255,255,255,.10); border: 1px solid rgba(255,255,255,.16);
  color: #fff; text-decoration: none; font-size: .95rem;
  transition: background .25s ease, border-color .25s ease, transform .25s var(--bounce);
}
.about-cta:hover { background: #fff; border-color: #fff; color: var(--color-secondary);
                   transform: translateY(-2px); }

.about-points { list-style: none; margin: 0; padding: 0;
                display: flex; flex-direction: column; gap: 1.35rem; }
.about-points li { display: flex; gap: .9rem; align-items: flex-start; line-height: 1.6; }
.about-tick {
  flex: none; display: flex; align-items: center; justify-content: center;
  width: 1.6rem; height: 1.6rem; margin-top: .1rem; border-radius: 999px;
  background: rgba(255,255,255,.12); color: #fff;
  transition: background .25s ease, transform .25s var(--bounce);
}
.about-points li:hover .about-tick {
  background: var(--color-primary-500); transform: scale(1.08);
}

/* ⚠ Default state is fully visible with no transform - safe with no script,
   or before the observer fires. [data-stagger] only ADDS a one-time entrance
   once `.stagger-in` lands (see reveal.js); it never hides anything, which is
   why this can sit inside the card's own data-ns-animate fade without the
   double-hide problem .reveal has to guard against. nth-child rather than an
   inline custom property: style-src 'self' discards a style attribute same
   as it does a <style> block, so the delay has to be a selector, not a value. */
[data-stagger] > li { opacity: 1; transform: none; }
[data-stagger].stagger-in > li {
  animation: about-tick-in .5s var(--bounce) backwards;
}
[data-stagger].stagger-in > li:nth-child(1) { animation-delay: .05s; }
[data-stagger].stagger-in > li:nth-child(2) { animation-delay: .16s; }
[data-stagger].stagger-in > li:nth-child(3) { animation-delay: .27s; }
[data-stagger].stagger-in > li:nth-child(4) { animation-delay: .38s; }
[data-stagger].stagger-in > li:nth-child(5) { animation-delay: .49s; }
[data-stagger].stagger-in > li:nth-child(6) { animation-delay: .60s; }
@keyframes about-tick-in {
  from { opacity: 0; transform: translateX(-10px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .about-cta, .about-tick { transition: none; }
  [data-stagger] > li { animation: none !important; }
}

/* ── the dark sections ────────────────────────────────────────────────────
   The policy-gate block and the closing call to action were flat near-black
   rectangles: correct, and the two places on the page where a reader is meant
   to feel something. One soft light in each, in the brand colour, drifting
   slowly enough to read as depth rather than as animation. */
.dark-wash { position: relative; overflow: hidden; isolation: isolate; }
.dark-wash::before, .dark-wash::after {
  content: ""; position: absolute; z-index: -1; border-radius: 50%;
  filter: blur(100px); pointer-events: none;
}
.dark-wash::before {
  width: 40rem; height: 40rem; top: -16rem; right: -12rem;
  background: var(--color-primary-500, #864ffe); opacity: .38;
  animation: wash-a 40s ease-in-out infinite alternate;
}
.dark-wash::after {
  width: 28rem; height: 28rem; bottom: -14rem; left: -10rem;
  background: var(--color-primary-500, #864ffe); opacity: .16;
  animation: wash-b 52s ease-in-out infinite alternate;
}
@keyframes wash-a { to { transform: translate3d(-3rem, 3rem, 0) scale(1.15); } }
@keyframes wash-b { to { transform: translate3d(3rem, -2rem, 0) scale(1.2); } }

/* A dot grid over the dark, faded out where the copy sits. Same reasoning as
   the sign-in screen: it is what stops a large blurred gradient reading as a
   rendering artefact. */
.dark-wash > .wash-grid {
  position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background-image: radial-gradient(rgba(255,255,255,.13) 1px, transparent 1px);
  background-size: 26px 26px;
  -webkit-mask-image: linear-gradient(to bottom, #000, rgba(0,0,0,.4) 55%, transparent 85%);
          mask-image: linear-gradient(to bottom, #000, rgba(0,0,0,.4) 55%, transparent 85%);
}

/* ── cards that answer the pointer ────────────────────────────────────────
   The kit's cards are static. A card that lifts says it is a thing rather
   than a paragraph with a border, and it costs one transform. */
.lift { transition: transform .3s var(--bounce),
                    box-shadow .3s var(--bounce),
                    border-color .3s var(--bounce); }
.lift:hover { transform: translateY(-4px);
              box-shadow: 0 2px 6px rgba(18,22,31,.05), 0 26px 50px -28px rgba(18,22,31,.35); }

/* ── the FAQ ──────────────────────────────────────────────────────────────
   A native <details> with the browser's own triangle, which is the one piece
   of default browser furniture nothing else on this page resembles. */
.faq-q { list-style: none; cursor: pointer; display: flex; align-items: center; gap: .75rem; }
.faq-q::-webkit-details-marker { display: none; }
.faq-q::marker { content: ""; }
.faq-q::before {
  content: ""; flex: none; width: .9rem; height: .9rem;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(-45deg) translate(-.1em, -.1em); opacity: .45;
  transition: transform .25s var(--bounce), opacity .25s ease;
}
[open] > .faq-q::before { transform: rotate(45deg) translate(-.05em, -.05em); opacity: 1; }
.faq-q:hover::before { opacity: 1; }

/* ── the gate cards ────────────────────────────────────────────────────────
   .lift's shadow is a neutral grey tuned for a white card on a light page;
   painted here it would vanish into the near-black band. Its own hover, lit
   from the brand instead, and the icon chip brightens with it so the card
   reads as switching on rather than just moving. */
.gate-card { transition: background .3s ease, transform .3s var(--bounce); }
.gate-card:hover {
  background: rgba(255,255,255,.09) !important;
  transform: translateY(-7px) scale(1.02);
}
.gate-card-ico { transition: background .3s ease, color .3s ease; }
.gate-card:hover .gate-card-ico {
  background: color-mix(in srgb, var(--color-primary-500) 32%, transparent) !important;
}

@media (prefers-reduced-motion: reduce) {
  .dark-wash::before, .dark-wash::after { animation: none; }
  .lift, .faq-q::before, .gate-card, .gate-card-ico { transition: none; }
}

/* ── the reveal guard ─────────────────────────────────────────────────────
   ⚠ The kit's own stylesheet carries

       [data-ns-animate], [data-text-reveal] { opacity: 0 }

   and only its JavaScript ever puts that back. Every section body on this
   site has one of those attributes, so a page whose animation library does
   not arrive is a header, a footer and nothing else — silently, with the page
   reporting itself as loaded.

   So the hiding is conditional now: it applies only while the document is
   marked as animating, which assets/reveal.js does in the head before the
   first paint and takes back if nothing has been revealed a moment after
   load. With no script at all, the site is simply a site that does not
   animate — which is what it should always have been. */
html:not(.ns-ready) [data-ns-animate],
html:not(.ns-ready) [data-text-reveal] { opacity: 1 !important; }

/* ⚠ A reduced-motion visitor must not be shown a blank page below the hero
   while a scroll-triggered reveal they asked us not to play waits for a scroll.
   The kit's JS does not check the preference, so this does: show the content
   immediately, in whatever reveal state the page is in. reveal.js's .reveal
   opt-in already has its own reduced-motion rule further down; this is the
   kit's own attributes, which it does not. */
@media (prefers-reduced-motion: reduce) {
  [data-ns-animate], [data-text-reveal],
  [data-ns-animate] .text-reveal-line, [data-text-reveal] .text-reveal-line {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }
}

/* ── the four steps ───────────────────────────────────────────────────────
   Four identical white boxes read as four items on a list. They are a
   sequence, and the two things that say so are the alternating fill and the
   chevron between one card and the next. The number is the size of a number
   rather than a caption above one, and the words sit at the foot of the card,
   so the eye goes 1-2-3-4 first and reads second. */
.step-card {
  position: relative; height: 100%;
  /* ⚠ flex-start, not space-between. Bottom-aligning the words meant a card
     with three lines of description started its title lower than a card with
     two, so the four titles sat at four different heights and the row read as
     ragged rather than as a sequence. The gap is fixed instead: every title
     lands on the same line, and a shorter description simply ends sooner. */
  display: flex; flex-direction: column; justify-content: flex-start;
  gap: 3.25rem; min-height: 15rem; padding: 1.75rem;
  border-radius: 20px; border: 1px solid var(--color-stroke-1, #e6e8ec);
  background: var(--color-background-3, #f4f5f7);
  transition: transform .3s var(--bounce),
              box-shadow .3s var(--bounce);
}
.step-card:hover { transform: translateY(-4px);
                   box-shadow: 0 2px 6px rgba(18,22,31,.05), 0 26px 50px -28px rgba(18,22,31,.35); }

/* The accent is the brand's own, not a colour picked here, so a platform that
   sets its palette in the panel gets its own ladder rather than ours. */
.step-card-accent {
  border-color: transparent;
  background: color-mix(in srgb, var(--color-primary-500) 14%, #fff);
}
.step-n {
  font-size: 2.75rem; line-height: 1; font-weight: 500; letter-spacing: -.04em;
  color: var(--color-secondary);
}
.step-card-accent .step-n { color: var(--color-primary-500); }
.step-body p:first-child { color: var(--color-secondary); }
.step-body p + p { margin-top: .4rem; color: color-mix(in srgb, var(--color-secondary) 60%, transparent); }

/* The connector. Only between cards, only when they are actually side by side,
   and never after the last one — which is why it hangs off the card that
   follows rather than the one before, where :last-child could not reach it. */
@media (min-width: 1024px) {
  .steps .step + .step .step-card::before {
    content: ""; position: absolute; left: -1.55rem; top: 50%; z-index: 1;
    width: 1.1rem; height: 1.1rem; margin-top: -.55rem;
    border-right: 2px solid var(--color-secondary); border-top: 2px solid var(--color-secondary);
    opacity: .3; transform: rotate(45deg);
  }
}

/* ── the icon chip on a feature card ──────────────────────────────────────
   A one-pixel grey square around a grey glyph is the least a card can do. A
   filled circle in the brand accent is what makes a grid of six cards read as
   six things rather than as a wall of paragraphs — and it is the only colour
   spent on them. */
.feat-ico {
  display: flex; align-items: center; justify-content: center;
  width: 2.75rem; height: 2.75rem; border-radius: 999px;
  background: color-mix(in srgb, var(--color-primary-500) 16%, #fff);
  color: var(--color-primary-500);
  transition: transform .3s var(--bounce), background .3s ease;
}
/* ⚠ kicon() writes `class="size-full"`, which is 100% of whatever box it is
   put in. Inside a 44px circle that is a 44px glyph: the icon ran edge to
   edge and the ring around it disappeared behind the drawing. A mark in a
   circle needs the circle to be visible as a circle, so it is sized here
   rather than at every call site. */
.feat-ico > svg { width: 1.4rem; height: 1.4rem; max-width: 100%; max-height: 100%; flex: none; }
.lift:hover .feat-ico { transform: scale(1.08) rotate(-4deg);
                        background: color-mix(in srgb, var(--color-primary-500) 26%, #fff); }

/* ── feature cards: a panel, then the words ────────────────────────────────
   Four white cards with an icon on top read as one long list. Giving each a
   tinted panel above the copy separates the mark from the sentence, so the
   grid scans as four things rather than as twelve stacked lines.

   ⚠ The tint is mixed from the operator's own primary, not a fixed colour, so
   a workspace that repaints the palette repaints this too. Everything on this
   site derives from that variable and this cannot be the one exception. */
.feat-card { display: flex; flex-direction: column; overflow: hidden; padding: 0; }
.feat-panel {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  min-height: 7.5rem; margin: .5rem .5rem 0; border-radius: 16px;
  background:
    radial-gradient(115% 80% at 26% 0%,
      color-mix(in srgb, var(--color-primary-500) 13%, #fff) 0%,
      color-mix(in srgb, var(--color-primary-500) 3%, #fff) 62%),
    linear-gradient(180deg, #fff 0%, color-mix(in srgb, var(--color-primary-500) 5%, #fff) 100%);
}
/* A faint grid behind the mark, so the panel is a surface rather than a wash.
   Masked to fade out before the edges, which is what stops it reading as
   graph paper. */
.feat-panel::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background-image:
    linear-gradient(color-mix(in srgb, var(--color-secondary) 5%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--color-secondary) 5%, transparent) 1px, transparent 1px);
  background-size: 22px 22px;
  -webkit-mask-image: radial-gradient(75% 65% at 50% 45%, #000 0%, transparent 78%);
          mask-image: radial-gradient(75% 65% at 50% 45%, #000 0%, transparent 78%);
}
.feat-body { padding: 1.25rem 1.5rem 1.5rem; }
/* On the panel the mark can be larger: it is the only thing there. */
.feat-panel .feat-ico { position: relative; width: 3.4rem; height: 3.4rem; }
.feat-panel .feat-ico > svg { width: 1.7rem; height: 1.7rem; }

@media (prefers-reduced-motion: reduce) {
  .step-card, .feat-ico { transition: none; }
}

/* ── the header ───────────────────────────────────────────────────────────
   ⚠ The pill shipped with `backdrop-blur-[25px]` AND `bg-white`. A blur
   behind an opaque surface has nothing to blur: the class was doing nothing
   at all, and had been since the header was written. Glass needs something to
   see through.

   The hairline and the shadow are not decoration either — a translucent bar
   over a pale page has no edge, and without one it stops reading as a thing
   floating above the content and starts reading as a gap in it. */
.glass-pill {
  background: rgba(255, 255, 255, .72);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
          backdrop-filter: blur(22px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, .7);
  box-shadow: 0 1px 2px rgba(18, 22, 31, .04), 0 18px 40px -24px rgba(18, 22, 31, .30);
  transition: background .3s ease, box-shadow .3s ease;
}
.glass-pill:hover { box-shadow: 0 1px 2px rgba(18,22,31,.05), 0 22px 48px -24px rgba(18,22,31,.38); }

/* ── the header is a flat bar at rest and a pill once you scroll ───────────
   ⚠ The kit already toggles .scroll-header past 100px (assets/kit/main.js),
   so this is the resting state only and needs no script.

   ⚠ Selector is `header > .header-scroll:not(.scroll-header)`, never a bare
   `.scroll-header`: that class is added by JavaScript and never appears in
   markup, and the class-parity guard reads markup.

   ⚠ NO !important HERE, AND THAT IS THE FIX. Tailwind's utilities sit in
   @layer utilities, and for IMPORTANT declarations the cascade reverses: a
   layered !important beats an unlayered one however specific. So the first
   attempt at this rule lost to `lp:max-w-[1290px]!` and shipped a bar 1290px
   wide anchored to the left edge of the window. The `!` came off that one
   utility in shell.php instead; an unlayered normal declaration then beats a
   layered one, which is the ordinary direction of the cascade. */
header > .header-scroll:not(.scroll-header) {
  top: 0;
  left: 0;
  right: 0;
  width: 100%;
  max-width: none;
  transform: none;
  translate: none;   /* v4 emits `translate`, not `transform` */
  border-radius: 0;
  border: 0;
  border-bottom: 1px solid rgba(18, 22, 31, .08);
  box-shadow: none;
  background: rgba(255, 255, 255, .92);
  /* Padding, not a wrapper: the header is a flex row with justify-between, so
     an inner max-width box would need extra markup to do this one line's job. */
  padding-inline: max(20px, calc((100% - 1290px) / 2));
}
header > .header-scroll:not(.scroll-header):hover { box-shadow: none; }

/* ⚠ The pill's widest cap lived on an `!important` utility that had to come
   off the markup (see above), and the build has no un-flagged twin of it, so
   the scrolled pill would have narrowed to 1140. Restored here. `lp` is this
   kit's 1440px breakpoint. The resting rule above is more specific, so at the
   top of the page `max-width: none` still wins. */
@media (min-width: 1440px) {
  header > .header-scroll { max-width: 1290px; }
}

/* ⚠ The kit sets `transition: all 0.5s` on this element inline, from its own
   scroll handler, so going bar-to-pill animated width, max-width, left,
   translate and border-radius at once. Every one of those is a layout
   property; half a second of the browser interpolating all five is the
   glitchy slide.

   Snap the shape, fade only the surface. !important because the kit's is an
   inline style and this is the one thing that outranks it.

   ⚠ border-radius and top joined the fade later, and neither is the mistake
   above. border-radius is paint only — it never reflows anything, unlike
   width/max-width/left/translate, which move or resize the box itself and
   are what made the original animation glitchy. top does reflow, but it is
   one number changing by 12px on an element already position:fixed, not
   five racing at once — cheap enough that the sharp corner-cut and the
   sudden lift off the edge no longer read as a jump cut against the surface
   fade around them. Width, max-width, left and translate still snap. */
header > .header-scroll {
  transition: background .18s ease, box-shadow .18s ease, border-color .18s ease,
              border-radius .22s ease, top .22s ease !important;
}

/* Safari on iOS and every browser with backdrop-filter disabled would render
   72% white over the page, which is legible but muddy. Where the filter is not
   supported, commit to opaque instead. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass-pill { background: #fff; }
}

/* ── the hero reveal ──────────────────────────────────────────────────────
   The product mock is the argument the headline is making, so it is the one
   thing on this page worth animating for its own sake. It arrives a little
   small and a little low and settles — once, on load, with a delay long
   enough that the headline has been read first.

   Not scroll-linked: the mock sits above the fold, and a scroll-triggered
   reveal above the fold either fires before anyone sees it or never fires at
   all. And a glow beneath it, so it sits on the page rather than on top of
   it. */
.hero-stage { position: relative; }
.hero-stage::before {
  content: ""; position: absolute; z-index: -1; left: 50%; top: 12%;
  width: min(70rem, 92%); height: 60%; transform: translateX(-50%);
  background: radial-gradient(60% 50% at 50% 50%,
              color-mix(in srgb, var(--color-primary-500) 26%, transparent) 0%, transparent 70%);
  filter: blur(70px); pointer-events: none;
  animation: hero-glow-breathe 9s ease-in-out infinite alternate;
  transform-origin: center;
}
@keyframes hero-glow-breathe {
  from { transform: translateX(-50%) scale(1);   opacity: .85; }
  to   { transform: translateX(-50%) scale(1.14); opacity: 1;  }
}
/* ⚠ A SECOND hue, off to one side. One glow the same colour as the "Get
   started" button reads as a spotlight on the button; two different brand
   colours, offset, is what makes the page behind the mock look like a place
   rather than a backdrop. --brand-accent is the operator's own accent (see
   site_palette_css()), so a workspace with a different palette gets its own
   pair of colours here too, never a fixed one. Drifts, slowly, because a
   held-still glow reads as a lighting rig and a moving one reads as air. */
.hero-stage::after {
  content: ""; position: absolute; z-index: -1; right: 6%; top: -6%;
  width: min(38rem, 70%); height: 46%;
  background: radial-gradient(55% 55% at 50% 50%,
              color-mix(in srgb, var(--brand-accent, var(--color-primary-500)) 30%, transparent) 0%, transparent 72%);
  filter: blur(60px); pointer-events: none;
  animation: hero-glow-drift 9s ease-in-out infinite alternate;
}
@keyframes hero-glow-drift {
  from { transform: translate(0, 0) scale(1); }
  to   { transform: translate(-11%, 9%) scale(1.2); }
}
.hero-reveal {
  animation: hero-settle 1.1s var(--bounce) .35s both;
  will-change: transform, opacity;
}
@keyframes hero-settle {
  from { opacity: 0; transform: translateY(34px) scale(.94); }
  to   { opacity: 1; transform: none; }
}
/* ⚠ will-change on an element that has finished animating is a layer the
   compositor keeps for nothing. Dropped once the animation is over. */
.hero-reveal { animation-fill-mode: both; }

@media (prefers-reduced-motion: reduce) {
  .hero-reveal { animation: none; }
  .hero-stage::before, .hero-stage::after { animation: none; }
}

/* ── the provider marquee, big tiles ──────────────────────────────────────
   Went from a marquee of small pills to a static grid of big cards to this:
   the big cards, moving. Same seamless-loop mechanism the original marquee
   used (track holds the list N times, translates by exactly half its own
   width — see inc/home/works.php for why the pass count is computed and
   must stay even), sized for cards roughly three times the old pills' width,
   which is why this is one row rather than the original's two. */
.works-marq {
  position: relative; overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
          mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
}
.works-marq-track { display: flex; width: max-content; animation: works-marq 38s linear infinite; }
.works-marq:hover .works-marq-track { animation-play-state: paused; }
.works-marq-set { display: flex; align-items: stretch; gap: 1rem; margin: 0; padding: 0 .5rem; list-style: none; }
@keyframes works-marq { to { transform: translateX(-50%); } }

/* features.php's own "works with" mention, at the foot of the page rather
   than the headline moment the home page gives it - a static wrapped row
   of the same tiles is the right weight here, not a second marquee. */
.works-grid { display: flex; flex-wrap: wrap; align-items: stretch; justify-content: center; gap: 1rem; }

.works-grid-tile {
  flex: none; display: flex; flex-direction: column; align-items: center; gap: .7rem;
  width: 8.5rem; padding: 1.5rem 1rem; border-radius: 20px;
  background: #fff; border: 1px solid var(--color-stroke-1, #e6e8ec);
  box-shadow: 0 1px 2px rgba(18,22,31,.04);
  transition: transform .25s var(--bounce), box-shadow .25s ease, border-color .25s ease;
}
.works-grid-tile:hover {
  transform: translateY(-4px);
  border-color: color-mix(in srgb, var(--color-primary-500) 30%, var(--color-stroke-1, #e6e8ec));
  box-shadow: 0 2px 6px rgba(18,22,31,.05), 0 22px 40px -24px rgba(18,22,31,.38);
}
.works-grid-mark {
  display: flex; align-items: center; justify-content: center;
  width: 3.2rem; height: 3.2rem; border-radius: 14px;
  background: color-mix(in srgb, var(--color-primary-500) 6%, #fff);
}
.works-grid-mark img { width: 30px; height: 30px; object-fit: contain; }
.works-grid-mark b { font-size: 1.2rem; font-weight: 600; color: var(--color-primary-500); }
.works-grid-word { font-size: .85rem; font-weight: 500; color: var(--color-secondary); text-align: center; }
@media (prefers-reduced-motion: reduce) {
  .works-marq { overflow-x: auto; }
  .works-marq-track { animation: none; }
  .works-marq-set:last-child { display: none; }
  .works-grid-tile { transition: box-shadow .25s ease, border-color .25s ease; }
  .works-grid-tile:hover { transform: none; }
}

/* ── the footer ───────────────────────────────────────────────────────────
   It used to be the page's own pale ground with a hairline above it, which
   meant the site ran out rather than ending. Dark gives it a floor, and it is
   the one place a large block of colour costs nothing because nobody is down
   here to read. The glow is the same device as the closing call to action
   directly above it, so the two read as one ending rather than as two. */
.site-foot {
  position: relative; overflow: hidden; isolation: isolate;
  margin-top: 5rem;
  /* The kit's own top padding assumes the footer shares the page's ground and
     needs air above the hairline. On a dark floor that air IS the floor, and
     six rems of empty black before the first word reads as a mistake. */
  padding-top: 2.5rem !important;
  background: var(--color-secondary);
  color: rgba(255, 255, 255, .62);
}
/* ⚠ Half the strength it started at, and pushed further off the corner. At
   .30 it reached the footer blurb and left body text sitting on a violet
   wash — legible in a screenshot, tiring to read, and the one thing down here
   that is a sentence rather than a link. A glow that costs legibility is not
   worth having. */
.site-foot .foot-glow {
  position: absolute; z-index: -1; left: -16rem; bottom: -20rem;
  width: 34rem; height: 34rem; border-radius: 50%;
  background: var(--color-primary-500); opacity: .16; filter: blur(120px);
  pointer-events: none;
}
.site-foot .foot-top > div { position: relative; }   /* copy above the glow */
/* ⚠ Setting the colour on .site-foot alone was not enough: the kit's compiled
   build colours paragraphs directly, and a rule on an ancestor loses to one on
   the element. So the footer's two sentences — the blurb and the copyright,
   the only prose down here — rendered near-black on near-black and were
   invisible. Inheritance is not a colour scheme; say it on the elements. */
.site-foot p, .site-foot li, .site-foot span { color: inherit; }
.site-foot a { color: inherit; }
.site-foot .text-heading-6 { color: #fff; }
.foot-top    { border-top: 1px solid rgba(255,255,255,.10); }
.foot-bottom { border-top: 1px solid rgba(255,255,255,.10); }
.foot-head   { font-size: var(--text-tagline-3, .8rem); font-weight: 500; color: #fff; }
.foot-link   { color: rgba(255,255,255,.60); transition: color .2s ease; }
.foot-link:hover { color: #fff; }

/* The logo mark inverts: a dark square on a dark floor is a hole. */
.site-foot .bg-secondary {
  background: rgba(255,255,255,.12) !important; color: #fff !important;
}

.foot-social { display: flex; gap: .5rem; margin: 0; padding: 0; list-style: none; }
.foot-social a {
  display: flex; align-items: center; justify-content: center;
  width: 2.25rem; height: 2.25rem; border-radius: 999px;
  border: 1px solid rgba(255,255,255,.14); color: rgba(255,255,255,.7);
  transition: background .2s ease, color .2s ease, transform .2s ease, border-color .2s ease;
}
.foot-social a:hover {
  background: rgba(255,255,255,.12); border-color: rgba(255,255,255,.28);
  color: #fff; transform: translateY(-2px);
}

@media (prefers-reduced-motion: reduce) {
  .foot-link, .foot-social a { transition: none; }
}

/* ── the mega-menu's third column ─────────────────────────────────────────
   Two columns of links and then the menu stopped: a 946-pixel panel whose
   whole purpose is helping somebody choose, ending without ever asking them
   to. This is the one thing in it that is not a link to more reading, and it
   is dark for the same reason the closing call to action is — it is the end of
   the thing. */
.menu-promo {
  position: relative; overflow: hidden; isolation: isolate;
  flex: none; width: 17rem; align-self: stretch;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: 1.35rem; border-radius: 16px;
  background: var(--color-secondary); color: rgba(255,255,255,.66);
  text-decoration: none;
  transition: transform .3s var(--bounce);
}
.menu-promo:hover { transform: translateY(-2px); }
.menu-promo-glow {
  position: absolute; z-index: -1; right: -6rem; top: -6rem;
  width: 16rem; height: 16rem; border-radius: 50%;
  background: var(--color-primary-500); opacity: .5; filter: blur(50px);
  transition: opacity .35s ease;
}
.menu-promo:hover .menu-promo-glow { opacity: .68; }
.menu-promo-eyebrow {
  display: inline-block; font-size: .72rem; letter-spacing: .04em;
  text-transform: uppercase; color: rgba(255,255,255,.55); margin-bottom: .6rem;
}
.menu-promo-title { color: #fff; font-size: 1.15rem; line-height: 1.25; font-weight: 500; }
.menu-promo-body  { margin-top: .5rem; font-size: .84rem; line-height: 1.5; }
.menu-promo-cta {
  display: inline-flex; align-items: center; gap: .4rem;
  margin-top: 1.1rem; color: #fff; font-size: .88rem; font-weight: 500;
}
.menu-promo-cta svg { transition: transform .22s var(--bounce); }
.menu-promo:hover .menu-promo-cta svg { transform: translateX(3px); }

/* Below the width the mega-menu itself needs, it is not shown at all. */
@media (max-width: 1279px) { .menu-promo { display: none; } }
@media (prefers-reduced-motion: reduce) {
  .menu-promo, .menu-promo-glow, .menu-promo-cta svg { transition: none; }
}

/* ══ reveal, and the answer to a pointer ═════════════════════════════════════
   The kit animates what carries its own attributes and nothing else, which
   left whole sections — the plans, the questions, the figures — simply
   appearing. `.reveal` is the opt-in; assets/reveal.js adds `.is-in` when the
   element enters the viewport.

   ⚠ Hidden ONLY while html.ns-ready is set, exactly like the kit's own rule
   and for exactly the same reason: with no script, or with the watchdog having
   given up, every one of these is visible. Nothing in this file can hide
   content permanently. That is the whole discipline here.
   ══════════════════════════════════════════════════════════════════════════ */
html.ns-ready .reveal {
  opacity: 0; transform: translateY(18px);
  transition: opacity .6s var(--bounce), transform .6s var(--bounce);
}
html.ns-ready .reveal.is-in { opacity: 1; transform: none; }

/* A stagger for a row of siblings, so a grid arrives as a grid rather than as
   one block. Capped at six: past that nobody is watching the order, and a long
   cascade is a page that takes a second to settle for no reason. */
html.ns-ready .reveal-1 { transition-delay: .06s; }
html.ns-ready .reveal-2 { transition-delay: .12s; }
html.ns-ready .reveal-3 { transition-delay: .18s; }
html.ns-ready .reveal-4 { transition-delay: .24s; }
html.ns-ready .reveal-5 { transition-delay: .30s; }
html.ns-ready .reveal-6 { transition-delay: .36s; }

/* ── the answer to a pointer ──────────────────────────────────────────────
   Every interactive thing on the page should move when the pointer is on it,
   and nothing else should. That second half is the rule that keeps this from
   becoming noise: a paragraph that lifts is a paragraph that lied about being
   clickable. */

/* Plans. The one somebody is considering separates from the two beside it. */
.plan-card { transition: transform .3s var(--bounce),
                         box-shadow .3s ease, border-color .3s ease; }
.plan-card:hover { transform: translateY(-9px) scale(1.015);
                   border-color: color-mix(in srgb, var(--color-primary-500) 45%, transparent);
                   box-shadow: 0 2px 6px rgba(18,22,31,.05), 0 30px 56px -30px rgba(18,22,31,.4); }

/* Questions. A card each, so the grouping is visible rather than inferred. */
.faq-list { display: flex; flex-direction: column; gap: .75rem; }
.faq-row {
  border-radius: 16px; border: 1px solid var(--color-stroke-1, #e6e8ec);
  background: #fff; padding: 0 1.5rem;
  transition: border-color .25s ease, box-shadow .25s ease, background .25s ease;
}
.faq-row:hover { border-color: color-mix(in srgb, var(--color-primary-500) 35%, var(--color-stroke-1)); }
/* The open one says so by its ground, not only by its chevron. */
.faq-row[open] {
  background: color-mix(in srgb, var(--color-primary-500) 4%, #fff);
  border-color: color-mix(in srgb, var(--color-primary-500) 28%, transparent);
  box-shadow: 0 1px 2px rgba(18,22,31,.03), 0 16px 34px -26px rgba(18,22,31,.35);
}
.faq-q  { padding: 1.35rem 0; font-size: 1.02rem; color: var(--color-secondary); }
.faq-a  { padding: 0 0 1.4rem 1.65rem; line-height: 1.65; max-width: 62ch;
          color: color-mix(in srgb, var(--color-secondary) 60%, transparent); }
/* The chevron goes to the far end, where a disclosure control belongs. */
.faq-q::before { order: 2; margin-left: auto; }

/* Figures. They are not links, so they get colour rather than lift. */
.proof-item { transition: transform .3s var(--bounce); }
.proof-strip:hover .proof-item { opacity: .55; transition: opacity .3s ease; }
.proof-strip .proof-item:hover { opacity: 1; transform: translateY(-3px); }

/* Nav. ⚠ No underline. It was drawn growing from the middle, which is a nice
   effect and the wrong one here: the kit already answers a nav hover with a
   colour change, and a second signal on the same event reads as two things
   happening. The colour is enough. */

/* Rows in the hero mock. It is a picture of the product, and a picture that
   responds to the pointer is one people put their pointer on. */
.mock-row { transition: background .25s ease; }
.mock-row:hover { background: color-mix(in srgb, var(--color-primary-500) 4%, transparent); }

/* The badge above a heading. A small thing, and the first thing on screen. */
.badge { transition: transform .3s var(--bounce); }
.badge:hover { transform: translateY(-2px) scale(1.06); }

@media (prefers-reduced-motion: reduce) {
  html.ns-ready .reveal { opacity: 1; transform: none; transition: none; }
  .plan-card, .faq-row, .proof-item, .mock-row, .badge { transition: none; }
}

/* ── the closing ask ──────────────────────────────────────────────────────
   ⚠ This was a dark card with a picture behind it, and the picture was the
   tell: the block had to be dressed because it had nothing to say. It is the
   last thing on every page and the only place the whole site is asking for
   something, so it is the page itself now — no box, no border, no ground of
   its own.

   The three reassurances are what the box was standing in for. "No card" and
   "it sends nothing until you say so" answer the two objections that actually
   stop somebody pressing the button, and they used to be nowhere near it. */
.final { max-width: 46rem; margin: 0 auto; }
.final-title {
  color: var(--color-secondary); margin: 0 auto; max-width: 18ch;
  font-size: clamp(2.1rem, 4.2vw, 3.4rem); line-height: 1.08;
  letter-spacing: -.035em; font-weight: 500;
}
.final-blurb {
  margin: 1.15rem auto 0; max-width: 34rem; line-height: 1.6;
  color: color-mix(in srgb, var(--color-secondary) 58%, transparent);
}
.final-points {
  display: flex; flex-wrap: wrap; justify-content: center; gap: .6rem 2rem;
  margin: 2rem 0 0; padding: 0; list-style: none;
}
.final-points li {
  display: flex; align-items: center; gap: .55rem;
  font-size: .92rem; color: color-mix(in srgb, var(--color-secondary) 68%, transparent);
}
.final-tick {
  display: flex; align-items: center; justify-content: center; flex: none;
  width: 1.25rem; height: 1.25rem; border-radius: 999px;
  background: var(--color-secondary); color: #fff;
}

/* The mega-menu's column headings. One word each, and the reason for them is
   that two unlabelled columns of six links make a reader scan all twelve to
   work out what the split is. */
.menu-col-head {
  font-size: .78rem; font-weight: 500; letter-spacing: .02em;
  color: color-mix(in srgb, var(--color-secondary) 45%, transparent);
  padding: .35rem .75rem .1rem;
}

/* ── the hero, product first ──────────────────────────────────────────────
   ⚠ The source order is the design. The mock comes first and the words sit
   over its lower half, fading it out beneath them — the headline is a claim
   and the mock is the evidence, and a reader who bounces off the claim never
   scrolls far enough to reach the evidence when it is underneath.

   Below the breakpoint the two simply stack, words first, because a
   screenshot behind text on a phone is unreadable and there the words are
   what have to carry it. Which is also why this is a media query and not a
   transform: on a phone the DOM order IS the reading order. */
@media (min-width: 1024px) {
  /* ⚠ Larger than the mobile figure ON PURPOSE, and it is not slack: the
     mock below carries margin-top: -2.5rem so it tucks under the header, so
     40px of this is spent immediately. What is left is the clearance.
     The 100px is <main>'s header padding, handed back — see the note on
     .hero further up. */
  .hero { padding-top: calc(100px + 8rem); }
  .hero .hero-stage {
    position: relative; z-index: 0;
    /* Pulled up under the header and faded out at the foot, so the panel
       reads as something the page is standing in front of. */
    margin: -2.5rem auto 0;
    /* ⚠ The fade has to finish ABOVE the words, not behind them. The first
       version reached the copy while the mock was still at a third of its
       opacity, and the headline landed on readable rows of a fake inbox —
       two pieces of text over each other, both illegible. Gone by 62%, and
       the copy starts below that. */
    -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 18%, rgba(0,0,0,.28) 44%, transparent 62%);
            mask-image: linear-gradient(to bottom, #000 0%, #000 18%, rgba(0,0,0,.28) 44%, transparent 62%);
  }
  /* Lifted back over the mock by the height of its faded half. A negative
     margin rather than absolute positioning: the section still sizes itself
     from its content, so nothing has to be measured or kept in sync.
     ⚠ -10rem ALONE WAS NOT RESPONSIVE TO THE CONTAINER: it is tuned for the
     mock at its widest, and jumping straight to it the instant the viewport
     crosses 1024px pulled the headline up over READABLE rows (not the
     faded ones) at 1024-1279px, where the mock is narrower but the pull was
     already the full amount. Eased in over a second breakpoint instead - see
     the 1280px block below - so the overlap only ever reaches the mock's
     actual faded zone. */
  .hero .hero-say { position: relative; z-index: 2; margin-top: -5.5rem; }
}
@media (min-width: 1280px) {
  /* ⚠ -10rem pulled the eyebrow badge up to ~85% of the stage's height from
     its top — inside the mask's 44%-62% fade band, where rows are dimmed
     but still legible, not past it. The badge sat on top of readable mock
     text instead of the fully-transparent zone below 62% it was meant to
     clear. Measured live against the actual 4-row mock at this breakpoint:
     -7.5rem lands it just past the 62% mark instead. */
  .hero .hero-say { margin-top: -7.5rem; }

  /* ⚠ The two annotation cards float off the panel edges, and the mask above
     clips anything outside it — so they were cut in half. They belong to the
     words now, not to the picture. */
  .hero .hero-stage > .absolute { display: none !important; }
}

/* The glow follows the panel up. */
.hero .hero-stage::before { top: 4%; height: 52%; }

@media (max-width: 1023px) {
  /* Stacked, words first — which the source order already gives us in
     reverse, so this is the one place a flex order is honest. */
  .hero .main-container { display: flex; flex-direction: column; }
  .hero .hero-say   { order: 1; }
  .hero .hero-stage { order: 2; margin-top: 3rem; }
}

/* ── the showcase ─────────────────────────────────────────────────────────
   Left-aligned, unlike every other header on the page, and that is what it is
   for: a centred header says "here is a section"; a left-aligned one with the
   button under it says "here is the thing". */
.show-head { max-width: 46rem; }
.show-title {
  color: var(--color-secondary); margin: 1.25rem 0 0; max-width: 20ch;
  font-size: clamp(2rem, 3.6vw, 3.1rem); line-height: 1.08;
  letter-spacing: -.035em; font-weight: 500;
}
.show-blurb { margin-top: 1.1rem; max-width: 38rem; line-height: 1.65;
              color: color-mix(in srgb, var(--color-secondary) 58%, transparent); }

.show-grid { position: relative; display: grid; gap: 1.5rem; }
@media (min-width: 1024px) { .show-grid { grid-template-columns: 1fr 1fr; } }

/* ── the fork between the two cards ───────────────────────────────────────
   Absolute, so it takes no space in the grid and sits on the seam between
   the two columns regardless of how tall either card grows. lg-only: below
   that the cards stack and there is no seam to sit on. */
.show-link { display: none; }
@media (min-width: 1024px) {
  .show-link {
    display: flex; align-items: center; justify-content: center;
    position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
    width: 2.75rem; height: 2.75rem; border-radius: 999px; z-index: 5;
    background: #fff; border: 1px solid var(--color-stroke-1, #e6e8ec);
    box-shadow: 0 6px 16px -6px rgba(18,22,31,.22);
    color: var(--color-primary-500);
  }
  .show-link svg { width: 1.15rem; height: 1.15rem; }
  /* The dashed lead-ins either side, so the badge reads as sitting ON a path
     rather than floating over the gap. */
  .show-link::before, .show-link::after {
    content: ""; position: absolute; top: 50%; width: 2.5rem; height: 1px;
    background-image: repeating-linear-gradient(90deg,
      var(--color-stroke-1, #e6e8ec) 0 6px, transparent 6px 12px);
  }
  .show-link::before { right: 100%; }
  .show-link::after  { left: 100%; }
  /* A ring that expands and fades, once every few seconds — the same "this is
     live" language as the hero's live-dot, spent here instead of a second
     time on a decoration nobody reads as meaning anything. */
  .show-link-pulse {
    position: absolute; inset: -1px; border-radius: inherit; pointer-events: none;
    border: 1px solid color-mix(in srgb, var(--color-primary-500) 45%, transparent);
    animation: show-link-pulse 2.8s var(--bounce) infinite;
  }
  @keyframes show-link-pulse {
    0%   { transform: scale(.9); opacity: .85; }
    75%  { opacity: 0; }
    100% { transform: scale(1.7); opacity: 0; }
  }
}
@media (prefers-reduced-motion: reduce) {
  .show-link-pulse { animation: none; opacity: 0; }
}

/* ── the pipeline ──────────────────────────────────────────────────────────
   inc/home/pipeline.php. First version used a soft background-position sheen
   on a 2px line and a near-invisible box-shadow flash on the icons - correct
   in principle, too subtle to actually read as motion at the size either ran
   at. This one moves something you can point at: a real dot that travels the
   full link at 4x the size, and a ring that visibly expands and fades off
   each icon in turn, the same device show-link-pulse already proved out in
   showcase.php. Each step is also a real card now, not text floating on the
   page background - consistent weight with every other section here. */
.pipe { display: flex; align-items: stretch; justify-content: center; flex-wrap: wrap; gap: 0; }
.pipe-node {
  display: flex; flex-direction: column; align-items: center; gap: .7rem;
  width: 11.5rem; text-align: center; padding: 1.5rem 1.1rem;
  border-radius: 18px; border: 1px solid var(--color-stroke-1, #e6e8ec); background: #fff;
  box-shadow: 0 1px 2px rgba(18,22,31,.04);
  transition: transform .3s var(--bounce), box-shadow .3s ease, border-color .3s ease;
}
.pipe-node:hover {
  transform: translateY(-4px);
  border-color: color-mix(in srgb, var(--color-primary-500) 30%, var(--color-stroke-1, #e6e8ec));
  box-shadow: 0 2px 6px rgba(18,22,31,.05), 0 22px 40px -24px rgba(18,22,31,.38);
}
.pipe-node b { font-size: .95rem; font-weight: 500; color: var(--color-secondary); }
.pipe-node > span:last-child {
  font-size: .8rem; line-height: 1.45;
  color: color-mix(in srgb, var(--color-secondary) 55%, transparent);
}
.pipe-ico {
  position: relative; display: flex; align-items: center; justify-content: center;
  width: 3.6rem; height: 3.6rem; border-radius: 999px;
  background: color-mix(in srgb, var(--color-primary-500) 14%, #fff);
  color: var(--color-primary-500);
}
.pipe-ico svg { width: 1.6rem; height: 1.6rem; }
.pipe-ico-ring {
  position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  border: 2px solid color-mix(in srgb, var(--color-primary-500) 55%, transparent);
  animation: pipe-ring 3.3s var(--bounce) infinite;
}
.pipe-ico-1 .pipe-ico-ring { animation-delay: 0s; }
.pipe-ico-2 .pipe-ico-ring { animation-delay: 1.1s; }
.pipe-ico-3 .pipe-ico-ring { animation-delay: 2.2s; }
@keyframes pipe-ring {
  0%   { transform: scale(.9); opacity: .9; }
  60%  { opacity: 0; }
  100% { transform: scale(1.6); opacity: 0; }
}
/* ⚠ NOT --color-stroke-1. That token is tuned for a 1px border, where the
   EDGE against white is what reads; filled 4px flat across a whole bar,
   the same colour is nearly invisible at normal zoom and vanishes entirely
   under screenshot/JPEG compression — a card row that is meant to read as
   "connected by a path" instead reads as three floating cards and, wherever
   the travelling dot currently sits, one lonely dot in mid-air. A darker,
   secondary-derived tint is still a hairline, not a rule, but it survives
   compression. */
.pipe-link {
  flex: none; align-self: center; position: relative;
  width: 3.25rem; height: 4px; margin: 0 -.1rem; border-radius: 999px;
  background: color-mix(in srgb, var(--color-secondary) 14%, transparent);
  overflow: visible;
}
.pipe-dot {
  position: absolute; top: 50%; left: 0; width: 10px; height: 10px; margin-top: -5px;
  border-radius: 999px; background: var(--color-primary-500);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-primary-500) 20%, transparent);
  animation: pipe-dot-move 3.3s cubic-bezier(.45,.05,.55,.95) infinite;
  opacity: 0;
}
.pipe-link-1 .pipe-dot { animation-delay: 0s; }
.pipe-link-2 .pipe-dot { animation-delay: 1.1s; }
.pipe-link-3 .pipe-dot { animation-delay: 2.2s; }
@keyframes pipe-dot-move {
  0%   { left: 0%;   opacity: 0; }
  8%   { opacity: 1; }
  46%  { opacity: 1; }
  54%  { left: 100%; opacity: 0; }
  100% { left: 100%; opacity: 0; }
}
.pipe-fork {
  display: flex; flex-direction: column; justify-content: center; gap: .9rem;
  align-self: center; margin-left: .5rem; padding: 1.1rem 1.25rem;
  border-radius: 18px; border: 1px solid var(--color-stroke-1, #e6e8ec); background: #fff;
  box-shadow: 0 1px 2px rgba(18,22,31,.04);
  transition: transform .3s var(--bounce), box-shadow .3s ease, border-color .3s ease;
}
.pipe-fork:hover {
  transform: translateY(-4px);
  border-color: color-mix(in srgb, var(--color-primary-500) 30%, var(--color-stroke-1, #e6e8ec));
  box-shadow: 0 2px 6px rgba(18,22,31,.05), 0 22px 40px -24px rgba(18,22,31,.38);
}
.pipe-fork-out { display: flex; align-items: flex-start; gap: .7rem; text-align: left; }
.pipe-fork-ico {
  flex: none; display: flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; border-radius: 999px; margin-top: .1rem;
}
.pipe-fork-ico svg { width: 1rem; height: 1rem; }
.pipe-fork-ico-yes  { background: color-mix(in srgb, var(--color-primary-500) 14%, #fff); color: var(--color-primary-500); }
.pipe-fork-ico-hold { background: #fef3c7; color: #b45309; }
.pipe-fork-out-say { display: flex; flex-direction: column; align-items: flex-start; gap: .3rem; }
.pipe-fork-out-say span:last-child {
  max-width: 11rem; font-size: .78rem; line-height: 1.4;
  color: color-mix(in srgb, var(--color-secondary) 55%, transparent);
}

/* Cards arriving one after another, on top of the pulse/dot motion that
   already runs continuously - the two read as different things (one plays
   once, the other never stops) rather than fighting for attention. Uses
   [data-stagger] (reveal.js's own observer, see about.php's checklist for
   the first use of it), not .reveal: this div already carries data-ns-
   animate for its own single fade-in, and [data-stagger] is safe to nest
   inside that because its default state is fully visible, never hidden. */
.pipe > .pipe-node, .pipe > .pipe-fork { opacity: 1; transform: none; }
[data-stagger].stagger-in > .pipe-node,
[data-stagger].stagger-in > .pipe-fork {
  animation: pipe-card-in .5s var(--bounce) backwards;
}
.pipe > .pipe-node:nth-child(1) { animation-delay: .05s; }
.pipe > .pipe-node:nth-child(3) { animation-delay: .2s; }
.pipe > .pipe-node:nth-child(5) { animation-delay: .35s; }
.pipe > .pipe-fork { animation-delay: .5s; }
@keyframes pipe-card-in {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

@media (max-width: 900px) {
  .pipe { flex-direction: column; align-items: center; }
  .pipe-node { width: 100%; max-width: 20rem; }
  .pipe-link { width: 4px; height: 2.25rem; margin: 0; }
  .pipe-dot {
    top: 0; left: 50%; margin-top: 0; margin-left: -5px;
    animation-name: pipe-dot-move-v;
  }
  @keyframes pipe-dot-move-v {
    0%   { top: 0%;   opacity: 0; }
    8%   { opacity: 1; }
    46%  { opacity: 1; }
    54%  { top: 100%; opacity: 0; }
    100% { top: 100%; opacity: 0; }
  }
  .pipe-fork { margin: 0; width: 100%; max-width: 20rem; align-items: flex-start; }
}
@media (prefers-reduced-motion: reduce) {
  .pipe-ico-ring, .pipe-dot { animation: none; opacity: 0; }
  .pipe > .pipe-node, .pipe > .pipe-fork { animation: none; transition: box-shadow .3s ease, border-color .3s ease; }
  .pipe-node:hover, .pipe-fork:hover { transform: none; }
}

/* ── process.php's own path, the same travelling-dot device ─────────────── */
.path-row { transition: background .2s ease; }
.path-row:hover { background: color-mix(in srgb, var(--color-primary-500) 3%, transparent); }
.path-dot { display: none; }
@media (min-width: 768px) {
  .path-track { position: relative; }
  /* Left offset matches the numbered circle's own centre: the track's
     padding (1.5rem) plus half the circle's width (2.75rem / 2). Not a
     guess - if .path-num's own size ever changes this has to move with it. */
  .path-track::before {
    content: ""; position: absolute; left: 2.875rem; top: 3rem; bottom: 3rem;
    width: 2px; background: var(--color-stroke-1, #e6e8ec); z-index: 0;
  }
  /* ⚠ THE CIRCLE HAD NO FILL. Its markup is a border and nothing else
     (process.php: border-stroke-1 ... border, no background- class), so
     the z-index that put it above the connecting line only lifted the
     NUMBER above the line - the ring itself stayed transparent, and the
     line kept passing visibly through it. A solid ground the same colour
     as the card behind it hides the line cleanly, the way an opaque token
     on a board covers the line under it rather than the line just being
     drawn thinner where a token happens to sit. */
  .path-num { position: relative; z-index: 1; background: #fff; }
  .path-dot {
    display: block; position: absolute; left: 2.875rem; top: 3rem;
    width: 10px; height: 10px; margin-left: -4px; border-radius: 999px;
    background: var(--color-primary-500); z-index: 1;
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-primary-500) 20%, transparent);
    animation: path-dot-move 5s cubic-bezier(.45,.05,.55,.95) infinite;
    opacity: 0;
  }
  @keyframes path-dot-move {
    0%   { top: 3rem;    opacity: 0; }
    6%   { opacity: 1; }
    44%  { opacity: 1; }
    50%  { top: calc(100% - 3rem); opacity: 0; }
    100% { top: calc(100% - 3rem); opacity: 0; }
  }
}
@media (prefers-reduced-motion: reduce) {
  .path-dot { animation: none; opacity: 0; }
}

.show-card {
  display: flex; flex-direction: column; min-width: 0;
  border-radius: 24px; border: 1px solid var(--color-stroke-1, #e6e8ec);
  background: #fff; padding: 2rem;
  transition: transform .3s var(--bounce), box-shadow .3s ease;
}
.show-card:hover { transform: translateY(-4px);
                   box-shadow: 0 2px 6px rgba(18,22,31,.05), 0 30px 56px -30px rgba(18,22,31,.38); }

/* The vignette sits between the icon and the words, and it grows to fill
   whatever height the taller card has — so two cards of different text length
   still line their headings up at the bottom. */
.show-vig { flex: 1; display: flex; align-items: center; justify-content: center;
            margin: 1.75rem 0 2rem; min-height: 12rem; }
.vig-card {
  width: 100%; border-radius: 16px; border: 1px solid var(--color-stroke-1, #e6e8ec);
  background: var(--color-background-3, #f7f8fa); padding: 1.15rem 1.25rem;
  transition: transform .35s var(--bounce);
}
.show-card:hover .vig-card { transform: scale(1.015); }
.vig-row  { display: flex; align-items: center; gap: .65rem; }
.vig-subj { font-size: .95rem; color: var(--color-secondary); font-weight: 500; }
.vig-why  { margin-top: .6rem; font-size: .86rem; line-height: 1.55;
            color: color-mix(in srgb, var(--color-secondary) 55%, transparent); }
.vig-draft { margin-top: .9rem; padding-left: .85rem;
             border-left: 2px solid color-mix(in srgb, var(--color-primary-500) 40%, transparent); }
.vig-label { display: block; font-size: .72rem; letter-spacing: .03em;
             color: color-mix(in srgb, var(--color-secondary) 45%, transparent); }
.vig-draft p { margin-top: .3rem; font-size: .86rem; line-height: 1.55;
               color: color-mix(in srgb, var(--color-secondary) 62%, transparent); }
.vig-set { display: flex; align-items: center; justify-content: space-between; gap: 1rem;
           padding: .6rem 0; border-bottom: 1px solid var(--color-stroke-1, #e6e8ec); }
.vig-set:last-child { border-bottom: 0; }
.vig-set span { font-size: .86rem; color: color-mix(in srgb, var(--color-secondary) 55%, transparent); }
.vig-set b    { font-size: .86rem; font-weight: 500; color: var(--color-secondary); text-align: right; }
.vig-pill { padding: .2em .7em; border-radius: 999px;
            background: color-mix(in srgb, var(--color-primary-500) 14%, transparent);
            color: var(--color-primary-500); }

.show-say h3 { font-size: 1.35rem; line-height: 1.2; letter-spacing: -.02em;
               font-weight: 500; color: var(--color-secondary); }
.show-say p  { margin-top: .65rem; line-height: 1.6;
               color: color-mix(in srgb, var(--color-secondary) 58%, transparent); }

@media (prefers-reduced-motion: reduce) {
  .show-card, .vig-card { transition: none; }
}

/* ── the mockup component — see inc/mockup.php ────────────────────────────
   The window chrome (three dots, a fake address) is the one visual device
   that reads as "this is the product" before a single word is read. Content
   inside reuses the vig-* language above wherever the shape already fits
   (mock-pill mirrors vig-pill, for instance) rather than inventing a second
   vocabulary for the same idea. */
.mock-frame {
  border-radius: 20px; border: 1px solid var(--color-stroke-1, #e6e8ec);
  background: #fff; overflow: hidden;
  box-shadow: 0 2px 6px rgba(18,22,31,.05), 0 30px 56px -30px rgba(18,22,31,.30);
}
.mock-topbar {
  display: flex; align-items: center; gap: .5rem;
  padding: .7rem 1rem; background: var(--color-background-3, #f7f8fa);
  border-bottom: 1px solid var(--color-stroke-1, #e6e8ec);
}
.mock-dot { width: 8px; height: 8px; border-radius: 999px; background: var(--color-stroke-3, #d7dde5); }
.mock-addr {
  margin-left: .5rem; padding: .2rem .7rem; border-radius: 999px;
  background: #fff; border: 1px solid var(--color-stroke-1, #e6e8ec);
  font-size: .74rem; color: color-mix(in srgb, var(--color-secondary) 50%, transparent);
}
.mock-body { padding: 1.1rem 1.25rem 1.35rem; }

/* CSS-only tabs — hidden radios, sibling-selector panels. Same mechanism as
   the console's period-switch and this site's own FAQ <details>. The radio
   inputs sit as direct children of .mock-frame, BEFORE .mock-tabs (their
   labels) and .mock-body (their panels) — both of those are later siblings,
   which is what lets one :checked input reach into both with ~ and a
   descendant selector. A per-index class pairs each input with its own
   label and panel; nth-of-type would count across mixed tag names and
   silently pick the wrong one. */
.mock-tab-input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.mock-tabs {
  display: flex; gap: .35rem; padding: .6rem .9rem 0;
  background: var(--color-background-3, #f7f8fa);
  border-bottom: 1px solid var(--color-stroke-1, #e6e8ec);
}
.mock-tab-lbl {
  padding: .5rem .9rem; margin-bottom: -1px; cursor: pointer;
  font-size: .82rem; font-weight: 500; border-radius: 10px 10px 0 0;
  color: color-mix(in srgb, var(--color-secondary) 55%, transparent);
  border: 1px solid transparent; border-bottom: none;
}
.mock-tab-panel { display: none; }

.mtab-0:checked ~ .mock-tabs .mtabl-0, .mtab-1:checked ~ .mock-tabs .mtabl-1,
.mtab-2:checked ~ .mock-tabs .mtabl-2, .mtab-3:checked ~ .mock-tabs .mtabl-3,
.mtab-4:checked ~ .mock-tabs .mtabl-4 {
  background: #fff; color: var(--color-secondary);
  border-color: var(--color-stroke-1, #e6e8ec);
}
.mtab-0:checked ~ .mock-body .mtabp-0, .mtab-1:checked ~ .mock-body .mtabp-1,
.mtab-2:checked ~ .mock-body .mtabp-2, .mtab-3:checked ~ .mock-body .mtabp-3,
.mtab-4:checked ~ .mock-body .mtabp-4 {
  display: block;
}
.mtab-0:focus-visible ~ .mock-tabs .mtabl-0, .mtab-1:focus-visible ~ .mock-tabs .mtabl-1,
.mtab-2:focus-visible ~ .mock-tabs .mtabl-2, .mtab-3:focus-visible ~ .mock-tabs .mtabl-3,
.mtab-4:focus-visible ~ .mock-tabs .mtabl-4 {
  outline: 2px solid var(--color-primary-500); outline-offset: 2px;
}

.mock-row {
  display: flex; align-items: center; gap: .7rem;
  padding: .6rem 0; border-bottom: 1px solid var(--color-stroke-1, #e6e8ec);
}
.mock-row:last-child { border-bottom: 0; }
.mock-avatar {
  display: grid; place-items: center; width: 2rem; height: 2rem; flex: none;
  border-radius: 999px; background: color-mix(in srgb, var(--color-primary-500) 14%, #fff);
  color: var(--color-primary-500); font-size: .78rem; font-weight: 600;
}
.mock-row-text { display: flex; flex-direction: column; gap: .1rem; min-width: 0; flex: 1; }
.mock-row-text b { font-size: .86rem; color: var(--color-secondary); font-weight: 500; }
.mock-row-text span {
  font-size: .8rem; color: color-mix(in srgb, var(--color-secondary) 55%, transparent);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mock-pill {
  flex: none; padding: .2em .65em; border-radius: 999px; font-size: .72rem; font-weight: 500;
  background: var(--color-background-4, #f0f2f6); color: color-mix(in srgb, var(--color-secondary) 60%, transparent);
}
.mock-violet { background: color-mix(in srgb, var(--color-primary-500) 14%, transparent); color: var(--color-primary-500); }
.mock-good   { background: #e6f4ea; color: #2f7d52; }
.mock-warn   { background: #fbf1e1; color: #8a5e10; }
.mock-bad    { background: #fbe9e8; color: #a3342f; }
/* Same red, for a plain word rather than a pill — a vig-set value that needs
   to read as wrong (an SPF failure, a missing DKIM signature) without the
   weight of a full pill around it. */
.mock-neg    { color: #a3342f; }

.mock-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(6.5rem, 1fr)); gap: .75rem; }
/* The gap between a stats row and the bars under it - a class, not
   style="margin-top:…", for the same style-src 'self' reason as .mock-bar-fill.wNN above. */
.mock-bars { margin-top: 1.1rem; }
.mock-stat {
  display: flex; flex-direction: column; gap: .15rem;
  padding: .85rem .9rem; border-radius: 14px;
  background: var(--color-background-3, #f7f8fa); border: 1px solid var(--color-stroke-1, #e6e8ec);
}
.mock-stat b { font-size: 1.4rem; font-weight: 600; color: var(--color-secondary); letter-spacing: -.01em; }
.mock-stat span { font-size: .76rem; color: color-mix(in srgb, var(--color-secondary) 55%, transparent); }

.mock-barrow { display: flex; align-items: center; gap: .65rem; padding: .4rem 0; }
.mock-bar-lbl { flex: 0 0 6.5rem; font-size: .8rem; color: color-mix(in srgb, var(--color-secondary) 62%, transparent); }
.mock-bar-track { flex: 1; height: 7px; border-radius: 4px; background: var(--color-background-4, #f0f2f6); overflow: hidden; }
.mock-bar-fill { display: block; height: 100%; border-radius: 4px; background: var(--color-primary-500); }
.mock-bar-val { flex: 0 0 2.5rem; text-align: right; font-size: .8rem; color: var(--color-secondary); font-weight: 500; }
/* Widths come from a class, not style="width:…%" — this site's CSP ships
   style-src 'self' with no 'unsafe-inline', so an inline width would just be
   discarded. Same reasoning and the same step-of-5 rounding console.css's
   own .conf-fill.wNN uses. */
.mock-bar-fill.w0   { width: 0%; }
.mock-bar-fill.w5   { width: 5%; }
.mock-bar-fill.w10  { width: 10%; }
.mock-bar-fill.w15  { width: 15%; }
.mock-bar-fill.w20  { width: 20%; }
.mock-bar-fill.w25  { width: 25%; }
.mock-bar-fill.w30  { width: 30%; }
.mock-bar-fill.w35  { width: 35%; }
.mock-bar-fill.w40  { width: 40%; }
.mock-bar-fill.w45  { width: 45%; }
.mock-bar-fill.w50  { width: 50%; }
.mock-bar-fill.w55  { width: 55%; }
.mock-bar-fill.w60  { width: 60%; }
.mock-bar-fill.w65  { width: 65%; }
.mock-bar-fill.w70  { width: 70%; }
.mock-bar-fill.w75  { width: 75%; }
.mock-bar-fill.w80  { width: 80%; }
.mock-bar-fill.w85  { width: 85%; }
.mock-bar-fill.w90  { width: 90%; }
.mock-bar-fill.w95  { width: 95%; }
.mock-bar-fill.w100 { width: 100%; }

.mock-rule {
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  padding: .7rem .85rem; border-radius: 12px;
  background: var(--color-background-3, #f7f8fa); border: 1px solid var(--color-stroke-1, #e6e8ec);
}
.mock-rule + .mock-rule { margin-top: .5rem; }
.mock-rule span:first-child { flex: 1; min-width: 10rem; font-size: .84rem; color: color-mix(in srgb, var(--color-secondary) 65%, transparent); }
.mock-rule-arrow { color: color-mix(in srgb, var(--color-secondary) 40%, transparent); }

@media (prefers-reduced-motion: reduce) {
  .mock-tab-lbl { transition: none; }
}

/* ── the model strip ──────────────────────────────────────────────────────
   Which models the assistant can think with, under the hero CTAs and on the
   sign-in panel. Deliberately quiet: it is reassurance, not a claim, and it
   sits directly beneath the two buttons that are the actual point of the hero.

   ⚠ Marks are 20px, not the 28px the mail strip uses. That strip is a section
   of its own with its own heading; this one is a footnote to a call to action,
   and at 28px it competed with the buttons above it. */
.powered {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: .5rem 1.1rem;
  margin-top: 1.5rem;
}
.powered-label {
  font-size: .78rem; font-weight: 500; letter-spacing: .06em;
  text-transform: uppercase;
}
.powered-list {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: .45rem 1.1rem;
  margin: 0; padding: 0; list-style: none;
}
.powered-item {
  display: inline-flex; align-items: center; gap: .4rem;
  font-size: .88rem; font-weight: 500;
}
.powered-mark {
  width: 20px; height: 20px; display: block; flex: none;
  /* An official mark is a picture with its own proportions; contain keeps it
     from being stretched into the square. Harmless on the inlined SVGs. */
  object-fit: contain;
}

/* ⚠ .powered-light recolours the TEXT only. The official marks are images and
   keep their own colours, which is what a brand asset is for — so a mark that
   is black (OpenAI's is) stays black on a dark ground and disappears. Both
   places this strip is used today sit on a pale ground. Before putting one on
   a dark band, check every mark in the list survives it.

   ⚠ The two tones both carry their own colours, rather than one of them being
   the bare .powered default. Naming a tone in the markup and then styling only
   the other one is how `powered-dark` ended up in the HTML with no rule behind
   it anywhere — which the compiled-class test caught, and which would otherwise
   have been invisible until somebody changed the inherited colour.

   `dark` means dark ink, for a pale ground; `light` means light ink, for a dark
   one — the same sense `kbtn()` uses for its own tone argument.

   The marks drawn in currentColor follow the text. The two carrying brand
   colour do not, because that colour is part of recognising them. */
.powered-dark  .powered-label { color: color-mix(in srgb, var(--color-secondary) 45%, transparent); }
.powered-dark  .powered-item  { color: color-mix(in srgb, var(--color-secondary) 70%, transparent); }
.powered-light .powered-label { color: color-mix(in srgb, #fff 55%, transparent); }
.powered-light .powered-item  { color: color-mix(in srgb, #fff 80%, transparent); }

/* Left-aligned variant, for the sign-in panel where everything else is. */
.powered-start { justify-content: flex-start; }

/* ── the mobile menu ──────────────────────────────────────────────────────
   ⚠ Below 1280px the header's <nav> is `hidden xl:flex` and nothing replaced
   it. On a phone the site was the logo and one button: Features, How it
   works, Pricing and Contact could not be reached at all.

   Built out of a checkbox and two labels, with no script. The kit's main.js
   drives the desktop mega-menu, and a navigation that only exists once a
   300KB bundle has parsed is a navigation that is missing on the connection
   most likely to need it. */
.nav-toggle { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }

.burger {
  display: flex; align-items: center; justify-content: center;
  width: 42px; height: 42px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--color-stroke-1, rgba(18,22,31,.12));
  background: rgba(255,255,255,.6);
  transition: background .2s ease, border-color .2s ease;
  flex: none;
}
.burger:hover { background: #fff; }
.nav-toggle:focus-visible ~ div .burger { outline: 2px solid var(--color-primary-500); outline-offset: 2px; }

.burger-bars { display: block; position: relative; width: 17px; height: 12px; }
.burger-bars span {
  position: absolute; left: 0; width: 100%; height: 1.6px; border-radius: 2px;
  background: var(--color-secondary, #12161f);
  transition: transform .28s var(--bounce), opacity .18s ease;
}
.burger-bars span:nth-child(1) { top: 0; }
.burger-bars span:nth-child(2) { top: 5.2px; }
.burger-bars span:nth-child(3) { top: 10.4px; }

/* Bars to cross. The middle one fades rather than moving, so the two that
   rotate meet in the centre instead of sweeping past a third line. */
.nav-toggle:checked ~ div .burger-bars span:nth-child(1) { transform: translateY(5.2px) rotate(45deg); }
.nav-toggle:checked ~ div .burger-bars span:nth-child(2) { opacity: 0; transform: scaleX(.4); }
.nav-toggle:checked ~ div .burger-bars span:nth-child(3) { transform: translateY(-5.2px) rotate(-45deg); }

/* The tap-away closer. Inert until the menu is open, so it never sits over
   the page swallowing clicks. It is mostly redundant now that the sheet is
   full-screen, and it is kept for the sliver beside it on a tablet. */
.nav-scrim {
  position: fixed; inset: 0; z-index: 40;
  background: rgba(18,22,31,.28);
  opacity: 0; pointer-events: none;
  transition: opacity .25s ease;
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}
.nav-toggle:checked ~ .nav-scrim { opacity: 1; pointer-events: auto; }

/* ── the sheet ────────────────────────────────────────────────────────────
   ⚠ Full-screen and OPAQUE. The first version was a translucent card hanging
   under the header pill, and on a phone the page read straight through it:
   the headline and the hero mock sat behind the links and neither was
   legible. Glass works over a photograph. It does not work over text.        */
/* ⚠ The tint is a second, opaque paint layer under everything, not glass:
   same reasoning as .feat-panel. A radial wash mixed from the operator's own
   primary colour into the solid page colour is still 100% opaque at every
   pixel, so it repaints per-workspace and never risks the "text through
   glass" problem the comment above this rule already ruled out once. */
.nav-sheet {
  position: fixed; inset: 0; z-index: 60;
  display: flex; flex-direction: column;
  padding: 18px 20px calc(20px + env(safe-area-inset-bottom));
  background:
    radial-gradient(120% 60% at 100% 0%,
      color-mix(in srgb, var(--color-primary-500) 10%, transparent) 0%,
      transparent 60%),
    var(--color-background-13, #f4f5f9);
  overflow-y: auto; overscroll-behavior: contain;
  opacity: 0; visibility: hidden;
  transform: translateY(-10px);
  transition: opacity .22s ease, transform .3s var(--bounce),
              visibility 0s .3s;
}
.nav-toggle:checked ~ .nav-sheet {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity .22s ease, transform .3s var(--bounce), visibility 0s;
}

/* ⚠ THE SHEET IS A SIBLING OF THE PILL, NOT A CHILD OF IT, and it has to be.
   The pill carries `-translate-x-1/2` and a backdrop-filter, and either one
   alone makes it the containing block for `position: fixed` inside it — so
   `inset: 0` meant "the pill", and the sheet rendered as a 64px strip across
   the top with its links outside the box and invisible. Nothing errored; the
   element was there, correctly styled, and the wrong size.
   Out here `inset: 0` means the viewport, which is what it says. */

/* Scroll lock. :has() is the only way to reach <body> from a control inside
   the header; where it is unsupported the sheet still covers the screen and
   the page merely scrolls behind it, which is the old behaviour rather than a
   broken one. */
body:has(.nav-toggle:checked) { overflow: hidden; }

/* ⚠ A column, capped. Below 1280px includes tablets, and a menu row stretched
   across 1000px puts the label at one edge and its arrow at the other with
   nothing in between — the two stop reading as one thing. Everything inside
   the sheet shares the cap and is centred. */
.nav-sheet > * { width: 100%; max-width: 34rem; margin-inline: auto; }

.nav-sheet-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding-bottom: 8px;
}
.nav-sheet-brand {
  display: flex; align-items: center; gap: 10px;
  text-decoration: none; color: var(--color-secondary, #12161f);
  font-size: 17px; font-weight: 500;
}
.nav-sheet-mark {
  width: 32px; height: 32px; border-radius: 9px; flex: none;
  display: grid; place-items: center;
  background: var(--color-secondary, #12161f); color: #fff; font-size: 12px;
}
.nav-sheet-close {
  flex: none; display: grid; place-items: center;
  width: 42px; height: 42px; border-radius: 999px; cursor: pointer;
  color: var(--color-secondary, #12161f);
  border: 1px solid rgba(18,22,31,.12); background: rgba(255,255,255,.7);
  transition: background .2s ease, transform .2s ease;
}
.nav-sheet-close:hover { background: #fff; transform: rotate(90deg); }
.nav-sheet-close:focus-visible { outline: 2px solid var(--color-primary-500); outline-offset: 2px; }

/* ── the entrance ─────────────────────────────────────────────────────────
   Each row arrives just after the one above it. --i is set per item in the
   markup, so the delay is arithmetic rather than a rule per child, and the
   footer continues the same count instead of restarting it. */
@keyframes nav-row-in {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}
.nav-sheet-list { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 2px; }
.nav-sheet-list li, .nav-sheet-foot { opacity: 0; }
.nav-toggle:checked ~ .nav-sheet .nav-sheet-list li,
.nav-toggle:checked ~ .nav-sheet .nav-sheet-foot {
  animation: nav-row-in .42s var(--bounce) both;
}
/* ⚠ nth-child rather than a --i written into a style attribute. An inline
   style is discarded by `style-src 'self'` with no error, so the delays would
   quietly stop existing the day this site gets a policy and the only symptom
   would be that the menu no longer felt like anything. Six rules is a small
   price for a stagger that cannot evaporate.
   The footer continues the count rather than restarting it, so the buttons
   arrive after the last link instead of alongside the first. */
.nav-toggle:checked ~ .nav-sheet .nav-sheet-list li:nth-child(1) { animation-delay: 105ms; }
.nav-toggle:checked ~ .nav-sheet .nav-sheet-list li:nth-child(2) { animation-delay: 150ms; }
.nav-toggle:checked ~ .nav-sheet .nav-sheet-list li:nth-child(3) { animation-delay: 195ms; }
.nav-toggle:checked ~ .nav-sheet .nav-sheet-list li:nth-child(4) { animation-delay: 240ms; }
.nav-toggle:checked ~ .nav-sheet .nav-sheet-list li:nth-child(5) { animation-delay: 285ms; }
.nav-toggle:checked ~ .nav-sheet .nav-sheet-list li:nth-child(n+6) { animation-delay: 330ms; }
.nav-toggle:checked ~ .nav-sheet .nav-sheet-foot { animation-delay: 375ms; }

.nav-sheet-list a {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 16px 14px; border-radius: 14px; text-decoration: none;
  font-size: 20px; letter-spacing: -.01em;
  color: color-mix(in srgb, var(--color-secondary, #12161f) 72%, transparent);
  transition: background .2s ease, color .2s ease;
}
.nav-sheet-row { display: flex; align-items: baseline; gap: 12px; }
/* ⚠ The index, not a bullet. Six identical rows of plain text read as one
   grey block on a phone-sized sheet; a mono count gives each row its own
   address the way the reference sites (Superhuman, nativecreativa) do, and
   it costs nothing dynamic — site_pages() is already an ordered list. */
.nav-sheet-idx {
  font-variant-numeric: tabular-nums; font-size: 13px; font-weight: 600;
  color: color-mix(in srgb, var(--color-primary-500, #864ffe) 70%, transparent);
  min-width: 1.4em;
}
.nav-sheet-list a svg { opacity: 0; transform: translateX(-8px); transition: opacity .24s ease, transform .24s ease; }
.nav-sheet-list a:hover,
.nav-sheet-list a:focus-visible { background: rgba(18,22,31,.05); color: var(--color-secondary, #12161f); }
.nav-sheet-list a:hover svg { opacity: .5; transform: none; }
.nav-sheet-list a.is-here { color: var(--color-secondary, #12161f); font-weight: 500; }
.nav-sheet-list a.is-here svg { opacity: .35; transform: none; }
.nav-sheet-list a.is-here .nav-sheet-idx { color: var(--color-primary-500, #864ffe); }

/* ── the Features row's own submenu, mobile ─────────────────────────────
   <details> nested inside one row of .nav-sheet-list rather than a second
   navigation mechanism — the summary carries the exact row styling the
   plain links above it use, so it reads as one more row that happens to
   open, not as a different kind of control. */
.nav-sheet-sub > summary {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 16px 14px; border-radius: 14px; cursor: pointer; list-style: none;
  font-size: 20px; letter-spacing: -.01em;
  color: color-mix(in srgb, var(--color-secondary, #12161f) 72%, transparent);
  transition: background .2s ease, color .2s ease;
}
.nav-sheet-sub > summary::-webkit-details-marker { display: none; }
.nav-sheet-sub > summary:hover,
.nav-sheet-sub > summary:focus-visible { background: rgba(18,22,31,.05); color: var(--color-secondary, #12161f); }
.nav-sheet-sub > summary.is-here { color: var(--color-secondary, #12161f); font-weight: 500; }
.nav-sheet-sub-chev { flex: none; transition: transform .3s var(--bounce, ease); }
.nav-sheet-sub[open] .nav-sheet-sub-chev { transform: rotate(180deg); }

.nav-sheet-sublist {
  list-style: none; margin: 2px 0 6px; padding: 0 0 0 38px; display: grid; gap: 2px;
}
/* ⚠ justify-content: flex-start, explicitly, not left to the default: this
   `<a>` is still a descendant of `.nav-sheet-list` (the sublist nests inside
   one of its `<li>`s), and that ancestor rule sets `justify-content:
   space-between` on every `a` beneath it — same specificity, earlier in the
   file, so it silently wins on any property this rule doesn't redeclare.
   Without this line the icon sat left and the label got shoved to the far
   right edge with nothing overriding the gap between them. */
.nav-sheet-sublist a {
  display: flex; align-items: center; justify-content: flex-start; gap: 10px;
  padding: 11px 14px; border-radius: 12px;
  text-decoration: none;
  font-size: 16px; color: color-mix(in srgb, var(--color-secondary, #12161f) 60%, transparent);
  transition: background .2s ease, color .2s ease;
}
.nav-sheet-sublist a:hover,
.nav-sheet-sublist a:focus-visible { background: rgba(18,22,31,.05); color: var(--color-secondary, #12161f); }
/* Same mark the desktop panel's own .feat-menu-ico uses, shrunk for a
   narrower row - a phone gets the same nine icons, not a plainer list
   because there was less room to draw them in. */
.nav-sheet-sub-ico {
  flex: none; display: grid; place-items: center; width: 1.9rem; height: 1.9rem;
  border-radius: .6rem; background: color-mix(in srgb, var(--color-primary-500) 14%, #fff);
  color: var(--color-primary-500);
}
/* ⚠ `.nav-sheet-list a svg { opacity: 0; ... }` (the hover-reveal arrow on
   the plain top-level rows) reaches these icons too, same inheritance issue
   as above, and there `.nav-sheet-sub-ico svg` alone can't win: its
   specificity (one class + svg) is lower than that rule's (one class + a +
   svg). `.nav-sheet-sublist a svg` matches it exactly and comes later, so it
   wins and the icons render instead of sitting invisible at opacity 0. */
.nav-sheet-sublist a svg { width: 1rem; height: 1rem; opacity: 1; transform: none; }

.nav-sheet-foot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: auto; padding: 18px 4px 4px;
  border-top: 1px solid rgba(18,22,31,.08);
}
/* ⚠ THE BRAND NEVER APPEARED ON THE ONE DEVICE THIS COMPONENT EXISTS FOR.
   The primary action was filled with --color-secondary (the ink) and only turned
   --color-primary-500 on :hover. This sheet is the PHONE navigation; there is
   no hover on a phone, so the operator's colour was on a state no visitor
   could ever reach, and the drawer closed on a black pill while every other
   call to action on the site is brand-filled. The fills are the way round the
   rest of the site has them - brand at rest, ink under the pointer, which is
   what kbtn() does with `btn-primary hover:btn-secondary`.

   Both pills centre their own contents now: text-align does nothing to a flex
   item's children, so adding an icon to a text-align:center pill would have
   left the pair hanging off the left edge. */
.nav-sheet-in, .nav-sheet-go {
  flex: 1 1 40%; display: inline-flex; align-items: center; justify-content: center;
  gap: 8px; padding: 15px; border-radius: 999px;
  text-decoration: none; font-size: 15px; font-weight: 500; white-space: nowrap;
  transition: background-color .2s ease, background-position .5s cubic-bezier(.3,.7,.3,1),
              color .2s ease, box-shadow .2s ease, transform .12s ease;
}
.nav-sheet-in svg, .nav-sheet-go svg { flex: none; }

.nav-sheet-in {
  color: color-mix(in srgb, var(--color-secondary, #12161f) 68%, transparent);
  border: 1px solid rgba(18,22,31,.14);
  background: rgba(255,255,255,.7);
}
.nav-sheet-in:hover { color: var(--color-secondary, #12161f); background: #fff; }

/* ⚠ Same two-layer sheen as .btn-go (main.css / kbtn()), by hand: this pill
   is not built on kbtn's markup (see the note above on why), so it cannot
   inherit .btn-go itself, but it is still the site's primary action and was
   the one filled pill anywhere on the site with a flat, dead background. */
.nav-sheet-go {
  color: #fff;
  background-color: var(--color-primary-500, #864ffe);
  background-image:
      linear-gradient(105deg, transparent 38%, rgba(255,255,255,.26) 50%, transparent 62%),
      linear-gradient(180deg, rgba(255,255,255,.17), rgba(0,0,0,.12));
  background-repeat: no-repeat;
  background-size: 260% 100%, 100% 100%;
  background-position: 150% 0, 0 0;
  box-shadow: 0 8px 20px color-mix(in srgb, var(--color-primary-500, #864ffe) 38%, transparent);
}
.nav-sheet-go:hover {
  background-color: var(--color-secondary, #12161f);
  background-position: -60% 0, 0 0;
  box-shadow: 0 8px 20px color-mix(in srgb, var(--color-secondary, #12161f) 26%, transparent);
}
.nav-sheet-go svg { transition: transform .2s ease; }
.nav-sheet-go:hover svg { transform: translateX(3px); }

/* ⚠ Touch has no hover, so the only feedback a finger gets is this. Without
   it the pill is inert between the tap and the page changing, which on a slow
   connection is long enough to tap again. */
.nav-sheet-in:active, .nav-sheet-go:active { transform: translateY(1px); }
.nav-sheet-note {
  flex: 1 1 100%; margin: 4px 0 0; text-align: center; font-size: 12.5px;
  color: color-mix(in srgb, var(--color-secondary, #12161f) 50%, transparent);
}

/* Above the breakpoint the real navigation is back, so none of this exists. */
@media (min-width: 1280px) {
  .nav-sheet, .nav-scrim, .burger { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .nav-sheet, .nav-scrim, .burger-bars span, .nav-sheet-list a svg,
  .nav-sheet-close, .nav-sheet-go { transition: none; }
  /* The rows still need to end up visible, so the animation is removed rather
     than the opacity: 0 that starts it. */
  .nav-sheet-list li, .nav-sheet-foot { opacity: 1; }
  .nav-toggle:checked ~ .nav-sheet .nav-sheet-list li,
  .nav-toggle:checked ~ .nav-sheet .nav-sheet-foot { animation: none; }
  /* Same reasoning as .btn-go's own reduced-motion block: the sheen travels,
     so it goes; the resting depth layer is not motion and stays. */
  .nav-sheet-go { background-position: 0 0, 0 0; }
}

/* ── the photograph behind the hero ───────────────────────────────────────
   ⚠ Dimmed, blurred and masked HERE rather than baked into the file. Three
   things follow from that: the strength changes without re-exporting an
   image, one photograph can sit behind a light band and a dark one, and an
   operator who uploads their own gets the same treatment for free.

   The mask is the important half. A photograph that stops at a hard edge
   reads as a banner the page is sitting under; one that fades out before the
   text begins reads as depth. It is gone by 78% of the way down, and every
   word of the hero is below that.

   The blur is 3px, which is not enough to see and is enough to stop the
   photograph competing for focus with 60px type. */
/* ⚠ THE HERO STARTS AT THE TOP OF THE PAGE, not below the header.
   <main> carries the padding that clears the fixed header, so the hero
   section began 100px down and its photograph began with it — leaving a band
   of plain page above, with the header pill floating on nothing and a visible
   seam where the picture started. The navigation is meant to sit ON the
   photograph, not above it.
   So the hero pulls itself back up by exactly that padding and puts it back
   as its own. Same clearance for the words, and the section's box now reaches
   the top of the document, which is where inset: 0 sends the picture.
   ⚠ Only .hero. Every other page still wants <main>'s padding to be the gap
   it has always been. */
.hero { margin-top: -100px; padding-top: calc(100px + 3rem); position: relative; }

/* Two more orbs, at the section's own edges rather than centred on the mock
   like .hero-stage's pair — spread out so the whole hero reads as a living
   field of light rather than one lit object. Different hues from the kit's
   own extra palette (--color-ns-*), not new colours invented for this. */
.hero::before, .hero::after {
  content: ""; position: absolute; z-index: -11; pointer-events: none;
  width: min(30rem, 60%); height: 40%; border-radius: 50%;
  filter: blur(80px);
}
.hero::before {
  left: -8%; bottom: 8%;
  background: radial-gradient(55% 55% at 50% 50%,
              color-mix(in srgb, var(--color-ns-cyan, #83e7ee) 26%, transparent) 0%, transparent 72%);
  animation: hero-orb-a 11s ease-in-out infinite alternate;
}
.hero::after {
  right: -10%; bottom: -4%;
  background: radial-gradient(55% 55% at 50% 50%,
              color-mix(in srgb, var(--color-ns-yellow, #f9eb57) 22%, transparent) 0%, transparent 72%);
  animation: hero-orb-b 13s ease-in-out infinite alternate;
}
@keyframes hero-orb-a {
  from { transform: translate(0, 0) scale(1); }
  to   { transform: translate(6%, -8%) scale(1.15); }
}
@keyframes hero-orb-b {
  from { transform: translate(0, 0) scale(1); }
  to   { transform: translate(-7%, -6%) scale(1.1); }
}
@media (prefers-reduced-motion: reduce) {
  .hero::before, .hero::after { animation: none; }
}

.hero-photo {
  position: absolute; inset: 0; z-index: -10;
  overflow: hidden; pointer-events: none;
  -webkit-mask-image: radial-gradient(115% 78% at 50% 0%, #000 0%, rgba(0,0,0,.55) 42%, transparent 78%);
          mask-image: radial-gradient(115% 78% at 50% 0%, #000 0%, rgba(0,0,0,.55) 42%, transparent 78%);
}
.hero-photo img {
  width: 100%; height: 100%; object-fit: cover;
  opacity: .38;
  filter: blur(2px) saturate(.72);
  /* The blur samples past the element's edge and leaves a soft border of
     nothing; a hair of scale pushes that outside the mask. */
  transform: scale(1.03);
}
/* ⚠ The ground, over the photograph and under the text. Without it the
   contrast of the headline depends on what the picture happens to be doing
   at that point, and an operator who uploads a dark one gets dark grey text
   on it with nothing anywhere warning them. */
.hero-photo::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to bottom,
              color-mix(in srgb, var(--color-background-13, #f4f5f9) 30%, transparent) 0%,
              color-mix(in srgb, var(--color-background-13, #f4f5f9) 78%, transparent) 58%,
              var(--color-background-13, #f4f5f9) 100%);
}

/* ── every OTHER page's own opening band ──────────────────────────────────
   ⚠ shell_page_head()'s own device, not .hero-photo reused: that mask and
   fade are tuned for a tall, near-full-viewport hero, and stretched over a
   band this short they would either barely show or blow straight past the
   text into the section below. This is the same idea - dimmed, blurred,
   faded to the page ground at both edges - sized for something a few
   hundred pixels tall instead of a screenful.
   isolation: isolate gives .page-head its own stacking context, so the
   photo's z-index: -10 stays pinned behind THIS band - see .empty-badge's
   own comment on the same fix for the same reason. */
.page-head { position: relative; isolation: isolate; }
.page-head-photo {
  position: absolute; inset: 0; z-index: -10;
  overflow: hidden; pointer-events: none;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 25%, #000 65%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0%, #000 25%, #000 65%, transparent 100%);
}
.page-head-photo img {
  width: 100%; height: 100%; object-fit: cover;
  opacity: .3;
  filter: blur(2px) saturate(.72);
  transform: scale(1.03);
}
.page-head-photo::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to bottom,
              color-mix(in srgb, var(--color-background-13, #f4f5f9) 40%, transparent) 0%,
              var(--color-background-13, #f4f5f9) 88%);
}

/* The two cards lifted off the mock. Their positions were inline attributes,
   which a style-src 'self' policy discards without saying so — the cards
   would have stacked at the panel's top-left corner and nothing would have
   reported it.
   ⚠ .hero-note carries the position itself rather than leaving it to the kit's
   `absolute z-20` utilities beside it. A hook class with no rule of its own is
   exactly what the compiled-build test exists to catch: it reads as meaningful
   and does nothing. */
.hero-note    { position: absolute; z-index: 20; }
.hero-note-tr { top: -22px; right: 2%; }
.hero-note-bl { bottom: -22px; left: 2%; }

/* The trial badge under the buttons. Its own class rather than .badge: the
   kit's badge lowercases its text (see kbtn()'s comment on the same house
   style), which "Free trial" — a phrase built from live numbers, not typed
   as one string a first-letter fix could reach — cannot rely on.
   ⚠ A small lit chip for the check, not a bare glyph sitting in the text -
   same radial-mesh technique as the plan cards' own feature icons
   (.plan-feat-ico) and the primary button's chip, so "free, no card" reads
   as a considered reassurance rather than a caption. */
.hero-trial-badge {
  display: inline-flex; align-items: center; gap: .55rem;
  padding: .3rem .9rem .3rem .3rem; border-radius: 999px; font-size: .84rem; font-weight: 500;
  background: color-mix(in srgb, var(--color-primary-500) 10%, #fff);
  color: color-mix(in srgb, var(--color-primary-500) 85%, var(--color-secondary));
  box-shadow: 0 1px 2px rgba(16,24,40,.04);
}
.hero-trial-badge .trial-ico {
  flex: none; display: grid; place-items: center;
  width: 1.5rem; height: 1.5rem; border-radius: 999px;
  background:
    radial-gradient(120% 130% at 24% 22%,
      color-mix(in srgb, var(--color-primary-500) 55%, #fff) 0%,
      var(--color-primary-500) 45%,
      color-mix(in srgb, var(--color-primary-500) 78%, var(--color-secondary)) 100%);
  box-shadow: inset 0 1px 1px rgba(255,255,255,.4);
}
.hero-trial-badge .trial-ico svg { width: .7rem; height: .7rem; color: #fff; }
/* ⚠ EACH ON ITS OWN LINE, always - never inline-wrapped with the sentence
   beside it. Side by side, the pair only looked aligned when both fit one
   line; a badge with a longer number in it (a workspace with a bigger
   trial allowance) wrapped the sentence onto a second line that started
   under the badge instead of centred under the headline like everything
   else on this page. */
.hero-trial { display: flex; flex-direction: column; align-items: center; gap: .5rem; }
.hero-trial p { margin: 0; }

/* The highlighted half of the headline. --brand-accent is the operator's
   own accent colour (site_palette_css()), so this moves with the palette
   the same way the hero's second glow does — never a fixed colour typed
   here. */
.hero-hl { color: var(--brand-accent, var(--color-primary-500)); }

/* ── the CTA row and the Google button under it ───────────────────────────
   .hero-ctas-main is the two-button row exactly as before; .cta-or and the
   Google button sit under it as their own centred block, not a third
   button competing with the first two for the same row. */
.hero-ctas { display: flex; flex-direction: column; align-items: center; }
.hero-ctas-main { display: flex; flex-direction: column; align-items: center; gap: 1rem; }
@media (min-width: 768px) { .hero-ctas-main { flex-direction: row; } }
.hero-ctas-google { width: 100%; max-width: 22rem; }

@media (prefers-reduced-motion: reduce) {
  .hero-photo img { filter: saturate(.85); }
}

/* ── the two doors in the header ──────────────────────────────────────────
   ⚠ "Sign in" was a bare word in grey at 60% opacity next to a filled pill,
   and `hidden md:block` on top of that. Two problems in one line: it read as
   a caption on the button rather than as the other door, and below the
   breakpoint it was gone entirely — so a returning customer on a phone saw
   one option, and it was "start an account you already have".

   Both are controls now, both carry a mark, and both exist at every width.
   The visual weight still differs, because they are not equal choices: one
   is what most visitors are here to do. */
.nav-actions { display: flex; align-items: center; gap: .5rem; }

.nav-in {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .62rem .95rem; border-radius: 999px;
  font-size: .95rem; line-height: 1; text-decoration: none; white-space: nowrap;
  color: color-mix(in srgb, var(--color-secondary, #12161f) 72%, transparent);
  border: 1px solid transparent;
  transition: color .2s ease, background .2s ease, border-color .2s ease;
}
.nav-in svg { width: 1.05rem; height: 1.05rem; flex: none; opacity: .55;
              transition: opacity .2s ease, transform .25s var(--bounce); }
.nav-in:hover {
  color: var(--color-secondary, #12161f);
  background: rgba(18, 22, 31, .045);
  border-color: rgba(18, 22, 31, .08);
}
.nav-in:hover svg { opacity: .9; transform: translateX(2px); }
.nav-in:focus-visible { outline: 2px solid var(--color-primary-500); outline-offset: 2px; }

/* ── the header's own Get started, quieter than the page's ─────────────────
   A dark-bodied v3 pill in the header competes with the one in the hero,
   which is the button actually being offered. Up here it only has to be
   findable. Outline, transparent body, the chip's own brand mesh doing the
   colour work it always does - no fill-on-hover any more, because the chip
   is already the loudest thing on this control and filling the body too
   would put the same colour on the button twice.

   ⚠ Scoped to .nav-actions so .btn-v3-primary is untouched everywhere else,
   and no !important: the utilities it overrides are unlayered-versus-layered,
   which the ordinary cascade already settles. */
.nav-actions .btn-v3-primary {
  background: transparent;
  border: 1px solid var(--color-primary-500, #f0910a);
  color: var(--color-secondary, #14161a);
  box-shadow: none;
  padding: 4px 4px 4px 18px;
}
.nav-actions .btn-v3-primary:hover, .nav-actions .btn-v3-primary:focus-visible {
  border-color: color-mix(in srgb, var(--color-primary-500, #f0910a) 55%, var(--color-secondary, #14161a));
  box-shadow: 0 8px 18px -12px rgba(16, 24, 40, .3);
}
.nav-actions [data-button-v3-icon] { width: 1.9rem; height: 1.9rem; }
.nav-actions [data-button-v3-icon] svg { width: .85rem; height: .85rem; }

/* ⚠ On a phone the header holds four things — logo, name, this, the primary
   action, the menu button — and five is one too many for 390 points. A
   sign-in icon on its own beside a filled pill is also the least legible of
   them: it is the only one whose meaning has to be guessed.
   So below 560px it goes, and it goes to the MENU, where it is a full-width
   labelled button beside the primary action. That is the opposite of the old bug,
   where it was hidden and there was nowhere else to find it — there is
   somewhere else now, and it is better than this was. */
@media (max-width: 559px) {
  .nav-in { display: none; }
  .nav-actions { gap: .35rem; }
}
@media (prefers-reduced-motion: reduce) {
  .nav-in, .nav-in svg { transition: none; }
}

/* ── the signed-in visitor's own door ─────────────────────────────────────
   ⚠ <details>/<summary>, not a script — see the comment beside it in
   shell.php. Same shape as the console sidebar's account switcher
   (console.css's .switcher), redrawn in the kit's own tokens rather than
   the console's, the same way every other control on this page has its own
   copy rather than sharing the console's stylesheet.

   Hidden below 560px for the same reason .nav-in already is: the mobile
   sheet's .nav-sheet-foot carries the signed-in state there instead, as a
   full-width "Signed in as" line and two labelled pills rather than a
   dropdown with no room to open in. */
.acct-switcher { position: relative; }
.acct-switcher > summary {
  display: inline-flex; align-items: center; gap: .5rem; cursor: pointer;
  list-style: none; padding: .4rem .9rem .4rem .4rem; border-radius: 999px;
  font-size: .95rem; line-height: 1; white-space: nowrap;
  color: var(--color-secondary, #12161f);
  border: 1px solid rgba(18,22,31,.14); background: rgba(255,255,255,.7);
  transition: background .2s ease, border-color .2s ease;
}
.acct-switcher > summary::-webkit-details-marker { display: none; }
.acct-switcher > summary:hover { background: #fff; }
.acct-switcher > summary:focus-visible { outline: 2px solid var(--color-primary-500, #864ffe); outline-offset: 2px; }
.acct-switcher .chev { width: .7rem; height: .7rem; flex: none; opacity: .5; transition: transform .18s ease; }
.acct-switcher[open] .chev { transform: rotate(180deg); }
.acct-dot {
  display: grid; place-items: center; width: 1.6rem; height: 1.6rem; flex: none;
  border-radius: 50%; font-size: .72rem; font-weight: 600; color: #fff;
  background: var(--color-primary-500, #864ffe);
}
.acct-menu {
  position: absolute; top: calc(100% + .5rem); right: 0; z-index: 40;
  min-width: 12rem; padding: .4rem; border-radius: 1rem;
  background: #fff; border: 1px solid rgba(18,22,31,.1);
  box-shadow: 0 16px 40px rgba(18,22,31,.14);
}
.acct-menu a {
  display: flex; align-items: center; gap: .5rem; padding: .55rem .65rem;
  border-radius: .6rem; font-size: .9rem; text-decoration: none; white-space: nowrap;
  color: color-mix(in srgb, var(--color-secondary, #12161f) 80%, transparent);
  transition: background .16s ease, color .16s ease;
}
.acct-menu a svg { width: 1rem; height: 1rem; flex: none; opacity: .6; }
.acct-menu a:hover { background: rgba(18,22,31,.05); color: var(--color-secondary, #12161f); }
/* Wide enough for an email address on one line - the plain 12rem menu was
   sized for two short labels and wrapped "fidanovski.martin@example.com"
   across two lines under "Signed in". */
.acct-menu-wide { min-width: 16.5rem; }
.acct-menu-head { padding: .5rem .65rem .6rem; }
.acct-menu-head strong { display: block; font-size: .85rem; color: var(--color-secondary, #12161f); }
.acct-menu-head .mono {
  display: block; margin-top: .15rem; font-size: .78rem; word-break: break-all;
  color: color-mix(in srgb, var(--color-secondary, #12161f) 55%, transparent);
}
.acct-sep { height: 1px; margin: .3rem .2rem; background: rgba(18,22,31,.08); }
/* ⚠ Shrunk, not hidden. It used to disappear here entirely, on the reasoning
   that the burger sheet already shows "Signed in as ...". But that left the
   header itself with no sign a phone was signed in at all — every visit read
   as a stranger's until the menu was opened. The dot alone answers that at a
   glance, and still opens the same dropdown: only the name and chevron next
   to it go, for room. */
@media (max-width: 559px) {
  .acct-switcher > summary { padding: .4rem; }
  .acct-switcher > summary > span:not(.acct-dot),
  .acct-switcher .chev { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .acct-switcher .chev { transition: none; }
}

/* ── the Features mega-menu ────────────────────────────────────────────────
   ⚠ A PLAIN LINK, :hover AND :focus-within — NOT <details>/<summary>. The
   first build of this used <details>, the same mechanism .acct-switcher
   uses, and it does not work here: Chrome keeps a closed <details>'s
   content out of rendering internally, tied specifically to the `open`
   attribute, and no author CSS can make it paint on :hover alone — display,
   opacity, even !important all lose to that. See the note above the markup
   in shell.php. A plain element has no such restriction: opacity/transform/
   visibility here are the only things controlling it, so hover, keyboard
   focus (:focus-within, the moment Tab reaches the "Features" link or any
   item inside the panel) and touch (the link just navigates to /features)
   are three real ways in rather than one broken one. */
.feat-menu { position: relative; }
.feat-menu-chev { width: .7rem; height: .7rem; flex: none; opacity: .5; transition: transform .18s ease; }
.feat-menu:hover > a .feat-menu-chev,
.feat-menu:focus-within > a .feat-menu-chev { transform: rotate(180deg); }
.feat-menu-panel {
  position: absolute; top: calc(100% + .6rem); left: 0; z-index: 40;
  width: min(40rem, 92vw); padding: .9rem; border-radius: 1.1rem;
  background: #fff; border: 1px solid rgba(18,22,31,.1);
  box-shadow: 0 16px 48px rgba(18,22,31,.16);
  display: grid; grid-template-columns: 1fr; gap: .2rem;
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(-10px) scale(.96);
  transition: opacity .2s ease, transform .35s var(--bounce, ease), visibility 0s linear .2s;
}
.feat-menu:hover > .feat-menu-panel,
.feat-menu:focus-within > .feat-menu-panel {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateY(0) scale(1);
  transition: opacity .2s ease, transform .35s var(--bounce, ease), visibility 0s linear 0s;
}
/* ⚠ THE GAP ITSELF STAYS HOVERABLE. .6rem of clear space sits between the
   link and the panel — without this, moving the cursor from one to the
   other crosses dead space, :hover on .feat-menu lapses for a frame, and
   the menu closes just before the panel it was heading for. An invisible
   pseudo-element bridges it, exactly as wide as the real gap. */
.feat-menu::after {
  content: ""; position: absolute; left: 0; right: 0; top: 100%; height: .6rem;
}
@media (min-width: 640px) { .feat-menu-panel { grid-template-columns: 1fr 1fr; } }
.feat-menu-item {
  display: flex; align-items: flex-start; gap: .75rem; padding: .7rem .75rem;
  border-radius: .8rem; text-decoration: none;
  transition: background .15s ease;
}
.feat-menu-item:hover, .feat-menu-item:focus-visible { background: var(--color-background-3, #f7f8fa); }
.feat-menu-ico {
  flex: none; display: grid; place-items: center; width: 2.4rem; height: 2.4rem;
  border-radius: .7rem; background: color-mix(in srgb, var(--color-primary-500) 14%, #fff);
  color: var(--color-primary-500);
}
.feat-menu-ico svg { width: 1.2rem; height: 1.2rem; }
.feat-menu-item b {
  display: block; font-size: .9rem; font-weight: 500; color: var(--color-secondary, #12161f);
}
.feat-menu-item > span > span {
  display: block; margin-top: .15rem; font-size: .82rem; line-height: 1.4;
  color: color-mix(in srgb, var(--color-secondary) 58%, transparent);
}
/* ⚠ NO SUB-1280PX RULE HERE ON PURPOSE. The parent <nav> carries
   `hidden xl:flex` (Tailwind), so this whole menu, and everything in it, is
   display:none below that breakpoint regardless of anything written here -
   a fallback layout for it would be dead CSS. A phone gets its own nested
   <details> instead, .nav-sheet-sub below, with the same nine links in a
   plain stacked list rather than this two-column panel. */
@media (prefers-reduced-motion: reduce) {
  .feat-menu-chev { transition: none; }
  .feat-menu-panel { transition: none; }
}

/* ── legal pages ───────────────────────────────────────────────────────
   Privacy, Terms, Refund — long prose rendered from a setting by
   legal_prose() in inc/shell.php. The renderer emits bare <h2>/<p>/<ul>,
   no utility classes: the compiled kit build has no utility for a document
   this shape, and a half-set reads worse than none. All of it is here. */
.legal-doc { color: var(--color-secondary, #12161f); }
.legal-doc h2 {
  margin: 2rem 0 .6rem;
  font-size: 1.15rem;
  font-weight: 500;
  line-height: 1.35;
}
.legal-doc h2:first-child { margin-top: 0; }
.legal-doc p,
.legal-doc li {
  font-size: .975rem;
  line-height: 1.65;
  color: color-mix(in srgb, var(--color-secondary, #12161f) 72%, transparent);
}
.legal-doc p { margin: .9rem 0; }
.legal-list { list-style: disc; padding-left: 1.25rem; margin: .9rem 0; }
.legal-list li { margin: .35rem 0; padding-left: .15rem; }
.legal-doc strong { color: var(--color-secondary, #12161f); font-weight: 600; }
.legal-doc a { color: var(--color-primary-500, #864ffe); text-decoration: underline; }

/* ═══════════════════════════════════════════════════════════════════════
   Things that move, and why each one earns it.

   ⚠ THE RULE THIS FOLLOWS: motion here is either evidence or feedback. The
   product's whole claim is that something is reading your mail while you are
   not looking, and a page of still screenshots argues that badly. So the
   things that move are the things that would be moving — a reader that is
   awake, mail leaving, mail arriving — and everything else stays where it is.

   Nothing below is scroll-linked. The kit's GSAP already owns the reveals;
   adding a second system that also fires on scroll produces two animations
   disagreeing about when a section has arrived. These are load loops and
   hover states, which is why they need no script at all.

   Every one of them is switched off by the prefers-reduced-motion block at
   the end of this file.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── the live dot ─────────────────────────────────────────────────────
   In the mock's title bar. It is a CLAIM, not a decoration: the assistant
   reads on a cron that never stops, and this is the only thing on the page
   that says the screenshot is of something running. */
.live-dot { position: relative; display: inline-flex; width: .5rem; height: .5rem; flex: none; }
.live-dot-core, .live-dot-ring {
  position: absolute; inset: 0; border-radius: 50%;
  background: var(--color-green, #2f9e5f);
}
.live-dot-core { box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-green, #2f9e5f) 22%, transparent); }
/* The ring is the pulse. Slow, and it stops at nothing: a fast blink reads as
   an error indicator, which is the opposite of what this says. */
.live-dot-ring { animation: live-pulse 2.4s ease-out infinite; }
@keyframes live-pulse {
  0%   { transform: scale(1);   opacity: .55; }
  70%  { transform: scale(2.6); opacity: 0; }
  100% { transform: scale(2.6); opacity: 0; }
}

/* ── a row in the queue, on hover ─────────────────────────────────────
   ⚠ THE DIRECTION IS THE POINT. An answered message left; one waiting for a
   person arrived and stopped. data-flow carries which, and the row slides the
   way its mail was going — so hovering the queue demonstrates the one
   distinction the whole product turns on, without a word of copy. */
.mock-row { position: relative; }
.mock-row[data-flow] { transition: background .25s ease, transform .3s var(--bounce); }
.mock-row[data-flow="out"]:hover { transform: translateX(4px); }
.mock-row[data-flow="in"]:hover  { transform: translateX(-4px); }
/* A hairline that runs off the edge the mail went, drawn only while hovered.
   It is the trail, and it is what stops the slide reading as a wobble. */
.mock-row[data-flow]::after {
  content: ""; position: absolute; top: 50%; width: 0; height: 1px;
  background: linear-gradient(to right, transparent, var(--color-primary-500));
  opacity: 0; transition: width .32s var(--bounce), opacity .25s ease;
  pointer-events: none;
}
.mock-row[data-flow="out"]::after { right: 0; }
.mock-row[data-flow="in"]::after  { left: 0; transform: scaleX(-1); }
.mock-row[data-flow]:hover::after { width: 2.5rem; opacity: .8; }
/* The badge is the verdict, so it is what brightens. */
.mock-row:hover .badge { transform: translateY(-1px) scale(1.03); }

/* ── the two notes lifted off the panel ───────────────────────────────
   They drift, a couple of pixels, on long offset cycles so the two never
   agree. It is the same device as the sign-in panel's lights: the page is
   not dead while somebody is reading it. */
.hero-note { animation: note-float 4.5s ease-in-out infinite alternate; }
.hero-note-bl { animation-duration: 5.5s; animation-delay: -3s; }
@keyframes note-float {
  from { transform: translateY(0) rotate(0deg); }
  to   { transform: translateY(-11px) rotate(-1.5deg); }
}

/* ── the services it works with ───────────────────────────────────────
   A row of names is a list to skim. Given a target's response they become
   things somebody looks for their own provider in, which is the question
   that section exists to answer. */
#mailboxes span,
.works-item {
  transition: transform .2s var(--bounce), border-color .2s ease,
              box-shadow .2s ease;
}
#mailboxes span:hover,
.works-item:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--color-secondary) 22%, transparent);
  box-shadow: 0 4px 14px color-mix(in srgb, var(--color-secondary) 10%, transparent);
}

/* ── the rules that withhold a reply ──────────────────────────────────
   ⚠ On the DARK band, so the lift is light rather than shadow: a drop shadow
   on near-black is invisible and the card would appear not to respond at all. */
#gate .rounded-\[20px\] { transition: background .22s ease, transform .3s var(--bounce); }
#gate .rounded-\[20px\]:hover { background: rgba(255,255,255,.09); transform: translateY(-7px) scale(1.02); }

/* ── the numbered steps ───────────────────────────────────────────────
   The number is the thing being counted, so it is the thing that answers. */
.step-num, .flow-num { transition: transform .22s var(--bounce), background .22s ease; }
:hover > .step-num, :hover > .flow-num { transform: scale(1.06); }

@media (prefers-reduced-motion: reduce) {
  .live-dot-ring { animation: none; opacity: .5; }
  .hero-note { animation: none; }
  .mock-row[data-flow], .mock-row[data-flow]::after,
  #mailboxes span, .works-item, .step-num, .flow-num,
  #gate .rounded-\[20px\] { transition: none; }
  .mock-row[data-flow="out"]:hover, .mock-row[data-flow="in"]:hover,
  #mailboxes span:hover, .works-item:hover,
  #gate .rounded-\[20px\]:hover { transform: none; }
  .mock-row[data-flow]:hover::after { width: 0; opacity: 0; }
}

/* ── an optional photograph behind the dark band ──────────────────────
   ⚠ GATE_IMAGE was declared in site_spec() and read by nothing, so an
   operator could upload one and find no trace of it on the site. It sits
   UNDER the wash and the dot grid at a low opacity, because this band is
   white text on near-black and a picture behind it is a contrast problem
   before it is anything else. Empty by default; the band is unchanged until
   somebody sets one. */
.dark-wash .wash-photo {
  position: absolute; inset: 0; z-index: 0; margin: 0;
  overflow: hidden; pointer-events: none;
}
.dark-wash .wash-photo img {
  width: 100%; height: 100%; object-fit: cover;
  opacity: .16; filter: blur(2px) saturate(.6); transform: scale(1.04);
}
.dark-wash .wash-photo::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgba(18,22,31,.55), rgba(18,22,31,.75));
}
/* ⚠ .about-grid too: it is a .dark-wash section whose content sits directly
   in a div with no .main-container wrapper, so without this it is an
   unpositioned block descendant - painted BEFORE a z-index:0 .wash-photo,
   not after. The photo would sit on top of the words, not behind them. */
.dark-wash > .main-container, .dark-wash > .about-grid { position: relative; z-index: 1; }

/* The rule down the edge of an "in practice" card, which is what says these
   are quoted incidents rather than more feature copy. */
.case-item { position: relative; overflow: hidden; }
.case-item::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--color-primary-500);
  opacity: .55;
  transition: width .25s ease, opacity .25s ease;
}
.case-item:hover::before { width: 5px; opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .case-item::before { transition: none; }
}

/* ── the consent question ─────────────────────────────────────────────────
   ⚠ It sits at the bottom, not over the middle of the page. A modal that
   blocks the content until you answer is how "Allow" gets pressed by people
   who have not read it, and an answer given to make a box go away is not the
   answer anybody wanted. Nothing is running while this is on screen, so it can
   afford to wait.

   ⚠ The two buttons are the same size and the same shape. One filled and one
   ghost, so the page still has a primary — but a decline that is grey small
   print in the corner is not a choice, it is a shape that looks like one. */
.consent {
  position: fixed; left: 50%; bottom: 16px; transform: translateX(-50%);
  z-index: 60; width: calc(100% - 32px); max-width: 640px;
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  padding: 16px 20px; border-radius: 18px;
  background: #14161a; color: #e8eaee;
  box-shadow: 0 18px 44px -18px rgba(0, 0, 0, .7), 0 2px 8px -3px rgba(0, 0, 0, .4);
  font-size: 14px; line-height: 1.45;
}
/* ⚠ color: inherit, and it is load-bearing. The kit sets `p { color:#1a1a1c99 }`
   globally (main.css), and a rule that names the element beats a colour
   inherited from an ancestor - so this paragraph took the kit's near-black on
   top of the banner's near-black background and the text vanished. The banner
   has the right colour; the paragraph simply was not taking it.
   Same fix as .site-foot p above, for the same reason. */
.consent p { margin: 0; flex: 1 1 260px; color: inherit; }
.consent-acts { display: flex; gap: 8px; margin-left: auto; }
.consent-acts form { margin: 0; }
.consent-acts button {
  border: 1px solid transparent; border-radius: 999px;
  padding: 9px 20px; font: inherit; font-weight: 600; cursor: pointer;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.consent-yes { background: #fff; color: #14161a; }
.consent-yes:hover { background: #d9dde3; }
.consent-no { background: transparent; color: #e8eaee; border-color: #3a3f47; }
.consent-no:hover { border-color: #6b727c; background: rgba(255, 255, 255, .06); }
.consent-acts button:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* The footer's change-your-mind control: a button that has to read as the
   sentence it sits inside, because it is one. */
.consent-again { display: inline; margin: 0; }
.consent-again button {
  background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
  color: inherit; text-decoration: underline; text-underline-offset: 2px;
}
.consent-again button:hover { opacity: .75; }

@media (max-width: 560px) {
  /* Stacked, and the buttons go full width: a 9px-tall pill shared between two
     forms on a narrow screen is two mis-taps waiting to happen. */
  .consent { flex-direction: column; align-items: stretch; gap: 12px; }
  /* ⚠ flex: 1 1 260px WAS THE PARAGRAPH'S WIDTH BASIS, on the row layout above.
     flex-direction switches the main axis to vertical here, and a basis
     written for one axis does not become meaningless on the other - it
     becomes a HEIGHT of 260px, with flex-grow:1 free to fill whatever comes
     after it. The paragraph never needed more than its own two lines; it took
     up most of the screen instead, with "No thanks" and "Allow" pushed down
     into whatever was left over. grow:0 so it never expands past its content,
     basis:auto so that content is the only thing deciding its size. */
  .consent p { flex: 0 1 auto; }
  .consent-acts { margin-left: 0; }
  .consent-acts form, .consent-acts button { flex: 1 1 auto; width: 100%; }
}

/* the public demo -----------------------------------------------------------
   Two columns on a desktop, so the email and what the assistant made of it
   are side by side and the relationship is obvious without scrolling. One
   column on a phone, input first. */
.demo-grid { display: grid; gap: 20px; grid-template-columns: 1fr; }
@media (min-width: 900px) { .demo-grid { grid-template-columns: 1fr 1fr; } }

.demo-form, .demo-out {
  background: #fff; border: 1px solid var(--color-stroke-1, #e6e8ec);
  border-radius: 20px; padding: 24px;
}
.demo-label { display: block; font-size: 13px; font-weight: 600; margin: 0 0 6px; }
.demo-input {
  width: 100%; margin: 0 0 16px; padding: 11px 13px;
  border: 1px solid var(--color-stroke-1, #e6e8ec); border-radius: 12px;
  font: inherit; font-size: 14px; background: #fff;
}
.demo-body { resize: vertical; min-height: 150px; }
.demo-note { font-size: 12.5px; opacity: .65; margin: 12px 0 0; }
.demo-note svg { width: .8rem; height: .8rem; flex: none; vertical-align: -1px; }

/* ── the home page's demo teaser ──────────────────────────────────────────
   Three nodes and the same dashed lead-in language as .show-link, so the
   two "how this works" moments on the page read as one visual vocabulary
   rather than two different ideas. Stacks on a phone; the connecting line
   only means something as a row. */
.demo-teaser {
  display: flex; flex-direction: column; align-items: center; gap: 1.25rem;
  max-width: 42rem; margin-left: auto; margin-right: auto;
}
@media (min-width: 768px) { .demo-teaser { flex-direction: row; justify-content: center; } }
.demo-teaser-node {
  display: flex; flex-direction: column; align-items: center; gap: .6rem;
  text-align: center; color: var(--color-secondary);
}
.demo-teaser-node svg {
  width: 1.3rem; height: 1.3rem; padding: .7rem; border-radius: 999px;
  background: color-mix(in srgb, var(--color-primary-500) 12%, #fff);
  color: var(--color-primary-500);
}
.demo-teaser-node b { font-size: .86rem; font-weight: 500; max-width: 9rem; }
.demo-teaser-link {
  flex: none; width: 2px; height: 1.5rem;
  background-image: repeating-linear-gradient(180deg,
    var(--color-stroke-1, #e6e8ec) 0 6px, transparent 6px 12px);
}
@media (min-width: 768px) {
  .demo-teaser-link { width: 3rem; height: 2px; margin-top: 1.35rem;
    background-image: repeating-linear-gradient(90deg,
      var(--color-stroke-1, #e6e8ec) 0 6px, transparent 6px 12px); }
}
.demo-err  { font-size: 14px; color: #c4392f; margin: 0; }
.demo-row  { display: flex; gap: 12px; margin: 0 0 10px; font-size: 14px; }
.demo-k    { font-weight: 600; min-width: 96px; flex: none; }
.demo-k-block { display: block; margin: 18px 0 8px; }
.demo-v    { opacity: .8; }
.demo-reply {
  white-space: pre-wrap; font-size: 14px; line-height: 1.55;
  background: #f7f8fa; border-radius: 12px; padding: 16px;
}
/* A disabled submit still has to look like the control it is. */
.demo-form button[disabled] { opacity: .45; cursor: default; }

/* the value calculator, as a dialog ----------------------------------------
   ⚠ NO SCRIPT ANYWHERE. This site ships script-src 'self' and loads nothing
   inline, so the dialog is a hidden checkbox, a <label> for a button, and a
   sibling selector. Same device as the sign-in panes and the panels' nav
   drawer. With CSS off the form is simply visible, which is the right thing
   to fail to.

   ⚠ A dialog rather than a band under the plans, because the calculator is an
   aside to the price list and not a rival to it. Full width, it read as one
   more thing to get through before deciding. */
.calc-toggle { position: absolute; opacity: 0; pointer-events: none; }

.calc-cta { text-align: center; margin-top: 28px; }
.calc-open-btn {
  display: inline-flex; align-items: center; gap: .55rem; cursor: pointer;
  padding: 13px 26px; border-radius: 999px;
  border: 1px solid var(--color-stroke-1, #e6e8ec); background: #fff;
  font-size: 15px; font-weight: 600; color: inherit;
  transition: border-color .18s ease, transform .18s ease, box-shadow .18s ease;
}
/* The little calculator glyph, drawn rather than fetched: no request, nothing
   that can 404, and it follows the text colour. */
.calc-open-btn::before {
  content: ""; width: 15px; height: 17px; flex: none;
  border: 1.6px solid currentColor; border-radius: 3px;
  box-shadow: inset 0 5px 0 -3.6px currentColor, inset 0 9px 0 -3.6px currentColor;
}
.calc-open-btn:hover {
  border-color: currentColor; transform: translateY(-1px);
  box-shadow: 0 8px 18px -10px rgba(16, 24, 40, .45);
}
.calc-open-btn:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
.calc-cta-note { font-size: 12.5px; opacity: .6; margin: 10px 0 0; }

.calc-modal { display: none; }
.calc-toggle:checked ~ .calc-modal { display: block; position: fixed; inset: 0; z-index: 80; }
.calc-backdrop { position: absolute; inset: 0; background: rgba(10, 12, 16, .55); cursor: pointer; }
.calc-panel {
  position: relative; z-index: 1;
  width: calc(100% - 32px); max-width: 640px;
  margin: 8vh auto 0; max-height: 84vh; overflow-y: auto;
  background: #fff; border-radius: 22px; padding: 30px 28px 26px;
  box-shadow: 0 30px 70px -24px rgba(0, 0, 0, .55);
}
.calc-h { font-size: 22px; font-weight: 650; margin: 0 0 20px; }
.calc-close {
  position: absolute; top: 14px; right: 16px; cursor: pointer;
  width: 32px; height: 32px; border-radius: 999px;
  display: grid; place-items: center; font-size: 22px; line-height: 1;
  opacity: .5; transition: opacity .15s ease, background .15s ease;
}
.calc-close:hover { opacity: 1; background: rgba(16, 24, 40, .07); }

.calc { display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-end; }
.calc-field { display: flex; flex-direction: column; gap: 6px; font-size: 13px; font-weight: 600; }
.calc-field input {
  width: 150px; padding: 11px 13px; font: inherit; font-size: 15px;
  border: 1px solid var(--color-stroke-1, #e6e8ec); border-radius: 12px; background: #fff;
}
.calc-out { margin-top: 22px; padding-top: 20px; border-top: 1px solid var(--color-stroke-1, #e6e8ec); }
.calc-line { font-size: 15px; line-height: 1.6; margin: 0 0 8px; }
.calc-big  { font-size: 19px; }
.calc-note { font-size: 12.5px; opacity: .65; margin: 12px 0 0; }

/* the offer, sitting with the answer rather than a scroll away */
.calc-go {
  display: flex; flex-wrap: wrap; align-items: center; gap: 12px;
  margin: 18px 0 0; padding-top: 18px;
  border-top: 1px solid var(--color-stroke-1, #e6e8ec);
}
.calc-go-note { font-size: 12.5px; opacity: .6; }

/* ── the calculator's chart ────────────────────────────────────────────────
   ⚠ Fixed heights, and that is correct rather than lazy: saving accumulates
   over equal working days, so week one is always a fifth of the month however
   many emails the visitor typed. The NUMBERS change, the shape does not. It
   also keeps this CSP-safe, since style-src 'self' discards an inline
   height and a class per percentage would leave unused rules behind. */
.calc-chart {
  display: flex; align-items: stretch; gap: 10px;
  height: 132px; margin: 20px 0 0; padding-top: 4px;
}
/* ⚠ The column must be a definite height or the bars' percentages resolve
   against `auto` and every bar collapses to its text. Hence height:100% here
   and flex-end, so the bars grow up from the baseline. */
.calc-col {
  flex: 1 1 0; height: 100%;
  display: flex; flex-direction: column; justify-content: flex-end;
  align-items: stretch; gap: 6px;
}
.calc-bar {
  flex: none;
  display: flex; align-items: flex-start; justify-content: center;
  padding-top: 6px; border-radius: 7px;
  background: color-mix(in srgb, var(--color-secondary) 8%, transparent);
}
.calc-bar b {
  font-size: 11px; font-weight: 600;
  color: color-mix(in srgb, var(--color-secondary) 55%, transparent);
}
.calc-w1 { height: 17%; }
.calc-w2 { height: 34%; }
.calc-w3 { height: 51%; }
.calc-w4 { height: 68%; }
/* The month total is the figure being argued, so it is the one that is filled. */
.calc-w5 {
  height: 85%;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--color-primary-500) 88%, #fff) 0%,
    var(--color-primary-500) 55%,
    color-mix(in srgb, var(--color-primary-500) 78%, #000) 100%);
  box-shadow: 0 4px 12px -4px color-mix(in srgb, var(--color-primary-500) 55%, transparent);
}
.calc-w5 b { color: #fff; }
/* While the period is being refetched: the old prices stay readable and only
   dim, so nothing jumps. */
.pricing-live[aria-busy="true"] { opacity: .5; transition: opacity .12s ease; }

.calc-x {
  text-align: center; font-size: 9.5px; font-weight: 500; letter-spacing: .05em;
  color: color-mix(in srgb, var(--color-secondary) 45%, transparent);
}
/* While the figures are being refetched. Never a spinner: the old answer stays
   readable and only dims, so the panel does not jump. */
.calc-out.is-busy { opacity: .45; transition: opacity .12s ease; }

@media (max-width: 560px) {
  .calc-panel { margin-top: 4vh; padding: 26px 20px 22px; max-height: 92vh; }
  .calc-field, .calc-field input { width: 100%; }
  .calc-field { flex: 1 1 100%; }
  .calc .btn { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .calc-open-btn { transition: none; }
  .calc-open-btn:hover { transform: none; }
}

/* ── founding price ───────────────────────────────────────────────────────
   The count is real, so it can be stated plainly. No pulsing, no colour that
   shouts: the argument is the number, not the styling around it. */
.found-badge { font-size: 12px; font-weight: 700; letter-spacing: .06em;
               text-transform: uppercase; margin: 14px 0 4px; }
.found-price { font-size: 28px; font-weight: 650; margin: 0; }
.found-was   { font-size: 17px; opacity: .5; text-decoration: line-through; margin-left: 8px; }
.found-left  { font-size: 13px; font-weight: 600; margin: 6px 0 0; }
.found-note  { font-size: 12.5px; opacity: .65; margin: 4px 0 0; }

/* ── the free-trial line above the plans, and the allowance inside each ──── */
/* ⚠ margin-block, NOT the margin shorthand. This element also carries
   .main-container for its max-width and its margin-inline: auto centering -
   `margin: -.5rem 0 1.75rem` is shorthand for all four sides, so its own
   `0` for left/right silently zeroed main-container's auto and pinned the
   whole 1290px box to the left edge of the viewport. Everything inside it
   was still centered WITHIN that box via justify-content, which is exactly
   why it read as "off-centre by a fixed amount" rather than obviously
   broken - the box was correct, just not where main-container put it. */
/* ⚠ Now just the centring wrapper - the visible pill is .hero-trial-badge,
   reused rather than kept as a second, separately-drawn "free trial" style.
   Same reasoning as the pipeline content sharing GATE_IMAGE across pages:
   one idea, stated the same way everywhere it appears. */
/* ⚠ NOT margin-top: -.5rem any more. That pulled a single line of small
   bare text up closer to the blurb above it, which was fine at that
   height; the pill it now wraps is taller (padding, the icon chip), and on
   a phone - where the blurb above already wraps to two lines and there is
   less headroom to begin with - the negative margin pulled the badge up
   far enough to sit on top of that second line instead of under it. */
.price-trial-note {
  display: flex; justify-content: center;
  margin-block: 1.25rem 1.75rem;
}
.plan-allow {
  font-size: .82rem; font-weight: 500;
  color: color-mix(in srgb, var(--color-secondary) 55%, transparent);
}
/* ── which models a plan actually reaches, by provider ────────────────────
   plan_provider_marks() in shell.php - the plan's own model allowlist,
   grouped under the provider that makes each one, with the same
   .powered-mark images the "Powered by" strip elsewhere on the site
   already uses. Real ids, in a compact monospace column per provider -
   this is the one place on the marketing site that is allowed to be a
   little technical, because it is answering a question only someone
   comparing plans on model choice would come here to ask. */
.plan-models {
  display: flex; flex-direction: column; gap: .6rem;
  margin: .2rem 0 0; padding: .75rem .9rem;
  border-radius: 14px; background: color-mix(in srgb, var(--color-secondary) 3%, #fff);
  border: 1px solid var(--color-stroke-1, #e6e8ec);
}
.plan-models-head {
  display: flex; align-items: center; gap: .4rem;
  font-size: .74rem; font-weight: 650; letter-spacing: .02em; text-transform: uppercase;
  color: color-mix(in srgb, var(--color-secondary) 55%, transparent);
}
.plan-models-head .powered-mark { width: 14px; height: 14px; }
.plan-models-group ul {
  display: flex; flex-direction: column; gap: .15rem;
  margin: .3rem 0 0; padding: 0; list-style: none;
}
/* ⚠ Not monospace any more - that was right for a raw id like
   "gpt-5.4-mini" and wrong for "GPT-5.4 Mini", which reads as a name, not
   as code. */
.plan-models-group li {
  font-size: .8rem;
  color: color-mix(in srgb, var(--color-secondary) 72%, transparent);
}

/* ⚠ THE UNRESTRICTED CASE, COMPACT. "Every model" used to mean the same
   card above listed fifteen-plus individual model names across three
   columns - technically accurate and, for the plan most people actually
   buy, a wall of small text nobody reading a pricing card needs. Three
   provider marks and one sentence say the same thing a shopper deciding
   whether to buy actually wants to know. The detailed per-provider list
   above is unchanged for a plan an operator has genuinely restricted,
   where which exact model is included is the point. */
.plan-models-compact {
  flex-direction: row; align-items: center; flex-wrap: wrap; gap: .5rem;
}
/* ⚠ CENTERED BY DEFAULT, ONLY BECAUSE THE OPEN CASE OVERRIDES IT BELOW.
   flex-start (the row's old default) aligns every item's TOP edge, which is
   fine once the list is open and tall but wrong while everything on the row
   is one line: the 28px chip circles and the one-line note text both start
   at the row's top, so the note's own vertical centre sits ~6px above the
   chips' - reported live as the text reading "not aligned with the logos".
   Centering fixes the closed row; :has() switches back to flex-start only
   when .plan-models-more is open, which is the case that needed it in the
   first place - see that rule's own comment for why centering breaks there. */
.plan-models-compact:has(.plan-models-more[open]) { align-items: flex-start; }
.plan-models-chip {
  display: grid; place-items: center; width: 28px; height: 28px;
  border-radius: 999px; background: #fff; border: 1px solid var(--color-stroke-1, #e6e8ec);
}
.plan-models-chip .powered-mark { width: 15px; height: 15px; }
.plan-models-note {
  font-size: .78rem; color: color-mix(in srgb, var(--color-secondary) 55%, transparent);
}
/* The compact note, made openable - see the note above plan_provider_marks()'
   <details> block in shell.php. summary keeps the note's own look but reads
   as pressable and grows a small chevron; the body reuses the same group/ul
   styling the fully-restricted case renders unconditionally above. */
/* ⚠ NOT width:100% while closed. That forced the note onto its own row
   under the chips - reported live as looking like two unrelated lines
   rather than one sentence with the marks it is talking about. A plain
   flex item sits right after the last chip in the same row instead,
   wrapping only if the card is genuinely too narrow for both.
   ⚠ OPEN IS DIFFERENT ON PURPOSE. align-items on the row above went from
   center to flex-start for this: centering was fine while every item was
   one line tall, but the instant this one opened and grew to the height of
   the whole model list, centering it pulled the marks down to the middle
   of that height instead of leaving them where they were - reported live
   as the marks looking like they had jumped. flex-basis: 100% below makes
   the open block wrap onto its own row the same way width:100% used to,
   which is also what puts the list flush left instead of trailing the
   marks. Nothing here runs while it is closed - a flex-basis on an item
   that already fits does nothing - so the collapsed row above is untouched. */
.plan-models-more { flex: 0 1 auto; }
.plan-models-more[open] { flex-basis: 100%; }
.plan-models-more > summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: .3rem;
}
.plan-models-more > summary::-webkit-details-marker { display: none; }
.plan-models-more > summary::after {
  content: ""; width: 6px; height: 6px; margin-top: -.15rem;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg); transition: transform .15s ease;
}
.plan-models-more[open] > summary::after { transform: rotate(-135deg); margin-top: .1rem; }
.plan-models-more-body {
  display: flex; flex-direction: column; gap: .6rem;
  width: 100%; margin-top: .6rem; padding-top: .6rem;
  border-top: 1px solid var(--color-stroke-1, #e6e8ec);
}

/* ── the feature list ─────────────────────────────────────────────────────
   ⚠ A tinted badge with a glyph that matches the WORDS, not a bare check
   repeated twelve times down the page. plan_feature_icon() in shell.php
   picks the path from the feature text itself - see its own comment for
   why that is keyword matching on operator-typed copy rather than a picker
   field per bullet. Unmatched text still gets a check, so nothing here can
   render with a missing icon. */
.plan-feats { list-style: none; margin: 0; padding: 0; }
.plan-feat-ico {
  flex: none; margin-top: .05rem;
  display: flex; align-items: center; justify-content: center;
  width: 1.5rem; height: 1.5rem; border-radius: 8px;
  background: color-mix(in srgb, var(--color-primary-500) 12%, #fff);
  color: var(--color-primary-500);
}
.plan-feat-ico svg { width: .85rem; height: .85rem; }
/* The highlighted card's badges pick up a touch more contrast so they do
   not wash out against its own slightly darker border. */
.plan-card.border-secondary .plan-feat-ico {
  background: color-mix(in srgb, var(--color-primary-500) 16%, #fff);
}

/* ── billing period toggle ─────────────────────────────────────────────────
   The pricing page's monthly / yearly switch. A pair of links, so it works
   with script blocked; the "on" one is filled in. */
.period-toggle { display: flex; justify-content: center; gap: 6px; margin: 0 auto 28px; }
.period-toggle a {
  padding: 9px 18px; border-radius: 999px; text-decoration: none; color: inherit;
  border: 1px solid var(--color-stroke-1, #e6e8ec); font-size: 14px; font-weight: 600;
}
.period-toggle a.on { background: #14161a; color: #fff; border-color: #14161a; }
.period-save { font-weight: 500; opacity: .7; margin-left: 6px; }
.period-note {
  max-width: 620px; margin: -14px auto 28px; text-align: center;
  font-size: 14px; font-weight: 600; line-height: 1.5;
}
