/*
  biblebison.com

  Dark, warm, typographic. The palette, the serif and the ember gradient are lifted straight from the app, so
  the page and the product look like the same object.

  The motion is all CSS scroll-driven animation (`animation-timeline: view()`), so there is no scroll listener,
  no observer, and nothing to run on the main thread while the user is scrolling. Everything degrades to
  "already visible" where that is unsupported, and everything is switched off under `prefers-reduced-motion`.
*/

@property --sweep {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

:root {
  color-scheme: dark;

  --base: #100d0c;
  --raise: #171312;
  --surface: #1f1a17;
  --line: #2e2722;
  --parchment: #f3ebdd;
  --muted: #b6ab9b;
  /* 5.06:1 on the page ground. The previous value read better but sat at 4.06, under the 4.5 that
     small text has to clear, and every use of it is real copy: the marquee, the dates, the CTA notes. */
  --faint: #8d8478;
  --ember: #e0a35c;
  --ember-deep: #c07f33;
  --ember-grad: linear-gradient(160deg, #edbb7c, #c07f33);

  --serif: ui-serif, "New York", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, system-ui, sans-serif;

  --gut: clamp(1.25rem, 4vw, 2.5rem);
  --rhythm: clamp(4.25rem, 9vw, 7.5rem);
  --maxw: 76rem;
}

* { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  /* The sticky nav is 64px; anchors must not land underneath it. */
  scroll-padding-top: 5.5rem;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--base);
  color: var(--parchment);
  font: 400 clamp(1rem, 0.96rem + 0.2vw, 1.125rem)/1.65 var(--sans);
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}

img { max-width: 100%; display: block; }
a { color: inherit; }

::selection { background: color-mix(in oklab, var(--ember) 40%, transparent); color: #fff; }

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

/* ---------- The ground: a warm mesh and a film grain over everything ---------- */

.ground {
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background:
    radial-gradient(58rem 42rem at 12% -8%, color-mix(in oklab, var(--ember) 22%, transparent), transparent 62%),
    radial-gradient(44rem 38rem at 92% 8%, color-mix(in oklab, #8a4f1d 26%, transparent), transparent 60%),
    radial-gradient(52rem 46rem at 50% 108%, color-mix(in oklab, var(--ember-deep) 16%, transparent), transparent 62%),
    var(--base);
}

/* Grain keeps the large flat fields from banding on OLED, and is the single cheapest way to stop a dark
   page looking like flat CSS. Generated in an SVG filter rather than shipped as an image. */
.grain {
  position: fixed;
  inset: -50%;
  z-index: -1;
  pointer-events: none;
  /*
    Barely there on purpose. Grain is here to stop the large dark fields banding on OLED, and nothing else.
    At the strength this started on it stopped reading as texture and started reading as compression noise,
    which made every sharp asset on the page look like a bad JPEG.
  */
  opacity: 0.16;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23n)' opacity='0.3'/%3E%3C/svg%3E");
}

/* ---------- Shell ---------- */

.wrap { width: min(100% - var(--gut) * 2, var(--maxw)); margin-inline: auto; }

section { padding-block: var(--rhythm); position: relative; }

.eyebrow {
  font: 600 0.72rem/1.5 var(--sans);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ember);
  display: flex;
  /* Not `center`: when the label wraps to two lines on a phone, a centred rule floats into the gap
     between them. This keeps it on the first line where it reads as a lead-in. */
  align-items: flex-start;
  gap: 0.7rem;
  margin: 0 0 1.4rem;
}
.eyebrow::before {
  content: "";
  flex: none;
  width: 1.6rem;
  height: 1px;
  /* Half a line-height down, so it sits on the optical centre of the first line. */
  margin-top: 0.54em;
  background: var(--ember);
  opacity: 0.7;
}

h1, h2, h3 { font-family: var(--serif); font-weight: 600; letter-spacing: -0.022em; margin: 0; }
h1 { font-size: clamp(2.9rem, 1.4rem + 6.4vw, 5.9rem); line-height: 0.98; }
h2 { font-size: clamp(2.1rem, 1.2rem + 3.6vw, 3.6rem); line-height: 1.06; text-wrap: balance; }
h3 { font-size: clamp(1.2rem, 1.05rem + 0.6vw, 1.45rem); line-height: 1.24; }

.lead {
  font-size: clamp(1.1rem, 1rem + 0.55vw, 1.4rem);
  line-height: 1.6;
  color: var(--muted);
  max-width: 40ch;
  text-wrap: pretty;
  margin: 1.5rem 0 0;
}

p { text-wrap: pretty; }

/* ---------- Editorial split ---------- */

/*
  Heading on the left, the argument on the right.

  A single centred column left the right half of every text-only section empty at desktop widths, which read
  as an unfinished page rather than as breathing room. The measure stays readable because the body column is
  its own track rather than a max-width inside a full-width one.
*/
.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: start;
}
.split-head { position: sticky; top: 6rem; }
.split-body > p { color: var(--muted); margin: 0 0 1.1rem; max-width: 46ch; }
.split-body > p:last-child { margin-bottom: 0; }
.split-body .lead { margin-top: 0; color: var(--parchment); }
.split .eyebrow { margin-bottom: 1.1rem; }

@media (max-width: 62rem) {
  .split { grid-template-columns: 1fr; gap: 1.6rem; }
  .split-head { position: static; }
}

/* ---------- Nav ---------- */

.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  background: color-mix(in oklab, var(--base) 72%, transparent);
  border-bottom: 1px solid transparent;
  transition: border-color 0.3s, background 0.3s;
}
/* Scroll-driven: the rule only bites once the page has actually moved. */
@supports (animation-timeline: scroll()) {
  .nav {
    animation: nav-settle linear both;
    animation-timeline: scroll(root block);
    animation-range: 0 8rem;
  }
  @keyframes nav-settle {
    to {
      border-bottom-color: var(--line);
      background: color-mix(in oklab, var(--base) 88%, transparent);
    }
  }
}
.nav-in {
  display: flex;
  align-items: center;
  gap: 1rem;
  height: 4rem;
}
.brand {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  /* The mark is 34px; the target needs 44. */
  padding: 0.35rem 0.5rem 0.35rem 0;
  margin-inline-start: -0.1rem;
  text-decoration: none;
  font: 600 1.02rem/1 var(--serif);
  letter-spacing: -0.01em;
  margin-inline-end: auto;
}
.brand img {
  width: 32px;
  height: 32px;
  border-radius: 9px;
  box-shadow: 0 0 0 1px var(--line), 0 6px 18px -8px #000;
}
.nav-links { display: flex; gap: 0.4rem; }
.nav-links a {
  font: 500 0.92rem/1 var(--sans);
  color: var(--muted);
  text-decoration: none;
  transition: color 0.2s, background 0.2s;
  /* The text is 15px tall; the target has to be at least 24, and there is room here for a comfortable 40. */
  padding: 0.75rem 0.85rem;
  border-radius: 10px;
}
.nav-links a:hover { background: color-mix(in oklab, var(--surface) 70%, transparent); }
.nav-links a:hover { color: var(--parchment); }
@media (max-width: 46rem) { .nav-links { display: none; } }

