/* ============================================================
   didote — landing page
   Implementation of "Didote Landing.dc.html" (Claude Design)

   The source design is a fixed 1728 x 8063 absolute-positioned
   canvas. Everything here is the same design expressed as a
   fluid, responsive document: the 1728px measurements are kept
   as the upper bound of clamp()/max-width so the page is pixel
   -faithful at 1728px and degrades gracefully below it.
   ============================================================ */

/* ---------- tokens ---------- */

:root {
  --black:        rgb(0, 0, 0);
  --white:        rgb(255, 255, 255);
  --heading:      rgb(241, 241, 241);
  --muted:        rgb(194, 194, 194);
  --dot-bg:       rgb(24, 24, 24);
  /* #D04C63 — the brand accent. The marquee's dot and every highlight in the
     pricing table read from here, so there is one place to change it. */
  --accent:       rgb(208, 76, 99);

  --card-grad:    linear-gradient(180deg, rgb(20, 22, 25) -39.74%, rgb(104, 113, 127) 100%);

  --ease-out:     cubic-bezier(0.22, 1, 0.36, 1);
  --ease-header:  cubic-bezier(0.4, 0, 0.2, 1);

  /* 114px / 1728px and 84px / 1728px — the two gutters used in the design */
  --gutter:       clamp(20px, 6.6vw, 114px);
  --gutter-wide:  clamp(16px, 4.86vw, 84px);

  --content:      1500px;   /* hero, headings, features   */
  --content-wide: 1560px;   /* module cards               */
}

/* ---------- reset ---------- */

*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }

/* Several things overrun the page sideways on purpose — the module coins bow
   out past the wheel, the marquee tracks are wider than the stage, the tour's
   centre shot enters zoomed. Only vertical scrolling should ever be possible.

   `clip`, not `hidden`, and on BOTH elements:
   - `overflow-x: hidden` on body alone propagates to the viewport, but iOS
     still lets you drag the page sideways — which is what you could do here.
   - `hidden` on both would make body a scroll container, and every sticky
     stage on the page (the tour, the showcase) resolves against the nearest
     one — they would all stop sticking.
   `clip` clips at the box without creating a scroll container, so it stops
   the drag and leaves sticky alone. Browsers too old for it simply scroll
   sideways as before, which is where we already were. */
html,
body { overflow-x: clip; }

body {
  margin: 0;
  background: var(--black);
  color: var(--white);
  font-family: Sora, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, p, ul, figure { margin: 0; }
ul { padding: 0; list-style: none; }
img, video, svg { display: block; max-width: 100%; }

a { color: var(--white); text-decoration: none; transition: color 160ms ease; }
a:hover { color: var(--muted); }

:focus-visible {
  outline: 2px solid var(--white);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ---------- pixel cursors ----------
   32x32 PNGs drawn on a 16x16 grid at 2x, so every pixel stays a hard
   square. Numbers after the url are the hotspot; each rule keeps a native
   keyword as fallback if the image cannot load. */

body,
input,
textarea,
select { cursor: url(../assets/cursor.png) 0 0, auto; }

a,
button,
summary,
label,
[role="button"],
.orbit__dot { cursor: url(../assets/cursor-pointer.png) 8 0, pointer; }

/* Not over the hero. `cursor` inherits, so `auto` on the section overrides the
   body's pixel arrow for the whole subtree — and auto is context-aware, so the
   headline gets a real text caret. The hero's own links keep a pointer, which
   needs the extra specificity to beat the global `a` rule. */
.hero { cursor: auto; }

/* …but the hero's own controls keep the pixel pointer, like every other
   button on the page. Only the copy loses it. */
.hero a,
.hero button { cursor: url(../assets/cursor-pointer.png) 8 0, pointer; }

.skip-link {
  position: absolute;
  left: 16px;
  top: -60px;
  z-index: 200;
  padding: 10px 18px;
  border-radius: 100px;
  background: var(--white);
  color: var(--black);
  font-size: 14px;
  font-weight: 600;
  transition: top 160ms ease;
}
.skip-link:focus { top: 16px; color: var(--black); }

/* ---------- layout ---------- */

.container {
  width: 100%;
  max-width: calc(var(--content) + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.container--wide {
  max-width: calc(var(--content-wide) + 2 * var(--gutter-wide));
  padding-inline: var(--gutter-wide);
}

/* ---------- buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  height: 42px;
  min-width: 156px;
  padding-inline: 22px;
  border-radius: 100px;
  font-size: 14px;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  transition: background 160ms ease, color 160ms ease, opacity 160ms ease, transform 160ms ease;
}

/* EXPERIMENT: the highlighted button state is the brand red rather than white.
   Text is black on it, not white — white on #D04C63 is only 4.29:1, under the
   4.5:1 AA floor, while black clears it at 4.90:1. Revert by swapping
   var(--accent) back to var(--white) in the three places below. */
.btn--solid { background: var(--accent); color: var(--black); }
.btn--solid:hover { color: var(--black); opacity: 0.86; transform: translateY(-1px); }

.btn--ghost { color: var(--white); box-shadow: inset 0 0 0 1px var(--white); }
.btn--ghost:hover {
  background: var(--accent);
  color: var(--black);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.btn__arrow { width: 16px; height: 10px; overflow: visible; flex: none; }
.btn__apple { width: 15px; height: 18px; flex: none; }

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

.site-header {
  position: fixed;
  inset: 24px 0 auto;
  z-index: 101;
  padding-inline: clamp(16px, 5.79vw, 100px);
  transform: translateY(0);
  transition: transform 320ms var(--ease-header);
  will-change: transform;
}

.site-header.is-hidden { transform: translateY(-140px); }

.site-header__pill {
  display: flex;
  align-items: center;
  gap: 24px;
  max-width: 1528px;
  height: 66px;
  margin-inline: auto;
  padding-inline: 18px;
  border-radius: 100px;
  background: rgba(14, 14, 14, 0.55);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.10), 0 18px 50px rgba(0, 0, 0, 0.45);
  /* Drops in with the hero's film, on the same 1925ms handoff. This has to sit
     on the pill rather than .site-header: the header's own transform is the
     hide-on-scroll state, and an animation with fill:both would take ownership
     of that property and the header could never hide again. */
  animation: header-drop 900ms var(--ease-out) 1925ms both;
}

@keyframes header-drop {
  from { opacity: 0; transform: translateY(-160px); }
  to   { opacity: 1; transform: none; }
}

/* Sits in from the pill's curve. The 8px gap is a holdover from when a
   status chip sat beside the wordmark; harmless with a single child. */
.site-header__brand {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
  margin-left: 12px;          /* sit in from the pill's curve */
}

.wordmark {
  flex: none;
  width: 86px;
  /* the outlined artwork's own box, so it can never be squashed */
  aspect-ratio: 596.16 / 139.34;
  background: url(../assets/didote-wordmark.svg) center / contain no-repeat;
}

.site-nav {
  display: flex;
  gap: clamp(24px, 2.6vw, 45px);
  margin-left: clamp(16px, 6.9vw, 120px);
  font-size: 14px;
}

.site-header__actions {
  display: flex;
  align-items: center;
  gap: clamp(16px, 1.7vw, 30px);
  margin-left: auto;
}

.site-header__login { font-size: 14px; }

/* hamburger */
.nav-toggle {
  display: none;
  flex: none;
  width: 42px;
  height: 42px;
  margin-left: auto;
  padding: 0;
  border: 0;
  border-radius: 100px;
  background: transparent;
}
.nav-toggle span {
  display: block;
  width: 20px;
  height: 1.5px;
  margin: 5px auto;
  background: var(--white);
  transition: transform 220ms var(--ease-out), opacity 160ms ease;
}
.site-header.is-open .nav-toggle span:first-child { transform: translateY(3.25px) rotate(45deg); }
.site-header.is-open .nav-toggle span:last-child  { transform: translateY(-3.25px) rotate(-45deg); }

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

.hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: 100svh;
  padding-block: 180px 92px;
  overflow: hidden;
}

/* The hero opens on nothing but black. The headline types onto the empty
   stage, the film fades up behind it once the last character lands, and the
   rest arrives over the tail of that fade.

   1925ms is where the typewriter ends: line one is 990ms, line two runs 935ms
   starting at 990ms. Change either and this has to move with it. */
.hero__media {
  position: absolute;
  inset: 0;
  /* Fallback that reads as intentional if background.mp4 is absent. */
  background:
    radial-gradient(120% 90% at 20% 10%, rgba(58, 62, 78, 0.55) 0%, rgba(0, 0, 0, 0) 60%),
    radial-gradient(90% 80% at 85% 20%, rgba(28, 32, 44, 0.7) 0%, rgba(0, 0, 0, 0) 65%),
    rgb(8, 9, 12);
  /* Deliberately NOT --ease-out: that curve is front-loaded and puts the film
     at 93% within 475ms, which reads as a cut rather than a fade. */
  animation: fade-in 1300ms ease-in-out 1925ms both;
}

.hero__video { width: 100%; height: 100%; object-fit: cover; }

.hero__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    rgba(0, 0, 0, 0.55) 0%,
    rgba(0, 0, 0, 0.35) 45%,
    rgba(0, 0, 0, 0.85) 82%,
    rgb(0, 0, 0) 100%);
}

.hero__inner { position: relative; z-index: 1; }

.hero__eyebrow {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 10px;
  line-height: 1;
  color: var(--muted);
}

.hero__title {
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 620px;
  margin-top: 24px;
  /* The floor is fluid, not a flat 26px. The lines are nowrap and cannot
     shrink to fit, so the floor has to be safe on the narrowest phone — but a
     constant made the headline smaller than the paragraph beneath it on every
     screen under ~800px, since 3.24vw does not reach 26px until then.
     7.8vw tracks the width instead: the longest line is 9.0em, so it lands at
     about 67% of the text column at any size, and the 40px cap keeps it there
     for tablets. Above 1235px 3.24vw takes over and desktop is unchanged. */
  font-size: clamp(min(7.8vw, 40px), 3.24vw, 56px);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--heading);
}

