/* Caveat: the handwritten lines ("Greetings from the group chat", the sign-off), the same
   script as the store screenshots. Self-hosted, like Barlow for the legal pages. */
@font-face { font-family: 'Caveat'; src: url('/fonts/Caveat.ttf') format('truetype'); font-weight: 400 700; font-display: swap; }
/* ── THE FIELD ─────────────────────────────────────────────────────────────
   The green the postcard lies on, in three finished settings. Exactly ONE of
   these three lines must be uncommented; each one carries its whole version
   of the page — gradient stops, the card's shadow, the shadow under the
   screenshots, the text colours that sit on the green (all AA at the darkest
   point of the gradient), and how hard the drifting pins and tracks are
   allowed to push. They are not a hue slider; swapping the line swaps the
   page. Dark mode overrides all three further down and ignores the choice.

   A — the original, lightest.
   B — one step deeper.  LIVE.
   C — two steps deeper.
   ───────────────────────────────────────────────────────────────────────── */
/* A */
/* :root { --field:#D9EFE0; --field-deep:#C3E4D2; --wash:.55; --muted-field:#5A5646; --accent-field:#23686F; --pin-boost:1; --print:#16233C; --print-o:.75; --shadow:0 26px 60px -34px rgba(36,58,99,.55), 0 2px 6px rgba(36,58,99,.06); --phone-shadow:0 20px 40px -26px rgba(36,58,99,.85); } */
/* B */
:root { --field:#C6E6D2; --field-deep:#B2DCC4; --wash:.46; --muted-field:#545041; --accent-field:#1D5B61; --pin-boost:.96; --print:#16233C; --print-o:.75; --shadow:0 26px 62px -36px rgba(28,72,56,.5), 0 2px 6px rgba(28,72,56,.07); --phone-shadow:0 2px 4px rgba(28,72,56,.14), 0 22px 42px -26px rgba(28,72,56,.8); }
/* C */
/* :root { --field:#B2DCC4; --field-deep:#9CCFB2; --wash:.38; --muted-field:#494637; --accent-field:#17494E; --pin-boost:.90; --print:#16233C; --print-o:.75; --shadow:0 28px 66px -38px rgba(22,64,48,.45), 0 3px 8px rgba(22,64,48,.09); --phone-shadow:0 2px 5px rgba(20,60,46,.2), 0 24px 46px -26px rgba(20,60,46,.8); } */

/* ═══════════════════════════════════════════════════════════════════════════
   Pingy — joinpingy.com

   A postcard, because a postcard is the thing you send from a trip. The form
   is borrowed from stingwaveapp.com/cubby — one card, two sides, turn it over
   — but the weather is the opposite: Cubby is a postcard from the ice, all
   glacial blue on black. Pingy is the one that arrives in July. Cream paper,
   navy fountain-pen ink, a mint field behind it, and the penguin as the stamp.

   No web fonts, deliberately. Four downloads for four voices is the whole
   weight of a page like this, and the warmth here is doing its work through
   colour and paper rather than a display face. The three voices are all
   already on the device: the system UI face for everything, Georgia italic
   for the handwriting (a real serif that every platform has, and it reads as
   written rather than typed), and the system mono for the labels a post
   office would stamp.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* Straight out of src/ui/theme.ts, so the page and the app are the same
     blues. `ink` is the navy; the papers are the app's two surfaces. */
  --ink:      #243A63;
  --ink-deep: #1A2B4A;
  --paper:    #FAF7F1;
  --paper2:   #F1EADE;
  --text:     #243A63;
  --muted:    #6A6558;
  --body:     #4F4B42;   /* supporting text on the card: darker than muted, for reading */
  --faint:    #6E6A59;
  --rule:     rgba(36, 58, 99, .16);
  --dash:     rgba(36, 58, 99, .28);

  /* The mint the phone stands on inside the hero clip, sampled off the frame.
     Pinned to the video, NOT to the field above: the well must match the clip
     whatever the page's green is doing. */
  --video-mint: #D1EDDB;

  /* Brand accents. Teal is the penguin's ground, marigold his beak. `accent`
     is the darkened teal used wherever a colour has to carry text — the flat
     teal is 2.5:1 on cream and is only ever a fill. */
  --teal:     #4EA5AC;
  --accent:   #23686F;
  --marigold: #E8942B;
  --sky:      #41B6E6;

  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --hand: "Caveat", Georgia, "Iowan Old Style", "Palatino Linotype", Palatino, serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

/* Dusk. The postcard does not turn into a dark-grey card — it goes to the
   app's own dark surfaces, which is what somebody who has the app installed
   will recognise. The mint field becomes the night outside it. */
@media (prefers-color-scheme: dark) {
  :root {
    --ink:      #EAF1F6;
    --ink-deep: #C9DAE6;
    --paper:    #1B2A47;
    --paper2:   #16233C;
    --text:     #EAF1F6;
    --muted:    #A3B6C6;
    --body:     #C4D3DE;
    --faint:    #8195A6;
    --rule:     rgba(234, 241, 246, .16);
    --dash:     rgba(234, 241, 246, .26);
    --accent:   #7FD3D8;
    /* Dusk overrides the field choice entirely. */
    --field:      #101A2C;
    --field-deep: #0B1422;
    --wash:       .06;
    --muted-field:#A3B6C6;
    --accent-field:#7FD3D8;
    --print:      #A9BFD0;
    --print-o:    .42;
    --pin-boost:  1.35;
    --shadow:   0 26px 60px -30px rgba(0, 0, 0, .8), 0 0 0 1px rgba(234, 241, 246, .05);
    --phone-shadow: 0 0 0 1px rgba(234, 241, 246, .16), 0 20px 40px -26px #000;
  }
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%;
  /* Everything is in rem, so this one line sets the whole page's type: the reader's own default
     on a phone, growing to 115% of it on a laptop or monitor. Percent-based, so the reader's
     font-size setting and browser zoom still scale it. */
  font-size: clamp(93.75%, 93.75% + .25vw, 115%); }   /* site.js may lower this so the postcard fits the window's height */

body {
  font-family: var(--sans);
  font-size: 100%;          /* never px: this is the reader's own setting */
  line-height: 1.5;
  color: var(--text);
  background: var(--field);
  background-image:
    radial-gradient(1100px 620px at 50% -10%, rgba(255, 255, 255, var(--wash)), transparent 62%),
    radial-gradient(760px 420px at 88% 8%, rgba(78, 165, 172, .14), transparent 60%),
    linear-gradient(180deg, var(--field) 0%, var(--field-deep) 100%);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
  min-height: 100svh;
  padding: 0 clamp(16px, 4vw, 26px) 0;
  overflow-x: hidden;
}

img, video { max-width: 100%; display: block; }
[hidden] { display: none !important; }

/* z-index 1 keeps every word above the drifting field behind it. */
.page { width: 100%; max-width: clamp(1060px, 74vw, 1360px); margin: 0 auto; position: relative; z-index: 1; }

.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.skip {
  position: absolute; left: -9999px; top: 8px;
  background: var(--ink); color: var(--paper); padding: 10px 16px;
  border-radius: 8px; text-decoration: none; z-index: 50;
}
.skip:focus { left: 16px; }

a { color: var(--accent); }
:focus-visible { outline: 2.5px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

/* ── masthead ─────────────────────────────────────────────────────────── */
.masthead {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap;
  padding: clamp(18px, 3vw, 30px) 2px 0;
}
.brand {
  display: inline-flex; align-items: center; gap: 10px;
  font-size: clamp(1.5rem, 1.2rem + 1vw, 1.9rem); font-weight: 800;
  letter-spacing: -.028em; color: var(--ink); text-decoration: none;
}
.brand img { width: 36px; height: 36px; border-radius: 9px; box-shadow: 0 2px 6px rgba(36, 58, 99, .18); }
.eyebrow {
  /* The only line above the fold that says what Pingy is, so it is sized as
     a peer of the wordmark rather than as a caption under it. Tracking eased
     from .16 to .14 to buy back the width the larger size costs. */
  font-family: var(--mono); font-size: 1rem; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--accent-field);
  display: inline-flex; align-items: center; gap: 8px;
}
.dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--teal);
  animation: pulse 2.6s infinite;
}
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 rgba(78, 165, 172, .5); }
  70%  { box-shadow: 0 0 0 7px rgba(78, 165, 172, 0); }
  100% { box-shadow: 0 0 0 0 rgba(78, 165, 172, 0); }
}
.nav-cta {
  font-size: .92rem; font-weight: 650; text-decoration: none;
  color: var(--paper); background: var(--ink);
  padding: 10px 20px; border-radius: 999px;
  box-shadow: 0 6px 18px -8px rgba(36, 58, 99, .7);
}
.nav-cta:hover { background: var(--ink-deep); }

