/* =========================================================================
   pocats.app: the app's own golden hour.

   THE GROUND IS THE LIGHT IN THE APP'S SCENES AND ON ITS ICON, AND THE
   READING IS ON CREAM. The icon's field runs orange to a deep rust; the
   scenes (sakura, autumn, harbour, rooftop) are painted in the same late
   light. The ground holds that light at a lightness the ink still sits on;
   the numbers are in :root. Cream sheets carry the copy, which is how the
   app lays a card on any scene, and is the icon itself: the sweater on the
   field. For the record the site launched on a muted amber the app never
   had, spent a week on a dusk violet, and an evening on bare paper.

   THREE GROUND STATES, AND ONLY THREE, across all 22 pages:
     ground  a <section> with no band class. The default, and the colour.
     sheet   .band-paper, and .prose, which floats as a card on the ground.
     night   .ledge, the reveal screen's room.

   BODY COPY NEVER SITS ON THE BARE GROUND. It sits on a cream sheet. The
   three soft ink tiers fail AA on the ground and are held to sheets by the
   guard rule under body; grep --ink-soft / --ink-faint / --coffee before
   adding a consumer.

   The page is light-only on purpose. PoCats is a warm afternoon; a dark theme
   would be a different product, so color-scheme is pinned.
   ========================================================================= */

/* THE TYPE, self-hosted. Fredoka and Hanken Grotesk are variable files
   (one file, the whole weight range); DM Mono is two static weights.
   Latin-ext subsets load only for a glyph that needs them. */
@font-face { font-family: "Fredoka"; font-style: normal; font-weight: 300 700; font-display: swap; src: url("/assets/fonts/fredoka-700-latin.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Fredoka"; font-style: normal; font-weight: 300 700; font-display: swap; src: url("/assets/fonts/fredoka-700-latin-ext.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "Hanken Grotesk"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("/assets/fonts/hanken-grotesk-400-latin.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Hanken Grotesk"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("/assets/fonts/hanken-grotesk-400-latin-ext.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "DM Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("/assets/fonts/dm-mono-400-latin.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "DM Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("/assets/fonts/dm-mono-400-latin-ext.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "DM Mono"; font-style: normal; font-weight: 500; font-display: swap; src: url("/assets/fonts/dm-mono-500-latin.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "DM Mono"; font-style: normal; font-weight: 500; font-display: swap; src: url("/assets/fonts/dm-mono-500-latin-ext.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }

:root {
  color-scheme: light;

  /* THE GROUND. The light in the app's own scenes and on its icon: the icon's field runs from #F29A23 at the top through #E07D2C to a deep #842F02 at the corners, and every scene (sakura, autumn, harbour, rooftop) is painted in the same late golden light. The ground is that light held at a lightness the ink can still sit on (ink-deep 8.4:1 at the mid stop, 6.3:1 at the deepest), so it is the icon's orange, not the amber the site launched with (#E2A85C, a muted golden the app uses nowhere) and not the paper it spent an evening on. Reading copy stays on cream sheets, which is how the app lays a card on any of its scenes: cream on orange is the icon itself (the sweater on the field). The three soft ink tiers do not clear AA on the bare ground here and are held to sheets by the guard rule */
  --ground: #F6B25C;
  --ground-hi: #FFDFAE;
  --ground-lit: #FBC77E;
  --ground-deep: #EDA046;

  /* SHEETS. The app's cream cards. This is the reading surface everywhere:
     body copy never sits on the bare ground where there is a lot of it. */
  --paper: #FFF8E7;
  --paper-deep: #F6E7CA;
  --card: #FFFDF8;
  --sunk: #F7EBD4;
  --edge: #E6CFAC;
  /* was #D0A371, which is 2.39:1 on paper and failed 1.4.11 on every input
     border on the site. #A9762F is 3.69:1. */
  --edge-strong: #A9762F;

  /* NIGHT. The reveal screen's room. */
  --night: #2D170F;
  --night-soft: #3A2017;
  --night-line: #6B4530;
  --night-ink: #E8D3B8;
  --night-mute: #CDB499;

  /* INK. The app's own: ink for headings, its night-soft brown for body,
     coffee for the soft tier. ON THE GROUND ONLY --ink-deep AND --ink ARE LEGAL
     FOR TEXT: the three soft tiers fail AA on it, measured below, and are held
     to sheets by the guard rule under body; grep before adding a consumer. */
  --ink-deep: #2B211B;   /* 8.56 on ground, 7.27 on its deepest stop, 14.84 on paper, 5.31 on orange */
  --ink: #3A2017;   /* 8.18 on ground, 6.95 on its deepest stop, 14.18 on paper */
  --ink-soft: #5D3A24;   /* 9.46 on paper, 4.64 on the deepest ground stop */
  --ink-faint: #6E4A2C;   /* 7.40 on paper, 3.63 on the deepest ground stop -> SHEET ONLY */
  --coffee: #6B3D12;   /* 8.62 on paper, 4.22 on the deepest ground stop -> SHEET ONLY */

  /* ACTION. The app's own orange from theme.ts (colors.orange), its one
     action colour. The label stays --ink-deep because white on it is
     2.96:1 and unfixable. On this ground the fill is 1.61:1 against
     the page, so the hard underside (--orange-drop) is what draws the shape. */
  --orange: #FF6423;
  --orange-lift: #FF7F47;
  --orange-dark: #B84816;   /* link colour: 4.99 on a sheet, 2.88 on the ground -> SHEET ONLY */
  --orange-drop: #8E3204;   /* the button's hard underside AND the focus ring: 3.73 on the deepest ground stop, 7.60 on a sheet, so a control's shape passes 1.4.11 everywhere without a border */
  --orange-soft: #FFE3D2;
  --tag-ink: #9E3705;   /* on a cream pill: 6.61; 3.81 on the ground */
  /* The two colours a form answers in. They sit on the bare ground on the
     hero: 6.66 and 6.78 on it, 5.66 and 5.76 on its deepest stop. */
  --rust-deep: #5F2205;
  --moss-deep: #1E3A26;

  --gold: #F0A93C;
  --gold-deep: #8A5A0A;
  --gold-soft: #FFE6C2;
  --foil-lit: #E9C97A;

  --moss: #94CFA1;
  --moss-dark: #2F5A3C;
  --moss-soft: #E8F5DF;

  /* Rarity: the light a cat sits in IS its rarity */
  --halo-everyday: #FFE3B8;
  --halo-uncommon: #D8ECD9;
  --halo-rare: #DCC9EE;
  --halo-legendary: #FFD79A;
  --paw-everyday: #C97A2E;
  --paw-uncommon: #4C7F5C;
  --paw-rare: #6B4E9E;
  --paw-legendary: #A9700F;
  --lav: #C7B1D5;

  --display: "Fredoka", "Segoe UI", system-ui, sans-serif;
  --body: "Hanken Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --mono: "DM Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --wide: 1140px;
  --prose: 68ch;
  --gutter: clamp(20px, 5vw, 40px);
  --band: clamp(52px, 8vw, 104px);

  --shadow-lift: 0 2px 6px rgba(74, 46, 34, .08), 0 12px 28px rgba(74, 46, 34, .10);
  --shadow-card: 0 3px 8px rgba(45, 23, 15, .14), 0 18px 44px rgba(45, 23, 15, .20);
  --shadow-sheet: 0 1px 0 #FFFDF8 inset, 0 14px 34px rgba(60, 32, 12, .18), 0 2px 5px rgba(60, 32, 12, .12);
}

*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%; scroll-behavior: smooth; background: #F6B25C;
  /* The header is sticky and 65px tall. Without this every anchor jump,
     including the skip link's #main, lands its target behind the bar. */
  scroll-padding-top: 76px;
}

body {
  margin: 0;
  background: transparent;
  color: var(--ink-deep);
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.62;
  overflow-x: hidden;
}

/* THE WASH and THE TOOTH. Two fixed layers, composited once, so a 6,000px blog
   post costs the same as the home page and the sun stays at the top of the
   viewport instead of sliding away. No background-attachment (iOS ignores it)
   and no blend mode (a compositing pass per scroll on a cheap Android). */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -2; pointer-events: none;
  background: radial-gradient(118% 76% at 50% -12%,
    var(--ground-hi) 0%, var(--ground-lit) 30%, var(--ground) 58%, var(--ground-deep) 100%);
}
body::after {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  opacity: .55;
  background-image: url("/assets/art/paper-tooth.webp");
  background-size: 180px;
}

/* THE GUARD. The three soft ink tiers fail AA on the ground. Anything sitting on
   the bare ground takes --ink instead. Sheets (.band-paper) and night bands
   (.ledge) are exempt because their own surfaces are light or dark enough.

   It is :is() rather than :where() on purpose. As a zero-specificity rule it
   lost to every class it was meant to correct: .lede is (0,1,0) and
   .tiers figcaption is (0,1,1), so both kept --ink-soft on the bare ground,
   which measured 4.79:1 on the old amber and 4.13:1 here. Two selectors of
   one class each make this (0,2,0), which is the whole fix. */
:is(section:not(.band-paper):not(.ledge), .hero, .closer)
  :is(.eyebrow, .stampline, .muted, .small, .byline, .under, .stage-log,
      .crumbs, .crumbs li, .crumbs a, .pill-row li, .lede, .keynote, figcaption) {
  color: var(--ink);
}
/* .notify-msg is deliberately NOT in that list. It is the one line whose
   colour carries meaning, and the guard outranks a [data-state] attribute
   selector, so being in the list made every failed signup render in plain
   ink. It takes the legal ink as its own base rule instead, which the two
   state rules below can then beat. */
.notify-msg { color: var(--ink); }
/* A link on the bare ground cannot be --orange-dark: that is 2.36:1. It takes
   the ink and keeps its underline, which is what carries the affordance. */
:is(section:not(.band-paper):not(.ledge), .hero, .closer) :is(p, li, figcaption, .under, .lede) > a {
  color: var(--ink-deep);
  text-decoration-color: var(--orange);
  text-decoration-thickness: 2px;
}
/* Inside a sheet the ground is cream again and the soft tiers are legal. */
.prose :is(.eyebrow, .stampline, .muted, .small, .byline, .under, .crumbs, .crumbs li, .crumbs a) {
  color: var(--ink-faint);
}
.prose :is(p, li) > a { color: var(--orange-dark); text-decoration-color: currentColor; }


img, svg { display: block; max-width: 100%; height: auto; }
h1, h2, h3, h4 { font-family: var(--display); font-weight: 700; line-height: 1.08; letter-spacing: -.02em; margin: 0; }
a { color: var(--orange-dark); text-underline-offset: 3px; text-decoration-thickness: 1.5px; }
a:hover { color: var(--ink); }
p { margin: 0 0 1em; }
ul, ol { margin: 0 0 1em; padding-left: 1.25em; }
li { margin-bottom: .4em; }

:focus-visible { outline: 3px solid var(--orange-drop); outline-offset: 3px; border-radius: 8px; }

.sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.sr:focus { position: fixed; top: 12px; left: 12px; width: auto; height: auto; clip-path: none; background: var(--card); color: var(--ink); padding: 10px 16px; border-radius: 10px; box-shadow: var(--shadow-lift); z-index: 99; }

/* ---------------------------------------------------------------- layout -- */

.wrap { width: min(var(--wide), 100% - var(--gutter) * 2); margin-inline: auto; }
section { padding-block: var(--band); }
.band-paper { background: var(--paper); border-block: 1px solid var(--edge); }
.prose {
  max-width: calc(var(--prose) + 2 * clamp(20px, 5vw, 44px));
  margin-inline: auto;
  padding: clamp(26px, 6vw, 48px) clamp(20px, 5vw, 44px);
  background: var(--paper);
  border-radius: 24px;
  box-shadow: var(--shadow-sheet);
}
/* Where the section is already cream the sheet would be invisible, so the band
   is the sheet. */
.band-paper .prose { background: none; box-shadow: none; padding-inline: 0; border-radius: 0; max-width: var(--prose); }
.prose h2 { font-size: clamp(26px, 3.4vw, 34px); margin: 2.2em 0 .5em; padding-top: .7em; border-top: 1.5px dotted var(--edge-strong); }
.prose h3 { font-size: clamp(19px, 2.2vw, 22px); margin: 1.8em 0 .4em; }
.prose > p, .prose > ul, .prose > ol { font-size: 18px; line-height: 1.7; }
.prose img { border-radius: 14px; margin-block: 1.6em; }
.prose figcaption { font-family: var(--mono); font-size: 12px; text-transform: uppercase; letter-spacing: .12em; color: var(--ink-faint); margin-top: .7em; }

/* --------------------------------------------------------------- type ----- */

.h-xl { font-size: clamp(40px, 6vw, 68px); line-height: 1.02; letter-spacing: -.03em; }
.h-lg { font-size: clamp(30px, 4.4vw, 44px); }
.h-md { font-size: clamp(21px, 2.6vw, 27px); }
.lede { font-size: clamp(18px, 2.1vw, 21px); line-height: 1.55; color: var(--ink-soft); }
.muted { color: var(--ink-soft); }
.small { font-size: 15px; }

/* The field register: anything the journal would stamp or the card would print */
.eyebrow, .stampline { font-family: var(--mono); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: .16em; color: var(--coffee); margin: 0 0 14px; }
.keynote { font-family: var(--display); font-weight: 600; font-size: clamp(21px, 2.8vw, 27px); line-height: 1.32; color: var(--ink); max-width: 30ch; margin: 0 0 20px; }
.byline { font-family: var(--mono); font-size: 12.5px; color: var(--ink-faint); letter-spacing: .04em; }
.byline a { color: var(--ink); }

/* --------------------------------------------------------------- header --- */

.site-head { position: sticky; top: 0; z-index: 50; background: color-mix(in srgb, var(--ground) 92%, transparent); backdrop-filter: blur(8px); border-bottom: 1px solid var(--edge); }
.site-head .wrap { display: flex; align-items: center; gap: 18px; min-height: 64px; }
.brand { display: inline-flex; align-items: center; gap: 9px; color: var(--ink); text-decoration: none; font-family: var(--display); font-weight: 700; font-size: 19px; letter-spacing: -.02em; }
.brand img { border-radius: 9px; }
.site-nav { display: none; gap: 22px; margin-left: auto; }
.site-nav a { min-height: 44px; display: inline-flex; align-items: center; color: var(--ink-soft); text-decoration: none; font-weight: 500; font-size: 15px; }
.site-nav a:hover, .site-nav a[aria-current] { color: var(--ink); }
.site-nav a[aria-current] { box-shadow: inset 0 -2px 0 var(--orange); }
.site-head .btn-sm { display: none; min-height: 44px; margin-left: 4px; }
@media (min-width: 901px) { .site-head .btn-sm { display: inline-flex; } }
.menu { display: block; margin-left: auto; }
.menu summary { list-style: none; cursor: pointer; display: grid; place-items: center; width: 48px; height: 48px; border: 1px solid var(--edge-strong); border-radius: 11px; background: var(--card); color: var(--ink); }
.menu summary::-webkit-details-marker { display: none; }
.menu-panel { position: absolute; right: var(--gutter); top: 62px; background: var(--card); border: 1px solid var(--edge-strong); border-radius: 14px; box-shadow: var(--shadow-lift); padding: 10px; display: grid; min-width: 220px; }
.menu-panel a { min-height: 48px; display: flex; align-items: center; padding: 9px 12px; border-radius: 9px; color: var(--ink); text-decoration: none; font-weight: 500; font-size: 15px; }
.menu-panel a:hover { background: var(--sunk); }

/* The phone's header is the base: one menu button. The bar of links and the
   header call to action are what a wide window adds. */
@media (min-width: 901px) {
  .site-nav { display: flex; }
  .menu { display: none; }
}

/* -------------------------------------------------------------- buttons --- */

.btn { display: inline-flex; align-items: center; justify-content: center; min-height: 52px; gap: 9px; font-family: var(--display); font-weight: 600; font-size: 16px; padding: 13px 22px; border-radius: 999px; border: 1.5px solid transparent; text-decoration: none; cursor: pointer; transition: background-color .16s, transform .16s, border-color .16s; }
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--orange); color: var(--ink-deep); }
.btn-primary:hover { background: var(--orange-lift); color: var(--ink-deep); }
.btn-ghost { background: transparent; color: var(--ink); border-color: var(--ink); }
.btn-ghost:hover { background: var(--card); color: var(--ink); }
.btn-sm { font-size: 14.5px; padding: 9px 16px; }

