/* whoislexi marketing design system.
   =====================================================================
   This file IS the tellmabel marketing stylesheet, verbatim, with one
   thing changed: the token block below. Same class names, same layout
   rules, same components, same section rhythm — so a fix to either site's
   structure is portable to the other by copy-paste, and the two read as
   built by the same hand.

   Colour is the only place Lexi is Lexi. The sibling's token names are
   ROLE names, not hues (--nude is the page ground, --cream a raised
   section, --white a card, --salmon the primary action), which is exactly
   what makes swapping them enough. To put this site on the warm/cream
   palette instead, replace this one block and nothing else.

   DO NOT graft other styling into this file. It had Lexi's old dusk-violet
   sheet appended to it once (2026-08-16) — a second :root block, oklch
   colours, frosted-glass cards — and the result was a page wearing two
   design systems at once. The old sheet is deleted; this is the only
   marketing stylesheet.

   Two forced differences from the sibling, both mechanical:
   - Fonts are SELF-HOSTED. This site's CSP is 'self' only, so the
     sibling's Google Fonts <link> is simply blocked. Hanken Grotesk is the
     same face they use; Instrument Serif stands in for Newsreader.
   - No inline styles anywhere, same CSP reason. An inline style here does
     not error — it silently does nothing.
   ===================================================================== */

/* ---------- self-hosted fonts (CSP 'self'; no Google Fonts CDN) ---------- */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/fonts/hanken-grotesk-var.woff2') format('woff2');
}
@font-face {
  font-family: 'Instrument Serif';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/instrument-serif-latin.woff2') format('woff2');
}
@font-face {
  font-family: 'Instrument Serif';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/instrument-serif-italic-latin.woff2') format('woff2');
}

/* ---------- the ONLY divergence from the sibling: the palette ----------
   AFTER HOURS — black, neon green, neon violet. (2026-08-17, replacing
   SIGNAL: charcoal / message-green / peach, plus the nude ground that was
   bolted onto it later the same day. Every --paper-* token from that pass is
   DELETED rather than renamed, so a stale reference fails loudly instead of
   quietly resolving to something plausible.)

   Two layers, same arrangement as before:

     1. the palette's own values, nothing derived and nothing renamed;
     2. the sheet's ROLE tokens (the sibling's names: --nude is the page
        ground, --white a panel, --salmon the primary action) re-pointed at
        layer 1.

   Layer 2 is why the port stays a port. Every rule below this block still
   speaks the sibling's vocabulary, so a structural fix on either site is
   still portable by copy-paste; only the mapping is ours.

   ONE GROUND, and this is the simplification the last three passes were
   circling. Signal was four warm greys with a light sheet added on top, so
   every rule had to know which surface it had landed on, and the file
   carried a whole INVERSE token block at the bottom to tell it. There is one
   ground family now — a near-black page and a single lifted panel — so that
   machinery is gone, not disabled: --nude/--cream/--deep all resolve to the
   page, --white is the panel, and no rule anywhere swaps ink. Structure
   comes from panels, hairlines and light, not from bands of grey a reader
   cannot quite see. (That was the actual complaint about Signal, twice.)

   TWO NEONS, AND THEY DO NOT SHARE WORK:

   * GREEN is what you can DO. The primary button, an outgoing message
     bubble, a checkmark, a focus ring, her name in the headline, and a link
     the instant you hover it. Nothing else. It is the brightest thing on the
     site by a distance (15:1 against the page), which is exactly why it has
     to stay scarce — two green things in a viewport, three at the outside.
   * VIOLET is what something IS. Eyebrow labels, the feature icons, the
     ambient light behind the hero and the phone. It never fills a button and
     it is never the answer to anything, so the two can share a screen
     without competing for the same job.

   The green is the colour the outgoing bubbles were always going to be —
   this is an SMS product and the FAQ says out loud that the bubbles are
   green — taken up to neon. The phone mock stays Messages in dark mode.

   LIGHT IS PART OF THE PALETTE, which reverses the previous spec outright
   (it banned gradients and glows in as many words). On a near-black page
   they are the only way to make a section read as lit rather than as empty.
   The limit is where they may fall: ambient wash BEHIND content (--glow-*)
   and the edge of the phone, never a fill, never a text treatment, never a
   third accent smuggled in as a gradient stop.

   Measured against #08080B (the page) and #16151C (a panel), so the next
   person does not have to:

     grounds     page->panel 1.10 · page->raised 1.27 · hairline 1.54 / 1.40
     text ramp   18.1 · 8.1 · 5.3      on a panel  16.4 · 7.3 · 4.8
     green       15.0 on the page, 13.6 on a panel, 13.8 for its own ink
     violet       7.4 on the page,  6.7 on a panel — it is never a fill, so
                  it needs no ink of its own
     error        7.2 on the page; the delete button is 5.2 for its label
     her avatar   a LIGHT disc (17.2 on the page). Her hair, sweater, skirt
                  and shoes are all black: on a dark disc she is a face with
                  no silhouette, which at 34px reads as a broken crop.
   ---------------------------------------------------------------------- */
:root {
  /* ---- 1. the palette ---- */
  /* DAYLIGHT (white + deep green), 2026-08-24. This replaced AFTER HOURS
     (near-black + neon green + neon violet), and the swap is a genuine
     inversion rather than a re-tint: on a black page an accent can be a neon,
     because the ground is doing the contrast work. On white it cannot. Every
     accent below is the same HUE as the one it replaces and roughly half its
     lightness, which is what it costs to keep the same jobs and still clear AA
     against a near-white ground. */
  --bg:#F7F7FA;             /* the page — near-white with a violet cast     */
  --surface:#FEFEFF;        /* a panel: the page's white, lifted            */
  --surface-hover:#EDECF3;  /* a chip, a spent button, the flash under a tap */
  --border:#DAD8E4;         /* every hairline                               */
  --text:#15141B;
  --text-muted:#56526A;
  /* 5.12 on the page and 5.43 on a PANEL. Unlike the dark scheme, the PAGE is
     the binding ground here rather than the panel: a panel is now LIGHTER than
     the page, so dark ink is always safer on it. Fine print inside the verify
     card is the loosest case, not the tightest. */
  --text-subtle:#6B6680;

  /* THE ACTION IS APPLE'S MESSAGE-BUBBLE GREEN, and it is here by owner
     decision (2026-08-24) over the contrast rule this file otherwise enforces.
     #34C759 is 2.08:1 on the page — below AA — and that was accepted knowingly:
     this product is a text thread, the green people associate with one is this
     exact value, and a green chosen to satisfy a ratio was reading as forest
     rather than as Messages. It is the same green the Signal scheme shipped.

     WHAT IS NOT WAIVED is the label on top of it. --green-ink is 6.09 on the
     fill, so the CTA still reads even though the fill does not clear AA against
     the page behind it. Those are two different measurements and only the first
     one was given up. See --green-deep for the third case, which could not be.

     Hover goes DOWN on a light page. #43D468 was the Signal value and it went
     up, which on white reads as the button fading out rather than lighting. */
  --green:#34C759;
  --green-hover:#2AB14C;
  --green-ink:#12351D;      /* 6.09 ON green — what sits on a green fill    */
  /* Stripe's checkout button, and the one place the waiver could not reach.
     Stripe puts WHITE text on it and gives us no say; white on #34C759 is
     2.22:1. We cannot inspect that page, cannot restyle its label, and it is
     the screen where money moves — so it takes the same hue deepened until
     white clears AA (4.82). Deliberately unused elsewhere on the site. */
  --green-deep:#0E8347;

  /* Violet keeps its one job (the eyebrow) and takes the same treatment:
     #C77DFF was 2.2:1 on white. */
  --violet:#6D28D9;

  --error:#C0392B;          /* 5.09 — error TEXT                            */
  --error-edge:#A93226;     /* the border of a field that is wrong          */
  --error-btn:#A93226;      /* the destructive button; its label is --surface */
  --warning:#8A6100;        /* amber taken to ink weight; #F0C36B was 1.6:1 */

  /* The two accents again as raw channels, for glows and edges. Keep them in
     step with --green / --violet above; they are the same two colours, not
     near-misses. */
  --green-rgb:52,199,89;
  --violet-rgb:109,40,217;
  /* The ink as channels, for every shadow in the sheet. Same argument as the
     two above: a light page's elevation is a tinted ink at a low alpha, and
     without one token for it each shadow becomes a fresh rgba(0,0,0,...) —
     which is what the dark scheme had, and why they all had to be found by
     hand to invert them. Pure black shadows read as dirt on white. */
  --ink-rgb:21,20,27;

  /* THE LOCK SCREEN'S MATERIALS SPLIT IN TWO, and getting this wrong is what
     broke the mock for a day. They were all one token off the ink, which was
     true while the ink was light: on a dark wallpaper a notification card, the
     dock keys, the monogram disc and iOS's own furniture are all white. Invert
     the ink and only HALF of that stays true, because the two halves are doing
     opposite jobs -- and the failure was silent, because every one of them
     still resolved to a colour.

     --scrim-* is MATERIAL: a frosted card and the dock keys, which are white
     glass over the wallpaper the way iOS draws them on a light one.
     The FURNITURE -- the status bar, the date, the padlock, the monogram --
     is ink, and takes --ink-rgb, because it is type rather than glass. */
  --scrim-rgb:255,255,255;

  /* Ambient light. Always behind content, never a fill. Over white these read
     as a faint sage / lilac wash rather than as a glow, which is the light
     equivalent of the same idea: the page is not a flat sheet. */
  --glow-violet:rgba(var(--violet-rgb),.10);
  --glow-violet-soft:rgba(var(--violet-rgb),.05);
  --glow-green:rgba(var(--green-rgb),.09);
  --glow-green-soft:rgba(var(--green-rgb),.05);
  --edge-violet:rgba(var(--violet-rgb),.20);
  --edge-green:rgba(var(--green-rgb),.28);

  /* Her contact photo, and the phone's frame. Both are hardware-ish objects
     rather than chrome, so both sit outside the ink ramp. The frame stays DARK
     and is the one token this change did not invert: she is photographed
     against a real phone, and a white phone on a white page is the hole the
     dark scheme had to solve with a violet rim. */
  --ava:#E8E4F2;
  --phone-frame:#221F2D;

  /* ---- 2. the sheet's roles, pointed at the palette ---- */

  /* grounds. One family: all three section roles ARE the page, and the only
     raised thing is a panel. The class names stay so that putting an
     alternation back is a one-line change here rather than a re-tagging of
     every section in every view. */
  --nude:var(--bg); --cream:var(--bg); --deep:var(--bg); --footer:var(--bg);
  --white:var(--surface);
  /* A field is a WELL, not a panel — and the direction survives the inversion
     unchanged, which is the nice part. On black it was the page recessed into a
     lighter panel; on white it is the page recessed into a whiter one. Either
     way the input is a shade OFF the card it sits in, and the border is not the
     only thing telling them apart. */
  --field:var(--bg);

  /* ink. Three steps. --muted2 is the decorative tail — labels, timestamps
     and fine print — and never a paragraph. */
  --maroon:var(--text); --ink:var(--text);
  --body:var(--text-muted); --body2:var(--text-muted); --muted:var(--text-muted);
  --muted2:var(--text-subtle);
  --on-deep:var(--text); --on-deep-sub:var(--text-muted); --on-deep-accent:var(--text-muted);
  --on-footer:var(--text-muted); --on-footer-strong:var(--text); --on-footer-link:var(--text-muted);

  /* violet's one job */
  --eyebrow:var(--violet);

  /* the action, and the ink that sits on it */
  --salmon:var(--green); --on-salmon:var(--green-ink);
  --salmon-soft:var(--surface-hover);

  /* one hairline value for everything: dividers, card edges, input borders */
  --card-border:var(--border); --line:var(--border); --hair:var(--border);

  /* green's other jobs, each named for the JOB rather than for the colour —
     so a rule asks for "the checkmark" and the palette decides what that is */
  --check:var(--green); --link-hover:var(--green); --focus:var(--green);
  --success-ink:var(--green);

  /* chips, the spent button, the tap flash: all neutral. A chip is a label,
     and a coloured label is a label competing with the CTA. */
  --chip:var(--surface-hover); --chip-ink:var(--text);
  --btn-off:var(--surface-hover); --btn-off-ink:var(--text-subtle);
  --press:var(--surface-hover);

  /* states. --on-danger INVERTED with the scheme: the destructive button's
     label was --text because the ink was light. Ink is dark now, so the label
     has to come off the panel instead or it is near-black on red. */
  --danger:var(--error); --danger-edge:var(--error-edge); --danger-soft:var(--error-edge);
  --danger-btn:var(--error-btn); --on-danger:var(--surface);
  --warn:var(--warning); --warn-ink:var(--warning);

  /* the phone mock: Messages in LIGHT mode, following the site. The thread is
     the phone's white, the incoming bubble is iOS's own grey, and the outgoing
     bubble is our green with white on it — which is also what a real SMS thread
     looks like, since green bubbles ARE the unencrypted ones. */
  --mock-ground:#FDFDFF;
  --mock-head:#F5F5F7; --mock-line:#E3E3E8;
  --mock-in:#E9E9EB; --mock-in-ink:var(--text);
  --mock-out:var(--green);
  /* WHITE, not --green-ink, and this is the mock copying iOS rather than
     obeying our palette — the same exemption the mock's radii already have. An
     Apple message bubble is green with white type on it. Ours is 2.22:1 there
     and so is Apple's; reproducing somebody else's interface faithfully means
     reproducing what it actually looks like, and a dark-green label would make
     the one component whose whole job is to be mistaken for Messages stop
     looking like Messages. The CTA is a different question and keeps its ink. */
  --mock-out-ink:#FDFDFF;
  --mock-tint:var(--text-subtle);
  /* Lock-screen materials, all off --scrim-rgb rather than the ink. See the
     note on that token: these are over a photo, not over our page. */
  --mock-glass:rgba(var(--scrim-rgb),.55);
  /* The grey disc iOS puts a contact's initial on when it has no photo for
     them. Two alphas rather than one: the real tile carries a top-to-bottom
     gradient, and flat reads as a coloured dot. */
  --mock-mono:rgba(var(--ink-rgb),.20);
  --mock-mono-deep:rgba(var(--ink-rgb),.13);
  --mock-key:rgba(var(--scrim-rgb),.45);
  --mock-chrome:rgba(var(--ink-rgb),.70);

  --serif:'Instrument Serif', Georgia, 'Times New Roman', serif;
  --sans:'Hanken Grotesk', -apple-system, system-ui, Segoe UI, Roboto, sans-serif;
  /* Elevation on white is the other half of the inversion. A .55 black shadow
     was legible against a near-black ground and reads as dirt on a white one,
     so the shadows go far lighter and tint toward the ink rather than to pure
     black. The border is still what actually draws the card. */
  --card-shadow:0 8px 24px rgba(var(--ink-rgb),.08);
  --phone-shadow:0 18px 50px rgba(var(--ink-rgb),.18);
}

