/* produpoint.com — one stylesheet, no external requests.
 *
 * NOTHING HERE LOADS FROM A CDN, A FONT HOST OR AN ANALYTICS VENDOR, and that
 * is load-bearing rather than tidy: privacy.html tells a visitor that this site
 * reports nothing about them to a third party, and a single <link> to a font
 * host would make that sentence false. `scripts/site-check.sh` fails the build
 * if one appears. System fonts only, for the same reason.
 *
 * ALL MOTION IS CSS. No script runs on this site — the same gate forbids a
 * <script> tag outright — so every animation below is keyframes, transitions or
 * a view() timeline. Two rules that are not optional:
 *   1. Scroll reveals are opted INTO behind @supports. A browser without
 *      animation-timeline shows the content plainly; it never inherits the
 *      "start at opacity 0" half of a reveal it cannot finish.
 *   2. prefers-reduced-motion: reduce turns all of it off.
 *
 * COLOURS ARE THE REAL BRAND, read out of produpoint-social/assets/
 * banner-company-page.html rather than picked: orange #F96A16, navy #16213B,
 * cream #FBF4E9, with lavender / mint / blue as the accent trio.
 *
 * LAYOUT: A WIDE SHELL, A NARROW READING COLUMN. These are two different
 * measurements and the first version conflated them — one 50rem column for the
 * whole page, which read as a document rather than a product site and left the
 * screen empty on both sides. Bands now run the full width of the window and
 * the CONTENT inside them is what is constrained: `.shell` (76rem) for layout,
 * `.prose` (42rem) only where someone is actually reading sentences.
 *
 * SECTIONS ARE SEPARATED BY GROUND, NOT BY A LINE. The hairline rules that used
 * to sit between sections were the thinnest possible way to say "something
 * changed here" and looked like a rendering artefact. Alternating background
 * bands do the same job at the scale the eye actually reads.
 */

:root {
  /* brand */
  --orange: #F96A16;
  --orange-deep: #E25505;
  --navy: #16213B;
  --cream: #FBF4E9;
  --lavender: #B9A9F0;
  --mint: #A8DFC6;
  --blue: #2C6BD6;

  /* roles — light */
  --bg: var(--cream);
  --bg-soft: #F4E9D8;
  --bg-raised: #FFFCF7;
  --border: #E6D9C4;
  --border-strong: #D8C6AA;
  --text: var(--navy);
  --text-soft: #5C687F;
  --text-faint: #8B93A6;
  --accent: var(--orange);
  --accent-ink: #fff;
  --accent-wash: rgba(249, 106, 22, .10);
  --code-bg: #FFF7EC;
  /* THE ONE EMPHASIS COLOUR. There is deliberately no second accent: a blue
   * variant for the limiting clauses was built and then removed at the owner's
   * request — two accent colours in running text is a legend the reader has to
   * learn, and a legal page is not where anyone wants to learn one.
   * ORANGE-DEEP here, not orange, not orange, and the reason is
   * measured: the brand orange as TEXT on the cream ground is 2.71:1, which is
   * not readable body text by any standard. Orange-deep is 3.47:1, which clears
   * the large-text bar the marked phrases are sized for. In dark the real brand
   * orange is used — see the dark block. */
  --mk-ink: var(--orange-deep);
  --shadow-sm: 0 1px 2px rgba(22, 33, 59, .06);
  --shadow: 0 2px 4px rgba(22, 33, 59, .05), 0 12px 32px rgba(22, 33, 59, .07);
  --shadow-lift: 0 4px 8px rgba(22, 33, 59, .06), 0 24px 56px rgba(22, 33, 59, .14);
  --glow: radial-gradient(ellipse at center, rgba(249, 106, 22, .20), transparent 68%);

  --shell: 76rem;
  --prose: 42rem;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0D1526;
    --bg-soft: #111B2E;
    --bg-raised: #16213A;
    --border: #24314F;
    --border-strong: #33456B;
    --text: #F2ECE2;
    --text-soft: #9AA7C0;
    --text-faint: #67748E;
    /* THE BRAND ORANGE ITSELF, not a lightened version of it. This used to be
     * #FF8038 — a tint invented to sit better on the dark ground. Measured
     * rather than assumed, the real brand orange does that job perfectly well:
     * 6.16:1 as link and heading text on this background, and 6.41:1 as a button
     * ground under --accent-ink, both comfortably past WCAG AA. The tint bought
     * about one point of contrast and cost the site its actual brand colour in
     * half of all sessions. Owner instruction, 2026-09-07: only brand colours.
     * Asserted by site-check.sh §13. */
    --accent: var(--orange);
    --accent-ink: #1A0E04;
    --accent-wash: rgba(255, 128, 56, .13);
    --code-bg: #0A1120;
    /* --mk-ink changes here, and only here: on this dark ground the real brand
     * orange is 6.16:1 as text and needs no deepening, so the emphasis is the
     * actual brand colour. It is orange-deep in light only because orange itself
     * is 2.71:1 there. Same intent, measured per ground. */
    --mk-ink: var(--orange);
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);
    --shadow: 0 2px 4px rgba(0, 0, 0, .3), 0 12px 32px rgba(0, 0, 0, .35);
    --shadow-lift: 0 4px 8px rgba(0, 0, 0, .35), 0 24px 56px rgba(0, 0, 0, .5);
    --glow: radial-gradient(ellipse at center, rgba(255, 128, 56, .18), transparent 68%);
  }
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 17px/1.65 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

