/* ==========================================================================
   stellik — public marketing site.

   Deliberately separate from main.css. That stylesheet dresses a tablet held
   at arm's length: 64px tap targets, and a :root carrying whichever shop's
   brand colour is being served. This one dresses a phone in a hand and a
   laptop on a desk, in Stellik's own colours. Sharing the file would mean one
   of the two fighting the other's tokens.

   What the two DO share is vocabulary, so they read as one company: BEM
   naming, the same spacing series, tabular numerals on every figure, hairlines
   instead of shadows, uppercase letterspaced micro-labels, and one focus ring
   declared at zero specificity.
   ========================================================================== */

/* --- Faces ---------------------------------------------------------------
   Self-hosted and subset; see app/static/fonts/LICENSE.txt. Same-origin, so
   the app's `font-src 'self'` CSP needs no widening for them.

   Three faces only works if the roles never overlap, so the rule is absolute:
   Manrope sets display sizes, Inter sets reading sizes, Plex Mono sets
   anything that is a number, a code, or a label. There is no size at which
   two of them are both correct.
   ------------------------------------------------------------------------- */
@font-face {
  font-family: "Manrope";
  src: url("/static/fonts/manrope-var.woff2") format("woff2-variations");
  font-weight: 400 800;
  font-display: swap;
  font-style: normal;
}
@font-face {
  font-family: "InterVar";
  src: url("/static/fonts/inter-var.woff2") format("woff2-variations");
  font-weight: 400 700;
  font-display: swap;
  font-style: normal;
}
@font-face {
  font-family: "PlexMono";
  src: url("/static/fonts/plexmono-400.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
  font-style: normal;
}
@font-face {
  font-family: "PlexMono";
  src: url("/static/fonts/plexmono-500.woff2") format("woff2");
  font-weight: 500;
  font-display: swap;
  font-style: normal;
}

/* --- Tokens -------------------------------------------------------------- */
:root {
  /* Brand, sampled from the logo artwork.

     The governing fact about this palette: #00E8A4 is 1.61:1 on white. It can
     never be text, a link or an icon on a light surface -- only a fill, a rule
     or the mark itself. Emphasis on light surfaces is --teal-deep, which is
     5.13:1 on --base. Every pair below was computed, not eyeballed. */
  --teal:        #00E8A4;   /* on INK surfaces only. 11.40:1 on --ink */
  --teal-deep:   #007A56;   /* light-surface links and emphasis. 5.13:1 */
  --teal-wash:   #E8FBF3;
  --ink:         #131325;

  /* Warm family. Teal alone reads like a developer tool and the buyer sells
     food. Amber follows the same rule teal does -- a fill, never text on a
     light surface (2.25:1 on --base). Clay is the warm colour that IS safe as
     text on light. */
  --amber:       #FF8A3D;   /* CTA band and step markers. ink on it 7.80:1 */
  --amber-deep:  #E8752A;   /* hover for amber fills */
  --clay:        #C2410C;   /* warm text/emphasis on light. 4.96:1 on --base */
  --amber-ink:   #422814;   /* secondary text ON the amber band. 5.79:1 */
  --cream:       #FFF7ED;   /* warm section ground. ink on it 17.24:1 */

  /* Light surfaces */
  --base:        #FAFAF9;   /* page ground -- warm, not white */
  --card:        #FFFFFF;
  --line:        #E6E5E2;
  --line-strong: #D6D5D1;
  --muted:       #5C5B6B;   /* 6.36:1 on --base */

  /* Ink surfaces */
  --ink-text:    #FFFFFF;
  --ink-muted:   #A5A5BA;   /* 7.57:1 on --ink */
  --ink-line:    rgba(255, 255, 255, .14);
  --ink-raise:   #1C1C31;   /* one step up from --ink, for cards on ink */

  /* Borrowed unchanged from the app, so a VOID slip here is the same red the
     shop's printer actually puts on paper. */
  --void:        #A33A2B;

  /* Space -- the app's series (4/8/12/16/24/32/48), extended upward for the
     section rhythm a page of prose needs and an order screen does not. */
  --s-1: 4px;   --s-2: 8px;   --s-3: 12px;  --s-4: 16px;  --s-5: 24px;
  --s-6: 32px;  --s-7: 48px;  --s-8: 64px;  --s-9: 96px;  --s-10: 128px;

  --w-page:  1120px;
  --w-wide:  1280px;
  --w-prose: 680px;

  --radius:    8px;         /* same as the app. Not a 24px blob. */
  --radius-sm: 6px;
  --tap-min:   44px;        /* mouse and thumb, not tablet-at-arm's-length */

  --display: "Manrope", ui-sans-serif, system-ui, -apple-system, sans-serif;
  --body: "InterVar", ui-sans-serif, system-ui, -apple-system,
          "Segoe UI", Roboto, sans-serif;
  --mono: "PlexMono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Type. Two ramps: display sizes fluid, reading sizes fixed -- prose that
     grows with the viewport is harder to read, not easier. */
  --fs-display: clamp(38px, 5.2vw, 68px);
  --fs-h1:      clamp(30px, 3.4vw, 46px);
  --fs-h2:      clamp(23px, 2.2vw, 32px);
  --fs-h3:      19px;
  --fs-lead:    clamp(18px, 1.6vw, 21px);
  --fs-body:    17px;
  --fs-small:   14px;
  --fs-micro:   11px;

  --ease: cubic-bezier(.22, .61, .36, 1);

  /* Lets `block-size: 0` animate to `auto`, which is what makes the FAQ open
     at its real height instead of snapping. Ignored by browsers that do not
     support it, which fall back to the fade. */
  interpolate-size: allow-keywords;
}

/* --- Reset --------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--base);
  color: var(--ink);
  font-family: var(--body);
  font-size: var(--fs-body);
  line-height: 1.6;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Belt and braces against the one bug the app's own design pass kept
     finding: something one pixel too wide making the whole page swipe. */
  overflow-x: hidden;
}

img, svg { max-width: 100%; height: auto; display: block; }
a { color: inherit; }
button { font: inherit; color: inherit; }

/* `hidden` is only a `display: none` in the UA sheet, so any component that
   sets its own display -- .mbtn is inline-flex -- silently outranks it and the
   element stays on screen. This is the one place !important is the right
   answer: hidden must mean hidden, whatever the component wants. */
[hidden] { display: none !important; }

/* --- Typography ---------------------------------------------------------- */
h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--display);
  font-weight: 700;
  text-wrap: balance;
}
h1 { font-size: var(--fs-h1); line-height: 1.1;  letter-spacing: -.028em; }
h2 { font-size: var(--fs-h2); line-height: 1.2;  letter-spacing: -.02em; }
h3 { font-size: var(--fs-h3); line-height: 1.35; letter-spacing: -.01em; font-weight: 600; }

p { margin: 0; text-wrap: pretty; }

.display {
  font-family: var(--display);
  font-size: var(--fs-display);
  font-weight: 800;
  line-height: 1.03;
  letter-spacing: -.035em;
  margin: 0;
  text-wrap: balance;
}

.lead {
  font-size: var(--fs-lead);
  line-height: 1.5;
  color: var(--muted);
  max-width: 52ch;
}

.small  { font-size: var(--fs-small); line-height: 1.5; }
.muted  { color: var(--muted); }
.prose  { max-width: var(--w-prose); }
.prose p + p { margin-top: var(--s-4); }

/* Every figure on the site is tabular. The app does this everywhere and it is
   half of why columns of money look deliberate rather than accidental. */
.num, .receipt, .spec__value, .plan__monthly,
.matrix__price, .calc__value {
  font-variant-numeric: tabular-nums;
}