* { box-sizing:border-box; }
html { scroll-behavior:smooth; }
body { margin:0; font-family:var(--sans); color:var(--body); background:var(--nude);
  font-size:16px; line-height:1.5; -webkit-font-smoothing:antialiased; }

/* ---- the lit page ----
   ONE light source for the whole top of the homepage, on the BODY rather than
   on any section, because the thing it has to cross is the gap between the
   <header> and <main> — they are siblings, so no element contains both and
   nothing inside <main> can reach up behind the nav.

   It used to be three washes on three elements (nav: none, hero: two,
   signup band: one), which is three light sources pretending to be one and
   reads as banding at every boundary between them.

   Where it STOPS is structural, not arithmetic: this paints the full height of
   the document and the first .mk-section paints its own opaque ground over the
   top of it. The cut is that section's top edge on every viewport, however
   tall the hero turns out to be. (Which is why .mk-section is opaque, and why
   the hero and the signup band must not be.)

   Sizes are in px, not percentages. A percentage in a background-image
   resolves against the positioning area, and for a background propagated from
   the body that area is the WHOLE DOCUMENT — so "40%" would mean something
   different on a short page than on a long one, and the light would move when
   the copy changed.

   Two pools that overlap between roughly 310px and 550px down, which is what
   makes them read as one field rather than as two lamps: violet high and
   right, behind the nav and her shoulder; green lower and left, carrying
   through the hero into the verify card. Keep the alphas where they are — at
   the --glow-* values these read as a room with a lamp in it, and at double
   they read as a gradient, which is a different and much worse thing. */
body.mk-lit {
  background:
    radial-gradient(1100px 760px at 74% 20px,  var(--glow-violet), transparent 72%),
    radial-gradient(1000px 660px at 26% 800px, var(--glow-green),  transparent 74%),
    var(--nude);
  background-repeat:no-repeat;
}
/* The nav sits INSIDE the lit zone now, so the rule under it would draw a line
   across a continuous field — the one thing this change exists to remove. The
   glow is the separation. Other pages keep the border (see the desktop block). */
@media (min-width:900px) { body.mk-lit .mk-header { border-bottom:0; } }

/* The snow (public/js/lexi-snow.js). EVERY declaration it needs lives here and
   nothing is set from JS, because an inline style is dropped by this CSP with
   no error at all — a particle effect that silently does not appear is the
   worst possible bug to own.

   fixed + inset:0 means the canvas never needs a computed height: it covers
   the viewport and the script draws in document coordinates offset by scrollY.
   z-index:-1 is what puts it OVER the page's ambient light and UNDER every
   block background, so flakes pass behind the copy, behind her, and behind the
   opaque verify card — which is why this is safe over a signup form.
   pointer-events:none so it can never take a tap meant for the phone field.

   translateZ(0) promotes it to its own compositor layer. On iOS a fixed layer
   that is NOT promoted is rasterised with the page during momentum scroll and
   stutters against it; this is the standard fix and costs one layer. It also
   makes the canvas a stacking context, which does not affect where it paints —
   z-index:-1 still places it in the negative-z step of the root. */
.lx-snow { position:fixed; inset:0; width:100%; height:100%; z-index:-1; pointer-events:none;
  transform:translateZ(0); }
/* The script bails before creating the element under reduced motion; this is
   the belt to that braces, and covers a preference changed after load. */
@media (prefers-reduced-motion:reduce) { .lx-snow { display:none; } }
svg { display:block; }
a { color:inherit; }

/* ---- shared bits ---- */
/* Peach's ONE job. Every eyebrow on the site is this rule and nothing else —
   no per-section override, no size drift — which is what lets a 12px uppercase
   label read as a label rather than as a coloured word. */
.mk-eyebrow { font:600 12px var(--sans); letter-spacing:.12em; color:var(--eyebrow); text-transform:uppercase; }
.mk-serif { font-family:var(--serif); color:var(--maroon); font-weight:600; }
.mk-ava { border-radius:50%; overflow:hidden; display:flex; align-items:center; justify-content:center; flex:none; }
.mk-ava svg { width:120%; height:120%; }
/* Both variants take the same light disc. The two class names survive because
   the sibling's markup uses them to say which ground the avatar landed on —
   there is one ground here, so they land in the same place. */
.mk-ava.on-nude, .mk-ava.on-white { background:var(--ava); }

/* Radii, one decision, applied everywhere: 10px on buttons and inputs, 16px on
   panels, pill on bubbles and chips. */
.mk-btn { display:flex; align-items:center; justify-content:center; gap:8px; text-decoration:none;
  border:0; cursor:pointer; font-family:var(--sans); font-weight:600; border-radius:10px; padding:16px 24px;
  font-size:17px; width:100%; }
/* The one green fill on the page, and the only element that is allowed to
   throw light. The glow is the button's edge as much as its shadow — on a
   near-black ground a flat neon rectangle reads as pasted on, and the spill
   is what makes it read as lit. It gets brighter on hover and that is the
   whole hover state; the fill barely moves. */
.mk-btn-primary { background:var(--salmon); color:var(--on-salmon); border:0;
  box-shadow:0 0 0 1px var(--edge-green), 0 10px 30px -10px rgba(var(--green-rgb),.55); }
.mk-btn-primary:hover { background:var(--green-hover);
  box-shadow:0 0 0 1px rgba(var(--green-rgb),.6), 0 14px 38px -10px rgba(var(--green-rgb),.8); }
.mk-btn-secondary { background:transparent; border:1px solid var(--border); color:var(--text); }
.mk-btn-secondary:hover { border-color:var(--green); }
.mk-btn:focus-visible { outline:3px solid var(--focus); outline-offset:2px; }
/* Busy state for a button that hands off to something slow we don't own — today only "go to Stripe",
   which is ~0.8s of our own API round trips before Stripe's page even starts loading. The spinner is a
   real child element, not a ::after, because .mk-btn is already a flex row with a gap: it lines up next
   to the label for free, and the label stays real text for screen readers. */
.mk-btn[aria-busy="true"] { opacity:.72; cursor:default; }
.mk-spin { width:17px; height:17px; flex:0 0 auto; border-radius:50%; border:2px solid currentColor;
  border-top-color:transparent; animation:mk-spin .7s linear infinite; }
@keyframes mk-spin { to { transform:rotate(360deg); } }
/* Reduced motion gets a slow sweep rather than nothing: a frozen ring next to "Taking you to Stripe…"
   reads as hung, which is the opposite of what this element is for. */
@media (prefers-reduced-motion: reduce) { .mk-spin { animation-duration:2.4s; } }

/* ---- header / footer ---- */
.mk-header { display:flex; align-items:center; justify-content:space-between; padding:16px 22px; }
.mk-brand { display:flex; align-items:center; gap:8px; text-decoration:none; }
.mk-brand .mk-ava { width:28px; height:28px; background:var(--ava); }
.mk-wordmark { font-family:var(--serif); font-size:20px; font-weight:600; color:var(--maroon); }
/* Compliance "pill": the Trust & compliance link + A2P badge grouped into one salient rounded chip in
   the top-right — the badge falls inline to the right of the text. Shared by the mobile header link
   (.mk-navlink) and the desktop nav item (.mk-nav-compliance). */
/* .mk-app-pill is in this group as of 2026-08-17 and was not before, which is
   the whole bug: it had exactly one declaration (margin-left:auto, further
   down) and none of the pill. So the app shell's header rendered it as bare
   16px body text with a 36px badge — and since .mk-nav-badge is display:block
   inside a non-flex <span>, the badge dropped onto its own line underneath.
   A class named "pill" that is not a pill reads as a styling bug rather than
   as a missing rule, which is why it survived this long. */
.mk-navlink, .mk-nav-compliance, .mk-app-pill { display:flex; flex-direction:row; align-items:center; gap:8px; white-space:nowrap;
  background:var(--white); border:1px solid var(--card-border); border-radius:999px; padding:4px 8px 4px 15px;
  box-shadow:0 2px 8px rgba(var(--ink-rgb),.08); color:var(--maroon); font:600 12.5px var(--sans); text-decoration:none; }
.mk-nav-badge { width:36px; height:36px; display:block; flex:none; }
.mk-nav-desktop { display:none; align-items:center; gap:28px; font:500 14.5px var(--sans); color:var(--text-subtle); }
.mk-nav-desktop a { text-decoration:none; }
.mk-nav-desktop a:hover { color:var(--text); }
/* .mk-nav-compliance shares the pill styling above (grouped with .mk-navlink). */
.mk-nav-desktop .mk-btn { width:auto; padding:11px 20px; font-size:15px; border-radius:10px; }

.mk-footer { background:var(--footer); padding:16px 26px 28px; display:flex; flex-direction:column; gap:12px; align-items:center; justify-content:center; }
.mk-footer .mk-wordmark { font-size:16px; color:var(--on-footer-strong); }
.mk-footer-login { font-family:var(--sans); font-size:13px; font-weight:600; color:var(--on-footer-strong); text-decoration:none; border:1px solid var(--border); border-radius:999px; padding:6px 18px; transition:background .15s ease, border-color .15s ease; }
.mk-footer-login:hover { background:transparent; border-color:var(--green); }
/* legal disclosure: text on the left, A2P 10DLC badge to the right of it (stacks on mobile) */
.mk-legal-row { display:flex; flex-direction:column; gap:16px; align-items:center; }
.mk-legal-text { flex:1; min-width:0; }
.mk-legal-badge { flex:none; }
.mk-legal-badge img { width:104px; height:104px; display:block; }
@media (min-width:760px){
  .mk-legal-row { flex-direction:row; align-items:center; gap:32px; }
  .mk-legal-badge img { width:124px; height:124px; }
}

/* simple content page (e.g. /compliance) */
.mk-page-title { font-family:var(--serif); font-weight:600; color:var(--maroon); font-size:34px; line-height:1.12; margin:6px 0 0; }
/* Centred and padded. It is a plain content column on the marketing pages,
   where .mk-section supplies the gutter — but /cancel and /error use it as the
   WHOLE page with no section around them, and there it was a 680px block
   pinned to the left edge of the viewport with no padding at all. */
.mk-page-body { max-width:680px; margin:0 auto; padding:0 22px; }
.mk-section .mk-page-body, .mk-app-main .mk-page-body { padding:0; }
.mk-page-body p { font-size:16px; line-height:1.6; color:var(--body); margin:14px 0 0; }
.mk-page-body h2 { font-family:var(--serif); color:var(--maroon); font-weight:600; font-size:22px; margin:30px 0 0; }
.mk-page-body ul, .mk-page-body ol { margin:12px 0 0; padding-left:22px; }
.mk-page-body li { font-size:16px; line-height:1.6; color:var(--body); margin:8px 0 0; }
.mk-page-body strong { color:var(--ink); font-weight:600; }
.mk-page-body em { font-style:italic; }
.mk-placeholder { display:inline-block; background:var(--nude); border:1px dashed var(--warn); color:var(--warn-ink);
  border-radius:7px; padding:4px 10px; font-size:12.5px; margin-top:8px; }
/* compliance disclosure (business identity + SMS program) preserved in the footer for 10DLC */
.mk-legal { background:var(--footer); color:var(--on-footer); padding:22px 26px 6px; font-size:11.5px; line-height:1.6; }
.mk-legal .mk-inner { max-width:1180px; }
.mk-legal p { margin:0 0 10px; }
.mk-legal strong { color:var(--on-footer-strong); font-weight:600; }
@media (min-width:900px){ .mk-legal { padding:34px 48px 6px; } }

/* ---- sections ---- */
/* Sections carry no ground of their own — all three roles resolve to --bg (see
   the token block). The classes stay so the markup keeps speaking the
   sibling's vocabulary, and so putting the alternation back is a one-line
   change to the tokens rather than a re-tagging of every section. Separation
   is the padding below plus a hairline between neighbours. */
/* OPAQUE, and that is load-bearing rather than tidy. The page's light lives on
   body.mk-lit and runs the full height of the document; what stops it is the
   first section painting its own ground over it. So the cut is exactly the top
   edge of the first .mk-section — no magic number, no dependence on how tall
   the hero happens to be on a given viewport. Make a section transparent and
   the glow leaks through it. */
.mk-section { padding:52px 26px; background:var(--nude); }
/* One ground means the hairline is the ONLY thing marking a change of subject,
   so it is not optional decoration — remove it and four sections become one
   scroll of paragraphs, which is exactly what happened the last time the
   grounds were collapsed. The signup band is a section for this purpose even
   though it isn't one structurally: it is the thing the first .mk-section
   follows. */
.mk-section + .mk-section,
.mk-signup-band + .mk-section { border-top:1px solid var(--card-border); }
.mk-nude { background:var(--nude); } .mk-cream { background:var(--cream); }
.mk-deep { background:var(--deep); color:var(--on-deep); text-align:center; }
.mk-inner { max-width:1180px; margin:0 auto; }

/* ---- hero ---- */
/* NO GROUND OF ITS OWN, and that is the point — see body.mk-lit. The light
   that used to live here belongs to the page, so that it can run unbroken from
   the top of the nav down behind the verify card. An opaque background on this
   section (including .mk-nude, which is why the markup no longer carries it)
   puts the seam straight back. */
.mk-hero { padding:22px 26px 30px; }
.mk-h1 { font-family:var(--serif); font-size:clamp(30px, 9.5vw, 40px); line-height:1.04; font-weight:600; color:var(--maroon); margin:0; }
/* Each sentence on its own line; nowrap keeps "Stop texting yourself." from breaking mid-line on mobile
   (the clamp above scales the size down so it always fits the viewport width). */
.mk-h1 span { display:block; white-space:nowrap; }
/* The homepage headline is four words, so it is set as ONE line at display
   size rather than as a stack of sentences — hence the opt-out from the rule
   above rather than an override of it. */
.mk-h1-solo { font-size:clamp(44px, 15vw, 66px); letter-spacing:-.015em; }
.mk-h1-solo span { display:inline; white-space:normal; }
/* Her name is the one word on the page that is allowed to glow. It is the
   headline, not a link and not an action, so it takes the accent without
   spending any of green's scarcity — nothing else in this viewport is green
   except the button, which is the point. Two-class selector so it beats
   `.mk-h1 span` above on specificity rather than on source order. */
.mk-h1 .mk-h1-name { color:var(--green); text-shadow:0 0 34px rgba(var(--green-rgb),.45); }
.mk-sub { font-size:16.5px; line-height:1.5; color:var(--body); margin:16px 0 0; max-width:520px; }
.mk-hero .mk-btn { margin-top:22px; }
.mk-trust { text-align:center; font:500 12.5px var(--sans); color:var(--muted); margin-top:12px; }
.mk-trust b { font-weight:600; color:var(--body); }
.mk-hero-media { display:flex; align-items:flex-end; justify-content:center; margin-top:28px; }
/* margin:10px 0 0 — the zero bottom is load-bearing. As a <p> it carried a ~13px UA bottom margin that
   nothing asked for, on top of whatever padding the section below it already had. */
.mk-caption { text-align:center; font:500 13.5px var(--sans); color:var(--muted); margin:10px 0 0; }
/* The homepage hero alone: its caption is answered by the signup band immediately below, so it closes
   tight instead of at the full section gap. Every other hero is followed by a change of subject and
   keeps the standard padding. */