/* ===================== layout ========================================= */

.shell {
  width: 100%;
  max-width: var(--shell);
  margin: 0 auto;
  padding-inline: clamp(1.25rem, 4vw, 3rem);
}

.prose { max-width: var(--prose); }

/* A full-width band. Sections are told apart by their GROUND, not a hairline. */
.band { padding-block: clamp(3.5rem, 7vw, 6rem); position: relative; }
.band--soft { background: var(--bg-soft); }
.band--raised { background: var(--bg-raised); }

/* ===================== motion primitives ============================== */

@keyframes rise {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}
@keyframes riseIn {
  from { opacity: 0; transform: translateY(26px) scale(.99); }
  to   { opacity: 1; transform: none; }
}
@keyframes driftGlow {
  0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: .9; }
  50%      { transform: translate(-46%, -55%) scale(1.16); opacity: 1; }
}
@keyframes nudge {
  0%, 100% { transform: translateX(0); }
  50%      { transform: translateX(3px); }
}
@keyframes drawLine { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes popIn {
  0%   { opacity: 0; transform: translateY(8px); }
  100% { opacity: 1; transform: none; }
}
@keyframes caret { 0%, 45% { opacity: 1; } 55%, 100% { opacity: 0; } }
@keyframes sweep { to { background-position: 200% 0; } }

.enter { animation: rise .7s cubic-bezier(.2, .7, .3, 1) both; }
.enter-1 { animation-delay: .05s; }
.enter-2 { animation-delay: .14s; }
.enter-3 { animation-delay: .23s; }
.enter-4 { animation-delay: .32s; }
.enter-5 { animation-delay: .42s; }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* Range ends at `entry 80%` — BEFORE the element is fully in view — so a
     * reveal is never caught half-faded at rest, and finishes while the element
     * is still comfortably on screen rather than at the last possible instant.
     * The end point is the whole safety property here: a view() timeline is
     * driven by scroll DISTANCE, and an element near the bottom of a short
     * document can run out of scrollable distance before its range completes.
     * With `both` fill it then sits at a partial keyframe permanently — opacity
     * .4, for ever, with no event that ever rescues it. Ending early buys margin
     * against exactly that. Never end a reveal at `cover` or `exit`. */
    .reveal {
      animation: riseIn linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 80%;
    }

    /* STAGGER. Siblings in a grid used to share one range, so a row of three
     * arrived as a single block — which reads as a page jumping rather than
     * settling. Each sibling starts a little later. The ends stay inside the
     * `entry 80%` budget above; none of them reach for `cover`. */
    .cards > .reveal:nth-child(2),
    .flow > .reveal:nth-child(2),
    .compare > .reveal:nth-child(2) { animation-range: entry 8% entry 88%; }
    .cards > .reveal:nth-child(3),
    .flow > .reveal:nth-child(3) { animation-range: entry 15% entry 95%; }
    .flow > .reveal:nth-child(4) { animation-range: entry 20% entry 100%; }
    .flow > .reveal:nth-child(5) { animation-range: entry 25% entry 100%; }
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    /* NEGATIVE DELAY, so the animation starts already finished. Without this
     * line the block above only made the animations instant — it left the
     * DELAYS intact, so a reduced-motion visitor sat looking at a blank hero
     * for .42s (`.enter-5`) before the content snapped in. Zeroing the delay
     * would be enough; a negative value is belt and braces. */
    animation-delay: -1ms !important;
  }
}

/* THIS BLOCK DOES NOT STOP SCROLL-DRIVEN ANIMATION, AND THAT IS NOT A BUG HERE
 * — IT IS THE REASON THE GUARD BELOW EXISTS. For an animation whose progress
 * comes from a timeline, `animation-duration` is ignored: the scroll position
 * supplies the progress, not the clock. So every rule above is a no-op against
 * anything driven by view() or scroll().
 * The ONLY thing protecting a reduced-motion visitor from scroll motion is the
 * `@media (prefers-reduced-motion: no-preference)` wrapper nested inside each
 * `@supports` block. That wrapper is load-bearing on EVERY scroll effect in
 * this file. Do not "tidy" one away on the grounds that the reset above
 * already handles motion. It does not. */

/* ===================== the mark ======================================= */

.mark { width: 1.75em; height: 1.75em; display: block; color: var(--accent); flex: none; }
.brand:hover .mark .tri { animation: nudge .6s ease-in-out; }

