/* Memono — the app's own palette, one step up in size for a page.
   Tokens match Memono/Design/Theme.swift exactly, light and dark. */

:root {
  --canvas: #FFFFFF;
  --surface: #F1F1F3;
  --panel: #EDEDEF;
  --ink: #121214;
  --ink-soft: #8A8A8E;
  --accent: #121214;
  --on-accent: #FFFFFF;
  --hairline: #E3E3E6;
  /* The die-cut edge StickerRenderer bakes into a cutout. */
  --paper: #FFFFFF;

  /* Shape lock: one radius scale for the page, matching the app's.
     Trays and cards 24, panels inside them 16, small tiles 12,
     anything interactive is a full pill. */
  --r-tray: 24px;
  --r-panel: 16px;
  --r-tile: 12px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --canvas: #121214;
    --surface: #1E1E21;
    --panel: #232328;
    --ink: #EDEDF0;
    --ink-soft: #9C9CA2;
    --accent: #EDEDF0;
    --on-accent: #121214;
    --hairline: #2C2C30;
    --paper: #3E3E44;
  }
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", system-ui, sans-serif;
  font-size: 17px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; }

.wrap {
  width: 100%;
  max-width: 1080px;
  margin-inline: auto;
  padding-inline: 24px;
}

/* ── Bar ─────────────────────────────────────────────────────────── */

.bar {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--canvas);
  border-bottom: 1px solid var(--hairline);
}

.bar-inner {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 60px;
}

.mark {
  width: 34px;
  height: 34px;
  border-radius: 10px;
}