/* The caret is a real element sitting immediately after the clipped text,
   so it advances with the reveal. The text width animates to --w, measured
   in js/main.js — CSS cannot interpolate to fit-content and the headline
   size is fluid. */
.type-line {
  display: flex;
  align-items: flex-end;
  gap: 0.1em;
}

.type-line__text {
  display: block;
  /* Never let the flex row squeeze this below the width measured for it —
     it is overflow:hidden, so any shrink silently crops the line. */
  flex: 0 0 auto;
  width: 0;
  white-space: nowrap;
  /* line-height:1 makes the box exactly one em tall, but ascenders and the
     descenders on p/g reach past it — overflow:hidden would shave them.
     Pad the clip box out and pull it back with an equal negative margin, so
     the clip happens clear of the glyphs and the layout is unchanged. */
  padding-block: 0.2em;
  margin-block: -0.2em;
  overflow: hidden;
  animation: type var(--dur) steps(var(--chars), end) var(--delay, 0ms) both;
}

/* Pixel-block caret: stacked square "pixels" with hard edges, to sit with
   the pixel-art hero. --caret-px is one pixel unit; tune the block here. */
/* A plain solid caret. It used to be a stack of stripes via a repeating
   gradient, to read as pixel art — at this size that just looked like a
   rendering fault beside clean type. */
.type-caret {
  flex: none;
  width: 0.08em;
  height: 0.74em;
  margin-bottom: 0.07em;
  opacity: 0;
  background: var(--heading);
  animation: caret-blink 900ms steps(1) var(--delay, 0ms) infinite;
}

/* line one hands the caret over the moment line two starts */
.type-line:not(.type-line--last) .type-caret {
  animation:
    caret-blink 900ms steps(1) infinite,
    caret-off 0s 990ms forwards;
}

/* line two keeps it, then it fades away */
.type-line--last .type-caret {
  animation:
    caret-blink 900ms steps(1) var(--delay) infinite,
    caret-off 300ms 2185ms forwards;
}

.hero__lede {
  max-width: 528px;
  margin-top: 22px;
  font-size: 16px;
  line-height: 1.5;
}

.hero__cta {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 16px;
  margin-top: 33px;
}
.hero__cta .btn:last-child { min-width: 190px; }

.hero__trust {
  display: flex;
  flex-wrap: wrap;
  gap: 14px clamp(18px, 2.1vw, 36px);
  margin-top: 34px;
  font-size: 12.5px;
  line-height: 1;
}
.hero__trust li { display: flex; align-items: center; gap: 9px; }
.hero__trust svg { flex: none; width: 17px; height: 17px; }
.hero__trust li:first-child svg { width: 18px; height: 18px; }

/* ============================================================
   product tour — scroll-driven

   400vh of runway with a sticky 100vh stage. js/main.js drives
   the centre tile's scale/opacity, the two captions, and the
   staggered fade-in of the surrounding eight tiles.
   ============================================================ */

/* The tour paints above the modules section so the icons, which fly up out
   of .modules on their way in, are hidden behind the grid's bottom row until
   they clear it. Both need an explicit z-index: without one, DOM order alone
   would put the later section on top. */
.tour {
  position: relative;
  z-index: 2;
  height: 400vh;
  margin-top: clamp(80px, 11.6vw, 200px);
  background: var(--black);
}

.tour__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  padding: 3vh 3vw;
  overflow: hidden;
}

/* --- the word bed behind the tour ---
   Rows counter-scroll past each other. Each track holds enough copies to
   overrun the stage, and js/main.js wraps the offset by one copy's width,
   so the run never ends and never shows a seam. */