/* ===================== the non-production banner ======================= */
/* SHIPPED TO BOTH ENVIRONMENTS AND RENDERED IN NEITHER ON ITS OWN. Nothing in
 * distribution/site/ carries the markup — `scripts/build-dev-site.sh` injects it
 * into the DEV copy only, and `scripts/site-check.sh` refuses a production tree
 * that contains it. The rule lives here rather than in the injected element so
 * the markup needs no inline `style` attribute: _headers permits inline styles
 * today, but its own comment says that exemption is to be retired, and a safety
 * control should not be the thing keeping it alive.
 *
 * WHY IT EXISTS: environments-and-targets.md — "Every non-production build MUST
 * show a persistent, unmissable indicator of which environment it is. Production
 * shows nothing." Until this shipped, the public dev copy of this site was
 * byte-identical to produpoint.com.
 *
 * WHY IT IS NOT A BRAND COLOUR, stated because the owner's standing instruction
 * (2026-09-07) is "use only the branding colours" and this is the one deliberate
 * exception. The same doctrine that requires the banner exempts it: it "treats
 * the banner itself as exempt from 'off-brand' objections — it is a safety
 * control, not decoration." A banner painted in the brand reads as part of the
 * design, which is precisely how someone ends up believing a dev copy is the
 * real site. It never appears on produpoint.com. One token, so changing it is
 * one line.
 *
 * IT DOES NOT ADAPT TO THE THEME. Same colour in light and dark, for the same
 * reason the mark is always orange: a safety marker that changes appearance is
 * a safety marker someone has to learn. */
:root { --env-banner-bg: #B00020; --env-banner-ink: #FFFFFF; --env-banner-h: 2.1rem; }

.env-banner {
  position: sticky;
  top: 0;
  /* Above the header (30), which sticks below it — see the :has() rule. */
  z-index: 40;
  height: var(--env-banner-h);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--env-banner-bg);
  color: var(--env-banner-ink);
  font: 700 .75rem/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  letter-spacing: .09em;
  text-transform: uppercase;
  padding: 0 1rem;
  text-align: center;
}

/* THE HEADER STICKS BELOW THE BANNER, and only where the banner exists. Scoped
 * through :has() so production — which never carries the element — is not
 * touched by a single declaration. If a browser lacks :has(), the header sticks
 * at the top and covers the banner once the page is scrolled; the banner is
 * still the first thing on the page, so the degraded case loses persistence,
 * never the marker itself. */
body:has(.env-banner) header.site { top: var(--env-banner-h); }

/* ===================== header ========================================= */

header.site {
  position: sticky;
  top: 0;
  z-index: 30;
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  /* BASE IS THE VISIBLE BORDER — the complete, never-broken state. The header
   * used to start `transparent` here and gain its edge only from the scroll
   * animation below, which meant the edge was invisible to anyone the animation
   * never reached. Caught 2026-09-07 by site-check.sh §11 on the real tree: this
   * rule had the `@supports` guard and had never had the reduced-motion one, in
   * a file whose own header states both are required. */
  border-bottom: 1px solid var(--border);
  transition: border-color .3s ease;
}

@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    header.site {
      /* Only inside BOTH guards is it safe to start from the hidden state. */
      border-bottom-color: transparent;
      animation: headerEdge linear both;
      animation-timeline: scroll(root);
      animation-range: 0 90px;
    }
  }
  @keyframes headerEdge { to { border-bottom-color: var(--border); } }
}

header.site .shell {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-height: 4.25rem;
  flex-wrap: wrap;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  font-size: 1.22rem;
  font-weight: 700;
  letter-spacing: -.032em;
  color: var(--text);
  text-decoration: none;
}
.brand .word { line-height: 1; }
.brand .word b { font-weight: 700; color: var(--text); }
.brand .word i { font-style: normal; font-weight: 700; color: var(--accent); }

nav.site { display: flex; flex-wrap: wrap; align-items: center; gap: .1rem 1.5rem; }

nav.site a {
  position: relative;
  color: var(--text-soft);
  text-decoration: none;
  font-size: .9375rem;
  font-weight: 500;
  padding: .2rem 0;
  transition: color .2s ease;
}
nav.site a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px; border-radius: 2px;
  background: var(--accent);
  transform: scaleX(0); transform-origin: left;
  transition: transform .25s cubic-bezier(.2, .7, .3, 1);
}
nav.site a:hover { color: var(--text); }
nav.site a:hover::after { transform: scaleX(1); }

nav.site a.nav-cta {
  background: var(--accent);
  color: var(--accent-ink);
  padding: .45rem .9rem;
  border-radius: .6rem;
  font-weight: 620;
}
nav.site a.nav-cta::after { display: none; }
nav.site a.nav-cta:hover { color: var(--accent-ink); filter: brightness(1.07); }

/* ===================== hero =========================================== */

.hero { padding-block: clamp(3.5rem, 8vw, 6.5rem) clamp(3rem, 6vw, 5rem); isolation: isolate; }

.hero::before {
  content: "";
  position: absolute;
  left: 30%; top: 40%;
  width: min(60rem, 130vw);
  height: 34rem;
  background: var(--glow);
  transform: translate(-50%, -50%);
  z-index: -1;
  pointer-events: none;
  animation: driftGlow 15s ease-in-out infinite;
}

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.02fr) minmax(0, .98fr);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
}

@media (max-width: 60rem) { .hero-grid { grid-template-columns: 1fr; } }

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  background: var(--accent-wash);
  color: var(--accent);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
  border-radius: 999px;
  padding: .32rem .85rem .32rem .6rem;
  font-size: .76rem;
  font-weight: 660;
  letter-spacing: .05em;
  text-transform: uppercase;
  margin: 0 0 1.3rem;
}
.eyebrow svg { width: .85rem; height: .85rem; flex: none; }