/* ---------- Buttons ---------- */

.btn {
  --pad: 0.95rem 1.6rem;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: var(--pad);
  border-radius: 999px;
  font: 600 0.97rem/1 var(--sans);
  text-decoration: none;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform 0.22s cubic-bezier(.2,.7,.3,1), box-shadow 0.22s, background 0.22s, border-color 0.22s;
  white-space: nowrap;
}
.btn-ember {
  background: var(--ember-grad);
  color: #191310;
  box-shadow: 0 10px 34px -12px color-mix(in oklab, var(--ember) 75%, transparent);
}
.btn-ember:hover { transform: translateY(-2px); box-shadow: 0 16px 44px -12px color-mix(in oklab, var(--ember) 85%, transparent); }
.btn-quiet {
  background: color-mix(in oklab, var(--surface) 80%, transparent);
  border-color: var(--line);
  color: var(--parchment);
}
.btn-quiet:hover { border-color: color-mix(in oklab, var(--ember) 55%, var(--line)); transform: translateY(-2px); }
.btn small { font-weight: 500; opacity: 0.72; font-size: 0.8rem; }

.cta-row { display: flex; flex-wrap: wrap; gap: 0.8rem; align-items: center; margin-top: 2.4rem; }
.cta-note { font-size: 0.85rem; color: var(--faint); margin: 1rem 0 0; }

@media (max-width: 34rem) {
  /* Stacked buttons at two different widths read as a mistake; full width reads as a choice. */
  .cta-row { flex-direction: column; align-items: stretch; }
  .cta-row .btn { justify-content: center; }
}

/* ---------- Hero ---------- */