.marquee {
  /* Rows and gaps have to add up to the stage exactly, or the type floats in
     the middle with dead black above and below it. --rows must match the
     number of .marquee__row that are actually displayed, and it feeds the
     font-size below — change the two together. */
  --rows: 5;
  --line: 1.05;
  --mgap: clamp(6px, 1vh, 20px);
  position: absolute;
  inset: 0;
  z-index: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--mgap);
  overflow: hidden;
  pointer-events: none;
}

/* No shrinking: the rows are sized to fill the stage exactly, and any
   sub-pixel overflow should bleed off the top and bottom edges rather than
   squeeze a row and let its own overflow:hidden shave the ascenders. */
.marquee__row { flex: none; overflow: hidden; }

/* The extra seven are for phones only — see the note in index.html. */
.marquee__row:nth-child(n + 6) { display: none; }

.marquee__track {
  display: flex;
  width: max-content;
  transform: translate3d(var(--mx, 0px), 0, 0);
  will-change: transform;
}

.marquee__phrase {
  padding-right: 0.32em;
  /* Sized off the stage height, not the width, so the stack always lands flush
     top and bottom. The vw term is only a guard for very narrow viewports,
     where a height-driven size would put barely one letter on screen. */
  /* vh, deliberately — NOT dvh. The stage this fills is `height: 100vh`, so
     sizing the type off anything else means the two disagree. dvh was worse
     than a mismatch on a phone: it changes continuously as the URL bar
     collapses, so the phrases resized mid-scroll while js/main.js was still
     parking the rows off-screen using the width it measured before. The rows
     ended up short of the edge and the tail of each phrase sat visible until
     the 200ms resize debounce rebuilt them. */
  font-size: min(
    calc((100vh - (var(--rows) - 1) * var(--mgap)) / var(--rows) / var(--line)),
    34vw
  );
  font-weight: 600;
  line-height: var(--line);
  letter-spacing: -0.03em;
  white-space: nowrap;
  color: var(--heading);
}

.marquee__phrase i { font-style: normal; color: var(--accent); }

.tour__grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  gap: 12px;
  width: 100%;
  height: 100%;
  /* How far the centre shot is zoomed in when the section arrives. js/main.js
     reads this and eases it back to 1 — it lives here rather than in the
     script so the phone layout, which has far less width to zoom into, can
     override it with the rest of its rules. */
  --tour-zoom: 2.4;
}

.tour__grid img {
  position: relative;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  border-radius: 6px;
  opacity: 0;
  will-change: opacity, transform;
}

/* Only the bottom row fades, so the grid dissolves into the page at its
   outer edge. Fading every tile put a seam across the middle of the grid. */
.tour__grid img:nth-child(n + 7) {
  -webkit-mask-image: var(--tile-fade);
  mask-image: var(--tile-fade);
}

/* A long smoothstep ramp rather than a short linear one: the extra stops
   ease the fade in and out, so there is no visible line where it starts
   and the tile dissolves evenly into the page. */
.tour__grid {
  --tile-fade: linear-gradient(to bottom,
    rgb(0, 0, 0) 0,
    rgb(0, 0, 0) 48%,
    rgba(0, 0, 0, 0.94) 56%,
    rgba(0, 0, 0, 0.78) 64%,
    rgba(0, 0, 0, 0.58) 71%,
    rgba(0, 0, 0, 0.35) 79%,
    rgba(0, 0, 0, 0.16) 87%,
    rgba(0, 0, 0, 0.03) 95%,
    rgba(0, 0, 0, 0) 100%);
}

/* the centre tile — enters zoomed in and shrinks into its cell */
.tour__hero-shot {
  z-index: 2;
  object-fit: contain;
  object-position: center;
  border-radius: 0;
  transform: scale(var(--tour-zoom, 2.4));
  transform-origin: center;
}


/* ============================================================
   modules
   ============================================================ */

/* Deliberately NOT positioned. It used to carry `position: relative; z-index: 1`
   to sit under .tour, but that promoted the whole subtree above every later
   static section — including the showcase's waiting copy step, which is parked
   a full viewport below its stage and has no opacity of its own. It landed on
   top of .features and .focus and swallowed their clicks. .tour's own z-index
   of 2 already beats this section, so nothing is needed here. */
.modules {
  /* This gap is the runway the icons fly down. The queue is roughly ten tile
     diameters long, so too little here and it arrives piled up instead of
     strung out. Also keeps the ring off the fixed header as it passes. */
  padding-top: clamp(96px, 11vw, 210px);
  padding-bottom: clamp(48px, 5vw, 96px);
}

/* --- 3D tilt wheel ---------------------------------------------
   The ten icons ride a ring that drifts on its own. The ring tilts
   toward the pointer; scrolling through the section spreads the
   tiles and eases the perspective. js/main.js writes --wheel-persp
   and the per-tile transforms; everything here is the rest state,
   so the wheel still reads correctly with JS off.               */

.wheel {
  position: relative;
  display: grid;
  place-items: center;
  /* Sized for the headline plus the resting ring, not for the ring at full
     tilt and expansion — the section is unclipped, so the extremes spill
     into the empty space above and below rather than being sliced. Sizing
     for the worst case just left a lot of dead space. Horizontal overflow
     is handled by `overflow-x: hidden` on body. */
  /* The vw term is what gives the flight room on desktop; the px floor is for
     narrow screens, where the ring is small and a taller box would just be
     dead space. */
  min-height: max(560px, 56vw);
  /* Resting ring radius as a share of the wheel's width; js/main.js reads it.
     Lives here so the phone breakpoint, where the ring has far less width to
     spread across, can open it out with the rest of its rules. */
  --wheel-r: 0.28;
  cursor: url(../assets/cursor-pointer.png) 8 0, grab;
  /* horizontal drags turn the ring, vertical ones still scroll the page */
  touch-action: pan-y;
}

.wheel.is-dragging {
  cursor: url(../assets/cursor-pointer.png) 8 0, grabbing;
  user-select: none;
  -webkit-user-select: none;
}

/* The WebGL canvas sits under everything else in the wheel. The headline is
   already forced in front of every icon (see textZ), so losing depth sorting
   between canvas and DOM costs nothing — but the stage needs an explicit
   z-index to stay above a canvas that comes after it in the DOM.

   Fixed to the VIEWPORT, not sized to .wheel. The icons fly about a screen and
   a half above the ring on their way in and a screen and a bit below it on the
   way out; a canvas the size of the wheel cut them off at a hard horizontal
   line in mid-air, at both ends. Covering the viewport means there is no edge
   to hit. Growing the canvas instead would work too, but the backing store
   would be three viewports tall — this way it never changes size.

   z-index 0 is what makes them pass *behind* things: .tour (z-index 2) hides
   the run-in until they clear the grid, and .showcase — positioned, later in
   the tree, so it paints after — hides the drop as they go under the card. */
.wheel__canvas {
  position: fixed;
  inset: 0;
  /* Explicit, not left to inset:0. A canvas is a replaced element, so with
     width:auto it takes its intrinsic size — the backing store, which is
     device-pixel sized — and renders at twice the box on a 2x screen. */
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}

