/* HoneyMood public site — landing, privacy, terms.
   The app's redesign pairing (src/constants/honey-theme.ts): Bricolage
   Grotesque for headings and the wordmark, Figtree for everything else. Both
   self-hosted from /fonts, never from Google Fonts: that would send every
   visitor's IP to a third party the privacy policy doesn't name, which would
   make the policy inaccurate. Served from this origin, a font is just another
   file on the page. SIL Open Font License, see fonts/OFL-*.txt. Static files,
   one per weight used below, subset to Latin + Latin Extended. */

@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('fonts/bricolage-800.woff2') format('woff2');
}

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

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

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

:root {
  /* One look whatever the system setting, like the app: the honey theme has
     no dark variant, so neither does the site. `light` also keeps form
     controls and scrollbars from turning dark under it. */
  color-scheme: light;
  /* The app's honey palette (src/constants/honey-theme.ts). Ink is text and
     outlines, ~14:1 on the cream page. */
  --ink: #2a1f4e;
  /* Secondary text: the app's textMuted, 5.9:1 on cream. */
  --muted: #6b5d4c;
  /* Honey for fills, washes and markers; never for words — it's ~1.5:1 on
     cream. --accent-strong is the deeper honey the app uses for icons. */
  --accent: #ffc83d;
  --accent-strong: #e89a12;
  /* Text in the accent: links, the active nav item. The app's #c27800 is
     3.3:1 here, enough for the big headline tagline but not for body-size links;
     this darker honey is 5.1:1. */
  --link: #9a5b00;
  --tagline: #c27800;

  /* The type scale. Every size on the site is one of these. */
  --fs-small: 14px;
  --fs-ui: 15px;
  --fs-body: 17px;
  --fs-lede: clamp(17px, 2.4vw, 20px);
  --fs-h3: 20px;
  --fs-h2: 23px;
  --tracking-heading: -0.01em;
  --font-heading: 'Bricolage Grotesque', 'Figtree', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  /* The app's coral. */
  --pink: #ff6f8a;
  /* The launcher icon: an ink mark on honey, straight from app.json. */
  --brand-tile: #ffc83d;
  --brand-mark: #2a1f4e;
  --bg: #fff6e6;
  --card: #fffffff2;
  --border: #eadfca;
  /* The app's hard ink edge under cards and buttons. */
  --outline: #2a1f4e;
  --shadow: rgba(42, 31, 78, 0.1);
}


* {
  box-sizing: border-box;
}