.mk-hero-tight { padding-bottom:16px; }

.mk-mabel { flex:none; position:relative; z-index:2; margin-right:-10px; width:118px; }
/* She is dressed head to toe in black, so against a black page she is a face
   and a pair of socks. This is the light that gives her an edge back: a wash
   under her, inside her own stacking context (z-index:2 above already made
   one) so the -1 stays behind the image and in front of nothing else. It is
   not a drop shadow — it sits low and wide, like a floor. */
.mk-mabel::before { content:''; position:absolute; z-index:-1; left:-32%; right:-32%; bottom:-6%; height:74%;
  background:radial-gradient(60% 50% at 50% 78%, var(--glow-green), transparent 72%); pointer-events:none; }
.mk-mabel img { width:100%; height:auto; display:block; animation:mabelbob 4.5s ease-in-out infinite; }
@keyframes mabelbob { 0%,100%{transform:translateY(0) rotate(0)} 50%{transform:translateY(-8px) rotate(-1.6deg)} }
@media (prefers-reduced-motion:reduce){ .mk-mabel img { animation:none; } }

/* ---- iPhone hero video (spokesperson clip masked to the screen shape) ---- */
/* On a light page the frame was black hardware and that was the whole trick.
   On a black page a black phone is a hole, so the frame is graphite (1.24 from
   the page, 1.20 from its own screen — you can see the bezel) and the outline
   is drawn in violet light rather than left to the shadow. The violet is what
   stops the phone competing with the button: the one bright object in the hero
   is still green, and it is still the thing you press. */
.mk-phone { flex:none; position:relative; z-index:1; width:246px; background:var(--phone-frame); border-radius:34px;
  padding:8px; box-shadow:0 0 0 1px var(--edge-violet), 0 0 70px -16px rgba(var(--violet-rgb),.18), var(--phone-shadow); }
/* The halo dropped from .5 to .18 with the light scheme. On black it was the
   only thing lifting a black phone off a black page; on white the frame is its
   own outline and the same value read as a purple smudge around the device. */
/* The screen's own ground, so the thread has something to sit on before the
   animator paints its first bubble. */
.mk-screen { position:relative; background:var(--mock-ground); border-radius:27px; overflow:hidden; }
/* The source is 9:16 (404x720); width:100%/height:auto keeps its natural aspect, so no cropping. */
.mk-hero-video { display:block; width:100%; height:auto; }
/* CHROME OVER MEDIA DOES NOT FOLLOW THE PAGE (2026-08-24). This button, the
   home indicator below it and the lock screen's dim are drawn on top of a video
   or a photo, not on our ground. They stayed dark-on-light when the rest of the
   sheet inverted, because what they have to stay legible against is whatever
   frame is playing underneath them — which no palette controls. */
.mk-hero-sound { position:absolute; right:10px; bottom:16px; width:34px; height:34px; border-radius:50%;
  border:0; padding:0; background:rgba(13,13,15,.55); display:flex; align-items:center; justify-content:center; cursor:pointer; }
.mk-hero-sound .on { display:none; }
.mk-hero-sound.is-on .on { display:block; }
.mk-hero-sound.is-on .off { display:none; }
.mk-home-ind { position:absolute; left:50%; bottom:6px; transform:translateX(-50%);
  width:86px; height:4px; border-radius:2px; background:rgba(255,255,255,.65); }

/* ---- how it works ---- */
.mk-steps { display:flex; flex-direction:column; gap:22px; margin-top:18px; }
.mk-step { display:flex; gap:14px; align-items:flex-start; }
.mk-num { width:34px; height:34px; border-radius:50%; background:var(--chip); color:var(--chip-ink);
  font:600 15px var(--sans); display:flex; align-items:center; justify-content:center; flex:none; }
.mk-step-t { font:600 17px var(--sans); color:var(--ink); }
.mk-step-d { font-size:14.5px; line-height:1.45; color:var(--body); margin-top:3px; }
.mk-kicker { text-align:center; font-size:14px; color:var(--muted); line-height:1.5; max-width:42ch; margin:22px auto 0; }

/* ---- what people text (cards) ---- */
.mk-cards { display:grid; grid-template-columns:1fr 1fr; gap:12px; margin-top:18px; }
.mk-card { background:var(--white); border:1px solid var(--card-border); border-radius:16px; padding:16px; }
.mk-card-t { font:600 15px var(--sans); color:var(--ink); }
.mk-card-d { font-size:13px; color:var(--body2); margin-top:5px; line-height:1.4; }

/* ---- private by design ---- */
.mk-private .mk-eyebrow { text-align:center; } /* section rhythm: eyebrows center like mk-calls/mk-persist */
.mk-checks { display:flex; flex-direction:column; gap:14px; margin-top:18px; }
.mk-check { display:flex; gap:11px; align-items:flex-start; }
.mk-check svg { flex:none; margin-top:1px; }
.mk-check div { font-size:14.5px; color:var(--ink); line-height:1.4; }
.mk-check b { font-weight:600; }

/* ---- pricing: one card, with a billing-period switch above it ----
   The switch is a native radio group (.mk-pp-r), visually hidden but still focusable — display:none
   would drop it out of the tab order and take the keyboard with it. Its :checked state does all the
   work, so the whole control needs no JavaScript and no inline script under our CSP.
   The pairs below are POSITIONAL: radio N lights tab N and reveals panel N. One pair per plan in
   PLANS (stripePrices.ts) — a fourth plan needs a fourth pair added here. */
.mk-plan-picker { position:relative; max-width:430px; margin:0 auto; }
.mk-pp-r { position:absolute; width:1px; height:1px; opacity:0; margin:0; pointer-events:none; }
.mk-pp-tabs { display:grid; grid-template-columns:repeat(3,1fr); gap:4px; padding:4px; margin-bottom:18px;
  background:var(--nude); border:1px solid var(--line); border-radius:999px; }
.mk-pp-tab { padding:10px 6px; border-radius:999px; text-align:center; cursor:pointer; user-select:none;
  font:600 14.5px var(--sans); color:var(--muted); transition:background .15s ease, color .15s ease; }
.mk-pp-tab:hover { color:var(--text); }
.mk-pp-panel { display:none; }
/* checked: the tab reads as pressed and its panel is the one on screen */
.mk-pp-r:nth-of-type(1):checked ~ .mk-pp-tabs .mk-pp-tab:nth-child(1),
.mk-pp-r:nth-of-type(2):checked ~ .mk-pp-tabs .mk-pp-tab:nth-child(2),
.mk-pp-r:nth-of-type(3):checked ~ .mk-pp-tabs .mk-pp-tab:nth-child(3) {
  background:var(--white); color:var(--text); box-shadow:0 1px 2px rgba(var(--ink-rgb),.10); }
.mk-pp-r:nth-of-type(1):checked ~ .mk-pp-panels .mk-pp-panel:nth-child(1),
.mk-pp-r:nth-of-type(2):checked ~ .mk-pp-panels .mk-pp-panel:nth-child(2),
.mk-pp-r:nth-of-type(3):checked ~ .mk-pp-panels .mk-pp-panel:nth-child(3) { display:block; }
/* keyboard focus lands on the invisible radio, so the ring has to be drawn on its tab */
.mk-pp-r:nth-of-type(1):focus-visible ~ .mk-pp-tabs .mk-pp-tab:nth-child(1),
.mk-pp-r:nth-of-type(2):focus-visible ~ .mk-pp-tabs .mk-pp-tab:nth-child(2),
.mk-pp-r:nth-of-type(3):focus-visible ~ .mk-pp-tabs .mk-pp-tab:nth-child(3) {
  outline:2px solid var(--green); outline-offset:2px; }
/* The price card, built to the shape every pricing card is built to. That is
   the point of it and not a lack of imagination: a visitor down here is looking
   for the catch, and a layout they have read a hundred times is one they can
   check in seconds. Three bands separated by rules — what it is and what it
   costs, then what you get, then the button — with the padding on the BANDS
   rather than the card, so the rules run edge to edge. A rule that stops short
   of the border is a divider that looks like a mistake.

   Left-aligned throughout. It was centred, which is how a card with four
   ragged feature lines ends up reading as a poem instead of a spec. */
.mk-price { position:relative; background:var(--white); border:1px solid var(--card-border);
  border-radius:16px; overflow:hidden; text-align:left; box-shadow:var(--card-shadow); }
.mk-price-top { padding:26px 26px 22px; }
.mk-price-row { display:flex; align-items:center; justify-content:space-between; gap:12px; }
.mk-price-name { font-family:var(--serif); font-size:22px; line-height:1; color:var(--ink); }
/* The trial, where a pricing card puts it. Green as a LIGHT — a tinted ground
   and an edge — never as ink: green is the action colour and the only thing you
   press in this card is the button under it. */
.mk-price-badge { flex:none; white-space:nowrap; background:rgba(var(--green-rgb),.10);
  border:1px solid var(--edge-green); color:var(--text);
  font:600 11px var(--sans); letter-spacing:.05em; text-transform:uppercase;
  padding:5px 11px; border-radius:999px; }
/* The number is the thing being read, so it is set in the SANS and set large.
   It was 34px of Instrument Serif, which is a beautiful face and reads as an
   article's pull quote rather than a price. The dollar sign is aligned to the
   cap height rather than the baseline — flush with the digits it looks like a
   typo, and every card that gets this right raises it. */
.mk-price-amt { display:flex; align-items:baseline; gap:5px; margin-top:18px; }
.mk-price-amt .cur { align-self:flex-start; margin-top:9px; font:600 26px var(--sans); color:var(--ink); }
.mk-price-amt .n { font:600 56px/1 var(--sans); letter-spacing:-.035em; color:var(--ink); }
.mk-price-amt .per { font:500 15px var(--sans); color:var(--muted); }
/* How it bills, immediately under what it costs. This is the line the visitor
   came down here to find, and putting it anywhere else is how a page gets
   accused of hiding it. */
.mk-price-terms { font-size:13.5px; line-height:1.5; color:var(--muted); margin:11px 0 0; }
/* What you get. Its own band between two rules, so the list is visibly a
   different kind of statement from the price above it. */
.mk-feats { list-style:none; margin:0; padding:22px 26px; display:grid; gap:12px; text-align:left;
  border-top:1px solid var(--card-border); border-bottom:1px solid var(--card-border); }
.mk-feat { display:grid; grid-template-columns:20px 1fr; gap:10px; align-items:start;
  font-size:13.5px; line-height:1.45; color:var(--body2); }
/* the check only — the platform glyphs live inside the text and align themselves */
.mk-feat > svg { margin-top:1px; }
/* <strong> is the claim, <b> is emphasis inside a detail ("Messages", "CarPlay") — two elements rather
   than two classes, so neither rule can out-specify the other by accident. */
.mk-feat strong { font-weight:600; color:var(--maroon); }
.mk-feat b { font-weight:600; color:var(--body); }
/* A platform glyph and its name travel together: inline-flex so the pair wraps as one unit and the icon
   sits on the text's centre line rather than its baseline. */
.mk-brand { display:inline-flex; align-items:center; gap:4px; white-space:nowrap; }
.mk-brand + .mk-brand { margin-left:2px; }
.mk-brand svg { flex:none; }
.mk-price-foot { padding:22px 26px 24px; }
.mk-price .mk-btn { margin-top:0; padding:15px; font-size:16px; border-radius:10px; }
.mk-cta-note { display:block; font-size:12px; font-weight:500; opacity:.82; margin-top:2px; }
.mk-price-fine { text-align:center; font-size:12.5px; color:var(--muted2); margin:12px 0 0; }
/* Pricing header: the centred eyebrow + serif heading pair every other section opens with. It gets its
   own centring parent (like .mk-signup-head) rather than centring the section, because the cards below
   need the full inner width and mk-calls-h is capped at 18ch. */
.mk-price-head { display:flex; flex-direction:column; align-items:center; text-align:center;
  margin:0 auto 26px; }
.mk-price-head .mk-calls-h { margin-bottom:0; }

/* ---- faq ---- */
.mk-faq { display:flex; flex-direction:column; margin-top:14px; }
.mk-q { border-top:1px solid var(--hair); padding:15px 0; }
.mk-q:last-child { border-bottom:1px solid var(--hair); }
.mk-q-t { font:600 15px var(--sans); color:var(--ink); }
.mk-q-d { font-size:13.5px; color:var(--body2); margin-top:5px; line-height:1.45; }

/* ---- final cta ---- */
.mk-final-h { font-family:var(--serif); font-size:30px; font-weight:600; color:var(--on-deep); line-height:1.1; margin:0; }
.mk-final-sub { font-family:var(--serif); font-size:17px; font-weight:500; color:var(--on-deep-accent); margin:10px 0 0; }
.mk-deep .mk-btn { margin-top:22px; max-width:420px; margin-left:auto; margin-right:auto; }

/* ============ DESKTOP ============ */
@media (min-width:900px) {
  .mk-header { padding:20px 48px; border-bottom:1px solid var(--hair); max-width:1320px; margin:0 auto; }
  .mk-wordmark { font-size:23px; }
  .mk-brand .mk-ava { width:32px; height:32px; }
  .mk-navlink { display:none; }
  .mk-nav-desktop { display:flex; }

  .mk-section { padding:68px 48px; }
  .mk-hero { padding:56px 48px 50px; }
  .mk-hero-tight { padding-bottom:22px; }
  .mk-hero-grid { max-width:1320px; margin:0 auto; display:grid; grid-template-columns:1fr 540px; gap:30px; align-items:center; }
  .mk-h1 { font-size:clamp(42px, 4.6vw, 62px); line-height:1.04; }
  .mk-h1 span { white-space:normal; } /* wide desktop column fits each line; avoid overflow if it narrows */
  /* Restated inside the query, not just above it: .mk-h1-solo and .mk-h1 have
     the same specificity, so out here the media rule would simply win. */
  .mk-h1-solo { font-size:clamp(64px, 6.4vw, 96px); }
  .mk-sub { font-size:19px; margin-top:22px; }
  .mk-hero-cta-row { display:flex; align-items:center; gap:16px; margin-top:30px; }
  .mk-hero-cta-row .mk-btn { width:auto; padding:16px 28px; }
  .mk-hero-cta-row .mk-trust { margin:0; text-align:left; font-size:14px; }
  .mk-hero-media { margin-top:0; }
  .mk-mabel { width:210px; margin-right:-12px; }
  .mk-mabel img { animation-duration:5s; }
  /* Size and radius only — the shadow stack carries the violet edge and would
     be dropped by restating box-shadow here. */
  .mk-phone { width:300px; border-radius:40px; padding:9px; }
  .mk-screen { border-radius:32px; }

  .mk-steps { flex-direction:row; gap:26px; }
  .mk-step { flex-direction:column; }
  .mk-cards { grid-template-columns:repeat(4,1fr); }
  /* The grid lives on .mk-checks (the items' direct parent) — on .mk-checks-inner it had a single
     child, so every check stacked in the left 1fr track and the right column sat empty. */
  .mk-checks-inner { max-width:900px; margin:0 auto; }
  .mk-checks { display:grid; grid-template-columns:1fr 1fr; gap:16px 40px; }
  .mk-faq-inner { max-width:760px; margin:0 auto; }
  .mk-final-h { font-size:44px; }
  .mk-footer { padding:26px 48px; }
}