.wordmark {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* ── Sections ────────────────────────────────────────────────────── */

/* The hero is one screen: the words in the middle, the app under them, and a
   ring of your things turning around the pair. */
.hero {
  position: relative;
  min-height: 100dvh;
  overflow: hidden;
  padding-block: 72px 88px;
  display: grid;
  align-content: start;
  justify-items: center;
  text-align: center;
}

.hero-inner {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* A clearing in the middle of the ring. Without it a tile can pass behind a
   line of the headline and take the edge off it. */
.hero-inner::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: 50%;
  top: -8%;
  width: 150%;
  height: 78%;
  transform: translateX(-50%);
  background: radial-gradient(ellipse 46% 50% at 50% 42%, var(--canvas) 42%, transparent 72%);
  pointer-events: none;
}

/* The paper the app lays its pile out on, faded off at the edges so it never
   reads as a border. */
.dots {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-image: radial-gradient(circle at center, var(--hairline) 1.1px, transparent 1.2px);
  background-size: 26px 26px;
  mask-image: radial-gradient(ellipse 62% 58% at 50% 42%, #000 25%, transparent 78%);
  -webkit-mask-image: radial-gradient(ellipse 62% 58% at 50% 42%, #000 25%, transparent 78%);
  pointer-events: none;
}

/* ── The ring ────────────────────────────────────────────────────── */

/* Cut-out things on tiles, set around a circle that turns slowly on its own
   and opens outwards as you scroll into it. Both are transforms on one
   element, so the whole thing runs on the compositor: no layout, no paint,
   no scroll handler. */
.ring {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: grid;
  place-items: center;
  pointer-events: none;
  /* Centred on the phone rather than on the section, so the ring turns
     around the app itself. */
  padding-top: 22vh;
}

.ring-spin {
  position: relative;
  width: 0;
  height: 0;
  --r: min(46vw, 470px);
  --tile: clamp(64px, 7.2vw, 104px);
}

.ring-item {
  position: absolute;
  width: var(--tile);
  height: var(--tile);
  margin: calc(var(--tile) / -2);
  display: grid;
  place-items: center;
  border-radius: 18px;
  background: var(--surface);
  box-shadow: 0 12px 26px -18px rgba(0, 0, 0, 0.4);
  transform: rotate(var(--a)) translateY(calc(var(--r) * var(--rr, 1) * -1));
}

/* Sized off the tile in absolute terms rather than as a percentage of it: a
   percentage height against a container whose height the layout has not
   settled resolves to auto, and a tall cutout then runs out of its tile.
   Each one leans its own way, so the orbit reads as things that were put
   down rather than things that were printed. */
.ring-item img {
  width: calc(var(--tile) * 0.74);
  height: calc(var(--tile) * 0.74);
  object-fit: contain;
  rotate: var(--lean, 0deg);
}

@media (prefers-reduced-motion: no-preference) {
  .ring-spin {
    rotate: 0deg;
    animation: turn 120s linear infinite;
    will-change: rotate, scale;
  }

  @keyframes turn { to { rotate: 360deg; } }

  /* The tiles arrive one after another around the circle, a beat behind the
     headline — the orbit assembling rather than being there already. */
  .ring-item {
    scale: 0;
    animation: pop 0.6s cubic-bezier(0.2, 0.9, 0.3, 1.25) forwards;
    animation-delay: calc(0.5s + var(--n) * 0.045s);
  }

  @keyframes pop { to { scale: 1; } }

  /* The collapse — everything you own drawn in and collected by the app as
     you scroll — is driven by the motion engine in app.js rather than a CSS
     scroll timeline, so it runs in every browser rather than the newest
     ones. The engine writes only `scale` and `opacity`. */
}

/* ── The kinetic band ────────────────────────────────────────────── */

/* The line the app is about, running past, with the things themselves going
   the other way underneath it. Both tracks are one transform each. */
.kinetic {
  position: relative;
  overflow: hidden;
  padding-block: 78px;
  border-block: 1px solid var(--hairline);
  background: var(--panel);
}

/* The sled carries both tracks and shears with scroll velocity — the type
   smearing the way print does when the page is pulled. */
.kinetic-sled { will-change: transform; }

.kinetic-row,
.kinetic-tiles {
  display: flex;
  align-items: center;
  width: max-content;
  will-change: transform;
}

.kinetic-row {
  gap: 52px;
  font-size: clamp(38px, 6.6vw, 84px);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1;
}

.kinetic-row span { white-space: nowrap; }

/* A solid faint fill, not a stroked outline — text-stroke breaks into
   artifacts at the tight joins of heavy weights, the same fault the footer
   wordmark had. */
.kinetic-row .ghost { color: var(--hairline); }

.kinetic-tiles {
  gap: 18px;
  margin-top: 34px;
}

.kinetic-tiles div {
  --cell: 86px;
  display: grid;
  place-items: center;
  width: var(--cell);
  height: var(--cell);
  flex: none;
  border-radius: var(--r-panel);
  background: var(--canvas);
}

.kinetic-tiles img {
  width: calc(var(--cell) * 0.72);
  height: calc(var(--cell) * 0.72);
  object-fit: contain;
}

@media (prefers-reduced-motion: no-preference) {
  .kinetic-row { animation: slideLeft 34s linear infinite; }
  .kinetic-tiles { animation: slideRight 28s linear infinite; }

  /* Half the track is a copy of the other half, so a shift of exactly half
     is seamless whichever clock is driving it. */
  @keyframes slideLeft { to { transform: translate3d(-50%, 0, 0); } }
  @keyframes slideRight { from { transform: translate3d(-50%, 0, 0); } }
}

/* Band: the demo runs the full column width with the words above it. */
.band {
  padding-block: 96px;
}

.band .words {
  max-width: 760px;
  margin-inline: auto;
  text-align: center;
}

.band h2 { font-size: clamp(36px, 5.2vw, 56px); }

.band .sub { margin-inline: auto; }

.band .tray { margin-top: 44px; }

h1 {
  margin: 0;
  font-size: clamp(40px, 5.8vw, 60px);
  font-weight: 700;
  line-height: 1.04;
  letter-spacing: -0.03em;
}

/* Each line sits in a box that clips it, and rises out of it on load. */
.line {
  display: block;
  overflow: hidden;
  padding-bottom: 0.06em;
}

.line > span { display: block; }

/* Once the headline is split into characters, the whole-line rise stands
   down and the letters take over. */
h1.split .line > span {
  animation: none;
  transform: none;
}

.w { display: inline-block; }
.ch { display: inline-block; }

@media (prefers-reduced-motion: no-preference) {
  .line > span {
    transform: translateY(105%);
    animation: rise 0.9s cubic-bezier(0.16, 1, 0.3, 1) forwards;
  }

  .line:nth-child(2) > span { animation-delay: 0.09s; }

  /* Each character surfaces out of its clipped line on its own beat, left
     to right across both lines — the same motion as the whole-line rise,
     at letter resolution. */
  .ch {
    transform: translateY(112%);
    animation: rise 0.7s cubic-bezier(0.16, 1, 0.3, 1) forwards;
    animation-delay: calc(var(--c) * 0.017s);
  }

  @keyframes rise { to { transform: translateY(0); } }

  /* Everything under the headline follows it up. */
  .rise {
    opacity: 0;
    animation: liftIn 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.34s forwards;
  }

  .phone.rise { animation-delay: 0.46s; }

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

h2 {
  margin: 0;
  font-size: clamp(32px, 4.4vw, 46px);
  font-weight: 700;
  line-height: 1.07;
  letter-spacing: -0.028em;
}

.sub {
  margin: 18px 0 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ink-soft);
  max-width: 44ch;
}

.sub.wide { max-width: 60ch; }

.meta {
  margin: 14px 0 0;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink-soft);
}

/* The App Store line. Shaped like the button it becomes the day there is
   something to link to — swap the <p> for an <a href> and it is one. */
.store {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin: 30px 0 0;
  height: 52px;
  padding-inline: 28px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 16px;
  font-weight: 600;
  text-decoration: none;
}

.store.big { margin-top: 32px; }

a.store { transition: transform 0.18s cubic-bezier(0.16, 1, 0.3, 1); }
a.store:active { transform: scale(0.98); }

/* ── The stack ───────────────────────────────────────────────────── */

/* Each demo pins under the bar and holds the screen while it plays, then the
   next slides over it. The offsets leave the edge of the card underneath
   showing, so it reads as a stack rather than a page swap. */
.stack {
  padding-block: 96px 0;
  display: flex;
  flex-direction: column;
}

/* Exactly card 2's bottom margin, no more: a sticky element's constraint
   rectangle is inset by its own bottom margin, and when it releases it
   comes to rest at that constraint's edge. Tail equal to margin means a
   released card lands exactly behind the card that covered it; any excess
   is a sliver of it poking out underneath as they ride away together. */
.stack-tail { height: 42vh; }

.stack-card {
  position: sticky;
  /* Every card pins at the same point, and they share one height, so the
     card sliding in covers the held one edge to edge — a deck being dealt,
     not a pile of mismatched paper. The deck sits at the middle of the
     viewport rather than under the bar, with a floor for short windows. */
  top: max(88px, calc(50svh - 264px));
  /* The gap under a card is how long it holds the screen before the next one
     slides over it. The demos inside run for several seconds, so they get
     most of a screen's worth of scroll each. */
  margin-bottom: 42vh;
}

.stack-card:last-of-type {
  margin-bottom: 0;
  /* The last card never pins. It arrives like the others, sliding over the
     held card, and then simply carries on with the page — the top card of
     the deck being carried away. Pinning it left it sitting alone with
     nothing to cover it, which read as a different animation.

     Relative, not static: it has to paint above the pinned card as one
     piece. Static would split it across paint layers — its positioned tray
     above the deck, its background below. */
  position: relative;
  z-index: 1;
  top: auto;
}

.stack-inner {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: 48px;
  padding: 48px;
  min-height: 480px;
  border-radius: 28px;
  background: var(--canvas);
  border: 1px solid var(--hairline);
  box-shadow: 0 24px 60px -30px rgba(0, 0, 0, 0.35);
}

/* The demo leads on the odd cards, the words on the even ones. */
.stack-card:nth-child(even) .tray { order: -1; }

/* Depth: a held card sinks and dims as the next one slides over it, so the
   stack reads as cards on a desk rather than a page swap. Each card's fall
   is driven by the view timeline of the card covering it. */
@media (min-width: 901px) and (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .stack { timeline-scope: --card2, --card3; }

    .stack-card:nth-child(2) { view-timeline: --card2 block; }
    .stack-card:nth-child(3) { view-timeline: --card3 block; }

    .stack-card:nth-child(1),
    .stack-card:nth-child(2) {
      transform-origin: 50% 0%;
      animation: recede linear both;
      animation-range: entry 0% entry 100%;
    }

    .stack-card:nth-child(1) { animation-timeline: --card2; }
    .stack-card:nth-child(2) { animation-timeline: --card3; }

    /* Scale only, never opacity: a half-transparent card over another card
       reads as a rendering mistake, not as depth. */
    @keyframes recede {
      to { scale: 0.965; }
    }
  }
}

.stack .tray { max-height: 340px; }

/* Entry motion. Sections arrive rather than being there already, which is
   the only thing the page animates that the app itself does not. */
@media (prefers-reduced-motion: no-preference) {
  .reveal {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1),
                transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
  }

  .reveal.shown {
    opacity: 1;
    transform: none;
  }

  /* The tray in a pair comes a beat after its words. */
  .slide .tray.shown,
  .band .tray.shown { transition-delay: 0.08s; }
}