.wheel__stage {
  position: absolute;
  inset: 0;
  z-index: 1;
  perspective: var(--wheel-persp, 1100px);
  perspective-origin: 50% 50%;
  pointer-events: none;
}

/* The ring is a horizontal circle in the XZ plane — a carousel, not a
   dial. Tiles are billboarded (translate only, no rotation) so they
   stay upright while perspective makes the near arc large and low and
   the far arc small and high. */
.wheel__ring {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
  will-change: transform;
}

.wheel__items {
  position: absolute;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
}

.wheel__center {
  position: absolute;
  left: 0;
  top: 0;
  /* wide enough to hold the headline's three authored lines — a narrow box
     wraps it to five and the band then crosses far more of the text */
  width: min(90vw, 820px);
  text-align: center;
  transform: translate(-50%, -50%);
  transform-style: preserve-3d;
}

.modules__lede {
  /* narrower than the headline so the line length stays readable */
  max-width: 560px;
  margin: clamp(20px, 2vw, 32px) auto 0;
  font-size: clamp(15px, 1.15vw, 18px);
  line-height: 1.55;
  color: var(--muted);
}

/* Section CTAs sit the same distance below their body copy. */
.modules__cta,
.focus__cta { margin-top: clamp(24px, 2.4vw, 40px); }

/* The modules one lives inside .wheel__stage, which is pointer-events:none so
   drags fall through to .wheel — it has to opt back in to be clickable, the
   same way .orbit__dot does. */
.modules__cta { pointer-events: auto; }

.modules__title {
  /* Sized so the authored two-line break holds. The old headline was two short
     words a line; this one runs to a full sentence, and at the previous 4.8vw
     it wrapped to three lines with the em dash stranded on its own. */
  font-size: clamp(27px, 3.5vw, 60px);
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--heading);
}

/* --- the module icons --- */

.orbit {
  --dot: clamp(46px, 7.7vw, 132px);
}

.orbit__dot {
  position: absolute;
  left: calc(var(--dot) * -0.5);
  top: calc(var(--dot) * -0.5);
  width: var(--dot);
  height: var(--dot);
  border-radius: 50%;
  /* The dot is a real 3D object, not a flat sprite: js/main.js fills it with a
     face plus a stack of edge discs offset in Z, and turns it on the ring so
     the thickness is actually visible. It paints nothing itself — the face
     layer carries the artwork. --icon is set per module below. */
  transform-style: preserve-3d;
  will-change: transform;
  /* the stage is pointer-events:none so drags fall through to .wheel;
     the icons opt back in so they can be hovered and focused */
  pointer-events: auto;
  transition: box-shadow 200ms ease, filter 200ms ease;
}

/* Meshes took over the drawing. The CSS coins stay in the DOM — they still
   carry the titles, stay keyboard-focusable, and their live rects are what the
   hover bubble anchors to — they just no longer paint. */
.wheel.is-3d .orbit__dot { opacity: 0; pointer-events: none; }

/* Nothing to hover while the icons are still flying in or already dropping
   away — a tip anchored to a moving target reads as a glitch. */
.wheel.is-travelling .orbit__dot { pointer-events: none; }

/* --- the coin's own layers --- */

/* The depth fade rides on the LAYERS, never on .orbit__dot. Opacity below 1
   forces transform-style: flat on the element it is set on, which collapsed
   every coin's stack onto a single plane and killed the 3D entirely. On leaf
   layers it is harmless. js/main.js writes --fade on the dot; it inherits. */
.orbit__face,
.orbit__edge {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  opacity: var(--fade, 1);
  backface-visibility: hidden;
}

.orbit__face { background: var(--icon) center / contain no-repeat; }

/* Solid discs stacked behind the face, forming the coin's rim. This has to be
   markedly lighter than the page: at the artwork's own #181818 the whole rim
   was black on black and the depth was invisible. js/main.js sets the Z offset
   and darkens each layer as it recedes. */
.orbit__edge { background: rgb(92, 92, 102); }

/* The artwork is on the face layer, so hover cannot tint the disc — painting a
   colour here would cover the icon entirely. Lift it instead.
   The ring doubles as the focus indicator, hence outline: none. */
.orbit__dot.is-active,
.orbit__dot:focus-visible {
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.22), 0 0 34px rgba(255, 255, 255, 0.10);
  filter: brightness(1.15);
  outline: none;
}

/* --- hovered module description: a bubble anchored to the icon --- */

.wheel__tip {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 6;
  width: max-content;
  max-width: min(80vw, 290px);
  padding: 13px 16px 14px;
  border-radius: 14px;
  background: rgb(26, 26, 28);
  box-shadow: 0 22px 50px rgba(0, 0, 0, 0.65);
  text-align: center;
  opacity: 0;
  transform: translate(-50%, 4px);
  pointer-events: none;
  transition: opacity 170ms ease, transform 170ms var(--ease-out);
}

.wheel__tip.is-shown { opacity: 1; transform: translate(-50%, 0); }

/* the tail — points down at the icon, or up when the bubble is flipped
   below because there was no room above */
.wheel__tip::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 100%;
  border: 8px solid transparent;
  border-top-color: rgb(26, 26, 28);
  transform: translateX(-50%);
}

.wheel__tip.is-below::after {
  top: auto;
  bottom: 100%;
  border-top-color: transparent;
  border-bottom-color: rgb(26, 26, 28);
}

.wheel__tip-title {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--white);
}

.wheel__tip-desc {
  margin-top: 8px;
  font-size: 14px;
  line-height: 1.45;
  color: var(--muted);
}

/* Artwork per module. These must live in the stylesheet: a relative url()
   held in a custom property resolves against the sheet that *uses* it, so
   declaring them inline in the HTML would resolve against css/ and 404. */
.orbit__dot:nth-child(1) { --icon: url(../assets/modules/archieve.svg); }
.orbit__dot:nth-child(2) { --icon: url(../assets/modules/notes.svg); }
.orbit__dot:nth-child(3) { --icon: url(../assets/modules/testimonials.svg); }
.orbit__dot:nth-child(4) { --icon: url(../assets/modules/rate-builder.svg); }
.orbit__dot:nth-child(5) { --icon: url(../assets/modules/proposals.svg); }
.orbit__dot:nth-child(6) { --icon: url(../assets/modules/agreement.svg); }
.orbit__dot:nth-child(7) { --icon: url(../assets/modules/invoice.svg); }
.orbit__dot:nth-child(8) { --icon: url(../assets/modules/focus-timer.svg); }
.orbit__dot:nth-child(9) { --icon: url(../assets/modules/communication.svg); }
.orbit__dot:nth-child(10) { --icon: url(../assets/modules/ask-didote.svg); }