/* Micro-label. Same idiom as the app's .who__label, same 11px/0.12em. */
.eyebrow {
  display: block;
  font-family: var(--mono);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: var(--s-4);
}
.msection--ink .eyebrow { color: var(--teal); }

/* --- Layout -------------------------------------------------------------- */
.container {
  width: 100%;
  max-width: var(--w-page);
  margin-inline: auto;
  padding-inline: var(--s-5);
}
.container--wide  { max-width: var(--w-wide); }
.container--prose { max-width: var(--w-prose); }

.msection { padding-block: clamp(64px, 9vw, 128px); }

/* The header is sticky, so an anchor jump would otherwise land its target
   underneath it -- which reads as the link having gone to the wrong place.
   Applies to /how-it-works#hardware and /contact#who. */
[id] { scroll-margin-top: 88px; }
.msection--tight { padding-block: clamp(48px, 6vw, 80px); }

.msection--wash { background: var(--card); border-block: 1px solid var(--line); }

/* Warm ground. The page alternates on how warm the content is, not on a
   schedule -- an ink section is where the subject turns serious, a cream one
   is where it turns human. */
.msection--cream { background: var(--cream); border-block: 1px solid #F2E4D3; }

/* The closing call to action. A band of its own colour so it cannot be read as
   the footer's opening paragraph, which is exactly what an ink CTA above an
   ink footer became. */
/* Not a flat fill. Two warm lights over a gradient that deepens toward the
   bottom-right, plus a fine dot grid so the surface has texture at close
   range. The darkest stop is #D95E1A: ink measures 4.85:1 on it, and #C2410C
   -- the next step down -- fails at 3.53:1, so that is the floor. */
.msection--amber {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  color: var(--ink);
  background:
    radial-gradient(70% 90% at 12% 8%, #FFB37A 0%, transparent 58%),
    radial-gradient(60% 80% at 88% 96%, #D95E1A 0%, transparent 62%),
    linear-gradient(122deg, #FFA766 0%, var(--amber) 46%, #E2661F 100%);
}
.msection--amber::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: radial-gradient(rgba(19, 19, 37, .09) 1px, transparent 1px);
  background-size: 22px 22px;
  mask-image: linear-gradient(160deg, #000 0%, transparent 72%);
}
/* Secondary text on amber has to be much darker than --muted would be:
   #5C3A1E measured 4.31:1 and the eyebrow 3.51:1, both under the line. */
.msection--amber .lead,
.msection--amber .muted,
.msection--amber .eyebrow { color: var(--amber-ink); }

.msection--ink {
  background: var(--ink);
  color: var(--ink-text);
}
.msection--ink h1, .msection--ink h2, .msection--ink h3 { color: var(--ink-text); }
.msection--ink .lead, .msection--ink .muted { color: var(--ink-muted); }

.stack       > * + * { margin-top: var(--s-5); }
.stack--tight > * + * { margin-top: var(--s-3); }
.stack--loose > * + * { margin-top: var(--s-7); }

.section-head { max-width: 60ch; margin-bottom: var(--s-8); }
.section-head .lead { margin-top: var(--s-4); }

/* --- Buttons ------------------------------------------------------------- */
.mbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: var(--tap-min);
  padding: 0 var(--s-5);
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-family: var(--body);
  font-size: 16px;
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 120ms var(--ease), border-color 120ms var(--ease),
              color 120ms var(--ease);
}

.mbtn--primary {
  background: var(--ink);
  color: var(--ink-text);
}
.mbtn--primary:hover { background: #24243d; }

/* On an ink section the primary button inverts to the accent, which is the
   one place teal carries weight rather than decoration. */
.msection--ink .mbtn--primary,
.mbtn--accent {
  background: var(--teal);
  color: var(--ink);
}
.msection--ink .mbtn--primary:hover, .mbtn--accent:hover { background: #4ff0bf; }

.mbtn--ghost {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--ink);
}
.mbtn--ghost:hover { border-color: var(--ink); }
.msection--ink .mbtn--ghost {
  border-color: var(--ink-line);
  color: var(--ink-text);
}
.msection--ink .mbtn--ghost:hover { border-color: var(--ink-text); }

.mbtn--block { width: 100%; }

/* A button that cannot act yet has to look like it. The hero's Print starts
   disabled, because printing an empty ticket is not a thing. */
.mbtn:disabled { opacity: .4; cursor: not-allowed; }
.mbtn:disabled:hover { background: var(--ink); }
.msection--ink .mbtn--primary:disabled:hover { background: var(--teal); }

/* A plain text link that still reads as an action. */
.arrowlink {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-weight: 600;
  color: var(--teal-deep);
  text-decoration: none;
  min-height: var(--tap-min);
}
.arrowlink:hover { text-decoration: underline; text-underline-offset: 3px; }
.msection--ink .arrowlink { color: var(--teal); }
.arrowlink__chev { transition: transform 120ms var(--ease); }
.arrowlink:hover .arrowlink__chev { transform: translateX(3px); }

/* --- Header -------------------------------------------------------------- */
.mnav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--base) 88%, transparent);
  backdrop-filter: saturate(1.4) blur(10px);
  border-bottom: 1px solid var(--line);
}
.mnav__inner {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s-5);
  min-height: 68px;
}
.mnav__brand {
  display: flex;
  align-items: center;
  min-height: var(--tap-min);   /* the mark is 30px; the target must not be */
  text-decoration: none;
  color: var(--ink);
  margin-right: auto;
}
.mnav__brand svg, .mnav__brand img { height: 30px; width: auto; }

.mnav__links { display: flex; align-items: center; gap: var(--s-2); }

.mnav__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding-inline: var(--s-3);
  border-radius: var(--radius-sm);
  font-size: var(--fs-small);
  font-weight: 500;
  color: var(--muted);
  text-decoration: none;
  transition: color 120ms var(--ease), background-color 120ms var(--ease);
}
.mnav__link:hover { color: var(--ink); background: var(--card); }
.mnav__link[aria-current="page"] { color: var(--ink); font-weight: 600; }

/* Mobile menu with no JavaScript. The app does the same trick for its cart
   sheet -- state in a real form control rather than a script.

   Clipped rather than display:none, so the control keeps its place in the tab
   order. A menu that only a pointer can open is a menu half the visitors
   cannot reach. */
.mnav__toggle-state {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.mnav__toggle { display: none; }
.mnav__toggle-state:focus-visible ~ .mnav__toggle {
  outline: 2px solid var(--teal-deep);
  outline-offset: 2px;
}

/* --- Footer -------------------------------------------------------------- */
.mfoot {
  background: var(--ink);
  color: var(--ink-muted);
  padding-block: var(--s-9) var(--s-7);
}
.mfoot__grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: var(--s-8);
  padding-bottom: var(--s-8);
  border-bottom: 1px solid var(--ink-line);
}
/* The lockup is an <img>, not inline SVG -- targeting `svg` here left it at
   its natural 334px and the footer brand rendered enormous. */
.mfoot__brand img { height: 34px; width: auto; }
.mfoot h4 {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-text);
  margin-bottom: var(--s-4);
}
.mfoot__list { list-style: none; margin: 0; padding: 0; }
.mfoot__list li + li { margin-top: var(--s-1); }
.mfoot__list a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);   /* a footer link is still a link on a phone */
  color: var(--ink-muted);
  text-decoration: none;
  font-size: var(--fs-small);
}
.mfoot__list a:hover { color: var(--ink-text); }
.mfoot__base {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  justify-content: space-between;
  padding-top: var(--s-6);
  font-size: 13px;
}