.closing {
  /* Most of the tail exists for card 2's sticky constraint, not for the
     reader; pull the closing up so the gap it leaves reads as ordinary
     section spacing. Both are in flow, so they can never overlap. */
  margin-top: calc(84px - 42vh);
  padding-block: 24px 104px;
  text-align: center;
}

/* The one theme flip on the page: a dark panel whichever mode you are in,
   because the claim it carries is the one to remember. It arrives a touch
   small and swells into place as it reveals. */
@media (prefers-reduced-motion: no-preference) {
  .closing .panel.reveal { transform: translateY(18px) scale(0.955); }
  .closing .panel.reveal.shown { transform: none; }
}

.closing .panel {
  padding: 76px 32px 84px;
  border-radius: 32px;
  background: #121214;
  color: #EDEDF0;
}

@media (prefers-color-scheme: dark) {
  .closing .panel {
    background: #1E1E21;
    border: 1px solid var(--hairline);
  }
}

.closing .sub {
  margin-inline: auto;
  max-width: 52ch;
  color: #9C9CA2;
}

.closing .meta { color: #8A8A8E; }

.closing .store {
  background: #EDEDF0;
  color: #121214;
}

footer {
  padding-block: 30px 44px;
  border-top: 1px solid var(--hairline);
  font-size: 14px;
  color: var(--ink-soft);
  text-align: center;
}

footer p { margin: 0; }

.foot-links {
  display: flex;
  justify-content: center;
  gap: 26px;
  margin-bottom: 14px;
}

.foot-links a {
  color: var(--ink-soft);
  text-decoration: none;
  font-weight: 500;
}

.foot-links a:hover { color: var(--ink); }

/* The name, set as an outline the width of the page — the label on the
   bottom of the box. It fills with ink under the pointer. */
.giant {
  margin: 8px 0 18px;
  font-size: clamp(84px, 16.5vw, 224px);
  font-weight: 800;
  line-height: 0.95;
  letter-spacing: -0.045em;
  text-align: center;
  color: var(--hairline);
  transition: color 0.5s ease;
  user-select: none;
}

.giant:hover { color: var(--ink); }

/* ── The hero's own type and phone ───────────────────────────────── */

.hero h1 { max-width: 16ch; }

.hero .sub {
  margin-top: 20px;
  max-width: 42ch;
  font-size: 18px;
}

.hero .store { margin-top: 28px; }

.hero .phone {
  margin-top: 48px;
  max-width: 252px;
  margin-bottom: 8px;
}

/* ── The phone ───────────────────────────────────────────────────── */

/* The screenshots carry the device frame with them, so the phone is the
   picture: a drop-shadow traces the silhouette rather than a box, and the
   faint rim keeps a black phone off a near-black page in dark mode. */
.phone {
  position: relative;
  justify-self: center;
  width: 100%;
  max-width: 292px;
  aspect-ratio: 589 / 1280;
  filter:
    drop-shadow(0 0 1.5px rgba(255, 255, 255, 0.28))
    drop-shadow(0 26px 50px rgba(0, 0, 0, 0.3));
}

.shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity 0.38s ease;
}