/* ── the postcard ─────────────────────────────────────────────────────── */
.scene { perspective: 2600px; margin-top: clamp(20px, 4vw, 34px); }
/* Room between the masthead and the card, even where .fold centres the card with auto margins. */
.masthead { margin-bottom: clamp(14px, 2.2vw, 26px); }
.faces { display: grid; }
/* The flip, drawn the same in every browser: the card turns edge-on, the other face swaps in
   at the half-way point, and it turns back. No 3D back face (Firefox drew it blank). site.js
   adds .flipping for one turn. */
.faces.flipping { animation: turn .8s linear; }
/* One continuous spin: the front turns away to edge-on, the other face swaps in and keeps
   turning the same way into view, so it reads as a whole card turning over, not a wobble. */
@keyframes turn {
  0%     { transform: perspective(2600px) rotateY(0deg);   animation-timing-function: cubic-bezier(.5, 0, 1, 1); }
  49.99% { transform: perspective(2600px) rotateY(90deg); }
  50%    { transform: perspective(2600px) rotateY(-90deg); animation-timing-function: cubic-bezier(0, 0, .5, 1); }
  100%   { transform: perspective(2600px) rotateY(0deg); }
}
/* Out of the page's flow and always in view, so focusing it never scrolls the page. */
.flip-state { position: fixed; top: 50%; left: 0; }

.face {
  grid-area: 1 / 1;
  background: linear-gradient(158deg, var(--paper) 0%, var(--paper2) 100%);
  border: 1px solid var(--rule);
  border-radius: 16px; padding: 12px;
  box-shadow: var(--shadow);
  position: relative; overflow: hidden;
  /* visibility, not just a hidden backface: the face turned away must be out
     of the tab order and out of the accessibility tree, and it swaps at the
     half-way point of the turn. */
  transition: visibility 0s linear .4s;
}
/* Paper tooth. One tiny inline SVG at a whisper of opacity; it is the
   difference between cream card stock and a cream rectangle, and at anything
   above .05 it stops being paper and starts being dust. */