/* --- Claim: a bordered statement, the site's one pull-quote device -------- */
.claim {
  border: 1px solid var(--line);
  border-left: 3px solid var(--teal-deep);
  border-radius: var(--radius);
  background: var(--card);
  padding: var(--s-6);
}
.claim__text {
  font-family: var(--display);
  font-size: clamp(20px, 2vw, 26px);
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: -.015em;
}
.claim__by { margin-top: var(--s-4); font-size: var(--fs-small); color: var(--muted); }
.msection--ink .claim {
  background: var(--ink-raise);
  border-color: var(--ink-line);
  border-left-color: var(--teal);
}
.msection--ink .claim__by { color: var(--ink-muted); }

/* --- Spec rows: label left, mono value right ----------------------------- */
.spec { border-top: 1px solid var(--line); }
.spec__row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--s-4);
  align-items: baseline;
  padding: var(--s-4) 0;
  border-bottom: 1px solid var(--line);
}
.spec__label { font-size: var(--fs-small); color: var(--muted); }
.spec__value {
  font-family: var(--mono);
  font-size: var(--fs-small);
  font-weight: 500;
  text-align: right;
}
.msection--ink .spec, .msection--ink .spec__row { border-color: var(--ink-line); }
.msection--ink .spec__label { color: var(--ink-muted); }
.msection--ink .spec__value { color: var(--teal); }

/* --- Cards --------------------------------------------------------------- */
.mcard {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-6);
}
.msection--ink .mcard { background: var(--ink-raise); border-color: var(--ink-line); }

.mgrid { display: grid; gap: var(--s-5); }
.mgrid--2 { grid-template-columns: repeat(2, 1fr); }
.mgrid--3 { grid-template-columns: repeat(3, 1fr); }

/* Numbered steps. A sequence, deliberately not an icon grid. */
.step__n {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .12em;
  color: var(--teal-deep);
  margin-bottom: var(--s-3);
}
.msection--ink .step__n { color: var(--teal); }
.step h3 { margin-bottom: var(--s-2); }
.step p { color: var(--muted); font-size: var(--fs-small); }
.msection--ink .step p { color: var(--ink-muted); }

/* --- FAQ ----------------------------------------------------------------
   Real <details>, so it opens with a click or a keypress, is announced as a
   disclosure, works with JavaScript off, and is findable by the browser's own
   in-page search -- all of which a div-and-script accordion gives up.
   ------------------------------------------------------------------------- */
.faq { border-top: 1px solid var(--line); }

.faq__item { border-bottom: 1px solid var(--line); }

.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  min-height: 64px;
  padding: var(--s-4) 0;
  cursor: pointer;
  list-style: none;
  font-family: var(--display);
  font-weight: 600;
  font-size: 17px;
  letter-spacing: -.01em;
  transition: color 120ms var(--ease);
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q:hover { color: var(--clay); }
.faq__item[open] .faq__q { color: var(--clay); }

/* A plus that becomes a minus. Two bars, one of which rotates away -- lighter
   than swapping an icon and it animates for free. */
.faq__mark {
  position: relative;
  flex: none;
  width: 18px;
  height: 18px;
}
.faq__mark::before,
.faq__mark::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto 0;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
  transition: transform 240ms var(--ease), opacity 240ms var(--ease);
}
.faq__mark::after { transform: rotate(90deg); }
.faq__item[open] .faq__mark::after { transform: rotate(0deg); opacity: 0; }

.faq__a {
  padding-bottom: var(--s-5);
  max-width: 68ch;
  font-size: var(--fs-small);
  line-height: 1.65;
  color: var(--muted);
}

/* Height animation where the browser can do it, a fade everywhere else. Both
   land on the same final state, so nothing is missing on the older path. */
.faq__item:not([open]) .faq__a { animation: none; }
.faq__item[open] .faq__a { animation: panelin 260ms var(--ease); }

@supports selector(details::details-content) {
  .faq__item::details-content {
    block-size: 0;
    overflow: hidden;
    transition: block-size 280ms var(--ease), content-visibility 280ms allow-discrete;
    content-visibility: hidden;
  }
  .faq__item[open]::details-content {
    block-size: auto;
    content-visibility: visible;
  }
  .faq__item[open] .faq__a { animation: none; }
}

/* --- Focus --------------------------------------------------------------- */
/* One ring, everywhere, at zero specificity -- so any component with its own
   treatment still wins without needing !important. Lifted from main.css. */
:where(a, button, summary, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--teal-deep);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
.msection--ink :where(a, button, input, textarea, [tabindex]):focus-visible,
.mfoot :where(a, button):focus-visible {
  outline-color: var(--teal);
}

/* --- Reveal -------------------------------------------------------------- */
/* The only scroll effect on the site. Fires once, 8px, and is inert without
   JavaScript: the class is only added by script, so no-JS renders finished. */
.reveal { opacity: 0; transform: translateY(8px); }
.reveal.is-in {
  opacity: 1;
  transform: none;
  transition: opacity 400ms var(--ease), transform 400ms var(--ease);
}

/* --- Responsive ---------------------------------------------------------- */
/* Same breakpoints as the app, so the two products break at the same widths. */
@media (max-width: 1023px) {
  .mgrid--3 { grid-template-columns: repeat(2, 1fr); }
  .mfoot__grid { grid-template-columns: 1fr 1fr; gap: var(--s-6); }
}

@media (max-width: 767px) {
  .mnav__inner { min-height: 60px; }
  .mnav__brand svg, .mnav__brand img { height: 26px; }

  /* Links move behind a toggle. The open state is a checkbox sitting outside
     the panel, so it needs no script and cannot get out of step with the
     markup -- the same device main.css uses for the cart sheet. */
  .mnav__links {
    display: none;
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: var(--s-2) var(--s-5) var(--s-5);
    background: var(--base);
    border-bottom: 1px solid var(--line);
  }
  .mnav__toggle-state:checked ~ .mnav__links { display: flex; }
  .mnav__link { min-height: 52px; padding-inline: 0; font-size: 16px; }
  .mnav__toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--tap-min);
    height: var(--tap-min);
    background: none;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    cursor: pointer;
  }
  .mnav__cta { display: none; }

  .mgrid--2, .mgrid--3 { grid-template-columns: 1fr; }
}

@media (max-width: 599px) {
  .container { padding-inline: var(--s-4); }
  .mcard, .claim { padding: var(--s-5); }
  .mfoot__grid { grid-template-columns: 1fr; }
}

/* Touch devices never get hover styling stuck on. */
@media (hover: none) {
  .mbtn:hover, .mnav__link:hover, .mfoot__list a:hover { background: none; }
  .mbtn--primary:hover { background: var(--ink); }
  .msection--ink .mbtn--primary:hover, .mbtn--accent:hover { background: var(--teal); }
  .mbtn--ghost:hover { border-color: var(--line-strong); }
  .arrowlink:hover .arrowlink__chev { transform: none; }
}

/* Wholesale rather than an enumerated list -- forgetting to add the next one
   is how a reduced-motion setting quietly stops being honoured. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; }
}

/* ==========================================================================
   Signature 1 — the receipt.

   Rendered at true 80mm, in mono, with the daily number set larger and heavier
   than anything else, because that is how the shop's printer actually puts it
   on paper. Used exactly twice on the site: once as a sale in the hero, once
   as its reversal in the control section. It is never card chrome.
   ========================================================================== */
.receipt {
  --paper: #FFFFFF;
  width: 302px;                    /* 80mm at 96dpi */
  max-width: 100%;
  background: var(--paper);
  color: #000;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.5;
  padding: var(--s-5) var(--s-4) var(--s-4);
  border-radius: 2px;              /* till roll, not a card */
  position: relative;
}

/* The torn foot. One clip-path zigzag, no image, no texture -- the paper is
   suggested by the shape of the cut, not by a scanned-paper background. */