.hero { padding-block: clamp(3.5rem, 9vw, 7rem) clamp(3rem, 7vw, 5rem); }
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
}
@media (max-width: 62rem) {
  .hero-grid { grid-template-columns: 1fr; }
  /*
    The headline stays first on a narrow screen. Leading with the art meant a phone-sized visitor got a
    picture of a phone and had to scroll to find out what the product was.
  */
  .hero-art { min-height: 0; margin-top: 1.25rem; }
  .hero-art .phone { width: min(82%, 18.5rem); }
  .hero { padding-block: 2.5rem 3rem; }
}

h1 .soft { color: var(--muted); display: block; }

/* The mark behind the phone: a slow ember sweep, purely decorative. */
.hero-art {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 30rem;
  /*
    The bison watermark is deliberately wider than this box, and on a narrow screen that spilled past the
    viewport and gave the whole page a horizontal scrollbar. `clip` contains it without creating a scroll
    container (which `hidden` would, breaking the sticky nav), and the clip margin lets the phone's glow
    still bleed out of the edges.
  */
  overflow: clip;
  overflow-clip-margin: 2.5rem;
}
.halo {
  position: absolute;
  inset: 8% 2%;
  border-radius: 50%;
  background: conic-gradient(from var(--sweep), transparent 0deg, color-mix(in oklab, var(--ember) 34%, transparent) 90deg, transparent 200deg);
  filter: blur(48px);
  animation: sweep 14s linear infinite;
  z-index: 0;
}
@keyframes sweep { to { --sweep: 360deg; } }

/* ---------- Phone ---------- */

.phone {
  position: relative;
  z-index: 1;
  width: min(100%, 19rem);
  aspect-ratio: 1206 / 2622;
  border-radius: 13% / 6%;
  padding: 0.55rem;
  background: linear-gradient(150deg, #38302a, #17120f 42%, #0a0807 70%, #2c2520);
  box-shadow:
    0 0 0 1px #000,
    0 2px 1px rgba(255, 255, 255, 0.12) inset,
    0 50px 90px -40px #000,
    0 20px 50px -30px color-mix(in oklab, var(--ember) 40%, transparent);
}
.phone img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 11.5% / 5.4%;
  background: var(--base);
}
/* The screen's own glass highlight. */
.phone::after {
  content: "";
  position: absolute;
  inset: 0.55rem;
  border-radius: 11.5% / 5.4%;
  pointer-events: none;
  background: linear-gradient(145deg, rgba(255,255,255,0.1), transparent 34%);
}

@supports (animation-timeline: view()) {
  .hero-art .phone {
    animation: hero-rise linear both;
    animation-timeline: view(block);
    animation-range: entry 0% cover 46%;
  }
  @keyframes hero-rise {
    from { transform: translateY(2.5rem) rotate(-2.5deg) scale(0.94); }
    to { transform: none; }
  }
}

/* ---------- Reveal (used by every section below the fold) ---------- */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /*
      Only the cards move, and only they are allowed to.

      Two earlier attempts at a section-level scroll reveal both failed the same way: an `entry`-phase range
      does not resolve on an element taller than the viewport, so the section sat at its `from` keyframe
      forever. With opacity that meant invisible text. With transform it meant every section permanently
      offset by 32px AND permanently promoted to its own composited layer, rasterised at a fractional device
      pixel (this display runs at 1.8x), which is what made the type look soft everywhere.

      So nothing that holds body text carries a scroll-driven transform now. Cards are comfortably shorter
      than the viewport, so their range completes and they land on `transform: none`, back on the pixel grid
      and back to crisp text.
    */
    .cell, .slide, .voice, .nevers li {
      animation: rise linear both;
      animation-timeline: view(block);
      animation-range: entry 0% entry 85%;
    }
    @keyframes rise {
      from { transform: translateY(1.4rem); }
      to { transform: none; }
    }
  }
}

/* ---------- Marquee ---------- */

.strip {
  border-block: 1px solid var(--line);
  background: color-mix(in oklab, var(--raise) 60%, transparent);
  padding-block: 1.1rem;
  overflow: clip;
  /* Fade both ends so the loop seam is never the thing you look at. */
  mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
}
.marquee { display: flex; width: max-content; gap: 0; animation: slide 46s linear infinite; }
.marquee ul {
  display: flex;
  align-items: center;
  gap: 2.6rem;
  margin: 0;
  padding: 0 1.3rem;
  list-style: none;
  font: 500 0.82rem/1 var(--sans);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--faint);
  white-space: nowrap;
}
.marquee li { display: flex; align-items: center; gap: 2.6rem; }
.marquee li::after { content: "◆"; color: color-mix(in oklab, var(--ember) 60%, transparent); font-size: 0.55rem; }
@keyframes slide { to { transform: translateX(-50%); } }
.strip:hover .marquee { animation-play-state: paused; }