body {
  /* Shared by .glow and the strip behind the sticky header, which has to paint
     the identical wash to stay invisible over it. */
  --glow:
    radial-gradient(60% 45% at 78% 4%, color-mix(in srgb, var(--pink) 22%, transparent), transparent 70%),
    radial-gradient(55% 40% at 8% 0%, color-mix(in srgb, var(--accent) 26%, transparent), transparent 72%);
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: 'Figtree', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  font-size: var(--fs-body);
  /* The app's body weight (HoneyFonts.body). */
  font-weight: 500;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* Heavy display headings over a calm body, as in the app: the contrast
   between the two is what gives the page its hierarchy. */
h1,
h2,
h3 {
  font-family: var(--font-heading);
  font-weight: 800;
  letter-spacing: var(--tracking-heading);
}

/* One width on every page, so the header bar, the glass and the footer rule
   all land in the same place as you move between them. The landing page needs
   1000px for the two-column hero; the legal pages get their narrow reading
   measure from `.doc` below rather than from a narrower frame, which would
   make the header jump width on navigation. */
.wrap {
  max-width: 1000px;
  margin: 0 auto;
  padding-inline: 20px;
  padding-block: 40px 72px;
}

/* --- header --- */

/* A floating glass bar rather than a row of loose elements: it gives the
   lockup something to sit on, and it follows you down the legal pages, which
   are long enough that the way back to the landing page was otherwise a scroll
   to the top. Kept inside .wrap so it stays the width of the content instead
   of spanning a 1000px page edge to edge. */
.site-head {
  position: sticky;
  top: 12px;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 44px;
  padding: 9px 10px 9px 9px;
  border-radius: 22px;
  /* The same surface, border and lift as the cards below it. As tinted glass
     it read as a different material next to them — greyer, softer-edged. The
     blur stays for the few percent of transparency --card keeps. */
  border: 2px solid var(--outline);
  background: var(--card);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
  backdrop-filter: blur(16px) saturate(160%);
  box-shadow: 0 3px 0 var(--outline);
}

/* The bar floats 12px below the top of the window, and without this whatever
   scrolled under it showed through that gap — the tops of the hero headline
   sliced off above the bar. A strip of page background covers the gap and fades
   out just below the bar's top edge. On the landing page it paints the same
   fixed glow as .glow, laid out against the same viewport-sized box, so at rest
   it is indistinguishable from what is behind it. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 19;
  background: var(--bg);
  -webkit-mask-image: linear-gradient(#000 12px, transparent 26px);
  mask-image: linear-gradient(#000 12px, transparent 26px);
  pointer-events: none;
}

body:has(.glow)::before {
  background: var(--glow), var(--bg);
}

/* The landing page's header sits closer to the hero than the legal pages'
   does to their first heading — there the gap gives a document its title
   space, here it was just distance. */
body:has(.hero-split) .site-head {
  margin-bottom: 56px;
}

/* The lockup is the launcher icon as a honey tile, with the wordmark beside
   it in ink — the same pairing as the footer and the store listing. The word
   used to sit inside one long purple capsule with the mark, which made the
   whole lockup read as a button. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 1px 12px 1px 1px;
  color: var(--ink);
  text-decoration: none;
}

/* The header svg crops the viewBox to the mark's own bounds and the tile adds
   its own padding — the launcher icon's built-in margin, sized for the
   platforms' masks, would leave the mark lost in a 40px square. */
.brand-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex: none;
  border-radius: 12px;
  background: var(--brand-tile);
  /* The app's ink edge and hard shadow, not a soft glow. */
  border: 2px solid var(--outline);
  box-shadow: 0 2px 0 var(--outline);
  transition:
    transform 0.25s cubic-bezier(0.2, 0.8, 0.25, 1),
    box-shadow 0.25s ease;
}

.brand:hover .brand-mark {
  transform: translateY(-1px);
  box-shadow: 0 3px 0 var(--outline);
}

.brand-mark svg {
  display: block;
  width: 24px;
  height: 19.4px;
}

.brand-mark path {
  fill: var(--brand-mark);
  /* fill-box so the translation below is read in the path's own user units
     rather than the viewport's. */
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 0.32s cubic-bezier(0.2, 0.8, 0.25, 1);
}

/* On hover the two curves lean toward each other — the mood closing. The
   whole app is about those two reacting to one another, so the logo may as
   well say it. */
.brand:hover .mark-top {
  transform: translateY(4px);
}

.brand:hover .mark-bottom {
  transform: translateY(-4px);
}

@media (prefers-reduced-motion: reduce) {
  .brand-mark,
  .brand-mark path {
    transition: none;
  }
  .brand:hover .brand-mark,
  .brand:hover .mark-top,
  .brand:hover .mark-bottom {
    transform: none;
  }
}

.brand-word {
  font-family: var(--font-heading);
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1;
}

.site-nav {
  display: flex;
  gap: 6px;
  font-size: var(--fs-ui);
  font-weight: 800;
}

.site-nav a {
  color: var(--muted);
  text-decoration: none;
  padding: 7px 13px;
  border-radius: 999px;
  transition:
    color 0.18s ease,
    background 0.18s ease;
}

/* Like the app's tab bar: the current page is a solid honey pill with ink
   text, the way Home's selected tab is. Hover is the lighter creamHoney the app
   uses for chips, so it never reads as "you are here". */