.receipt::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -7px;
  height: 8px;
  background: var(--paper);
  clip-path: polygon(
    0 0, 4% 100%, 8% 0, 12% 100%, 16% 0, 20% 100%, 24% 0, 28% 100%,
    32% 0, 36% 100%, 40% 0, 44% 100%, 48% 0, 52% 100%, 56% 0, 60% 100%,
    64% 0, 68% 100%, 72% 0, 76% 100%, 80% 0, 84% 100%, 88% 0, 92% 100%,
    96% 0, 100% 100%, 100% 0);
}

.receipt__shop {
  text-align: center;
  font-weight: 500;
  font-size: 13px;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.receipt__meta { text-align: center; font-size: 11px; color: #555; }

.receipt__rule { border-top: 1px dashed #B9B9B9; margin: var(--s-3) 0; }
.receipt__rule--solid { border-top-style: solid; border-color: #000; }

/* The number staff say across a counter and write on a box. */
.receipt__no {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
}
.receipt__no span { font-size: 11px; letter-spacing: .1em; color: #555; }
.receipt__no strong {
  font-size: 30px;
  font-weight: 500;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

.receipt__items { list-style: none; margin: 0; padding: 0; }
.receipt__items li {
  display: grid;
  grid-template-columns: 18px 1fr auto;
  gap: var(--s-2);
  font-variant-numeric: tabular-nums;
}
.receipt__items li + li { margin-top: 3px; }
/* An extra prints indented beneath the line it belongs to, the way the app
   pairs them with order_items.sort_order. */
.receipt__add { color: #444; }
.receipt__add .receipt__name { padding-left: var(--s-3); }
.receipt__amt { text-align: right; }

.receipt__total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-weight: 500;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}

.receipt__note {
  text-align: center;
  font-size: 10px;
  line-height: 1.4;
  color: #333;
  margin-top: var(--s-3);
}
.receipt__copy {
  text-align: center;
  font-size: 10px;
  letter-spacing: .16em;
  color: #666;
  margin-top: var(--s-2);
}

/* The void slip. Same document, stamped -- so the reversal is legibly the
   same sale rather than a different kind of object. */
.receipt--void .receipt__stamp {
  position: absolute;
  inset-inline: 0;
  top: 42%;
  text-align: center;
  font-size: 46px;
  font-weight: 500;
  letter-spacing: .18em;
  color: var(--void);
  border-block: 3px solid var(--void);
  padding-block: 2px;
  transform: rotate(-8deg);
  opacity: .92;
  pointer-events: none;
}
.receipt--void .receipt__items,
.receipt--void .receipt__total { opacity: .45; }

/* ==========================================================================
   Signature 2 — the re-tintable order screen.

   The product's promise is that a shop gets its own brand, not Stellik's. The
   claim is cheap; the demonstration is not. This is the real order screen's
   structure, tinted by a single custom property -- which is exactly the
   mechanism the app uses, an inline `:root { --color-primary: ... }` written
   from tenant_brand(). Swapping a swatch here is the same operation as
   onboarding a shop.

   The menu is deliberately generic. Showing Napoli's would teach a visitor
   that the product is olive green and sells pizza.
   ========================================================================== */
.appshot { --shop: #C2410C; }

.appshot__frame {
  background: #FFF;
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
  font-size: 12px;
  line-height: 1.4;
  color: #111;
  /* The one shadow on the site, and it earns it: this is a device sitting on
     a surface, not a panel in the flow. */
  box-shadow: 0 24px 60px -24px rgba(19, 19, 37, .35);
}

.appshot__bar {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--line);
  background: #FFF;
}
.appshot__mark {
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--shop);
  flex: none;
}
.appshot__shop { font-weight: 600; letter-spacing: -.01em; }
.appshot__links { margin-left: auto; display: flex; gap: var(--s-3); color: #8A8A96; font-size: 11px; }
.appshot__links b { color: var(--shop); font-weight: 600; }

.appshot__body { display: grid; grid-template-columns: 1fr 168px; }
.appshot__menu { padding: var(--s-4); border-right: 1px solid var(--line); }

.appshot__tabs { display: flex; gap: var(--s-2); margin-bottom: var(--s-4); flex-wrap: wrap; }
.appshot__tab {
  padding: 5px 10px;
  border-radius: 999px;
  border: 1px solid var(--line);
  font-size: 11px;
  color: #6B6B78;
  white-space: nowrap;
}
.appshot__tab--on {
  background: var(--shop);
  border-color: var(--shop);
  color: #FFF;
  font-weight: 600;
}

.appshot__cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-3); }
.appshot__card {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--s-3);
  min-height: 74px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.appshot__thumb {
  height: 34px;
  border-radius: 4px;
  background: color-mix(in srgb, var(--shop) 12%, #FFF);
  margin-bottom: var(--s-1);
}
.appshot__name { font-weight: 600; font-size: 11px; line-height: 1.25; }
.appshot__price { font-size: 11px; color: #6B6B78; font-variant-numeric: tabular-nums; }

.appshot__cart { padding: var(--s-4); background: #FCFCFB; display: flex; flex-direction: column; }
.appshot__cart h4 {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: #8A8A96;
  margin: 0 0 var(--s-3);
  font-weight: 500;
}
.appshot__line {
  display: flex;
  justify-content: space-between;
  gap: var(--s-2);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}
.appshot__line + .appshot__line { margin-top: 5px; }
.appshot__cart-total {
  margin-top: auto;
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
  display: flex;
  justify-content: space-between;
  font-weight: 700;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.appshot__place {
  margin-top: var(--s-3);
  background: var(--shop);
  color: #FFF;
  border-radius: var(--radius-sm);
  text-align: center;
  padding: 9px;
  font-weight: 600;
  font-size: 11px;
}

/* --- The swatches -------------------------------------------------------- */
/* State lives in real radio inputs and is read with :has(), so re-tinting
   needs no JavaScript at all -- the same approach main.css uses for its
   selected-size chips. */
.swatches {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin: var(--s-5) 0 0;
  padding: 0;
  border: 0;
  flex-wrap: wrap;
}
.swatches legend {
  padding: 0;
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
  float: left;
  width: 100%;
  margin-bottom: var(--s-3);
}
.msection--ink .swatches legend { color: var(--ink-muted); }

.swatch { position: relative; display: inline-flex; }
.swatch input {
  position: absolute;
  inset: 0;
  opacity: 0;
  margin: 0;
  cursor: pointer;
}
.swatch span {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--tap-min);
  padding: 0 var(--s-4) 0 var(--s-3);
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  font-size: 13px;
  font-weight: 500;
  background: var(--card);
  transition: border-color 120ms var(--ease);
}
.swatch span::before {
  content: "";
  width: 14px; height: 14px;
  border-radius: 50%;
  background: var(--dot);
}
.swatch:has(input:checked) span { border-color: var(--ink); }
.swatch:has(input:focus-visible) span { outline: 2px solid var(--teal-deep); outline-offset: 2px; }

.msection--ink .swatch span { background: var(--ink-raise); border-color: var(--ink-line); color: var(--ink-text); }
.msection--ink .swatch:has(input:checked) span { border-color: var(--teal); }

/* Four believable restaurant brands, none of them Napoli's olive -- showing a
   real tenant's palette here would teach the visitor that the product looks
   like that shop, which is the opposite of what this section argues.

   Keyed on `value` rather than on an id, so the partial can appear more than
   once on a page without minting duplicate ids. */
.appshot:has([value="terracotta"]:checked) { --shop: #C2410C; }
.appshot:has([value="harbour"]:checked)    { --shop: #1E3A5F; }
.appshot:has([value="crimson"]:checked)    { --shop: #9B1C31; }
.appshot:has([value="bayleaf"]:checked)    { --shop: #2F6B4F; }

@media (max-width: 767px) {
  .appshot__body { grid-template-columns: 1fr; }
  .appshot__menu { border-right: 0; border-bottom: 1px solid var(--line); }
  .appshot__cards { grid-template-columns: repeat(2, 1fr); }
  .appshot__cart { flex-direction: row; align-items: center; gap: var(--s-4); }
  .appshot__cart h4, .appshot__line { display: none; }
  .appshot__cart-total { margin: 0; padding: 0; border: 0; flex: 1; }
  .appshot__place { margin: 0; padding: 9px var(--s-4); }
}

/* ==========================================================================
   Page compositions.

   Deliberately few. Every one of these is a named two-column relationship
   between a piece of argument and the artefact it is about -- not a generic
   grid utility, which is how a component set turns into a template.
   ========================================================================== */

/* Hero: sentence left, paper right. Asymmetric because the centred
   headline-over-screenshot is the default of every generated landing page,
   and this product has something physical to put beside the words. */
.hero { padding-block: clamp(72px, 10vw, 132px); }
.hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
  gap: clamp(40px, 6vw, 88px);
  align-items: center;
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-5);
  margin-top: var(--s-7);
}
.hero__paper { display: flex; justify-content: center; }

/* The receipt sits at a slight angle, as paper on a counter does. Small
   enough to read as placed rather than as an effect. */
.hero__paper .receipt,
.control__paper .receipt {
  transform: rotate(-1.4deg);
}
.control__paper .receipt { transform: rotate(1.8deg); }

.control__grid,
.napoli__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr);
  gap: clamp(40px, 6vw, 88px);
  align-items: center;
}
.control__paper { display: flex; justify-content: center; }
.napoli__card { align-self: start; }

.cta__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
}

@media (max-width: 1023px) {
  .hero__grid, .control__grid, .napoli__grid { grid-template-columns: 1fr; }
  .hero__paper, .control__paper { justify-content: flex-start; }
}

@media (max-width: 599px) {
  .hero__paper, .control__paper { justify-content: center; }
  .hero__actions { gap: var(--s-4); }
  .hero__actions .mbtn { width: 100%; }
}

/* ==========================================================================
   Contact
   ========================================================================== */
.contact__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 480px);
  gap: clamp(40px, 6vw, 80px);
  align-items: start;
}
.contact__direct { margin-top: var(--s-7); max-width: 380px; }

.contact__line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  min-height: var(--tap-min);
  padding-block: var(--s-2);
  border-bottom: 1px solid var(--line);
  text-decoration: none;
}
.contact__label {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
}
.contact__value { font-weight: 600; }
.contact__line:hover .contact__value { color: var(--teal-deep); }

.contact__form { align-self: start; }

/* --- Forms --------------------------------------------------------------- */
.mform { margin-top: var(--s-5); }
.mform > * + * { margin-top: var(--s-4); }
.mform__row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-4); }

.mfield { display: block; }
.mfield__label {
  display: block;
  font-size: var(--fs-small);
  font-weight: 600;
  margin-bottom: var(--s-2);
}
.mfield__input {
  display: block;
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--s-3) var(--s-4);
  font: inherit;
  font-size: 16px;          /* 16px or iOS zooms the page on focus */
  color: var(--ink);
  background: var(--base);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  transition: border-color 120ms var(--ease);
}
.mfield__input:focus { border-color: var(--ink); }
.mfield__input--area { min-height: 92px; resize: vertical; line-height: 1.5; }