/* ===== App screens: signup / verify (cream, focused, mobile-canonical) ===== */
/* --nude, not --cream: these screens are a card on a ground, and --cream is
   now LIGHTER than a card (see the token block). A form card on the band
   would be darker than the page it sits on. */
body.mk-app { background:var(--nude); }
.mk-app-header { display:flex; align-items:center; gap:10px; padding:14px 22px 4px; max-width:520px; margin:0 auto; }
.mk-app-header .mk-wordmark { font-size:18px; }
.mk-back { display:inline-flex; align-items:center; color:var(--muted); text-decoration:none; }
.mk-app-main { max-width:520px; margin:0 auto; padding:12px 26px 44px; }

.mk-reassure { display:flex; align-items:center; gap:13px; margin:6px 0 6px; }
.mk-reassure .mk-ava { width:56px; height:56px; background:var(--ava); }
.mk-reassure-title { font-family:var(--serif); font-size:22px; color:var(--maroon); font-weight:600; line-height:1.15; }
.mk-reassure-sub { font-size:14px; color:var(--body); margin-top:2px; line-height:1.4; }

.mk-plan-note { font-size:13px; color:var(--body2); margin:0 0 16px; }
.mk-plan-note strong { color:var(--maroon); }

.mk-form-card { background:var(--white); border:1px solid var(--card-border); border-radius:16px; padding:22px; box-shadow:var(--card-shadow); }
.mk-field-label { font:600 13px var(--sans); color:var(--ink); }
.mk-input-group { display:flex; align-items:center; gap:8px; border:1px solid var(--line); border-radius:10px; padding:12px 15px; margin-top:8px; background:var(--field); }
/* The focus ring the phone field never had. The <input> inside carries
   border:0/outline:0 so it can share one box with the +1 prefix, which meant
   focus landed on an element with nothing to show for it — the one field the
   whole funnel depends on, with no visible focus state at all. The ring goes
   on the group, because the group is what looks like the input. */
.mk-input-group:focus-within { border-color:var(--focus); box-shadow:0 0 0 2px var(--focus); }
.mk-input-group.err { border-color:var(--danger-edge); }
.mk-input-prefix { font:600 15px var(--sans); color:var(--muted2); flex:none; }
.mk-input { border:0; outline:0; background:transparent; font:400 17px var(--sans); color:var(--ink); flex:1; min-width:0; letter-spacing:.02em; }
.mk-input::placeholder { color:var(--muted2); }
.mk-field-err { font:500 12.5px var(--sans); color:var(--danger); margin-top:7px; }

.mk-consent { display:flex; gap:11px; align-items:flex-start; margin-top:16px; }
.mk-consent input { flex:none; width:20px; height:20px; margin-top:2px; accent-color:var(--green); }
.mk-consent-copy { font-size:12px; line-height:1.5; color:var(--body2); }
/* Not peach. It sits INSIDE a sentence of consent copy, and a coloured word
   inside a sentence is exactly what this palette pass removes; the eyebrows
   are the only peach labels. Weight and case carry it instead. */
.mk-optional { color:var(--muted); font-weight:700; text-transform:uppercase; letter-spacing:.06em; font-size:10.5px; }

/* The second consent (AI-voice calls). Required like the first, but separated by
   a rule so it reads as its own distinct agreement rather than a second half of
   the box above it — that separation is the point, since a bundled consent to an
   artificial voice is not consent at all. The rule is what keeps two required
   boxes from looking like one long sentence with two checkboxes in it. Type size
   stays at .mk-consent-copy. */
.mk-consent-split { margin-top:14px; padding-top:14px; border-top:1px solid var(--hair); }

/* Click-wrap notice under the signup CTA. Replaces the old Terms checkbox: submitting the form IS the
   acceptance, so this has to sit with the button and stay legible — conspicuousness is what makes it
   enforceable. Same type size as .mk-consent-copy; do not shrink it below that. */
.mk-clickwrap { margin-top:14px; font-size:12px; line-height:1.5; color:var(--body2); text-align:center; }

.mk-form-card .mk-btn { margin-top:18px; padding:15px; border-radius:10px; }
/* A <details> used as a card needs its own handle; nothing else on the site
   uses one, so the rule lives with the card rather than in a section of its
   own. list-style:none + the ::marker reset is the pair that actually removes
   the disclosure triangle across browsers. */
.mk-form-card > summary { font:600 14px var(--sans); color:var(--ink); cursor:pointer;
  list-style:none; display:flex; align-items:center; gap:8px; }
.mk-form-card > summary::-webkit-details-marker { display:none; }
.mk-form-card > summary::before { content:'+'; color:var(--check); font-weight:700; font-size:16px; line-height:1; }
.mk-form-card[open] > summary::before { content:'\2013'; }
.mk-form-card > summary:focus-visible { outline:3px solid var(--focus); outline-offset:3px; border-radius:6px; }
.mk-btn:disabled, .mk-btn.is-disabled { background:var(--btn-off); color:var(--btn-off-ink); cursor:default; }

.mk-foot-note { display:flex; align-items:center; justify-content:center; gap:7px; margin-top:18px; font-size:12.5px; color:var(--muted); }
/* Compliance signal under the signup foot-note. One quiet grey line — the seal read as a fake award to
   consumers; this keeps the reassurance for the one carrier reviewer who looks, without the billboard. */
.mk-compliance-line { text-align:center; margin-top:16px; font-size:11px; color:var(--muted); }
.mk-compliance-line a { color:var(--muted); text-decoration:underline; text-decoration-color:var(--line); text-underline-offset:2px; }
.mk-alt { text-align:center; font-size:13px; color:var(--body); margin-top:16px; }
.mk-alt a { font-weight:600; }

/* Verification code: ONE wide field (see codeFieldHtml() in templates.ts for why it is not six boxes).
   padding-left equals letter-spacing on purpose: centred text includes the trailing letter-space in its
   width, which pushes the digits half a space left of true centre — an equal left pad cancels it exactly. */
.mk-code-field { margin-top:10px; }
.mk-code-input { box-sizing:border-box; display:block; width:100%; height:58px; padding:0 0 0 .3em;
  border:1px solid var(--line); border-radius:10px; background:var(--field); outline:0;
  text-align:center; font:600 29px var(--sans); color:var(--ink);
  letter-spacing:.3em; font-variant-numeric:tabular-nums; }
.mk-code-input::placeholder { color:var(--muted2); font-weight:600; }
.mk-code-input:focus { border-color:var(--focus); box-shadow:0 0 0 2px var(--focus); }
.mk-code-field.err .mk-code-input { border-color:var(--danger-edge); }
.mk-resend { text-align:center; font:500 13px var(--sans); color:var(--body); margin-top:14px; }
.mk-resend button { background:none; border:0; font-weight:600; cursor:pointer; padding:0; font-size:13px; font-family:var(--sans); }
.mk-resend button:disabled { color:var(--muted2); cursor:default; }
.mk-msg { text-align:center; font:500 13px var(--sans); color:var(--maroon); margin-top:14px; min-height:1.1em; }
.mk-msg.error { color:var(--danger); }

/* ===== Account / payment-success page ===== */
.mk-acc-hero { text-align:center; padding:14px 0 8px; }
/* Her face at the head of the auth screens. It used to be a bare letter in an
   unsized .mk-ava — a shrink-to-fit circle around one character, so a lopsided
   blob rather than an avatar, and once the disc went LIGHT (see --ava) the
   letter took body ink and was 2:1 on it. It is her photo now, the same mark
   the header wears, so the app screens and the marketing shell agree on what
   the brand looks like. */
.mk-acc-hero > .mk-ava { width:64px; height:64px; margin:0 auto; }
.mk-badge { width:64px; height:64px; border-radius:50%; background:var(--chip); color:var(--chip-ink); display:flex; align-items:center; justify-content:center; margin:6px auto 0; }
.mk-acc-h { font-family:var(--serif); font-size:34px; font-weight:600; color:var(--maroon); line-height:1.05; margin:16px 0 0; text-align:center; }
.mk-acc-h.mk-left { text-align:left; font-size:26px; margin:8px 0 6px; }
.mk-acc-sub { font-size:15.5px; color:var(--body); margin:8px 0 0; line-height:1.45; text-align:center; }
.mk-app-main .mk-form-card { margin-top:16px; }
.mk-card-h { font-family:var(--serif); font-size:18px; color:var(--maroon); font-weight:600; margin-bottom:4px; }
.mk-acc-note { font-size:14px; line-height:1.5; color:var(--body2); margin:8px 0 0; }
.mk-acc-note strong { color:var(--ink); font-weight:600; }

/* Account-page cards run tighter than the signup forms sharing .mk-form-card: the page is a utility
   screen, and the goal is info line + plan + contact photo all visible without scrolling. */
.mk-tight { padding:14px 18px; }
.mk-app-main .mk-form-card.mk-tight { margin-top:12px; }
.mk-acc-line { display:flex; flex-wrap:wrap; align-items:center; gap:4px 18px; font-size:14.5px; color:var(--body2); }
.mk-acc-line b { color:var(--ink); font-weight:600; }
.mk-acc-item { display:inline-flex; align-items:center; gap:6px; }
/* stroke="currentColor" in the markup, so the tick takes its colour from here — an SVG
   with a hardcoded hex would keep the sibling's green through any palette change. */
.mk-acc-check { flex:none; color:var(--check); }
.mk-acc-off { color:var(--muted2); font-weight:600; }

.mk-contact-hint { font-size:12.5px; color:var(--muted2); margin:2px 0 0; }
.mk-photo-grid { display:grid; grid-template-columns:repeat(2, 1fr); gap:10px; margin-top:10px; }
.mk-photo-card { position:relative; display:block; width:100%; padding:0; border:1px solid var(--hair); border-radius:16px; overflow:hidden; background:var(--white); cursor:pointer; text-align:left; font:inherit; transition:transform .12s ease; }
.mk-photo-card:active { transform:scale(.96); }
.mk-photo-card img { display:block; width:100%; height:auto; aspect-ratio:1/1; object-fit:cover; transition:opacity .12s ease; }
/* Busy = the beat between tap and the share sheet (the full-res fetch). The tapped tile dims and
   spins so the tap visibly landed; account.js clears it the moment the sheet is invoked. */