/* ---------- Bento ---------- */

.bento {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: clamp(0.75rem, 1.4vw, 1.1rem);
  margin-top: clamp(2.5rem, 5vw, 4rem);
}
.cell {
  grid-column: span 2;
  container-type: inline-size;
  position: relative;
  padding: clamp(1.4rem, 2.4vw, 2rem);
  border-radius: 26px;
  border: 1px solid var(--line);
  background:
    radial-gradient(24rem 18rem at 100% 0%, color-mix(in oklab, var(--ember) 9%, transparent), transparent 70%),
    color-mix(in oklab, var(--surface) 72%, transparent);
  overflow: clip;
  transition: border-color 0.3s, transform 0.3s cubic-bezier(.2,.7,.3,1), background 0.3s;
}
.cell:hover { border-color: color-mix(in oklab, var(--ember) 42%, var(--line)); transform: translateY(-4px); }
.cell.wide { grid-column: span 3; }
.cell.tall { grid-column: span 3; grid-row: span 2; }
@media (max-width: 62rem) { .cell, .cell.wide, .cell.tall { grid-column: span 6; grid-row: auto; } }

.cell h3 { margin-bottom: 0.6rem; }
.cell p { color: var(--muted); margin: 0; font-size: 0.97rem; line-height: 1.62; }
.glyph {
  width: 2.6rem;
  height: 2.6rem;
  display: grid;
  place-items: center;
  border-radius: 13px;
  margin-bottom: 1.1rem;
  background: color-mix(in oklab, var(--ember) 15%, transparent);
  border: 1px solid color-mix(in oklab, var(--ember) 28%, transparent);
  color: var(--ember);
}
.glyph svg { width: 1.25rem; height: 1.25rem; }

/*
  The screenshot inside the tall cell is a real phone that the card clips at the bottom edge, rather than the
  whole screen shown end to end. At full length it forced a 1600px grid row and the two cells beside it
  stretched to match, leaving three short paragraphs floating in an acre of empty card. Clipped, it reads as
  a device standing in the card, and the row height comes back to something the other cells can live with.
*/
.cell.tall { padding-bottom: 0; }
.cell.tall .shot {
  margin: 1.8rem auto 0;
  width: min(100%, 15.5rem);
  height: clamp(13rem, 22vw, 19rem);
  overflow: clip;
}
@media (max-width: 62rem) {
  /* One column here, so the card is full width and the device can be shown at a readable size. */
  .cell.tall .shot { width: min(100%, 18rem); height: clamp(15rem, 52vw, 21rem); }
}
.cell.tall .shot .phone {
  width: 100%;
  /* Taller than its box on purpose: the card crops the bottom. */
  height: auto;
  aspect-ratio: 1206 / 2622;
}

/* Cards size to their own content; only the tall one drives the row height. */
.bento { align-items: start; }
.cell.tall { align-self: stretch; }
.cell.wide, .cell:not(.tall) { align-self: stretch; }

/* ---------- Gallery ---------- */

.gallery {
  display: flex;
  gap: clamp(1rem, 2.5vw, 2rem);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /*
    Full bleed, but the first slide still has to line up with the headline above it. `.wrap` is centred at
    `--maxw`, so its left edge is half the leftover width, and below that width it is just the gutter.
  */
  padding-block: 2.5rem 3rem;
  padding-inline: max(var(--gut), (100vw - var(--maxw)) / 2);
  scrollbar-width: none;
  overscroll-behavior-x: contain;
}
.gallery::-webkit-scrollbar { display: none; }
.slide { scroll-snap-align: center; flex: 0 0 auto; width: min(72vw, 18rem); }