.formerror {
  margin-top: var(--s-4);
  padding: var(--s-3) var(--s-4);
  border: 1px solid color-mix(in srgb, var(--void) 35%, var(--line));
  border-left: 3px solid var(--void);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--void) 6%, var(--card));
  color: var(--void);
  font-size: var(--fs-small);
}

@media (max-width: 1023px) {
  .contact__grid { grid-template-columns: 1fr; }
  .contact__direct { max-width: none; }
}
@media (max-width: 599px) {
  .mform__row { grid-template-columns: 1fr; }
}

/* ==========================================================================
   The hero builder.

   The product's whole loop is about four seconds long, so the hero lets a
   visitor do it instead of describing it: tap items, watch the ticket build,
   print it. Everything here is a real button, and the receipt beside it is the
   same .receipt component the rest of the site uses.
   ========================================================================== */
.builder__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
  gap: clamp(36px, 5vw, 80px);
  align-items: start;
}

.builder__menu {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-3);
  margin-top: var(--s-6);
}

.bitem {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-height: 68px;
  padding: var(--s-3) var(--s-4);
  background: rgba(255, 255, 255, .05);
  border: 1px solid var(--ink-line);
  border-radius: var(--radius);
  color: var(--ink-text);
  text-align: left;
  cursor: pointer;
  transition: background-color 120ms var(--ease), border-color 120ms var(--ease),
              transform 120ms var(--ease);
}
.bitem:hover:not(:disabled) { background: rgba(255, 255, 255, .1); border-color: var(--teal); }
.bitem:active:not(:disabled) { transform: scale(.97); }
.bitem:disabled { opacity: .38; cursor: not-allowed; }
.bitem__name { font-size: var(--fs-small); font-weight: 600; line-height: 1.2; }
.bitem__price {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--teal);
  font-variant-numeric: tabular-nums;
}
.bitem--add { grid-column: 1 / -1; flex-direction: row; align-items: center; min-height: 52px; }
.bitem--add .bitem__name { color: var(--ink-muted); }
.bitem--add:not(:disabled) .bitem__name { color: var(--ink-text); }

/* The single ghost tap. It fires once, four seconds in, only if nobody has
   touched anything -- it exists to say "this is not a picture". */
.bitem.is-ghost {
  background: rgba(0, 232, 164, .18);
  border-color: var(--teal);
  transform: scale(.97);
}

.builder__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-4);
  margin-top: var(--s-6);
}
.builder__hint {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.builder__count {
  font-family: var(--mono);
  font-size: var(--fs-small);
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}

/* The empty ticket still looks like a ticket -- it has a header and a number,
   it simply has nothing on it yet. An empty box would read as broken. */
.builder.is-empty .receipt__items::after {
  content: "Tap an item to start the order";
  display: block;
  padding: var(--s-6) 0;
  text-align: center;
  font-size: 11px;
  color: #999;
}

/* --- Printing ------------------------------------------------------------
   A head sweeps down the paper, then the ticket settles. 900ms, once, and
   skipped outright under reduced motion -- the state still changes, it simply
   arrives instead of animating. */
.receipt__sweep {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  border-radius: 2px;
}
.receipt.is-printing .receipt__sweep { opacity: 1; }
.receipt__sweep::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 42%;
  top: -42%;
  background: linear-gradient(to bottom,
    rgba(19, 19, 37, 0) 0%,
    rgba(19, 19, 37, .05) 60%,
    rgba(19, 19, 37, .13) 90%,
    rgba(19, 19, 37, 0) 100%);
}
.receipt.is-printing .receipt__sweep::before { animation: sweep 900ms ease-out forwards; }
@keyframes sweep { to { top: 100%; } }

.receipt.is-printed { animation: settle 320ms var(--ease); }
@keyframes settle {
  0%   { transform: translateY(-6px) rotate(-1.4deg); }
  100% { transform: translateY(0) rotate(-1.4deg); }
}

.receipt__items li.is-new { animation: linein 260ms var(--ease); }
@keyframes linein {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

/* ==========================================================================
   The configurator.

   Recolouring proves you can re-skin. What earns this section is the mock
   changing shape -- a table number for a cafe, a weight for a grill house, a
   rider for a cloud kitchen -- and the chip that names what changed.
   ========================================================================== */
.config__grid {
  display: grid;
  grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
  gap: clamp(36px, 5vw, 72px);
  align-items: start;
}

.picker { display: flex; flex-wrap: wrap; gap: var(--s-3); padding: 0; margin: 0; border: 0; }
.picker legend {
  padding: 0;
  width: 100%;
  margin-bottom: var(--s-4);
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
}
.pick { position: relative; display: inline-flex; }
.pick input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.pick span {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--tap-min);
  padding: 0 var(--s-5);
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--card);
  font-size: var(--fs-small);
  font-weight: 600;
  transition: border-color 120ms var(--ease), background-color 120ms var(--ease);
}
.pick span::before {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--dot);
}
.pick:has(input:checked) span { border-color: var(--ink); background: var(--ink); color: var(--ink-text); }
.pick:has(input:focus-visible) span { outline: 2px solid var(--teal-deep); outline-offset: 2px; }