.mk-photo-card.is-busy img { opacity:.5; }
.mk-photo-card.is-busy::after { content:''; position:absolute; top:calc(50% - 27px); left:calc(50% - 11px); width:22px; height:22px; border:2.5px solid var(--check); border-top-color:transparent; border-radius:50%; animation:mk-spin .7s linear infinite; }
@keyframes mk-spin { to { transform:rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .mk-photo-card.is-busy::after { animation:none; border-top-color:var(--check); opacity:.6; } }
.mk-photo-label { display:flex; align-items:center; justify-content:space-between; gap:6px; padding:7px 10px; }
.mk-photo-label b { font:600 13px var(--sans); color:var(--ink); }
.mk-photo-hex { font-size:11px; color:var(--muted2); letter-spacing:.03em; }
/* The gallery's status line, and the ONLY thing it ever says: the full-res photo would not load.
   Reserved height, so a tile tapped on a bad connection does not jog the thirty cards below it up
   and down. Nothing is said on success -- the OS share sheet is its own receipt, and a page that
   also announced "shared!" underneath it would be narrating something the user is looking at. */
.mk-photo-msg { font-size:12.5px; line-height:1.45; color:var(--muted2); margin:10px 0 0; min-height:1.1em; }
.mk-photo-msg.error { color:var(--danger); }

.mk-acc-tz { margin-top:12px; }
.mk-tz-row { display:flex; align-items:center; gap:8px; margin-top:8px; }
.mk-tz-row .mk-btn { margin-top:0; }
.mk-select { flex:1; min-width:0; border:1px solid var(--line); border-radius:10px; padding:11px 12px; font:400 15px var(--sans); color:var(--ink); background:var(--field); }
.mk-select:focus { outline:0; border-color:var(--focus); box-shadow:0 0 0 2px var(--focus); }
.mk-btn-sm { width:auto; padding:11px 16px; font-size:14px; border-radius:10px; }
.mk-muted { display:block; font-size:12.5px; color:var(--muted); margin-top:8px; }
.mk-acc-link { margin-top:12px; }
.mk-acc-link a { font-weight:600; }

/* Free-tier "Your plan": three micro price cards, one tap each straight to Stripe (2026-08-09).
   Stays 3-across at EVERY width — the panel replaced a two-line paragraph and has to keep that
   footprint, and stacking on mobile would triple its height on the narrow screens that are most of
   this page's traffic. The sizes below are what fits three cards on a 320px viewport. */
.mk-miniplans { display:grid; grid-template-columns:repeat(3, 1fr); gap:8px; margin-top:8px; }
.mk-miniplan { display:flex; flex-direction:column; align-items:center; border:1px solid var(--line);
  border-radius:16px; padding:9px 5px 8px; text-align:center; }
.mk-miniplan-name { font:600 12.5px var(--sans); color:var(--muted); }
.mk-miniplan-price { font:600 20px var(--serif); color:var(--ink); line-height:1.15; white-space:nowrap; }
.mk-miniplan-per { font:600 11.5px var(--sans); color:var(--muted2); margin-left:1px; }
/* Not .mk-btn: that one is a 17px full-width block built for a page's single primary action, and three
   of them side by side would be a wall. Same focus ring, same busy dimming, a third of the weight. */
.mk-miniplan-btn { width:100%; margin-top:7px; border:0; border-radius:10px; padding:8px 4px;
  font:700 13px var(--sans); cursor:pointer; }
/* A price ladder, so only the middle rung — the one being recommended — is the
   action colour. The other two are neutral, told apart by their edge rather
   than by a second and third hue. */
.mk-miniplan-soft { background:transparent; border:1px solid var(--card-border); color:var(--text); }
.mk-miniplan-mid { background:var(--salmon); color:var(--on-salmon); }
.mk-miniplan-deep { background:var(--chip); color:var(--chip-ink); }
.mk-miniplan-btn:focus-visible { outline:3px solid var(--green); outline-offset:2px; }
.mk-miniplan-btn[disabled] { opacity:.6; cursor:default; }
/* Label for the row below it, so it reads left and quiet like a field label rather than centred like
   a footnote. Doubles as the error slot (account.js), which is why it keeps a reserved height — a line
   appearing and disappearing under the state note would jump the cards on every failed tap. */
.mk-miniplan-note { font-size:12.5px; line-height:1.45; color:var(--muted); margin:10px 0 0; min-height:1.1em; }
.mk-miniplan-note.error { color:var(--danger); }

/* Tap-to-copy starter prompts, woven between the gallery's rows (2026-08-10). `grid-column:1/-1` is the
   whole trick: they are children of .mk-photo-grid, so they span both columns and the two-up rhythm of
   the photos is never broken. Deliberately NOT a card — bordered rows in a section of their own read as
   a second gallery competing with the first. Type carries it instead: a small uppercase lead-in, then
   the sentence in the brand serif, italic and quoted, so it reads as something SAID rather than a
   control. Generous vertical padding is the separation the border used to give. */
.mk-idea { grid-column:1 / -1; display:block; width:100%; text-align:center; cursor:pointer;
  background:none; border:0; border-radius:10px; padding:14px 8px 16px; margin:0; font:inherit;
  color:var(--ink); transition:background .12s ease; }
.mk-idea-lead { display:block; font:700 11px var(--sans); letter-spacing:.1em; text-transform:uppercase;
  color:var(--eyebrow); margin-bottom:5px; }
.mk-idea-quote { font:italic 400 15.5px/1.5 var(--serif); color:var(--maroon); }
.mk-idea-icon { display:inline-block; vertical-align:-2px; margin-left:7px; color:var(--muted2); }
.mk-idea:focus-visible { outline:3px solid var(--green); outline-offset:2px; }
.mk-idea:active { background:var(--press); }
/* Copied: the line itself is the receipt. It reverts after a beat (account.js) so ten lines can't
   accumulate ten ticks and stop telling you which one you just took. */
.mk-idea.is-copied { background:var(--press); }
.mk-idea.is-copied .mk-idea-icon { color:var(--maroon); }
.mk-idea-i-done { display:none; }
.mk-idea.is-copied .mk-idea-i-copy { display:none; }
.mk-idea.is-copied .mk-idea-i-done { display:block; }
.mk-idea-msg { font-size:12.5px; line-height:1.45; color:var(--muted2); margin:10px 0 0; min-height:1.1em; }
.mk-idea-msg.error { color:var(--danger); }
.mk-acc-actions { text-align:center; margin-top:24px; }
.mk-textbtn { background:none; border:0; font:600 14px var(--sans); cursor:pointer; }
/* A one-button form that has to read as part of the sentence before it. The account
   page's toggles are form POSTs rather than fetch calls — no script, no CSRF surface
   beyond the origin check — and a <form> is a block, so without this the button drops
   to its own line and stops looking like the end of the sentence it belongs to. */
.mk-inline-form { display:inline; }

/* Self-serve deletion: the entry button sits under Log out, quieter than it; the confirm card it
   expands is the one red thing on the page (same red as .mk-msg.error). */
.mk-danger-entry { margin-top:14px; }
.mk-textbtn-danger { color:var(--danger); font-weight:500; font-size:13px; }
.mk-app-main .mk-form-card.mk-danger { border:1px solid var(--danger-soft); }
.mk-danger .mk-card-h, .mk-danger .mk-acc-note strong { color:var(--danger); }
.mk-btn-danger { background:var(--danger-btn); color:var(--on-danger); }
.mk-danger .mk-keep { margin-top:12px; }

/* /checkout plan radios (returning-user resubscribe) */
.mk-plan-radios { display:flex; flex-direction:column; gap:10px; margin-top:10px; }
.mk-plan-radio { display:grid; grid-template-columns:auto 1fr; column-gap:11px; row-gap:3px; align-items:center;
  border:1px solid var(--line); border-radius:16px; padding:14px; cursor:pointer; }
.mk-plan-radio:has(input:checked) { border-color:var(--salmon); box-shadow:0 0 0 1px var(--salmon); }
.mk-plan-radio input { grid-row:1 / span 2; accent-color:var(--green); }
.mk-plan-radio-main { display:flex; align-items:baseline; justify-content:space-between; gap:8px; }
.mk-plan-radio-name { font:600 15px var(--sans); color:var(--ink); }
.mk-plan-radio-price { font:600 15px var(--sans); color:var(--maroon); white-space:nowrap; }
.mk-plan-radio-sub { grid-column:2; color:var(--muted); font-size:12.5px; }

/* content pages (terms / privacy / sms-terms / about) rendered in the marketing chrome */
.mk-page-body h1 { font-family:var(--serif); color:var(--maroon); font-weight:600; font-size:30px; line-height:1.12; margin:0 0 6px; }
.mk-page-body address { font-style:normal; color:var(--body); line-height:1.6; margin-top:8px; }
.mk-page-body .business-info { list-style:none; padding-left:0; margin:12px 0 0; }
.mk-page-body .business-info li { margin:4px 0; }
.mk-page-body .muted { color:var(--muted); font-size:14px; }
.mk-page-body .draft { background:var(--nude); border:1px solid var(--warn); padding:8px 12px; border-radius:8px; font-size:13px; }

/* Hero mockup carousel — a cream "device showcase" that crossfades phone screens (driven by carousel.js).
   Each slide is centered; iframes hold the standalone mockups (scaled to fit) and the threadMock slide is
   the existing class-styled phone. The cream card matches the mockups' own background so edges blend. */
.mk-carousel { position:relative; flex:none; z-index:1; width:264px; height:496px; border-radius:34px; overflow:hidden; background:var(--nude); box-shadow:0 8px 20px rgba(var(--ink-rgb),.12); }
.mk-cslide { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; opacity:0; transition:opacity .7s ease; }
.mk-cslide.is-active { opacity:1; }
.mk-cslide iframe { flex:none; border:0; width:446px; height:904px; transform:scale(.55); transform-origin:center; pointer-events:none; background:transparent; }
.mk-cslide .mk-phone { transform:scale(.86); }
@media (prefers-reduced-motion:reduce){ .mk-cslide { transition:none; } }
@media (min-width:900px){
  .mk-carousel { width:300px; height:576px; }
  .mk-cslide iframe { transform:scale(.64); }
  .mk-cslide .mk-phone { transform:scale(.96); }
}

/* "Really important reminders" calling-feature section */
.mk-calls { text-align:center; display:flex; flex-direction:column; align-items:center; }
.mk-calls-h { font-family:var(--serif); color:var(--maroon); font-weight:600; font-size:30px; line-height:1.14; margin:8px 0 12px; max-width:18ch; text-wrap:balance; }
.mk-calls-sub { color:var(--body); font-size:16px; line-height:1.55; max-width:46ch; margin:0 0 30px; }
@media (min-width:900px){ .mk-calls-h { font-size:40px; } .mk-calls-sub { font-size:17px; } }

/* "She doesn't mind asking twice" — persistence section on the deep band, where the bright phone
   showcase pops. Mirrors mk-calls-h/sub but with light-on-deep text. */
.mk-persist { text-align:center; display:flex; flex-direction:column; align-items:center; }
.mk-persist-h { font-family:var(--serif); color:var(--on-deep); font-weight:600; font-size:30px; line-height:1.14; margin:8px 0 12px; max-width:18ch; text-wrap:balance; }
.mk-persist-sub { color:var(--on-deep-sub); font-size:16px; line-height:1.55; max-width:48ch; margin:0 0 30px; }
/* On the deep band the phone floats free — drop the nude "showcase card" bg + shadow (the tan box). */
.mk-persist .mk-carousel { background:transparent; box-shadow:none; }
@media (min-width:900px){ .mk-persist-h { font-size:40px; } .mk-persist-sub { font-size:17px; } }

/* ---- UGC testimonials ("Real people, real reminders") — swipeable vertical-video cards ----
   Swiping is native CSS scroll-snap (no JS); ugc.js only handles tap-to-play. Sits on the deep berry
   band (like the final CTA) so the video cards pop between the cream #calls and nude #pricing bands.
   The track bleeds to the viewport edge on mobile so the next card peeks = swipe affordance. */
.mk-ugc { text-align:center; }
.mk-ugc-h { font-family:var(--serif); color:var(--on-deep); font-weight:600; font-size:30px; line-height:1.14; margin:8px 0 4px; text-wrap:balance; }
.mk-ugc-track { display:flex; gap:14px; overflow-x:auto; scroll-snap-type:x mandatory; -webkit-overflow-scrolling:touch; padding:22px 26px 6px; margin:0 -26px; scrollbar-width:none; }
.mk-ugc-track::-webkit-scrollbar { display:none; }
.mk-ugc-card { flex:none; width:min(64vw, 240px); margin:0; scroll-snap-align:center; }
.mk-ugc-frame { position:relative; aspect-ratio:9/16; border-radius:16px; overflow:hidden; background:var(--footer); box-shadow:0 8px 20px rgba(var(--ink-rgb),.12); }
@supports not (aspect-ratio: 9/16) { .mk-ugc-frame::before { content:''; display:block; padding-top:177.78%; } }
.mk-ugc-frame iframe { position:absolute; inset:0; width:100%; height:100%; border:0; }
.mk-ugc-poster { position:absolute; inset:0; width:100%; border:0; padding:0; background:none; cursor:pointer; display:block; }
.mk-ugc-poster img { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
.mk-ugc-poster:focus-visible { outline:3px solid var(--green); outline-offset:2px; border-radius:16px; }
.mk-ugc-badge { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); width:56px; height:56px; border-radius:50%; background:var(--salmon); display:flex; align-items:center; justify-content:center; box-shadow:0 4px 12px rgba(0,0,0,.35); }
.mk-ugc-badge svg { margin-left:3px; }
.mk-ugc-name { font:600 14.5px var(--sans); color:var(--on-deep-sub); margin-top:10px; text-align:center; }
@media (min-width:900px){
  .mk-ugc-h { font-size:40px; }
  .mk-ugc-card { width:206px; }
  .mk-ugc-track { padding-left:48px; padding-right:48px; margin:0 -48px; }
}
/* Center the row when it fits without justify-content:center (which clips the left edge of an
   overflowing overflow-x scroller): auto margins on the end cards absorb spare space when the row
   fits and resolve to 0 when it overflows, keeping every card scrollable. */
.mk-ugc-card:first-child { margin-left:auto; }
.mk-ugc-card:last-child { margin-right:auto; }

/* Desktop prev/next arrows: a mouse can't swipe and the track's scrollbar is hidden. Markup ships
   [hidden]; ugc.js unhides them only while the track overflows, and :not([hidden]) keeps the UA's
   display:none authoritative so they never appear on phones (or when everything fits). */
.mk-ugc-wrap { position:relative; }
.mk-ugc-arrow { display:none; position:absolute; z-index:2; top:50%; transform:translateY(-50%); width:46px; height:46px; border-radius:50%; border:0; background:var(--salmon); cursor:pointer; align-items:center; justify-content:center; box-shadow:0 2px 8px rgba(var(--ink-rgb),.14); }
.mk-ugc-arrow-prev { left:-12px; }
.mk-ugc-arrow-next { right:-12px; }
.mk-ugc-arrow-prev svg { transform:scaleX(-1); }
.mk-ugc-arrow:disabled { opacity:.35; cursor:default; }
.mk-ugc-arrow:focus-visible { outline:3px solid var(--green); outline-offset:2px; }
@media (min-width:640px){ .mk-ugc-arrow:not([hidden]) { display:flex; } }

/* Tiny salient pills labelling the SMS + voice consent boxes on signup */
.mk-consent-tag { display:inline-block; background:var(--chip); color:var(--chip-ink); font:600 9.5px var(--sans); letter-spacing:.04em; text-transform:uppercase; padding:2px 8px; border-radius:100px; margin-right:6px; vertical-align:1.5px; white-space:nowrap; }

/* ---------------------------------------------------------------------------
   Capture landing page (LANDING_VARIANT=capture).

   One screen, one job: face → headline → phone field → button, nothing else clickable. Sized so the
   button is above the fold on a small phone (iPhone SE, 667px tall) without scrolling — that is the
   whole point of the page, so if you add anything here, re-check that.

   Reuses .mk-form-card / .mk-btn / .mk-foot-note from the app screens; only the page shell and the
   proof strip are new.
--------------------------------------------------------------------------- */
.cap-body { background:var(--nude); }   /* a card on a ground — see body.mk-app */
.cap-main { max-width:460px; margin:0 auto; padding:26px 24px 40px; }

/* Proof strip: the creator's face, first thing on the page, for message match with the ad. */
.cap-proof { display:flex; align-items:center; gap:13px; margin-bottom:20px; }
.cap-face {
  width:76px; height:76px; flex:none; border-radius:50%; object-fit:cover;
  background:var(--nude); box-shadow:0 2px 8px rgba(var(--ink-rgb),.10);
}
.cap-quote p {
  margin:0 0 3px; font-family:var(--serif); font-size:16px; line-height:1.35;
  color:var(--ink); font-style:italic;
}
.cap-quote span { font-size:12px; color:var(--muted); }

.cap-h1 {
  font-family:var(--serif); font-size:34px; line-height:1.12; font-weight:600;
  letter-spacing:-0.02em; color:var(--maroon); margin:0 0 20px;
}

/* The form card carries the page here, so let it sit tight under the headline. */
.cap-main .mk-form-card { margin-top:0; }
.cap-main .mk-msg { text-align:center; }

@media (max-width:380px) {
  .cap-main { padding:18px 18px 32px; }
  .cap-h1 { font-size:29px; margin-bottom:16px; }
  .cap-face { width:64px; height:64px; }
  .cap-quote p { font-size:15px; }
}
@media (min-width:520px) {
  .cap-main { padding-top:44px; }
  .cap-h1 { font-size:38px; }
}

/* ---- Capture page: example text thread -------------------------------------
   A phone Messages screen, not a decorative mockup: it teaches the interaction (plain English in,
   confirmation back, reminder arrives later) before anyone reads a word of copy.

   Fixed height with the thread bottom-anchored, so bubbles animating in push upward like a real
   conversation instead of resizing the page and shoving the CTA around mid-scroll. Nothing below this
   block may move when the animation runs — that's the constraint to preserve if you edit it.
--------------------------------------------------------------------------- */
/* NOT a card, despite sharing their shape: this frames a Messages thread, so
   it takes the mock ground. As --white it would have gone nude under the
   thread's own dark chrome. */
.cap-demo {
  background:var(--mock-ground); border:1px solid var(--card-border); border-radius:16px; overflow:hidden;
  box-shadow:var(--card-shadow); margin:0 0 18px;
}
.cap-demo-bar {
  display:flex; align-items:center; gap:8px; padding:9px 14px;
  background:var(--mock-head); border-bottom:1px solid var(--mock-line);
}
.cap-demo-back { color:var(--muted2); font-size:21px; line-height:1; flex:none; }
.cap-demo-id { display:flex; align-items:center; gap:8px; flex:1; justify-content:center; }
.cap-demo-id .mk-ava { width:26px; height:26px; flex:none; }
.cap-demo-num {
  display:flex; flex-direction:column; line-height:1.15;
  font-size:13px; font-weight:600; color:var(--ink);
}
.cap-demo-num small { font-size:10.5px; font-weight:400; color:var(--muted2); }
.cap-demo-spacer { width:16px; flex:none; }

/* Bottom-anchored viewport: the thread grows from the bottom edge, older lines slide off the top. */
.cap-demo-view { height:248px; overflow:hidden; display:flex; }
.cap-demo-thread {
  display:flex; flex-direction:column; justify-content:flex-end; gap:5px;
  width:100%; padding:12px 13px 14px;
}
.cap-demo-label { text-align:center; font-size:10.5px; color:var(--muted2); margin-bottom:2px; }
.cap-demo-label b { font-weight:600; color:var(--muted); }
.md-ts { text-align:center; font-size:10.5px; color:var(--muted2); margin:6px 0 2px; }
.md-ts b { font-weight:600; color:var(--muted); }

.md-row { display:flex; animation:md-pop .22s ease-out; }
.md-row--in { justify-content:flex-start; }
.md-row--out { justify-content:flex-end; }
.md-bubble {
  max-width:80%; padding:8px 12px; border-radius:17px; font-size:13.5px; line-height:1.35;
}
.md-bubble--in { background:var(--mock-in); color:var(--mock-in-ink); border-bottom-left-radius:5px; }
.md-bubble--out { background:var(--mock-out); color:var(--mock-out-ink); border-bottom-right-radius:5px; }

.md-typing {
  display:flex; gap:4px; align-items:center; padding:11px 13px;
  background:var(--mock-in); border-radius:17px; border-bottom-left-radius:5px;
}
.md-dot { width:6px; height:6px; border-radius:50%; background:var(--muted2); animation:md-blink 1.3s infinite; }
.md-dot:nth-child(2) { animation-delay:.18s; }
.md-dot:nth-child(3) { animation-delay:.36s; }

@keyframes md-pop { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:none; } }
@keyframes md-blink { 0%,60%,100% { opacity:.32; } 30% { opacity:1; } }