/* ============================================================
   pinned module showcase

   The section pins for its runway. As it scrolls, the copy
   cross-fades 01 -> 04 while the video rail slides through the
   four clips. js/main.js -> updateShowcase() writes --rail and
   each step's opacity.
   ============================================================ */

.showcase {
  position: relative;
  /* 100vh of runway per module — 3 transitions across 300vh */
  height: 400vh;
  margin-top: clamp(60px, 9vw, 150px);
}

.showcase__stage {
  position: sticky;
  top: 0;
  display: flex;
  align-items: center;
  height: 100vh;
}

.showcase__grid {
  display: grid;
  grid-template-columns: minmax(0, 430px) minmax(0, 1fr);
  gap: clamp(28px, 5.73vw, 99px);
  align-items: center;
  width: 100%;
}

/* --- copy column: steps stacked on one cell, cross-faded --- */

/* The steps stack on one cell. They travel a full viewport height apart,
   so the waiting step is genuinely below the bottom of the browser and the
   finished one is above the top — no clipping window needed, and never two
   of them on screen at once. */
.showcase__copy { display: grid; }

.mstep {
  grid-area: 1 / 1;
  will-change: transform;
}

/* the copy travels up through the column as the steps advance */

.mstep__num {
  font-size: clamp(52px, 6.25vw, 108px);
  font-weight: 100;
  line-height: 1;
  color: var(--white);
}

.mstep__title {
  margin-top: clamp(28px, 4vw, 64px);
  font-size: clamp(26px, 2.6vw, 44px);
  font-weight: 600;
  line-height: 1.05;
  color: var(--heading);
}

.mstep__copy {
  display: grid;
  gap: 14px;
  max-width: 430px;
  margin-top: clamp(20px, 2.6vw, 40px);
  font-size: 16px;
  line-height: 1.5;
  color: var(--muted);
}

/* bullets lead with an icon rather than an arrow */
.mstep__point {
  display: flex;
  gap: 12px;
  align-items: flex-start;
}

.mstep__point svg {
  flex: none;
  width: 21px;
  height: 21px;
  margin-top: 2px;              /* optically centre on the first line */
  color: var(--white);
}

/* --- media column: clips cross-fade in place ---
   The frame clips again now that nothing travels in from outside it. */

.showcase__media {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: clamp(12px, 1.4vw, 24px);
  overflow: hidden;
  /* Blue bloom behind the card. This can sit on the media box itself: overflow
     clips descendants, not an element's own outer shadow, and nothing up the
     chain to .showcase__stage clips either — so no wrapper is needed.
     Three layers: a hairline edge, a weighted pool under the card, and a wide
     ambient haze. */
  box-shadow:
    0 0 0 1px rgba(142, 178, 255, 0.10),
    0 22px 60px -18px rgba(44, 88, 198, 0.55),
    0 0 150px -20px rgba(78, 130, 245, 0.38);
}

.showcase__rail { position: absolute; inset: 0; }

.showcase__clip {
  position: absolute;
  inset: 0;
  overflow: hidden;
  opacity: var(--o, 0);
  transform: scale(var(--s, 1));
  will-change: opacity, transform;
}

.showcase__clip video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ============================================================
   features
   ============================================================ */

/* a lighter panel, inset from the page edges, so the two rows read as one
   block rather than floating on the black */
.features {
  margin: clamp(60px, 7vw, 120px) clamp(12px, 1.6vw, 28px) 0;
  padding-block: clamp(56px, 6.5vw, 110px);
  border-radius: clamp(18px, 2vw, 34px);
  background: rgb(15, 15, 16);
}

.features__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: clamp(20px, 1.7vw, 30px);
  row-gap: clamp(56px, 6vw, 100px);
}

/* inline now, so each column can carry its own icon */
.feature__icon {
  display: block;
  width: 31px;
  height: 31px;
  color: var(--white);
}

.feature__title {
  margin-top: clamp(28px, 2.9vw, 50px);
  font-size: clamp(26px, 2.26vw, 39px);
  font-weight: 300;
  line-height: 1;
  color: var(--heading);
}

/* The grid stretches all three columns to the same height, and the copy
   takes up the slack — so the cards below start on the same line however
   many lines of copy each column runs to. */
.feature { display: flex; flex-direction: column; }

/* a longer, slower slide than the page default — these are the one place the
   arrival is the point, so it wants to be legible rather than incidental */
html.js .feature.reveal {
  transform: translateY(46px);
  transition-duration: 880ms;
}

.feature__copy {
  flex: 1 1 auto;
  /* the design's 272px suited one placeholder line; the real copy runs to a
     short paragraph, so give it a readable measure */
  max-width: 400px;
  margin-top: clamp(18px, 2vw, 34px);
  font-size: 16px;
  line-height: 1.45;
  color: var(--muted);
}

/* The source paints the 558x480 frame art into a 479.34x404.525
   rounded window and clips it — so only the middle of card-frame.png
   is ever visible. ::before reproduces that crop. */
.feature__card {
  position: relative;
  aspect-ratio: 479.34 / 404.525;
  margin-top: clamp(20px, 2.5vw, 43px);
  border-radius: 4.856% / 5.754%;   /* 23.274px on the source window */
  overflow: hidden;
  background: rgb(20, 24, 28);
}

.feature__card::before {
  content: "";
  position: absolute;
  left: -6.881%;
  top: -6.738%;
  width: 116.41%;
  height: 118.66%;
  background: url(../assets/card-frame.png) 50% 50% / 100% 100% no-repeat;
}

/* The shot fills the window edge to edge instead of sitting inset inside
   the frame art — an inset left a rim of frame showing in a different
   tone, which read as a seam. The card's own colour matches the shots'
   background (they all sit around rgb(20,24,28)) so the rounded corners
   blend into the image rather than outlining it. */
.feature__card img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ============================================================
   focus mode
   ============================================================ */

.focus { margin-top: clamp(72px, 9vw, 160px); }

.focus__title,
.pricing__title {
  font-size: clamp(26px, 2.3vw, 40px);
  font-weight: 600;
  line-height: 1.18;
  letter-spacing: -0.01em;
  color: var(--heading);
}

.focus__lede,
.pricing__lede {
  /* The design's 380px suited its two-line placeholder; the real copy is a
     full paragraph and at that width it stacked into a narrow five-line
     column. This is ~66 characters a line. */
  max-width: 540px;
  margin-top: clamp(14px, 1.4vw, 24px);
  font-size: 14px;
  line-height: 1.5;
  color: var(--muted);
}

/* The shot carries its own rounded corners and hairline border, and its corner
   pixels are fully transparent — so this frame stays undecorated. A background
   or radius here would show as wedges in the corners and a doubled edge.
   aspect-ratio is the artwork's own, and holds the space before it decodes so
   the footer does not jump on a slow connection. */
.focus__shot {
  aspect-ratio: 2800 / 1575;
  margin: clamp(32px, 4vw, 72px) 0 0;
}

.focus__shot img { width: 100%; height: 100%; object-fit: contain; }