.config__pitch {
  margin-top: var(--s-6);
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--muted);
  max-width: 44ch;
  min-height: 5.5em;   /* holds its height so switching does not jump the page */
}

/* The chip is the argument of the whole section: it says the interface
   changed, not the paint. */
.config__changed {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  margin-top: var(--s-5);
  padding: var(--s-2) var(--s-4);
  border-radius: 999px;
  background: color-mix(in srgb, var(--clay) 10%, var(--card));
  border: 1px solid color-mix(in srgb, var(--clay) 24%, transparent);
  color: var(--clay);
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  opacity: 0;
}
.config__changed::before { content: "+"; font-weight: 700; }
.config__changed.is-in { animation: chipin 420ms var(--ease) forwards; }
@keyframes chipin {
  from { opacity: 0; transform: translateY(5px); }
  to   { opacity: 1; transform: none; }
}

.appshot__card.is-swapping { animation: cardin 320ms var(--ease) backwards; }
@keyframes cardin {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* The unit sits on its own line. Inline, "Rs 1,250 Medium · Large" wraps
   mid-phrase in a narrow card and reads as two prices. */
.appshot__unit { display: block; color: #9A9AA6; }

/* The per-business element. Same slot, different contents -- which is the
   whole point being made. */
.appshot__feature {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  margin-bottom: var(--s-3);
  padding: 7px 9px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--shop) 10%, #FFF);
  border: 1px solid color-mix(in srgb, var(--shop) 22%, transparent);
}
.appshot__feature-label {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #6B6B78;
}
.appshot__feature-value { font-size: 11px; font-weight: 700; color: var(--shop); }

/* ==========================================================================
   Stepper
   ========================================================================== */
.stepper {
  display: grid;
  grid-template-columns: minmax(0, 340px) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 72px);
  align-items: start;
}
.stepper__tabs { display: flex; flex-direction: column; gap: var(--s-2); }

.step-tab {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--s-4) var(--s-5);
  background: none;
  border: 0;
  border-left: 2px solid var(--line);
  border-radius: 0 var(--radius) var(--radius) 0;
  cursor: pointer;
  transition: background-color 120ms var(--ease), border-color 120ms var(--ease);
}
.step-tab:hover { background: var(--card); }
.step-tab.is-on { border-left-color: var(--clay); background: var(--card); }
.step-tab__n {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: .12em;
  color: var(--muted);
}
.step-tab.is-on .step-tab__n { color: var(--clay); }
.step-tab__h { font-family: var(--display); font-weight: 600; font-size: 17px; margin-top: 2px; }
.step-tab__p {
  font-size: var(--fs-small);
  color: var(--muted);
  margin-top: var(--s-2);
  display: none;
}
.step-tab.is-on .step-tab__p { display: block; }

/* A hairline that fills over the hold, so the section says it is going to move
   before it moves. Paused with the timer when a pointer is on the section. */
.step-tab__bar {
  height: 2px;
  margin-top: var(--s-3);
  background: var(--line);
  border-radius: 2px;
  overflow: hidden;
  display: none;
}
.step-tab.is-on .step-tab__bar { display: block; }
.stepper.is-manual .step-tab__bar { display: none; }
.step-tab__bar i {
  display: block;
  height: 100%;
  width: 0;
  background: var(--clay);
}
.step-tab.is-on .step-tab__bar i { animation: fill 6000ms linear forwards; }
.stepper.is-paused .step-tab__bar i { animation-play-state: paused; }
@keyframes fill { to { width: 100%; } }

.stepper__panel { animation: panelin 300ms var(--ease); }
@keyframes panelin {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* ==========================================================================
   Two-position switch
   ========================================================================== */
.flip {
  display: inline-flex;
  padding: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .07);
  border: 1px solid var(--ink-line);
}
.flip__opt { position: relative; display: inline-flex; }
.flip__opt input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.flip__opt span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 40px;
  padding: 0 var(--s-5);
  border-radius: 999px;
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--ink-muted);
  white-space: nowrap;
  transition: background-color 160ms var(--ease), color 160ms var(--ease);
}
.flip__opt:has(input:checked) span { background: var(--teal); color: var(--ink); }
.flip__opt:has(input:focus-visible) span { outline: 2px solid var(--teal); outline-offset: 3px; }

.outcome { margin-top: var(--s-6); }
.outcome__body { display: none; }
[data-state="before"] .outcome__body--before,
[data-state="after"]  .outcome__body--after { display: block; animation: panelin 300ms var(--ease); }

/* ==========================================================================
   Timeline (how we work) and paired rows (what we built)
   ========================================================================== */
.timeline { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s-5); }
.tstep { position: relative; padding-top: var(--s-6); }
.tstep::before {
  content: "";
  position: absolute;
  top: 11px;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--line);
}
.tstep::after {
  content: "";
  position: absolute;
  top: 5px;
  left: 0;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--cream);
  border: 2px solid var(--line-strong);
  transition: background-color 160ms var(--ease), border-color 160ms var(--ease);
}
.tstep:hover::after,
.tstep:focus-within::after { background: var(--amber); border-color: var(--clay); }
.tstep__n {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: .12em;
  color: var(--clay);
}
.tstep h3 { margin: var(--s-2) 0; }
.tstep p { font-size: var(--fs-small); color: var(--muted); }

.pairs { border-top: 1px solid var(--line); }
.pair { border-bottom: 1px solid var(--line); }
.pair summary {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--s-4);
  align-items: center;
  min-height: 60px;
  padding: var(--s-3) 0;
  cursor: pointer;
  list-style: none;
  font-weight: 600;
}
.pair summary::-webkit-details-marker { display: none; }
.pair__chev {
  color: var(--clay);
  font-family: var(--mono);
  transition: transform 160ms var(--ease);
}
.pair[open] .pair__chev { transform: rotate(45deg); }
.pair__body {
  padding-bottom: var(--s-5);
  font-size: var(--fs-small);
  color: var(--muted);
  max-width: 62ch;
  animation: panelin 240ms var(--ease);
}

/* ==========================================================================
   Business chips (CTA and contact) — they rewrite the first message
   ========================================================================== */
.chips { display: flex; flex-wrap: wrap; gap: var(--s-3); }
.chipbtn {
  min-height: var(--tap-min);
  padding: 0 var(--s-5);
  border-radius: 999px;
  border: 1px solid rgba(19, 19, 37, .28);
  background: transparent;
  color: var(--ink);
  font-size: var(--fs-small);
  font-weight: 600;
  cursor: pointer;
  transition: background-color 120ms var(--ease), border-color 120ms var(--ease);
}
.chipbtn:hover { background: rgba(255, 255, 255, .35); }
.chipbtn[aria-pressed="true"] { background: var(--ink); color: var(--ink-text); border-color: var(--ink); }
.msection--wash .chipbtn,
.msection:not(.msection--amber) .chipbtn { border-color: var(--line-strong); }
.msection:not(.msection--amber) .chipbtn:hover { background: var(--card); }