/*
  On a phone the cards were 281px inside a 390px viewport, which made the app's own type inside each
  screenshot too small to read and left the first card sitting at an arbitrary 55px offset (a centred snap
  needs half the leftover width as padding before the first card can actually reach the centre).

  So on mobile one card fills most of the screen, and the gutters are computed from the card width instead
  of from the page gutter, which makes the snap land exactly and the first card start centred.
*/
@media (max-width: 48rem) {
  .slide { width: min(86vw, 21rem); }
  .gallery {
    padding-inline: calc((100vw - min(86vw, 21rem)) / 2);
    gap: 1.25rem;
  }
  .slide figcaption { margin-top: 1rem; }
}
.slide figcaption {
  margin-top: 1.2rem;
  font-size: 0.92rem;
  color: var(--muted);
  text-align: center;
}
.slide figcaption b { display: block; color: var(--parchment); font: 600 1.02rem/1.3 var(--serif); margin-bottom: 0.22rem; }

/* ---------- Voices ---------- */

/* Exactly three across, because the roster is six: an auto-fill track gave four and left two holes. */
.voices {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.7rem;
  margin-top: clamp(2.5rem, 5vw, 4rem);
}
@media (max-width: 62rem) { .voices { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 34rem) { .voices { grid-template-columns: 1fr; } }
.voice {
  padding: 1.05rem 1.2rem;
  border-radius: 17px;
  border: 1px solid var(--line);
  background: color-mix(in oklab, var(--surface) 52%, transparent);
  transition: border-color 0.25s, background 0.25s;
}
.voice:hover { border-color: color-mix(in oklab, var(--ember) 40%, var(--line)); }
.voice b { display: block; font: 600 1.05rem/1.25 var(--serif); }
.voice span { display: block; font-size: 0.82rem; color: var(--faint); margin-top: 0.2rem; }
.note {
  margin-top: 2rem;
  margin-inline-end: auto;
  padding: 1.2rem 1.4rem;
  border-inline-start: 2px solid color-mix(in oklab, var(--ember) 55%, transparent);
  background: color-mix(in oklab, var(--raise) 60%, transparent);
  border-radius: 0 14px 14px 0;
  color: var(--muted);
  font-size: 0.93rem;
  max-width: 76ch;
}

/* ---------- Privacy list ---------- */

/* Inside the split it is already a column, so it sets no max-width of its own. */
.nevers { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.55rem; }
.nevers li {
  display: flex;
  gap: 0.9rem;
  align-items: flex-start;
  padding: 1rem 1.2rem;
  border-radius: 15px;
  background: color-mix(in oklab, var(--surface) 45%, transparent);
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.97rem;
}
.nevers svg { width: 1.15rem; height: 1.15rem; flex: none; margin-top: 0.16rem; color: var(--ember); }
.nevers b { color: var(--parchment); font-weight: 600; }

/* ---------- Verse rule ---------- */

.verse {
  text-align: center;
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(1.5rem, 1rem + 2.4vw, 2.6rem);
  line-height: 1.34;
  color: var(--parchment);
  max-width: 24ch;
  margin-inline: auto;
  text-wrap: balance;
}
.verse cite {
  display: block;
  margin-top: 1.6rem;
  font: 600 0.74rem/1 var(--sans);
  font-style: normal;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ember);
}

/* ---------- Closing ---------- */

.close-card {
  border-radius: 34px;
  border: 1px solid color-mix(in oklab, var(--ember) 26%, var(--line));
  padding: clamp(2.5rem, 6vw, 5rem);
  text-align: center;
  background:
    radial-gradient(38rem 22rem at 50% 0%, color-mix(in oklab, var(--ember) 16%, transparent), transparent 70%),
    color-mix(in oklab, var(--raise) 70%, transparent);
}
.close-card .cta-row { justify-content: center; }

footer {
  border-top: 1px solid var(--line);
  padding-block: 2.6rem 3.4rem;
  color: var(--faint);
  font-size: 0.87rem;
}
.foot-in { display: flex; flex-wrap: wrap; gap: 1rem 2rem; align-items: center; }
.foot-in nav { display: flex; gap: 0.3rem; margin-inline-start: auto; }
.foot-in nav a { padding: 0.6rem 0.7rem; border-radius: 9px; }
footer a { text-decoration: none; transition: color 0.2s; }
footer a:hover { color: var(--parchment); }

/* ---------- Motion off ---------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .halo { display: none; }
}

/* ---------- The mark ---------- */

/* The bison and cross lifted out of the app icon, sitting behind the phone as a watermark. It is the
   one piece of the brand that is not type, so it is allowed to be big. */
