@font-face {
  font-family: 'Sohne';
  src: url('fonts/soehne-buch.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

/* Same file, with tabular figures baked into the face. Canvas has no way to
   ask for an OpenType feature at draw time, but it honours one the @font-face
   declares — this is the only route to tabular numerals on a canvas. */
@font-face {
  font-family: 'SohneTabular';
  src: url('fonts/soehne-buch.woff2') format('woff2');
  font-feature-settings: 'tnum' 1;
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Signifier';
  src: url('fonts/signifier-regular.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

/* One motion system — the symbol's entrance reads --dur-enter from here too,
   so duration and easing are defined in a single place. */
:root {
  --dur-enter: 900ms;
  --ease-enter: cubic-bezier(0.33, 1, 0.68, 1); /* easeOutCubic */
  --stagger: 110ms;

  /* Price wall. The script reads these, so the breakpoint below can retune
     the whole field without touching JS. --field-wave is per pixel: higher
     means smaller wave shapes. */
  --field-size: 12px;
  --field-row: 24px;
  --field-gap: 12px;
  --field-wave: 0.0024;

  /* How much height the symbol and price wall get before the copy starts.
     Only used on stacked layouts, where the copy follows them in flow. */
  --hero-visual: 55vh;
}

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

@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

@keyframes fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

body {
  background: #131313;
  color: #edeae2;
  font-family: 'Sohne', system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}

#hero {
  position: relative;
  height: 100vh;
}

/* Five columns at 40px margin and gutter; the outer two stay empty. */
#grid {
  position: absolute;
  left: 40px;
  right: 40px;
  bottom: 80px;
  z-index: 2;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 40px;
  align-items: start;
}

.col {
  grid-column: span 1;
  animation: rise var(--dur-enter) var(--ease-enter) backwards;
}

.col:nth-child(1) { animation-delay: 240ms; }
.col:nth-child(2) { animation-delay: calc(240ms + var(--stagger)); }
.col:nth-child(3) { animation-delay: calc(240ms + var(--stagger) * 2); }

.col:first-child {
  grid-column-start: 2;
}

h1, h2 {
  font-size: 20px;
  font-weight: normal;
  line-height: 0.95;
}

.sub {
  font-family: 'Signifier', Georgia, serif;
  font-size: 20px;
  line-height: 0.95;
  opacity: 0.4;
  margin-top: 7px;
}

.col p:not(.sub) {
  font-size: 14px;
  line-height: normal;
  color: rgba(255, 255, 255, 0.4);
}

.sub + p {
  margin-top: 50px;
}

.col p:not(.sub) + p:not(.sub) {
  margin-top: 1em;
}

.cta {
  display: inline-block;
  margin-top: 50px;
  font-size: 14px;
  color: #edeae2;
  text-decoration: none;
  border-bottom: 1px solid rgba(255, 255, 255, 0.18);
  padding-bottom: 3px;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.cta span {
  font-size: 0.75em;
  margin-left: 0.5em;
}

.cta:hover {
  color: #edeae2;
  border-color: rgba(237, 234, 226, 0.5);
}

/* A field of prices sitting just above the background. The cursor acts as a
   lens: figures near it resolve, the rest stay submerged. Masked away before
   it reaches the copy columns so it never competes with them. */
#field {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  will-change: transform;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 14%, #000 40%, transparent 66%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 14%, #000 40%, transparent 66%);
}

#field canvas {
  display: block;
}

/* The scroll parallax translates this box down past the hero, so without
   opting out of hit testing it swallows clicks on whatever it overhangs.
   Nothing here needs events — the symbol listens on window. */
#scene {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  will-change: transform;
}

#scene canvas {
  display: block;
  touch-action: none;
}

/* Menu in column 2, phone centred on column 3 — it is wider than the track
   and deliberately overhangs, as in the comp. */
/* Top padding makes up the difference against the hero's 80px bottom inset,
   so the caption sits the same distance from the copy as from the phone. */
#preview {
  padding: 60px 40px 160px;
}

/* Menu sits on column 2; the phone centres across columns 3 and 4, so the
   pair fills the content columns without leaving one empty. */
.showcase {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 40px;
  align-items: center;
}

.stack {
  grid-column: 3 / 5;
  justify-self: center;
  text-align: center;
}