/* ============================================================
   pricing
   ============================================================ */

.pricing { margin-top: clamp(72px, 9vw, 160px); }

/* Narrower than the rest of the page and centred — the three columns read as
   one block rather than spanning the full measure. The heading and lede centre
   with it; the plan columns stay left-aligned, because a centred tick list is
   much harder to scan. */
.pricing > .container { max-width: calc(1000px + 2 * var(--gutter)); }

.pricing__title { text-align: center; }

.pricing__lede {
  max-width: 620px;
  margin-inline: auto;
  text-align: center;
}

/* --- credit calculator ---
   Same rules as the plans below it: no panel, no fill, no second accent. Two
   columns under one hairline, and the only saturated colour is the meter fill
   and the figure it produces. */
.calc {
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  gap: clamp(28px, 4vw, 72px);
  margin-top: clamp(40px, 4.8vw, 80px);
  padding-top: clamp(22px, 2vw, 34px);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
}

.calc__label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgb(140, 146, 156);
}

.calc__title {
  margin-top: clamp(10px, 1vw, 16px);
  font-size: clamp(17px, 1.35vw, 21px);
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--heading);
}

.calc__sub {
  margin-top: 6px;
  font-size: 13px;
  line-height: 1.5;
  color: rgb(130, 136, 146);
}

.calc__rows { margin-top: clamp(16px, 1.6vw, 26px); }

.calc__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 11px 0;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.calc__row:first-child { border-top: none; }

.calc__what { display: flex; flex-direction: column; gap: 2px; }
.calc__name { font-size: 14px; color: var(--white); }
.calc__cost { font-size: 11px; color: rgb(120, 126, 136); }

.calc__ctrl { display: flex; align-items: center; gap: 10px; }

/* Square-ish outline buttons, the same hairline as everything else. */
.calc__btn {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 8px;
  background: transparent;
  color: var(--white);
  font: inherit;
  font-size: 15px;
  line-height: 1;
  transition: border-color 160ms ease, color 160ms ease;
}
.calc__btn:hover { border-color: var(--accent); color: var(--accent); }
.calc__btn:active { transform: scale(0.94); }

/* tabular figures so the column does not jitter as the number changes */
.calc__val {
  min-width: 30px;
  text-align: center;
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--white);
}

.calc__row--free { color: rgb(120, 126, 136); }
.calc__row--free .calc__name { color: rgb(150, 156, 166); }
.calc__free {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
}

/* --- the read-out --- */
.calc__total {
  display: flex;
  align-items: baseline;
  gap: 9px;
  margin-top: clamp(12px, 1.2vw, 20px);
}
.calc__n {
  font-size: clamp(48px, 4.8vw, 82px);
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--white);
}
.calc__unit { font-size: 15px; color: rgb(130, 136, 146); }

.calc__verdict {
  margin-top: 10px;
  font-size: 13.5px;
  line-height: 1.5;
  color: rgb(140, 146, 156);
}
.calc__verdict b { color: var(--white); font-weight: 600; }
.calc__verdict .calc__fits { color: var(--accent); font-weight: 600; }

/* The read-out is shorter than the column of steppers beside it, which left a
   band of dead space under the meters. The figure holds the top, the meters
   drop to the foot, and the two columns finish on the same line. */
.calc__side--out { display: flex; flex-direction: column; }

.calc__meters {
  display: grid;
  gap: clamp(16px, 1.8vw, 26px);
  margin-top: auto;                        /* to the bottom of the column */
  padding-top: clamp(22px, 2.2vw, 34px);   /* but never crowding the verdict */
}

.meter__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 7px;
}
.meter__name { font-size: 12.5px; font-weight: 600; color: var(--white); }
.meter__name em {
  font-style: normal;
  font-weight: 400;
  font-size: 11px;
  color: rgb(120, 126, 136);
  margin-left: 5px;
}
.meter__status { font-size: 11.5px; color: rgb(120, 126, 136); }
.meter__status.is-over { color: var(--accent); }

.meter__track {
  display: block;
  height: 4px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.10);
  overflow: hidden;
}
.meter__fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 3px;
  background: var(--accent);
  transition: width 420ms var(--ease-out);
}
/* a cap that has been blown reads as the bar plus the label, not a new hue */
.meter__fill.is-over { background: rgba(208, 76, 99, 0.42); }

/* No filled cards. Each plan is a column under a hairline rule — the rule and
   the whitespace do the separating, so the only saturated colour in the whole
   section is the one that marks the recommended plan. */
.pricing__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(22px, 2.6vw, 46px);
  margin-top: clamp(40px, 4.8vw, 84px);
}

.plan {
  --pad: clamp(22px, 2vw, 34px);
  position: relative;
  display: flex;
  flex-direction: column;
  padding-top: var(--pad);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
}

.plan--featured { border-top-color: var(--accent); }

/* sits on the plan-name line, right-aligned — no pill, no fill */
.plan__badge {
  position: absolute;
  right: 0;
  top: var(--pad);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
}

.plan__name {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgb(140, 146, 156);
}

.plan__price {
  display: flex;
  align-items: baseline;
  gap: 7px;
  margin-top: clamp(14px, 1.4vw, 24px);
}

.plan__amount {
  font-size: clamp(34px, 3vw, 52px);
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--white);
}

.plan__per { font-size: 13px; color: rgb(130, 136, 146); }

.plan__note {
  margin-top: clamp(10px, 1vw, 16px);
  font-size: 13.5px;
  line-height: 1.45;
  color: rgb(140, 146, 156);
}

/* What the credits actually buy, in plain terms. Set off from the note above
   it by a rule rather than a filled box, like everything else here. */
.plan__outcome {
  margin-top: clamp(12px, 1.2vw, 18px);
  padding-top: clamp(12px, 1.2vw, 18px);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  font-size: 13px;
  line-height: 1.5;
  color: rgb(140, 146, 156);
}
.plan__outcome b {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}

.plan__list {
  display: grid;
  align-content: start;
  gap: clamp(10px, 0.9vw, 15px);
  margin-top: clamp(22px, 2.2vw, 36px);
  margin-bottom: clamp(26px, 2.6vw, 44px);
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--muted);
}

.plan__list li { display: flex; gap: 10px; align-items: flex-start; }

.plan__tick {
  flex: none;
  width: 14px;
  height: 14px;
  margin-top: 2px;
  stroke-width: 2;
  color: var(--accent);
}

/* margin-top:auto pushes the button to the bottom of its column, and since the
   grid stretches all three to one height they land on the same line whatever
   the list length. The minimum gap has to come from the list's own margin —
   a margin-top here would override the auto and break the alignment. */
.plan__cta {
  width: 100%;
  margin-top: auto;
}

/* the footnote is just text under a rule, not a panel. Centred to match the
   heading, and held to a readable measure — centred text set to the full
   1000px grid is hard to track back to the start of each line. */