.bison-mark {
  position: absolute;
  z-index: 0;
  width: min(128%, 44rem);
  max-width: none;
  opacity: 0.12;
  filter: drop-shadow(0 0 60px color-mix(in oklab, var(--ember) 40%, transparent));
  pointer-events: none;
  transform: translateY(6%);
  /*
    Faded at the edges rather than cut. `.hero-art` clips its overflow so the mark cannot give the page a
    horizontal scrollbar, but a hard clip on a watermark draws a visible rectangle around the hero. The mask
    makes it end in nothing, so the clip edge never has anything left to cut.
  */
  mask-image: radial-gradient(closest-side at 50% 45%, #000 45%, rgba(0, 0, 0, 0.35) 72%, transparent 100%);
}
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .bison-mark {
      /* Drifts a little slower than the page, so the hero has depth without a scroll listener. */
      animation: bison-drift linear both;
      animation-timeline: view(block);
      animation-range: cover 0% cover 100%;
    }
    @keyframes bison-drift {
      from { transform: translateY(12%) scale(1.06); }
      to { transform: translateY(-6%) scale(1); }
    }
  }
}

.brand img { border-radius: 0; box-shadow: 0 4px 14px -6px #000; }

.close-icon {
  width: 112px;
  height: 112px;
  margin: 0 auto 1.8rem;
  border-radius: 25px;
  box-shadow: 0 18px 50px -18px #000, 0 0 0 1px color-mix(in oklab, var(--ember) 22%, transparent);
}

.foot-brand { display: inline-flex; align-items: center; gap: 0.6rem; }
.foot-brand img { border-radius: 7px; opacity: 0.8; }

/* ---------- Document pages (privacy, terms, support) ---------- */

.doc { max-width: 46rem; padding-block: clamp(3rem, 7vw, 5rem) clamp(4rem, 9vw, 7rem); }
.doc h1 { font-size: clamp(2.2rem, 1.4rem + 3vw, 3.4rem); margin-bottom: 0.6rem; }
.doc .updated { color: var(--faint); font-size: 0.9rem; margin: 0 0 2.5rem; }
.doc h2 {
  font-size: clamp(1.35rem, 1.1rem + 1vw, 1.75rem);
  margin: 3rem 0 0.9rem;
  padding-top: 1.6rem;
  border-top: 1px solid var(--line);
}
.doc h2:first-of-type { border-top: 0; padding-top: 0; }
.doc h3 { font-size: 1.08rem; margin: 1.8rem 0 0.5rem; color: var(--ember); }
.doc p, .doc li { color: var(--muted); line-height: 1.72; }
.doc p { margin: 0 0 1.05rem; }
.doc ul, .doc ol { margin: 0 0 1.2rem; padding-inline-start: 1.3rem; }
.doc li { margin-bottom: 0.5rem; }
.doc li::marker { color: var(--ember); }
.doc strong, .doc b { color: var(--parchment); font-weight: 600; }
.doc a { color: var(--ember); text-underline-offset: 3px; }
.doc code {
  font: 500 0.9em/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  background: color-mix(in oklab, var(--surface) 85%, transparent);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 0.18em 0.42em;
}
.doc table { width: 100%; border-collapse: collapse; margin: 0 0 1.4rem; font-size: 0.94rem; }
.doc th, .doc td { text-align: left; padding: 0.7rem 0.8rem; border-bottom: 1px solid var(--line); vertical-align: top; }
.doc th { color: var(--parchment); font-weight: 600; font-size: 0.82rem; letter-spacing: 0.06em; text-transform: uppercase; }
.doc td { color: var(--muted); }
/* Wide tables scroll inside their own box rather than widening the page. */
.doc .scroller { overflow-x: auto; margin-bottom: 1.4rem; }
.doc .scroller table { margin-bottom: 0; }

.callout {
  padding: 1.2rem 1.4rem;
  border-radius: 16px;
  border: 1px solid color-mix(in oklab, var(--ember) 30%, var(--line));
  background: color-mix(in oklab, var(--raise) 70%, transparent);
  margin: 0 0 1.6rem;
}
.callout p:last-child { margin-bottom: 0; }

.toc { margin: 0 0 2.5rem; padding: 1.2rem 1.4rem; border-radius: 16px; background: color-mix(in oklab, var(--surface) 50%, transparent); border: 1px solid var(--line); }
.toc ol { margin: 0; padding-inline-start: 1.2rem; }
.toc li { margin-bottom: 0.35rem; }
.toc a { color: var(--muted); text-decoration: none; }
.toc a:hover { color: var(--ember); }