#preview .caption {
  font-family: 'Signifier', Georgia, serif;
  font-size: 20px;
  line-height: 0.95;
  color: #edeae2;
  opacity: 0.4;
  margin-bottom: 140px;
  white-space: nowrap;
}

.screens {
  grid-column: 2;
  list-style: none;
}

/* Each item carries its own screenshot for the stacked carousel. On desktop
   the single stage image does the work, so these stay out of the way —
   lazily, so four images are never fetched for a layout that hides them. */
.screens .shot {
  display: none;
}

.screens li + li {
  margin-top: 44px;
}

/* Every description stays visible; the whole item dims when it isn't
   selected, so title and copy read as one block. */
.screens li {
  opacity: 0.45;
  transition: opacity 0.2s ease;
}

.screens li.active {
  opacity: 1;
}

.screens li:not(.active):hover {
  opacity: 0.75;
}

/* Label and copy live inside the button, so the whole block is the target. */
.screens button {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  cursor: pointer;
  text-align: left;
}

.screens .label {
  display: block;
  font-size: 14px;
  line-height: 0.95;
  color: #edeae2;
}

.screens .note {
  display: block;
  font-size: 14px;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.4);
  margin-top: 8px;
}

.stage {
  display: block;
}

.stage img {
  display: block;
  width: 420px;
  max-width: 100%;
  height: auto;
  transition: opacity 0.25s ease;
}

.stage img.swapping {
  opacity: 0;
}

footer {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 40px;
  margin: 0 40px;
  padding-bottom: 160px;
  align-items: start;
}

/* The rule stops at the content columns rather than running full bleed. */
footer .rule {
  grid-column: 2 / 5;
  grid-row: 1;
  height: 1px;
  background: rgba(255, 255, 255, 0.06);
  /* The grid's 40px row gap already supplies the spacing below. */
}

footer .badge {
  grid-column: 2;
  grid-row: 2;
  display: block;
  width: 120px;
}

footer .badge img {
  display: block;
  width: 100%;
  height: auto;
}

/* Links and copyright share a cell, so their spacing is set between
   themselves rather than by the badge's height across the row. */
footer .legal {
  grid-column: 4;
  grid-row: 2;
  justify-self: end;
  text-align: right;
}

footer nav {
  display: flex;
  justify-content: flex-end;
  gap: 28px;
}

footer nav a {
  font-size: 14px;
  line-height: 0.95;
  color: #edeae2;
  text-decoration: none;
  opacity: 0.4;
  transition: opacity 0.2s ease;
}

footer nav a:hover {
  opacity: 1;
}

footer .copyright {
  margin-top: 16px;
  font-size: 14px;
  line-height: 0.95;
  color: #edeae2;
  opacity: 0.4;
}

/* Sections below the fold arrive as they enter the viewport. */
.reveal {
  opacity: 0;
}

.reveal.seen {
  animation: rise var(--dur-enter) var(--ease-enter) forwards;
}

@media (prefers-reduced-motion: reduce) {
  .reveal.seen {
    animation-name: fade;
  }
}

@media (prefers-reduced-motion: reduce) {
  .col {
    animation-name: fade;
  }
}

/* Document pages (privacy, terms) — the splash pins itself to the viewport,
   these scroll. Both html and body carry .doc so no :has() is needed. */
html.doc, body.doc {
  height: auto;
  overflow: visible;
}

.doc main {
  max-width: 640px;
  margin: 0 auto;
  padding: 120px 40px 160px;
  animation: rise var(--dur-enter) var(--ease-enter) backwards;
  animation-delay: 120ms;
}

.doc h1 {
  font-size: 20px;
  line-height: 0.95;
}

.doc .sub {
  margin-bottom: 80px;
}

.doc h2 {
  font-size: 14px;
  font-weight: normal;
  line-height: 1.4;
  color: #edeae2;
  margin-top: 48px;
  margin-bottom: 10px;
}

.doc p,
.doc li {
  font-size: 14px;
  line-height: 1.65;
  color: rgba(255, 255, 255, 0.66);
}

.doc p + p,
.doc ul + p {
  margin-top: 1em;
}

.doc ul {
  margin-top: 0.75em;
  padding-left: 1.1em;
}

.doc .meta {
  margin-bottom: 80px;
  color: rgba(255, 255, 255, 0.4);
}

.doc strong {
  font-weight: normal;
  color: #edeae2;
}