.site-nav a:hover {
  color: var(--ink);
  background: #fff0c7;
}

.site-nav a[aria-current='page'] {
  color: var(--ink);
  background: var(--accent);
}

/* On a phone the lockup and the two links come to about 362px against a 360px
   viewport, and the bar wraps to two rows over a hair. Taking a few pixels out
   of both keeps it on one line down to ~340px. */
@media (max-width: 480px) {
  .site-head {
    padding: 7px 8px 7px 7px;
    border-radius: 20px;
  }

  .brand {
    gap: 9px;
  }

  .brand-mark {
    width: 36px;
    height: 36px;
    border-radius: 11px;
  }

  .brand-word {
    font-size: 19px;
  }

  .site-nav {
    font-size: var(--fs-small);
  }

  .site-nav a {
    padding: 6px 10px;
  }
}

/* --- ambient glow behind the hero ---
   One soft radial wash, fixed behind everything. Cheap, and it stops the page
   reading as a white sheet with text on it. */

.glow {
  position: fixed;
  inset: 0;
  z-index: -1;
  background: var(--glow);
  pointer-events: none;
}

/* --- hero --- */

/* The app's welcome headline: four short lines, then the tagline in honey on
   its own line. The size follows the text column's width so the tagline, the
   longest line (~13.5em in Bricolage Grotesque at 800), fits it beside the
   phone: the column is the viewport minus the page padding, the phone and the
   gap (~320px). Capped at 43px, where the 1000px frame tops out. */
.hero h1 {
  font-size: clamp(28px, calc((100vw - 347px) / 14), 43px);
  line-height: 1.08;
  margin: 0 0 18px;
}

.hero-line,
.hero-tagline {
  display: block;
}

.hero-tagline {
  margin-top: 0.12em;
  color: var(--tagline);
}

/* Stacked on a phone, the column is the full screen; the tagline may wrap. */
@media (max-width: 720px) {
  .hero h1 {
    font-size: clamp(30px, 9vw, 40px);
  }
}

/* As on the app's welcome screen: the lines light up one by one, then the
   tagline pops in. Unlike the app it loops, every 8s, for as long as the page
   is open: everything holds lit for ~3s, the lines fade back in order, the
   tagline last, and it starts again. All five share the period; the stagger
   is the animation-delay. Without motion everything is simply there. */
@media (prefers-reduced-motion: no-preference) {
  .hero-line {
    animation: line-lit 8s ease infinite both;
  }

  .hero-line:nth-child(1) { animation-delay: 0.3s; }
  .hero-line:nth-child(2) { animation-delay: 0.75s; }
  .hero-line:nth-child(3) { animation-delay: 1.2s; }
  .hero-line:nth-child(4) { animation-delay: 1.65s; }

  .hero-tagline {
    transform-origin: 0 60%;
    animation: tagline-pop 8s ease 2.2s infinite both;
  }
}

@keyframes line-lit {
  0% { opacity: 0.28; }
  4.5%, 70% { opacity: 1; }
  76%, 100% { opacity: 0.28; }
}