.pricing__note {
  max-width: 720px;
  margin-inline: auto;
  text-align: center;
  margin-top: clamp(32px, 3.4vw, 60px);
  padding-top: clamp(18px, 1.8vw, 30px);
  border-top: 1px solid rgba(255, 255, 255, 0.10);
  font-size: 13px;
  line-height: 1.6;
  color: rgb(130, 136, 146);
}
.pricing__note strong { color: rgb(200, 205, 213); font-weight: 600; }

@media (max-width: 900px) {
  .pricing__grid { grid-template-columns: 1fr; }
  /* the read-out drops under the steppers, with a rule between them */
  .calc { grid-template-columns: 1fr; gap: 0; }
  .calc__side--out {
    margin-top: clamp(24px, 4vw, 36px);
    padding-top: clamp(20px, 3vw, 30px);
    border-top: 1px solid rgba(255, 255, 255, 0.14);
  }
}

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

.site-footer {
  padding-top: clamp(140px, 20vw, 345px);
  padding-bottom: clamp(24px, 2.6vw, 46px);
}

.site-footer__wordmark {
  /* sits below the links now — see the note on .site-footer__bar */
  aspect-ratio: 596.16 / 139.34;
  margin-top: clamp(30px, 3.4vw, 60px);
  background: url(../assets/didote-wordmark.svg) center / contain no-repeat;
}

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

@keyframes type {
  from { width: 0; }
  to   { width: var(--w, 100%); }
}
@keyframes caret-blink {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0; }
}
@keyframes caret-off { to { opacity: 0; } }
@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes fade-up {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

.anim { animation: fade-up 620ms var(--ease-out) var(--delay, 0ms) both; }

/* Scroll-triggered reveal. Only armed once JS is running, so the
   page is fully readable without it. */
html.js .reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 700ms var(--ease-out), transform 700ms var(--ease-out);
  /* --i is the item's place in its row, so a group of siblings that all cross
     the threshold together still arrives one at a time. */
  transition-delay: calc(var(--i, 0) * 140ms);
}
html.js .reveal.is-visible { opacity: 1; transform: none; }

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

.legal-header { padding-block: 26px clamp(40px, 5vw, 80px); }

.legal-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}

.legal-header__brand {
  display: flex;
  align-items: center;
  gap: 8px;
}

.legal-header__back { font-size: 14px; color: var(--muted); }
.legal-header__back:hover { color: var(--white); }

.legal { padding-bottom: clamp(60px, 8vw, 130px); }

/* One narrow column — these are read, not scanned. */
.legal__inner { max-width: calc(720px + 2 * var(--gutter)); }

.legal__title {
  font-size: clamp(30px, 3.4vw, 54px);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--heading);
}

.legal__updated {
  margin-top: 14px;
  font-size: 13px;
  color: rgb(130, 136, 146);
}

.legal p,
.legal .legal__list { margin-top: clamp(14px, 1.4vw, 20px); }

.legal p,
.legal li {
  font-size: 15px;
  line-height: 1.7;
  color: var(--muted);
}

.legal h2 {
  margin-top: clamp(34px, 3.6vw, 60px);
  font-size: clamp(18px, 1.5vw, 23px);
  font-weight: 600;
  line-height: 1.3;
  color: var(--heading);
}

.legal strong { color: rgb(226, 229, 235); font-weight: 600; }
.legal em { color: rgb(150, 156, 166); }

.legal__list { display: grid; gap: 10px; padding-left: 22px; list-style: disc; }
.legal__list li::marker { color: rgb(96, 102, 112); }

/* Unfilled placeholders from the source document. Loud on purpose — nothing
   should go live with one of these still in it. */
.todo {
  padding: 1px 5px;
  border-radius: 4px;
  background: rgba(208, 76, 99, 0.20);
  box-shadow: inset 0 0 0 1px rgba(208, 76, 99, 0.40);
  color: rgb(240, 168, 182);
  font-size: 0.92em;
  font-weight: 600;
}

/* --- footer --- */

/* Two rows: the sections first in white with the download CTA opposite them,
   then the legal links muted, with the rule between. Same spacing either side
   of the rule. */
.site-footer__top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 18px clamp(24px, 3vw, 56px);
  padding-bottom: clamp(20px, 2.2vw, 34px);
  border-bottom: 1px solid rgba(255, 255, 255, 0.10);
}

.site-footer__nav {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(22px, 2.6vw, 46px);
  font-size: 15px;
}

.site-footer__download { min-width: 190px; }

.site-footer__nav a { color: var(--white); }
.site-footer__nav a:hover { color: var(--muted); }

.site-footer__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: clamp(18px, 2.4vw, 44px);
  margin-top: clamp(20px, 2.2vw, 34px);
}

.site-footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(14px, 1.8vw, 30px);
  font-size: 13.5px;
}

.site-footer__links a { color: rgb(150, 156, 166); }
.site-footer__links a:hover { color: var(--white); }
.site-footer__links a[aria-current="page"] { color: var(--white); }

.site-footer__note {
  max-width: 460px;
  font-size: 12.5px;
  line-height: 1.55;
  color: rgb(120, 126, 136);
}
.site-footer__note strong { color: rgb(170, 176, 186); font-weight: 600; }

/* ============================================================
   responsive
   ============================================================ */

@media (max-width: 1180px) {
  .orbit { --dot: clamp(44px, 9vw, 98px); }

  /* stack the showcase: copy above, video below */
  .showcase { height: 320vh; }
  .showcase__grid { grid-template-columns: minmax(0, 1fr); gap: 28px; }
  .mstep__copy { max-width: 620px; }
}

@media (max-width: 980px) {
  .features__grid { grid-template-columns: minmax(0, 1fr); gap: 72px; }
  .feature__copy { max-width: 420px; }
  .feature__card { max-width: 480px; }
}

@media (max-width: 900px) {
  .site-nav, .site-header__actions { display: none; }
  .nav-toggle { display: block; }

  .site-header.is-open .site-header__pill {
    flex-wrap: wrap;
    height: auto;
    padding-block: 12px 20px;
    border-radius: 28px;
  }
  /* keep the toggle beside the wordmark once the panel wraps open */
  .site-header.is-open .wordmark { order: 0; }
  .site-header.is-open .nav-toggle { order: 1; }
  .site-header.is-open .site-nav { order: 2; }
  .site-header.is-open .site-header__actions { order: 3; }

  .site-header.is-open .site-nav,
  .site-header.is-open .site-header__actions {
    display: flex;
    flex-basis: 100%;
    flex-direction: column;
    gap: 18px;
    margin: 12px 0 0;
    padding-inline: 6px;
  }
  .site-header.is-open .site-header__actions { align-items: stretch; }
  .site-header.is-open .btn { width: 100%; }

  /* The copy is bottom-aligned, so the trailing figure is what you read as
     dead space under the trust row — 76px of it on a phone, below a block
     that only fills half the screen. */
  .hero { padding-block: 132px 34px; }
}