h1 {
  font-size: clamp(2.3rem, 5.4vw, 4rem);
  line-height: 1.05;
  letter-spacing: -.038em;
  margin: 0 0 1.1rem;
  font-weight: 770;
}
h1 .hl { color: var(--accent); }

.hero .lede {
  font-size: clamp(1.06rem, 1.5vw, 1.24rem);
  line-height: 1.55;
  color: var(--text-soft);
  margin: 0 0 2rem;
  max-width: 34rem;
}

.cta-row { display: flex; flex-wrap: wrap; gap: .8rem; align-items: center; }

.btn {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .85rem 1.4rem;
  border-radius: .8rem;
  font-weight: 640;
  font-size: .97rem;
  text-decoration: none;
  border: 1px solid transparent;
  transition: transform .18s cubic-bezier(.2,.7,.3,1), box-shadow .25s ease,
              background-color .2s ease, border-color .2s ease, color .2s ease;
}
.btn svg { width: 1rem; height: 1rem; flex: none; transition: transform .25s ease; }

.btn-primary {
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: 0 8px 22px color-mix(in srgb, var(--accent) 34%, transparent);
}
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 12px 30px color-mix(in srgb, var(--accent) 44%, transparent); }
.btn-primary:hover svg { transform: translateX(3px); }
.btn-primary:active { transform: translateY(0); }

.btn-ghost { border-color: var(--border-strong); color: var(--text); background: var(--bg-raised); }
.btn-ghost:hover { transform: translateY(-2px); border-color: var(--accent); color: var(--accent); box-shadow: var(--shadow); }

.facts {
  display: flex; flex-wrap: wrap;
  gap: .6rem 1.7rem;
  margin: 2.2rem 0 0; padding: 0;
  list-style: none;
  color: var(--text-soft);
  font-size: .9rem;
}
.facts li { display: flex; align-items: center; gap: .45rem; }
.facts svg { width: 1rem; height: 1rem; color: var(--accent); flex: none; }

/* ---------- the session window in the hero ---------- */

.window {
  border-radius: 1.1rem;
  border: 1px solid var(--border-strong);
  background: var(--bg-raised);
  box-shadow: var(--shadow-lift);
  overflow: hidden;
  font-size: .9rem;
}

.window-bar {
  display: flex;
  align-items: center;
  gap: .45rem;
  padding: .75rem 1rem;
  border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg-soft) 70%, var(--bg-raised));
}
.window-bar i { width: .62rem; height: .62rem; border-radius: 50%; background: var(--border-strong); display: block; }
.window-bar i:first-child { background: var(--accent); }
.window-bar span { margin-left: .4rem; color: var(--text-faint); font-size: .78rem; letter-spacing: .02em; }

.window-body { padding: 1.1rem 1.15rem 1.3rem; display: grid; gap: .72rem; }

.line { display: flex; gap: .6rem; align-items: flex-start; line-height: 1.5; }
.line .who { color: var(--text-faint); font-size: .78rem; font-weight: 640; letter-spacing: .04em; text-transform: uppercase; padding-top: .16rem; flex: none; width: 3.1rem; }
.line .said { color: var(--text); }

.line .caret {
  display: inline-block;
  width: .55ch; height: 1.05em;
  background: var(--accent);
  vertical-align: -.16em;
  margin-left: .12em;
  animation: caret 1.1s steps(1) infinite;
}

.step-out {
  display: flex; align-items: center; gap: .55rem;
  color: var(--text-soft);
  animation: popIn .5s cubic-bezier(.2,.7,.3,1) both;
}
.step-out svg { width: 1rem; height: 1rem; color: var(--mint); flex: none; }
.step-out b { color: var(--text); font-weight: 620; }
.step-out:nth-of-type(1) { animation-delay: .9s; }
.step-out:nth-of-type(2) { animation-delay: 1.25s; }
.step-out:nth-of-type(3) { animation-delay: 1.6s; }
.step-out:nth-of-type(4) { animation-delay: 1.95s; }

.window-foot {
  border-top: 1px solid var(--border);
  padding: .7rem 1.15rem;
  display: flex; align-items: center; gap: .5rem;
  font-size: .8rem; color: var(--text-faint);
  background: var(--code-bg);
}
.window-foot .dot { width: .5rem; height: .5rem; border-radius: 50%; background: var(--mint); flex: none; }

/* ===================== section heads ================================== */

.section-head { margin: 0 0 clamp(1.8rem, 3vw, 2.8rem); }

.kicker {
  display: block;
  color: var(--accent);
  font-size: .78rem;
  font-weight: 680;
  letter-spacing: .1em;
  text-transform: uppercase;
  margin: 0 0 .6rem;
}

h2 {
  font-size: clamp(1.7rem, 3.2vw, 2.5rem);
  letter-spacing: -.032em;
  margin: 0 0 .7rem;
  font-weight: 740;
  line-height: 1.1;
}

.section-head p { color: var(--text-soft); margin: 0; max-width: var(--prose); font-size: 1.05rem; }

h3 { font-size: 1.08rem; margin: 0 0 .4rem; font-weight: 660; letter-spacing: -.012em; }

p { margin: 0 0 1rem; }
a { color: var(--accent); }
ul, ol { margin: 0 0 1rem; padding-left: 1.25rem; }
li { margin: .35rem 0; }
.muted { color: var(--text-soft); }
.small { font-size: .9rem; }