/* Reduced motion: mabel-demo.js renders the thread statically, so kill the entrance/typing animation
   and let the viewport grow to fit instead of clipping messages the visitor can no longer watch. */
@media (prefers-reduced-motion: reduce) {
  .md-row { animation:none; }
  .md-dot { animation:none; }
  .cap-demo-view { height:auto; }
}

@media (max-width:380px) {
  .cap-demo-view { height:224px; }
  .md-bubble { font-size:13px; }
}

/* ---------------------------------------------------------------------------
   Friction-killer homepage (LANDING_VARIANT=friction).

   The page IS the phone screen: one 412px column, no nav, no footer, nothing clickable but the field.
   Everything above the signup card exists to answer "what do I even say to her?" before the visitor
   has to read a word of explainer copy.

   The demo thread here is deliberately NOT brand-coloured like the capture page's — it uses the system
   font stack and iMessage's own green/grey so it reads as a screenshot of a real conversation rather
   than a branded illustration. That's the point of it; don't "fix" it to match the palette.
--------------------------------------------------------------------------- */
.fk-body { background:var(--nude); font-family:var(--sans); }
.fk-main { max-width:412px; margin:0 auto; padding-bottom:8px; }

.fk-header { display:flex; align-items:center; justify-content:space-between; padding:10px 20px 4px; }
/* Trust chip: wordmark-height pill with the A2P seal falling inline to its right. Shared — the
   friction-killer homepage header links it to #trust, the app header (/welcome) wears it as a badge. */
.mk-pill {
  display:flex; align-items:center; gap:7px; white-space:nowrap; text-decoration:none;
  background:var(--white); border:1px solid var(--card-border); border-radius:999px; padding:4px 8px 4px 13px;
  box-shadow:0 2px 8px rgba(var(--ink-rgb),.08); color:var(--maroon); font:600 12px var(--sans);
}
.mk-pill svg { display:block; flex:none; }

.fk-greet { display:flex; align-items:center; gap:13px; padding:16px 22px 0; }
.fk-greet-img { width:78px; height:78px; object-fit:contain; object-position:bottom; flex:none; }
.fk-greet-h { font-family:var(--serif); font-size:24px; font-weight:600; color:var(--maroon); line-height:1.1; margin:0; }
.fk-greet-sub { font-size:15px; color:var(--body); line-height:1.4; margin:3px 0 0; }

.fk-block { padding:16px 22px 0; }

/* ---- demo thread: iMessage chrome ---- */
.fk-thread { margin:0; font-family:-apple-system, 'SF Pro Text', 'Helvetica Neue', system-ui, sans-serif; }
.fk-thread-bar {
  display:flex; align-items:center; gap:6px; padding:8px 12px;
  background:var(--mock-head); border-bottom:1px solid var(--mock-line);
}
.fk-thread-back { flex:none; width:26px; display:flex; justify-content:center; color:var(--mock-tint); }
.fk-thread-id { flex:1; min-width:0; display:flex; flex-direction:column; align-items:center; gap:2px; }
.fk-thread-id .mk-ava { width:38px; height:38px; background:var(--ava); border:1px solid var(--mock-line); }
.fk-thread-name { display:flex; align-items:center; gap:3px; font-size:12px; font-weight:600; color:var(--text); }
.fk-thread-name span { color:var(--text-subtle); font-weight:400; }
.fk-thread-spacer { flex:none; width:26px; }

.fk-thread .cap-demo-view { height:300px; background:var(--mock-ground); }
.fk-thread .cap-demo-thread { gap:6px; padding:12px 12px 14px; }
.fk-thread .md-ts { font-size:11px; color:var(--text-subtle); }
.fk-thread .md-ts b { font-weight:600; color:var(--text-muted); }
.fk-thread .md-bubble {
  max-width:80%; padding:8px 13px 9px; border-radius:20px; font-size:15px; line-height:1.34;
  white-space:pre-line; word-wrap:break-word;
}
.fk-thread .md-bubble--out { background:var(--mock-out); color:var(--mock-out-ink); border-bottom-right-radius:7px; }
.fk-thread .md-bubble--in { background:var(--mock-in); color:var(--mock-in-ink); border-bottom-left-radius:7px; }
.fk-thread .md-typing { padding:11px 14px; background:var(--mock-in); border-radius:20px; border-bottom-left-radius:7px; }
.fk-thread .md-dot { background:var(--text-subtle); }

/* ---- inline signup card: field and button on one row, code revealed below ---- */
.fk-card { padding:18px; }
.fk-phone-row { display:flex; gap:8px; align-items:stretch; margin-top:8px; }
.fk-phone-row .mk-input-group { flex:1; min-width:0; margin-top:0; }
/* min-width holds the slot while the label cycles "Send code" -> "Resend 29s" -> "Resend", so the
   countdown never squeezes the number the visitor just typed. */
.fk-card .fk-phone-row .fk-send {
  width:auto; min-width:112px; white-space:nowrap; padding:0 15px; margin-top:0;
  border-radius:10px; font-size:15px;
}
/* Left-aligned here (not centred like the app screens) because it sits under a left-aligned field and
   reads as part of the form. Same type size as everywhere else — see .mk-clickwrap. */
.fk-card .mk-clickwrap { text-align:left; margin-bottom:0; }
.fk-sent {
  display:flex; align-items:center; gap:8px; margin-top:2px; padding-top:16px;
  border-top:1px solid var(--card-border); font:500 13.5px var(--sans); color:var(--body2);
}
.fk-sent svg { flex:none; }
.fk-sent b { color:var(--ink); font-weight:600; }
.fk-card #code-step .mk-field-label { margin-top:16px; }
.fk-card #code-step .mk-btn { margin-top:16px; }
/* The status line only exists once there is something to say. Elsewhere .mk-msg reserves a line so the
   layout doesn't jump; on this card that reserved line plus the two default <p> margins left ~45px of
   blank white under the click-wrap, which read as a mistake. Collapse it until it has text. */
.fk-card .mk-msg { margin:12px 0 0; }
/* Collapsed, not display:none — the element stays in the a11y tree so its aria-live announcement still
   fires the moment signup.js writes into it. */
.fk-card .mk-msg:empty { min-height:0; margin:0; }

/* ---- trust ---- */
.fk-trust { padding:18px 22px 26px; }
.fk-chips { display:flex; flex-wrap:wrap; gap:8px; justify-content:center; }
.fk-chip { background:var(--surface); color:var(--text); border-radius:999px; padding:7px 13px; font:600 13px var(--sans); }
.fk-priv {
  display:flex; align-items:center; justify-content:center; gap:6px; text-align:center;
  font:500 12.5px var(--sans); color:var(--muted); margin:13px 0 0;
}
.fk-priv svg { flex:none; }
.fk-company { display:flex; align-items:center; justify-content:center; gap:11px; margin-top:14px; text-decoration:none; }
.fk-company svg { display:block; flex:none; }
.fk-company span { font:500 12px var(--sans); color:var(--muted2); text-align:left; line-height:1.4; }

@media (max-width:380px) {
  .fk-greet, .fk-block { padding-left:16px; padding-right:16px; }
  .fk-trust { padding-left:16px; padding-right:16px; }
  .fk-greet-img { width:66px; height:66px; }
  .fk-greet-h { font-size:22px; }
  .fk-card { padding:16px; }
  .fk-thread .cap-demo-view { height:262px; }
  .fk-thread .md-bubble { font-size:14.5px; }
  .mk-code-input { height:52px; font-size:25px; }
}

/* ---------------------------------------------------------------------------
   Demo-led homepage hero + signup band (LANDING_VARIANT=homepage).

   The friction page's demo thread moved into the sales hero's right column and its signup card into a
   full-bleed band underneath. Nothing new is styled here — only the geometry that changes when those
   pieces stop being the whole page: the thread gains a phone bezel around it, and the card gains a strip
   of its own. These rules come after the .fk-* block on purpose; they share its specificity and win on
   source order.
--------------------------------------------------------------------------- */
/* The screen holds a Messages thread, not the spokesperson video, so the near-black bezel colour that
   .mk-screen paints to hide an unpainted video frame would show through as a gutter here. Still true
   now that the thread is dark too: --mock-ground is the page charcoal, #0d0d0f is phone hardware. */
.mk-screen-thread { background:var(--mock-ground); }
/* Taller than the friction page's bare thread (300px): inside a bezel, a squat screen reads as a widget
   rather than a phone. Still shorter on small screens than the 468px the design asks for, so the signup
   band below it starts within the first scroll rather than a full screen down. */
.fk-thread .mk-hero-thread-view { height:330px; }

/* The band. Full-bleed, the same nude as the hero, and since 2026-07-28 with NO hairline and no eyebrow
   above its heading: it is the hero's conclusion, not a new subject, and both were drawing a line under
   the caption where the reader should just keep going. Everything else on the page still announces itself
   with an eyebrow; this one section deliberately doesn't. */
/* Bottom padding back to 48px on 2026-08-01, reversing the 16px of 07-30: that tightening existed only
   because the band ENDED with the registration line and fine print belongs against the edge it is
   printed above. The line now prints inside the card, so the band ends with the trust bar again — the
   arrangement 48px was written for — and 16px under a row of trust claims crowds them into the brown
   example strip. */
/* Transparent, like the hero and for the same reason: the band is the hero's
   conclusion rather than a new subject, which is also why there is no hairline
   between them and no eyebrow above the heading. The light comes from
   body.mk-lit and simply keeps going. */
.mk-signup-band { padding:18px 22px 48px; }
.mk-signup-card { max-width:620px; margin:0 auto; }
/* The one panel the whole funnel depends on, so it is the one panel that is
   lit. A violet edge, not green: the button inside it is green, and an edge
   the same colour as the action turns the whole box into a button. */
.mk-signup-card .mk-form-card { box-shadow:0 0 0 1px var(--edge-violet), 0 24px 60px -20px rgba(var(--violet-rgb),.30), var(--card-shadow); }
/* Centres the heading the way .mk-calls does for the other sections — mk-calls-h is capped at 18ch, so
   without a centring parent it would sit left of a card that is centred. */
.mk-signup-head { display:flex; flex-direction:column; align-items:center; text-align:center;
  margin:0 auto 20px; }
.mk-signup-head .mk-calls-h { margin-bottom:0; }
/* The free-entry promise, directly under the heading that asks for a phone number. Muted and smaller
   than the heading on purpose: it answers the "what will this cost me" objection without competing
   with the ask itself. */
.mk-signup-sub { margin:10px 0 0; font-size:1.02rem; color:var(--muted); }

/* The registration line, printed at the foot of the signup card itself (moved inside it 2026-08-01; it
   closed the band under the trust bar from 07-30). Set as a stamp, not a sentence: bold, uppercase,
   widely tracked — the typography of something printed on a certificate, which is the whole job of the
   line. The hairline above it is .fk-sent's, the card's own way of marking off a footer band, so the
   stamp reads as printed on the card rather than as a fifth line of the form.

   ONE LINE AT EVERY WIDTH, phones included — which is what makes it read as fine print rather than as a
   short paragraph. The size is therefore not a clamp between two guesses but a fit: the string measures
   about 46em set like this, so dividing the available width by 50 sizes it to its container with ~8% in
   hand for a fallback font rendering wider than Hanken Grotesk, and 10px is the ceiling it stops at once
   there is room to spare.

   What it divides changed with the move. The line used to span the band, so the width was the viewport
   less the band's padding; inside the card it gets the CARD's inner width, which is the smaller of
   620px - 36px of card padding (584px) and the viewport less that padding plus the band's 22px sides
   (100vw - 80px). Below 380px the card's padding drops to 16px, which only makes the true width larger
   than this figure — the arithmetic stays conservative, never optimistic, because the failure mode of
   optimism is an overflowing nowrap.

   The cost of the move is real and was accepted: a 620px container instead of the viewport means the
   ceiling is reached later and phones set it smaller than before (~5.6px at 360px, against ~6.2px in the
   band). Genuinely small — but the alternative at 77 characters is two lines, and this line's whole job
   is to look like the small print on a licence. If legibility ever wins over the stamp, delete the
   nowrap and the font-size fit and let it set two lines at 10px; nothing else here depends on it.

   Neutral ink, NOT the peach of .mk-eyebrow, even though the two share a shape. The band deliberately
   opens with no eyebrow (see .mk-signup-band), and peach is how this page announces a new section — the
   last line of one is not that.

   Uppercased in CSS, sentence case in the HTML: screen readers pronounce some all-caps strings letter by
   letter, and the claim should be spoken as the sentence it is. */
.fk-card .mk-signup-official {
  margin:14px 0 0; padding-top:13px; border-top:1px solid var(--card-border);
  text-align:center; white-space:nowrap;
  font-family:var(--sans); font-weight:700;
  font-size:min(10px, calc(min(584px, 100vw - 80px) / 50));
  letter-spacing:.03em; line-height:1.5; text-transform:uppercase; color:var(--body2); }
/* The status line sits directly above the stamp and collapses when empty (.fk-card .mk-msg:empty), so
   the stamp's own top margin is the entire gap in the common case — but when signup.js writes an error
   into it, .mk-msg's 12px would otherwise stack against the stamp's 14px. Nothing to fix: they are
   separated by the hairline, and 26px above a rule reads as deliberate. Noted so it isn't "tidied". */