/* --- Responsive ---------------------------------------------------------- */
@media (max-width: 1023px) {
  .builder__grid, .config__grid, .stepper { grid-template-columns: 1fr; }
  .timeline { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 599px) {
  .builder__menu { grid-template-columns: repeat(2, 1fr); }
  .timeline { grid-template-columns: 1fr; gap: var(--s-6); }
  .tstep::before { display: none; }
  .flip { width: 100%; }
  .flip__opt { flex: 1; }
  .flip__opt span { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .step-tab.is-on .step-tab__bar { display: none; }
}



/* ==========================================================================
   The hardware picker.

   Two questions and an answer, instead of three rules the reader has to map
   onto their own shop. Entirely CSS: four combinations, four results, matched
   with :has() on the real radios -- no script, and it works the moment the
   markup arrives.
   ========================================================================== */
.kit__qs { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s-6); }

.kit__out {
  margin-top: var(--s-6);
  padding: var(--s-6);
  background: var(--card);
  border: 1px solid var(--line);
  border-left: 3px solid var(--clay);
  border-radius: var(--radius);
}
.kit__r { display: none; }
.kit__r h3 { margin-bottom: var(--s-3); }
.kit__r p { color: var(--muted); font-size: var(--fs-small); max-width: 62ch; }

.kit:has(#kit-roam:checked):has(#kit-budget:checked)    .kit__r--roam-budget,
.kit:has(#kit-roam:checked):has(#kit-solid:checked)     .kit__r--roam-solid,
.kit:has(#kit-counter:checked):has(#kit-budget:checked) .kit__r--counter-budget,
.kit:has(#kit-counter:checked):has(#kit-solid:checked)  .kit__r--counter-solid {
  display: block;
  animation: panelin 260ms var(--ease);
}

.kit__rule { margin-top: var(--s-5); max-width: 66ch; }

@media (max-width: 767px) {
  .kit__qs { grid-template-columns: 1fr; gap: var(--s-5); }
}

/* ==========================================================================
   Pricing — three builds.

   Control is the centre column and is meant to win. It is raised, wider, and
   the only card drawn in the warm accent. That is deliberate commercial
   design, not decoration: with three options people take the middle, and the
   middle is the build the business is built to sell.

   The two either side are real products, not props. A decoy a buyer can see
   through costs more trust than it wins.
   ========================================================================== */
.billing {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-4);
}
/* The toggle borrows .flip, which was written for an ink section. On light
   ground it needs its own frame. */
.billing .flip { background: var(--card); border-color: var(--line-strong); }
.billing .flip__opt span { color: var(--muted); }
.billing .flip__opt:has(input:checked) span { background: var(--ink); color: var(--ink-text); }
.billing__save {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--clay);
}

.plans {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-5);
  align-items: start;
}

.plan {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-6);
}

/* Centre stage, literally: raised out of the row and given the accent. */
.plan--featured {
  border-color: var(--clay);
  border-width: 2px;
  margin-top: calc(var(--s-6) * -1);
  padding-top: var(--s-8);
  padding-bottom: var(--s-7);
  box-shadow: 0 20px 44px -28px rgba(19, 19, 37, .3);
}
.plan__badge {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -50%);
  white-space: nowrap;
  padding: 5px var(--s-4);
  border-radius: 999px;
  background: var(--amber);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.plan__name { font-size: 22px; }
.plan__who {
  margin-top: var(--s-2);
  font-size: var(--fs-small);
  color: var(--muted);
  min-height: 3.2em;   /* holds the price row level across all three cards */
}

.plan__price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-top: var(--s-5);
}
.plan__from { font-size: var(--fs-small); color: var(--muted); }
.plan__setup {
  font-family: var(--display);
  font-size: clamp(30px, 3.2vw, 40px);
  font-weight: 800;
  letter-spacing: -.03em;
  line-height: 1;
}
.plan__once {
  width: 100%;
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}

.plan__monthly {
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid var(--line);
  font-size: var(--fs-small);
  color: var(--muted);
}
.plan__monthly strong { font-size: 19px; color: var(--ink); font-weight: 700; }
.plan__saving {
  display: inline-block;
  margin-left: var(--s-2);
  padding: 2px var(--s-2);
  border-radius: 999px;
  /* 6%, not 10%: clay on its own 10% tint measured 4.48:1, just under. */
  background: color-mix(in srgb, var(--clay) 6%, transparent);
  color: var(--clay);
  font-size: 12px;
  font-weight: 600;
}
.plan__extra {
  margin-top: var(--s-2);
  font-size: 13px;
  color: var(--muted);
  /* Reserved on every card, not only the one that fills it, so the three call
     to action buttons sit on one line instead of stepping down. */
  min-height: 1.5em;
}

.plan .mbtn { margin-top: var(--s-5); }

.plan__inherits {
  margin-top: var(--s-6);
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--clay);
}
.plan__list {
  list-style: none;
  margin: var(--s-4) 0 0;
  padding: 0;
  font-size: var(--fs-small);
}
.plan:not(.plan--featured) .plan__list { margin-top: var(--s-6); }
.plan__list li {
  position: relative;
  padding-left: 24px;
  padding-block: 5px;
  line-height: 1.45;
}
.plan__list li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 11px;
  width: 10px;
  height: 5px;
  border-left: 2px solid var(--teal-deep);
  border-bottom: 2px solid var(--teal-deep);
  transform: rotate(-45deg);
}
.plan--featured .plan__list li::before { border-color: var(--clay); }

.plans__note {
  max-width: 68ch;
  margin: var(--s-7) auto 0;
  text-align: center;
  font-size: var(--fs-small);
  color: var(--muted);
}

/* ==========================================================================
   The comparison matrix.

   A real <table>, because it is tabular data and a screen reader should be
   able to say "Control, VOID slips, included" rather than read a wall of divs.
   ========================================================================== */
.vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Below the plan grid's breakpoint the table scrolls in its own box rather
   than pushing the page sideways -- a four-column table cannot fit 390px and
   pretending otherwise is what makes a whole page swipe. */
.matrix__scroll {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
}

.matrix {
  width: 100%;
  min-width: 640px;
  border-collapse: collapse;
  font-size: var(--fs-small);
}

.matrix thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--card);
  padding: var(--s-4);
  text-align: center;
  font-family: var(--display);
  font-size: 17px;
  border-bottom: 1px solid var(--line-strong);
}
.matrix thead th:first-child { text-align: left; width: 46%; }
.matrix__price {
  display: block;
  margin-top: 2px;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 400;
  color: var(--muted);
}

.matrix__group th {
  padding: var(--s-5) var(--s-4) var(--s-2);
  text-align: left;
  font-family: var(--mono);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
  background: var(--base);
}

.matrix tbody tr:not(.matrix__group) th {
  padding: var(--s-3) var(--s-4);
  text-align: left;
  font-weight: 400;
  line-height: 1.4;
  border-top: 1px solid var(--line);
}
.matrix td {
  padding: var(--s-3) var(--s-4);
  text-align: center;
  border-top: 1px solid var(--line);
}

/* The target column is tinted the whole way down, so the eye stays on it. */
.matrix th.is-featured,
.matrix td.is-featured {
  background: color-mix(in srgb, var(--clay) 5%, var(--card));
}
.matrix thead th.is-featured { border-bottom-color: var(--clay); }

.matrix__yes {
  display: inline-block;
  width: 12px;
  height: 6px;
  border-left: 2px solid var(--teal-deep);
  border-bottom: 2px solid var(--teal-deep);
  transform: rotate(-45deg) translateY(-2px);
}
.is-featured .matrix__yes { border-color: var(--clay); }
/* The dash is not decoration -- it is the "not included" marker, and in the
   two control groups it IS the argument the page is making. At --line-strong
   it measured 1.47:1 and was effectively invisible. */