.doc p a,
.doc li a {
  color: #edeae2;
  text-decoration: none;
  border-bottom: 1px solid rgba(255, 255, 255, 0.25);
  transition: border-color 0.2s ease;
}

.doc p a:hover,
.doc li a:hover {
  border-color: rgba(237, 234, 226, 0.7);
}

.doc li + li {
  margin-top: 0.4em;
}

.doc .home {
  display: inline-block;
  margin-top: 80px;
  font-size: 14px;
  color: rgba(255, 255, 255, 0.4);
  text-decoration: none;
  border-bottom: 1px solid rgba(255, 255, 255, 0.18);
  padding-bottom: 3px;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.doc .home:hover {
  color: #edeae2;
  border-color: rgba(237, 234, 226, 0.5);
}

@media (prefers-reduced-motion: reduce) {
  .doc main {
    animation-name: fade;
  }
}

/* Below the point where three 14px columns stop being readable, the grid
   collapses to one column and the page becomes a normal scrolling document. */
@media (max-width: 900px) {
  :root {
    /* Smaller type and a much tighter wave — at the desktop scale a single
       shape would span the whole phone. */
    --field-size: 9px;
    --field-row: 18px;
    --field-gap: 9px;
    --field-wave: 0.0072;
    --hero-visual: 66vh;
  }

  #hero {
    height: auto;
  }

  /* Stacked, the hero is the symbol plus every column of copy — so the field
     is pinned to the symbol's block and faded out well before the text. */
  #field {
    bottom: auto;
    height: var(--hero-visual);
    -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 14%, #000 42%, transparent 74%);
    mask-image: linear-gradient(to bottom, transparent 0%, #000 14%, #000 42%, transparent 74%);
  }

  #scene {
    position: relative;
    inset: auto;
    height: var(--hero-visual);
  }

  #grid {
    position: static;
    display: block;
    padding: 0 40px 80px;
  }

  .col + .col {
    margin-top: 80px;
  }

  /* Stacked, the columns read as one running block — the desktop gap between
     a subhead and its copy is far too wide at this width. */
  .sub + p {
    margin-top: 20px;
  }

  #preview {
    display: block;
    padding-bottom: 100px;
  }

  /* Caption first, then the carousel — the stage image is redundant here
     because every slide carries its own. */
  .showcase {
    display: flex;
    flex-direction: column;
  }

  .stack {
    order: -1;
    text-align: left;
  }

  .stack .stage {
    display: none;
  }

  /* Swipe rather than tap: one screen per slide, snapping to centre. The
     negative margin lets slides bleed to the screen edge while the padding
     keeps the first and last ones inset. */
  .screens {
    /* Screenshot and copy share one width. The vh term only bites on unusually
       short screens, where it shrinks both together rather than letting the
       image and the copy drift apart. */
    --card: min(240px, 34vh);
    display: flex;
    gap: 24px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    align-items: start;
    margin-inline: -40px;
    margin-bottom: 48px;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

  .screens::-webkit-scrollbar {
    display: none;
  }

  .screens li,
  .screens li:not(.active):hover {
    flex: 0 0 var(--card);
    scroll-snap-align: center;
    opacity: 1;
  }

  .screens li + li {
    margin-top: 0;
  }

  /* Half the leftover width on the outer edges, so the first and last slides
     can travel to the centre like every other one. Both this and the slide
     width resolve against the track's content box, so they stay in step. */
  .screens li:first-child {
    margin-left: calc((100% - var(--card)) / 2);
  }

  .screens li:last-child {
    margin-right: calc((100% - var(--card)) / 2);
  }

  .screens .shot {
    display: block;
    width: 100%;
    height: auto;
    margin: 0 auto 20px;
  }

  /* Same width as the screenshot, and centred with it in the slide. */
  .screens button {
    width: 100%;
    cursor: default;
  }

  #preview .caption {
    margin-bottom: 40px;
    white-space: normal;
  }

  footer {
    display: block;
    margin: 0 40px;
    padding-bottom: 80px;
  }

  footer .rule {
    margin-bottom: 40px;
  }

  /* justify-self applies to block-level boxes in current Chrome, so the
     desktop 'end' keeps shrink-wrapping and right-aligning this without it. */
  footer .legal {
    justify-self: stretch;
    margin-top: 32px;
    text-align: left;
  }

  footer nav {
    justify-content: flex-start;
    gap: 24px;
  }
}