/* ===================== cards ========================================== */

.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: 1.1rem; }

.card {
  position: relative;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 1.1rem;
  padding: 1.5rem;
  box-shadow: var(--shadow-sm);
  transition: transform .25s cubic-bezier(.2,.7,.3,1), box-shadow .3s ease, border-color .3s ease;
  overflow: hidden;
}
.card::after {
  content: "";
  position: absolute; inset: 0 0 auto 0; height: 3px;
  background: var(--tone, var(--accent));
  transform: scaleX(0); transform-origin: left;
  transition: transform .35s cubic-bezier(.2,.7,.3,1);
}
.card:hover { transform: translateY(-5px); box-shadow: var(--shadow-lift); border-color: var(--border-strong); }
.card:hover::after { transform: scaleX(1); }

.icon {
  width: 2.8rem; height: 2.8rem;
  border-radius: .85rem;
  display: grid; place-items: center;
  margin: 0 0 1rem;
  background: color-mix(in srgb, var(--tone, var(--accent)) 16%, transparent);
  color: var(--tone, var(--accent));
  transition: transform .3s cubic-bezier(.2,.7,.3,1);
}
.icon svg { width: 1.4rem; height: 1.4rem; }
.card:hover .icon { transform: scale(1.08) rotate(-5deg); }

.card p { margin: 0; font-size: .96rem; color: var(--text-soft); }

/* ===================== how it works =================================== */

.flow { display: grid; grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); gap: 1.1rem; }

.stage {
  position: relative;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 1.2rem;
  padding: 1.6rem 1.5rem 1.4rem;
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: transform .28s cubic-bezier(.2,.7,.3,1), box-shadow .3s ease, border-color .3s ease;
}
.stage:hover { transform: translateY(-5px); box-shadow: var(--shadow-lift); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }

/* the oversized ghost numeral */
.stage .n {
  position: absolute;
  right: .55rem; top: -.9rem;
  font-size: 6.2rem;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -.06em;
  color: var(--accent);
  opacity: .11;
  pointer-events: none;
  transition: opacity .3s ease, transform .3s ease;
}
.stage:hover .n { opacity: .2; transform: translateY(2px); }

.stage .icon { margin-bottom: 1.1rem; }
.stage h3 { font-size: 1.15rem; margin-bottom: .5rem; }
.stage p { color: var(--text-soft); font-size: .96rem; margin: 0 0 1.1rem; }

/* the concrete thing each stage hands you */
.yield {
  display: inline-flex; align-items: center; gap: .45rem;
  font-size: .8rem; font-weight: 600;
  color: var(--text-soft);
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: .5rem;
  padding: .32rem .6rem;
}
.yield svg { width: .85rem; height: .85rem; color: var(--accent); flex: none; }

/* the progress rail under the whole flow */
.rail { height: 3px; border-radius: 3px; margin-top: 2rem; overflow: hidden; background: var(--border); }
.rail i {
  display: block; height: 100%;
  background: linear-gradient(90deg, var(--accent), var(--lavender), var(--mint));
  transform-origin: left;
  transform: scaleX(1);
}
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* Was `entry 30% cover 60%`. `cover` is far down the timeline, and this rail
     * sits at the END of its section — so on a tall viewport it could run out of
     * scroll before reaching 60% and stay a permanently half-drawn line. Pulled
     * back inside the entry phase, same rule as `.reveal` above. */
    .rail i { animation: drawLine linear both; animation-timeline: view(); animation-range: entry 25% entry 90%; }
  }
}

/* ===================== the comparison ================================= */

.compare { display: grid; grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr)); gap: 1.1rem; }

.col { border: 1px solid var(--border); border-radius: 1.2rem; padding: 1.6rem; background: var(--bg-raised); }
.col.win { border-color: color-mix(in srgb, var(--accent) 42%, transparent); background: linear-gradient(180deg, var(--accent-wash), transparent 55%), var(--bg-raised); }

.col h3 { display: flex; align-items: center; gap: .55rem; margin-bottom: 1rem; font-size: 1.1rem; }
.col h3 svg { width: 1.15rem; height: 1.15rem; flex: none; }
.col.lose h3 { color: var(--text-soft); }
.col.win h3 { color: var(--accent); }

.col ul { list-style: none; padding: 0; margin: 0; }
.col li { display: flex; gap: .6rem; align-items: flex-start; font-size: .96rem; margin: .65rem 0; }
.col li svg { width: 1.05rem; height: 1.05rem; margin-top: .3rem; flex: none; }
.col.lose li { color: var(--text-soft); }
.col.lose li svg { color: var(--text-faint); }
.col.win li svg { color: var(--accent); }

/* ===================== callout / panel ================================ */

.note {
  position: relative;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 1rem;
  padding: 1.3rem 1.4rem;
  margin: 1.5rem 0;
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.note::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--accent); }
.note p:last-child { margin-bottom: 0; }

.panel {
  border: 1px solid var(--border-strong);
  border-radius: 1.4rem;
  padding: clamp(1.6rem, 4vw, 3rem);
  background:
    radial-gradient(120% 120% at 0% 0%, var(--accent-wash), transparent 58%),
    var(--bg-raised);
  box-shadow: var(--shadow);
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: center;
}
@media (max-width: 56rem) { .panel { grid-template-columns: 1fr; } }