@keyframes tagline-pop {
  0% {
    opacity: 0;
    transform: translateY(0.3em) scale(0.85);
    animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  6%, 66% { opacity: 1; transform: none; }
  72%, 100% { opacity: 0; transform: translateY(0.15em) scale(0.95); }
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.hero .lede {
  font-size: var(--fs-lede);
  color: var(--muted);
  margin: 0 0 26px;
  max-width: 34em;
}

/* The privacy promise: one quiet line under the buttons. As a bordered panel
   it weighed as much as the call to action it was meant to reassure. */
.promise {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-size: var(--fs-ui);
  line-height: 1.5;
  color: var(--muted);
}

/* Drawn like the card icons: a green shield in an ink outline with a white
   tick. No tile, so this line stays quieter than the buttons above it. */
.promise-mark {
  display: flex;
  flex: none;
}

.promise-mark svg {
  width: 24px;
  height: 24px;
  fill: #2e8b62;
  stroke: var(--ink);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.promise-mark svg .fill-none {
  fill: none;
  stroke: #fff;
  stroke-width: 2.2;
}

/* The default underline sat right on the descenders and looked like a
   correction. */
.promise a {
  color: var(--link);
  font-weight: 800;
  text-decoration-color: color-mix(in srgb, var(--link) 40%, transparent);
  text-underline-offset: 3px;
  white-space: nowrap;
  transition: text-decoration-color 0.18s ease;
}

.promise a:hover {
  text-decoration-color: currentColor;
}

/* Two columns on a wide screen, stacked on a phone. `minmax(0, …)` on the
   first column stops the long headline forcing the grid wider than the page. */
.hero-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 40px;
  align-items: center;
  margin-bottom: 8px;
}

@media (max-width: 720px) {
  .hero-split {
    grid-template-columns: 1fr;
    gap: 40px;
  }
}

.hero-art {
  display: flex;
  justify-content: center;
}

/* Side by side, the mockup's transparent margin above and below the device
   would otherwise set the row's height and push the text down into the middle
   of empty space. Letting that margin overhang the row sizes it to the text. */
@media (min-width: 721px) {
  .hero-art {
    margin-block: -56px;
    /* Centred on the column, the phone started well below "Do the chores."
       and read as floating beside the text; lifting it lines its top up with
       the headline. On the wrapper, not the <img>, because the phone's hover
       lift owns the image's `transform`. */
    transform: translateY(-36px);
  }
}

/* --- the phone ---
   The mockup ships with its own device frame on a transparent background, so
   all this adds is a drop shadow shaped to the phone (`filter`, not
   `box-shadow`, which would draw a rectangle around the transparent bounds). */

/* Sized by HEIGHT, not width. The mockup is roughly 1:2.2, so a width-based
   cap made it far taller than the text beside it — and since the hero centres
   both columns, that height difference showed up as a large dead gap above the
   headline. Capping the height keeps the two columns closer in size, and the
   vh term stops the phone running past the fold on a short laptop screen.
   580px: at 520 it read as an afterthought beside the two-line headline; the
   headline's size formula above accounts for the extra width it takes. */
/* The <picture> around the phone only picks WebP or PNG; `contents` keeps it
   out of the layout so the <img> stays the flex item sized below. */
.hero-art picture {
  display: contents;
}

.hero-phone {
  display: block;
  height: min(580px, 76vh);
  width: auto;
  max-width: 100%;
  filter: drop-shadow(0 30px 45px color-mix(in srgb, var(--accent-strong) 40%, transparent));
  /* `filter` and `transform` are both animated on the compositor, so this
     stays smooth without any JavaScript — which keeps the page's zero-script
     property intact. */
  transition:
    transform 0.45s cubic-bezier(0.2, 0.8, 0.25, 1),
    filter 0.45s ease;
  will-change: transform;
}

/* Hover only where there is a real pointer. On a touchscreen `:hover` sticks
   after a tap, which would leave the phone lifted and never settle. */
@media (hover: hover) {
  .hero-art:hover .hero-phone {
    transform: translateY(-12px) rotate(-1.6deg) scale(1.025);
    filter: drop-shadow(0 42px 60px color-mix(in srgb, var(--accent-strong) 52%, transparent));
  }
}

@media (max-width: 720px) {
  .hero-phone {
    /* Stacked under the text, so vh would make it fill the screen. */
    height: auto;
    width: 250px;
  }

  /* The mockup's transparent margin above and below the device (~10% of its
     height) left a ~120px empty band on each side of the phone once it was
     stacked. Pulled in the same way the side-by-side layout does, leaving a
     little of it as breathing room. */
  .hero-art {
    margin-block: -36px -44px;
  }

  .cards {
    margin-top: 40px;
  }
}


/* Someone who has asked their OS for less motion gets the shadow shift and
   nothing that moves. */
@media (prefers-reduced-motion: reduce) {
  .hero-phone {
    transition: filter 0.3s ease;
  }

  .hero-art:hover .hero-phone {
    transform: none;
  }
}

/* --- feature cards --- */

/* Two by two. Four across would squeeze each card to ~230px on the 1000px
   frame, too narrow for these headlines. */
.cards {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 18px;
  margin: 56px 0 0;
}

@media (max-width: 620px) {
  .cards {
    grid-template-columns: 1fr;
  }
}

.card {
  background: var(--card);
  /* The app's InkSurface: an ink outline on a hard ink shadow. */
  border: 2px solid var(--outline);
  border-radius: 20px;
  padding: 22px;
  box-shadow: 0 4px 0 var(--outline);
  transition:
    transform 0.18s ease,
    box-shadow 0.18s ease;
}

.card:hover {
  transform: translateY(-3px);
  box-shadow: 0 7px 0 var(--outline);
}

/* Drawn icons, one per card, each saying what its card is about — the
   previous ◆ ● ✦ ▲ were decoration pretending to be icons. Inline SVG, so
   they inherit `--mark` and cost no requests. Hidden from screen readers in
   the markup: each one only restates its own heading. */
/* Drawn like the app's own objects (the window, fridge and microwave on Home):
   flat honey, coral and green fills inside ink outlines, on an icon tile that
   is itself an InkSurface — creamHoney, a thin ink edge, a hard ink shadow. */
.card-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 54px;
  margin-bottom: 17px;
  border-radius: 15px;
  border: 2px solid var(--outline);
  background: #fff0c7;
  box-shadow: 0 3px 0 var(--outline);
  transition: transform 0.22s cubic-bezier(0.2, 0.8, 0.25, 1);
}

/* Outlines by default; shapes opt into a fill with an f-* class. The
   48-unit icons draw at 0.83x, so a 2.4 stroke lands on the ~2px the tile's
   border uses. */
.card-mark svg {
  width: 40px;
  height: 40px;
  overflow: visible;
  fill: none;
  stroke: var(--ink);
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.card-mark svg .f-honey { fill: var(--accent); }
.card-mark svg .f-coral { fill: var(--pink); }
.card-mark svg .f-green { fill: #2e8b62; }
.card-mark svg .f-white { fill: #fff; }
.card-mark svg .f-ink { fill: var(--ink); }

/* The avatar is drawn at 100x100 like the app's, so its strokes are scaled
   up to match the 48-unit icons. */
.card-mood .card-mark svg {
  stroke-width: 5;
}

.card-play .ring-lines {
  opacity: 0;
}

.card-memories .heart {
  transform-box: fill-box;
  transform-origin: 50% 60%;
}

.card-missions {
  --mark: var(--accent-strong);
}

.card-mood {
  --mark: var(--pink);
}

/* Coral, the colour of its icon's heart. The palette has no fourth warm colour:
   ink tinted the highlight grey, and honey would repeat the card above it. */
.card-memories {
  --mark: var(--pink);
}

.card-play {
  --mark: #2e8b62;
}

/* Swings from the top, where a bell hangs. */
.card-play .bell {
  transform-box: fill-box;
  transform-origin: 50% 0;
}

.card:hover .card-mark {
  transform: rotate(-6deg) scale(1.08);
}

/* --- card antics ---
   Every visitor gets the finished state by default: mission ticked, avatar
   grinning, speech bubble out. Only where motion is welcome is it rewound to
   the start and played — on hover with a real pointer, or scrubbed by scroll
   on a touchscreen whose browser supports view timelines. So a browser that
   can do neither, or a visitor who asked for less motion, still sees the
   joke, just without the performance. */

.card {
  display: flex;
  flex-direction: column;
  view-timeline: --card;
}

.card-mark {
  position: relative;
}

.card-mood .face-sad {
  opacity: 0;
}

.card-mood .card-mark svg {
  transform-origin: 50% 90%;
}

.spark {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  border-radius: 50%;
  border: 1.5px solid var(--outline);
  opacity: 0;
  pointer-events: none;
}

.spark-1 { --dx: -24px; --dy: -20px; background: var(--accent); }
.spark-2 { --dx: 26px; --dy: -16px; background: var(--pink); }
.spark-3 { --dx: 4px; --dy: -30px; background: var(--accent); }

/* The surprise: a speech bubble that pops out of the icon, in the avatar's
   voice. It floats in the empty space right of the icon, so it takes no room
   in the card's flow and nothing reflows when it appears. */
.card {
  position: relative;
}

/* The avatar's speech bubble from Home: white, an ink outline on a hard ink
   shadow, ink text. Vertically centred on the 54px icon tile beside it. */
.card .card-quip {
  position: absolute;
  top: 30px;
  left: 92px;
  max-width: calc(100% - 114px);
  margin: 0;
  padding: 6px 12px;
  border-radius: 14px;
  border: 2px solid var(--outline);
  background: #fff;
  box-shadow: 0 3px 0 var(--outline);
  font-size: var(--fs-small);
  font-weight: 800;
  line-height: 1.35;
  color: var(--ink);
  transform-origin: 0 50%;
  pointer-events: none;
}

/* The tail, pointing back at the icon that "said" it: a square turned 45°
   with only its two outer edges inked, so it joins the bubble's outline. */
.card .card-quip::before {
  content: '';
  position: absolute;
  left: -7px;
  top: 50%;
  width: 11px;
  height: 11px;
  margin-top: -6.5px;
  background: inherit;
  border-left: 2px solid var(--outline);
  border-bottom: 2px solid var(--outline);
  border-bottom-left-radius: 3px;
  transform: rotate(45deg);
}

@keyframes quip-pop {
  0% { opacity: 0; transform: translateX(-8px) scale(0.6); }
  60% { opacity: 1; transform: translateX(2px) scale(1.06); }
  100% { opacity: 1; transform: none; }
}

.tick {
  stroke-dasharray: 1;
}

@keyframes tick {
  from { stroke-dashoffset: 1; }
  to { stroke-dashoffset: 0; }
}

@keyframes face-out {
  0%, 45% { opacity: 1; }
  55%, 100% { opacity: 0; }
}

@keyframes face-in {
  0%, 45% { opacity: 0; }
  55%, 100% { opacity: 1; }
}

/* Sulks, droops a little lower, then the grin arrives with a hop. */
@keyframes cheer-up {
  0% { transform: none; }
  35% { transform: translateY(1px) scale(1.06, 0.92); }
  55% { transform: translateY(-5px) scale(0.94, 1.08); }
  75% { transform: translateY(0) scale(1.04, 0.96); }
  100% { transform: none; }
}

/* Two quick beats, like a heartbeat. */
@keyframes heart-beat {
  0%, 100% { transform: none; }
  20% { transform: scale(1.3); }
  35% { transform: scale(0.95); }
  50% { transform: scale(1.22); }
  70% { transform: none; }
}

@keyframes ring-lines {
  0%, 100% { opacity: 0; }
  20%, 70% { opacity: 1; }
}

@keyframes spark {
  0% { opacity: 0; transform: translate(0, 0) scale(0.4); }
  25% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(1); }
}

@keyframes ring {
  0%, 100% { transform: none; }
  15% { transform: rotate(18deg); }
  30% { transform: rotate(-16deg); }
  45% { transform: rotate(12deg); }
  60% { transform: rotate(-8deg); }
  75% { transform: rotate(4deg); }
}

@media (prefers-reduced-motion: no-preference) {
  /* A real pointer: rewind to the start state, play on hover. Leaving the
     card rewinds it, so the next hover plays it again. */
  @media (hover: hover) {
    .tick { stroke-dashoffset: 1; }
    .card-mood .face-sad { opacity: 1; }
    .card-mood .face-happy { opacity: 0; }
    .card .card-quip { opacity: 0; }

    .card:hover .tick { animation: tick 0.35s ease-out 0.1s both; }
    .card:hover .face-sad { animation: face-out 0.9s steps(1, end) both; }
    .card:hover .face-happy { animation: face-in 0.9s steps(1, end) both; }
    .card-mood:hover .card-mark svg { animation: cheer-up 0.9s ease-in-out both; }
    .card:hover .heart { animation: heart-beat 0.8s ease-in-out both; }
    .card:hover .spark-1 { animation: spark 0.7s ease-out 0.25s both; }
    .card:hover .spark-2 { animation: spark 0.7s ease-out 0.32s both; }
    .card:hover .spark-3 { animation: spark 0.7s ease-out 0.4s both; }
    .card:hover .bell { animation: ring 0.8s ease-in-out both; }
    .card:hover .ring-lines { animation: ring-lines 0.8s ease-in-out both; }
    .card:hover .card-quip { animation: quip-pop 0.4s cubic-bezier(0.2, 0.8, 0.25, 1) 0.5s both; }

    /* The generic tilt would fight the heartbeat, the blob's hop and the bell. */
    .card-mood:hover .card-mark,
    .card-memories:hover .card-mark,
    .card-play:hover .card-mark {
      transform: scale(1.08);
    }
  }

  /* A touchscreen: no hover, so the act plays as the card scrolls up into
     view, scrubbed by the scroll itself. */
  @media (hover: none) {
    @supports (animation-timeline: view()) {
      .tick,
      .face-sad,
      .face-happy,
      .card-mood .card-mark svg,
      .heart,
      .spark,
      .bell,
      .ring-lines,
      .card .card-quip {
        animation-duration: auto;
        animation-fill-mode: both;
        animation-timeline: --card;
        animation-range: entry 100% cover 40%;
      }

      .tick { animation-name: tick; }
      .face-sad { animation-name: face-out; animation-timing-function: steps(1, end); }
      .face-happy { animation-name: face-in; animation-timing-function: steps(1, end); }
      .card-mood .card-mark svg { animation-name: cheer-up; }
      .heart { animation-name: heart-beat; }
      .spark { animation-name: spark; }
      .bell { animation-name: ring; }
      .ring-lines { animation-name: ring-lines; }
      .card .card-quip { animation-name: quip-pop; }
    }
  }
}

.card h3 {
  margin: 0 0 8px;
  font-size: var(--fs-h3);
  line-height: 1.25;
}

/* Between muted and ink: the plain muted grey was too faint for body copy
   people are meant to actually read. */
.card p {
  margin: 0;
  font-size: 16px;
  line-height: 1.6;
  color: color-mix(in srgb, var(--muted) 60%, var(--ink));
}

/* The one phrase per card worth catching when skimming: a soft full-height
   tint in the card's colour, like a highlighter pen. An underline-style stroke
   read as a link. Kept on one line so the tint never breaks mid-phrase —
   except on the narrowest phones, where a long phrase could overflow. */
.card mark {
  color: var(--ink);
  font-weight: 800;
  white-space: nowrap;
  /* The negative margin cancels the padding, so a following comma or full stop
     sits against the tint rather than a gap away from it. */
  padding: 1px 5px;
  margin: 0 -4px 0 -1px;
  border-radius: 5px;
  background: color-mix(in srgb, var(--mark) 18%, transparent);
}

@media (max-width: 380px) {
  .card mark {
    white-space: normal;
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
  }
}

/* --- long-form pages --- */

/* The frame is 1000px wide everywhere; prose is not. 680px is the measure
   these pages already had — about 93 characters a line, measured, which is
   already past the 45-75 prose is comfortable at. Stretching it to the
   frame's 960px would make it ~130.

   Left-aligned, not centred: the column shares its left edge with the logo,
   the nav and the landing page's headline, so moving between pages nothing
   shifts sideways. Centring it would buy symmetry and lose that. */
.doc {
  max-width: 680px;
}

.doc h1 {
  font-size: clamp(30px, 5vw, 40px);
  line-height: 1.1;
  margin: 0 0 8px;
}

.doc .updated {
  color: var(--muted);
  font-size: var(--fs-small);
  margin: 0 0 36px;
}

.doc h2 {
  font-size: var(--fs-h2);
  line-height: 1.2;
  margin: 38px 0 10px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

.doc h2:first-of-type {
  border-top: none;
  padding-top: 0;
}

.doc h3 {
  font-size: 18px;
  margin: 24px 0 6px;
}

.doc ul,
.doc ol {
  padding-left: 20px;
}

.doc li {
  margin-bottom: 7px;
}

.doc li::marker {
  color: var(--accent);
}

.doc a {
  color: var(--link);
  font-weight: 700;
  text-underline-offset: 3px;
}

.table-scroll {
  overflow-x: auto;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-ui);
  margin: 14px 0;
}

th,
td {
  text-align: left;
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

th {
  font-size: 13px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}

.note {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border-radius: 14px;
  padding: 16px 18px;
  font-size: var(--fs-ui);
  margin: 22px 0;
}

.note strong {
  color: var(--link);
}

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

.stores {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

/* Answers "does my partner need it too?" before anyone has to ask. */
.stores-note {
  margin: 0 0 22px;
  font-size: var(--fs-small);
  color: var(--muted);
}

/* Shaped like the store badges people already recognise — dark, two lines,
   small print above — without borrowing the stores' own artwork, which is
   only licensed for linking to a live listing. */
.store {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 168px;
  padding: 9px 20px 10px;
  border-radius: 14px;
  background: var(--ink);
  /* The app's hard shadow; a deeper ink so it shows under an ink button. */
  box-shadow: 0 4px 0 #120c26;
  color: var(--bg);
  font-size: 18px;
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.01em;
  text-decoration: none;
  transition:
    transform 0.18s ease,
    box-shadow 0.18s ease;
}

.store small {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.01em;
  opacity: 0.75;
}

/* Live links only (at launch): lift on hover, press down into the shadow on
   tap, like the app's InkPressable. */
a.store:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 0 #120c26;
}

a.store:active {
  transform: translateY(4px);
  box-shadow: 0 0 0 #120c26;
}

/* Two 168px buttons overflow a 375px phone by a hair and stack; sharing the
   row keeps them reading as a pair. */
@media (max-width: 480px) {
  .store {
    flex: 1;
    min-width: 0;
    max-width: 180px;
    padding-inline: 14px;
    font-size: 16.5px;
  }
}

/* Not a link yet. Full-strength ink — fading it read as washed-out grey —
   and "Coming soon" in the small print says the rest; with no hover lift or
   press, nothing invites a tap. */
.store.is-soon {
  cursor: default;
}

/* --- footer --- */

.site-foot {
  margin-top: 48px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 18px 24px;
  flex-wrap: wrap;
  font-size: var(--fs-small);
  color: var(--muted);
}

.foot-brand {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.foot-lockup {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--ink);
  font-family: var(--font-heading);
  font-size: 17px;
  font-weight: 800;
  letter-spacing: -0.01em;
  text-decoration: none;
}

/* The header's tile in miniature: the ink mark on honey. */
.foot-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  background: var(--brand-tile);
  border: 1.5px solid var(--outline);
}

.foot-mark svg {
  width: 16px;
  height: 13px;
  fill: var(--brand-mark);
}

.foot-links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
}

.site-foot a:not(.foot-lockup) {
  color: var(--muted);
  text-decoration: none;
}

.site-foot a:not(.foot-lockup):hover,
.site-foot a[aria-current='page'] {
  color: var(--link);
}