.matrix__no { color: #6F6F7B; }
.matrix__val { font-family: var(--mono); font-size: 12px; }

/* Rows this business leans on. Lifted, never filtered -- hiding the rest would
   misrepresent what a build includes. */
.matrix tbody tr[data-need].is-key th,
.matrix tbody tr[data-need].is-key td {
  background: color-mix(in srgb, var(--clay) 9%, transparent);
}
.matrix tbody tr[data-need].is-key th { font-weight: 600; }

/* ==========================================================================
   What it is worth
   ========================================================================== */
.calc {
  display: grid;
  grid-template-columns: minmax(0, 320px) minmax(0, 1fr);
  gap: clamp(24px, 4vw, 56px);
  align-items: start;
  padding: var(--s-6);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.calc__inputs { display: grid; gap: var(--s-4); }
.calc__field { position: relative; display: block; }
.calc__unit {
  position: absolute;
  left: var(--s-4);
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--mono);
  font-size: 13px;
  color: var(--muted);
  pointer-events: none;
}
.calc__field .mfield__input { padding-left: 42px; }

.calc__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-4);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--line);
}
.calc__label { font-size: var(--fs-small); color: var(--muted); }
.calc__value { font-family: var(--mono); font-weight: 500; }
.calc__row--lead { border-bottom: 0; }
.calc__row--lead .calc__value { color: var(--clay); font-size: 17px; }

.calc__note {
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid var(--line);
  font-size: var(--fs-small);
  color: var(--muted);
  line-height: 1.6;
}
.calc__note strong { color: var(--ink); font-family: var(--mono); }

/* --- Responsive ---------------------------------------------------------- */
@media (max-width: 1023px) {
  .plans { grid-template-columns: 1fr; gap: var(--s-6); }
  /* Stacked, the featured card must not hang off the top of the one above it. */
  .plan--featured { margin-top: 0; padding-top: var(--s-7); }
  .plan__who { min-height: 0; }
  .calc { grid-template-columns: 1fr; }
}
@media (max-width: 599px) {
  .plan, .calc { padding: var(--s-5); }
  .billing { flex-direction: column; align-items: flex-start; }
}

/* ==========================================================================
   The night section — one order in, everything the shop needs out.

   The only place on the site with ambient motion. The rule everywhere else is
   that motion is caused by the visitor or reports state; here the travelling
   dots are the point being made -- an order moving through the system -- so
   they move on their own. Still cut entirely under prefers-reduced-motion.
   ========================================================================== */
.msection--night {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--ink);
  color: var(--ink-text);
}
/* Depth, from two soft lights rather than a flat fill. Kept low enough that
   white text stays well clear of the contrast floor anywhere on the panel. */
.msection--night::before {
  content: "";
  position: absolute;
  inset: -20% -10%;
  z-index: -1;
  background:
    radial-gradient(38% 46% at 18% 82%, rgba(0, 232, 164, .16), transparent 70%),
    radial-gradient(42% 50% at 86% 12%, rgba(112, 84, 255, .20), transparent 72%),
    radial-gradient(60% 60% at 50% 50%, rgba(28, 28, 49, .9), transparent 75%);
}
/* A faint grid, so the dark is a surface rather than a void. */
.msection--night::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image:
    linear-gradient(rgba(255, 255, 255, .028) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, .028) 1px, transparent 1px);
  background-size: 56px 56px;
  mask-image: radial-gradient(70% 60% at 50% 45%, #000 30%, transparent 78%);
}
.msection--night h2 { color: var(--ink-text); }
.msection--night .lead { color: var(--ink-muted); }
.msection--night .eyebrow { color: var(--teal); }

/* The eyebrow as a bordered pill, centred -- the reference's device, and it
   gives the headline something to sit under. */
.pill {
  display: inline-flex;
  align-items: center;
  padding: 6px var(--s-4);
  border: 1px solid var(--ink-line);
  border-radius: 999px;
  background: rgba(255, 255, 255, .04);
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--teal);
  margin-bottom: var(--s-5);
}

.hub {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: clamp(8px, 2vw, 28px);
  margin-top: clamp(48px, 6vw, 88px);
}

.hub__side { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-5); }

.hub__node { display: flex; align-items: center; gap: 0; }
.hub__side--out .hub__node { flex-direction: row-reverse; }

.hub__label {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  flex: none;
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--ink-line);
  border-radius: 999px;
  background: rgba(255, 255, 255, .06);
  backdrop-filter: blur(6px);
  font-size: var(--fs-small);
  font-weight: 600;
  white-space: nowrap;
}
.hub__label svg { width: 16px; height: 16px; flex: none; color: var(--ink-muted); }

/* The wire. A hairline that fades toward the label so it reads as coming out
   of the hub rather than being glued to a pill. */
.hub__wire {
  position: relative;
  flex: 1 1 auto;
  min-width: 24px;
  height: 1px;
  background: linear-gradient(90deg, rgba(255, 255, 255, .05), rgba(255, 255, 255, .22));
}
.hub__side--out .hub__wire {
  background: linear-gradient(90deg, rgba(255, 255, 255, .22), rgba(255, 255, 255, .05));
}

.hub__dot {
  position: absolute;
  top: 50%;
  left: 0;
  width: 6px;
  height: 6px;
  margin-top: -3px;
  border-radius: 50%;
  background: var(--teal);
  box-shadow: 0 0 10px 2px rgba(0, 232, 164, .55);
  opacity: 0;
  animation: hubflow 3.4s linear infinite;
}
.hub__side--out .hub__dot { animation-name: hubflow-out; }
/* Staggered so the three wires never pulse in lockstep, which would read as a
   loading bar rather than as traffic. */
.hub__node:nth-child(2) .hub__dot { animation-delay: 1.1s; }
.hub__node:nth-child(3) .hub__dot { animation-delay: 2.2s; }

@keyframes hubflow {
  0%   { left: 0;    opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { left: 100%; opacity: 0; }
}
@keyframes hubflow-out {
  0%   { left: 100%; opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { left: 0;    opacity: 0; }
}

/* The core. A ring of light with the mark inside it. */
.hub__core {
  position: relative;
  display: grid;
  place-items: center;
  width: clamp(148px, 20vw, 216px);
  aspect-ratio: 1;
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 50%, rgba(0, 232, 164, .10), transparent 62%),
    var(--ink-raise);
  border: 1px solid rgba(0, 232, 164, .30);
  box-shadow:
    0 0 0 1px rgba(0, 232, 164, .10),
    0 0 60px -6px rgba(0, 232, 164, .40),
    inset 0 0 40px -10px rgba(0, 232, 164, .30);
}
.hub__core svg { width: 44%; height: auto; color: var(--ink-text); }

/* A slow breath on the halo. It is the one thing on the page that moves with
   nothing prompting it, so it is deliberately almost imperceptible. */
.hub__core::before {
  content: "";
  position: absolute;
  inset: -14%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(0, 232, 164, .22), transparent 62%);
  z-index: -1;
  animation: hubbreathe 6s ease-in-out infinite;
}
@keyframes hubbreathe {
  0%, 100% { transform: scale(1);    opacity: .75; }
  50%      { transform: scale(1.08); opacity: 1; }
}

@media (max-width: 899px) {
  /* Stacked, the wires would point at nothing, so they go and the two groups
     become labelled columns either side of the mark. */
  .hub { grid-template-columns: 1fr; justify-items: center; gap: var(--s-6); }
  .hub__side { grid-auto-flow: row; justify-items: center; width: 100%; }
  .hub__side--in { order: 1; }
  .hub__core { order: 2; }
  .hub__side--out { order: 3; }
  .hub__wire { display: none; }
  .hub__node, .hub__side--out .hub__node { flex-direction: row; justify-content: center; }
}

@media (prefers-reduced-motion: reduce) {
  .hub__dot { animation: none; opacity: 1; left: 50%; }
  .hub__core::before { animation: none; }
}