.panel h2 { margin-bottom: .6rem; }

.keycard {
  border: 1px dashed color-mix(in srgb, var(--accent) 50%, transparent);
  border-radius: 1rem;
  padding: 1.2rem;
  background: var(--code-bg);
  font-size: .85rem;
}
.keycard .lbl { color: var(--text-faint); font-size: .72rem; letter-spacing: .08em; text-transform: uppercase; margin: 0 0 .5rem; }
.keycard .key {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--accent);
  font-size: .92rem;
  letter-spacing: .01em;
  word-break: break-all;
  margin: 0 0 .9rem;
  position: relative;
  background: linear-gradient(90deg, var(--accent) 0%, var(--accent) 40%, color-mix(in srgb, var(--accent) 45%, var(--text-faint)) 50%, var(--accent) 60%, var(--accent) 100%);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: sweep 4.5s linear infinite;
}
.keycard p:last-child { margin: 0; color: var(--text-soft); font-size: .82rem; }

/* The access panel's fallback for a visitor whose mail app is not set up. The
 * CSP forbids scripts, so there is no copy button: `user-select: all` makes one
 * click select the whole address on desktop. iOS Safari does not reliably honour
 * it, which is why the address also sits in a clearly bounded box for a
 * long-press. */
.access-alt { margin: 1.2rem 0 0; }
.addr {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--text);
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: .4rem;
  padding: .05em .45em;
  /* Not `word-break: break-all` — that split the address as "yu / tomshazion…"
   * mid-line on desktop (seen in a screenshot). This wraps the whole address to
   * the next line, and breaks it only if it is wider than the line itself. */
  overflow-wrap: anywhere;
  -webkit-user-select: all;
  user-select: all;
}
.next-steps { margin: 1rem 0 0; padding-left: 1.3rem; color: var(--text-soft); }
.next-steps li { margin: .3rem 0; }
.next-steps strong { color: var(--text); }

/* ===================== reading progress =============================== */

/* A 3px bar across the very top showing how far through the document you are.
 * It earns its place on the LEGAL pages above all: the reason people bounce off
 * a privacy policy is not that it is boring, it is that they cannot tell how
 * long it is. This is informational motion, not decoration.
 *
 * BASE STATE IS AN EMPTY BAR, deliberately. Everywhere else in this file the
 * base state must be the FINISHED state, because the base is what a browser
 * without scroll timelines keeps for ever — and content stuck at opacity 0 is
 * the failure that rule exists to prevent. A progress bar is the one exception
 * that is safe, because an unfilled 3px transparent strip carries no content and
 * is invisible. A FULL bar would be the wrong fallback: it would assert
 * "you are at the end" to every Firefox visitor. */

.progress {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 3px;
  z-index: 60;
  background: transparent;
  pointer-events: none;
}
.progress i {
  display: block;
  height: 100%;
  transform: scaleX(0);
  transform-origin: left;
  background: linear-gradient(90deg, var(--accent), var(--lavender), var(--mint));
}
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .progress i {
      animation: drawLine linear both;
      animation-timeline: scroll(root);
    }
  }
}

/* ===================== the marker ===================================== */

/* THE GLOWING HIGHLIGHTER — the owner asked for it by name, on the legal pages.
 *
 * CLASS IS `.mk`, NOT `.hl`. `.hl` is already taken by the hero headline
 * (index.html's <span class="hl">real, launched</span>, styled at `h1 .hl`), and
 * a bare `.hl { background: … }` here would have painted a gradient behind the
 * front page's biggest words.
 *
 * WHAT IS MARKED, AND WHY IT IS NOT ONLY THE FLATTERING PARTS. The obvious way
 * to use a highlighter on your own terms is to light up the promises — "your
 * project is yours" — and leave "as is, without warranty" plain. That is the one
 * thing this must not do: it would make the clauses that LIMIT the reader's
 * rights the least conspicuous text on the page, and conspicuousness is exactly
 * the doctrine those clauses are judged by. So the marker is applied to both
 * kinds, in the SAME colour. This is not lawyerly caution for its own sake — a
 * privacy page's whole job is to be believed, and a document that emphasises
 * only its own good news reads as marketing.
 *
 * BUDGET: four to six marks per page. Emphasising ten sentences emphasises
 * nothing.
 *
 * BASE STATE IS FULLY PAINTED. Firefox has no scroll timelines at all today, and
 * a printer's view timelines are inactive by spec — both keep the base style, so
 * the base has to be the finished highlight, with the paint-in added on top only
 * where it can actually complete. */