/* Trust bar: four claims under the signup card, one row on desktop, wrapping on narrow screens. Labels
   don't wrap internally (a two-line "Encrypted &amp; Secure" reads as two claims), so the row breaks
   between items instead. The column gap is fluid because the four labels total ~800px and the desktop
   content box is 804px at the 900px breakpoint — a fixed 40px gap fits by two pixels, i.e. it wraps the
   moment a fallback font renders a hair wider. Shrinking the gap on narrower viewports keeps the single
   row real rather than nominal, and gives phones tighter pairs.

   Phones want two rows of two, and the two long labels ("Encrypted & Secure" + "We Never Sell Your
   Information") are the pair that doesn't fit at 13px — they were taking a line each. The label size is
   therefore fluid below 449px as well, which is the only way to pair them without wrapping a label or
   cutting the copy. Both clamps hold their old value from 449px/550px up, so desktop is untouched.
   Everything here is a best effort at a pairing, never a guarantee: flex-wrap means the failure mode on
   an unusually narrow screen is the item dropping to its own line, exactly as it does today.

   No rule above it: the band already opens with one, and a second a few hundred pixels down bracketed
   the bar into a strip of its own rather than letting it settle under the card. The top margin carries
   the separation on its own, so it is wider than the 16px this used against the hero caption. */
.mk-trustbar { display:flex; flex-wrap:wrap; justify-content:center; align-items:center;
  gap:12px clamp(14px, 4vw, 40px);
  max-width:1000px; margin:26px auto 0; padding:0; list-style:none; }
/* Four badges, one size, one ink. The row used to carry a full-colour US flag
   at 21x15 among 20x20 line icons — the only saturated artwork on the page and
   the only item that was not a stroke glyph, which is why it read as pasted in
   rather than as one of a set. It is a line icon like the other three now, and
   the labels are label ink rather than body ink. */
.mk-tb-item { display:flex; align-items:center; gap:8px; min-height:20px; }
.mk-tb-item span { font-family:var(--sans); font-weight:600; font-size:clamp(10.5px, 2.9vw, 13px);
  color:var(--muted2); white-space:nowrap; }
.mk-tb-icon { flex:none; }
/* The flag. Not .mk-tb-icon, because that rule paints its glyph in label ink
   and this one carries its own colours — a national flag's are specified, not
   chosen. It is 21x15 among three 20x20 glyphs, so the corner radius and the
   hairline are what stop it reading as pasted-in artwork: they make it a small
   OBJECT sitting in the row rather than an image among icons. The hairline is
   doing real work on a near-black page, where the flag's white stripes run
   straight into the ground at the top and bottom edges. */
.mk-tb-flag { flex:none; border-radius:2px; box-shadow:0 0 0 1px var(--border); }

/* Rotating example texts: a full-bleed white strip below the signup band. All eight are in the DOM; one
   is visible at a time. They are absolutely positioned in a fixed-height box so a longer line can never
   resize the strip mid-fade — it is tall enough for the two rows the longest line takes on a phone, and
   that height never changes. (It was a white pill around each line until 2026-07-28; the pill resized
   with every sentence, which made the box the thing you watched.)

   Timing: every line runs the SAME keyframes for the WHOLE cycle and is offset by its own inline
   animation-delay (one --ex-step per position), so the percentages below are fractions of the cycle, not
   of a slot: within its 4.6s slot a line fades up over ~0.9s, holds ~2.8s, fades out over ~0.9s, and is
   invisible for the other seven slots. That ties them to the LIST LENGTH — eight items, 12.5% each — AND
   to the step: change --ex-step and the 2.45%/10.05% marks move with it, because they are a fraction of
   eight slots, not of one. A test pins the length; this comment is the only record of the rest. */
/* THE one section that is set apart, and the only one. It names --surface
   directly rather than going through a ground role, because every ground role
   now resolves to --bg and this is the deliberate exception. Set apart by
   being a different surface AND by having room around it — 4% of lightness on
   its own is not a section break, it is a seam. */
.mk-ex-band { background:var(--surface); border-top:1px solid var(--card-border);
  border-bottom:1px solid var(--card-border); padding:30px 22px; }
.mk-examples { --ex-step:4.6s; --ex-cycle:calc(var(--ex-step) * var(--ex-count));
  position:relative; min-height:46px; max-width:860px; margin:0 auto; padding:0; list-style:none;
  display:flex; align-items:center; justify-content:center; }
.mk-ex-line { position:absolute; left:0; right:0; text-align:center; opacity:0;
  animation:mk-ex-fade var(--ex-cycle) linear infinite; }
.mk-ex-line span { display:inline-block; max-width:94%; font:500 15px var(--sans); color:var(--on-deep);
  line-height:1.45; }
/* Hovering holds the line still, the same courtesy the hero carousel extends to anyone reading it. */
.mk-examples:hover .mk-ex-line { animation-play-state:paused; }
@keyframes mk-ex-fade { 0%, 12.5%, 100% { opacity:0; } 2.45%, 10.05% { opacity:1; } }
/* Reduced motion: no rotation at all. The first example stays up — an example that never changes is
   still an example, whereas a stack of eight would be a wall. */
@media (prefers-reduced-motion:reduce) {
  .mk-ex-line { animation:none; }
  .mk-ex-line:first-child { opacity:1; }
}

@media (min-width:900px) {
  .fk-thread .mk-hero-thread-view { height:468px; }
  .mk-signup-band { padding:24px 48px 18px; }
  /* One line fits at this width, so the strip only has to be one line tall. */
  .mk-ex-band { padding:38px 48px; }
  .mk-examples { min-height:26px; }
  .mk-ex-line span { font-size:16px; }
}
@media (max-width:380px) {
  .fk-thread .mk-hero-thread-view { height:300px; }
  .mk-signup-band { padding-left:16px; padding-right:16px; }
}

/* ---------------------------------------------------------------------------
   Welcome / consent gate (/welcome).

   The emptiest screen in the product on purpose: Mabel, one line of ask, the two consent strings, one
   button. Anything added here competes with the single tap the whole funnel depends on.
--------------------------------------------------------------------------- */
/* The pill itself comes from the shared rule up with the header components;
   this is only its placement in the app header. Keep it that way — the three
   pills drifting apart is what this file exists to prevent. */
.mk-app-pill { margin-left:auto; }
.wg-hero { padding-top:6px; }
/* Sized to the artwork's own aspect (440x1002 — she is a tall figure, not the
   squarer illustration this box was cut for), so object-fit:contain has
   nothing to letterbox. Get the box wrong here and contain silently shrinks
   her to fit the SHORT side, which reads as a rendering bug rather than a
   layout one. */
.wg-mabel { display:block; margin:0 auto; width:92px; height:210px; object-fit:contain; object-position:bottom; }
.wg-hero .mk-acc-h { margin-top:10px; font-size:30px; }
.wg-hero .mk-acc-sub { max-width:21em; margin-left:auto; margin-right:auto; }
/* The hook gets its own line and the number gets weight — for a 55+ visitor, "who is this texting
   me?" is the moment the welcome text either lands or gets reported as spam. */
.wg-hero .mk-acc-sub strong { display:block; color:var(--ink); font-weight:600; margin-bottom:2px; }
.wg-hero .mk-acc-sub b { color:var(--ink); font-weight:600; white-space:nowrap; }

@media (max-width:380px) {
  .wg-mabel { width:78px; height:178px; }
  .wg-hero .mk-acc-h { font-size:27px; }
}


/* =====================================================================
   ADDENDUM — the handful of components this site has and the sibling does
   not. Written in the sibling's idiom (their tokens, their spacing scale)
   so it reads as part of the same sheet rather than as a graft.

   Keep this short. Anything that grows here is a sign the two sites are
   drifting, and drift is the thing this file exists to prevent.
   ===================================================================== */

/* --- two rules the dark palette forces, and only the dark palette --- */

/* The seam above the legal block. The sibling gets this for free: a near-black
   footer under a light page IS the line. Invert it and the footer sits 1.05:1
   from the ground — the page just stops with no edge. Drawn, not borrowed. */
.mk-legal { border-top:1px solid var(--card-border); }

/* ---- "The most realistic AI companion" — the heading block and the mock ---- */
.lx-head { display:flex; flex-direction:column; align-items:center; text-align:center;
  max-width:660px; margin:0 auto 30px; }
.lx-head .mk-calls-h { margin-bottom:0; }
.lx-head .mk-calls-sub { margin:14px 0 0; }
/* ---- the lock screen ----
   Four panels of prose about how she arrives, replaced by the arrival. The
   brief was that it read as a photograph of somebody's phone, and v1 failed it
   by drawing only the notifications — the furniture around them is what makes a
   lock screen a lock screen, so all of it is here: island, status bar, padlock,
   date, clock, the two dock keys, the home indicator.

   SYSTEM FONT, not ours. Every other mock on the page already does this
   (.fk-thread) and here it matters more: on any Apple device this renders in
   the actual SF Pro the actual screen uses, and the illusion is free. Hanken
   Grotesk is a lovely face and it is instantly not-an-iPhone.

   The wallpaper is the section's own violet pool rather than a photo. A real
   lock screen has an image behind it and this one deliberately does not: the
   notifications are the content, and a face behind them competes with the four
   lines the section exists to show. */
.lx-lock { display:flex; flex-direction:column; align-items:center; }
/* Wider than every other phone on the page, because it is the whole section
   rather than a column of one. 274px of screen puts the notification type
   within sight of its true scale — at the hero's 246px the same mock is a
   phone with 22pt titles on it. */
.lk-phone { width:290px; }
/* A REAL IMAGE, not a CSS gradient, and the change that makes everything else
   here read as a photograph rather than a drawing. Two reasons, and the second
   is the one that matters: a CSS gradient is perfectly smooth, which no
   wallpaper on anybody's phone is, so the file carries grain; and frosted glass
   has nothing to frost over a flat field, so every translucent surface below was
   rendering as flat grey paint.

   THE SCRIM REVERSED WITH THE SCHEME (2026-08-24) and it is the same idea both
   times: iOS pushes the wallpaper away from the type so the type survives
   whatever picture you chose. It used to DIM, because the type was white. The
   type is near-black now, so it LIFTS. Getting this backwards does not error --
   it renders a screen you cannot read, which is what shipped for a day when the
   ink inverted and the wallpaper did not follow. The wallpaper is built from the
   palette by scripts/build-lock-wallpaper.py; rerun it if the palette moves. */
.lk-screen { height:594px; display:flex; flex-direction:column;
  font-family:-apple-system, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', system-ui, sans-serif;
  background:linear-gradient(rgba(var(--scrim-rgb),.34), rgba(var(--scrim-rgb),.16) 42%, rgba(var(--scrim-rgb),.44)),
             url('/img/lock-wallpaper.webp') center/cover no-repeat,
             var(--mock-ground); }

/* Status bar. On a lock screen the left half is empty — the clock is the big
   one below — so the row is the island and the three right-hand glyphs. */
.lk-status { position:relative; display:flex; align-items:center; justify-content:flex-end;
  padding:9px 17px 0; color:var(--mock-chrome); }
.lk-island { position:absolute; left:50%; top:7px; transform:translateX(-50%);
  width:78px; height:23px; border-radius:12px; background:var(--phone-frame); }
.lk-status-icons { display:flex; align-items:center; gap:5px; }
.lk-status-icons svg { display:block; }

.lk-top { padding:14px 18px 0; text-align:center; color:var(--text); }
.lk-glyph { display:block; color:var(--mock-chrome); margin-bottom:8px; }
.lk-day { font-size:15px; font-weight:600; color:var(--mock-chrome); letter-spacing:-.01em; }
/* Thin and huge, the way iOS sets it. 200 is real here in a way it would not be
   in our own face: the stack above resolves to SF Pro on Apple hardware, which
   has the weight — elsewhere it clamps to the lightest the fallback carries. */
.lk-clock { font-size:76px; font-weight:200; line-height:1.04; letter-spacing:-.03em; margin-top:-2px; }
/* The inline widget iOS hangs off the date line. Its glyph sits on the text's
   centre line rather than its baseline, which is the only fiddly part. */
.lk-inline { display:inline-flex; align-items:center; gap:3px; margin-left:7px; color:var(--mock-chrome); }
.lk-inline svg { display:block; }
/* Four widgets under the clock, because a lock screen with none is a drawing of
   one. Same glass as a notification at half strength: they sit BEHIND the
   notifications in importance and must not read as four more of them. */
/* FOUR SLOTS, not four widgets. A rectangular widget takes two of them and a
   circular one takes a single square slot, so the row is a mix of shapes — four
   equal tiles with a caption under each is an app grid, which is what the first
   version of this accidentally drew. */
.lk-widgets { list-style:none; margin:16px 0 0; padding:0 15px;
  display:grid; grid-template-columns:repeat(4, 1fr); gap:9px; align-items:center; }
.lk-w { color:var(--mock-chrome);
  -webkit-backdrop-filter:blur(18px) saturate(160%); backdrop-filter:blur(18px) saturate(160%); }
.lk-w svg { display:block; }
/* The rectangular one: two slots, a glyph and two lines of text, left-aligned.
   Its value is the loud line and the condition is the quiet one under it. */
.lk-w-rect { grid-column:span 2; height:54px; border-radius:14px; background:var(--mock-key);
  display:flex; align-items:center; gap:9px; padding:0 12px; }
.lk-w-rect span { display:flex; flex-direction:column; line-height:1.15; min-width:0; }
.lk-w-rect b { font-size:15px; font-weight:600; color:var(--text); letter-spacing:-.02em; }
.lk-w-rect span { font-size:10.5px; }
/* The circular ones: a round well with a gauge in it and NO caption. The label
   under a circular widget was the most obviously wrong thing in the row —
   iOS puts the value inside the ring or leaves it off entirely. */
.lk-w-circ { width:54px; height:54px; justify-self:center; border-radius:50%;
  background:var(--mock-key); display:flex; align-items:center; justify-content:center; }

/* THE STREAM. margin-top:auto pins the window to the bottom of the glass, above
   the dock; the track inside it holds four notifications twice over and steps
   up one card per beat, forever. At the halfway mark the track is showing the
   second copy, which is pixel-identical to the first, so the loop has nothing
   to reset and there is no seam to catch.

   --lk-pitch is one card plus one gap and it is the only number here: the step
   distance, the window height and the card height are all derived from it, so
   changing the type size means changing the pitch and nothing else. It is also
   why the cards are a FIXED height rather than hugging their text — a stack of
   uneven cards cannot be stepped by a single distance. */
.lk-notes-view { --lk-pitch:83px; margin:auto 0 0; padding:0 9px;
  height:calc(var(--lk-pitch) * 3 - 8px); overflow:hidden;
  /* The oldest card dissolves as it leaves rather than being guillotined by the
     window edge. Not a palette colour — a mask reads only the alpha channel, so
     these are the keywords rather than tokens. */
  -webkit-mask-image:linear-gradient(to bottom, transparent, black 16%);
  mask-image:linear-gradient(to bottom, transparent, black 16%); }
.lk-notes { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:8px;
  animation:lk-stream 13s ease-in-out infinite; }
/* Frosted, properly, and no border because iOS draws none. This is the one
   corner of the sheet allowed a backdrop blur and the exception is narrow on
   purpose (assets.test.js pins it to .lk-*): the blur is banned everywhere else
   as the grafted stylesheet's idiom for CARDS, and what it does here is not
   decorate a card — it is the material Apple draws a notification in, over a
   photograph. Without it a translucent tile on a wallpaper is a grey rectangle,
   which is exactly how the first two versions of this looked. */