/* --------------------------------------------------------------- stores --- */

/* THE STORE BADGES. The lockup everyone already recognises: a black field, a
   hairline, the platform mark, a small line over the wordmark. Both badges
   show from the start; the small line is the only honest part that changes,
   and wireStores() swaps it for the store's own wording the day a URL exists.
   Black rather than a cream chip, because these are the two objects on the
   page a visitor is looking for and they should not be mistaken for tags. */
.stores { display: flex; flex-wrap: wrap; gap: 10px; margin: 16px 0 8px; }
.store-btn {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 8px 16px 8px 13px; border-radius: 10px;
  border: 1px solid #A6A6A6; background: #000000; color: #FFFFFF;
  text-decoration: none; min-height: 52px; flex: 0 1 auto;
}
.store-btn:not([href]) { cursor: default; }
.store-btn[href]:hover { background: #1A1A1A; }
.store-btn[href]:active { transform: translateY(1px); }
.store-btn .lines { display: grid; line-height: 1.1; text-align: left; }
.store-btn .k { font-family: var(--body); font-weight: 400; font-size: 10px; text-transform: uppercase; letter-spacing: .085em; color: #FFFFFF; }
.store-btn .v { font-family: var(--body); font-weight: 500; font-size: 19px; letter-spacing: -.01em; color: #FFFFFF; }
.store-btn svg { flex: none; }
.stores-note { margin: 0 0 18px; font-family: var(--mono); font-size: 11.5px; letter-spacing: .04em; color: var(--ink); }
/* Once anything is downloadable the caption is a lie, and site.js has already
   set the flag it keys off. */
[data-store-live="true"] .stores-note { display: none; }
.stores-compact .store-btn { min-height: 46px; padding: 6px 13px 6px 10px; }
.stores-compact .store-btn .v { font-size: 17px; }

/* ------------------------------------------------------------- waitlist --- */

.notify { margin: 0; }
.notify-row { display: flex; flex-wrap: wrap; gap: 10px; }
.notify input { flex: 1 1 240px; min-height: 52px; padding: 0 16px; border-radius: 13px; border: 1.5px solid var(--edge-strong); background: var(--card); color: var(--ink); font: inherit; font-size: 16px; }
.notify input::placeholder { color: var(--ink-faint); }
.notify input:focus-visible { border-color: var(--orange-dark); }
.notify-msg { font-size: 14px; color: var(--ink-faint); margin: 10px 0 0; }
.notify-msg[data-state="ok"] { color: var(--moss-deep); font-weight: 600; }
.notify-msg[data-state="error"] { color: var(--rust-deep); font-weight: 600; }

/* ----------------------------------------------------------------- hero --- */

.hero { padding-top: clamp(40px, 6vw, 76px); }
.hero-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(30px, 5vw, 64px); align-items: center; }
.hero-grid > .stage-wrap, .hero-grid > .cardstack { order: -1; }
@media (min-width: 861px) {
  .hero-grid { grid-template-columns: 1.02fr .98fr; }
  .hero-grid > .stage-wrap, .hero-grid > .cardstack { order: 0; }
}
.creds { list-style: none; display: flex; flex-wrap: wrap; gap: 8px 10px; padding: 0; margin: 26px 0 0; }
.cred { display: inline-flex; align-items: center; gap: 7px; font-size: 13.5px; font-weight: 600; color: var(--ink-soft); background: var(--card); border: 1px solid var(--edge); border-radius: 999px; padding: 6px 13px; }
.cred svg { color: var(--moss-dark); flex: none; }



/* ------------------------------------------------------- THE CARD -------- */
/* Built to the app's own spec (TradingCard.tsx: aspect 1.4, every measure a
   fraction of the width, reference 340px). It tilts with the pointer, its foil
   sweeps at the app's 20deg, and it flips to the leather-and-gold back. */

.cardstack { display: grid; place-items: center; perspective: 1200px; }
.pcard { --w: 300px; width: var(--w); aspect-ratio: 1 / 1.4; container-type: inline-size; position: relative; transform-style: preserve-3d; transition: transform .5s cubic-bezier(.22, 1, .36, 1); cursor: pointer; border: 0; padding: 0; background: none; font: inherit; color: inherit; }
.pcard.is-flipped { transform: rotateY(180deg); }
.pcard-face { position: absolute; inset: 0; backface-visibility: hidden; border-radius: 18px; overflow: hidden; box-shadow: var(--shadow-card); }
/* THE BACK is the app's own: assets/card-back.webp from the app, the face
   its album shows, with the medallion, the paws and the wordmark painted in.
   It is drawn at the card's exact 1:1.4, so cover crops nothing. The leather
   colour sits under it so a card whose art has not arrived is still a card.
   A new file name on purpose: /assets is cached as immutable, and a returning
   browser holding the old card-back.webp would never see this one. */
.pcard-back {
  transform: rotateY(180deg);
  --leather: #4A2C16;
  background: var(--leather) url("/assets/art/card-back-app.webp") center / cover no-repeat;
}

/* THE CARD BODY IS THE WASH. Measured against the app's own rendered cards
   (Tofu on gold, Mochi on rose, Biscuit on lavender): the wash covers the
   whole card, not a window inside a cream card, the ink is a deep tone of the
   same hue, and every row is a white pill sitting on the wash. The earlier
   cream-bodied version was a spec sheet, and the owner said so. Two variables
   drive a card: --wash (the cat's colour) and --card-ink (its deep tone). */
.pcard { --wash: #F2E3C4; --card-ink: #5A3B1A; --pill: rgba(255, 255, 255, .88); }
.pcard-front {
  background: linear-gradient(155deg, color-mix(in srgb, var(--wash) 62%, #FFFFFF) 0%, var(--wash) 46%, color-mix(in srgb, var(--wash) 82%, #6A5A9A) 100%);
  display: grid; grid-template-rows: auto auto 1fr auto auto auto auto auto auto; gap: 0;
  padding: 3.2cqw 3.2cqw 2.6cqw; align-content: start;
  border: 1.5px solid rgba(255, 255, 255, .55);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .55), var(--shadow-card);
}
/* rarity is carried by the edge, as the app does it */
.pcard[data-rarity="uncommon"] .pcard-front { border: 2px solid rgba(255, 255, 255, .7); box-shadow: inset 0 0 0 1.5px var(--paw-uncommon), var(--shadow-card); }
.pcard[data-rarity="rare"] .pcard-front { border: 2px solid rgba(255, 255, 255, .7); box-shadow: inset 0 0 0 2px var(--paw-rare), var(--shadow-card); }
.pcard[data-rarity="legendary"] .pcard-front { border: 2px solid var(--gold); box-shadow: inset 0 0 0 2px var(--gold-soft), 0 0 0 1px var(--gold-deep), var(--shadow-card), 0 0 26px rgba(240,169,60,.3); }
.pcard[data-rarity="legendary"] .pcard-back { box-shadow: var(--shadow-card), 0 0 26px rgba(240,169,60,.3); }

.pcard-head { display: flex; align-items: center; gap: 2.6cqw; }
.pcard-brand { font-family: var(--mono); font-size: 2.7cqw; font-weight: 500; letter-spacing: .24em; color: #FFFFFF; background: var(--card-ink); border-radius: 999px; padding: 1.3cqw 2.6cqw; }
.pcard-title { font-family: var(--display); font-weight: 700; font-size: 7.2cqw; color: var(--card-ink); line-height: 1; margin-right: auto; letter-spacing: -.02em; }
/* The app's paw glyph, once, as a mask so it takes whatever ink the card has. */
.pcard { --paw-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cellipse cx='12' cy='15.5' rx='5.2' ry='4.2'/%3E%3Ccircle cx='6.2' cy='10.4' r='2.1'/%3E%3Ccircle cx='17.8' cy='10.4' r='2.1'/%3E%3Ccircle cx='9.2' cy='6.6' r='2.1'/%3E%3Ccircle cx='14.8' cy='6.6' r='2.1'/%3E%3C/svg%3E"); }
.pcard-seal { width: 8cqw; height: 8cqw; border-radius: 50%; border: 1.5px solid #5FA97A; background: #DDF1E3; flex: none; display: grid; place-items: center; }
.pcard-seal::before { content: ""; width: 4.6cqw; height: 4.6cqw; background: #2F7A4E; -webkit-mask: var(--paw-mask) center / contain no-repeat; mask: var(--paw-mask) center / contain no-repeat; }
.pcard-sub { font-family: var(--body); font-style: italic; font-size: 3.5cqw; color: var(--card-ink); opacity: .78; margin: 1.6cqw 0 2.4cqw; }

/* The window: the wash again (no inner box), the serial in a white tab, the
   cat with the app's soft ground ellipse. */
/* Clipped, and the picture is capped by HEIGHT as well as width: a wide cat
   (Biscuit, 3:2) fills the width, a square silhouette fills the height, and
   neither one can spill onto the rows below. */
/* grid-template-rows: 100% makes the row definite, so the picture's
   percentage max-height resolves and a wide cat is capped by the window's
   height rather than spilling past its bottom edge. */
.pcard-window { position: relative; border-radius: 10px; overflow: hidden; background: none; display: grid; grid-template-rows: 100%; place-items: center; height: 46cqw; padding: 1.5cqw 0 1cqw; }
.pcard-window::after { content: ""; position: absolute; left: 50%; bottom: 2cqw; width: 58%; height: 5cqw; transform: translateX(-50%); z-index: 0; background: radial-gradient(50% 50% at 50% 50%, rgba(70, 50, 110, .34) 0%, rgba(70, 50, 110, 0) 70%); }
.pcard-window img { position: relative; z-index: 1; width: auto; height: auto; max-width: 90%; max-height: 100%; object-fit: contain; }
.pcard-no { position: absolute; top: 0; left: 0; z-index: 2; background: var(--pill); border-radius: 7px; padding: 1.2cqw 2.8cqw 1.4cqw; font-family: var(--display); font-weight: 600; font-size: 4cqw; color: var(--card-ink); line-height: 1.1; text-align: left; white-space: nowrap; min-width: max-content; }
.pcard-no i { display: block; font-family: var(--mono); font-style: normal; font-weight: 500; font-size: 2.2cqw; letter-spacing: .14em; color: var(--card-ink); opacity: .7; }

.pcard-stamp { justify-self: center; margin: 2.2cqw 0 2cqw; font-family: var(--mono); font-weight: 500; font-size: 3cqw; letter-spacing: .28em; color: var(--card-ink); background: var(--pill); border: 1.5px solid var(--card-ink); border-radius: 999px; padding: 1.3cqw 4.4cqw 1.3cqw 3.4cqw; display: inline-flex; align-items: center; gap: 1.6cqw; }
/* the app's camera glyph, drawn: a body with a lens */
.pcard-stamp::before { content: ""; width: 3.4cqw; height: 2.6cqw; border: .45cqw solid var(--card-ink); border-radius: .6cqw; background: radial-gradient(circle at 50% 55%, var(--card-ink) 0 .55cqw, transparent .7cqw); }
.pcard-marks { display: flex; flex-wrap: wrap; gap: 1.6cqw; justify-content: center; }
.pcard-marks i { font-family: var(--mono); font-style: normal; font-weight: 500; font-size: 2.6cqw; letter-spacing: .18em; color: var(--card-ink); background: var(--pill); border: 1px solid rgba(255,255,255,.9); border-radius: 999px; padding: 1.1cqw 2.8cqw; }
.pcard-role { display: flex; align-items: center; gap: 2.4cqw; margin: 2.4cqw 0 1.8cqw; background: var(--pill); border-radius: 9px; padding: 1.8cqw 2.8cqw; }
/* the title row's paw-in-a-circle: the circle is ::before, the paw is ::after
   laid over it, both in the card's ink */
.pcard-role { position: relative; }
.pcard-role::before { content: ""; flex: none; width: 6.4cqw; height: 6.4cqw; border-radius: 50%; background: color-mix(in srgb, var(--card-ink) 16%, #FFFFFF); }
.pcard-role::after { content: ""; position: absolute; left: calc(2.8cqw + 1.3cqw); top: 50%; width: 3.8cqw; height: 3.8cqw; transform: translateY(-50%); background: var(--card-ink); -webkit-mask: var(--paw-mask) center / contain no-repeat; mask: var(--paw-mask) center / contain no-repeat; }
.pcard-role b { font-family: var(--display); font-weight: 700; font-size: 4.2cqw; color: var(--card-ink); margin-right: auto; }
.pcard-role span { font-size: 3.4cqw; color: var(--gold-deep); font-weight: 700; }
.pcard-stats { display: grid; grid-template-columns: repeat(3, 1fr); background: var(--pill); border-radius: 9px; overflow: hidden; }
.pcard-stats span { display: grid; gap: .6cqw; justify-items: center; padding: 1.8cqw 0 1.6cqw; font-family: var(--display); font-weight: 600; color: var(--card-ink); border-right: 1px solid color-mix(in srgb, var(--card-ink) 14%, transparent); }
.pcard-stats span:last-child { border-right: 0; }
.pcard-stats i { font-family: var(--mono); font-style: normal; font-weight: 500; font-size: 2.3cqw; letter-spacing: .2em; color: var(--card-ink); opacity: .75; text-transform: uppercase; }
.pcard-stats b { color: var(--card-ink); font-size: 4.6cqw; line-height: 1; display: inline-flex; align-items: baseline; gap: .3cqw; }
.pcard-stats b small { font-size: 2.6cqw; font-weight: 500; opacity: .7; }
.pcard-flavour { font-style: italic; font-size: 3.2cqw; color: var(--card-ink); opacity: .85; text-align: center; padding: 2cqw 1cqw 1.6cqw; line-height: 1.35; }
.pcard-foot { display: flex; justify-content: space-between; align-items: center; font-family: var(--mono); font-weight: 500; font-size: 2.5cqw; letter-spacing: .22em; color: var(--card-ink); opacity: .7; border-top: 1px solid color-mix(in srgb, var(--card-ink) 18%, transparent); padding-top: 1.6cqw; }

/* The foil: the app's own slice colours, swept at its own 20deg */
.pcard-foil { position: absolute; inset: 0; border-radius: 16px; overflow: hidden; pointer-events: none; opacity: 0; }
.pcard[data-foil] .pcard-foil { opacity: 1; }
.pcard-foil::after {
  content: ""; position: absolute; top: -40%; bottom: -40%; width: 46%; left: -60%;
  background: linear-gradient(90deg, transparent 0%, rgba(143, 217, 255, .30) 5%, rgba(191, 235, 255, .45) 10%, rgba(255, 255, 255, .72) 20%, rgba(255, 255, 255, .80) 28%, rgba(255, 255, 255, .72) 48%, rgba(255, 233, 166, .48) 59%, rgba(255, 217, 166, .30) 64%, transparent 100%);
  transform: translateX(0) rotate(20deg); opacity: .55;
  animation: foil 5.4s cubic-bezier(.4, 0, .5, 1) infinite;
}
/* The sweep travels on a transform, never on left: a positioned box moving
   by left is a layout shift on every frame, and it was the whole CLS budget. */
@keyframes foil { 0%, 62% { transform: translateX(0) rotate(20deg); } 100% { transform: translateX(387%) rotate(20deg); } }   /* 178% of the card, in its own 46% width */

.pcard-hint { font-family: var(--mono); font-size: 11.5px; text-transform: uppercase; letter-spacing: .13em; color: var(--ink-faint); text-align: center; margin-top: 16px; }

/* The ladder of four */
.ladder { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: clamp(18px, 3vw, 34px); align-items: start; }
.ladder .pcard { --w: 100%; }
.ladder figure { margin: 0; display: grid; gap: 12px; }
.ladder figcaption { font-size: 14.5px; color: var(--ink-soft); }
.ladder figcaption b { display: block; font-family: var(--display); font-size: 17px; color: var(--ink); }

/* ------------------------------------------------------- objects on paper - */

.objects { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: clamp(20px, 3vw, 36px); }
.object { display: grid; gap: 12px; }
.object img { width: clamp(76px, 9vw, 104px); height: auto; transform: rotate(-4deg); filter: drop-shadow(0 8px 14px rgba(74, 46, 34, .16)); }
.object:nth-child(even) img { transform: rotate(5deg); }
.object h3 { font-size: 20px; }
.object p { color: var(--ink-soft); font-size: 15.5px; margin: 0; }

/* index cards: blog listings, help topics, anything enumerable */
.notes { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: clamp(18px, 2.4vw, 26px); }
.note { background: var(--card); border: 1px solid var(--edge-strong); border-radius: 16px; padding: 20px 20px 22px; box-shadow: 0 1px 3px rgba(74, 46, 34, .06); transition: transform .18s, box-shadow .18s; display: grid; gap: 9px; align-content: start; }
.note:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); }
.note h3 { font-size: 20px; letter-spacing: -.015em; }
.note h3 a { color: var(--ink); text-decoration: none; }
.note h3 a:hover { color: var(--orange-dark); }
.note p { color: var(--ink-soft); font-size: 15.5px; margin: 0; }
.note .stampline { margin: 0; }

/* the dark ledge: appears once per page, low */
.ledge { background: linear-gradient(180deg, var(--night) 0%, var(--night-soft) 100%); color: #F3E3D2; border-block: 1px solid var(--night-line); }
.ledge h2, .ledge h3 { color: #FFF4E4; }
.ledge .lede, .ledge p { color: #E2CDB8; }
.ledge .stampline { color: var(--gold); }
.ledge a { color: var(--gold); }
.promises { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 22px; }
.promises li { display: grid; gap: 6px; }
.promises .n { font-family: var(--mono); font-size: 12px; letter-spacing: .16em; color: var(--gold); }
.promises b { font-family: var(--display); font-weight: 600; font-size: 18px; color: #FFF4E4; }
.promises span { font-size: 15px; color: #E2CDB8; }

/* spec tables */
.spec { width: 100%; border-collapse: collapse; font-size: 15.5px; }
.spec caption { text-align: left; font-family: var(--mono); font-size: 12px; text-transform: uppercase; letter-spacing: .14em; color: var(--ink-faint); padding-bottom: 12px; }
.spec th, .spec td { text-align: left; padding: 13px 14px; border-bottom: 1px solid var(--edge); vertical-align: top; }
.spec thead th { font-family: var(--mono); font-size: 11.5px; text-transform: uppercase; letter-spacing: .12em; color: var(--coffee); border-bottom: 1.5px solid var(--edge-strong); }
.spec tbody th { font-weight: 600; color: var(--ink); width: 34%; }
.table-scroll { overflow-x: auto; }

/* ------------------------------------------------------------------ faq --- */

.faq { display: grid; gap: 10px; max-width: 780px; }
.faq details { background: var(--card); border: 1px solid var(--edge-strong); border-radius: 14px; overflow: hidden; }
.faq summary { cursor: pointer; list-style: none; padding: 16px 20px; font-family: var(--display); font-weight: 600; font-size: 17.5px; color: var(--ink); display: flex; justify-content: space-between; gap: 14px; align-items: center; }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; font-family: var(--mono); font-size: 20px; color: var(--orange-dark); flex: none; }
.faq details[open] summary::after { content: "−"; }
.faq .faq-a { padding: 0 20px 18px; color: var(--ink-soft); font-size: 16px; }
.faq .faq-a p:last-child { margin-bottom: 0; }
.jump { display: flex; flex-wrap: wrap; gap: 10px; padding: 0; margin: 0 0 30px; list-style: none; }
.jump a { font-family: var(--mono); font-size: 11.5px; text-transform: uppercase; letter-spacing: .12em; min-height: 44px; display: inline-flex; align-items: center; padding: 0 15px; border-radius: 999px; border: 1px solid var(--edge-strong); background: var(--card); color: var(--coffee); text-decoration: none; }
.jump a:hover { border-color: var(--orange-dark); color: var(--orange-dark); }

/* ----------------------------------------------------- the catch demo ---- */

.stage-wrap { display: grid; justify-items: center; gap: 14px; }
.stage { position: relative; width: min(390px, 100%); aspect-ratio: 390 / 500; border-radius: 26px; overflow: hidden; background: var(--night) url("/assets/art/garden.webp") center 30% / cover no-repeat; box-shadow: var(--shadow-card); touch-action: pan-y; user-select: none; -webkit-user-select: none; }
.stage-veil { position: absolute; inset: 0; background: radial-gradient(ellipse at 50% 35%, transparent 40%, rgba(20, 12, 6, .45) 100%); }
.stage-pill { position: absolute; top: 16px; left: 50%; transform: translateX(-50%); background: rgba(24, 13, 8, .82); color: #FFF4D4; border: 1.5px solid var(--moss); border-radius: 999px; padding: 6px 15px; font-family: var(--display); font-weight: 600; font-size: 13px; white-space: nowrap; }
.stage-ledge { position: absolute; left: 0; right: 0; bottom: 0; height: 40%; background: linear-gradient(180deg, rgba(24, 13, 8, 0) 0%, rgba(24, 13, 8, .55) 22%, rgba(24, 13, 8, .55) 100%); }
.stage-line { position: absolute; left: 0; right: 0; bottom: 27%; border-top: 1px dotted rgba(255, 244, 212, .45); }
.stage-cat { position: absolute; bottom: 27%; height: 21%; width: auto; left: 0; transform-origin: 50% 100%; will-change: transform; filter: drop-shadow(0 4px 5px rgba(0, 0, 0, .4)); }
.stage-can { position: absolute; left: 50%; bottom: 22%; width: 22%; margin-left: -11%; }
.stage-ring { position: absolute; left: 50%; bottom: 22%; width: 30%; aspect-ratio: 1; margin-left: -15%; margin-bottom: -3%; border: 3px solid var(--gold-soft); border-radius: 50%; opacity: 0; }
.stage-instruction { position: absolute; left: 12px; right: 12px; bottom: calc(40% + 12px); text-align: center; }
.stage-instruction span { display: inline-block; background: rgba(67, 48, 29, .88); border: 1px solid #F2DDB8; border-radius: 9px; color: #FFF4D4; font-size: 14px; font-weight: 600; padding: 6px 13px; }
.stage-stamp { position: absolute; left: 0; right: 0; top: 34%; text-align: center; font-family: var(--display); font-weight: 700; font-size: clamp(30px, 8vw, 42px); color: #FFF4D4; text-shadow: 0 3px 10px rgba(0, 0, 0, .6); opacity: 0; pointer-events: none; }
.stage-stamp.show { animation: punch .28s cubic-bezier(.2, 1.6, .4, 1) forwards; }
.stage-stamp.gold { color: var(--gold); }
.stage-stamp.bad { color: #FFD9C7; }
@keyframes punch { from { opacity: 0; transform: scale(.6); } 60% { opacity: 1; transform: scale(1.14); } to { opacity: 1; transform: scale(1); } }
.stage-hold { position: absolute; left: 0; right: 0; bottom: 0; height: 46%; cursor: pointer; touch-action: none; }
.stage-log { font-family: var(--mono); font-size: 12.5px; color: var(--ink-faint); text-align: center; min-height: 1.4em; }

.beats { width: 100%; border-collapse: collapse; font-size: 15px; }
.beats th, .beats td { text-align: left; padding: 11px 12px; border-bottom: 1px solid var(--edge); }
.beats th { font-family: var(--mono); font-size: 11.5px; text-transform: uppercase; letter-spacing: .12em; color: var(--coffee); white-space: nowrap; width: 30%; }
.beats tr.win td, .beats tr.win th { background: var(--moss-soft); }

/* ---------------------------------------------------------------- tools --- */

.tool { background: var(--card); border: 1.5px solid var(--edge-strong); border-radius: 20px; padding: clamp(20px, 3vw, 30px); box-shadow: var(--shadow-lift); display: grid; gap: 20px; grid-template-columns: 1fr; }
.tool label { display: block; font-family: var(--mono); font-size: 11.5px; text-transform: uppercase; letter-spacing: .12em; color: var(--coffee); margin-bottom: 6px; }
.tool select, .tool input[type="text"] { width: 100%; min-height: 48px; padding: 0 14px; border-radius: 12px; border: 1.5px solid var(--edge-strong); background: var(--sunk); color: var(--ink); font: inherit; font-size: 16px; }
.tool .field { margin-bottom: 16px; }
.tool-out { display: grid; place-items: center; gap: 14px; }
.tool-msg { font-size: 14px; color: var(--rust-deep); font-weight: 600; min-height: 1.2em; }
@media (min-width: 760px) { .tool { grid-template-columns: 1.05fr .95fr; align-items: start; gap: 34px; } }

/* --------------------------------------------------------------- closer --- */

.closer { background: var(--paper); border-top: 1px solid var(--edge); }
.closer-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(24px, 4vw, 54px); align-items: center; }
@media (min-width: 801px) { .closer-grid { grid-template-columns: 1.15fr .85fr; } }
.closer-art { display: none; position: relative; place-items: center; }
@media (min-width: 801px) { .closer-art { display: grid; } }
.closer-art img { filter: drop-shadow(0 10px 18px rgba(74, 46, 34, .2)); }
.closer-can { position: absolute; right: 8%; bottom: 0; width: 110px; transform: rotate(-8deg); }


/* --------------------------------------------------------------- footer --- */

.site-foot { background: var(--night); color: #E2CDB8; padding-block: clamp(44px, 6vw, 68px) 30px; }
.site-foot .brand { color: #FFF4E4; }
.foot-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 34px; }
@media (min-width: 521px) { .foot-grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 821px) { .foot-grid { grid-template-columns: 1.6fr repeat(3, 1fr); } }
.site-foot h3 { font-size: 15px; color: #FFF4E4; margin-bottom: 12px; letter-spacing: 0; }
.site-foot ul { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; }
.site-foot a { color: #E2CDB8; text-decoration: none; font-size: 15px; }
.site-foot a:hover { color: var(--gold); }
.safety { font-size: 14px; color: #C6AC94; max-width: 42ch; }
.foot-rule { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 10px; border-top: 1px solid var(--night-line); margin-top: 34px; padding-top: 18px; font-size: 13px; color: #B59872; }


/* ---------------------------------------------------------- page furniture */

.crumbs { margin-bottom: 18px; }
.crumbs ol { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; padding: 0; margin: 0; font-family: var(--mono); font-size: 11.5px; text-transform: uppercase; letter-spacing: .1em; color: var(--ink-faint); }
.crumbs li + li::before { content: "/"; margin-right: 8px; color: var(--edge-strong); }
.crumbs a { color: var(--ink); text-decoration: none; }
.page-head { padding-bottom: 0; }
.callout { background: var(--sunk); border: 1px solid var(--edge-strong); border-left: 4px solid var(--gold); border-radius: 0 14px 14px 0; padding: 18px 20px; margin: 1.8em 0; }
.callout p:last-child { margin-bottom: 0; }
.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: clamp(20px, 3vw, 40px); }
.stack-lg > * + * { margin-top: clamp(30px, 4vw, 48px); }
.center { text-align: center; }
.pill-row { display: flex; flex-wrap: wrap; gap: 8px; padding: 0; margin: 18px 0 0; list-style: none; }
.pill-row li { font-family: var(--mono); font-size: 11.5px; text-transform: uppercase; letter-spacing: .1em; padding: 6px 12px; border-radius: 999px; background: var(--orange-soft); color: var(--tag-ink); }

/* ------------------------------------------------------------- motion ----- */

[data-rise] { opacity: 0; transform: translateY(14px); }
.js [data-rise].shown { opacity: 1; transform: none; transition: opacity .6s ease, transform .6s cubic-bezier(.22, 1, .36, 1); }
html:not(.js) [data-rise] { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
  [data-rise] { opacity: 1; transform: none; }
}

/* The rarity ladder as light rather than four copies of the same card. The
   card itself is a prize and appears once per page at most; repeating it is
   what turns a signature into wallpaper. */
.tiers { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: clamp(18px, 2.6vw, 30px); }
.tiers li { display: grid; gap: 8px; align-content: start; }
.tiers .swatch { height: 74px; border-radius: 14px; border: 2px solid var(--edge-strong); background: radial-gradient(ellipse at 50% 60%, var(--halo-everyday) 0%, var(--halo-everyday) 40%, var(--sunk) 92%); }
.tiers li[data-rarity="uncommon"] .swatch { border: 3px double var(--paw-uncommon); background: radial-gradient(ellipse at 50% 60%, var(--halo-uncommon) 0%, var(--halo-uncommon) 40%, var(--sunk) 92%); }
.tiers li[data-rarity="rare"] .swatch { border-color: var(--paw-rare); background: radial-gradient(ellipse at 50% 60%, var(--halo-rare) 0%, var(--halo-rare) 40%, var(--sunk) 92%); }
.tiers li[data-rarity="legendary"] .swatch { border-color: var(--paw-legendary); background: radial-gradient(ellipse at 50% 60%, var(--halo-legendary) 0%, var(--halo-legendary) 40%, var(--sunk) 92%); box-shadow: 0 0 20px rgba(240,169,60,.28); }
.tiers b { font-family: var(--display); font-weight: 600; font-size: 18px; }
.tiers span:last-child { color: var(--ink-soft); font-size: 15px; }

/* The phone. A frame, not a photograph of a phone: a bitmap mock-up with a
   hand in it dates the moment the hardware changes, and it always looks like
   stock. The screens inside are captures from the real build. */
.screens { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: clamp(24px, 4vw, 48px); }
.screens figure { margin: 0; display: grid; gap: 14px; justify-items: center; }
.screens figcaption { font-family: var(--mono); font-size: 11.5px; text-transform: uppercase; letter-spacing: .13em; color: var(--ink-faint); text-align: center; }
.phone { width: 100%; max-width: 280px; border-radius: 30px; padding: 8px; background: linear-gradient(160deg, #3A2017 0%, #2D170F 60%, #4A2E22 100%); box-shadow: var(--shadow-card); }
.phone img { border-radius: 23px; display: block; width: 100%; height: auto; }

/* ============================================================================
   HOME, REBUILT FOR A PHONE.
   The first version read like an essay: four explanatory tiles, paragraphs of
   prose, and the pictures used as illustration. Most visitors arrive on a
   phone from a shared card or a store listing, decide in about four seconds,
   and never scroll past the first screen. So the hook leads, the pictures do
   the arguing, and the copy under each one is a caption rather than a
   paragraph. Everything below is written phone-first and widened for desks.
   ========================================================================= */

.hero-tight { padding-block: clamp(24px, 4vw, 56px) clamp(36px, 5vw, 72px); }
.hero-tight .h-xl { font-size: clamp(38px, 11vw, 68px); }
.hero-tight .lede { max-width: 32ch; }
.hero-shot { display: grid; justify-items: center; }
.phone-hero { max-width: 300px; }
.hero-copy .creds { margin-top: 20px; }
.hero-copy .cred { font-size: 13px; }

/* The hook: one dark screen with one sentence on it. */
.ledge-hook { padding-block: clamp(48px, 8vw, 92px); }
.ledge-hook .h-lg { font-size: clamp(30px, 8vw, 46px); line-height: 1.06; }

/* Captions, not paragraphs. */
.under { max-width: 42ch; margin: 26px auto 0; color: var(--ink-soft); font-size: 16px; }

/* A card and a screen, side by side on a desk, stacked on a phone. */
.showcase { display: grid; gap: clamp(26px, 5vw, 54px); justify-items: center; align-items: center; margin-top: clamp(28px, 4vw, 44px); }
@media (min-width: 720px) { .showcase { grid-template-columns: auto auto; justify-content: center; } }
.showcase .phone { max-width: 260px; }

/* The pals: a row that scrolls on a phone rather than shrinking to nothing. */
.pals { display: grid; grid-auto-flow: column; gap: clamp(14px, 3vw, 30px); overflow-x: auto; scroll-snap-type: x mandatory; padding: clamp(24px, 4vw, 38px) 4px 10px; margin-inline: calc(var(--gutter) * -1); padding-inline: var(--gutter); scrollbar-width: none; }
.pals::-webkit-scrollbar { display: none; }
.pals figure { margin: 0; display: grid; justify-items: center; gap: 8px; scroll-snap-align: center; }
.pals img { height: clamp(130px, 30vw, 190px); width: auto; filter: drop-shadow(0 10px 16px rgba(74, 46, 34, .18)); }
.pals figcaption { font-family: var(--display); font-weight: 600; font-size: 17px; color: var(--ink); }
@media (min-width: 720px) { .pals { justify-content: center; overflow: visible; margin-inline: 0; } }

.demo-wrap { display: grid; justify-items: center; margin-top: clamp(26px, 4vw, 40px); }

/* On a phone the picture goes first: it is the argument, and the headline is
   the caption for it. */
@media (max-width: 860px) {
  .hero-grid { gap: 22px; }
  /* The hook has to be readable before anything is scrolled. A full-height
     screenshot above it looked handsome and pushed the headline, the promise
     and both buttons off the first screen, which is the only screen most
     phone visitors ever see. Copy first, then a capped shot. */
  .hero-tight { padding-top: 18px; }
  .hero-tight .h-xl { font-size: clamp(34px, 9.5vw, 46px); line-height: 1.04; }
  .hero-tight .lede { font-size: 17px; max-width: 30ch; }
  .phone-hero { max-width: min(70vw, 240px); }
  .hero-copy .creds { margin-top: 16px; }
  .stores { gap: 10px; }
  .btn { min-height: 52px; }
  .notify input { min-height: 54px; font-size: 16px; }
  section { padding-block: clamp(48px, 11vw, 72px); }
  .promises { gap: 18px; }
}

/* The hero is a whole moment rather than a screen: the real cat in the
   viewfinder and the card it becomes, with the card overhanging the phone.
   It carries its own device and its own glow, so it takes no .phone frame. */
.hero-catch { width: 100%; max-width: 430px; height: auto; margin-inline: auto; filter: drop-shadow(0 18px 36px rgba(74, 46, 34, .2)); }
@media (max-width: 860px) { .hero-catch { max-width: min(92vw, 360px); } }

/* ============================================================================
   PHONE HYGIENE, measured on a real iPhone viewport rather than guessed.
   The audit found menu rows at 43px and a summary at 42px, both a hair under
   the 44px Apple and Google both ask for, and a handful of labels under 14px.
   None of it fails an automated check, which is exactly why it survived.
   ========================================================================= */
/* What is left of the old phone-hygiene block. The four floors it used to
   gate are base rules now, because a 44px target and a 16px field are not
   worse on a desktop, and because a touch tablet at 1024 matched none of
   them: nav links measured 24px tall there and the header button 41px. */
@media (max-width: 900px) {
  .cred { font-size: 14px; padding: 8px 14px; }
  .pcard-hint { font-size: 13px; }
}
.faq summary { min-height: 56px; }

/* The short answer, first. A reader who leaves after two sentences still has
   what they came for, and an answer engine has a self-contained passage it can
   quote without dragging in the paragraph around it. */
.takeaway { background: var(--sunk); border: 1.5px solid var(--edge-strong); border-left: 5px solid var(--orange); border-radius: 0 16px 16px 0; padding: 20px 24px; margin: 0 0 2em; }
.takeaway .stampline { margin: 0 0 8px; }
.takeaway p:last-child { margin: 0; font-size: 17.5px; line-height: 1.6; color: var(--ink); }


/* ============================================================== THE FOLD ==
   One column, centred, sized so the whole thing lands above the crease on a
   390x750 phone: headline, the card, the turn, the status, the one CTA. The
   store buttons stay in the markup and are hidden until a store is actually
   live, so nothing has to be unhidden by JS and no dead badge competes with
   the email field before launch. */
.hero-fold { padding-block: 20px 34px; }
.hero-stack { display: grid; justify-items: center; text-align: center; gap: 0; }
.hero-loss { max-width: 15ch; margin: 0; text-wrap: balance; }
.hero-sub { max-width: 30ch; margin: 10px 0 0; font-size: 17px; line-height: 1.45; color: var(--ink-soft); }
.hero-turn { margin: 16px 0 0; font-family: var(--display); font-weight: 600; font-size: 21px; color: var(--ink-deep); }
.hero-status { margin: 6px 0 16px; font-family: var(--mono); font-size: 12.5px; letter-spacing: .04em; color: var(--coffee); }
.hero-status .live { display: none; }
[data-store-live] .hero-status .pre { display: none; }
[data-store-live] .hero-status .live { display: inline; }
/* The badges sit under the field from the start, because the owner asked for
   the pair a visitor is looking for, and the caption under them is what keeps
   that honest while both stores are still closed. */
.hero-fold .stores { margin-top: 14px; justify-content: center; }
@media (min-width: 1024px) { .hero-fold .stores { justify-content: flex-start; } }
.hero-fold [data-waitlist] { width: min(100%, 420px); }
@media (min-width: 760px) {
  .hero-loss { font-size: clamp(44px, 5vw, 62px); }
  .hero-sub { font-size: 19px; }
  /* The phone cap starves the card on a desktop, where the object is the whole
     point and there is room for it to be one. */
  /* Sized against the viewport's HEIGHT, not just its width: a 1280x800
     laptop has plenty of room sideways and none to spare downward, and the
     email field has to stay above the crease on both. */
  .hero-fold [data-waitlist] { width: min(100%, 520px); }
  .hero-fold .notify-row { display: flex; gap: 10px; }
  .hero-fold .notify-row input { flex: 1 1 auto; }
  .hero-fold .notify-row .btn { flex: 0 0 auto; }
}


/* stamp-punch is shared by the hero print and the reveal band */
@keyframes stamp-punch { from { transform: scale(1.5) rotate(-7deg); opacity: 0; } to { transform: none; opacity: 1; } }


/* ===================================================== THE NEW FURNITURE ==

   Everything below exists because the ground changed. */

/* THE MARKER SWIPE. One phrase per page, on the noun that matters. The ink
   never changes colour, so contrast is untouched: --ink-deep is 6.96:1 where
   it crosses amber and 5.40:1 where a descender crosses the orange. */
.hot {
  background-image: linear-gradient(180deg, transparent 60%, var(--orange) 60%, var(--orange) 92%, transparent 92%);
  padding-inline: .06em;
}

/* THE STAMP, lifted from the app's own CAUGHT IRL pill. Used three times
   sitewide and never more: the hero status, the rarity chips, the card. */
.stamp {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--mono); font-size: 11.5px; font-weight: 500;
  text-transform: uppercase; letter-spacing: .18em; color: var(--ink-deep);
  background: var(--paper); border: 1.5px solid var(--ink-deep); border-radius: 999px;
  padding: 7px 14px; transform: rotate(-3deg);
  box-shadow: 0 2px 0 rgba(53, 28, 10, .18);
}

/* ------------------------------------------------------------- the hero --
   Jupi, the starter pal the app actually gives you, planted on the ground with
   his own shadow and the can and yarn on the same ground line. He is a painted
   asset with real transparency, which is the whole reason he replaced the
   photographic cutout: that file was a crude chroma key and it showed. */
.hero-fold { padding-block: 18px 30px; }
.hero-stack { display: grid; justify-items: center; text-align: center; }
.hero-status { margin: 0 0 16px; }
.hero-status .live { display: none; }
[data-store-live] .hero-status .pre { display: none; }
[data-store-live] .hero-status .live { display: inline; }
.hero-loss { max-width: 13ch; margin: 0; }
.hero-sub { max-width: 28ch; margin: 12px 0 0; font-size: 17px; line-height: 1.45; color: var(--ink); }

.hero-scene { position: relative; display: grid; place-items: end center; width: min(100%, 420px); margin: 10px 0 22px; }
.hero-scene::before {
  content: ""; position: absolute; left: 50%; bottom: 4px;
  width: min(84vw, 330px); height: 46px; transform: translateX(-50%);
  background: radial-gradient(50% 50% at 50% 50%, rgba(88, 48, 16, .34) 0%, rgba(88, 48, 16, .15) 46%, transparent 72%);
}
.hero-pal {
  position: relative; height: clamp(196px, 52vw, 300px); width: auto;
  aspect-ratio: 424 / 698; filter: drop-shadow(0 12px 10px rgba(88, 48, 16, .20));
}
.prop { position: absolute; bottom: 0; filter: drop-shadow(0 6px 6px rgba(88, 48, 16, .22)); }
.prop-can { left: 4%; width: 68px; transform: rotate(-11deg); }
.prop-yarn { right: 6%; width: 54px; transform: rotate(7deg); }
.prop-star { top: 6%; right: 10%; width: 28px; bottom: auto; opacity: .9; }
.hero-fold [data-waitlist] { width: min(100%, 420px); }

/* ------------------------------------------------------- the blackout ----
   The page turns off. The reveal screenshot is the most dramatic frame the
   app owns and the site used to bury it at 74vw beside another phone. */
.reveal-band { position: relative; overflow: hidden; padding-block: clamp(56px, 12vw, 110px); }
.reveal-band::before {
  content: ""; position: absolute; inset: -12% -20% auto 50%; width: 140%; height: 130%;
  transform: translateX(-50%); pointer-events: none;
  background: radial-gradient(56% 44% at 50% 40%, rgba(240, 169, 60, .32) 0%, rgba(240, 169, 60, .11) 38%, transparent 72%);
}
.reveal-grid { position: relative; display: grid; gap: clamp(26px, 5vw, 54px); justify-items: center; text-align: center; }
.reveal-shot { margin: 0; }
.reveal-shot img {
  border-radius: 26px; width: min(76vw, 300px); height: auto;
  box-shadow: 0 0 0 1px var(--night-line), 0 30px 70px rgba(0, 0, 0, .55), 0 0 90px rgba(240, 169, 60, .22);
}
.js .reveal-shot[data-rise] { opacity: 0; transform: translateY(30px) scale(.965); }
.js .reveal-shot[data-rise].shown { opacity: 1; transform: none; transition: opacity .7s ease, transform .8s cubic-bezier(.22, 1, .36, 1); }
@media (min-width: 860px) {
  /* The copy is capped at 46ch, so a 1fr column left a lake of empty ground
     between the words and the phone. The pair is sized to its content and
     centred instead. */
  .reveal-grid {
    grid-template-columns: minmax(0, 52ch) auto; justify-content: center;
    gap: clamp(34px, 5vw, 76px); text-align: left; justify-items: start; align-items: center;
  }
}

/* ---------------------------------------------------- the reveal band ----
   The band used to be a sticker, a line, a line and a phone, and everything
   worth reading was inside the phone at 8px. It now sets the card's own text
   as type beside the screenshot of it: the flavour line as a quote, the
   title, the serial. Every string is read off that same capture. */
.reveal-copy { max-width: 46ch; }
.reveal-out {
  margin: clamp(18px, 3vw, 26px) 0 0; padding: 18px 20px 16px;
  border: 1px solid var(--night-line); border-left: 3px solid var(--gold);
  border-radius: 4px 14px 14px 4px; background: rgba(255, 255, 255, .045);
}
.reveal-out blockquote {
  margin: 0; font-family: var(--body); font-style: italic;
  font-size: clamp(17px, 4.4vw, 20px); line-height: 1.4; color: #FFF4E4;
}
.reveal-out blockquote::before { content: "\201C"; }
.reveal-out blockquote::after { content: "\201D"; }
.reveal-out figcaption {
  margin-top: 12px; display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px;
}
.reveal-out figcaption b { font-family: var(--display); font-weight: 700; font-size: 17px; color: var(--gold); }
.reveal-out figcaption span {
  font-family: var(--mono); font-size: 12.5px; letter-spacing: .16em; text-transform: uppercase; color: var(--night-mute);
}
.reveal-foot { margin: 14px 0 0; font-size: 15px; }
.ledge .reveal-foot { color: var(--night-mute); }

/* ---------------------------------------------------------- the pal rail -
   A group photo, not an icon row: they overlap, they are different heights and
   they stand on one ground line. The fifth is uncaptioned because the app has
   not named it and inventing a name would be a fabrication. */
.pal-row {
  display: flex; align-items: flex-end; justify-content: center;
  overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none;
  margin-inline: calc(var(--gutter) * -1); padding: 0 var(--gutter) 8px;
  position: relative;
}
.pal-row::-webkit-scrollbar { display: none; }
.pal-row figure { margin: 0; position: relative; scroll-snap-align: center; flex: none; }
.pal-row img { height: var(--h); width: auto; filter: drop-shadow(0 14px 10px rgba(74, 40, 14, .30)); }
.pal-row figcaption {
  font-family: var(--mono); font-size: 11.5px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink); text-align: center; margin-top: 10px;
}
.pal-row figure:nth-child(1) { --h: 134px; z-index: 2; }
.pal-row figure:nth-child(2) { --h: 158px; z-index: 4; margin-inline: -14px; }
.pal-row figure:nth-child(3) { --h: 178px; z-index: 5; }
.pal-row figure:nth-child(4) { --h: 150px; z-index: 3; margin-inline: -12px; }
.pal-row figure:nth-child(5) { --h: 118px; z-index: 1; opacity: .92; }
/* All five have to fit a 390px phone, because the caption under them names Jupi
   and he was the one scrolled off the left edge. */
@media (max-width: 560px) {
  .pal-row { justify-content: flex-start; }
  .pal-row figure:nth-child(1) { --h: 84px; }
  .pal-row figure:nth-child(2) { --h: 98px; }
  .pal-row figure:nth-child(3) { --h: 112px; }
  .pal-row figure:nth-child(4) { --h: 94px; }
  .pal-row figure:nth-child(5) { --h: 76px; }
  .pal-row figcaption { font-size: 10px; letter-spacing: .12em; }
}

/* --------------------------------------------------------- rarity chips --
   Replaces the four-card ladder, which turned the signature into wallpaper. */
.chips { display: flex; flex-wrap: wrap; gap: 14px; justify-content: center; padding: 0; margin: 0; list-style: none; }
.chips li { display: grid; place-items: center; padding: 18px 10px 14px; border-radius: 18px; min-width: 128px; }
.chips li:nth-child(1) { background: radial-gradient(70% 70% at 50% 40%, var(--halo-everyday), transparent); }
.chips li:nth-child(2) { background: radial-gradient(70% 70% at 50% 40%, var(--halo-uncommon), transparent); }
.chips li:nth-child(3) { background: radial-gradient(70% 70% at 50% 40%, var(--halo-rare), transparent); }
.chips li:nth-child(4) { background: radial-gradient(70% 70% at 50% 40%, var(--halo-legendary), transparent); }
.chips small { display: block; margin-top: 8px; font-size: 13px; color: var(--ink); }

/* The end-of-article pal, one per post, cycled by slug so they are not all the
   same cat. */
.post-pal { display: block; height: 92px; width: auto; margin: 6px auto -6px; }


/* ================================================ RETUNED FOR THE GROUND ==

   These selectors already existed. Only their surfaces changed, and each one
   is here because it either sat on a colour that no longer exists or failed
   contrast on the one that replaced it. */

/* The type scale. Roughly 20% bigger on a phone, which is where the whole
   audience is, paid for by the copy that was cut. */
.h-xl { font-size: clamp(38px, 12.5vw, 76px); line-height: .96; letter-spacing: -.035em; }
.h-lg { font-size: clamp(29px, 8vw, 46px); line-height: 1.02; letter-spacing: -.03em; }
.lede { font-size: clamp(17px, 4.6vw, 21px); line-height: 1.45; font-weight: 500; }
h1, h2, h3, h4 { color: var(--ink-deep); text-wrap: balance; }

/* THE HEADER was the most documentlike element on the site: a row of plain
   links across the top of all 22 pages. It becomes part of the sky. */
.site-head {
  background: color-mix(in srgb, var(--ground-lit) 88%, transparent);
  border-bottom: 1px solid transparent;
}
.site-head[data-stuck] { border-bottom-color: var(--ink-deep); }
.site-nav a { color: var(--ink); }
.site-nav a[aria-current] { color: var(--ink-deep); box-shadow: inset 0 -2px 0 var(--orange); }

/* The primary control. Its shape is carried by a 4px hard underside at
   #8E3204, which is 3.11:1 against the darkest ground stop, so the button
   passes 1.4.11 with no border. The label stays --ink-deep: white on this
   orange is 2.96:1 and unfixable. */
.btn-primary {
  background: var(--orange); color: var(--ink-deep); border: 0;
  box-shadow: 0 4px 0 var(--orange-drop);
}
.btn-primary:hover { background: var(--orange-lift); }
.btn-primary:active { transform: translateY(3px); box-shadow: 0 1px 0 var(--orange-drop); }

/* Every control border was #D0A371 at 2.39:1 and failed 1.4.11. */
.notify-row input, .tool input, .tool select, .tool textarea {
  border: 1.5px solid var(--edge-strong); background: var(--card); min-height: 48px;
}

/* The night bands stop hardcoding their own greys. */
.ledge { background: linear-gradient(180deg, var(--night) 0%, var(--night-soft) 100%); color: var(--night-ink); }
/* :not(.pcard *): a card on the band keeps its own ink; these two rules
   used to reach into it and paint its serial, stamp and stats cream on cream */
.ledge :is(h2, h3, b):not(.pcard *) { color: #FFF4E4; }
.ledge :is(p, .lede, span):not(.pcard *) { color: var(--night-mute); }
.ledge .stampline, .ledge a { color: var(--gold); }
.ledge .promises .n { color: var(--gold); }
.site-foot { background: var(--night); color: var(--night-mute); }
.site-foot h3, .site-foot strong { color: #FFF4E4; }
.site-foot a { color: var(--night-ink); }

/* The phone is the app's own brown, not a generic bezel. */
.phone {
  max-width: 280px; border-radius: 30px; padding: 8px;
  background: linear-gradient(160deg, #4A2C1E 0%, #2D170F 60%, #3A2017 100%);
  box-shadow: 0 3px 8px rgba(45, 26, 15, .20), 0 22px 50px rgba(45, 26, 15, .28);
}
.phone img { border-radius: 23px; }

/* A card, an FAQ disclosure and a note are objects on the ground now, so they
   need their own edge rather than borrowing the page's. */
.faq details, .note, .tool { border: 1.5px solid var(--edge-strong); }
.jump a { background: var(--paper); border: 1.5px solid var(--edge-strong); color: var(--ink-deep); }

/* The playable catch earns more room now that it is the third thing. */
.stage { width: min(430px, 100%); }

/* The closer's painted cats need to separate from an amber ground. */
.closer-cat, .closer-can { filter: drop-shadow(0 10px 14px rgba(88, 48, 16, .28)); }


/* Both badges show before either store is live, because the owner asked for
   the pair a visitor recognises, and the caption under them carries the
   truth. wireStores() only adds an href when that STORE entry has a URL, so
   an inert badge is a dead control by design, not an oversight. */
.store-btn:not([href]) { pointer-events: none; }


/* The skip link only exists once focused, and when it does it must be legible
   against whatever it is sitting on. */
.sr:focus, .sr:focus-visible {
  background: var(--paper); color: var(--ink-deep);
  outline: 3px solid var(--orange-drop); outline-offset: 2px;
}


/* The two lines that carry the pull. The scarcity is a fact about the product
   (cards are numbered in catch order) and the invitation is a fact about the
   game (Jupi is the starter pal everybody is given), so neither needs a
   counter, a countdown or an invented number to work. */
.hero-fomo {
  margin: 14px 0 0; max-width: 30ch;
  font-family: var(--mono); font-size: 12.5px; line-height: 1.5;
  letter-spacing: .02em; color: var(--ink);
}
.hero-fomo strong { color: var(--ink-deep); font-weight: 500; }

/* THE EYEBROW. One generated line over the title, from the store switch in
   build/ui.mjs, so it can only ever name a store that is live. */
.hero-eyebrow {
  margin: 0 0 12px; font-family: var(--mono); font-size: 12px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink);
}
@media (min-width: 1024px) {
  .hero-eyebrow { font-size: 13px; margin-bottom: 16px; }
  /* The card is taller than the copy beside it, and the print grid gives the
     head and the form a row each, centred: the spare height landed as a hole
     between the subtitle and the badges. The head sits on the seam and the
     form hangs from it, so the column is one block centred on the card. */
  .print-grid .print-head { align-self: end; }
  .print-grid .print-form { align-self: start; }
  /* The old title's longest line was 18 characters and capped the size at
     52px in the ~560px head column. This one's is 16, which is room for 58. */
  .hero-print .print-h1 { font-size: 58px; }   /* two classes: the print block's own .print-h1 rule comes later in this file */
  .hero-print .hero-sub { font-size: 21px; }
}

/* THE IPHONE LIST. Under the badges while Android is out and the App Store
   is not: the one visitor the black badges cannot serve gets an address
   field instead of a dead chip. Ghost button, so the badges stay the loudest
   objects in the row. The status line is empty until there is a status. */
.notify-ios { width: 100%; margin-top: 14px; text-align: left; }
.notify-lead { margin: 0 0 8px; font-size: 14px; line-height: 1.4; color: var(--ink); }
.notify-ios .btn-ghost { min-height: 52px; padding-inline: 18px; white-space: nowrap; }
.notify-ios .notify-msg:empty { display: none; }
/* One row everywhere, as in the hero: the field takes what the button leaves. */
.notify-ios .notify-row { flex-wrap: nowrap; }
.notify-ios input { flex: 1 1 0%; width: 0; min-width: 0; }

/* THE STAKES. Three sheets beside the album phone, each one thing the app
   does that makes a card worth more than a photo. */
.stakes { list-style: none; padding: 0; margin: clamp(22px, 3vw, 30px) 0 0; display: grid; gap: 12px; width: 100%; }
.stakes li { display: grid; gap: 4px; padding: 16px 18px; border-radius: 16px; background: var(--card); border: 1px solid var(--edge); box-shadow: var(--shadow-sheet); }
.stakes b { font-family: var(--display); font-weight: 600; font-size: 19px; line-height: 1.15; color: var(--ink-deep); }
.stakes span { font-size: 15px; line-height: 1.45; color: var(--ink-soft); }
@media (min-width: 720px) { .album-grid .stakes { margin-top: 0; } }
/* The App Store chip lands here: one pulse of the app's orange around the
   field, so the tap is seen to arrive. */
.notify-ios.pinged input { animation: field-ping 1.1s ease-out 1; }
@keyframes field-ping {
  0% { box-shadow: 0 0 0 0 rgba(255, 100, 35, .75); border-color: var(--orange); }
  100% { box-shadow: 0 0 0 14px rgba(255, 100, 35, 0); border-color: var(--edge-strong); }
}
@media (prefers-reduced-motion: reduce) { .notify-ios.pinged input { animation: none; border-color: var(--orange); } }
.hero-invite {
  margin: 0 0 12px; font-family: var(--display); font-weight: 600;
  font-size: 18px; color: var(--ink-deep);
}


/* THE FOLD IS A HEIGHT PROBLEM, NOT A WIDTH ONE. A 360x640 phone and a
   1280x800 laptop both have plenty of room sideways and none to spare
   downward, and the email field has to stay above the crease on both. One rule
   keyed to height, compressing everything a little rather than one thing a
   lot. Measured at 360x640, 390x750, 390x844, 430x932 and 1280x800. */
@media (max-height: 880px) {
  .hero-fold { padding-block: 10px 18px; }
  .hero-loss { font-size: clamp(30px, 10.5vw, 46px); }
  .hero-sub { margin-top: 8px; font-size: 16px; }
  .hero-fomo { margin-top: 10px; max-width: 40ch; font-size: 11.5px; }
  .hero-scene { margin: 4px 0 10px; }
  .hero-pal { height: clamp(150px, 40vw, 210px); }
  .hero-invite { font-size: 16px; margin-bottom: 8px; }
  .hero-status { margin-bottom: 10px; }
  .prop-can { width: 52px; }
  .prop-yarn { width: 42px; }
  .prop-star { width: 22px; }
}
@media (max-height: 700px) {
  .hero-pal { height: 132px; }
  .hero-loss { font-size: clamp(26px, 9vw, 36px); }
  .hero-fomo { display: none; }   /* the scarcity line survives below the fold */
}


/* ============================================================================
   THE STICKER BOOK.

   The app is not a field journal. Open it: a chonk in fairy wings and a crown,
   sparkle trails, a lavender holo card, a pink gift box, "Knot Examiner: the
   coil has an owner now." It is cozy, generous and funny, and the people it is
   for grew up on sticker albums and card binders. Everything below moves the
   site into that register without touching a single fact: the section labels
   become the app's own pill stickers, rarity becomes the app's own dot rows,
   and the four painted pals, of which the site had been using one frame out
   of twenty-eight, become a cast that stands together and waves back.

   The ground, the type and the section order are unchanged. This is what sits
   on the ground, and how it talks.
   ========================================================================= */

:root {
  /* The card washes, sampled from the app's own cards: Tofu prints on gold,
     Mochi on rose, Biscuit on lilac. Each pal gets its own, as each cat does. */
  --wash-gold: #F7E9C8;
  --wash-honey: #FFE3B8;
  --wash-rose: #F6DCE4;
  --wash-lilac: #E4DAF4;
  --pad-pink: #F2B7C1;      /* the pals' paw pads, from PocketPalModel */
}

/* ------------------------------------------------------------ stickers --
   A section label in the app's own pill style ("Everyday", "My Day", "Care"):
   rounded display type on cream with a hard drop, sat slightly askew like a
   sticker pressed on by hand. Replaces the mono eyebrow on the home page. */
.sticker {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--display); font-weight: 600; font-size: 15px; line-height: 1;
  color: var(--ink-deep); background: var(--paper);
  border: 2px solid var(--ink-deep); border-radius: 999px;
  padding: 9px 16px; margin: 0 0 16px;
  transform: rotate(-2deg);
  box-shadow: 3px 3px 0 var(--ink-deep);
}
.sticker.tilt-r { transform: rotate(2deg); }
/* `.ledge p` is declared later and wins on specificity, so the night band's
   sticker has to name its own ink or it inherits muted cream on gold. */
.ledge .sticker, .ledge p.sticker { color: var(--night); background: var(--gold); border-color: var(--night); box-shadow: 3px 3px 0 rgba(0, 0, 0, .55); }

/* The app's CAUGHT IRL pill, used inline in a sentence. */
.irl {
  display: inline-block; vertical-align: middle;
  font-family: var(--mono); font-size: .62em; font-weight: 500;
  letter-spacing: .22em; text-transform: uppercase; line-height: 1;
  color: var(--ink-deep); background: var(--paper);
  border: 1.5px solid var(--ink-deep); border-radius: 999px;
  padding: .42em .9em; margin: 0 .1em; transform: rotate(-2deg);
}
.ledge .irl { background: var(--gold); border-color: var(--night); color: var(--night); }

/* --------------------------------------------------------- the twinkle --
   The app's own star prop, three of them, breathing. The app puts stars
   around every reveal; the site had none. Static under reduced motion. */
.twinkle { position: absolute; width: 22px; height: auto; pointer-events: none; opacity: .9; animation: twinkle 2.6s ease-in-out infinite; }
.twinkle:nth-of-type(2) { width: 16px; animation-delay: .8s; }
.twinkle:nth-of-type(3) { width: 26px; animation-delay: 1.5s; }
@keyframes twinkle { 0%, 100% { opacity: .35; transform: scale(.8) rotate(-8deg); } 50% { opacity: 1; transform: scale(1.08) rotate(6deg); } }

/* ----------------------------------------------------------- the cast --
   THE HERO GROUP PHOTO. Four painted pals on one ground line, overlapping
   the way a group of cats actually stands: Jupi in front waving, Miso and Luna
   behind, Mochi at the edge. One shared ground shadow, so they read as one
   object on the page and not four stickers. --cast is Jupi's height and every
   other pal is a ratio of it, so one variable resizes the whole group and the
   fold rules below only have to move one number. */
.cast-hero {
  --cast: clamp(196px, 52vw, 300px);
  position: relative; display: flex; align-items: flex-end; justify-content: center;
  width: min(100%, 760px); margin: 8px auto 20px; padding-bottom: 14px;
}
.cast-hero::before {
  content: ""; position: absolute; left: 50%; bottom: 6px; z-index: 0;
  width: min(92%, 640px); height: 48px; transform: translateX(-50%);
  background: radial-gradient(50% 50% at 50% 50%, rgba(88, 48, 16, .34) 0%, rgba(88, 48, 16, .14) 48%, transparent 72%);
}
.cast-pal {
  position: relative; height: calc(var(--cast) * var(--r, 1)); width: auto;
  filter: drop-shadow(0 12px 10px rgba(88, 48, 16, .22));
}
.cast-hero .miso  { --r: .76; z-index: 2; margin-right: -6%; }
.cast-hero .pip   { --r: 1;   z-index: 4; }
.cast-hero .luna  { --r: .80; z-index: 3; margin-left: -7%; }
.cast-hero .mochi { --r: .62; z-index: 1; margin-left: -4%; }
.cast-hero .twinkle:nth-of-type(1) { top: 4%; left: 14%; }
.cast-hero .twinkle:nth-of-type(2) { top: 22%; right: 10%; }
.cast-hero .twinkle:nth-of-type(3) { top: -2%; right: 26%; }

/* Each pal walks in from its own side and settles, 90ms apart. The
   observer's data-delay does the stagger; this is only the motion. */
.js .cast-pal[data-rise] { opacity: 0; transform: translateY(18px) scale(.94); }
.js .cast-pal[data-rise].shown { opacity: 1; transform: none; transition: opacity .5s ease, transform .6s cubic-bezier(.22, 1, .36, 1); }

@media (max-width: 560px) {
  /* Four on a 390px phone: the outer two step in harder and Mochi tucks in. */
  .cast-hero { --cast: clamp(168px, 46vw, 196px); }
  .cast-hero .miso  { --r: .70; margin-right: -12%; }
  .cast-hero .luna  { --r: .74; margin-left: -13%; }
  .cast-hero .mochi { --r: .56; margin-left: -9%; }
  .cast-hero::before { height: 36px; }
  .twinkle { width: 16px; } .twinkle:nth-of-type(2) { width: 12px; } .twinkle:nth-of-type(3) { width: 18px; }
}
@media (max-height: 880px) {
  .cast-hero { --cast: clamp(150px, 40vw, 210px); margin: 4px auto 10px; }
}
@media (max-height: 700px) {
  .cast-hero { --cast: 132px; }
}

/* ------------------------------------------------------ the pal cards --
   THE SIGNATURE. Each pal on a card printed in its own wash, with the app's
   own personality tag and bio, and a second pose that shows on hover or tap.
   Miso's second pose is her sleeping, because "claims every sunbeam as a
   bed" is the joke and the frame delivers it. Two images stacked and
   cross-faded, so the card never changes height. */
.cast {
  list-style: none; padding: 0; margin: clamp(26px, 4vw, 40px) 0 0;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(12px, 2vw, 22px);
}
@media (max-width: 760px) { .cast { grid-template-columns: repeat(2, 1fr); } }

.palcard {
  position: relative; display: grid; grid-template-rows: auto 1fr auto; gap: 6px;
  padding: 16px 14px 18px; border-radius: 22px;
  background: linear-gradient(170deg, var(--wash) 0%, color-mix(in srgb, var(--wash) 55%, #FFFFFF) 100%);
  border: 2px solid var(--ink-deep);
  box-shadow: 4px 4px 0 var(--ink-deep);
  text-align: center; cursor: pointer; user-select: none; -webkit-user-select: none;
  transition: transform .18s cubic-bezier(.22, 1, .36, 1), box-shadow .18s ease;
}
.palcard:hover, .palcard:focus-within { transform: translate(-2px, -3px); box-shadow: 6px 7px 0 var(--ink-deep); }
.palcard:active { transform: translate(1px, 1px); box-shadow: 3px 3px 0 var(--ink-deep); }
.palcard .tag {
  justify-self: center; font-family: var(--mono); font-size: 10.5px; font-weight: 500;
  letter-spacing: .18em; text-transform: uppercase; color: var(--ink-deep);
  background: rgba(255, 255, 255, .55); border: 1px solid var(--ink-deep); border-radius: 999px; padding: 5px 10px;
}
/* The frame is a real <button>: reset the UA chrome, keep the focus ring. */
.palcard .frame {
  position: relative; height: clamp(150px, 20vw, 210px); width: 100%; display: grid; place-items: end center;
  border: 0; padding: 0; margin: 0; background: none; font: inherit; color: inherit; cursor: pointer; border-radius: 14px;
}
.palcard .frame:focus-visible { outline: 3px solid var(--orange-drop); outline-offset: 4px; }
.palcard .frame::before {
  content: ""; position: absolute; left: 50%; bottom: 2px; width: 70%; height: 22px; transform: translateX(-50%);
  background: radial-gradient(50% 50% at 50% 50%, rgba(88, 48, 16, .28) 0%, transparent 70%);
}
.palcard .frame img {
  position: absolute; bottom: 0; left: 50%; transform: translateX(-50%);
  height: 100%; width: auto; max-width: 100%; object-fit: contain; object-position: bottom;
  transition: opacity .28s ease, transform .28s cubic-bezier(.22, 1, .36, 1);
  filter: drop-shadow(0 8px 8px rgba(88, 48, 16, .18));
}
.palcard .alt { opacity: 0; transform: translateX(-50%) translateY(6px); }
.palcard:hover .base, .palcard.is-alt .base { opacity: 0; }
.palcard:hover .alt,  .palcard.is-alt .alt  { opacity: 1; transform: translateX(-50%); }
.palcard h3 { font-size: clamp(20px, 2.4vw, 24px); margin-top: 8px; letter-spacing: -.02em; }
.palcard .bio { font-size: 14.5px; line-height: 1.4; color: var(--ink); margin: 0; }
.palcard .bio em { font-style: italic; }
.palcard .hint { font-family: var(--mono); font-size: 11.5px; letter-spacing: .14em; text-transform: uppercase; color: var(--ink); margin-top: 8px; opacity: .8; }
/* Room for the sticker above the tag, so the two never overlap on a narrow
   card. */
.palcard.is-you { padding-top: 30px; }
.palcard.is-you::after {
  /* Jupi is the starter every player is given: the app says so, and the card
     wears it as a sticker rather than a sentence. */
  content: "YOURS FROM MINUTE ONE"; position: absolute; top: -12px; right: -8px;
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .16em;
  color: var(--ink-deep); background: var(--orange); border: 2px solid var(--ink-deep);
  border-radius: 999px; padding: 6px 10px; transform: rotate(6deg);
  box-shadow: 2px 2px 0 var(--ink-deep);
}
@media (max-width: 760px) {
  .palcard { padding: 14px 10px 16px; }
  .palcard .frame { height: clamp(132px, 34vw, 170px); }
  .palcard.is-you::after { font-size: 8.5px; padding: 5px 8px; right: -4px; }
}

/* ------------------------------------------------------- the dot rows --
   Rarity as the app draws it on the reveal screen: four dots, the filled
   count is the tier, on the tier's own light. Replaces the halo chips. */
.dots { list-style: none; padding: 0; margin: clamp(22px, 3vw, 30px) 0 0; display: grid; gap: 10px; }
.dots li {
  display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 14px;
  padding: 12px 16px; border-radius: 16px; border: 1.5px solid var(--edge-strong);
  background: var(--card);
}
.dots li[data-tier="everyday"]  { background: linear-gradient(90deg, var(--halo-everyday), var(--card) 60%); }
.dots li[data-tier="uncommon"]  { background: linear-gradient(90deg, var(--halo-uncommon), var(--card) 60%); }
.dots li[data-tier="rare"]      { background: linear-gradient(90deg, var(--halo-rare), var(--card) 60%); }
.dots li[data-tier="legendary"] { background: linear-gradient(90deg, var(--halo-legendary), var(--card) 60%); box-shadow: 0 0 18px rgba(240, 169, 60, .25); }
.dots .pips { display: inline-flex; gap: 5px; }
.dots .pips i { width: 11px; height: 11px; border-radius: 50%; border: 1.5px solid var(--ink-deep); background: transparent; }
.dots .pips i.on { background: var(--paw); }
.dots li[data-tier="everyday"]  { --paw: var(--paw-everyday); }
.dots li[data-tier="uncommon"]  { --paw: var(--paw-uncommon); }
.dots li[data-tier="rare"]      { --paw: var(--paw-rare); }
.dots li[data-tier="legendary"] { --paw: var(--paw-legendary); }
.dots b { font-family: var(--display); font-weight: 600; font-size: 17px; color: var(--ink-deep); }
.dots small { font-size: 13.5px; color: var(--ink-soft); }

/* The album showcase: the real album beside the ladder, stacked on a phone. */
.album-grid { display: grid; gap: clamp(24px, 4vw, 48px); align-items: center; justify-items: center; margin-top: clamp(26px, 4vw, 40px); }
@media (min-width: 720px) { .album-grid { grid-template-columns: auto minmax(260px, 420px); } .album-grid .dots { margin-top: 0; } }

/* -------------------------------------------------------- copy retune --
   The hero sub carries the promise now, so it gets the reading weight. */
.hero-sub { font-weight: 500; }
.under.big { font-size: 17.5px; }

@media (prefers-reduced-motion: reduce) {
  .twinkle { animation: none; opacity: .8; }
  .js .cast-pal[data-rise] { opacity: 1; transform: none; }
  .palcard, .palcard .frame img { transition: none; }
}
/* ------------------------------------------------------ standing loops ----
   Everything that animates for as long as the page is open stops when it is
   not on the screen. site.js wireLoops() adds .out-of-view; nothing here
   depends on the class arriving, so no JavaScript means no pausing rather
   than no animation. */
.out-of-view .pcard-foil::after,
.pcard.out-of-view .pcard-foil::after,
.twinkle.out-of-view,
.out-of-view .twinkle { animation-play-state: paused; }
/* ============================================================================
   THE PHONE SETS THE FLOOR. Measured with scratchpad mobile.js and taps.js at
   360x640 and 390x844 with touch, across the home page, /cards, /the-catch,
   /faq, /blog, a post and a tool. No page overflows sideways. What did fail
   was hit areas and caption sizes, and both are fixed here as base rules
   rather than as a phone-only patch, because a 44px target and a legible
   caption are not worse on a desktop.

   The grow-without-moving pattern is padding-block with an equal negative
   margin-block: the box a thumb can hit gets taller, the text stays where
   the layout put it.
   ========================================================================= */

/* the header wordmark was 102x34 */
.site-head .brand { min-height: 44px; }

/* breadcrumbs were 48x15, the byline link 96x16 */
.crumbs a, .byline a { display: inline-block; padding-block: 14px; margin-block: -14px; }

/* every footer link was 40 tall, and the two short ones (FAQ, Blog) were
   under 44 wide as well */
.site-foot a { min-height: 44px; min-width: 44px; display: inline-flex; align-items: center; }

/* "Print it again" was 105x19 */
.print-again { min-height: 44px; display: inline-flex; align-items: center; padding-inline: 4px; }
.print-hint { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 6px; }

/* captions a phone has to read */
.stores-note { font-size: 13px; }
.stage-log { font-size: 14px; }
.byline { font-size: 13px; }
.crumbs ol { font-size: 12px; }
.hero-print .hero-fomo { font-size: 13px; }
/* ============================================================================
   THE PRINT. The hero is the app's own reveal, performed on a real cat.

   Frame zero: Biscuit, the owner's real cutout, loose on the paper inside four
   viewfinder brackets that drift as they search. Then, in order and on the
   app's own beats: the brackets lock; the shutter flashes and he settles; the
   light drops (a card-shaped night rises behind him and he stays lit in a
   warm pool); the print head crosses the
   blank top to bottom and the card is
   printed under it, row by row, around a cat who never moves; the serial pops
   as the head passes his window; the card ejects a hair and lands; CAUGHT IRL
   punches; the marker draws under "he walks off."; one foil sweep crosses
   it. Done at 3.0s, and from then on it is the site's ordinary
   card: it leans to the pointer and turns over on tap.

   Everything is keyed on .hero-print.beat-1 (site.js wireSequence adds it
   once fonts are ready and the cat has decoded) with the timings carried by
   CSS delays, so the CSS owns every curve. The base state IS the end state:
   a visitor with no JavaScript, or with reduced motion, sees the finished
   card. Removing .beat-1 snaps back to frame zero with no transition, which
   is the replay.
   ========================================================================= */

.hero-print { padding-block: 12px 26px; }
.print-grid {
  display: grid; grid-template-columns: minmax(0, 1fr); grid-template-areas: "head" "stage" "form";
  justify-items: center; text-align: center; row-gap: 10px;
}
.print-head { grid-area: head; }
.print { grid-area: stage; display: grid; justify-items: center; gap: 8px; }
.print-form { grid-area: form; width: min(100%, 420px); display: grid; justify-items: center; }
/* Two sentences, two lines, decided here rather than by whatever width the
   phone happens to be: the first sentence is a block, so the break lands
   between the loss and the remedy and the marker always underlines the
   remedy on its own line.

   The size is the longest line's constraint, not a taste: the head column is
   about 560px at every desktop width because .wrap is capped. "The cat walks
   off." was 18 characters and capped it at 52px; "Catch him before" is 16,
   and the desktop size (58px) is set beside the lockup rules in the main
   body, with two classes so it outranks this line. The site's own .h-xl
   would have set 76px here. Measured at ten viewports from 320x568 up with
   mine-h1.js: one line per span everywhere. */
.print-h1 { max-width: 20ch; font-size: clamp(29px, 9.4vw, 52px); }
.print-h1 > span:first-child { display: block; }
.hero-print .hero-sub { max-width: 34ch; margin-top: 10px; }
.hero-print .hero-fomo { margin-top: 12px; text-align: center; }

/* One line for the email row on every phone: it is the ~50px that keeps the
   button above the crease at 360x640. */
.hero-print .notify-row { flex-wrap: nowrap; }
.hero-print .notify input { flex: 1 1 0%; width: 0; min-width: 0; }   /* no intrinsic width: it is the row that decides */
.hero-print .notify .btn { flex: 0 0 auto; padding-inline: 16px; white-space: nowrap; }

/* THE STAGE. Sized by --w; the card fills it; every overlay is placed in
   cqw of the card so the window geometry below is the card's own. */
.print-stage {
  --w: min(56vw, 208px);
  --win-top: 21.7cqw; --win-h: 46cqw; --win-x: 3.2cqw;
  position: relative; width: var(--w); container-type: inline-size;
  perspective: 1200px; cursor: pointer;
}
.print-stage .pcard { --w: 100%; display: block; }
.print-stage .pcard-window img { display: none; }   /* the cat lives outside the clipped face */

/* THE SCENE. The app's own sakura garden (assets/scenes/sakura.jpg, the one
   the app's comments call the scene that sells the scenes) as a painted
   panel behind the card, the way every card in the app is printed over a
   scene, with the game's own petal sprite drifting through it. The panel
   reaches past the stage by fixed pixels and the stage carries the same
   pixels as margin, so it never covers the subtitle above or the hint below.
   Under everything (z 0); the flash and the print head still cross it. */
.print-stage { margin: 22px 0 18px; }
.print-scene {
  position: absolute; z-index: 0; left: -17cqw; right: -17cqw; top: -22px; bottom: -18px;
  border-radius: 22px; overflow: hidden; pointer-events: none;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .45), 0 16px 36px rgba(60, 32, 12, .22);
}
.print-scene img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 72%; }
/* the light and, during the print, the dark: the same night-life curve the
   card's own blank follows, on a brown that only ever gets 60% of the way */
.print-scene::after {
  content: ""; position: absolute; inset: 0; opacity: 0;
  background: rgba(45, 23, 15, .6);
}
.hero-print.beat-1 .print-scene::after { animation: night-life 1.75s linear both; }
.print-scene i {
  position: absolute; top: -9%; width: 5.5cqw; aspect-ratio: 150 / 160;
  background: url("/assets/props/petal.webp") center / contain no-repeat;
  opacity: .92; will-change: transform;
  animation: petal-fall var(--d, 12s) linear var(--delay, 0s) infinite;
}
.print-scene i:nth-child(1) { left: 12%; --d: 12.5s; --delay: -3s; --sway: 4cqw; }
.print-scene i:nth-child(2) { left: 30%; --d: 14s; --delay: -9s; --sway: -5cqw; width: 4.5cqw; }
.print-scene i:nth-child(3) { left: 52%; --d: 11s; --delay: -1s; --sway: 3cqw; }
.print-scene i:nth-child(4) { left: 68%; --d: 15s; --delay: -6s; --sway: -4cqw; width: 6cqw; }
.print-scene i:nth-child(5) { left: 84%; --d: 13s; --delay: -11s; --sway: 5cqw; width: 4.5cqw; }
.print-scene i:nth-child(6) { left: 42%; --d: 16s; --delay: -13s; --sway: -3cqw; width: 5cqw; }
/* the fall is measured in the card's own width: the panel is about 1.6 cards tall */
@keyframes petal-fall {
  0%   { transform: translate3d(0, 0, 0) rotate(0deg); }
  50%  { transform: translate3d(var(--sway, 4cqw), 82cqw, 0) rotate(160deg); }
  100% { transform: translate3d(0, 170cqw, 0) rotate(330deg); }
}
/* parked with the rest of the loops when the stage is off screen */
.hero-print.done .print-stage.out-of-view .print-scene i { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
  .print-scene i { animation: none; top: auto; }
  .print-scene i:nth-child(1) { top: 18%; } .print-scene i:nth-child(2) { top: 41%; } .print-scene i:nth-child(3) { top: 8%; }
  .print-scene i:nth-child(4) { top: 62%; } .print-scene i:nth-child(5) { top: 30%; } .print-scene i:nth-child(6) { top: 76%; }
}
@media (min-width: 1024px) {
  .print-stage { margin: 34px 0 30px; }
  .print-scene { top: -34px; bottom: -30px; left: -20cqw; right: -20cqw; border-radius: 26px; }
}

/* The night is the card's own silhouette, the blank the print goes onto; the
   pool is the warm light he sits in while it is dark. Both are under the card. */
.print-night, .print-pool { position: absolute; pointer-events: none; }
.print-night { inset: 0; z-index: 1; border-radius: 18px; background: var(--night); opacity: 0; }
.print-pool {
  z-index: 2; left: 0; right: 0; top: calc(var(--win-top) - 10cqw); height: calc(var(--win-h) + 20cqw);
  opacity: 0; transform: scale(.6);
  background: radial-gradient(50% 50% at 50% 56%, rgba(255, 214, 140, .95) 0%, rgba(240, 169, 60, .5) 42%, rgba(240, 169, 60, .1) 68%, transparent 80%);
}
.print-stage .pcard { position: relative; z-index: 3; }

/* The flash (::before) and the print head (::after) are the stage's own.
   No cqw in here: a pseudo-element's container units resolve against the
   stage's ancestors, not the stage. The head is a box as tall as the stage
   plus the clip's 48px margins, with the line painted 12px from its top, so
   translateY(100%) is exactly the clip's journey and the two stay together. */
.print-stage::before {
  content: ""; position: absolute; inset: -14px; z-index: 6; border-radius: 26px;
  background: #FFF6E2; opacity: 0; pointer-events: none;
}
.print-stage::after {
  content: ""; position: absolute; left: -3%; right: -3%; top: -60px; bottom: -36px; z-index: 7; pointer-events: none; opacity: 0;
  background: linear-gradient(180deg, rgba(255, 100, 35, 0) 0, rgba(255, 100, 35, .25) 4px, rgba(255, 226, 160, .85) 10px, #FFFBF0 12px, #FFFBF0 15px, rgba(255, 226, 160, .85) 17px, rgba(255, 100, 35, .28) 25px, rgba(255, 100, 35, 0) 40px) top / 100% 40px no-repeat;
  -webkit-mask: linear-gradient(90deg, transparent, #000 16%, #000 84%, transparent);
  mask: linear-gradient(90deg, transparent, #000 16%, #000 84%, transparent);
}

/* The cat: a direct child of .pcard, over the window's rectangle, so it can
   overhang the card at frame zero and still turn over with it. */
.print-cat {
  position: absolute; z-index: 3; left: var(--win-x); right: var(--win-x); top: var(--win-top); height: var(--win-h);
  display: grid; grid-template-rows: 100%; align-items: center; justify-items: end; padding: 1.5cqw 1.5cqw 1cqw 0; pointer-events: none;
  backface-visibility: hidden; -webkit-backface-visibility: hidden;
}
/* The window and its overlays share one height so the cat lands exactly
   where the card's own window is; phones get a shorter window (below). */
.hero-print .pcard-window { height: var(--win-h); }
/* No drop-shadow filter here. It re-rasterises on every frame the card
   rotates under the pointer, and the window's own ::after ellipse is already
   casting the shadow you can actually see. */
.print-cat img[data-lcp] { width: auto; height: auto; max-width: 90%; max-height: 100%; object-fit: contain; transform-origin: 50% 82%; }

.print-bracket { position: absolute; z-index: 4; width: 7.5cqw; height: 7.5cqw; border: 3px solid var(--ink-deep); pointer-events: none; opacity: 0; }
.print-bracket.tl { --dx: -1; --dy: -1; top: calc(var(--win-top) - 2.6cqw); left: calc(var(--win-x) - 2.6cqw); border-right: 0; border-bottom: 0; border-radius: 4px 0 0 0; }
.print-bracket.tr { --dx: 1; --dy: -1; top: calc(var(--win-top) - 2.6cqw); right: calc(var(--win-x) - 2.6cqw); border-left: 0; border-bottom: 0; border-radius: 0 4px 0 0; }
.print-bracket.bl { --dx: -1; --dy: 1; top: calc(var(--win-top) + var(--win-h) - 4.9cqw); left: calc(var(--win-x) - 2.6cqw); border-right: 0; border-top: 0; border-radius: 0 0 0 4px; }
.print-bracket.br { --dx: 1; --dy: 1; top: calc(var(--win-top) + var(--win-h) - 4.9cqw); right: calc(var(--win-x) - 2.6cqw); border-left: 0; border-top: 0; border-radius: 0 0 4px 0; }


/* Legibility floor at hero size. The card's own type is sized in cqw, which
   at a 200px card puts its finest rows at 5px, so every string on the hero
   card carries a px floor: 8px for the small caps, and the two that sit
   inside a bigger figure (the stat number and its /5) keep their ratio so
   the row still reads as one number. Measured with type.js: nothing on the
   hero card renders under 8px on a 360x640 phone. */
.hero-print .pcard-marks i, .hero-print .pcard-foot, .hero-print .pcard-stats i, .hero-print .pcard-no i,
.hero-print .pcard-brand, .hero-print .pcard-stamp, .hero-print .pcard-role span { font-size: max(2.6cqw, 8px); }
.hero-print .pcard-sub { font-size: max(3.5cqw, 9px); line-height: 5.7cqw; }
.hero-print .pcard-flavour { font-size: max(3.2cqw, 8.5px); }
.hero-print .pcard-role b { font-size: max(4.2cqw, 9.5px); }
.hero-print .pcard-stats b { font-size: max(4.6cqw, 12px); }
.hero-print .pcard-stats b small { font-size: max(2.6cqw, 8px); }

.print-hint { margin: 0; font-family: var(--mono); font-size: 11px; letter-spacing: .08em; white-space: nowrap; text-transform: uppercase; color: var(--ink); text-align: center; }
.print-again { font: inherit; color: var(--ink-deep); background: none; border: 0; padding: 0 0 1px; cursor: pointer; text-decoration: underline; text-decoration-color: var(--orange); text-decoration-thickness: 2px; text-underline-offset: 3px; letter-spacing: inherit; text-transform: inherit; }
.print-again:hover { color: var(--orange-dark); }

/* The front is printed by a clip that travels down it. The clip reaches 48px
   past the card on every side so the drop shadow prints with it; the face
   itself is already overflow-clipped at 18px. The card back is not fetched
   until the sequence is done. */
.hero-print .pcard-front { clip-path: inset(-48px); }
.js .hero-print:not(.done) .pcard-back { background-image: none; }
.js .hero-print:not(.done) .pcard { pointer-events: none; }
/* Hidden, not transparent. As a fading opacity it spent the whole three
   seconds part-way between the ink and the ground, which is a real
   contrast failure while it lasts (Lighthouse caught it at 2.91:1) and
   also announces a tap that is not available yet. */
.js .hero-print:not(.done) .print-hint { visibility: hidden; }
.hero-print.done .print-stage { cursor: default; }

/* ---- FRAME ZERO (js only; without js the base state is the end state) --- */
.js .hero-print:not(.beat-1) .pcard-front { clip-path: inset(-48px -48px calc(100% + 48px) -48px); }
.js .hero-print:not(.beat-1) .pcard-front > :is(.pcard-head, .pcard-sub) { opacity: 0; transform: translateY(-8px); }
.js .hero-print:not(.beat-1) .pcard-front > :is(.pcard-marks, .pcard-role, .pcard-stats, .pcard-flavour, .pcard-foot) { opacity: 0; transform: translateY(8px); }
.js .hero-print:not(.beat-1) .pcard-stamp { opacity: 0; }
.js .hero-print:not(.beat-1) .pcard-no { opacity: 0; transform: scale(1.4); }
.js .hero-print:not(.beat-1) .print-cat img[data-lcp] { transform: scale(1.18); }
.js .hero-print:not(.beat-1) .print-bracket { opacity: 1; animation: bracket-seek 1.3s ease-in-out infinite alternate; }
@keyframes bracket-seek {
  from { transform: translate(calc(var(--dx) * .6cqw), calc(var(--dy) * .6cqw)); }
  to   { transform: translate(calc(var(--dx) * 2cqw), calc(var(--dy) * 2cqw)); }
}
.js .hero-print:not(.beat-1) .print-h1 .hot { background-size: 0% 100%; }
.js .hero-print:not(.beat-1) .pcard-foil::after { animation: none; }
.print-h1 .hot { background-repeat: no-repeat; background-size: 100% 100%; }

/* ---- THE SEQUENCE, in milliseconds from .beat-1 ------------------------- */
/* 0 to 250: the brackets lock. 300: the flash; they are gone inside it. */
.hero-print.beat-1 .print-bracket { animation: bracket-life .42s cubic-bezier(.22, 1, .36, 1) both; }
@keyframes bracket-life {
  0%   { opacity: 1; transform: translate(calc(var(--dx) * 1.4cqw), calc(var(--dy) * 1.4cqw)); }
  60%  { opacity: 1; transform: none; }          /* 250ms: locked */
  72%  { opacity: 1; }                           /* 300ms: the flash */
  100% { opacity: 0; transform: none; }          /* 420ms */
}
.hero-print.beat-1 .print-stage::before { animation: flash-pop .3s ease-out .3s both; }
@keyframes flash-pop { 0% { opacity: 0; } 22% { opacity: .96; } 100% { opacity: 0; } }
/* he settles as the shutter closes */
.hero-print.beat-1 .print-cat img[data-lcp] { transition: transform .5s cubic-bezier(.22, 1, .36, 1) .3s; }

/* 420 to 770: the light drops. 540 to 1000: the pool warms. */
.hero-print.beat-1 .print-night { animation: night-life 1.75s linear both; }
@keyframes night-life {
  0%, 24% { opacity: 0; }                        /* 420ms */
  44%     { opacity: 1; }                        /* 770ms */
  91%     { opacity: 1; }                        /* 1600ms: printed over */
  100%    { opacity: 0; }
}
.hero-print.beat-1 .print-pool { animation: pool-bloom 1.75s cubic-bezier(.22, 1, .36, 1) both; }
@keyframes pool-bloom {
  0%, 31% { opacity: 0; transform: scale(.6); }  /* 540ms */
  57%     { opacity: 1; transform: scale(1); }   /* 1000ms */
  74%     { opacity: 1; }                        /* 1300ms */
  100%    { opacity: 0; transform: scale(1); }
}

/* 1150 to 1700: THE PRINT. The head crosses the blank and the clip follows
   it; each row lands as the head passes it (the delays are the head's
   arrival times, top to bottom, on a card 140cqw tall plus the 48px margins). */
.hero-print.beat-1 .print-stage::after { animation: print-head .55s linear 1.15s both; }
@keyframes print-head {
  0%   { transform: translateY(0); opacity: 0; }
  6%   { opacity: 1; }
  94%  { opacity: 1; }
  100% { transform: translateY(100%); opacity: 0; }
}
.hero-print.beat-1 .pcard-front { transition: clip-path .55s linear 1.15s; }
/* The delay lives in each row's own shorthand. A separate transition-delay
   rule loses to a more specific shorthand (which resets delay to 0), and
   that is the bug that had every row printing at once. */
.hero-print.beat-1 .pcard-front > .pcard-head    { transition: opacity .3s ease 1.22s, transform .3s cubic-bezier(.22, 1, .36, 1) 1.22s; }
.hero-print.beat-1 .pcard-front > .pcard-sub     { transition: opacity .3s ease 1.26s, transform .3s cubic-bezier(.22, 1, .36, 1) 1.26s; }
.hero-print.beat-1 .pcard-front > .pcard-marks   { transition: opacity .3s ease 1.42s, transform .3s cubic-bezier(.22, 1, .36, 1) 1.42s; }
.hero-print.beat-1 .pcard-front > .pcard-role    { transition: opacity .3s ease 1.47s, transform .3s cubic-bezier(.22, 1, .36, 1) 1.47s; }
.hero-print.beat-1 .pcard-front > .pcard-stats   { transition: opacity .3s ease 1.52s, transform .3s cubic-bezier(.22, 1, .36, 1) 1.52s; }
.hero-print.beat-1 .pcard-front > .pcard-flavour { transition: opacity .3s ease 1.57s, transform .3s cubic-bezier(.22, 1, .36, 1) 1.57s; }
.hero-print.beat-1 .pcard-front > .pcard-foot    { transition: opacity .3s ease 1.62s, transform .3s cubic-bezier(.22, 1, .36, 1) 1.62s; }
/* the serial pops as the head reaches his window, 1300ms */
.hero-print.beat-1 .pcard-no { animation: serial-pop .22s cubic-bezier(.3, 1.6, .5, 1) 1.3s both; }
@keyframes serial-pop { from { opacity: 0; transform: scale(1.4); } to { opacity: 1; transform: none; } }

/* 1700: the card ejects a hair and lands, 2400 settled */
.hero-print.beat-1 .print-stage { animation: stage-settle 2.4s cubic-bezier(.22, 1, .36, 1) both; }
@keyframes stage-settle {
  0%, 70% { transform: none; }                                        /* until the print is done */
  77%     { transform: perspective(900px) translateY(-8px) rotateX(6deg); } /* 1850ms: ejected */
  100%    { transform: none; }                                        /* 2400ms: landed */
}
/* CAUGHT IRL, 1900ms */
.hero-print.beat-1 .pcard-stamp { animation: stamp-punch .34s cubic-bezier(.3, 1.6, .5, 1) 1.9s both; }
/* the marker, 1950ms */
.hero-print.beat-1 .print-h1 .hot { transition: background-size .35s cubic-bezier(.22, 1, .36, 1) 1.95s; }
/* one foil sweep at 2300ms, then the standing 5.4s loop. The loop's sweep
   occupies its last 38%; a negative delay starts the cycle 1048ms in, so the
   first sweep begins exactly at 2300ms and every later one is the loop. */
.hero-print.beat-1 .pcard-foil::after { animation-delay: -1.048s; }

@media (min-width: 1024px) {
  .print-grid {
    grid-template-columns: 1.02fr .98fr; grid-template-areas: "head stage" "form stage";
    align-items: center; justify-items: start; text-align: left; column-gap: clamp(28px, 4vw, 64px); row-gap: 22px;
  }
  .print { justify-self: center; }
  .print-form { justify-items: start; width: min(100%, 520px); }
  .print-h1 { max-width: 20ch; }
  .hero-print .hero-fomo { text-align: left; }
  .print-stage { --w: 300px; }
  .hero-print { padding-block: 26px 40px; }
}
@media (min-width: 1024px) and (max-height: 760px) { .print-stage { --w: 260px; } }
@media (min-width: 1200px) and (min-height: 860px) { .print-stage { --w: 340px; } }   /* a big desktop gets the card at album size */
/* Under 1024px the card is 208px or less, where the 8px type floor makes
   every row taller than its cqw budget; a shorter window and tighter pills
   pay for it so the foot stays on the card. */
@media (max-width: 1023px) {
  .print-stage { --win-h: 40cqw; }
  .hero-print .pcard-marks { gap: 1.2cqw; }
  .hero-print .pcard-marks i { padding: 1cqw 1.6cqw; letter-spacing: .08em; }
  .hero-print .pcard-stamp { margin: 1.8cqw 0 1.6cqw; padding: 1.1cqw 3.6cqw 1.1cqw 3cqw; }
  .hero-print .pcard-role { margin: 1.8cqw 0 1.4cqw; padding: 1.5cqw 2.6cqw; }
  .hero-print .pcard-stats span { padding: 1.4cqw 0 1.2cqw; }
  .hero-print .pcard-flavour { padding: 1.6cqw 1cqw 1.2cqw; }
}
@media (max-width: 1023px) and (max-height: 700px) {
  .hero-print .pcard-marks i { padding: 1cqw 1.2cqw; letter-spacing: .05em; }
  .print-hint { display: none; }
  .hero-print .hero-sub { font-size: 15px; }
  .hero-print { padding-block: 6px 16px; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-print *, .hero-print *::before, .hero-print *::after {
    animation-duration: .001ms !important; animation-delay: 0s !important;
    transition-duration: .001ms !important; transition-delay: 0s !important;
  }
  .js .hero-print:not(.beat-1) .print-bracket { animation: none; }
  .hero-print.done .print-stage .twinkle { animation: none; opacity: .8; transform: none; }
  .hero-print .pcard-foil::after { animation: none; }
}