@media (max-width: 640px) {
  /* --- the tour, retuned for portrait ---------------------------------
     Three columns on a phone gave each shot a 99px-wide, 1:2.2 cell, so a
     16:9 screenshot was cropped to a vertical sliver, and five rows of type
     made every phrase more than twice the screen wide. Both want width the
     phone has not got, so the composition turns: two columns instead of
     three, tiles in the source's own 16:9 rather than a portrait crop, and
     the word bed divided into twelve rows instead of five. */
  .marquee { --rows: 12; }
  .marquee__row:nth-child(n + 6) { display: block; }

  .tour__grid {
    grid-template-columns: repeat(2, 1fr);
    /* rows come from the tiles now, and the stack centres in the stage */
    grid-template-rows: none;
    grid-auto-rows: auto;
    align-content: center;
    height: 100%;
    --tour-zoom: 1.6;
  }
  /* height from the aspect ratio, not from a stretched row */
  .tour__grid img { height: auto; aspect-ratio: 16 / 9; }
  /* four tiles above, four below, the dashboard across the middle */
  .tour__hero-shot { grid-column: 1 / -1; }
  /* the mask is for the bottom row; in two columns that is the last four */
  .tour__grid img:nth-child(n + 7) { -webkit-mask-image: none; mask-image: none; }
  .tour__grid img:nth-child(n + 8) {
    -webkit-mask-image: var(--tile-fade);
    mask-image: var(--tile-fade);
  }

  /* Ten coins have to sit round the ring without stacking up. At 0.28 of a
     phone's width the circle was 225px across carrying 500px of coin — nearly
     twice the crowding a wide screen gets — so open the ring out and bring the
     coins down to match. */
  .wheel { --wheel-r: 0.36; }
  .orbit { --dot: clamp(34px, 10.5vw, 52px); }
  /* vw, not %. The percentage resolved against .wheel__ring, which is a
     zero-size origin point — so this computed to max-width: 0 and collapsed the
     headline and its paragraph into a one-word-per-line column. */
  .wheel__center { max-width: 84vw; }

  /* Everything a notch down from the desktop scale, and a tighter vertical
     rhythm. The two full-width buttons are a big pair of bars on a phone, and
     the stock desktop sizes let the whole block read as oversized. */
  .hero__title { margin-top: 18px; }
  .hero__lede { font-size: 14.5px; margin-top: 14px; }
  .hero__cta { gap: 10px; margin-top: 20px; }
  .hero__cta .btn { width: 100%; }
  .hero__cta .btn,
  .hero__trust .btn { height: 40px; font-size: 13px; }
  .hero__trust { gap: 10px 18px; margin-top: 18px; font-size: 12px; }
  .hero__trust svg { width: 16px; height: 16px; }
  .hero__trust li:first-child svg { width: 17px; height: 17px; }
}

/* On the narrowest screens the headline's first half is wider than the text
   column, so the authored break would push a word or two onto a line of their
   own. Let it wrap where it falls instead. */
@media (max-width: 480px) {
  .modules__title br { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .anim, .type-line__text, .type-caret,
  .hero__media, .site-header__pill { animation: none !important; }
  .type-line__text { width: auto; }
  .type-caret { opacity: 0; }
  html.js .reveal { opacity: 1; transform: none; transition: none; }
  .site-header { transition: none; }
}


/* ============================================================
   Download page
   ============================================================ */
.dl { padding: clamp(96px, 14vh, 168px) 0 clamp(72px, 10vh, 120px); }

.dl__inner { max-width: 880px; }

.dl__title {
  color: var(--heading);
  font-weight: 300;
  font-size: clamp(34px, 5.2vw, 60px);
  line-height: 1.06;
  letter-spacing: -0.02em;
  margin: 0 0 18px;
}

.dl__lede {
  color: var(--muted);
  font-weight: 200;
  font-size: clamp(15px, 1.35vw, 19px);
  line-height: 1.62;
  max-width: 60ch;
  margin: 0 0 clamp(36px, 5vw, 54px);
}

/* Two equal cards, stacking on narrow screens. Neither is ever hidden —
   the recommendation is a hint on top of a list that works without it. */
.dl__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(268px, 1fr));
  gap: 16px;
}

.dl__card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 26px 24px 22px;
  border-radius: 16px;
  text-decoration: none;
  color: var(--white);
  background: rgba(255, 255, 255, 0.028);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.11);
  transition: background 260ms var(--ease-out),
              box-shadow 260ms var(--ease-out),
              transform 260ms var(--ease-out);
}
.dl__card:hover {
  background: rgba(255, 255, 255, 0.055);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.24);
  transform: translateY(-2px);
}
.dl__card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.dl__card.is-recommended {
  background: rgba(208, 76, 99, 0.07);
  box-shadow: inset 0 0 0 1px rgba(208, 76, 99, 0.5);
}
.dl__card.is-recommended:hover {
  background: rgba(208, 76, 99, 0.11);
  box-shadow: inset 0 0 0 1px rgba(208, 76, 99, 0.72);
}

.dl__badge {
  position: absolute;
  top: -9px; left: 22px;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--black);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
/* The script un-hides this; without JS it never appears, which is correct. */
.dl__badge[hidden] { display: none; }

.dl__chip {
  color: var(--accent);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
}

.dl__name {
  color: var(--heading);
  font-size: clamp(17px, 1.5vw, 20px);
  font-weight: 400;
  letter-spacing: -0.01em;
}

.dl__meta {
  color: var(--muted);
  font-size: 13.5px;
  font-weight: 200;
}

.dl__go {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  color: var(--white);
  font-size: 14px;
  font-weight: 400;
}
.dl__go svg { width: 17px; height: 17px; flex: none; }

.dl__version {
  margin: 20px 0 0;
  color: var(--muted);
  font-size: 13px;
  font-weight: 200;
}

.dl__notes {
  list-style: none;
  margin: clamp(40px, 6vw, 58px) 0 0;
  padding: 0;
  display: grid;
  gap: 18px;
}
.dl__notes li {
  display: flex;
  gap: 13px;
  align-items: flex-start;
  color: var(--muted);
  font-size: 14.5px;
  font-weight: 200;
  line-height: 1.58;
}
.dl__notes svg { width: 19px; height: 19px; flex: none; margin-top: 2px; color: var(--white); }
.dl__notes strong { color: var(--heading); font-weight: 400; }

.dl__notmac,
.dl__browser {
  margin: 30px 0 0;
  color: var(--muted);
  font-size: 14px;
  font-weight: 200;
  line-height: 1.6;
}
.dl__notmac[hidden] { display: none; }
.dl__notmac a,
.dl__browser a { color: var(--white); text-decoration: underline; text-underline-offset: 3px; }
.dl__notmac a:hover,
.dl__browser a:hover { color: var(--accent); }

@media (prefers-reduced-motion: reduce) {
  .dl__card, .dl__card:hover { transition: none; transform: none; }
}