/* The pitch minus the gap, and overflow:hidden as the backstop. Every line in
   the run is written to wrap to exactly two, but a fallback face on a non-Apple
   device sets wider — and a third line spilling out of a fixed-height card is a
   worse failure than a clipped one. */
.lk-note { flex:none; height:calc(var(--lk-pitch) - 8px); overflow:hidden;
  -webkit-backdrop-filter:blur(24px) saturate(180%); backdrop-filter:blur(24px) saturate(180%);
  display:flex; gap:9px; align-items:flex-start; padding:10px 12px 11px;
  border-radius:19px; background:var(--mock-glass); }
/* A CONTACT photo, round, with the Messages glyph badged onto it. This is the
   Communication Notification layout, and it is the tell: an app icon here would
   read as a service pushing at you, which is the thing she is not.

   The badge takes --mock-out, not --green. They resolve to the same colour and
   the distinction is the point: --mock-out is iOS's green, which this mock is
   copying, and --green is OUR action colour, which may only appear on something
   you press. The outgoing bubbles have made exactly this distinction since the
   scheme landed. */
.lk-note-ico { position:relative; flex:none; width:36px; height:36px; }
/* The no-photo monogram. 400 and roomy: iOS sets the initial light and large
   inside the disc, and a bold one turns the tile into a logo. */
.lk-mono { display:flex; align-items:center; justify-content:center; width:100%; height:100%;
  border-radius:50%; background:linear-gradient(var(--mock-mono), var(--mock-mono-deep));
  color:var(--text); font-size:17px; font-weight:400; line-height:1; }
.lk-badge { position:absolute; right:-1px; bottom:-1px; width:14px; height:14px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:var(--mock-out); color:var(--mock-out-ink); border:1.5px solid var(--mock-ground); }
.lk-badge svg { display:block; }
.lk-note-body { flex:1; min-width:0; }
.lk-note-head { display:flex; align-items:baseline; gap:8px; }
.lk-note-head b { flex:1; min-width:0; font-size:14px; font-weight:600; color:var(--text); letter-spacing:-.01em; }
.lk-note-when { flex:none; font-size:12px; font-weight:400; color:var(--text-subtle); }
/* Wraps rather than truncating. There are ~26 characters on a line in here and
   her texts are longer, so a one-line ellipsis cut every preview mid-sentence —
   and a notification you cannot read the end of is not one anyone reads. */
.lk-note-text { display:block; font-size:13.5px; line-height:1.33; color:var(--text); margin-top:1px; }

.lk-dock { display:flex; justify-content:space-between; padding:16px 30px 0; }
.lk-key { width:38px; height:38px; border-radius:50%; display:flex; align-items:center; justify-content:center;
  background:var(--mock-key); color:var(--text);
  -webkit-backdrop-filter:blur(18px) saturate(160%); backdrop-filter:blur(18px) saturate(160%); }
.lk-key svg { display:block; }
/* The home indicator is white in its base rule because its other home is on top
   of the hero VIDEO, where white is right and the page has no say. Here it is on
   a light wallpaper, so it takes the ink instead — the same split as the scrim
   materials above, and the same failure if it is missed: an invisible bar rather
   than a broken one. */
.lk-screen .mk-home-ind { bottom:9px; background:rgba(var(--ink-rgb),.30); }
.lx-lock .mk-caption { margin-top:16px; }

/* Four beats, each a quick slide and then a long hold — a phone shunts the
   stack up and stops, it does not drift. The last hold ends on the fourth
   pitch, which is where the second copy of the run begins, so the jump back to
   0% is invisible. Keep the step count, the run length and the doubling in
   step: three cards visible, four unique lines, eight in the DOM. */
@keyframes lk-stream {
  0%,  20% { transform:translateY(0); }
  25%, 45% { transform:translateY(calc(var(--lk-pitch) * -1)); }
  50%, 70% { transform:translateY(calc(var(--lk-pitch) * -2)); }
  75%, 95% { transform:translateY(calc(var(--lk-pitch) * -3)); }
  100%     { transform:translateY(calc(var(--lk-pitch) * -4)); }
}
/* Reduced motion parks the track: three notifications on screen and no motion.
   The information was always the stack, never the stepping. */
@media (prefers-reduced-motion:reduce){ .lk-notes { animation:none; } }
@media (max-width:420px){
  .lk-phone { width:250px; }
  .lk-screen { height:512px; }
  .lk-clock { font-size:64px; }
  .lk-day { font-size:13.5px; }
  .lk-widgets { margin-top:12px; gap:7px; padding:0 12px; }
  .lk-w-rect { height:48px; padding:0 10px; }
  .lk-w-circ { width:48px; height:48px; }
  .lk-note-text { font-size:12.5px; }
  .lk-note-head b { font-size:13px; }
  /* Smaller type, shorter cards — and the pitch has to follow or the track
     steps by the wrong distance and the stack drifts out of its window. */
  .lk-notes-view { --lk-pitch:79px; }
}

/* ---- "She actually looks at it" — the photo demo and its three lines ----
   The frame is the hero's, unchanged: same bezel, same bar, same animator. Only
   the screen is taller, because this thread has a portrait photo in it and a
   300px screen would have scrolled the picture off the top before her reply to
   it arrived — which is the one thing the section exists to show side by side.

   Stacked below 760px with the phone first: on a narrow screen the demo IS the
   argument and the three lines are the footnotes, so they read after it. */
.lx-see-grid { display:grid; gap:28px; justify-items:center; max-width:920px; margin:0 auto; }
.lx-see-list { list-style:none; margin:0; padding:0; max-width:440px;
  display:flex; flex-direction:column; gap:16px; }
/* The same check and the same shape as the price list, deliberately: a claim
   about what she can do looks the same everywhere on this page — including the
   glyph's ink, which is --check and NOT green. Green is the action colour and
   is only allowed on something you press; a row of green ticks at rest is the
   exact thing the palette test fails the build over. */
.lx-see-list li { display:flex; gap:11px; font-size:14.5px; line-height:1.55; color:var(--body); }
.lx-see-list svg { flex:none; margin-top:2px; color:var(--check); }
.lx-see-list strong { font-weight:600; color:var(--ink); }
.fk-thread .lx-see-view { height:404px; }
@media (min-width:760px){
  .lx-see-grid { grid-template-columns:auto 1fr; gap:44px; align-items:center; justify-items:start; }
}
@media (max-width:380px){ .fk-thread .lx-see-view { height:344px; } }

/* A photo bubble IS the picture — no padding and no ground behind it, since a
   tinted margin around an MMS is a thing Messages has never drawn. It keeps the
   tail radius, so it still reads as a message from that side rather than an
   illustration dropped into the thread.

   Capped well under a text bubble's 80%: this is a portrait photo, and at 80%
   of a phone screen it is the entire screen and the conversation around it
   stops existing. Both selectors are needed — .fk-thread .md-bubble sets the
   padding and the width it is overriding, and one class cannot outrank two. */
.md-bubble--photo, .fk-thread .md-bubble--photo {
  padding:0; background:none; overflow:hidden; max-width:60%; line-height:0;
}
.md-bubble--photo img { display:block; width:100%; height:auto; }

/* Section light. Every section is lit now, alternating, because one ground for
   the whole page ran four subjects together into a single scroll and a hairline
   was doing all the work of separating them.

   THE RULE, and it is not decoration: violet opens a section and green closes
   one. That is the same job the two hues have everywhere else — violet says
   what a thing IS, green says what you can DO — so a violet wash pools at a
   section's TOP edge and a green one at its BOTTOM. Alternating the hue then
   alternates the anchor for free, which is what makes the rhythm read as a
   rhythm rather than as four sections that happen to be tinted.

     why      violet, top      what she is
     photos   green,  bottom   what she can do
     faq      violet, top      what she is
     pricing  green,  bottom   what you can do

   TWO NUMBERS DECIDE WHETHER THIS IS VISIBLE AT ALL, and the first attempt got
   both wrong. The alpha: --glow-*-soft is 6-8%, which over a #08080B page moves
   the ground about four values and cannot be seen on anything but a good
   monitor in the dark. And the reach: those pools faded to `transparent 70%`,
   so the tint sat in a band at one edge and five-sixths of every section was
   still bare ground — a glow, not an alternating background. These are the full
   --glow-* tokens now, and they run to `transparent 100%` so the wash covers
   the whole section and merely fades ACROSS it.

   They stay on `background` over an opaque --nude, because a section that goes
   transparent leaks the page's own light through it — see the lit-zone test. */
.lx-why { background:radial-gradient(124% 100% at 50% 0%, var(--glow-violet), transparent 100%), var(--nude); }
.lx-see { background:radial-gradient(124% 100% at 50% 100%, var(--glow-green), transparent 100%), var(--nude); }
.lx-faq { background:radial-gradient(124% 100% at 50% 0%, var(--glow-violet), transparent 100%), var(--nude); }
.lx-pricing { background:radial-gradient(124% 100% at 50% 100%, var(--glow-green), transparent 100%), var(--nude); }
/* The price card is the page's last action, so it is the second and last
   thing allowed a green edge. Capped narrow: .mk-inner is 1180px and one plan
   stretched across it reads as a table with a row missing. */
.lx-pricing .mk-price { max-width:460px; margin:0 auto;
  box-shadow:0 0 0 1px var(--edge-green), 0 24px 60px -22px rgba(var(--green-rgb),.32), var(--card-shadow); }

/* Their trust-bar and checklist icons carry a hardcoded ink colour on the <svg>
   itself. That colour is half the palette, so ours inherit instead — same
   marks, one place to change them. (An inline style would be the obvious fix
   and would silently do nothing here; see the CSP note at the top.)

   The two marks part company here. A CHECKMARK is the one place small green
   earns its keep: it is the affirmative answer the line next to it gives, and
   at 20px it can carry the accent without becoming a block of colour. A TRUST
   BADGE is not an answer and not an action — it is a label, so it takes the
   label ink and gets out of the way. */
.mk-check svg, .mk-feat > svg { color:var(--check); }
.mk-tb-icon { color:var(--muted2); }
.fk-sent svg { color:var(--success-ink); }

/* One ground, so nothing below :root has to swap ink. What used to live here
   was the inverse of the token block — a list of every dark surface on a nude
   page, restoring nine roles apiece. Deleted with the nude ground on
   2026-08-17. If a second surface family ever comes back, it belongs here and
   nowhere else; scattering per-surface overrides through the sheet is how the
   two-ground version became unmaintainable in the first place. */


/* --- quiet links: green scarcity, decided in ONE place ---
   Green means "the action": the CTA fill, an outgoing bubble, a checkmark. It
   stopped meaning that when it was also every link, the phone number, the
   email address and the five-item footer row — a legal block set in 11.5px
   with six green links in it reads as an error state, not as navigation.
   Every link is therefore body ink with a hairline underline, and turns green
   only on HOVER, where exactly one of them is lit at a time and green is
   unambiguously "this is the thing you are about to do".
   One rule rather than ten because ten drifted: five different combinations of
   colour and underline had accumulated across the sheet, all meant to be the
   same link. */
.mk-page-body a, .mk-legal a, .mk-plan-note a, .mk-consent-copy a, .mk-clickwrap a,
.mk-alt a, .mk-acc-link a, .mk-textbtn, .mk-resend button, .mk-back {
  color:var(--body); text-decoration:underline; text-decoration-color:var(--line);
  text-underline-offset:2px; }
.mk-page-body a:hover, .mk-legal a:hover, .mk-plan-note a:hover, .mk-consent-copy a:hover,
.mk-clickwrap a:hover, .mk-alt a:hover, .mk-acc-link a:hover, .mk-textbtn:hover,
.mk-resend button:hover, .mk-back:hover {
  color:var(--link-hover); text-decoration-color:var(--link-hover); }
/* Two that opt out, both later in the cascade than the rule above on purpose:
   deleting your account stays the one red thing on the page, and a spent
   resend button stays visibly spent. */
.mk-textbtn-danger, .mk-textbtn-danger:hover { color:var(--danger); text-decoration-color:var(--danger); }
.mk-resend button:disabled, .mk-resend button:disabled:hover { color:var(--muted2); text-decoration-color:var(--line); }

/* Focused app screens (the consent gate, login, account). These used to take
   the raised ground, matching the sibling's app layout; they take the page
   ground now, because the raised ground is lighter than the cards they are
   made of. */
.mk-app-body { background:var(--nude); }
.mk-app-foot { display:flex; gap:18px; justify-content:center; padding:26px 20px 40px;
  font-size:13px; color:var(--muted); }
.mk-app-foot a { color:var(--muted); text-decoration:none; }
.mk-app-foot a:hover { color:var(--body); text-decoration:underline; }

/* The consent gate's card. Wider than a signup form on purpose: the consent
   string is long, and its legibility is the entire point of that screen. */
.sms-hero { max-width:520px; margin:0 auto; }

/* The long disclaimer. Quiet and dense on purpose — a completeness
   document, not something anyone reads top to bottom. Body ink even so:
   --muted2 is for labels and timestamps, and this is four sentences of
   prose that somebody may genuinely need to read. */
.mk-fine-print { max-width:70ch; margin:14px auto 0; font-size:11.5px; line-height:1.55;
  color:var(--muted); }

/* Busy state for the inline signup buttons. The sibling uses
   .mk-btn[aria-busy] with a real spinner child; this is the same idea for
   buttons whose label we swap instead. */
.mk-btn-busy { opacity:.72; cursor:progress; }

/* Once the code is sent, the phone row is history rather than an input. */
#phone-step.is-sent .fk-phone-row { opacity:.55; pointer-events:none; }

/* Her face in the thread header. The sibling's avatar is an inline SVG sized by
   .mk-ava svg; ours is a raster crop, so it needs the equivalent fit rule. */
.mk-ava img { width:100%; height:100%; object-fit:cover; object-position:50% 22%; }

/* The sibling sets --ex-count on the <ul> and animation-delay per <li>, both
   inline. This site's CSP forbids inline styles (they fail silently), so the
   count is a class and the stagger is nth-child. Same rules, same cycle — only
   the delivery differs. */
.mk-ex-8 { --ex-count:8; }
.mk-ex-8 .mk-ex-line:nth-child(1) { animation-delay:calc(var(--ex-step) * 0); }
.mk-ex-8 .mk-ex-line:nth-child(2) { animation-delay:calc(var(--ex-step) * 1); }
.mk-ex-8 .mk-ex-line:nth-child(3) { animation-delay:calc(var(--ex-step) * 2); }
.mk-ex-8 .mk-ex-line:nth-child(4) { animation-delay:calc(var(--ex-step) * 3); }
.mk-ex-8 .mk-ex-line:nth-child(5) { animation-delay:calc(var(--ex-step) * 4); }
.mk-ex-8 .mk-ex-line:nth-child(6) { animation-delay:calc(var(--ex-step) * 5); }
.mk-ex-8 .mk-ex-line:nth-child(7) { animation-delay:calc(var(--ex-step) * 6); }
.mk-ex-8 .mk-ex-line:nth-child(8) { animation-delay:calc(var(--ex-step) * 7); }