.face::after {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  opacity: .04; mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
.face .keyline { z-index: 1; }
.face.back { visibility: hidden; }
.flip-state:checked ~ .scene .face.front { visibility: hidden; }
.flip-state:checked ~ .scene .face.back  { visibility: visible; }

/* The keyline, and the stamp's corner. */
.keyline {
  position: relative; height: 100%;
  border: 1.5px dashed var(--dash); border-radius: 10px;
}
.stamp {
  position: absolute; top: 4px; right: 4px; z-index: 6; width: 84px;
  transform: rotate(3.5deg); background: var(--paper); padding: 6px;
  filter: drop-shadow(0 5px 10px rgba(36, 58, 99, .3));
  --d: 4px; --g: 11px;
  -webkit-mask:
    radial-gradient(var(--d) at var(--d) var(--d), #0000 98%, #000) calc(-1 * var(--d)) calc(-1 * var(--d)) / var(--g) var(--g) round,
    linear-gradient(#000 0 0) 50% / calc(100% - var(--g)) calc(100% - var(--g)) no-repeat;
  mask:
    radial-gradient(var(--d) at var(--d) var(--d), #0000 98%, #000) calc(-1 * var(--d)) calc(-1 * var(--d)) / var(--g) var(--g) round,
    linear-gradient(#000 0 0) 50% / calc(100% - var(--g)) calc(100% - var(--g)) no-repeat;
}
.stamp img { width: 100%; height: auto; border-radius: 2px; }
.postmark {
  position: absolute; top: 10px; right: 100px; z-index: 5;
  width: 78px; height: 78px; border-radius: 50%;
  border: 1.5px solid rgba(36, 58, 99, .3); transform: rotate(-12deg);
  display: flex; align-items: center; justify-content: center; text-align: center;
  opacity: .62; pointer-events: none;
}
@media (prefers-color-scheme: dark) { .postmark { border-color: rgba(234, 241, 246, .32); } }
.postmark::before { content: ""; position: absolute; inset: 6px; border: 1px dashed var(--dash); border-radius: 50%; }
.postmark span {
  font-family: var(--mono); font-size: .52rem; font-weight: 700;
  letter-spacing: .12em; line-height: 1.6; text-transform: uppercase; color: var(--ink);
}

/* Both faces share one height so the card cannot resize mid-turn, and that
   height follows the viewport so a 13-inch laptop gets a whole postcard
   rather than a cropped one. */
/* height:100% so the grid fills the face — both faces share one height,
   and without it the shorter one's slack all collects at the bottom. */
.card-grid { display: grid; height: 100%; min-height: clamp(380px, 60svh, 600px); }
.front-grid { grid-template-columns: 1fr 1.08fr; }
.back-grid  { grid-template-columns: 1.06fr 1fr; }
.pane { padding: clamp(18px, 2.4vw, 26px); }

/* front — the pitch */
.pane.say { display: flex; flex-direction: column; justify-content: center; gap: 12px; border-right: 1.5px dashed var(--dash); }
.hand { font-family: var(--hand); font-weight: 600; font-size: clamp(1.55rem, 1.3rem + .8vw, 2rem); line-height: 1.08; color: var(--accent); }
h1 {
  font-size: clamp(1.62rem, 1.15rem + 2.1vw, 2.42rem); font-weight: 800;
  letter-spacing: -.028em; line-height: 1.08; text-wrap: balance; color: var(--ink);
}
.blurb { font-size: clamp(1rem, .94rem + .25vw, 1.08rem); line-height: 1.55; color: var(--body); max-width: 48ch; }
.buy { display: flex; flex-direction: column; align-items: flex-start; gap: 9px; margin-top: 2px; }
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--ink); color: var(--paper); font-weight: 650; font-size: .98rem;
  text-decoration: none; border: none; border-radius: 12px; padding: 13px 22px;
  box-shadow: 0 10px 24px -12px rgba(36, 58, 99, .8);
  transition: transform .16s ease, box-shadow .16s ease;
}
.btn:hover { transform: translateY(-1px); box-shadow: 0 14px 28px -12px rgba(36, 58, 99, .9); }
.fineprint { font-family: var(--mono); font-size: .7rem; letter-spacing: .08em; text-transform: uppercase; color: var(--faint); }

/* front — the clip */
.pane.media { display: flex; flex-direction: column; justify-content: center; }
/* The stamp sits in the card's top-right corner; the clip always starts below it. */
@media (min-width: 860px) { .pane.media { padding-top: 5.6rem; padding-bottom: 3.4rem; } }   /* clear of the stamp above and the flip button below */
.shot-fig { display: flex; flex-direction: column; gap: 10px; }
/* 4:3, not the clip's own 16:9. object-fit crops the mint margin off the
   sides and leaves the phone its full height, so the phone is a third of the
   frame rather than a quarter of it. */
.well {
  position: relative; aspect-ratio: 4 / 3; border-radius: 12px; overflow: hidden;
  background: var(--video-mint); border: 1px solid var(--rule);
  box-shadow: 0 14px 30px -20px rgba(36, 58, 99, .7);
}
.well video, .well img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.well .still { display: none; }
.vidbtn {
  position: absolute; right: 8px; bottom: 8px; z-index: 3;
  font-family: var(--mono); font-size: .58rem; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: #243A63; background: rgba(250, 247, 241, .82);
  border: 1px solid rgba(36, 58, 99, .18); border-radius: 7px; padding: 5px 9px;
  cursor: pointer; backdrop-filter: blur(3px); opacity: .55; transition: opacity .18s ease;
}
.well:hover .vidbtn, .vidbtn:focus-visible { opacity: 1; }
figcaption { font-size: .9rem; line-height: 1.5; color: var(--body); }

/* back — the message side */
.pane.msg { display: flex; flex-direction: column; border-right: 1.5px dashed var(--dash); }
.msg .hand { margin-bottom: 14px; }
.notes { display: flex; flex-direction: column; gap: 14px; flex: 1; }
.note b { display: block; font-size: 1.12rem; font-weight: 700; letter-spacing: -.012em; margin-bottom: 2px; color: var(--ink); }
.note span { display: block; font-size: 1rem; line-height: 1.5; color: var(--body); }
.signoff { font-family: var(--hand); font-weight: 600; font-size: 1.5rem; color: var(--accent); margin-top: 14px; }

/* back — the address side */
.pane.addr { display: flex; flex-direction: column; padding-bottom: 52px; }
.addr-lines { margin-top: 70px; }
.pane.addr .btn { font-size: 1.08rem; padding: 15px 26px; }
.pane.addr .fineprint { font-size: .78rem; }
.line { margin-bottom: 16px; display: block; }
.lbl { display: block; font-family: var(--mono); font-size: .8rem; font-weight: 700; letter-spacing: .18em; text-transform: uppercase; color: var(--accent); }
.val {
  display: block; font-family: var(--hand); font-weight: 600; font-size: 2rem; line-height: 1.15;
  color: var(--ink); border-bottom: 1.5px solid var(--rule); padding-bottom: 6px; margin-top: 4px; margin-bottom: 14px;
}

/* ── the sign-up ──────────────────────────────────────────────────────── */
.notify { margin-top: auto; }
/* The honeypot. Clipped rather than parked at left:-9999px — inside the
   back face, which is mirrored by rotateY, a negative offset lands ten
   thousand pixels off the RIGHT edge instead. */
.hp { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
.field-label { display: block; font-size: .88rem; font-weight: 650; margin-bottom: 7px; color: var(--ink); }
.field { display: flex; gap: 8px; flex-wrap: wrap; }
.field input {
  flex: 1 1 11ch; min-width: 0; font: inherit; font-size: .95rem; color: var(--text);
  background: rgba(255, 255, 255, .6); border: 1.5px solid var(--rule);
  border-radius: 11px; padding: 12px 14px;
}
@media (prefers-color-scheme: dark) {
  .field input { background: rgba(255, 255, 255, .06); }
}
.field input::placeholder { color: var(--faint); }
.field input:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 3px rgba(35, 104, 111, .18); }
.field button {
  font: inherit; font-size: .95rem; font-weight: 650; cursor: pointer;
  color: var(--paper); background: var(--ink); border: none; border-radius: 11px; padding: 12px 20px;
  box-shadow: 0 10px 24px -14px rgba(36, 58, 99, .9);
}
.field button:hover { background: var(--ink-deep); }
.notify .fineprint { display: block; margin-top: 8px; }

/* ── the flip tab ─────────────────────────────────────────────────────── */
.fliptab {
  position: absolute; right: 14px; bottom: 12px; z-index: 8; cursor: pointer;
  font-family: var(--mono); font-size: .74rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--paper); background: var(--ink);
  border-radius: 10px; padding: 11px 16px; user-select: none;
  box-shadow: 0 8px 18px -10px rgba(36, 58, 99, .9);
}
.fliptab.ghost { color: var(--ink); background: transparent; border: 1.5px solid var(--rule); box-shadow: none; }
.fliptab:hover { opacity: .88; }
.flip-state:focus-visible ~ .scene .fliptab { outline: 2.5px solid var(--accent); outline-offset: 3px; }

/* ── below the fold ───────────────────────────────────────────────────── */
.more.no-room { display: none; }
.more {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  margin: 22px auto 0; width: max-content; text-decoration: none;
  font-family: var(--mono); font-size: .72rem; font-weight: 700;
  letter-spacing: .2em; text-transform: uppercase; color: var(--muted-field);
}
.more:hover { color: var(--accent-field); }
.more svg { width: 20px; height: 12px; animation: nudge 2.6s ease-in-out infinite; }
/* Only where the whole card AND this hint fit on screen; on a shorter window it would be cut
   off, and the page plainly scrolls anyway. */
@keyframes nudge { 0%, 100% { transform: translateY(0); opacity: .7 } 50% { transform: translateY(4px); opacity: 1 } }

.section { padding-top: clamp(46px, 8vw, 78px); scroll-margin-top: 20px; }
.sec-eyebrow { font-family: var(--mono); font-size: .76rem; font-weight: 700; letter-spacing: .2em; text-transform: uppercase; color: var(--accent-field); margin-bottom: 10px; }
.sec-head { font-size: clamp(1.6rem, 1.2rem + 1.6vw, 2.4rem); font-weight: 800; letter-spacing: -.024em; line-height: 1.14; color: var(--ink); }
.sec-lede { font-size: .97rem; color: var(--muted-field); max-width: 54ch; margin-top: 10px; }
/* The two showpieces (the screens, the film) have their heading centred over them; the FAQ reads from the left. */
#look, #watch { text-align: center; }
#look .sec-lede, #watch .sec-lede { margin-left: auto; margin-right: auto; }
#look .phone figcaption { text-align: center; }

/* the six screens: one row, fanned like cards in a hand. Each leans a little
   and overlaps its neighbour; hover or focus straightens it and lifts it to
   the front. Navy bezels, the same colour as the one in the clip. */
.fan { display: flex; justify-content: center; margin-top: 34px; padding: 18px 0 8px; }
.fan .phone { flex: 0 1 16%; margin: 0 -.3%; transition: transform .28s ease; position: relative; }
/* In the fan only each screen's bold title shows, so the tilted captions never run into each other. */
.fan .phone figcaption { font-size: 0; text-align: center; }
.fan .phone figcaption b { font-size: .86rem; line-height: 1.25; }
.fan .phone:nth-child(1) { transform: rotate(-7deg) translateY(22px); }
.fan .phone:nth-child(2) { transform: rotate(-4deg) translateY(8px); }
.fan .phone:nth-child(3) { transform: rotate(-1.3deg); }
.fan .phone:nth-child(4) { transform: rotate(1.3deg); }
.fan .phone:nth-child(5) { transform: rotate(4deg) translateY(8px); }
.fan .phone:nth-child(6) { transform: rotate(7deg) translateY(22px); }
.fan .phone:hover, .fan .phone:has(img:focus-visible) { transform: none; z-index: 2; }
.fan .phone:hover img { box-shadow: 0 26px 50px -20px rgba(22, 35, 60, .6); }
.fan img { cursor: zoom-in; }
/* The lightbox: a screen at full height, arrows to step through, Esc or a click outside to close. */
.lightbox { border: none; padding: 0; background: transparent; max-width: none; max-height: none; overflow: visible;
  /* The page's reset zeroes margins; a dialog centres itself with margin:auto, so it is put back. */
  position: fixed; inset: 0; margin: auto; width: fit-content; height: fit-content; }
.lightbox::backdrop { background: rgba(16, 26, 44, .78); backdrop-filter: blur(3px); }
.lightbox img { height: auto; width: auto; max-height: min(88svh, 900px); max-width: 92vw; border-radius: 26px; border: 9px solid #16233C; background: #16233C; box-shadow: 0 30px 80px -20px rgba(0,0,0,.6); }
.lightbox button {
  position: fixed; top: 50%; transform: translateY(-50%); width: 48px; height: 48px; border-radius: 50%;
  border: none; background: rgba(250, 247, 241, .92); color: #243A63; font-size: 30px; line-height: 1; cursor: pointer;
}
.lightbox .lb-prev { left: max(16px, calc(50vw - 330px)); }
.lightbox .lb-next { right: max(16px, calc(50vw - 330px)); }
.lightbox .lb-close { top: 28px; right: 28px; transform: none; font-size: 26px; }
.vidbox video { aspect-ratio: 600 / 1170; height: auto; width: min(92vw, calc(min(88svh, 900px) * 600 / 1170)); border-radius: 28px; border: 9px solid #16233C; background: #16233C; box-shadow: 0 30px 80px -20px rgba(0,0,0,.6); }
/* The whole loop is a button that opens it larger; the Pause button stays on top of it. */
.vid-open { position: absolute; inset: 0; z-index: 2; border: none; background: transparent; cursor: zoom-in; }
.phone { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.phone img {
  width: 100%; height: auto; border-radius: 18px;
  border: 7px solid #16233C; background: #16233C;
  box-shadow: var(--phone-shadow);
}
.phone figcaption { font-size: .85rem; color: var(--muted-field); line-height: 1.45; }
.phone figcaption b { display: block; font-size: .92rem; font-weight: 700; color: var(--ink); margin-bottom: 1px; }

/* the film, behind a click */
.watch summary {
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; cursor: pointer;
  width: max-content; max-width: 100%;
  padding: 14px 22px; border-radius: 999px;
  border: 1.5px solid var(--rule); background: var(--paper);
  box-shadow: 0 10px 26px -22px rgba(36, 58, 99, .9);
}
.watch summary::-webkit-details-marker { display: none; }
.watch summary::marker { content: ""; }
.sum-label { font-size: 1rem; font-weight: 700; color: var(--ink); }
.sum-label::before { content: "▶"; font-size: .62em; margin-right: 9px; color: var(--accent); }
.watch[open] .sum-label::before { content: "▼"; }
.sum-note { font-family: var(--mono); font-size: .62rem; letter-spacing: .1em; text-transform: uppercase; color: var(--faint); }
/* The film opens as wide as the postcard, on a panel where snowflakes and
   little ice cubes drift down behind it (site.js adds them; none for reduced motion). */
.watch-body {
  margin-top: 22px; position: relative;
  /* No panel of its own: the page's own green, with snow and ice falling in a band around the film. */
  padding: clamp(18px, 4vw, 56px) clamp(0px, 3vw, 44px);
}
.watch-body video { position: relative; z-index: 1; width: 100%; height: auto; border-radius: 14px; border: 1px solid var(--rule); background: #0B1422; box-shadow: var(--shadow); }
.drift { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.drift i { position: absolute; top: -40px; display: block; animation: fall linear infinite; opacity: .8; }
.drift i.flake { color: #7CC4CA; font-style: normal; text-shadow: 0 0 1px #fff, 0 0 6px rgba(255, 255, 255, .9); }
.drift i.cube {
  border-radius: 22%; background: linear-gradient(135deg, rgba(255,255,255,.95), rgba(150,210,225,.7));
  border: 1px solid rgba(78,165,172,.55); box-shadow: inset -2px -2px 3px rgba(110,180,200,.35), 0 2px 6px rgba(80,150,170,.2);
}
@keyframes fall {
  0%   { transform: translate(0, -40px) rotate(0deg); }
  50%  { transform: translate(var(--sway), 50vh) rotate(180deg); }
  100% { transform: translate(0, 110vh) rotate(360deg); }
}

/* the ask, at the end */
.notify-block .notify { margin-top: 20px; max-width: 520px; }
.notify-block .start { margin-top: 20px; }
.notify.wide .field input { flex-basis: 16rem; }

footer {
  margin-top: clamp(50px, 8vw, 84px); padding: 26px 0 34px;
  border-top: 1px solid var(--rule); text-align: center;
  font-family: var(--mono); font-size: .78rem; letter-spacing: .1em; color: var(--muted-field);
}
footer p { display: flex; align-items: center; justify-content: center; gap: 8px; }
footer img { width: 20px; height: 20px; border-radius: 5px; opacity: .85; }
footer .small { margin-top: 8px; font-size: .6rem; }

/* ── the fold, on a screen big enough to have one ─────────────────────── */
/* svh rather than vh: on iOS the browser chrome makes vh taller than what is
   actually visible, and a headline peeking over the bottom edge is exactly
   what gives a fold away. */
@media (min-width: 860px) {
  /* The fold is as tall as its content: site.js sizes the type so the card and "See it" fit
     the window, which is what used to be guessed with a min-height and auto margins. */
  .fold { display: flex; flex-direction: column; }
  .fold .more { padding-bottom: 6px; }
}

/* ── narrower than the card ───────────────────────────────────────────── */
@media (max-width: 859px) {
  .card-grid { grid-template-columns: 1fr; min-height: 0; }
  .pane.say, .pane.msg { border-right: none; border-bottom: 1.5px dashed var(--dash); }
  .addr-lines { margin-top: 10px; }
  /* The fan becomes a row you swipe sideways, each screen upright and snapping into place. */
  .fan { justify-content: flex-start; overflow-x: auto; scroll-snap-type: x mandatory; gap: 16px; padding: 6px 4px 14px; }
  .fan .phone, .fan .phone:nth-child(n) { flex: 0 0 min(58vw, 240px); margin: 0; transform: none; scroll-snap-align: center; }
  .fan .phone figcaption { font-size: .85rem; text-align: left; }
  .masthead { gap: 10px; }
  /* Its own line under the wordmark and the button, centred on it (Arpan, 2026-10-01). */
  .eyebrow { order: 3; flex: 1 0 100%; justify-content: center; text-align: center; }
}

@media (max-width: 430px) {
  /* Still the size that fits a 320px gutter on one line — but .78rem
     rather than the .6rem it was, which was invisible. */
  .eyebrow { font-size: .78rem; letter-spacing: .1em; gap: 6px; }
  .nav-cta { font-size: .78rem; padding: 8px 13px; }
  .brand   { font-size: 1.35rem; }
  .brand img { width: 30px; height: 30px; }
}

@media (max-width: 560px) {
  .stamp { width: 58px; top: 10px; right: 10px; --g: 9px; --d: 3.5px; }
  .postmark { width: 54px; height: 54px; top: 13px; right: 76px; }
  .postmark span { font-size: .34rem; }
  .pane { padding: 18px 16px; }
  /* Only the handwritten line has to clear the stamp. Indenting the whole
     pane would cost the headline a fifth of a 390px screen. */
  .hand { padding-right: 76px; }
  .nb { display: none; }               /* the headline finds its own breaks */
  .card-grid { align-content: center; }
  .face { padding: 9px; }
  .keyline { border-radius: 8px; }
  .phone img { border-width: 5px; border-radius: 13px; }
  .watch summary { width: 100%; }
  .field button { flex: 1 0 100%; }
  .nav-cta { font-size: .86rem; padding: 9px 16px; }
  .fliptab { right: 10px; bottom: 9px; }
  .pane.addr { padding-bottom: 44px; }
  .pane.media { padding-top: 6px; }
}


/* ── short and wide (a phone on its side) ─────────────────────────────── */
@media (min-width: 680px) and (max-height: 540px) {
  .card-grid  { grid-template-columns: 1fr 1.05fr; min-height: 0; }
  .pane.say, .pane.msg { border-right: 1.5px dashed var(--dash); border-bottom: none; }
  .pane { padding: 16px 18px; }
  .hand { padding-right: 0; font-size: 1.15rem; }
  .addr-lines { margin-top: 56px; }
  .notes { gap: 9px; }
  .fold { min-height: 0; }
}

/* ── the weather in the field ─────────────────────────────────────────────
   One scene, two bands. Map pins — the app's own glyph, in the app's own
   four day colours (src/trips/days.ts, DAY_PALETTE: Thu sky, Fri orange,
   Sat amber, Sun green) — drift down through the top two thirds and fade
   out before they land. Below them, one penguin walks left to right along
   the bottom band and its prints fade behind it. The bands never meet.

   Confined to the hero — see the markup. Thirty-two landing spots and one
   set of tracks, thinned on a phone. Seven or eight pins are down at any
   moment and about one is moving. Weights are
   set so the colour is legible at a glance — the first pass at .15-.25 read
   as grey specks — and the fix for anything that fights the copy is fewer
   and further out, never fainter.

   Transform and opacity only — no top/left, no filter, no blur, no canvas,
   no script. The whole layer is behind the page (z-index 0 against .page's
   1), inert to the pointer, and hidden from assistive technology.
   ─────────────────────────────────────────────────────────────────────── */
.fold { position: relative; }
/* Absolute inside the hero, not fixed to the viewport: it leaves when the
   hero leaves, with no scroll listener and no fade-out hack. overflow:hidden
   is the guarantee that nothing spills onto the section below — and because
   every pin now starts its fall inside the box, nothing is ever sliced. */
.bg {
  position: absolute; z-index: 0;
  /* Bound to the hero vertically, but the full width of the window: the
     gutters either side of the 1060px page are exactly where the field shows
     on the fold, and .fold itself is only as wide as the page. */
  top: 0; bottom: 0; left: 50%; width: 100vw; transform: translateX(-50%);
  pointer-events: none; overflow: hidden; contain: layout paint;
}
/* Both are static in the flow, so they need lifting over the layer. */
.scene, .fold .more { position: relative; z-index: 1; }
.bg-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
.pin, .foot { position: absolute; display: block; fill: currentColor; }

/* Which scene runs is the class on <body>. display:none also stops the
   animations, so the half that is off costs nothing. */
.bg-pins   .tracks { display: none; }
.bg-tracks .pins   { display: none; }

.pin {
  left: var(--x); top: var(--to); width: var(--s); height: auto; color: var(--c);
  /* .gr hangs off the right edge instead, for the same reason. */
  opacity: 0; transform-origin: 50% 100%;
  animation: pin-plant 52s linear var(--d) infinite;
}
.pin.gr { left: auto; right: var(--rx); }
.pins .c1 { --c: #41B6E6; }   /* Thu */
.pins .c2 { --c: #E87722; }   /* Fri */
.pins .c3 { --c: #F5A800; }   /* Sat */
.pins .c4 { --c: #1B9E5A; }   /* Sun */

/* A pin drops, plants with a small settle on its own tip, holds about four
   seconds and fades. Chosen over a continuous drift for two reasons: it is
   what the app itself does with a pin, and a pin that holds still is legible
   at a glance where a moving one was only ever a smudge. Every landing spot
   is fixed, so nothing ever comes to rest on a word.
   transform-origin is the tip: it plants on its point, not its middle. */
@keyframes pin-plant {
  /* 52s cycle. The fall is 1.5s and eases out, so the pin decelerates into
     the plant instead of arriving at speed; then a hair of overshoot, a
     settle, and about seven seconds held still before it fades. Long hold,
     short fall: seven or eight pins are down but only one is moving. */
  /* The spot is the element's own `top`; the animation only lifts it by
     --drop and puts it back down, so the geometry is height-independent. */
  0%    { opacity: 0; transform: translate3d(0, var(--drop), 0) scale(.7);
          animation-timing-function: cubic-bezier(.3, .05, .2, 1); }
  1.4%  { opacity: calc(var(--o) * var(--pin-boost)); }
  2.9%  { transform: translate3d(0, 0, 0) scale(1.07);
          animation-timing-function: ease-out; }
  3.8%  { transform: translate3d(0, 0, 0) scale(.97);
          animation-timing-function: ease-out; }
  4.7%  { transform: translate3d(0, 0, 0) scale(1); }
  16.4% { opacity: calc(var(--o) * var(--pin-boost)); }
  24%   { opacity: 0; transform: translate3d(0, 0, 0) scale(1); }
  100%  { opacity: 0; transform: translate3d(0, 0, 0) scale(1); }
}

.foot {
  left: var(--x); bottom: var(--yb); width: 18px; height: auto; color: var(--print);
  opacity: 0;
  /* The -8s pre-roll means somebody arriving on the page finds a trail
     already there rather than an empty field for the first eight seconds. */
  animation: foot-step 34s linear calc(var(--i) * 1.9s - 12s) infinite;
}
/* A step every 1.9 seconds and each print down for about eleven, so five or
   six are visible at once — a trail, not a dotted line. The last fades at
   27s of the 34s cycle, leaving the field empty before he comes back. */
@keyframes foot-step {
  0%   { opacity: 0;              transform: rotate(var(--rot)) scale(.8); }
  3%   { opacity: var(--print-o); transform: rotate(var(--rot)) scale(1); }
  13%  { opacity: var(--print-o); }
  32%  { opacity: 0; }
  100% { opacity: 0;              transform: rotate(var(--rot)) scale(1); }
}

/* A phone is a third of the width, so the same count would be three times
   the density. Half of each, and a smaller print. */
@media (max-width: 640px) {
  .pins .pin:nth-child(2n) { display: none; }
  /* Every third print rather than every other: on a narrow field a full
     trail brushes the copy too often, and fewer-and-further-out is the
     right answer to that, never fainter. */
  .tracks .foot:not(:nth-child(3n + 1)) { display: none; }
  .foot { width: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  /* Stopped dead, not slowed: the layer is decoration and nothing is lost
     with it gone. */
  .bg { display: none; }
}

/* ── less motion ──────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .faces, .face, .btn, .vidbtn, .fan .phone { transition: none; }
  .faces.flipping { animation: none; }
  .drift { display: none; }
  .dot, .more svg { animation: none; }
  /* The hero loop does not play. The poster frame stands in its place — and
     because that is CSS rather than script, it holds with JavaScript off. */
  .well video { display: none; }
  .well .still { display: block; }
  .vidbtn { display: none; }
}

/* ── DRAFT: web plan Task 14's additions (the front's lines, the FAQ, the badges, the footer links) ── */
.also { list-style: none; padding: 0; margin: 14px 0 6px; }
.also li { position: relative; padding-left: 18px; margin: 4px 0; }
.also li::before { content: ''; position: absolute; left: 2px; top: .62em; width: 7px; height: 7px; border-radius: 50%; background: currentColor; opacity: .55; }
.three { list-style: none; padding: 0; margin: 2px 0 0; display: flex; flex-direction: column; gap: 7px; }
.three li { position: relative; padding-left: 22px; color: var(--body); font-size: 1rem; line-height: 1.45; }
.three li::before { content: ''; position: absolute; left: 2px; top: .42em; width: 11px; height: 11px; border-radius: 50%; background: var(--q); box-shadow: 0 0 0 3px color-mix(in srgb, var(--q) 22%, transparent); }
.three b { font-size: 1.08rem; font-weight: 800; letter-spacing: -.012em; color: var(--ink); margin-right: 4px; }
.price { font-weight: 600; margin: 8px 0 14px; }
.faq { max-width: 40rem; }
.faq h3 { margin: 22px 0 6px; font-size: 1.1rem; }
.faq p { margin: 0; }
.stores { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-top: 18px; }
.stores[hidden] { display: none; }
footer .legal a { color: inherit; }

/* Where to get Pingy: the web app is live; Google Play is live; iPhone is the TestFlight beta (with .beta-note) until the App Store listing is public. */
.platforms { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 22px; }
.plat {
  display: inline-flex; align-items: center; gap: 10px; min-width: 11.5rem;
  padding: 10px 16px; border-radius: 12px; text-decoration: none; line-height: 1.15;
  background: var(--ink); color: var(--paper); border: 1.5px solid var(--ink);
}
.plat svg { width: 26px; height: 26px; flex: none; }
.plat span { display: flex; flex-direction: column; font-size: 1.02rem; font-weight: 700; }
.plat small { font-size: .7rem; font-weight: 600; opacity: .8; letter-spacing: .02em; }
.plat.live:hover { background: var(--ink-deep); }
.plat.soon { background: transparent; color: var(--faint); border-color: var(--rule); cursor: default; opacity: .75; }
.front-plat { margin-top: 4px; gap: 8px; flex-wrap: nowrap; }
.beta-note { font-size: .82rem; line-height: 1.4; color: var(--body); margin: 8px 0 0; max-width: 30rem; }
.front-plat .plat { min-width: 0; padding: 8px 12px; gap: 8px; border-radius: 11px; }
.front-plat .plat svg { width: 21px; height: 21px; }
.front-plat .plat span { font-size: .9rem; white-space: nowrap; }
.front-plat .plat small { font-size: .62rem; }
@media (max-width: 480px) { .front-plat { flex-wrap: wrap; } }

/* ── phones: the postcard on one screen (site.js 8, Arpan 2026-10-01) ──────────────────
   .phone-card is set by site.js below 640px, once the long parts have moved under the card.
   The fold is the window's height under the masthead (site.js sets it); the card sits in its
   middle and "See it" at its foot, just above the edge. */
.phone-card .fold { display: flex; flex-direction: column; }
.phone-card .fold .scene { margin-top: auto; margin-bottom: auto; padding-top: 6px; }
.phone-card .fold .more { padding: 10px 0 14px; }
/* Each face is one pane: no dashed rule under it. The front's button says "Get the app" and sits under the
   questions (site.js 8 puts it in their column); the back is where Pingy runs, the store buttons and the
   price, and nothing else (Arpan, 2026-10-04: the back said little at this size, so it was given a job). */
.phone-card .pane.say, .phone-card .pane.msg { border-bottom: none; }
.tl-phone, .getapp { display: none; }
.phone-card .tl-wide { display: none; }
.phone-card .tl-phone { display: inline; }
.phone-card .face.front .fliptab {
  position: static; grid-column: 1; grid-row: 5; justify-self: start; margin-top: 16px;
  font-size: .66rem; padding: 10px 13px; white-space: nowrap;
}
.phone-card .face.back .pane.msg > .hand, .phone-card .face.back .notes, .phone-card .face.back .signoff, .phone-card .face.back .pane.addr { display: none; }
.phone-card .face.back .pane.msg { padding-bottom: 58px; }   /* clear of "Turn back" */
/* The handwritten line on either face keeps clear of the postmark as well as the stamp, and finds its own
   breaks, so a narrow screen or a larger text size wraps it instead of running it under them. */
.phone-card .face .hand { padding-right: 120px; text-wrap: balance; }
.phone-card .face .hand br { display: none; }   /* the space before each <br> in the page is what then separates the words */
/* As on the front: a small handwritten line beside the stamp, then the bold one. */
.phone-card .getapp { display: flex; flex-direction: column; gap: 14px; }
.phone-card .getapp .hand { font-size: 1.12rem; }
.getapp .sync { font-size: 1.25rem; font-weight: 800; letter-spacing: -.028em; line-height: 1.12; color: var(--ink); }
.getapp-slot { display: flex; flex-direction: column; gap: 4px; margin-top: 4px; }
.getapp-slot .price { margin: 10px 0 0; }
.card-extras { padding-top: 22px; }
.card-extras .extras-front { display: flex; flex-direction: column; gap: 14px; }
/* The demo on the card (Arpan, 2026-10-04): the phone-only cut down the card's right; the headline, the
   three questions and the button down its left; the two centred on each other whichever is taller. Nothing here is a
   measured width: the left column is as wide as the longest question, each on its own line (the headline
   wraps inside that, taking no part in the sizing), and the phone has all the width that is left, up to its
   cap (a share of the screen's height, so the card still fits it) — so a larger text size or a narrower
   screen gives the words their room and the phone the rest. The questions stand alone here; they are
   answered in full under the card (.answers). Off the phone card the pocket is not drawn and never
   loads (preload none; site.js 8 is what plays it). */
.pocket { display: none; }
.phone-card .face.front .pane.say {
  display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-rows: auto 1fr auto auto auto 1fr;
  column-gap: 10px; row-gap: 0; align-content: center; padding-bottom: 28px;   /* the foot: room for Pause under the phone */
}
.phone-card .face.front .hand { grid-column: 1 / -1; grid-row: 1; margin-bottom: 14px; font-size: 1.12rem; }   /* an accent over the headline, not a rival to it */
.phone-card .face.front h1 { grid-column: 1; grid-row: 3; width: 0; min-width: 100%; font-size: 1.25rem; line-height: 1.12; text-wrap: pretty; margin-bottom: 14px; }
.phone-card .ask { display: contents; }
.phone-card .face.front .three { grid-column: 1; grid-row: 4; gap: 9px; }
.phone-card .face.front .three .ans { display: none; }
.phone-card .face.front .three li { padding-left: 18px; }
.phone-card .face.front .three li::before { left: 3px; width: 9px; height: 9px; box-shadow: 0 0 0 2.5px color-mix(in srgb, var(--q) 22%, transparent); }
.phone-card .face.front .three b { font-size: .84rem; margin-right: 0; white-space: nowrap; }
.phone-card .pocket { display: block; position: relative; grid-column: 2; grid-row: 2 / 7; align-self: center; justify-self: end; width: 100%; max-width: min(11rem, 23svh); }
.pocket video {
  display: block; width: 100%; height: auto; aspect-ratio: 360 / 702; object-fit: cover;
  border-radius: 1.15rem; border: .32rem solid #16233C; background: #16233C;
  box-shadow: 0 14px 26px -16px rgba(36, 58, 99, .8);
}
.pocket-open { position: absolute; inset: 0; z-index: 2; border: none; background: transparent; cursor: zoom-in; border-radius: 1.15rem; }
/* Under the phone, not on it: at this size a button on the screen hides a tab of the app. */
.pocket-pause {
  position: absolute; top: 100%; right: 0; z-index: 3;
  font-family: var(--mono); font-size: .6rem; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--faint); background: none; border: none; padding: 7px 2px 5px 12px; cursor: pointer;
}
.pocket-pause[hidden] { display: none; }
/* The wide clip's pane is not drawn on the phone card: the pocket is the demo there. */
.phone-card .face.front .pane.media { display: none; }
/* Nor the line that introduces the questions: under the card they are answered instead. */
.phone-card .face.front .blurb { display: none; }
/* Under the card: each question, then its answer in full. */
.answers { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 16px; }
.answers li { position: relative; padding-left: 22px; }
.answers li::before { content: ''; position: absolute; left: 2px; top: .5em; width: 11px; height: 11px; border-radius: 50%; background: var(--q); box-shadow: 0 0 0 3px color-mix(in srgb, var(--q) 22%, transparent); }
.answers b { display: block; font-size: 1.12rem; font-weight: 800; letter-spacing: -.012em; color: var(--ink); margin-bottom: 2px; }
.answers span { display: block; font-size: 1rem; line-height: 1.5; color: var(--body); }
