/* ============================================================
   swity — private-beta site
   Design system mirrors the iOS app: the same gold, the same ink greys,
   Bitter for display type only, Manrope for everything else.

   Type discipline (deliberately narrow — it was drifting):
     · serif  → hero H1 + section H2 + the wordmark. Nothing else.
     · sans   → every heading below H2, all body, all UI.
     · no italics anywhere.
     · three text colours per surface, not seven.
   ============================================================ */

/* ----------  TOKENS (1:1 with the app's Palette)  ---------- */
:root {
  /* surfaces */
  --bg:        #050506;   /* deepest dark */
  --bg-2:      #0A0A0B;
  --ink:       #0B0B0C;   /* primary text on light — app Palette.ink */
  --paper:     #F1F2F4;   /* app Palette.paper */
  --canvas:    #EFF0F2;   /* app Palette.canvas */
  --paper-2:   #FFFFFF;
  /* The access-code button's tone in the app. Kept as a token because it is a
     real brand colour, but the "two ways" section no longer uses it — that
     section is on --canvas, like the app. */
  --sand:      #E9E5DD;

  /* text on dark */
  --text:      #FAFAFA;
  --muted-1:   rgba(255,255,255,0.66);
  --muted-2:   rgba(255,255,255,0.42);
  --hair:      rgba(255,255,255,0.09);
  --hair-2:    rgba(255,255,255,0.16);

  /* text on light — app Palette.ink2 / inkMute / inkLine */
  --ink-2:     #2A2A2E;
  --ink-mute:  #6E6E73;
  --ink-line:  #E7E6E1;
  --ink-hair:  rgba(11,11,12,0.09);

  /* accent — the one brand gold, app Palette.gold */
  --gold:      #B08D57;
  --gold-soft: rgba(176,141,87,0.14);
  --success:   #298557;   /* app Palette.success */

  --serif: 'Bitter', Georgia, serif;
  --sans:  'Manrope', system-ui, -apple-system, sans-serif;

  --maxw: 1180px;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* the phone screen's true aspect — iPhone 17 Pro, 402×874pt */
  --phone-aspect: 402 / 874;
  /* HOW TALL A PHONE MAY BE, given the window it has to live in.
     The frames were sized in px only — 320px in the hero, 330px in the two demo
     sections — so their HEIGHT was whatever the aspect made it (measured: a
     frame is exactly 2.0959x its own width, bezels included). On a laptop with a
     shorter viewport that put the bottom of the phone below the fold: the hero
     mockup ran off the screen and the "how it works" one did the same.
     The reserve is what must fit AROUND the phone and never under it:
       55px  the fixed top nav
       40px  air above the frame
      145px  the hint, the "Open the live page" link and the scroll cue below
     Everything downstream takes `min(its own px, this)`, so a tall window keeps
     the sizes that were designed and only a short one scales down. */
  --phone-fit: calc((100dvh - 240px) / 2.0959);
  /* Height of the nav bar once scrolled — every full-screen mobile section
     pads by it so nothing starts underneath the bar. */
  --navh: 58px;
}

/* ----------  RESET / BASE  ---------- */
* { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-weight: 400;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
/* `overflow-x: hidden` on the BODY propagates to the viewport, which means the
   body box itself stops clipping — the decorative .glow radials (460px wide,
   absolutely positioned) still counted towards the document's width. On desktop
   the pinned viewports clip them; on mobile those lose their overflow, so the
   page measured 425px against a 390px screen, the browser zoomed out to fit, and
   every light section gained a dark strip down its right edge.

   Clipped per section rather than on the root: `overflow-x: clip` on <html>
   makes the root its own scroll container, and scroll snapping then stops
   applying to the page. `clip` (not `hidden`) because it creates no scroll
   container of its own, so the sticky scrollytelling inside .how and .gallery
   is untouched. */
.hero, section, .closing { overflow-x: clip; }
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; border: 0; background: none; color: inherit; }
/* <i> is only ever a decorative dot/tick here, never emphasis — and the design
   rule is no italics anywhere. */
i { font-style: normal; }
::selection { background: rgba(176,141,87,0.28); }
svg.sw-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

.noscroll { scrollbar-width: none; -ms-overflow-style: none; }
.noscroll::-webkit-scrollbar { display: none; width: 0; height: 0; }

/* ----------  SHARED  ---------- */
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 32px; width: 100%; }

.eyebrow {
  font-family: var(--sans); font-weight: 600; font-size: 11px;
  letter-spacing: 0.22em; text-transform: uppercase; color: var(--gold);
}

/* Display type — the ONLY places serif appears (plus the wordmark). */
h1, h2 { font-family: var(--serif); font-weight: 600; letter-spacing: -0.015em; line-height: 1.05; margin: 0; }
h3, h4 { font-family: var(--sans); font-weight: 600; letter-spacing: -0.01em; margin: 0; }

/* ⚠️ THE MARK'S FLAT EDGE SITS ON THE TEXT BASELINE, not centred against the
   word. The sun is a half-disc: the bottom tenth of its box is empty, so a
   centred box floats the flat edge above the line the letters stand on. The
   percentage translate resolves against the mark's own height, so one rule
   holds at every size. */
/* THE LOCKUP, and this nav is where its proportions come from: a 28px mark
   box beside a 25px word, 9px apart. Written as ems so the same rule holds
   wherever it is used — which is why the SIZE lives on `.logo` itself and the
   word inherits it, rather than each surface sizing the word and leaving the
   mark to be guessed at. `width: auto` takes the artwork's own 112x100 ratio,
   so the box never holds empty pixels beside the sun. */
.logo { display: inline-flex; align-items: baseline; gap: 0.36em; font-family: var(--serif); }
.logo .mark { display: block; height: 1.12em; width: 1.2544em; transform: translateY(10%); }
.logo .word { font-family: var(--serif); font-weight: 600; font-size: 1em; line-height: 1; }

/* buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  font-family: var(--sans); font-weight: 600; font-size: 15px;
  padding: 15px 26px; border-radius: 14px;
  transition: transform .35s var(--ease), background .3s var(--ease), box-shadow .35s var(--ease);
  white-space: nowrap;
}
.btn:active { transform: scale(0.98); }
.btn-gold { background: var(--gold); color: #FBF7EF; box-shadow: 0 16px 40px -18px rgba(176,141,87,0.85); }
.btn-gold:hover { transform: translateY(-2px); box-shadow: 0 22px 48px -18px rgba(176,141,87,0.95); }
.btn-lg { font-size: 16px; padding: 17px 32px; }

.glow {
  position: absolute; border-radius: 50%; pointer-events: none;
  background: radial-gradient(closest-side, rgba(176,141,87,0.22), transparent 70%);
  filter: blur(30px); z-index: 0;
}

section { position: relative; z-index: 1; }

/* ----------  FULL-SCREEN SECTIONS THAT HOLD THEIR PLACE  ----------
   Every section is already 100vh; without snapping, a scroll can still come to
   rest halfway across a boundary, which on this page means half a black screen
   and half a white one.

   `proximity`, never `mandatory`: "how it works" and "samples" are pinned
   scrollytelling several screens tall, and mandatory snapping would drag the
   scrub back to the top of the pin as soon as it let go. With proximity there is
   one snap point at the top of each section and nothing to fight inside them.

   Desktop only — below 900px the sections deliberately stop being one screen
   tall (the phone demos and stacked columns need the room). */
@media (min-width: 1081px) {
  html { scroll-snap-type: y proximity; }
  .hero, .real, .how, .gallery, .ways, .glance, .closing { scroll-snap-align: start; }
}
/* "The idea" sits on white; "How it works" on the app's off-white canvas, so the
   two light sections read as separate surfaces rather than one long slab. */
section.light { background: var(--paper-2); color: var(--ink); }
section.how   { background: var(--canvas); }
section.dark  { background: var(--bg); color: var(--text); }

/* reveal-on-scroll */
.reveal { opacity: 0; transform: translateY(18px); transition: opacity .8s var(--ease), transform .8s var(--ease); }
.reveal.in { opacity: 1; transform: none; }
.d1 { transition-delay: .08s; } .d2 { transition-delay: .16s; } .d3 { transition-delay: .24s; }
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  html { scroll-behavior: auto; }
}

/* ============================================================ PHONE
   One frame for every demo. The screen's aspect matches the real device, so a
   full-length capture of a swity page sits inside it at exactly the proportions
   a visitor sees on their own phone. */