/* NO BACKGROUND WASH. The owner looked at the first version and asked the
 * obvious question: why is this a highlighted block rather than simply nice
 * orange text? The honest answer is that plain brand-orange text does not work
 * on the cream ground — measured, #F96A16 on #FBF4E9 is 2.71:1, well under any
 * readability bar — and the wash was how that was worked around. It was a
 * workaround presented as a design.
 *
 * The emphasis is now the text itself, which is what he asked for, and the
 * contrast problem is solved by SIZE instead of by a background: the marked
 * phrases are set at 1.1em and 680 weight, which is 18.7px bold at this body
 * size. That is over the 18.66px threshold at which WCAG treats text as large
 * scale, where the required ratio is 3:1 rather than 4.5:1 — and both colours
 * clear it on both grounds (orange-deep 3.47:1 light, orange 6.16:1 dark, blue
 * 4.60:1 light and 3.63:1 dark). Asserted by site-check.sh §12, which was
 * rewritten alongside this to measure text-on-page rather than text-on-wash.
 *
 * The <mark> element is kept rather than swapped for a <span>: it is the correct
 * element for "relevant in the current context", and screen-reader support for
 * announcing it has improved. But nothing here DEPENDS on it being announced —
 * every marked sentence still reads as important with all styling removed, which
 * is the rule for any emphasis that carries meaning. */

.mk {
  /* The UA stylesheet gives <mark> black text on yellow. Both are reclaimed. */
  background: none;
  color: var(--mk-ink);
  font-size: 1.1em;
  /* 700, not the 660/680 semibold used elsewhere in this file. WCAG's large-text
   * threshold is defined for text that is BOLD, and 700 is what that means
   * without argument. The 3:1 ratio these colours rely on is only available at
   * that weight and size — site-check.sh §12 re-derives both from this rule and
   * falls back to demanding 4.5:1 if either is weakened. */
  font-weight: 700;
  letter-spacing: -.004em;
}

/* ONE COLOUR, AND BOTH KINDS OF CLAUSE STILL MARKED. There was briefly a second
 * class in brand blue for the clauses that cost the reader something. The owner
 * looked at it and wanted the orange alone, which is the right call: two accent
 * colours in running text is a legend the reader has to learn, and a legal page
 * is not where anyone wants to learn one.
 * What does NOT change with it: the limiting clauses — "can be revoked at any
 * time", "as is, without warranty" — are still marked, in the same orange as the
 * reassuring ones. Dropping the second colour was a design decision; dropping
 * the emphasis on the parts that have teeth would have been a different and much
 * worse one, and would have left the vendor-favourable text as the least
 * conspicuous on the page. */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* The `animation` shorthand must come BEFORE `animation-timeline`: the
     * shorthand resets the timeline to `auto`, so the other order silently
     * turns the whole effect back into a time-based animation. */
    .mk {
      animation: inkIn linear both;
      animation-timeline: view();
      animation-range: entry 55% contain 15%;
    }
  }
}
/* The colour arrives as the line scrolls into reading position, rather than
 * being there before you get to it. Base — and therefore Firefox, reduced
 * motion, and print — is the finished coloured state; only the journey to it is
 * the enhancement. `--mk-ink` is set per class, so this one keyframe serves both
 * the orange and the blue without knowing which is which. */
@keyframes inkIn { from { color: var(--text); } to { color: var(--mk-ink); } }

/* ===================== legal pages ==================================== */

.legal { padding-block: 3rem 1rem; max-width: 46rem; }
.legal h1 { font-size: clamp(1.9rem, 5vw, 2.5rem); margin-bottom: .4rem; }
.legal .updated { color: var(--text-soft); font-size: .92rem; margin: 0 0 2rem; }

/* Longer measure, looser leading. The single largest readability win on these
 * pages, and it involves no motion at all. */
.legal p, .legal li { line-height: 1.75; }
.legal p { margin: 0 0 1.15rem; }

/* Section headings get a rule above and an accent tick, so the eye can find the
 * boundaries when skimming for one fact — which is how legal pages are actually
 * read. `scroll-margin-top` keeps an anchored heading clear of the sticky bar. */
.legal h2 {
  font-size: 1.22rem;
  margin-top: 3rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--border);
  scroll-margin-top: 5.5rem;
  position: relative;
}
.legal h2::before {
  content: "";
  position: absolute;
  top: -1px; left: 0;
  width: 2.2rem; height: 2px;
  background: var(--accent);
}