/* Until the cycle takes over, the first screen is the one showing. */
.phone:not(.playing) .shot:first-child,
.shot.on { opacity: 1; }

.shot.on { z-index: 1; }

/* The outgoing screen, held opaque under the incoming one — see app.js. */
.shot.under {
  opacity: 1;
  z-index: 0;
  transition: none;
}

/* ── The tray every hero stands on ───────────────────────────────── */

.tray {
  position: relative;
  aspect-ratio: 4 / 3.4;
  max-height: 420px;
  background: var(--panel);
  border-radius: var(--r-tray);
  overflow: hidden;
}


.tray.wide { aspect-ratio: 16 / 6.4; max-height: 420px; }

.tray.wide.short { aspect-ratio: 16 / 7.4; }

.reverse .tray { order: 2; }
.reverse .words { order: 1; }

/* The app's die-cut paper edge, drawn as offset shadows of the cutout's own
   silhouette. Each one is a separate blur-and-composite pass, so this is
   reserved for the two places a single cutout is shown large. Everywhere
   else the cutouts sit on tiles, which is what the app's own grid does. */
.sticker {
  filter:
    drop-shadow(1.2px 0 0 var(--paper)) drop-shadow(-1.2px 0 0 var(--paper))
    drop-shadow(0 1.2px 0 var(--paper)) drop-shadow(0 -1.2px 0 var(--paper));
}