.phone {
  /* The size this frame was designed at. `--w` is that, capped by the window. */
  --w-max: 300px;
  --w: min(var(--w-max), var(--phone-fit));
  /* The bezel is a FRACTION of the frame, never a fixed 10px. It used to be
     fixed, so shrinking the phone on a narrow screen left a chunky black border
     around a tiny screenshot — the device stopped looking like a device. These
     ratios come from the 300px desktop frame (10/300, 46/300, 37/300), so the
     desktop rendering is unchanged and every smaller size is a true scale of it. */
  --bezel: calc(var(--w) * 0.0333);
  position: relative; z-index: 2;
  width: var(--w);
  padding: var(--bezel);
  border-radius: calc(var(--w) * 0.1533);
  background: linear-gradient(160deg, #2c2c30, #121214 42%, #232327);
  box-shadow: 0 46px 90px -40px rgba(0,0,0,0.85), 0 0 0 1px rgba(255,255,255,0.06) inset;
}
.phone-screen {
  position: relative;
  aspect-ratio: var(--phone-aspect);
  border-radius: calc(var(--w) * 0.1233);
  overflow: hidden;
  background: #EFF0F2;
}
/* a static screenshot fills the screen from the top */
.phone-screen .shot { width: 100%; height: auto; display: block; }
/* a long capture that scrolls inside the screen */
/* Dark backing: the screen itself is paper-coloured (for light app shots), so any
   sub-pixel sliver around a gallery capture would flash white. */
.shot-scroller { position: absolute; inset: 0; overflow: hidden; background: #0B0B0C; }
/* The capture's first screen ends exactly where the page's next (white) section
   begins, and the screen height is fractional (300 x 874/402 = 652.24px), so
   rounding lets a 1-2px white bar peek along the bottom. Pushing the capture
   DOWN 2px moves that seam past the clip edge at every phone size; the 2px
   opened at the top is covered by the dark backing above. */
.shot-scroller img { width: 100%; height: auto; display: block; margin-top: 2px; will-change: transform; }
/* The scroll rail. A screenshot has no scrollbar, so before this nothing on the
   page said the capture could be moved — or how much of it there was. Hidden by
   default and on any device with a real pointer: on desktop the capture tours
   itself, and a scrollbar next to a moving thing is noise. */
.shot-rail {
  position: absolute; top: 10px; right: 5px; bottom: 10px; width: 3px;
  border-radius: 3px; background: rgba(255,255,255,0.13);
  opacity: 0; transition: opacity .3s var(--ease); pointer-events: none; z-index: 3;
}
.shot-rail i {
  position: absolute; left: 0; right: 0; border-radius: 3px;
  background: rgba(255,255,255,0.55); transition: top .06s linear;
}
@media (hover: none) and (pointer: coarse) {
  /* THE RAIL IS GONE ON TOUCH. It existed to say "this scrolls" — and on a
     handheld it no longer does: the mockup is a picture and the gesture belongs
     to the page. A scrollbar on something that cannot be scrolled is a promise
     the page does not keep. */
  .shot-rail { display: none; }
  /* …EXCEPT on the samples demo, which does scroll on a phone. There the rail
     is the only thing on screen that says so — a phone screenshot has no
     scrollbar of its own — and it doubles as "how far through am I". */
  .shot-scroller.touchable .shot-rail { display: block; }
  .shot-scroller.touching { cursor: grabbing; }
}

/* BELT AND BRACES, IN CSS, WHERE THE BROWSER DECIDES.
   The JS handlers already stand down on a handheld, but `touch-action` settles
   it before a single event is dispatched: `pan-y` tells the browser that a
   vertical drag starting here is the PAGE'S scroll, so it cannot be captured by
   script at all. One line, and the "the phone eats my swipe" class of bug
   cannot come back through some path I did not think of. */
@media (max-width: 1080px) {
  .shot-scroller, .phone, .phone-screen { touch-action: pan-y; }
  /* THE SAMPLES DEMO IS THE EXCEPTION, and it has to be `none` rather than
     nothing at all.
     `pan-y` is not advisory: once the browser has claimed a vertical drag,
     `preventDefault` in `touchmove` is ignored, so the JS could set every flag
     it liked and the page would still be the thing that moved. `none` hands
     the gesture to script, which is the only way "drag inside the phone scrolls
     the phone" can be true.
     The cost is that a drag which reaches the end of the demo stops there
     rather than carrying on into the page — the browser is no longer holding a
     scroll to resume. That is the intended trade: the way out is to drag from
     outside the mockup, which is most of the screen. */
  .shot-scroller.touchable { touch-action: none; }
}
/* the app's fixed nav, pinned above a scrolling body */
/* The fixed toolbar, composited over the scrolling body so it stays put while
   the content moves underneath, exactly as it does in the app.
   Opaque all the way down: in the app the body clips under a solid bar, so a
   crisp edge is the correct look. Fading it instead let the photo bleed through
   Cancel and Save, which was worse than the problem it was meant to solve.
   The artefact was the capture itself, which had the top of the first content
   card baked into it; that fragment stayed frozen while everything behind it
   scrolled. Re-cropped to end just below the toolbar row. */
.shot-chrome { position: absolute; top: 0; left: 0; width: 100%; height: auto; z-index: 3; pointer-events: none; }
/* Start the strip just below the pinned nav instead of underneath it.
   318/1206 = 26.37%, and a percentage margin resolves against WIDTH — which is
   what we want, because both images are 1206 wide, so this tracks the chrome's
   rendered height at any phone size without a media query.
   Without it the strip's first 318px sat behind the chrome and the demo opened
   halfway down the form, on "Subtitle", instead of at the top of the editor. */
.shot-scroller.under-chrome img { margin-top: 26.37%; }

.phone-hint {
  margin-top: 16px; text-align: center;
  font-size: 12px; font-weight: 500; color: var(--muted-2);
}

/* The passphrase curtain's styles lived here until 6 Sep 2026 and went with it
   when the site was made public. */

/* ============================================================ NAV */
/* three columns so the links sit dead-centre regardless of the logo's width */
.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 900;
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  padding: 22px 32px; color: var(--text);
  transition: padding .4s var(--ease), background .4s var(--ease), backdrop-filter .4s var(--ease), border-color .4s var(--ease);
  border-bottom: 1px solid transparent;
}
.nav.scrolled { padding: 13px 32px; background: rgba(5,5,6,0.72); backdrop-filter: blur(18px); border-bottom-color: var(--hair); }
.nav .logo { color: var(--text); justify-self: start; font-size: 22.5px; }   /* white mark, not gold */
/* ⚠️ CENTRING THE LOCKUP'S BOX IS NOT CENTRING THE LOCKUP, and the gap is
   visible: the bar had 21.8px of air above the mark and 12.0px below the tail
   of the "y".

   Two things put the ink lower than the box that holds it. The lockup is
   baseline-aligned, so its box reaches a full 1.12em ABOVE the baseline (the
   mark's height) but only ~0.16em below it (the word's descender space), which
   already hangs the contents low. And the mark's artwork fills only y34 to y90
   of its 112x100 viewBox, so the top 34% of that 1.12em box is empty: the dome
   starts 0.627em above the baseline, not 1.008em. The `translateY(10%)` on the
   mark moves ink without moving the box, which widens the gap again.

   So measure the ink, not the box. It runs from the "t" ascender at
   baseline - 0.77em to the "y" descender at baseline + 0.241em, whose midpoint
   sits 0.196em below the bar's centre line. Lift the whole lockup by that, in
   em so it holds at the smaller size the mobile bar uses too.

   The same phantom space exists wherever `.logo` is used; this corrects the
   nav, which is the one against a measured horizon. */
.nav .logo { transform: translateY(-0.196em); }
.nav .logo .word { color: var(--text); }
.nav .flinks { display: flex; gap: 26px; font-size: 13.5px; font-weight: 500; color: var(--muted-1); justify-self: center; }
.nav .flinks a { transition: color .3s var(--ease); }
.nav .flinks a:hover { color: var(--text); }
/* Where you are. The nav is a table of contents for a page that is mostly
   full-screen sections, so without this it never tells you which one you are
   in. Weight as well as colour: on a dark bar, colour alone is easy to miss. */
.nav .flinks a.active { color: var(--text); font-weight: 700; }

/* ============================================================ 1 · HERO */
/* Column layout so the scroll cue is always the last thing in view — absolutely
   positioning it pushed it below the fold once the phone got tall. */
.hero {
  position: relative; min-height: 100vh;
  display: flex; flex-direction: column; justify-content: center;
  padding: 96px 40px 24px; overflow: hidden;
}
/* The floor: one warm wash low and right, under the phone. It exists only to
   stop the device floating in a void — no photograph competes with the screen. */
.hero-floor {
  position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(82% 76% at 71% 50%, rgba(176,141,87,0.34), transparent 72%),
    radial-gradient(130% 78% at 56% 112%, rgba(214,192,158,0.16), transparent 70%);
}
.hero-grid {
  position: relative; z-index: 2; width: 100%; max-width: var(--maxw); margin: 0 auto;
  display: grid; grid-template-columns: 1fr 0.92fr; gap: 72px; align-items: center;
}
/* Large, tight and *medium* weight. Heavy display weight at this size is the
   loudest template signal there is; 500 with negative tracking reads as
   typography rather than as a headline widget. */
/* "Launching soon" — white pill, hard left, above the headline. White rather
   than gold: gold is the colour of the things a visitor can act on, and this is
   a fact about the product rather than a button. */
.hero-badge {
  display: inline-block; margin-bottom: 22px;
  padding: 9px 18px; border-radius: 999px;
  /* ⚠️ OFF GOLD, NOT THE BRAND GOLD (founder, 1 Sep 2026). Full `--gold` beside
     a gold CTA lower down makes the page argue with itself about where to look;
     this is the same hue lifted and desaturated, so it reads as a stamp on the
     photograph rather than as a second button. Dark ink on it, because gold
     text on gold is the one combination that fails at every size. */
  background: #D9C39A; color: var(--ink);
  font-family: var(--sans); font-weight: 700; font-size: 13px;
  letter-spacing: 0.14em; text-transform: uppercase; line-height: 1;
}

.hero h1 {
  font-size: clamp(46px, 6.2vw, 82px); font-weight: 500;
  /* ⚠️ 0.98 PUT THE ROWS IN CONTACT. At 82px the descender of "your work" was
     landing on the cap-height of "to light."; a tenth of a line is roughly the
     10px the founder asked for at the largest size, and it scales with the
     clamp instead of being a fixed nudge that only works on one screen. */
  letter-spacing: -0.032em; line-height: 1.1; color: var(--text);
}
.hero .lede {
  font-size: clamp(16.5px, 1.45vw, 18.5px); line-height: 1.65; color: var(--muted-1);
  /* Two lines that break at the dash, one clause each.
     560px, measured rather than guessed: line 1 ("…to your artwork -") needs
     544px at the 18.5px desktop size, and the copy column is 577px wide. The
     old 500px was cut for shorter copy and split "your artwork" across the
     break, which read as a typo. Narrower than ~545 wraps "artwork" again;
     wider than the column has nowhere to go. */
  margin: 26px 0 0; max-width: 560px; text-wrap: pretty;
}
/* Desktop scan block — a genuinely scannable code, unboxed. The QR is the one
   physical object we can put on screen, so it sits on the page as an object
   rather than inside a glass card. */
.hero-scan {
  margin-top: 52px; align-self: flex-start;
  display: inline-flex; align-items: center; gap: 16px;
}
/* Minimal container: white ground, generous quiet zone, one soft shadow to lift
   it off the page. The code carries the swity badge itself now (tools/make-qr.js
   renders it exactly as QRGenerator.swift does), so the frame has nothing left
   to say and gets out of the way. */
.hero-scan img {
  width: 104px; height: 104px; border-radius: 14px;
  background: #fff; padding: 11px;
  box-shadow: 0 10px 30px -16px rgba(0,0,0,0.55);
}
/* One line, always — the label is short enough that wrapping it would only ever
   look like an accident. */
.hero-scan-txt { font-size: 14px; font-weight: 500; color: var(--muted-2); line-height: 1.5; white-space: nowrap; }

/* The App Store link, built to rhyme with the scan block above: the same 104px
   square, the same 16px gap, the same type. Two objects sitting on the page.
   Tighter top margin than .hero-scan because it is a second item in a pair
   rather than a new idea. */
.hero-store {
  /* ⚠️ BLOCK + fit-content, NOT inline-block. .hero-copy is a plain block
     container on desktop, so an inline-level child sits on the SAME LINE as
     the scan block whenever it fits, and a 104px badge fits. That silently
     turned "under the QR" into "beside it" twice before. */
  display: block; width: fit-content;
  margin-top: 18px; align-self: flex-start;
  text-decoration: none;
}
.hero-store img {
  display: block; width: 104px; height: auto;   /* the QR's width; height follows the 119.66 x 40 aspect */
  /* ⚠️ NOTHING ELSE GOES HERE. No radius, no shadow, no filter, no hover
     recolour. Apple's guidelines allow the badge to be scaled and nothing
     more, and a shadow on a lockup that already has its own border is both a
     rule broken and worse looking. */
}

/* On desktop the wrapper is not a box at all: the badge keeps behaving as a
   child of .hero-copy, sitting under the QR, and the button stays hidden. */
.hero-get { display: contents; }
.hero-mobile-cta { display: none; }

/* ----------  SCROLL CUES  ----------
   One at the bottom of every section, naming where it goes rather than just
   pointing down. On a page of full-screen sections the arrow alone leaves you
   guessing whether there is anything below worth the scroll. */
.scrollcue {
  position: relative; z-index: 3; margin: 26px auto 0; align-self: center;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  color: var(--muted-1); transition: color .3s var(--ease);
}
/* Pinned to the section floor. Inside the pinned sections it lives in the STICKY
   viewport, not the tall scroll track, so it stays on screen throughout. */
.scrollcue.floor {
  position: absolute; left: 50%; bottom: clamp(16px, 2.6vh, 30px);
  transform: translateX(-50%); margin: 0; white-space: nowrap;
}
/* In flow, for the two-column "idea" section where a centred floor cue would
   land on the photograph.

   `align-self: flex-start` keeps the whole cue at the text's left edge, so it
   lines up with the paragraph above it. `align-items: center` then centres the
   chevron INSIDE that box — and because the box shrinks to its widest child,
   which is the label, the arrow ends up centred under the words rather than
   hanging off their left edge. */
.scrollcue.inline { align-self: flex-start; align-items: center; margin: 30px 0 0; }
/* On the light sections the muted white is invisible. */
.real .scrollcue, .how .scrollcue, .ways .scrollcue, .glance .scrollcue { color: var(--ink-mute); }
.real .scrollcue:hover, .how .scrollcue:hover,
.ways .scrollcue:hover, .glance .scrollcue:hover { color: var(--ink); }
.scrollcue span { font-size: 11.5px; font-weight: 600; letter-spacing: 0.18em; text-transform: uppercase; }
.scrollcue svg { animation: cueBob 2.4s var(--ease) infinite; }
.scrollcue:hover { color: var(--text); }
@keyframes cueBob { 0%,100% { transform: translateY(0); opacity: .75 } 50% { transform: translateY(5px); opacity: 1 } }
@media (prefers-reduced-motion: reduce) { .scrollcue svg { animation: none; } }

.hero-stage { position: relative; display: flex; flex-direction: column; align-items: center; }
.hero-glow {
  width: 820px; height: 820px; top: -110px; left: 50%; transform: translateX(-50%);
  background: radial-gradient(closest-side, rgba(176,141,87,0.34), transparent 72%);
  filter: blur(46px);
}
/* The single focal element, deliberately larger than the copy column's weight. */
.phone.hero-phone { --w-max: 320px; box-shadow: 0 60px 90px -50px rgba(0,0,0,0.95); }

/* Caption at rest, affordance on hover — one line either way, so nothing shifts. */
.phone-hint { position: relative; }
.phone-hint .ph-credit,
.phone-hint .ph-scroll { display: block; transition: opacity .32s var(--ease); }
.phone-hint .ph-scroll { position: absolute; inset: 0; opacity: 0; color: var(--muted-1); }
.hero-stage:hover .ph-credit { opacity: 0; }
.hero-stage:hover .ph-scroll { opacity: 1; }

/* ============================================================ 2 · THE IDEA */
.real { display: grid; grid-template-columns: 1fr 1fr; align-items: stretch; min-height: 100vh; }
.real-photo { position: relative; overflow: hidden; background: #17140f; }
.real-photo .rw-imgwrap { position: absolute; top: 0; left: 0; width: 100%; }
.real-photo .rw-imgwrap img { width: 100%; height: auto; display: block; }
.real-body { position: relative; display: flex; flex-direction: column; justify-content: center; padding: 40px clamp(40px, 5vw, 88px); }
/* On the two-column desktop layout the cue drops to the floor of the column
   instead of sitting right under the paragraph, which left it stranded in the
   middle with a screen of empty white beneath it.
   Taken OUT OF FLOW rather than pushed with `margin-top: auto`: an auto margin
   eats all the free space, which would drag the heading off centre with it.
   Absolute keeps the copy exactly where it is and moves only the cue. `left`
   repeats the column's own padding so it still lines up with the text.
   Desktop only — below 1080px the section stacks and the cue belongs directly
   under the paragraph again (see the mobile rule further down). */
@media (min-width: 1081px) {
  .real-body .scrollcue.inline {
    position: absolute; margin: 0;
    left: clamp(40px, 5vw, 88px);
    bottom: clamp(30px, 6vh, 72px);
  }
}
.real-body .eyebrow { display: block; margin-bottom: 18px; }
.real-body h2 { font-size: clamp(30px, 3.2vw, 44px); line-height: 1.1; color: var(--ink); max-width: 620px; }
.real-sub { font-size: clamp(16px, 1.35vw, 18.5px); color: var(--ink-mute); line-height: 1.62; margin: 22px 0 0; max-width: 520px; }
.real-next {
  margin-top: 30px; display: inline-flex; align-items: center; gap: 9px; align-self: flex-start;
  font-family: var(--sans); font-weight: 600; font-size: 13px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--gold); transition: color .3s var(--ease);
}
.real-next:hover { color: var(--ink); }
.real-next svg { transition: transform .4s var(--ease); }
.real-next:hover svg { transform: translateY(3px); }

/* the tag marker on the café photo */
.rw-pin { position: absolute; z-index: 3; display: flex; align-items: center; gap: 10px; transform: translate(-50%, -50%); }
.rw-pin .rw-dot { position: relative; width: 15px; height: 15px; border-radius: 50%; background: var(--gold); border: 2px solid #F4ECDD; flex-shrink: 0; box-shadow: 0 2px 10px rgba(0,0,0,0.45); }
.rw-pin .rw-dot span { position: absolute; inset: -6px; border-radius: 50%; border: 2px solid rgba(176,141,87,0.85); animation: swWave 2.6s ease-out infinite; }
@keyframes swWave { 0% { transform: scale(0.7); opacity: 0.9 } 100% { transform: scale(2.1); opacity: 0 } }
.rw-pin .rw-lbl {
  display: inline-flex; align-items: baseline; gap: 0.36em; padding: 7px 13px; border-radius: 999px;
  background: rgba(176,141,87,0.94); border: 1px solid rgba(176,141,87,0.55);
  color: #F4ECDD; font-size: 12px; font-weight: 600; white-space: nowrap;
  box-shadow: 0 10px 26px -10px rgba(0,0,0,0.6);
}
.rw-pin .rw-lbl svg { height: 1.12em; width: 1.2544em; transform: translateY(10%); }

/* ============================================================ 3 · HOW IT WORKS */
.how-vp { position: sticky; top: 0; height: 100vh; display: flex; align-items: center; overflow: hidden; }
.how-grid { display: grid; grid-template-columns: 1fr 0.86fr; gap: 70px; align-items: center; }
.how-left .eyebrow { display: block; margin-bottom: 18px; }
.how-left h2 { font-size: clamp(30px, 3.4vw, 42px); line-height: 1.08; color: var(--ink); margin-bottom: 30px; }
.how-steps { list-style: none; margin: 0; padding: 0; }
.hstep { padding: 15px 0; border-top: 1px solid var(--ink-hair); cursor: pointer; opacity: 0.42; transition: opacity .45s var(--ease); }
.hstep:last-child { border-bottom: 1px solid var(--ink-hair); }
.hstep.active { opacity: 1; }
.hhead { display: flex; align-items: baseline; gap: 16px; }
.hstep .hnum { font-family: var(--sans); font-weight: 600; font-size: 12px; letter-spacing: 0.1em; color: var(--ink-mute); flex-shrink: 0; width: 24px; transition: color .35s var(--ease); }
.hstep.active .hnum { color: var(--gold); }
.hstep h3 { font-size: 22px; color: var(--ink); line-height: 1.15; }
.hbody { display: grid; grid-template-rows: 0fr; opacity: 0; transition: grid-template-rows .5s var(--ease), opacity .4s var(--ease), margin .5s var(--ease); }
.hbody > span { overflow: hidden; }
.hstep.active .hbody { grid-template-rows: 1fr; opacity: 1; margin-top: 8px; }
/* Runs to the width of the separator rules — at 400px the one-line steps
   were wrapping for no reason. */
.hbody p { margin: 0 0 0 40px; font-size: 15px; line-height: 1.6; color: var(--ink-mute); max-width: none; padding-right: 8px; }

.how-right { position: relative; display: flex; justify-content: center; }
.how-right .glow { width: 460px; height: 460px; top: 50%; left: 50%; transform: translate(-50%, -50%); }
.how-right .phone { --w-max: 330px; }
/* screens crossfade in place */
.cscreen { position: absolute; inset: 0; opacity: 0; transition: opacity .5s var(--ease); pointer-events: none; }
.cscreen.show { opacity: 1; pointer-events: auto; }

/* On a phone, the STEPS ARE SWIPED — so the screen has to travel.
   It used to cross-fade: you dragged your thumb across the phone and the image
   underneath dissolved into a different one without moving. The gesture said
   "carry this sideways" and the screen said "no". Now it slides out the way it
   was pushed and the next one arrives from the other side, which is the only
   thing that makes a swipe feel like it moved anything.

   Desktop keeps the cross-fade: there is no gesture there, the step changes as
   the page scrolls past it, and a horizontal slide would be a movement with no
   cause. */
@media (max-width: 1080px) {
  .cscreen {
    transition: opacity .34s var(--ease), transform .34s var(--ease);
    transform: translateX(var(--slide-out, 22%));
  }
  .cscreen.show { transform: translateX(0); }
  /* Direction: set by setHowScreen() on the container before the class flips. */
  #howScreens[data-dir="back"] .cscreen { --slide-out: -22%; }
  #howScreens[data-dir="fwd"] .cscreen { --slide-out: 22%; }
}

/* ---- step 03 · generating (recreated from the app so it can animate) ---- */
/* Everything below is sized from the phone's screen width (--sw) using the same
   ratios measured off the real capture, so the recreation matches the app at any
   phone size rather than only at one. Reference: 402pt-wide screen. */
.gen {
  --sw: calc(var(--w) - 20px);
  position: absolute; inset: 0;
  /* matches the app's GroupedBackground: canvas grey + a soft gold glow at the top */
  background: radial-gradient(circle 90% at 50% 0%, rgba(176,141,87,0.13), transparent 70%), var(--canvas);
  display: flex; flex-direction: column; align-items: center;
  /* NB: percentage padding resolves against WIDTH, so these read large — 55% of
     the 402pt-wide screen ≈ 22% down its height, measured off the real capture. */
  padding: 47% 8% 8%; text-align: center;
}
/* --sw (the screen's width) is declared here, on the shared parent, so BOTH the
   status bar and .gen can size off it. It used to live on .gen alone, which left
   every calc() in the sibling status bar invalid. */
.gen-wrap { --sw: calc(var(--w) - 20px); }

/* iOS status bar, drawn to the real device's proportions (402pt-wide screen) so it
   sits on the same surface as the rest of this recreated screen. */
.gen-status {
  position: absolute; top: 0; left: 0; right: 0; z-index: 4;
  height: calc(var(--sw) * 0.137);
  display: flex; align-items: center;
  padding: 0 calc(var(--sw) * 0.072);
  color: var(--ink); pointer-events: none;
}
.gs-time { font-size: calc(var(--sw) * 0.043); font-weight: 600; letter-spacing: 0.005em; }
.gs-island {
  position: absolute; left: 50%; transform: translateX(-50%); top: calc(var(--sw) * 0.028);
  width: calc(var(--sw) * 0.31); height: calc(var(--sw) * 0.092);
  border-radius: 999px; background: #000;
}
.gs-icons { margin-left: auto; display: flex; align-items: center; gap: calc(var(--sw) * 0.017); }
.gs-signal { width: calc(var(--sw) * 0.045); height: auto; }
.gs-wifi   { width: calc(var(--sw) * 0.042); height: auto; }
.gs-batt   { width: calc(var(--sw) * 0.066); height: auto; }

.gen-logo { display: inline-flex; align-items: baseline; gap: 0.36em; font-family: var(--serif); font-size: calc(var(--sw) * 0.05); color: var(--ink); }
.gen-logo svg { display: block; height: 1.12em; width: 1.2544em; transform: translateY(10%); }
.gen-logo span { font-weight: 600; color: var(--ink); }
.gen-ring { position: relative; width: 34%; aspect-ratio: 1 / 1; margin: 6% 0 5%; }
/* scoped to the ring itself — the checkmark is a sibling svg and must NOT inherit
   the 100% size or the -90° rotation */
.gen-ring .gr-svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.gr-track { fill: none; stroke: var(--ink-line); stroke-width: 4; }
.gr-arc {
  fill: none; stroke: var(--gold); stroke-width: 4; stroke-linecap: round;
  stroke-dasharray: 339.3; stroke-dashoffset: 237;   /* ~30% arc, as in the app */
  transform-origin: 50% 50%; animation: genSpin 1.15s linear infinite;
}
@keyframes genSpin { to { transform: rotate(360deg); } }
.gr-check {
  position: absolute; inset: 0; margin: auto; width: 30%; height: 30%; color: var(--gold);
  opacity: 0; transform: scale(0.4); transition: opacity .4s var(--ease), transform .5s var(--ease);
}
.gen h4 {
  font-family: var(--serif); font-weight: 600; font-size: calc(var(--sw) * 0.062);
  line-height: 1.15; color: var(--ink); margin: 0 0 6%; white-space: nowrap;
}
.gen-steps { list-style: none; margin: 0; padding: 0; width: 100%; background: #fff; border-radius: calc(var(--sw) * 0.045); box-shadow: 0 10px 26px -18px rgba(0,0,0,0.4); overflow: hidden; }
.gen-steps li { display: flex; align-items: center; gap: calc(var(--sw) * 0.032); padding: calc(var(--sw) * 0.035) calc(var(--sw) * 0.042); text-align: left; }
.gen-steps li + li { border-top: 1px solid var(--ink-line); }
.gen-steps i { width: calc(var(--sw) * 0.052); height: calc(var(--sw) * 0.052); border-radius: 50%; background: var(--paper); flex-shrink: 0; position: relative; transition: background .35s var(--ease); }
.gen-steps i::after {
  content: ''; position: absolute; inset: 0; margin: auto; width: 38%; height: 22%;
  border-left: 1.6px solid #fff; border-bottom: 1.6px solid #fff; transform: rotate(-45deg) scale(0.4);
  opacity: 0; transition: opacity .3s var(--ease), transform .3s var(--ease);
}
.gen-steps li.done i { background: var(--gold); }
.gen-steps li.done i::after { opacity: 1; transform: rotate(-45deg) scale(1); }
.gen-steps span { font-size: calc(var(--sw) * 0.036); color: var(--ink); line-height: 1.3; }
.gen-btn {
  /* auto bottom margin leaves the empty space BELOW the button, as in the app */
  margin: 6% 0 auto; width: 100%; padding: calc(var(--sw) * 0.042); border-radius: calc(var(--sw) * 0.038);
  background: #3A3A3E; color: #fff; font-size: calc(var(--sw) * 0.038); font-weight: 600;
  opacity: 0; transform: translateY(8px); transition: opacity .45s var(--ease), transform .45s var(--ease);
}
/* finished state */
.gen.done .gr-arc { animation: none; stroke-dashoffset: 0; transition: stroke-dashoffset .55s var(--ease); }
.gen.done .gr-check { opacity: 1; transform: scale(1); }
.gen.done .gen-btn { opacity: 1; transform: none; }

/* ============================================================ 4 · SAMPLES
   Not flat black — a warm gold wash over off-black, the same feel as the app's
   sign-in screen, so the dark UI elements have something to sit against. */
.gallery.dark { background: #0A0A0B; }
/* The gold wash lives on the STICKY viewport, not the (very tall) section — on the
   section it stretches over the whole pin and visibly drifts as you scroll. Here it
   stays put behind the pinned content. */
.gal-vp {
  position: sticky; top: 0; height: 100vh; display: flex; align-items: center; overflow: hidden;
  /* Warmer than it was, and thrown from the LEFT. Every other dark screen on the
     page glows from the top or the centre; an identical wash on each one made
     them read as the same screen scrolling past. Here the light comes from the
     side the copy is on, and the phone on the right sits against the darker half. */
  background:
    radial-gradient(ellipse 74% 68% at 2% 46%, rgba(176,141,87,0.30), transparent 72%),
    radial-gradient(ellipse 58% 42% at 24% 4%, rgba(176,141,87,0.14), transparent 70%),
    radial-gradient(ellipse 62% 46% at 92% 104%, rgba(176,141,87,0.09), transparent 72%),
    #0A0A0B;
}
.gallery-grid { display: grid; grid-template-columns: 1.12fr 0.88fr; gap: 60px; align-items: center; }
.g-left .eyebrow { display: block; margin-bottom: 18px; }
/* one line, never breaking the last word onto its own row */
.g-left h2 { font-size: clamp(30px, 3.5vw, 47px); color: var(--text); margin-bottom: 18px; white-space: nowrap; }
.g-desc { font-size: clamp(16px, 1.35vw, 18.5px); color: var(--muted-1); margin: 0 0 32px; max-width: 460px; line-height: 1.6; }
/* No cards, no rules — the list is just type. The chosen sample is the one at full
   strength, with a gold number and chevron; the rest sit back. */
.demo-list { display: flex; flex-direction: column; gap: 2px; max-width: 380px; }
.demo-item {
  display: flex; align-items: baseline; gap: 18px; padding: 11px 0; cursor: pointer;
  background: none; border: 0; opacity: 0.42;
  transition: opacity .35s var(--ease), transform .35s var(--ease);
}
.demo-item:hover { opacity: 0.72; transform: translateX(3px); }
.demo-item.active { opacity: 1; }
.demo-item .idx { font-family: var(--sans); font-weight: 600; font-size: 13.5px; letter-spacing: 0.1em; color: var(--muted-2); width: 26px; flex-shrink: 0; transition: color .35s var(--ease); }
.demo-item.active .idx { color: var(--gold); }
.demo-item .dmeta { flex: 1; min-width: 0; }
.demo-item .dmeta .t { font-family: var(--sans); font-weight: 600; font-size: 23px; color: var(--text); line-height: 1.3; }
.demo-item .go { color: var(--muted-2); flex-shrink: 0; display: flex; opacity: 0; transition: opacity .3s var(--ease), color .3s var(--ease); }
.demo-item:hover .go { opacity: 0.6; }
.demo-item.active .go { opacity: 1; color: var(--gold); }
.g-hint { margin-top: 20px; display: inline-flex; align-items: center; gap: 7px; font-size: 12px; color: var(--muted-2); }

.g-right { position: relative; display: flex; flex-direction: column; align-items: center; }
.g-right .glow { width: 460px; height: 460px; top: 50%; left: 50%; transform: translate(-50%, -50%); }
.g-right .phone { --w-max: 330px; }
.g-open {
  margin-top: 18px; display: inline-flex; align-items: center; gap: 7px;
  font-size: 12.5px; font-weight: 600; color: var(--muted-1);
  border-bottom: 1px solid var(--hair-2); padding-bottom: 3px; transition: color .3s var(--ease);
}
.g-open:hover { color: var(--gold); }


/* ============================================================ 6 · REGISTER INTEREST */
/* full-height, like every other section */
/* Sign-up + footer = one screen, together. */
.closing { min-height: 100vh; display: flex; flex-direction: column; }
.cta {
  /* ⚠️ ASYMMETRIC ON PURPOSE (22 Aug 2026). The card is centred in a flex box
     that also carries the footer, so an even 96/72 left it sitting high with a
     gap underneath. Moving 28px from the bottom padding to the top drops the
     whole block without changing the section's height — it still has to be one
     screen, footer included. */
  position: relative; overflow: hidden; text-align: center; padding: 124px 24px 44px;
  flex: 1; display: flex; align-items: center; justify-content: center;
  /* ⚠️ IT HAS TO LAND ON THE FOOTER'S BLACK. The section sat on `--bg-2` and
     the footer on `--bg`, one step darker, so the two met in a visible line
     across the full width of the page right under the sign-up. The linear stop
     below carries the last half of the section down to exactly `--bg`, so the
     boundary has nothing to show: the warm glow still opens the section at the
     top, and the bottom simply becomes the footer. */
  background:
    radial-gradient(ellipse 80% 50% at 50% 0%, rgba(176,141,87,0.10), transparent 70%),
    linear-gradient(to bottom, var(--bg-2) 48%, var(--bg) 100%),
    var(--bg-2);
}
.cta .glow { width: 700px; height: 480px; top: -150px; left: 50%; transform: translateX(-50%); opacity: 0.7; }
.cta-card { position: relative; z-index: 2; max-width: 600px; margin: 0 auto; }
.cta .eyebrow { display: block; margin-bottom: 20px; }
.cta h2 { font-size: clamp(38px, 5.6vw, 62px); color: var(--text); }
.cta .lede { color: var(--muted-1); font-size: 17px; margin: 24px auto 36px; max-width: 490px; line-height: 1.65; }
.cta-form { display: flex; gap: 10px; max-width: 470px; margin: 0 auto; }
.cta-form input {
  flex: 1; padding: 16px 20px; border-radius: 14px; background: rgba(255,255,255,0.05);
  border: 1px solid var(--hair-2); color: var(--text); font-family: var(--sans); font-size: 15px;
  transition: border-color .3s var(--ease), background .3s var(--ease);
}
.cta-form input::placeholder { color: var(--muted-2); }
.cta-form input:focus { outline: none; border-color: var(--gold); background: rgba(176,141,87,0.06); }
.cta-form .btn { flex-shrink: 0; }
.cta-done { color: var(--text); font-size: 16px; padding: 16px; line-height: 1.55; }
.cta-done strong { color: var(--gold); font-weight: 600; }
.cta-err { color: #E4A08F; font-size: 13.5px; margin-top: 12px; }
.cta-fine { margin-top: 20px; font-size: 13px; color: var(--muted-2); }

/* footer */
.foot {
  padding: 26px 32px 34px; background: var(--bg);
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
/* The rule is inset rather than full-bleed — a closing mark under the sign-up,
   not a separate band across the whole viewport. */
.foot::before {
  content: ''; display: block; width: min(340px, 62vw); height: 1px;
  background: var(--hair); margin-bottom: 22px;
}
/* Monochrome here, not gold: the footer is a full stop, not another accent. */
.foot .fnote { color: var(--muted-2); font-size: 12.5px; }
/* Quieter than the tagline above it and louder than the photo credit below:
   the legal links are a thing you should be able to find, not a thing the page
   is asking you to read. Wraps on a narrow phone rather than overflowing. */
.foot .flegal {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 4px 16px; font-size: 11.5px;
}
.foot .flegal a {
  color: var(--muted-2); text-decoration: none;
  transition: color .3s var(--ease);
  /* A comfortable tap target without visibly padding the row apart. */
  padding: 3px 0;
}
.foot .flegal a:hover { color: var(--text); }
.foot .fcredit { color: var(--muted-2); font-size: 10.5px; opacity: 0.45; }
.foot .fcredit a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.foot .fcredit a:hover { opacity: 1; }

/* ============================================================ RESPONSIVE */
@media (max-width: 1080px) {
  .how-grid, .gallery-grid { grid-template-columns: 1fr; gap: 40px; }
  .how-vp, .gal-vp { position: static; height: auto; padding: 90px 0; overflow: visible; }
  .how-pin, .gal-pin { height: auto !important; }
  /* phones sit BELOW the text on narrow screens */
  .how-right, .g-right { order: 0; }
  .how-left h2 { margin-bottom: 20px; }
  .how-steps { margin: 0; }
  .hstep { display: none; padding: 0; opacity: 1; border: 0; }
  .hstep.active { display: block; padding: 18px 20px; background: #fff; border: 1px solid var(--ink-line); border-radius: 18px; box-shadow: 0 14px 36px -22px rgba(0,0,0,0.3); }
  .hstep.active .hbody { grid-template-rows: 1fr; opacity: 1; margin-top: 8px; }
  .hbody p { margin-left: 40px; }
  .g-left h2 { margin-bottom: 10px; }
  .g-desc { margin-bottom: 20px; }
  .demo-list { gap: 8px; }
  .demo-item { padding: 12px 15px; }
  .real { grid-template-columns: 1fr; min-height: 0; }
  .real-photo { height: 54vh; order: -1; }
  .real-body { padding: 62px clamp(22px, 6vw, 60px) 78px; }
}
/* ⚠️ THE TABLET BAND IS GONE (22 Aug 2026). There used to be three switches —
   the nav at 860, most of the mobile layout at 900, and the stacked sections at
   1080 — so between roughly 900 and 1080 the page was neither thing: desktop
   nav links over stacked sections, the phone off its centre, the pinned
   scroll-through replaced by a swipe control that nothing else agreed with.
   One number now: below 1081 the page is the mobile page, which is also where
   the JS has always drawn the line (`DESKTOP` in script.js). */

/* mobile step control (built by JS) */
.how-swipe { display: none; flex-direction: column; align-items: center; gap: 10px; margin-top: 22px; }
.hs-row { display: flex; align-items: center; gap: 18px; }
.hs-arrow { width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--ink-line); background: #fff; color: var(--ink); font-size: 19px; line-height: 1; display: flex; align-items: center; justify-content: center; }
.hs-arrow:disabled { opacity: 0.35; }
.hs-dots { display: flex; gap: 7px; }
.hs-dots i { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-line); transition: background .3s var(--ease), transform .3s var(--ease); }
.hs-dots i.on { background: var(--gold); transform: scale(1.25); }
@media (max-width: 1080px) { .how-swipe { display: flex; } }
.hs-hint { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; color: var(--ink-mute); }

/* ============================================================ 4b · TWO WAYS
   Two halves of one screen, split by a single hairline. No cards and no icons:
   the whole point is that there are exactly two answers, and anything decorative
   would suggest a list that continues. */
.ways {
  /* The app's own canvas grey, not a colour invented for this page. Black above
     and white below already separate it; a bespoke beige only added a third
     palette the product never uses. */
  background: var(--canvas); color: var(--ink);
  min-height: 100vh;
  display: flex; align-items: center;
  padding: clamp(72px, 8vw, 104px) 0;
}
.ways-head { text-align: center; max-width: 620px; margin: 0 auto clamp(44px, 5.5vw, 72px); }
.ways-head .eyebrow { display: block; margin-bottom: 16px; }
.ways-head h2 {
  font-size: clamp(30px, 3.5vw, 46px); font-weight: 500;
  letter-spacing: -0.025em; color: var(--ink);
}

.ways-split { display: grid; grid-template-columns: 1fr 1fr; align-items: start; }
.way { padding: 0 clamp(20px, 4vw, 60px); }
/* The divider belongs to the second column, so it can't leave a stray rule at
   the edge when these stack on a phone. */
.way + .way { border-left: 1px solid rgba(11,11,12,0.13); }

.way-head { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; min-height: 1.05em; }
/* Display type, and the largest thing on the screen: the two words ARE the
   section. Serif, like the H2s, because that's what display type is here. */
.way-title {
  font-family: var(--serif); font-weight: 500;
  font-size: clamp(42px, 5.4vw, 72px); letter-spacing: -0.03em; line-height: 1;
  color: var(--ink); margin: 0;
}
.way-body {
  margin: 22px 0 0; max-width: 430px;
  font-size: clamp(16px, 1.4vw, 18.5px); line-height: 1.62; color: var(--ink-2);
}
/* Smaller and set further down than the body it qualifies: it is a footnote
   about what the space side will include, not a second paragraph. */
.way-note { margin: 26px 0 0; max-width: 400px; font-size: 13px; line-height: 1.6; color: var(--ink-mute); }

/* Coming soon is a promise, not a product. The Space column is set in the same
   type as Author but with less ink, so the difference is legible before anyone
   reads the badge.
   Per ELEMENT, not on the column: opacity on `.way` would establish a group and
   drag the gold badge down with it, and the badge is the one thing here that
   must stay at full strength. */
.way.soon .way-title,
.way.soon .way-body { opacity: 0.58; }

/* Gold, filled, and sitting beside the word it qualifies — the one thing on this
   side that must not be missed. */
.way-soon {
  display: inline-block; padding: 6px 13px; border-radius: 999px;
  background: var(--gold); color: #FBF7EF;
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
  box-shadow: 0 10px 24px -12px rgba(176,141,87,0.9);
}

@media (max-width: 1080px) {
  .ways { min-height: 0; padding: 64px 0; }
  .ways-split { grid-template-columns: 1fr; gap: 40px; }
  .way { padding: 0; }
  .way + .way { border-left: 0; border-top: 1px solid rgba(11,11,12,0.13); padding-top: 40px; }
  .way-title { font-size: clamp(38px, 12vw, 54px); }
  .way-body, .way-note { max-width: none; }
}

/* ============================================================ 5 · FEATURES
   Real tiles, not a hairline lattice: white cards with the app's soft borderless
   shadow (Palette.screen + black 5%, r10, y3 — see .softCard in Components.swift),
   floating on the app's canvas grey. Same object the app uses everywhere. */
.glance {
  background: var(--canvas);
  min-height: 100vh;
  display: flex; align-items: center;
  padding: clamp(72px, 8vw, 104px) 0;
}
.gl-head { max-width: 640px; margin-bottom: clamp(34px, 4.2vw, 50px); }
.gl-head h2 { font-size: clamp(30px, 3.5vw, 46px); font-weight: 500; letter-spacing: -0.025em; color: var(--ink); margin-top: 16px; }

/* ⚠️ `minmax(0, 1fr)`, NOT `1fr`, AND `min-width: 0` ON THE TILE (3 Sep 2026).
   A bare `1fr` is `minmax(auto, 1fr)`, and `auto` means "never narrower than
   the content needs". Combined with the tile's `aspect-ratio` that is worse
   than it sounds: the ratio TRANSFERS the content's minimum HEIGHT into a
   minimum WIDTH, so on a short window — where the rules below squash the ratio
   to 1/0.66 — each column demanded 327px, four of them wanted 1308px, and the
   1116px grid simply overflowed to the right. The section stopped being
   centred and the last tile ran off the screen.
   With both of these the column width wins and a tile that needs more room
   grows DOWNWARDS, which is what the note below always claimed happened. */
/* ⚠️ `grid-auto-rows: 1fr` AND NO `aspect-ratio`, AND THAT PAIR IS THE POINT
   (3 Sep 2026). The tiles used to be sized by a ratio, which sounds like it
   makes them equal and does the opposite: a ratio sets a height from a width,
   and any tile whose text does not fit simply grows past it. So the tile with
   three lines of copy stood taller than the three beside it and the row looked
   ragged. Equal rows are the grid's job, not the item's: `1fr` rows make every
   row the same height and the default `stretch` makes every tile fill it, so
   all eight end up identical in both directions. When they will not fit a short
   window, the space inside them gives (padding, type, clamped lines) rather
   than the box changing shape. */
.gl-grid {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-auto-rows: 1fr; gap: 16px;
}
.gl-tile {
  min-width: 0;
  background: var(--paper-2); border-radius: 18px; padding: 30px 24px 24px;
  box-shadow: 0 10px 26px -14px rgba(11,11,12,0.18), 0 1px 2px rgba(11,11,12,0.04);
  display: flex; flex-direction: column;
  transition: transform .4s var(--ease), box-shadow .4s var(--ease);
}
.gl-tile:hover { transform: translateY(-3px); box-shadow: 0 18px 38px -16px rgba(11,11,12,0.24), 0 1px 2px rgba(11,11,12,0.04); }
.gl-tile svg { width: 30px; height: 30px; fill: none; stroke: var(--gold); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; margin-bottom: 20px; }
.gl-tile h3 { font-family: var(--sans); font-size: 18.5px; font-weight: 600; letter-spacing: -0.012em; line-height: 1.28; color: var(--ink); }
.gl-tile p { margin: 10px 0 0; font-size: 14.5px; line-height: 1.55; color: var(--ink-mute); }
.gl-soon {
  align-self: flex-start; margin-top: 12px; padding: 3px 9px; border-radius: 999px;
  background: var(--gold-soft); color: #8a6a35;
  font-size: 9.5px; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase;
}

/* The odd tile out — gold, so the grid closes on what's next. */
.gl-next { background: linear-gradient(155deg, #C29B60, #A8834D); box-shadow: 0 14px 32px -14px rgba(176,141,87,0.72); }
.gl-next svg { stroke: #FBF7EF; }
.gl-next h3 { color: #FFFDF8; }
.gl-next p { color: rgba(255,253,248,0.86); }
.gl-next:hover { box-shadow: 0 22px 44px -16px rgba(176,141,87,0.8); }

/* ⚠️ SQUARE IS A PREFERENCE, NOT A REQUIREMENT (22 Aug 2026). The tiles were a
   fixed 1:1, so on a 13" laptop the six of them plus the heading were taller
   than the section and the last row fell off the screen — a "glance" you have
   to scroll through is not a glance. Below a tall-enough viewport they relax
   into rectangles, capped so they never become letterbox strips. */
@media (min-width: 1081px) and (max-height: 900px) {
  .gl-tile p { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
}
/* Room to breathe where there is room. Without the old ratio the tiles are as
   tall as their content plus their padding, which on a big screen left them
   looking squat next to the heading. This is the padding doing the job the
   ratio used to, and it cannot overflow anything: it adds the same amount to
   every tile, and they are all one row height. */
@media (min-width: 1081px) and (min-height: 901px) {
  .gl-tile { padding: 34px 26px 30px; }
  .gl-tile svg { margin-bottom: 26px; }
}
/* ⚠️ AND THE SPACE AROUND THEM HAS TO GIVE TOO (3 Sep 2026).
   Squashing the tiles was only half of it. The padding, the heading and the
   gaps were all sized off the viewport WIDTH (`8vw`, `4.2vw`), which says
   nothing about how much room there is to stack things in. On a laptop at
   600-700px tall the section came out 775px high inside a 600px window, so the
   "Stay in touch" cue that carries you to the next chapter sat below the fold
   and looked cut off. Everything vertical is measured in `vh` here, so the
   section answers the dimension it is actually running out of. */
@media (min-width: 1081px) and (max-height: 820px) {
  .glance { padding: clamp(20px, 3.4vh, 56px) 0; }
  .gl-head { margin-bottom: clamp(12px, 2vh, 30px); }
  .gl-head h2 { font-size: clamp(26px, 2.8vw, 38px); margin-top: 10px; }
  .gl-grid { gap: 12px; }
  .gl-tile { padding: 20px 18px 18px; }
  .gl-tile svg { width: 26px; height: 26px; margin-bottom: 12px; }
  .gl-tile h3 { font-size: 17px; }
  .gl-tile p { font-size: 13.5px; margin-top: 8px; }
}
@media (min-width: 1081px) and (max-height: 720px) {
  .gl-tile p { -webkit-line-clamp: 2; }
  .gl-tile { padding: 16px 16px 14px; }
  .gl-tile svg { margin-bottom: 10px; }
}
@media (max-width: 1080px) { .gl-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } .glance { min-height: 0; } }
@media (max-width: 560px)  { .gl-grid { grid-template-columns: 1fr; } }

/* ============================================================ MOBILE
   Phones get a different shape, not a squeezed desktop: each section has to sit
   inside one screen with the browser chrome eating top and bottom, so the demo
   phones shrink and the pickers become arrows flanking them. */
.m-br { display: none; }

@media (max-width: 1080px) {
  /* 1 · "Bring your work / to light." reads better broken after the noun. */
  .m-br { display: inline; }
  .hero h1 { font-size: clamp(40px, 11vw, 56px); line-height: 1.1; }

  /* 2 · Read first, then see the tag in place. The photo led before, which
     asked people to interpret an image before knowing what it was. */
  .real-photo { order: 1; height: 46vh; }
  .real-body { order: 0; padding: 56px clamp(22px, 6vw, 60px) 34px; }

  /* 3 + 4 · Both demo sections fit one screen, arrows either side of the phone.
     The budget is tight: ~720px of usable height once browser chrome is out, so
     the heading, the step card and the phone each give something up. */
  .how-vp, .gal-vp { padding: 40px 0 32px; }
  .how-grid, .gallery-grid { gap: 20px; }
  .how-left h2 { font-size: clamp(25px, 7vw, 32px); margin-bottom: 14px; }
  .how-left .eyebrow { margin-bottom: 10px; }
  .hstep.active { padding: 13px 15px; border-radius: 14px; }
  .hstep h3 { font-size: 18px; }
  .hbody p { font-size: 13.5px; margin-left: 32px; }
  .hstep .hnum { font-size: 11px; width: 20px; }
  .how-right .phone { --w-max: 172px; }
  .gallery .phone { --w-max: 190px; }
  .how-right, .g-right { position: relative; }

  .how-swipe { margin-top: 10px; }
  .hs-hint { display: none; }          /* the arrows say it; the line repeated it */

  /* Arrows sit level with the middle of the phone, outside its frame — and out
     into the page gutter, since the mockup now claims 82% of the stage. */
  .hs-row {
    /* Inside the stage, not hanging off it. At -14px against a 14px section
       inset the arrows sat flush against the edges of the screen, which reads
       as clipped and is awkward to hit near the bezel. */
    position: absolute; top: 50%; left: 4px; right: 4px;
    transform: translateY(-50%);
    justify-content: space-between; gap: 0; pointer-events: none;
  }
  .hs-row > * { pointer-events: auto; }
  .hs-arrow { width: 34px; height: 34px; font-size: 17px; }
  .hs-dots { display: none; }

  .gs-swipe {
    /* Same clearance as the how-it-works arrows; see `.hs-row`. */
    position: absolute; top: 50%; left: 4px; right: 4px;
    display: flex; justify-content: space-between;
    transform: translateY(-50%); pointer-events: none;
  }
  .gs-arrow {
    pointer-events: auto;
    width: 34px; height: 34px; border-radius: 50%;
    border: 1px solid var(--hair-2); background: rgba(255,255,255,0.07);
    backdrop-filter: blur(8px);
    color: var(--text); font-size: 20px; line-height: 1;
    display: flex; align-items: center; justify-content: center;
  }
  .gs-arrow:active { transform: scale(0.94); }
  .gs-meta { margin-top: 14px; text-align: center; }
  .gs-title { display: none; }   /* the page itself is showing its title */
  .gs-dots { margin-top: 0; }
  .gs-title { font-size: 15px; font-weight: 600; color: var(--text); }
  .gs-dots { display: flex; gap: 7px; justify-content: center; margin-top: 10px; }
  .gs-dots i { width: 7px; height: 7px; border-radius: 50%; background: var(--hair-2); transition: background .3s var(--ease), transform .3s var(--ease); }
  .gs-dots i.on { background: var(--gold); transform: scale(1.25); }

  /* The list is replaced by the arrows — three items don't need a list, and it
     was costing the vertical space the section needed. */
  .demo-list, .g-hint { display: none; }
  .g-left h2 { font-size: clamp(28px, 8vw, 38px); }
  .g-desc { margin-bottom: 4px; }

  /* 5 · Features are ONE column of horizontal pills on a phone. The layout
     itself lives in the MOBILE LAST block at the foot of this file, with
     everything else that shapes a phone screen; what was here was an earlier
     two-column version whose `!important` quietly beat it. Two attempts at
     "one column" shipped and neither took effect, because of this line. */
  .gl-head h2 { font-size: clamp(28px, 8vw, 38px); }
}

/* Desktop keeps the picker; the arrows are a phone affordance only. */
.gs-swipe, .gs-meta { display: none; }
@media (max-width: 1080px) { .gs-swipe { display: flex; } .gs-meta { display: block; } }

/* ═══════════════════════════════════════════════════════════════════════════
   MOBILE LAST.

   This block has to sit at the END of the stylesheet, after every base rule it
   overrides. It used to live in the middle, next to the other responsive rules,
   where `.ways`, `.glance` and `.closing` are all defined further down the file
   — same specificity, later in source, so the desktop paddings quietly won and
   two sections overflowed on a phone with nothing in the media query to blame.
   Add mobile overrides here, not up there.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ============================================================ MOBILE
   Below 900px every section is exactly ONE SCREEN and snaps to it, the same as
   on desktop. It used to give up on that here — sections went `min-height: 0`
   and simply ran on — which turned the page into one long scroll where a phone
   mockup could be half off the bottom while its caption was off the top.

   Two things make it work:
     · `dvh` — the DYNAMIC viewport, the height you can actually see right now.
       The other two units are each wrong half the time on iOS Safari, and both
       were tried:
         `vh`  is the height with the toolbars HIDDEN, so while they are shown
               every section is too tall and its bottom is cut off.
         `svh` is the height with the toolbars SHOWN — the smallest the viewport
               ever gets. It fixed the cropping and introduced the opposite
               fault: once you scroll and the toolbars retract, the visible area
               grows by ~90px that no section claims, so every screen carries a
               strip of the section above it and a strip of the one below. On a
               page whose whole premise is one-idea-per-screen, that is the
               defect you notice first, and it is invisible in a headless
               browser because there are no toolbars to retract.
       `dvh` is the only one that is right in both states. The cost is that a
       section resizes while the toolbar animates; it is affordable here only
       because the scroll is not snap-locked (see below), so a height change
       mid-gesture does not throw you into another section.
     · The phone is sized from the space LEFT OVER, in JS (fitPhones() in
       script.js), never from a width guess. The demo sections are the whole
       point of the page on a phone; a cropped mockup makes them unusable.
       Because dvh moves, the refit is debounced — see `measureAll`. */
@media (max-width: 1080px) {
  /* NO SNAPPING. This is a deliberate reversal of the mandatory snap that was
     here, and it is the single biggest thing wrong with the page on a phone.

     `mandatory` + `scroll-snap-stop: always` means the scroller owns the
     gesture: one flick moves exactly one section however hard you throw it,
     momentum is cancelled at every boundary, and a section that is even a pixel
     taller than the viewport can never come to rest where you left it — release
     mid-section and you are yanked somewhere else. It also made scrolling UP to
     the hero land on "the idea" instead, because the hero is the one section
     shorter than a full screen (the bar sits above it) and its snap start lost.

     The sections still READ as screens — each is `min-height: 100svh` — but the
     scroll is the visitor's again, and a section is now free to be taller than
     one screen where the content genuinely needs it (features). */
  html { scroll-snap-type: none; }
  .hero, .real, .how, .gallery, .ways, .glance, .closing {
    scroll-snap-align: none; scroll-snap-stop: normal;
  }

  /* One screen each. The demo sections stay exactly one — a phone mockup is
     only usable whole — while the text sections may grow past it. */
  /* AN ORDINARY PAGE THAT SCROLLS.
     `min-height`, never a fixed `height`, and nothing clipped. A section is at
     least a screen so the one-idea-per-screen rhythm survives, but it is free
     to be taller and the page just scrolls, which is what a thumb expects.

     `svh`, deliberately, and NOT `dvh`. `dvh` tracks the visible area, so every
     time iOS slides its toolbar away every section on the page changed height
     at once — the mockups were re-fitted, everything below them jumped, and the
     page appeared to breathe while you were trying to read it. `svh` is the
     height with the toolbars SHOWN and never moves. The old reason for `dvh` was
     that `svh` left a strip of the next section visible; with `height: auto` and
     nothing clipped, a section that is a little shorter than the window is not a
     defect any more, it is just a page. */
  .hero, .real, .how, .gallery, .ways,
  .glance, .closing {
    height: auto; min-height: 100svh;
    /* `overflow-x: clip`, not `hidden`, and not `visible`.
       The gold glows are 460-700px blurs deliberately bleeding past the section
       edges; the old `overflow: hidden` contained them. Letting sections flow
       set overflow to visible and those blurs pushed the page 35px wider than
       the screen, so the whole site scrolled sideways.
       `clip` trims them on the x-axis WITHOUT making the section a scroll
       container, so vertical flow is untouched — which `hidden` would not
       guarantee, since `hidden` on one axis forces `auto` on the other. */
    overflow-x: clip; overflow-y: visible;
  }
  /* The bar is in flow above the hero, so the hero's screen is what's left. */
  .hero { min-height: calc(100svh - var(--navh)); }
  .how-pin, .gal-pin { height: auto !important; }
  .how-vp, .gal-vp {
    position: static; height: auto; padding: 0; overflow: visible;
    align-items: stretch; justify-content: flex-start;
  }

  /* --- hero ---
     NO PHONE. The hero mockup is a still image of a page the visitor is about
     to be shown twice more, at a size worth looking at, in "how it works" and
     "samples". On a phone it was eating two thirds of the first screen to make
     the promise smaller and the proof no clearer — so the screen goes to the
     one sentence that has to land, and the button that acts on it.

     Centred, and centred only here. The rest of the page stays ragged-right
     against a left margin; a hero with nothing beside it is a title page, and a
     title page centres. */
  .hero {
    padding: 8px 24px 52px;
    justify-content: center; align-items: center; text-align: center;
  }
  .hero > .scrollcue {
    position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%); margin: 0;
  }
  .hero-stage { display: none; }
  .hero-scan { display: none; }   /* you can't scan a code on the screen you're holding */
  /* ⚠️ THE STORE LINK STAYS. The block above it goes on mobile because you
     cannot scan a code on the screen you are holding, and it would be easy to
     hide this one with it by association. The opposite is true: a phone is the
     one device that can act on this link immediately, so it is more use here
     than on a desktop. Smaller icon, and it centres with the rest of the
     mobile hero instead of hanging off the left. */
  /* The QR is hidden on a phone, so the badge and the gold button are the only
     two ways off this screen, and they read as a stack. ONE grid column sized
     to the widest of them, both taking all of it, so they match in English
     (button 217px) and in Polish (291px) without a number written down. The
     max-width is the small-phone guard: 291px on a 320px screen would leave
     14px of gutter. */
  .hero-get {
    display: grid; grid-template-columns: 1fr; justify-items: stretch;
    width: fit-content; max-width: calc(100vw - 48px);
    margin: 26px auto 0; gap: 16px;
  }
  /* ⚠️ SECOND, AND HALF THE WIDTH. The gold button is the primary action on a
     phone and the badge is the quieter alternative, so it sits under it at 50%
     of its width: about 108px in English and 145px in Polish, and the ratio
     holds in both because the column is sized by the button's own text.
     `order` rather than a DOM move, because on desktop the badge belongs under
     the QR and the button is not rendered at all. */
  .hero-mobile-cta { order: 1; }
  .hero-store { order: 2; display: block; width: 50%; justify-self: center; margin: 0; align-self: auto; }
  .hero-store img { width: 100%; }
  .phone-hint { display: none; }

  .hero-grid {
    display: flex; flex-direction: column; align-items: center; text-align: center;
    flex: 1 1 auto; min-height: 0; justify-content: center; gap: 0;
    /* Sits BELOW the centre line, deliberately. Dead-centre reads high once the
       scroll cue is pinned to the floor, and a title page wants its weight low. */
    padding-top: 7vh;
  }
  .hero-copy {
    max-width: 460px; display: flex; flex-direction: column; align-items: center;
    flex: none; width: 100%;
  }
  .hero h1 { font-size: clamp(38px, 11.5vw, 54px); line-height: 1.1; }
  .hero .lede {
    font-size: clamp(15px, 4.3vw, 17.5px); line-height: 1.5;
    margin: 20px auto 0; max-width: 380px;
  }
  .hero-mobile-cta {
    display: flex; align-items: center; justify-content: center; gap: 9px;
    width: 100%; font-size: 15.5px; padding: 15px 30px; margin-top: 0;
  }

  /* --- the idea ---
     The photograph and the words are ONE screen between them, split at a fixed
     ratio rather than left to negotiate. Before, the photo took "whatever is
     left" and the copy took "what it needs" — so on a tall phone the picture
     ballooned and on a short one the text pushed the picture down to a strip,
     and on some sizes the pair simply exceeded the viewport and the last line
     was cut. A fixed 42/58 split is predictable at every height. */
  .real { display: flex; flex-direction: column; overflow: hidden; }
  /* 60%, up from 48%. The words here are four short lines; at 48% they were
     centred in a 443px column with 200px of nothing around them, so the screen
     read half-empty even though the section was exactly one screen tall. The
     photograph is the argument the section makes, and it is the only thing
     here with any appetite for height — so the slack goes to it. */
  /* WORDS FIRST, PHOTOGRAPH UNDER THEM. `order: -1` used to pull the picture
     above the copy, so the section opened on an image with no idea attached and
     you had to scroll past it to find out what you were looking at. Reading
     order now matches the DOM: the claim, then the evidence.

     A REAL HEIGHT, not a percentage: `flex: 0 0 60%` resolved against the
     section's height, and the section is `height: auto` now — so the photograph
     collapsed to nothing and the section opened on a blank half-screen.
     `svh` keeps it stable while the toolbar moves. */
  .real-photo {
    order: 1; flex: 0 0 auto; height: 46svh; min-height: 0; overflow: hidden;
  }
  .real-body { order: 0; }
  .real-photo img { width: 100%; height: 100%; object-fit: cover; }
  .real-body {
    flex: 1 1 auto; min-height: 0; overflow: hidden;
    padding: 18px 22px 38px; justify-content: center; text-align: left;
  }
  .real-body h2 { font-size: clamp(22px, 6.2vw, 30px); }
  .real-sub { font-size: 14px; line-height: 1.55; margin-top: 10px; }
  /* The cue was 30px below the paragraph on a screen that had room to spare.
     It doesn't any more. */
  .real-body .scrollcue.inline { margin-top: 16px; }

  /* --- how it works + samples: heading, then the phone in whatever is left --- */
  /* `.how-grid.wrap`, not `.how-grid`: these elements carry BOTH classes, and
     `.wrap { padding: 0 22px }` is a shorthand that zeroes the top pad. Ordering
     the rules to win was tried twice and lost twice — one class more is specific
     enough to settle it wherever the rule ends up sitting.

     What it cost while it was losing: the section heading sat behind the nav bar,
     invisibly, on every phone. */
  /* No `var(--navh)` in the top padding any more — the bar is in flow above the
     page, not floating over it, so nothing needs to duck under it. That is
     ~52px handed straight to the mockup on both demo sections. */
  /* 14px of side padding, not 22. The phone is width-limited on these two
     sections — it hits the lane before it hits the ceiling — so every pixel of
     side margin is a pixel off the mockup in both directions. The heading above
     it keeps the page's normal 22px inset so the text still lines up with every
     other section. */
  .how-grid.wrap, .gallery-grid.wrap {
    grid-template-columns: 1fr; gap: 30px; height: 100%;
    /* Top: 26px, not 12. At 12 the eyebrow sat on the section's edge with the
       heading right behind it and the mockup right behind that — three things
       stacked with no air, which read as crammed however well each one was set.
       The demo sections are the two that suffered most, because the phone will
       happily take every pixel it is offered.
       Bottom: the SCROLL CUE'S LANE, not decoration. The cue is absolutely
       positioned 18px off the floor and stands ~34px tall, so anything under
       ~56px and the mockup grows straight through it — which it did, on both
       sections at once, the first time this was trimmed. */
    padding: 38px 14px 58px;
    display: flex; flex-direction: column; align-items: center;
  }
  .how-left, .g-left { padding: 0 8px; }
  .how-left, .g-left { flex: none; width: 100%; text-align: left; }
  .how-left h2, .g-left h2 { font-size: clamp(21px, 5.8vw, 28px); margin-bottom: 0; white-space: normal; }
  /* Eyebrow, heading, content — one rhythm, used by every section that has a
     header on a phone (see `.ways-head` and `.gl-head` below for the same
     numbers). It was 8px here and the label read as part of the heading. */
  .how-left .eyebrow, .g-left .eyebrow { margin-bottom: 14px; }
  /* Room under the heading before the caption starts, so the two do not read
     as one block of text. */
  .how-left h2, .g-left h2 { margin-bottom: 4px; }
  /* CENTRED, so leftover height splits above and below the mockup instead of
     pooling under it. The two demo sections have different amounts to spare —
     samples has a one-line heading, how-it-works has two plus a step card — and
     once the phone is capped on WIDTH rather than height, whatever is left over
     has to go somewhere. Anchored to the top it all collected beneath the
     "open the live page" link as an 85px hole; centred, it reads as margin. */
  .how-right, .g-right {
    flex: 0 0 auto; width: 100%;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
  }
  /* Sized from the WIDTH of the screen and nothing else. Height-derived sizing
     is what tied the mockup to the viewport and made it resize mid-scroll. */
  /* A quarter smaller than it was. At 74vw the mockup filled the section and
     left the heading and caption crowded against the top of the screen; the
     demo reads perfectly well at this size and the section can breathe. */
  .how-right .phone, .g-right .phone { --w-max: min(55vw, 218px); }
  /* ALL FIVE STEPS SHARE ONE CELL, so the block is always as tall as the
     LONGEST of them and the mockup underneath never moves.
     The previous fix reserved four lines, measured from the longest step at one
     width. That is a magic number and it only holds at that width: a narrower
     screen, a larger accessibility font, or a reworded step wraps to five and
     the phone hops again. Stacking every step in the same grid cell and hiding
     the inactive ones with `visibility` (which still takes up space, unlike
     `display: none`) makes the height the real maximum at whatever width the
     reader is on, with nothing to keep in sync. */
  /* 38px, not 8px. The heading and the step ran together: two lines of serif
     display type and then, almost touching it, "01 Start a project" in sans,
     which read as a third line of the heading rather than as the first step.
     The gap is what separates the section's claim from the walkthrough of it. */
  .how-steps { margin: 38px 0 0; width: 100%; display: grid; }
  .hstep { grid-area: 1 / 1; display: block; visibility: hidden; }
  .hstep.active { visibility: visible; }
  /* Laid out full-height for every step, active or not, or the hidden ones
     would measure zero and contribute nothing to the shared height. */
  .hstep .hbody { grid-template-rows: 1fr; opacity: 1; margin-top: 8px; }
  .hstep.active { opacity: 1; }

  /* NO CARD. A white box, a border and a shadow around two lines of caption
     put a heavy object between the section heading and the mockup, and three
     stacked containers in one screen is what made this section feel busy.
     The words carry themselves; the space they were sitting in goes back to
     the gaps between the things that matter. */
  .hstep, .hstep.active {
    padding: 0; background: none; border: 0; box-shadow: none; border-radius: 0;
  }
  .hstep h3 { font-size: 15.5px; }
  .hstep .hnum { font-size: 10.5px; width: 18px; }
  .hbody p { margin-left: 28px; }
  .hstep .hbody p { font-size: 12.5px; line-height: 1.4; }
  .how-swipe { margin-top: 10px; }
  .g-desc, .demo-list, .g-hint { display: none; }   /* the picker becomes arrows either side of the phone */
  .g-open { margin-top: 12px; }

  /* --- who it's for ---
     Sized up across the board. This section is nothing but words, so shrinking
     them to guarantee one screen was spending the only thing it has; with the
     bar out of the flow there is room to read it at a normal size. */
  .ways { padding: 26px 0 54px; align-items: center; }
  .ways-head { margin-bottom: 26px; }
  .ways-head .eyebrow { margin-bottom: 11px; }
  .ways-head h2 { font-size: clamp(28px, 7.6vw, 36px); }
  .ways-split { gap: 34px; }
  .way-title { font-size: clamp(32px, 9vw, 44px); }
  .way-body { font-size: 16px; line-height: 1.55; }
  .way-note { font-size: 14px; line-height: 1.5; margin-top: 18px; }

  /* --- features: ONE column of horizontal pills, and it may run past the fold.
     Two columns of squares was the wrong shape twice over: it forced 10px
     captions to fit, and the tiles still came out taller than they were wide.
     Sideways, each row gets the full width — icon left, name and sentence
     beside it — at a size worth reading.

     This is the one section allowed to be taller than a screen. Eight legible
     rows beat eight cramped ones that happen to fit; the section is a list, and
     a list is the one thing a phone scrolls happily. */
  .glance { padding: 26px 0 56px; display: block; }
  .gl-head { margin-bottom: 20px; }
  .gl-head .eyebrow { margin-bottom: 11px; }
  .gl-head h2 { font-size: clamp(26px, 7vw, 34px); margin-top: 6px; }
  .gl-grid { grid-template-columns: 1fr !important; gap: 10px; }
  .gl-tile {
    display: grid; grid-template-columns: 30px 1fr; align-items: center;
    column-gap: 14px; row-gap: 3px;
    aspect-ratio: auto; padding: 15px 16px; border-radius: 14px; text-align: left;
  }
  .gl-tile svg { width: 23px; height: 23px; margin: 0; grid-row: 1 / span 2; }
  .gl-tile h3 { font-size: 15.5px; line-height: 1.2; margin: 0; grid-column: 2; }
  .gl-tile p { font-size: 13px; line-height: 1.42; margin: 0; grid-column: 2; }
  /* In the text column, not auto-placed. Left to itself it landed in the 30px
     icon column and wrapped "COMING / SOON" into a two-line lozenge. */
  .gl-soon {
    grid-column: 2; justify-self: start; white-space: nowrap;
    font-size: 9px; padding: 3px 8px; margin-top: 7px;
  }
  /* The floor cue belongs to sections that ARE one screen. Here it would sit in
     the middle of the list. */
  .glance .scrollcue.floor { position: static; transform: none; margin: 22px auto 0; display: flex; }

  /* --- register interest --- */
  .closing { justify-content: center; }
  .cta { padding: 20px 22px 10px; }
  .cta-card h2 { font-size: clamp(26px, 7vw, 34px); }
  .cta .lede { font-size: 14.5px; }
  .foot { padding-bottom: 22px; }

  /* The floor cue is the one thing allowed to sit on the padding. */
  .scrollcue.floor { bottom: 18px; }
  .scrollcue span { font-size: 10.5px; letter-spacing: 0.15em; }

  /* ONE SCROLL CUE ON THE WHOLE PHONE, and it is the hero's.
     ------------------------------------------------------------------
     A cue at the foot of every section is a desktop idea: there, a section
     really is one screen and the arrow says "there is more". On a phone the
     page already scrolls like a page, so the arrow stops being information and
     becomes a repeated ornament, five times over, each one announcing what the
     reader is already doing. The hero's is different: it is the first screen,
     the one place where somebody genuinely might not know the page continues.

     Listed section by section rather than as a bare `.scrollcue.floor`, because
     `.glance .scrollcue.floor` above turns that one back into `display: flex`
     and would out-specify a shorter selector however late it came. */
  .real .scrollcue.inline,
  .how .scrollcue.floor,
  .gallery .scrollcue.floor,
  .ways .scrollcue.floor,
  .glance .scrollcue.floor { display: none; }

  /* THE FREED SPACE STAYS IN THE SECTION. The 58px above was the cue's lane,
     not slack; with the cue gone it becomes ordinary bottom padding rather than
     a reason to make the sections shorter. Sections keep their height and the
     content gets the air. */
}

/* On a short phone eight feature tiles cannot hold their captions and still fit
   one screen. The captions go rather than the tiles: "Powerful analytics" and
   "QR and NFC" say what they are on their own, whereas four rows of clipped
   sentences say nothing and a ninth scroll-within-a-scroll is worse than both. */
/* Short phones used to shrink the feature rows to keep eight of them on one
   screen. They no longer have to fit one screen, so there is nothing left to
   trade: the rows keep their size and the section scrolls. */

/* Landscape phones and very short windows: one screen genuinely cannot hold a
   phone mockup plus copy, so let those sections grow rather than crop. */
@media (max-width: 1080px) and (max-height: 620px) {
  .hero, .real, .how, .gallery, .ways, .glance, .closing { min-height: 0; }
  .how-vp, .gal-vp, .how-pin, .gal-pin { height: auto !important; }
  .how-grid, .gallery-grid { height: auto; }
}
/* ----------  MOBILE MENU  ----------
   The centre links vanish below 860px, and until now nothing replaced them: on a
   phone the page had no way to jump between sections at all.

   The panel is the nav bar continuing downward — same translucency, same blur,
   same hairline — rather than a full-screen takeover. It opens by growing from
   under the logo, so the thing you tapped stays where it was. */
.nav-burger {
  display: none; justify-self: end; grid-column: 3;
  width: 42px; height: 42px; padding: 0; border: 0; background: none;
  color: var(--text); cursor: pointer;
  flex-direction: column; align-items: center; justify-content: center; gap: 6px;
}
.nav-burger span {
  display: block; width: 20px; height: 1.6px; border-radius: 2px; background: currentColor;
  transition: transform .34s var(--ease), opacity .2s var(--ease);
}
.nav-burger[aria-expanded="true"] span:first-child { transform: translateY(3.8px) rotate(45deg); }
.nav-burger[aria-expanded="true"] span:last-child  { transform: translateY(-3.8px) rotate(-45deg); }

.nav-menu {
  position: absolute; top: 100%; left: 0; right: 0;
  display: flex; flex-direction: column;
  padding: 6px 26px 16px;
  background: rgba(5,5,6,0.86); backdrop-filter: blur(18px);
  border-bottom: 1px solid var(--hair);
  /* Grows downward from the bar. transform alone would slide a full-height panel
     up behind the logo; clipping the height is what makes it unfold. */
  transform-origin: top center;
  max-height: 0; opacity: 0; overflow: hidden;
  transition: max-height .42s var(--ease), opacity .28s var(--ease), padding .42s var(--ease);
}
.nav-menu[hidden] { display: none; }
.nav-menu.open { max-height: 60svh; opacity: 1; }
.nav-menu a {
  padding: 13px 2px; font-size: 15px; font-weight: 500; color: var(--muted-1);
  border-bottom: 1px solid rgba(255,255,255,0.07);
  transition: color .25s var(--ease);
}
.nav-menu a:last-child { border-bottom: 0; }
.nav-menu a.active { color: var(--text); font-weight: 700; }

@media (max-width: 1080px) {
  .wrap { padding: 0 22px; }
  .nav .flinks { display: none; }
  .nav-burger { display: flex; }
  .nav-spacer { display: none; }
  /* IN FLOW, not fixed, and as short as a tappable logo allows.
     A fixed bar costs its own height twice on a phone: once as the strip it
     occupies, and again as the padding every full-screen section had to add so
     its heading did not slide underneath. That is ~120px of a ~750px screen
     given to a bar nobody needed pinned — on the sections that are almost
     entirely a phone mockup, it was the difference between a usable demo and a
     cropped one. It scrolls away with the hero and the menu still works, since
     every link scrolls rather than navigating.

     Opaque near-black rather than a 72% wash: translucent, it picked up whatever
     was behind it and read as a smudge over the white sections. */
  .nav {
    position: relative;          /* the dropdown anchors to it */
    background: #08080a; backdrop-filter: none;
    border-bottom: 1px solid rgba(255,255,255,0.08);
    padding: 8px 20px;
  }
  .nav.scrolled { padding: 8px 20px; background: #08080a; }
  .nav .logo { font-size: 19px; }
  .nav-burger { width: 36px; height: 36px; gap: 5px; }
  .cta-form { flex-direction: column; }
  .pr-grid { grid-template-columns: 1fr; max-width: 420px; }
  /* ⚠️ THE FIVE-COLUMN RULE IS MORE SPECIFIC than the one above it, so it wins
     inside this query unless it is named here too — which is how a phone ends
     up scrolling sideways through five 200px cards. */
  .pr-grid.five { grid-template-columns: 1fr; max-width: 420px; }
  .pr-head-2 { margin-top: 40px; }
  .plan.featured { order: -1; }
  .foot { flex-direction: column; text-align: center; }
}
@media (max-width: 380px) {
  .phone.hero-phone { --w-max: 232px; }
  .how-right .phone, .g-right .phone { --w-max: 240px; }
}

/* ============================================================ LANGUAGE
   ENG / PL. Two homes, because the nav has two shapes.

   Desktop: the third grid column, top right, which is where a language control
   is looked for. It replaces `.nav-spacer` — that span existed only to fill
   column 3 so the centre links stayed centred, and a real control does that job
   better than an empty one.

   Mobile: the LAST row of the menu overlay, under the links. It is not a
   destination, so it should not sit among the ones that are; and putting it at
   the end means the thumb reaches it without passing anything it might hit by
   accident. */
.lang { display: inline-flex; align-items: center; gap: 2px; }
.lang-opt {
  appearance: none; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 11.5px; font-weight: 700; letter-spacing: .06em;
  color: var(--muted-2); padding: 5px 8px; border-radius: 6px;
  transition: color .25s var(--ease), background .25s var(--ease);
}
.lang-opt:hover { color: var(--text); }
.lang-opt.on { color: var(--text); background: rgba(255,255,255,0.11); }

.nav > .lang { grid-column: 3; justify-self: end; }
/* The spacer's job is done by the switch now. */
.nav > .nav-spacer { display: none; }

/* In the overlay it is a row like the links, but quieter and separated from
   them by the same hairline they use between each other. */
.nav-menu > .lang { padding: 13px 0 2px; gap: 4px; }
.nav-menu > .lang .lang-opt { padding: 6px 10px; font-size: 12.5px; }
.nav-menu a:last-of-type { border-bottom: 1px solid rgba(255,255,255,0.07); }

@media (max-width: 1080px) {
  /* One switch on screen at a time: the bar's copy goes, the menu's stays. */
  .nav > .lang { display: none; }
}
@media (min-width: 861px) {
  .nav-menu > .lang { display: none; }
}

/* ---- the NFC rings on step 4, pulsing ------------------------------------
   Over the capture, centred on the ring already printed in it. The static
   image keeps its own rings; these ride on top and expand, which is what the
   app does while it waits for a chip. Everything is a percentage of the phone
   screen, so it stays put from 280px wide to 420. */
.cscreen .nfc-pulse {
  position: absolute; left: 50%; top: 62.2%;
  width: 34%; aspect-ratio: 1; transform: translate(-50%, -50%);
  pointer-events: none;
}
.cscreen .nfc-pulse i {
  position: absolute; inset: 0; border-radius: 50%;
  border: 1.5px solid rgba(176, 141, 87, 0.75);
  opacity: 0; animation: nfcPulse 2.4s cubic-bezier(.22,.61,.36,1) infinite;
}
.cscreen .nfc-pulse i:nth-child(2) { animation-delay: .8s; }
.cscreen .nfc-pulse i:nth-child(3) { animation-delay: 1.6s; }
/* Only while the step is on screen: three rings expanding behind a step nobody
   is looking at is work the phone does not need to do. */
.cscreen:not(.show) .nfc-pulse i { animation-play-state: paused; }
@keyframes nfcPulse {
  0%   { transform: scale(0.55); opacity: 0; }
  18%  { opacity: 0.9; }
  100% { transform: scale(1.5); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .cscreen .nfc-pulse i { animation: none; opacity: 0; }
}