.legal code {
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: .35rem;
  padding: .08em .35em;
  font-size: .9em;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* ---------- blocks you are meant to copy ---------- */

/* install.html is the first page here that carries things a reader retypes —
 * a settings block and four commands — and a mis-copied character is the whole
 * failure mode. So: a real box, generous padding, and NO wrapping. A wrapped
 * JSON block on a phone reads as two lines of something that must be one, and
 * the reader "fixes" it by inserting a newline that breaks the file. It scrolls
 * sideways inside its own box instead, which is honest about being one line.
 *
 * The nested <code> loses the inline pill styling above — inside a <pre> the
 * border and background would draw a box inside a box. */
.legal pre {
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: .8rem;
  padding: 1rem 1.1rem;
  margin: 1.2rem 0 1.4rem;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  font-size: .86rem;
  line-height: 1.65;
  tab-size: 2;
}
.legal pre code {
  background: none;
  border: 0;
  padding: 0;
  font-size: inherit;
  white-space: pre;
}

/* ---------- the contents list ---------- */

/* A PLAIN STATIC LIST, on purpose. The first design here was a sticky index that
 * marked the section you were in, using `:target`. `:target` matches only after
 * someone clicks an in-page link — it cannot follow a scroll — so that index
 * would have marked nothing, for ever. A static list has none of that: it works
 * in every browser, it survives print, and it is still right for a reader who
 * arrived at privacy.html#7 from a link in an email. */

.toc {
  border: 1px solid var(--border);
  border-radius: 1rem;
  background: var(--bg-raised);
  padding: 1.2rem 1.4rem;
  margin: 0 0 2.5rem;
  box-shadow: var(--shadow-sm);
}
.toc h2 {
  font-size: .76rem;
  font-weight: 680;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 0 0 .8rem;
  padding: 0;
  border: 0;
}
.toc h2::before { display: none; }
.toc ol {
  margin: 0; padding: 0;
  list-style: none;
  counter-reset: toc;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: .1rem 1.4rem;
}
.toc li { counter-increment: toc; margin: 0; }
.toc a {
  display: flex;
  gap: .55rem;
  align-items: baseline;
  padding: .3rem 0;
  color: var(--text-soft);
  text-decoration: none;
  font-size: .92rem;
  transition: color .2s ease;
}
.toc a::before {
  content: counter(toc);
  color: var(--accent);
  font-size: .76rem;
  font-weight: 680;
  font-variant-numeric: tabular-nums;
  flex: none;
  min-width: 1.1rem;
}
.toc a:hover { color: var(--accent); }

/* ---------- the records table ---------- */

/* privacy.html carries the GDPR Article 30 table — what is recorded, the lawful
 * basis, and the retention. It is the most legally consequential object on the
 * site and until 2026-09-07 it had NO CSS AT ALL: browser-default borders, no
 * padding, no cell separation. A glowing highlighter was proposed for this page
 * while its actual readability defect sat three lines away. Fixed first. */

.legal table {
  width: 100%;
  border-collapse: collapse;
  margin: 1.4rem 0 1.6rem;
  font-size: .92rem;
  border: 1px solid var(--border);
  border-radius: .8rem;
  overflow: hidden;
  /* radius on a collapsed table needs the wrapper trick; this is the cheap
   * version that works because every edge cell is a plain background */
  background: var(--bg-raised);
}
.legal thead, .legal tr:first-child { background: var(--bg-soft); }
.legal th {
  text-align: left;
  font-weight: 660;
  font-size: .78rem;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--text-soft);
  padding: .75rem .9rem;
  border-bottom: 1px solid var(--border-strong);
}
.legal td {
  padding: .8rem .9rem;
  border-top: 1px solid var(--border);
  vertical-align: top;
  line-height: 1.55;
}
.legal tr:nth-child(even) td { background: color-mix(in srgb, var(--bg-soft) 45%, transparent); }

/* On a phone a three-column records table cannot be read across. Let it scroll
 * inside its own box rather than squeezing the page. */
@media (max-width: 34rem) {
  .table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .legal table { min-width: 30rem; }
}

/* ===================== print ========================================== */

/* THESE PAGES GET PRINTED AND PDF'd — by the owner, and by any payment provider
 * or enterprise buyer reviewing the terms. Until 2026-09-07 there was no print
 * stylesheet at all, so they printed with a sticky header, a dark palette and
 * full-bleed bands.
 *
 * The marker needs no special handling: a view() timeline is INACTIVE in paged
 * media by spec, so the mark keeps its base style — which is the fully painted
 * highlight. That is the same reason the base state has to be the finished state
 * everywhere else in this file. The glow is switched off because a blurred
 * orange halo behind text is a waste of toner and reads as a smudge. */

@media print {
  html, body { background: #fff !important; color: #000 !important; }
  header.site, footer.site, .progress, .toc { display: none !important; }
  .band, .band--soft, .band--raised { background: none !important; padding-block: 0 !important; }
  .legal { max-width: none; padding: 0; }
  .legal h2 { break-after: avoid; page-break-after: avoid; }
  .legal p, .legal li, .legal tr { break-inside: avoid; page-break-inside: avoid; }
  .mk::before { display: none !important; }
  .mk { background-image: none !important; box-shadow: inset 0 -.55em 0 rgba(0, 0, 0, .09); }
  .legal table { border-color: #999 !important; }
  .legal th, .legal td { border-color: #999 !important; }
  .note { border: 1px solid #999 !important; box-shadow: none !important; }
  /* A URL after every link, so a printed policy is still checkable. */
  .legal a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .85em; color: #555; }
}

/* ===================== footer ========================================= */

footer.site {
  border-top: 1px solid var(--border);
  padding-block: 2.6rem 3.5rem;
  color: var(--text-soft);
  font-size: .92rem;
  background: var(--bg);
}
footer.site .shell { display: flex; justify-content: space-between; gap: 1.2rem; flex-wrap: wrap; align-items: center; }
footer.site nav { display: flex; gap: 1.4rem; flex-wrap: wrap; }
footer.site a { color: var(--text-soft); text-decoration: none; }
footer.site a:hover { color: var(--accent); }
footer.site .brand { font-size: 1rem; }

/* ===================== keyboard focus ================================= */

a:focus-visible, .btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: .4rem; }

/* ===================== narrow screens ================================= */

@media (max-width: 40rem) {
  header.site { position: static; backdrop-filter: none; -webkit-backdrop-filter: none; border-bottom: 1px solid var(--border); }
  header.site .shell { min-height: 0; padding-block: .9rem; }
  .stage .n { font-size: 5rem; }
  footer.site .shell { justify-content: flex-start; }
}