.pile {
  display: block;
  width: 100%;
  height: 100%;
  /* The heap parts around the pointer — say so before it is touched. */
  cursor: grab;
}

.pile:active { cursor: grabbing; }

.glyph { width: 1em; height: 1em; }

/* ── Slide 2 — the cutout ────────────────────────────────────────── */

.cut-stage {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
}

.cut-photo,
.cut-sticker {
  grid-area: 1 / 1;
  width: 46%;
  max-width: 200px;
  aspect-ratio: 4 / 5;
  transition: opacity 0.45s ease, transform 0.6s cubic-bezier(0.2, 0.9, 0.25, 1.1);
}

.cut-photo {
  position: relative;
  border-radius: var(--r-panel);
  overflow: hidden;
}

.cut-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The read: a soft band travelling down the frame once. */
.sweep {
  position: absolute;
  left: 0;
  right: 0;
  height: 36%;
  top: -36%;
  opacity: 0;
  mix-blend-mode: plus-lighter;
  background: linear-gradient(to bottom, transparent, rgba(255, 255, 255, 0.5), transparent);
}

.cut-photo.reading .sweep {
  animation: sweep 0.95s linear;
}

@keyframes sweep {
  from { transform: translateY(0); opacity: 1; }
  to { transform: translateY(378%); opacity: 1; }
}

.cut-sticker {
  object-fit: contain;
  opacity: 0;
}

.cut-stage.lifted .cut-photo { opacity: 0; }

.cut-stage.lifted .cut-sticker {
  opacity: 1;
  transform: scale(1.1) rotate(-5deg);
}

.cut-stage.named .cut-sticker {
  transform: translateY(-15%) scale(1.1) rotate(-5deg);
}

.cut-prefill {
  grid-area: 1 / 1;
  align-self: end;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  margin-bottom: 6%;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.4s ease, transform 0.5s cubic-bezier(0.2, 0.9, 0.25, 1.15);
}

.cut-stage.named .cut-prefill {
  opacity: 1;
  transform: translateY(0);
}

.guess {
  font-size: 19px;
  font-weight: 600;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 13px;
  font-weight: 600;
}

.chip .glyph { width: 14px; height: 14px; }

/* ── Slide 3 — the shelf ─────────────────────────────────────────── */

.shelf-stage {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
}

.boxes {
  grid-area: 1 / 1;
  display: flex;
  gap: 32px;
  transform: translateY(26px);
}

.box {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.tile {
  position: relative;
  display: grid;
  place-items: center;
  width: 124px;
  height: 98px;
  border-radius: var(--r-panel);
  background: var(--surface);
  transition: transform 0.28s cubic-bezier(0.2, 0.9, 0.3, 1.2), background-color 0.28s ease;
}

.box.swallowing .tile {
  transform: scale(1.09);
  background: var(--hairline);
}

.tile-glyph {
  width: 30px;
  height: 30px;
  color: var(--ink-soft);
  transition: opacity 0.2s ease;
}

.box.filled .tile-glyph { opacity: 0; }

.cover {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.cover img {
  width: 50px;
  height: 50px;
  object-fit: contain;
}

.cover img:first-child:not(:only-child) { transform: rotate(-8deg); margin-right: -12px; }
.cover img:only-child { transform: rotate(-8deg); }
.cover img:nth-child(2) { transform: rotate(9deg); }

.box-name {
  font-size: 13px;
  font-weight: 600;
}

.flight {
  grid-area: 1 / 1;
  width: 82px;
  height: 82px;
  object-fit: contain;
  opacity: 0;
  pointer-events: none;
}

/* ── Slide 4 — search ────────────────────────────────────────────── */

.search-stage {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 22px;
  padding: 26px 24px;
}

/* Wide tray: the whole hoard fits on one line, so the narrowing reads left
   to right in a single sweep. */
.tray.wide .search-stage {
  max-width: 840px;
  margin-inline: auto;
  padding-inline: 0;
}

.tray.wide .grid { grid-template-columns: repeat(8, 1fr); }

.field {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 13px 16px;
  border-radius: 999px;
  background: var(--canvas);
  font-size: 16px;
}

.field .glyph {
  width: 17px;
  height: 17px;
  color: var(--ink-soft);
  flex: none;
}

.caret {
  width: 2px;
  height: 19px;
  margin-left: 1px;
  background: var(--ink);
  animation: blink 1.1s steps(1, end) infinite;
}

@keyframes blink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }

.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
}

.cell {
  display: grid;
  place-items: center;
  /* Grid items default to min-width:auto, and the cutouts are 400px wide —
     without this the four columns push out past the tray. */
  min-width: 0;
  overflow: hidden;
  aspect-ratio: 1 / 0.95;
  border-radius: var(--r-tile);
  background: var(--canvas);
  box-shadow: inset 0 0 0 0 var(--ink);
  transition: opacity 0.35s ease, transform 0.45s cubic-bezier(0.2, 0.9, 0.3, 1.15), box-shadow 0.3s ease;
}

.cell img {
  width: 68%;
  height: 68%;
  object-fit: contain;
}

.grid.narrowed .cell { opacity: 0.22; transform: scale(0.9); }

.grid .cell.hit {
  opacity: 1;
  transform: scale(1);
  box-shadow: inset 0 0 0 2px var(--ink);
}

/* ── Narrow ──────────────────────────────────────────────────────── */

@media (max-width: 900px) {
  .hero { padding-block: 40px 64px; }

  .hero .phone { max-width: 236px; margin-top: 36px; }

  /* A ring wider than the screen would only show as two things drifting past
     the edges, so it tightens up and the tiles come down with it. */
  .ring { padding-top: 16vh; }

  .ring-spin {
    --r: min(58vw, 320px);
    --tile: clamp(54px, 15vw, 74px);
  }

  .ring-item { border-radius: 14px; }

  .kinetic { padding-block: 52px; }
  .kinetic-tiles div { --cell: 64px; }

  .tray { aspect-ratio: 4 / 3.6; max-height: 360px; }
  .tray.wide { aspect-ratio: 4 / 3.6; max-height: 360px; }

  /* A band tray is column-width down here, so the hoard goes back to four
     across. Eight in a 327px tray gives 30px tiles. */
  .tray.wide .grid { grid-template-columns: repeat(4, 1fr); }
  .tray.wide .search-stage { padding-inline: 24px; }

  .band { padding-block: 60px; }
  .band .tray { margin-top: 30px; }

  /* No pinning on a phone. A stack of pinned cards on a short screen buries
     the words under the demo. */
  .stack {
    padding-block: 60px 72px;
    gap: 24px;
  }

  .stack-card { position: static; }

  .stack-tail { display: none; }

  .closing { margin-top: 0; }

  .stack-inner {
    grid-template-columns: 1fr;
    gap: 26px;
    padding: 24px;
    border-radius: 24px;
  }

  .stack-card:nth-child(even) .tray { order: 0; }

  .sub { max-width: none; }

  .closing { padding-block: 12px 72px; }
  .closing .panel { padding: 56px 22px 60px; border-radius: 26px; }

  .tile { width: 104px; height: 84px; }
  .boxes { gap: 26px; }
}

@media (prefers-reduced-motion: reduce) {
  .caret { animation: none; }
  .cut-photo.reading .sweep { animation: none; }
  * { transition-duration: 0.01ms !important; }
}


/* ── Inner pages ─────────────────────────────────────────────────── */

.page {
  max-width: 660px;
  margin-inline: auto;
  padding: 72px 24px 104px;
}

.page h1 {
  font-size: clamp(34px, 5vw, 48px);
}

.page h2 {
  font-size: 21px;
  letter-spacing: -0.01em;
  line-height: 1.3;
  margin-top: 44px;
}

.page p {
  margin: 14px 0 0;
  color: var(--ink-soft);
  line-height: 1.65;
}

.page .updated {
  margin-top: 10px;
  font-size: 14px;
}

.page a { color: var(--ink); }
