/* Marketing site (§1.11). One static stylesheet; no build step.
 *
 * Structure, in this order and only this order:
 *   1. Tokens        — the whole palette, scale and rhythm, light and dark
 *   2. Reset + base  — element defaults, focus, motion, selection
 *   3. Layout        — page container, sections, rhythm helpers
 *   4. Components    — header, buttons, cards, the shopfront, the proofs, forms, tables
 *   5. Page-specific — pricing, terms, signup
 *
 * A rule that sets a colour, a size or a duration by hand instead of by token is a bug:
 * the token is the only thing dark mode, the type scale and the motion preference can
 * reach. Anything hard-coded is invisible to all three and shows up as the one element
 * that stayed cream when the page went dark.
 */

/* ==========================================================================
   1. TOKENS
   ========================================================================== */

/*
 * Indigo dye and marigold on unbleached cotton, not the cream-and-terracotta every
 * template arrives in. Both colours come from the trade these shopkeepers are in --
 * indigo is what the cloth is dyed with, marigold is what the shop is garlanded with --
 * so the palette is the subject rather than a mood.
 *
 * Three tokens per hue rather than one, because one is what forced the failures below:
 *
 *   --x          the fill. Large areas, buttons, bars.
 *   --x-ink      the same hue at a weight that is legible as TEXT on --bg.
 *   --on-x       what is legible sitting ON the fill.
 *
 * Marigold is why. #dd9016 as text on cotton is 2.6:1 -- it was the "save 17%" badge on
 * the pricing card and the eyebrow on the closing panel, and neither passed. The fill is
 * still that marigold; --accent-ink is the same dye taken down until it reads.
 */
:root {
  color-scheme: light;

  /* -- surfaces, cool-tinted throughout; never two grey families on one page ---------- */
  --bg: #f5f2ea;              /* unbleached cotton */
  --surface: #fffdf8;         /* raised paper */
  --surface-2: #faf7f0;       /* raised, one step quieter */
  --sunk: #ece7db;            /* recessed */
  --border: #ded7c8;
  --border-strong: #c9c0ac;

  /* -- text -------------------------------------------------------------------------- */
  --text: #191d2e;            /* indigo-black */
  --muted: #565c70;           /* 5.9:1 on --bg */
  --faint: #767c8e;           /* decorative only, never load-bearing */

  /* -- brand ------------------------------------------------------------------------- */
  --primary: #26357a;         /* indigo dye */
  --primary-ink: #26357a;
  --on-primary: #fffdf8;
  --primary-hover: #1c2860;
  --primary-soft: #e7e9f4;

  --accent: #dd9016;          /* marigold */
  --accent-ink: #8a5209;      /* the same dye, taken down until it reads as text */
  --on-accent: #1a1206;
  --accent-soft: #fbf0dc;

  /* -- status ------------------------------------------------------------------------ */
  --ok: #1a6046;
  --ok-soft: #e4f0ea;
  --warn-ink: #8a5209;
  --warn-soft: #fdf3e2;
  --danger: #9b2226;
  --danger-soft: #f8e8e8;

  /* -- shadow -------------------------------------------------------------------------
     Two layers, always: one tight and dark for the contact edge, one wide and soft for
     the ambient fall. Tinted with the page's own indigo rather than black, so a card on
     cotton does not sit in a grey puddle. */
  --shadow-1: 0 1px 2px rgb(25 29 46 / 0.06), 0 1px 1px rgb(25 29 46 / 0.04);
  --shadow-2: 0 2px 4px rgb(25 29 46 / 0.05), 0 6px 16px -4px rgb(25 29 46 / 0.08);
  --shadow-3: 0 4px 8px rgb(25 29 46 / 0.05), 0 18px 40px -12px rgb(25 29 46 / 0.14);
  --ring: 0 0 0 1px rgb(25 29 46 / 0.04) inset;

  /* -- type ---------------------------------------------------------------------------
     Fraunces for display, Archivo for text. Fluid, and every step paired with the
     line-height and tracking it wants: large type needs negative tracking and a tight
     leading, small type needs the opposite, and a single scale with one line-height is
     how headings end up looking loose and captions cramped. */
  --display: "Fraunces", "Iowan Old Style", Georgia, serif;
  --body: "Archivo", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --t-xs:  0.75rem;
  --t-sm:  0.8125rem;
  --t-base: 0.9375rem;
  --t-md:  1rem;
  --t-lg:  clamp(1.0625rem, 0.99rem + 0.34vw, 1.25rem);
  --t-xl:  clamp(1.25rem, 1.13rem + 0.55vw, 1.5rem);
  --t-2xl: clamp(1.5rem, 1.29rem + 0.95vw, 2rem);
  --t-3xl: clamp(1.875rem, 1.5rem + 1.7vw, 2.75rem);
  --t-4xl: clamp(2.25rem, 1.6rem + 2.9vw, 3.75rem);

  --lh-tight: 1.06;
  --lh-snug: 1.22;
  --lh-body: 1.6;
  --lh-loose: 1.7;

  --tr-tight: -0.028em;
  --tr-snug: -0.015em;
  --tr-wide: 0.08em;

  /* -- space --------------------------------------------------------------------------
     One geometric-ish scale, used for every gap and every pad on the site. The old sheet
     had 0.35, 0.45, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.9 and 1.1rem in it -- nine values
     doing the work of three, none of which lined up with any other. */
  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-5: 1.5rem;
  --s-6: 2rem;
  --s-7: 3rem;
  --s-8: 4rem;
  --s-9: clamp(4rem, 2.7rem + 5.8vw, 7rem);   /* between sections */
  --s-10: clamp(5rem, 3rem + 9vw, 9.5rem);    /* between movements */

  /* -- radius -------------------------------------------------------------------------
     Concentric: a child's radius is the parent's minus the padding between them, so the
     curves stay parallel instead of the inner one looking squarer than the outer. */
  --r-1: 0.25rem;
  --r-2: 0.5rem;
  --r-3: 0.75rem;
  --r-4: 1rem;
  --r-5: 1.5rem;
  --r-full: 999px;

  /* -- motion -------------------------------------------------------------------------
     Durations are short because everything here is a state change, not a journey. The
     spring is for things that move a distance; the standard ease is for colour. */
  --d-1: 120ms;
  --d-2: 200ms;
  --d-3: 320ms;
  --e-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --e-spring: cubic-bezier(0.34, 1.42, 0.64, 1);

  /* -- depth --------------------------------------------------------------------------
     A named scale, so nothing ever needs to reach for 9999. */
  --z-base: 0;
  --z-raised: 10;
  --z-sticky: 100;
  --z-overlay: 1000;
  --z-skip: 10000;

  --measure: 34rem;           /* ~65 characters of Archivo at --t-md */
  --page: 72rem;
  /* The sticky header's height. Anything that scrolls into view has to clear it, or the
     line you jumped to is the line hidden behind the bar. */
  --header-h: 4.75rem;
}

/*
 * Dark. Not an inversion -- the cloth changes, the dyes do not. The ground becomes the
 * indigo-black the text used to be, and the two dyes lift by exactly as much as they need
 * to stay legible on it. Marigold barely moves; indigo has to travel a long way, which is
 * why --primary and --primary-ink separate here and are the same colour in light mode.
 */
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;

    --bg: #14161f;
    --surface: #1c1f2b;
    --surface-2: #232735;
    --sunk: #0e1018;
    --border: #2f3444;
    --border-strong: #454b5f;

    --text: #eceaf2;
    --muted: #a6abbd;
    --faint: #7f8497;

    --primary: #4356c0;
    --primary-ink: #a6b3f5;
    --on-primary: #f7f8ff;
    --primary-hover: #5566d0;
    --primary-soft: #1e2340;

    --accent: #dd9016;
    --accent-ink: #edaa3f;
    --on-accent: #1a1206;
    --accent-soft: #2a2114;

    --ok: #5cc39b;
    --ok-soft: #16281f;
    --warn-ink: #edaa3f;
    --warn-soft: #2a2114;
    --danger: #f08b8e;
    --danger-soft: #2c1719;

    /* Shadows do almost nothing on a dark ground -- there is no light to occlude. The
       separation has to come from the surface being lighter than the page, so the shadows
       shrink to a hairline and the borders carry the edge. */
    --shadow-1: 0 1px 2px rgb(0 0 0 / 0.4);
    --shadow-2: 0 2px 6px rgb(0 0 0 / 0.4);
    --shadow-3: 0 12px 32px -12px rgb(0 0 0 / 0.6);
    --ring: 0 0 0 1px rgb(255 255 255 / 0.04) inset;
  }
}

/* ==========================================================================
   2. RESET + BASE
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Applies to every scroll the BROWSER performs, not just anchor clicks -- including the
     one it does when Tab moves focus to something under the sticky header. Without it a
     keyboard user tabbing back up the page lands on a control they cannot see. */
  scroll-padding-top: calc(var(--header-h) + var(--s-3));
}

/* Anything linked to from a table of contents has to clear the header too. The terms page
   has sixteen of these and every one of them landed behind the bar. */
:target,
[id] { scroll-margin-top: calc(var(--header-h) + var(--s-3)); }

body {
  margin: 0;
  background-color: var(--bg);
  /* Grain, at the threshold of visible. Flat colour on a large area reads as a rendering
     surface rather than as paper; this is the tooth of the cotton. It is a background
     layer rather than a fixed overlay element on purpose -- an overlay with a blend mode
     sits above the Razorpay modal and tints it. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.035'/%3E%3C/svg%3E");
  color: var(--text);
  font-family: var(--body);
  font-size: var(--t-md);
  line-height: var(--lh-body);
  font-synthesis-weight: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  min-height: 100dvh;
}

h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 600;
  text-wrap: balance;
  margin: 0;
}
h1 { font-size: var(--t-4xl); line-height: var(--lh-tight); letter-spacing: var(--tr-tight); }
h2 { font-size: var(--t-2xl); line-height: var(--lh-snug); letter-spacing: var(--tr-snug); }
h3 { font-size: var(--t-xl); line-height: var(--lh-snug); letter-spacing: var(--tr-snug); }
h4 { font-size: var(--t-lg); line-height: var(--lh-snug); }

p { margin: 0; text-wrap: pretty; }
p + p { margin-block-start: var(--s-4); }

/* Body copy stops at a measure. A 72rem page with 72rem paragraphs in it is a page nobody
   can find the start of the next line on. */
main p, main li { max-width: var(--measure); }
main p.wide, main li.wide { max-width: none; }

a { color: var(--primary-ink); text-underline-offset: 0.18em; text-decoration-thickness: from-font; }
a:hover { color: var(--primary-hover); }

strong, b { font-weight: 600; }
code { font-family: var(--mono); font-size: 0.9em; }
img, svg, video { max-width: 100%; height: auto; }

/*
 * One focus ring for the whole site, and it is never the browser's. :focus-visible so a
 * mouse click on a button does not leave a ring behind it, and an offset so the ring sits
 * outside the element rather than eating its first pixel of border.
 */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-1);
}
/*
 * The one exception, and it needs stating because "outline: none" is otherwise a bug.
 * <main tabindex="-1"> exists so the skip link can move FOCUS rather than only the viewport;
 * it is a landmark, not a control, and ringing the entire page in marigold when somebody
 * uses the skip link is noise, not feedback. The feedback is the page scrolling to the top
 * of the content and the next Tab landing inside it. It is not reachable by Tab at all
 * (tabindex is -1), so no keyboard user can arrive here without having asked to.
 */
main:focus, main:focus-visible { outline: none; }

/* Any control whose entire interaction is "click to choose" says so on hover. Not text
   inputs: their cursor is the caret, and a hand over a field you type into says the wrong
   thing about what will happen. */
select, summary, button, input[type="checkbox"], input[type="radio"], input[type="file"] { cursor: pointer; }
select:disabled, button:disabled, input:disabled { cursor: not-allowed; }

/*
 * Touch. Two declarations that only ever matter on a phone, and both of them are invisible
 * on the desk where this was built:
 *
 *   touch-action: manipulation drops the 300ms wait a mobile browser spends deciding whether
 *   a tap was the first half of a double-tap-to-zoom. Every button on this site felt a third
 *   of a second slow on a phone, and nothing in a desktop browser shows it.
 *
 *   The tap highlight is a translucent flash of the brand colour rather than the platform's
 *   default blue-grey box, which on a cotton ground reads as a rendering fault.
 *
 * Scoped to the controls, never to the whole page: manipulation on <body> would also kill
 * double-tap-to-zoom on the text, which is a real accessibility affordance.
 */
:where(a, button, .btn, summary, label, select, [role="button"]) {
  touch-action: manipulation;
  -webkit-tap-highlight-color: color-mix(in srgb, var(--primary) 18%, transparent);
}

::selection { background: var(--accent); color: var(--on-accent); }

/*
 * Motion is opt-in, not opt-out. Everything animated on this site is declared inside this
 * query, so the reduced-motion default needs no counter-rules and cannot be forgotten --
 * a rule written outside it simply never runs for anyone who asked for stillness.
 */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* First tab stop on every page. Hidden until focused, then a real, visible control -- not
   moved off-screen with a negative offset it can never come back from. */
.skip {
  position: absolute; inset-block-start: var(--s-2); inset-inline-start: var(--s-2);
  z-index: var(--z-skip);
  transform: translateY(-250%);
  background: var(--primary); color: var(--on-primary);
  padding: var(--s-2) var(--s-4); border-radius: var(--r-2);
  font-size: var(--t-base); font-weight: 600; text-decoration: none;
  box-shadow: var(--shadow-3);
}
.skip:focus { transform: none; color: var(--on-primary); }
@media (prefers-reduced-motion: no-preference) {
  .skip { transition: transform var(--d-2) var(--e-out); }
}

/* ==========================================================================
   3. LAYOUT
   ========================================================================== */

.page {
  max-width: var(--page);
  margin-inline: auto;
  /* Safe areas, so a landscape phone with a notch does not clip the first character. */
  padding-inline: max(var(--s-5), env(safe-area-inset-left), env(safe-area-inset-right));
}
.page--narrow > * { max-width: 52rem; }

/* Vertical rhythm between sections, optically rather than mathematically: the bottom of a
   section needs a little more than the top or the next heading reads as belonging to the
   block above it. */
.band { padding-block: var(--s-9) calc(var(--s-9) + var(--s-4)); }
.band--tight { padding-block: var(--s-7) var(--s-8); }
.band--sunk { background: var(--sunk); }
.band--surface { background: var(--surface); }

.stack { display: grid; align-content: start; gap: var(--s-4); }
.stack--tight { gap: var(--s-2); }
.stack--loose { gap: var(--s-5); }

/* A rule is a rule. Twice this was the loudest thing on a quiet page -- first a woven band
   of indigo and marigold, then a hairline with a coloured stub at one end that read as a
   rendering fault. The page has enough of a voice without decorating its edges. */
.selvedge { height: 1px; background: var(--border); border: 0; margin: 0; }

/* ==========================================================================
   4. COMPONENTS
   ========================================================================== */

/* -- header ---------------------------------------------------------------------------
   Sticky, because the only two things a visitor wants from this site -- what it costs and
   how to open one -- are in it, and a marketing page is long. Backdrop-blurred rather than
   opaque so the page reads as continuous underneath. */
header.site {
  position: sticky;
  inset-block-start: 0;
  z-index: var(--z-sticky);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-4);
  padding-block: var(--s-3);
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(12px) saturate(1.4);
  border-block-end: 1px solid transparent;
}
/* The border appears only once the page has moved under it, so a header at rest is not a
   box sitting on top of the hero. */
header.site.is-scrolled { border-block-end-color: var(--border); }

.brand {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--t-xl);
  letter-spacing: var(--tr-snug);
  text-decoration: none;
  color: var(--text);
  /* 44px of vertical target on a phone even though the glyphs are shorter. */
  display: inline-flex; align-items: center; min-height: 2.75rem;
}
.brand:hover { color: var(--text); }

header.site nav { display: flex; align-items: center; gap: var(--s-1); }
header.site nav a {
  color: var(--muted);
  text-decoration: none;
  font-size: var(--t-base);
  font-weight: 500;
  /* Hit target, not just a word: 24px minimum everywhere, 44px on a touch screen. */
  display: inline-flex; align-items: center;
  min-height: 2.75rem;
  padding-inline: var(--s-3);
  border-radius: var(--r-2);
}
header.site nav a:hover { color: var(--text); background: var(--surface); }
/* Where you are, shown. A nav that looks identical on all four pages is a nav that tells
   you nothing. */
header.site nav a[aria-current="page"] { color: var(--text); font-weight: 600; }
header.site nav a.cta {
  color: var(--on-primary);
  background: var(--primary);
  padding-inline: var(--s-4);
  box-shadow: var(--shadow-1);
}
header.site nav a.cta:hover { background: var(--primary-hover); color: var(--on-primary); }

@media (prefers-reduced-motion: no-preference) {
  header.site { transition: border-color var(--d-3) var(--e-out); }
  header.site nav a { transition: color var(--d-1) var(--e-out), background-color var(--d-1) var(--e-out); }
}

@media (max-width: 34rem) {
  header.site nav a:not(.cta) { padding-inline: var(--s-2); }
  header.site nav a[href="/blog/"] { display: none; }
}

/* -- buttons ---------------------------------------------------------------------------
   Three weights, not two. Every AI landing page is one filled button beside one outlined
   button on every single row; a third, quieter option means the page can offer a way on
   without a second thing shouting. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 2.875rem;
  padding: var(--s-3) var(--s-5);
  border: 1px solid transparent;
  border-radius: var(--r-2);
  background: var(--primary);
  color: var(--on-primary);
  font: inherit;
  font-size: var(--t-base);
  font-weight: 600;
  letter-spacing: 0.005em;
  text-decoration: none;
  box-shadow: var(--shadow-1);
}
.btn:hover { background: var(--primary-hover); color: var(--on-primary); }
.btn:active { box-shadow: none; }

.btn.ghost {
  background: transparent;
  color: var(--primary-ink);
  border-color: var(--border-strong);
  box-shadow: none;
}
.btn.ghost:hover { background: var(--surface); border-color: var(--primary); color: var(--primary-ink); }

.btn.quiet {
  background: transparent; border-color: transparent; box-shadow: none;
  color: var(--muted); font-weight: 500; padding-inline: var(--s-3);
}
.btn.quiet:hover { background: var(--surface); color: var(--text); }

.btn.small { min-height: 2.25rem; padding: var(--s-2) var(--s-3); font-size: var(--t-sm); }
button.btn { border: 1px solid transparent; cursor: pointer; font: inherit; font-size: var(--t-base); font-weight: 600; }
button.btn.ghost { border-color: var(--border-strong); }
.btn[disabled], button.btn[disabled] {
  background: var(--sunk); color: var(--faint); border-color: var(--border);
  cursor: not-allowed; box-shadow: none;
}
.btn[disabled]:hover, button.btn[disabled]:hover { background: var(--sunk); color: var(--faint); transform: none; box-shadow: none; }

@media (prefers-reduced-motion: no-preference) {
  .btn {
    transition: background-color var(--d-1) var(--e-out), border-color var(--d-1) var(--e-out),
                transform var(--d-1) var(--e-out), box-shadow var(--d-1) var(--e-out);
  }
  .btn:hover { transform: translateY(-1px); box-shadow: var(--shadow-2); }
  /* Pressed, and it stays pressed for as long as the finger is down. A button with a hover
     state and no active state feels like a picture of a button. */
  .btn:active { transform: translateY(1px); }
}

.actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3); margin: 0; }

/* -- text bits ------------------------------------------------------------------------- */
.eyebrow {
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
}
.eyebrow.ok { color: var(--ok); }
.lede { font-size: var(--t-lg); line-height: var(--lh-body); color: var(--muted); max-width: var(--measure); }
.note { color: var(--muted); font-size: var(--t-base); }
.num, .tabular { font-variant-numeric: tabular-nums; }
.num { text-align: right; white-space: nowrap; }
.was { color: var(--faint); text-decoration: line-through; margin-inline-end: var(--s-1); }

/* -- hero ------------------------------------------------------------------------------- */
.hero { padding-block: var(--s-8) var(--s-7); }
.hero h1 + .lede { margin-block-start: var(--s-4); }
.hero .eyebrow + h1 { margin-block-start: var(--s-3); }

/*
 * The home hero. Deliberately not the 50/50 split every one of these pages has: the type
 * gets less than half and the shopfront gets more, because the shopfront IS the argument
 * and the sentence beside it is a caption. Below 62rem it stacks and the words come first.
 */
.hero.split {
  display: grid;
  gap: var(--s-7) var(--s-8);
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  align-items: center;
  padding-block: var(--s-8) var(--s-9);
}
.hero-say { display: grid; align-content: start; gap: var(--s-4); }
.hero-say .actions { margin-block-start: var(--s-2); }

/* -- the shopfront: the product, drawn --------------------------------------------------
   Built from gradients and type so it ships with the page: no image to license, nothing to
   load, and it takes the brand tokens, so it is never a screenshot that has drifted from
   the product. Lifted and rotated a hair -- a browser window pasted square onto a page
   reads as a screenshot; one that sits at an angle reads as an object. */
.shopfront {
  border: 1px solid var(--border);
  border-radius: var(--r-4);
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--shadow-3), var(--ring);
}
@media (min-width: 62rem) and (prefers-reduced-motion: no-preference) {
  .shopfront { transform: perspective(1400px) rotateY(-3deg) rotateX(1.2deg); transition: transform var(--d-3) var(--e-out); }
  .shopfront:hover { transform: perspective(1400px) rotateY(0deg) rotateX(0deg); }
}
.sf-chrome {
  display: flex; align-items: center; gap: var(--s-1);
  padding: var(--s-2) var(--s-3);
  background: var(--sunk);
  border-block-end: 1px solid var(--border);
}
.sf-chrome span { width: 0.5rem; height: 0.5rem; border-radius: var(--r-full); background: var(--border-strong); }
.sf-chrome em {
  margin-inline-start: var(--s-2); font-style: normal;
  font-family: var(--mono); font-size: var(--t-xs); color: var(--muted);
}
.sf-body { padding: var(--s-4); display: grid; gap: var(--s-4); }
.sf-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s-4); }
.sf-head strong { font-family: var(--display); font-size: var(--t-lg); font-weight: 600; }
.sf-nav { font-size: var(--t-xs); color: var(--muted); }
.sf-nav b {
  background: var(--primary); color: var(--on-primary);
  border-radius: var(--r-full); padding: 0.05rem 0.4rem;
  font-size: 0.6875rem; margin-inline-start: var(--s-1);
}
.sf-tiles { display: grid; gap: var(--s-2); grid-template-columns: repeat(3, 1fr); }
.sf-tile { margin: 0; display: grid; gap: var(--s-2); }
.sf-tile figcaption {
  font-size: var(--t-xs); color: var(--muted); line-height: 1.4;
  display: grid; gap: 0.1rem;
}
.sf-tile figcaption b { color: var(--text); font-size: var(--t-sm); font-variant-numeric: tabular-nums; }
.sf-pay {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1);
  border-block-start: 1px solid var(--border); padding-block-start: var(--s-3);
}
.chip {
  font-size: var(--t-xs); border: 1px solid var(--border); border-radius: var(--r-1);
  padding: 0.15rem 0.4rem; color: var(--muted); background: var(--bg);
}
.sf-pay em { font-style: normal; font-size: var(--t-xs); color: var(--ok); margin-inline-start: auto; }

/* -- goods -------------------------------------------------------------------------------
   Third attempt, and the first two are worth recording because they were both wrong in the
   same direction. Edge-to-edge woven texture was a swatch of cloth nobody could name. Line
   art on a pastel square was clipart.

   A drawing was never going to carry a shopfront; that is what photography is for, and
   there is none to license here. So the tile stops pretending to be a photograph and
   becomes what a lookbook does instead: a deep field of the colour the piece actually is,
   lit from one side, with the name set over it. Art direction rather than a failed icon. */
.good {
  position: relative;
  aspect-ratio: 3 / 4;
  border-radius: var(--r-1);
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  padding: var(--s-2);
  color: #fff;
  background-image:
    radial-gradient(120% 90% at 22% 12%, rgb(255 255 255 / 0.30), transparent 58%),
    linear-gradient(103deg, transparent 38%, rgb(255 255 255 / 0.10) 47%, transparent 56%),
    linear-gradient(160deg, var(--dye-1), var(--dye-2));
}
/* The photograph, when there is one. It sits over the field rather than replacing it, so a
   missing file degrades to the design already on the page instead of to a broken-image
   icon -- and onerror in the markup takes the element away entirely if the fetch fails. */
.good img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* The floor the piece stands on, so type never sits on the brightest part of the field. */
.good::after {
  content: ""; position: absolute; inset-inline: 0; inset-block-end: 0; height: 55%;
  background: linear-gradient(transparent, rgb(10 12 20 / 0.6));
}
.good b {
  position: relative; z-index: var(--z-raised);
  font-family: var(--display); font-weight: 600;
  font-size: var(--t-sm); line-height: 1.15; letter-spacing: var(--tr-snug);
  text-shadow: 0 1px 6px rgb(10 12 20 / 0.5);
}
.good-saree    { --dye-1: #4b5aa8; --dye-2: #1a1f45; }
.good-jewelry  { --dye-1: #6f5a2c; --dye-2: #241a0d; }
.good-kids     { --dye-1: #cf7189; --dye-2: #5c2138; }
.good-clothing { --dye-1: #5d6b82; --dye-2: #232b39; }

/* -- section headings -------------------------------------------------------------------- */
.say { display: grid; align-content: start; gap: var(--s-3); }
.say h2 + p { color: var(--muted); }
.say--center { justify-items: center; text-align: center; }
.say--center p { margin-inline: auto; }

/* -- the four claims ----------------------------------------------------------------------
   Each claim next to the thing it is talking about. Four identical two-column rows in a
   row is the shape this page had, and by the third one a reader has stopped looking at the
   right-hand column at all -- so the rhythm changes: split, band, filmstrip, single
   centred document. Same argument, four different shapes. */
.proofs { display: grid; gap: var(--s-10); }
.proof {
  display: grid;
  gap: var(--s-6) var(--s-8);
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
}
.proof-say { display: grid; align-content: start; gap: var(--s-3); }
.proof-say p { color: var(--muted); }
/* Alternating sides by source order, so the DOM still reads claim-then-evidence on a phone. */
.proof.flip .proof-say { order: 2; }
.proof-art { display: flex; min-width: 0; }
.proof:not(.flip) .proof-art { justify-content: flex-end; }

/* The evidence panels. Documents, not cards: a receipt, a spec sheet, an invoice. Tighter
   radius than a card and a hairline top-to-bottom, so they read as printed things. */
.art {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-3);
  padding: var(--s-5);
  font-size: var(--t-base);
  display: grid;
  gap: var(--s-2);
  width: min(100%, 30rem);
  box-shadow: var(--shadow-2), var(--ring);
}
.art-line { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s-4); margin: 0; max-width: none; }
.art-line b { font-variant-numeric: tabular-nums; font-weight: 600; }
.art-line span { color: var(--muted); }
.art-line.sub { font-size: var(--t-sm); }
.art-line.big { font-size: var(--t-lg); }
.art-line.big b { font-family: var(--display); font-size: var(--t-xl); }
.art-line b.ok { color: var(--ok); font-weight: 600; }
.art-rule { height: 1px; background: var(--border); margin-block: var(--s-1); }
.art-foot { margin: var(--s-1) 0 0; font-size: var(--t-sm); color: var(--muted); max-width: none; }
.art-title { font-family: var(--display); font-weight: 600; color: var(--text); }
.art-spec dl { display: grid; grid-template-columns: auto 1fr; gap: var(--s-2) var(--s-5); margin: var(--s-2) 0 0; }
.art-spec dt { color: var(--muted); font-size: var(--t-sm); }
.art-spec dd { margin: 0; }
.art-studio .frames { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-2); margin-block-end: var(--s-3); }
.art-studio .good { aspect-ratio: 1; }

/* The payments claim, as a band rather than a card: the money moving is a horizontal fact,
   so it is drawn horizontally and given the full width to move across. */
.flow {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-4);
  margin-block-start: var(--s-6);
  counter-reset: flow;
}
.flow-step {
  display: grid; align-content: start; gap: var(--s-2);
  padding: var(--s-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-3);
  box-shadow: var(--shadow-1);
  position: relative;
}
.flow-step .eyebrow { color: var(--accent-ink); }
.flow-step p { margin: 0; color: var(--muted); font-size: var(--t-base); max-width: none; }
.flow-step b { font-family: var(--display); font-size: var(--t-xl); font-weight: 600; font-variant-numeric: tabular-nums; }
/* The arrow between steps, drawn once in CSS rather than three times in markup. Hidden when
   the steps stack, because a rightward arrow pointing at the step below it is a lie. */
@media (min-width: 52rem) {
  .flow-step + .flow-step::before {
    content: ""; position: absolute;
    inset-inline-start: calc(var(--s-4) * -1 - 1px); inset-block-start: 50%;
    width: var(--s-4); height: 1px; background: var(--border-strong);
  }
}

/* The studio claim, as a filmstrip: six frames wide, scrolling on a phone, so it reads as
   a contact sheet rather than as three examples in a box. */
/*
 * The contact sheet WRAPS; it does not scroll sideways.
 *
 * It used to be a single row in an `overflow-x: auto` track, which fits at 1440px and
 * overflows below about 980px -- and on Windows, where scrollbars are not overlays, that
 * drew a permanent grey bar across the middle of the page under the photographs. A
 * horizontal scrollbar in the middle of a marketing page reads as a rendering fault, and
 * it hid two of the six categories behind an interaction nobody performs.
 *
 * Wrapping shows all six at every width, which matters more here than the filmstrip
 * gesture did: the whole point of the sheet is "we shoot what you sell", and that argument
 * fails if two of the categories are off-screen.
 */
.strip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s-3);
  margin-block-start: var(--s-6);
}
.strip .good { aspect-ratio: 4 / 5; }
/* Four tiles (footwear came off on 5 Oct 2026; it is closed on the platform): four across, and
   two by two below that. Five needed a stretched first tile to fill a narrower grid; four do not. */
@media (max-width: 62rem) { .strip { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* The price of the sheet above it, read as a caption to the sheet rather than as a card
   orphaned underneath one: a rule, a line, and the number set in the display face because
   the number is the point of the sentence. */
.quote-line {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-2) var(--s-4);
  margin-block-start: var(--s-4); padding-block-start: var(--s-4);
  border-block-start: 1px solid var(--border);
  max-width: none;
}
.quote-line span { color: var(--muted); font-size: var(--t-base); }
.quote-line b {
  font-family: var(--display); font-size: var(--t-xl); font-weight: 600;
  font-variant-numeric: tabular-nums; letter-spacing: var(--tr-snug);
}
.quote-line em { font-style: normal; color: var(--muted); font-size: var(--t-base); margin-inline-start: auto; }
@media (max-width: 40rem) { .quote-line em { margin-inline-start: 0; flex-basis: 100%; } }

/* -- the reel -----------------------------------------------------------------------------
   A short muted loop of one piece turning. Video rather than an animated GIF, because a GIF
   of this would be several times the size for worse colour; `playsinline` so iOS does not
   take it fullscreen; `preload="metadata"` so it costs a few hundred bytes until it is
   actually on screen. The poster is a real frame, so the box is never empty and never
   changes size when the video arrives. */
.reel {
  margin: var(--s-6) 0 0;
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: var(--s-3) var(--s-6);
  align-items: center;
}
/* The source is 848x480. Given the whole page width it was being blown up to 1100px and
   losing every bit of the detail the section is meant to be showing off, as well as making
   a two-second loop the largest thing on the page. The column caps it just under its native
   width, and the caption takes the space that frees up. */
@media (max-width: 52rem) {
  .reel { grid-template-columns: 1fr; }
}
.reel video {
  inline-size: 100%;
  block-size: auto;
  display: block;
  border-radius: var(--r-3);
  border: 1px solid var(--border);
  background: var(--sunk);
  box-shadow: var(--shadow-2);
}
.reel figcaption { color: var(--muted); font-size: var(--t-base); max-width: none; }
.reel figcaption b { color: var(--text); font-weight: 600; }
/* Saying it out loud costs nothing and buys something: on a page selling AI photography, a
   generated model that announces itself is a demonstration, and one that does not is a
   liability. It also shows the merchant the choice they will be given. */
.reel .disclosure {
  display: block;
  margin-block-start: var(--s-3);
  padding-inline-start: var(--s-3);
  border-inline-start: 2px solid var(--border-strong);
  font-size: var(--t-sm);
  color: var(--faint);
}

/* "and more" in the eyebrow, and the line under the contact sheet. Both exist to stop the
   six named categories being read as the whole list -- they are examples. */
.eyebrow .more { color: var(--accent-ink); }
.sheet-note { margin-block-start: var(--s-4); max-width: 46rem; }

/* The tax claim gets the opposite treatment: one document, centred, alone, with more air
   around it than anything else on the page. */
.solo { display: grid; justify-items: center; gap: var(--s-6); }
.solo .art { width: min(100%, 26rem); }

/* -- how it goes ---------------------------------------------------------------------------
   A timeline, because the objection this answers is "how long does this take me", and the
   answer is a sequence. Numbered by counter so the markup stays an ordered list. */
.track { list-style: none; margin: var(--s-6) 0 0; padding: 0; display: grid; gap: 0; counter-reset: track; }
.track li {
  display: grid;
  grid-template-columns: 2.5rem 1fr;
  gap: var(--s-4);
  padding-block: var(--s-4);
  max-width: none;
  border-block-start: 1px solid var(--border);
}
.track li:first-child { border-block-start: 0; }
.track li::before {
  counter-increment: track;
  content: counter(track);
  display: grid; place-items: center;
  width: 2.5rem; height: 2.5rem;
  border-radius: var(--r-full);
  border: 1px solid var(--border);
  background: var(--surface);
  font-family: var(--display); font-weight: 600; font-size: var(--t-base);
  color: var(--primary-ink);
  font-variant-numeric: tabular-nums;
}
.track h3 { font-size: var(--t-lg); }
.track p { margin: var(--s-1) 0 0; color: var(--muted); font-size: var(--t-base); }
.track .when { color: var(--accent-ink); font-size: var(--t-sm); font-weight: 600; }

/* -- cards ---------------------------------------------------------------------------------
   Elevation only where elevation means something. A card that is a border plus a shadow
   plus a white fill on a page where nothing is beneath it is three signals for no message. */
.grid { display: grid; gap: var(--s-4); grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-3); padding: var(--s-5);
  display: grid; align-content: start; gap: var(--s-2);
}
.card h3 { margin: 0; }
.card p { margin: 0; color: var(--muted); max-width: none; }

/* -- closing --------------------------------------------------------------------------------
   The one place on the page that changes ground colour, and it changes to the recessed tone
   of the same palette rather than jumping to a dark slab. */
.closing { background: var(--sunk); border-block-start: 1px solid var(--border); }
.closing-in { padding-block: var(--s-9); display: grid; justify-items: center; text-align: center; gap: var(--s-4); }
.closing-in .lede { margin-inline: auto; }
.closing-in .actions { justify-content: center; margin-block-start: var(--s-2); }

/* -- footer ---------------------------------------------------------------------------------- */
footer.site {
  border-block-start: 1px solid var(--border);
  color: var(--muted);
  font-size: var(--t-base);
  padding-block: var(--s-6);
  margin-block-start: var(--s-8);
}
footer.site p { max-width: none; }
.closing + footer.site { margin-block-start: 0; border-block-start: 0; }

/* -- tables ------------------------------------------------------------------------------------
   A table on a phone is the one thing on this site that may scroll sideways, and it does it
   inside its own box rather than taking the page with it. */
.scroller { overflow-x: auto; overscroll-behavior-x: contain; }
table { width: 100%; border-collapse: collapse; margin-block: var(--s-4); font-size: var(--t-base); }
th, td { text-align: left; padding: var(--s-3) var(--s-2); border-block-end: 1px solid var(--border); vertical-align: top; }
th { font-weight: 600; color: var(--muted); font-size: var(--t-sm); letter-spacing: 0.02em; }
tbody tr:last-child td { border-block-end: 0; }

/* -- forms -----------------------------------------------------------------------------------
   16px minimum on every field. Below that, iOS Safari zooms the page on focus and the
   merchant loses their place in the form. */
form.signup { display: grid; gap: var(--s-4); max-width: 30rem; }
form.signup label, .door-field { display: grid; gap: var(--s-2); font-size: var(--t-base); font-weight: 500; }
/* `.door-field` is the signup page's SECOND sign-in door, which is not inside `form.signup` --
   it sits on step 1, above the form, and there is nothing to submit. It is added to these
   selector lists rather than given rules of its own because a field that looks almost like the
   ones two steps later is worse than one that looks nothing like them: the first reads as a
   rendering fault. `.sheet-in` is here for the same reason and was the precedent. */
/* Radios and checkboxes are excluded, and it is not a nicety: this block gives a field
   width:100%, a 2.875rem min-height, padding and a border, and a radio that inherits it renders
   as a circle the width of the form with its label pushed underneath. A tick box is not a thing
   you type into, and none of these rules were ever meant for one. */
form.signup input:not([type="radio"], [type="checkbox"]), form.signup select, form.signup textarea,
.sheet-in input, .sheet-in select, .door-field input {
  width: 100%;
  min-height: 2.875rem;
  padding: var(--s-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-2);
  font: inherit;
  font-size: max(1rem, var(--t-md));
  font-weight: 400;
  background: var(--surface);
  color: var(--text);
}
/* Windows renders a native select with the system's own colours unless both are stated,
   which in dark mode is black text on a black field. */
form.signup select, .sheet-in select { background-color: var(--surface); color: var(--text); }
form.signup input::placeholder, .door-field input::placeholder { color: var(--faint); }
form.signup input:not([type="radio"], [type="checkbox"]):hover, form.signup select:hover,
.sheet-in input:hover, .sheet-in select:hover, .door-field input:hover { border-color: var(--primary); }
@media (prefers-reduced-motion: no-preference) {
  form.signup input:not([type="radio"], [type="checkbox"]), form.signup select,
  .sheet-in input, .sheet-in select, .door-field input { transition: border-color var(--d-1) var(--e-out); }
}


/* -- the registered address, answered in a sheet ----------------------------------------------
   Five address fields inline would push the price and the button below the fold on a form whose
   own question is "what shop are you opening". It is answered once, so it collapses to the
   answer: the lines as a label, and a button that now says Change. */
.field-block { display: grid; gap: var(--s-2); }
.field-block .field-title { font-size: var(--t-base); font-weight: 500; }
.field-block button { justify-self: start; }

/* The saved address, printed the way the invoice will print it. `pre-line` because the lines
   ARE the address -- collapsing them would show a merchant something other than what we hold. */
.address-label {
  white-space: pre-line;
  padding: var(--s-3);
  border: 1px solid var(--border);
  border-radius: var(--r-2);
  background: var(--surface-2);
  font-size: var(--t-md);
}

/* A <dialog>, like the menu drawer: the element brings the focus trap, Escape, inertness of the
   page behind it and a ::backdrop. Centred rather than slid in, because this one is a form to
   fill in rather than a place to go. */
.sheet {
  border: 0;
  padding: 0;
  inline-size: min(30rem, calc(100vw - 2rem));
  max-block-size: min(90dvh, 44rem);
  border-radius: var(--r-3);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-3);
}
.sheet::backdrop { background: rgb(10 12 20 / 0.45); backdrop-filter: blur(2px); }

/* Grid only while open, so the closed dialog stays hidden. Without this the inner panel has no
   height to scroll against -- the max-block-size above would be ignored and a long form on a
   short screen would run off the bottom of the sheet rather than scrolling inside it. */
.sheet[open] { display: grid; }
.sheet-in { display: grid; align-content: start; gap: var(--s-3); padding: var(--s-5); overflow: auto; }
.sheet-in h2 { margin: 0; font-size: var(--t-lg); }
.sheet-in label { display: grid; gap: var(--s-2); font-size: var(--t-base); font-weight: 500; }
.sheet-in .inline-2 { display: flex; gap: var(--s-3); flex-wrap: wrap; }

/* The postcode and the town belong on one line: they are read together and written together,
   and stacked they look like two unrelated questions. One column below 26rem, where two would
   leave a PIN code box four characters wide. */
.addr-pair { display: grid; gap: var(--s-3); grid-template-columns: 1fr; }
@media (min-width: 26rem) {
  .addr-pair { grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); }
}

/* ==========================================================================
   5. PAGE-SPECIFIC
   ========================================================================== */

/* -- pricing ----------------------------------------------------------------------------------
   Two cards, and the recommended one is raised and tinted rather than merely taller. Stretch,
   not start: two cards of different heights read as one being unfinished. */
.plans {
  display: grid; gap: var(--s-5);
  /* Four tiers side by side on a desktop (Ultra joined on 1 Oct 2026): at 20rem the fourth wrapped onto a row of its
     own, away from the plans it is compared with. */
  grid-template-columns: repeat(auto-fit, minmax(15.5rem, 1fr));
  align-items: stretch;
  padding-block: var(--s-4) var(--s-7);
}
.plan-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-4); padding: var(--s-6);
  display: flex; flex-direction: column; gap: var(--s-2);
  box-shadow: var(--shadow-1);
}
.plan-card.featured {
  border-color: var(--primary);
  box-shadow: var(--shadow-3), 0 0 0 1px var(--primary);
  background: linear-gradient(var(--surface), var(--surface)) padding-box,
              linear-gradient(160deg, var(--primary-soft), var(--surface)) border-box;
}
.plan-card .big {
  font-family: var(--display); font-size: var(--t-4xl); line-height: 1;
  letter-spacing: var(--tr-tight); margin: var(--s-2) 0 0;
  font-variant-numeric: tabular-nums;
}
.plan-card .note { margin: 0; }
.plan-card .btn { margin-block-start: var(--s-3); justify-self: start; }
.plan-card .pitch { line-height: var(--lh-body); }
/* Pushed to the bottom, so the small print of both cards sits on one line across the row
   however different the lists above it are. */
.plan-card .excl {
  border-block-start: 1px solid var(--border);
  padding-block-start: var(--s-3);
  margin-block-start: auto;
  line-height: var(--lh-body);
}
/* The asterisk on an AI feature, and the one note under the plans that it points to (8 Oct 2026). */
.plan-has .fn { color: var(--primary-ink); font-weight: 700; margin-inline-start: 0.1em; }
.plans-footnote { grid-column: 1 / -1; margin: 0; line-height: var(--lh-body); max-width: 60rem; }
/* Custom is not a tier, so it is not a column: a strip across the foot of the row, read after the plans. */
.plan-card.custom { grid-column: 1 / -1; }
@media (min-width: 48rem) {
  .plan-card.custom { display: grid; grid-template-columns: auto 1fr; column-gap: var(--s-6); align-items: baseline; }
  .plan-card.custom .eyebrow { grid-column: 1 / -1; }
}

/* "Domain handling is not a domain" (1 Oct 2026): boxed in the marigold, so it reads as the one thing on the card to
   stop at, and pushed to the bottom like the small print beside it. */
.plan-card .domain-note {
  margin-block-start: auto; padding: var(--s-3); border-radius: var(--r-2, 0.5rem); line-height: var(--lh-body);
  border: 1px solid var(--accent); background: var(--accent-soft); color: var(--text, inherit);
}
.plan-has { list-style: none; margin: var(--s-3) 0 0; padding: 0; display: grid; gap: var(--s-3); font-size: var(--t-base); }
.plan-has li { padding-inline-start: var(--s-6); position: relative; line-height: var(--lh-body); max-width: none; }
/* A tick per line, drawn rather than typed, so it cannot arrive as an emoji on one platform
   and a missing-glyph box on another. */
.plan-has li::before {
  content: ""; position: absolute;
  inset-inline-start: 0.15rem; inset-block-start: 0.5em;
  width: 0.5rem; height: 0.28rem;
  border-inline-start: 2px solid var(--primary-ink);
  border-block-end: 2px solid var(--primary-ink);
  transform: rotate(-45deg);
}

/* Monthly or yearly. A segmented pair rather than a switch: a switch implies one of them is
   "on" and the other is the absence of it, and neither of these is the absence of anything. */
.cadence {
  display: inline-flex; gap: 0; margin: 0 0 var(--s-1); padding: var(--s-1);
  border: 1px solid var(--border); border-radius: var(--r-full); background: var(--bg);
  justify-self: start;
}
.cadence label {
  display: inline-flex; align-items: center; gap: var(--s-2);
  min-height: 2.25rem; padding: var(--s-1) var(--s-4);
  border-radius: var(--r-full); font-size: var(--t-base); cursor: pointer; white-space: nowrap;
}
.cadence input { position: absolute; opacity: 0; pointer-events: none; }
.cadence label:has(input:checked) { background: var(--surface); box-shadow: var(--shadow-1); font-weight: 600; }
.cadence label:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.cadence .save { font-size: var(--t-xs); letter-spacing: 0.04em; text-transform: uppercase; color: var(--accent-ink); font-weight: 700; }
.cadence b { font-variant-numeric: tabular-nums; font-weight: 600; }
@media (prefers-reduced-motion: no-preference) {
  .cadence label { transition: background-color var(--d-1) var(--e-out); }
}

/* The same control inside the signup form.
   `form.signup label` is display:grid and outweighs `.cadence label` on specificity, so the
   segmented switch silently came apart there: each radio stacked above its own text, and with
   the field rules above still applying to the input it did so as a circle the width of the form.
   Stated explicitly rather than by lowering the form's specificity, because the form's own
   fields do want that grid. */
form.signup .cadence { display: inline-flex; }
form.signup .cadence label {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-size: var(--t-base); font-weight: 400;
}
form.signup .cadence label:has(input:checked) { font-weight: 600; }

/* Three options with a price on each do not fit one line on a phone, and a segmented control
   that overflows its own border is worse than one that wraps. */
.cadence.wrap { display: flex; flex-wrap: wrap; }
form.signup .cadence.wrap { display: flex; flex-wrap: wrap; }

.terms { padding-block: var(--s-4) var(--s-7); }
.terms h2 { font-family: var(--display); }
.terms ul { display: grid; gap: var(--s-3); padding-inline-start: var(--s-5); max-width: 48rem; line-height: var(--lh-body); }
.terms ul li { max-width: none; }
.book { margin-block-start: var(--s-6); border-block-start: 1px solid var(--border); padding-block-start: var(--s-4); }
.book summary {
  cursor: pointer; color: var(--muted); font-size: var(--t-base);
  min-height: 2.75rem; display: flex; align-items: center; gap: var(--s-3);
  list-style: none;
}
.book summary::-webkit-details-marker { display: none; }
.book summary::before {
  content: ""; flex: none;
  width: 0.5rem; height: 0.5rem;
  border-inline-end: 2px solid currentColor; border-block-end: 2px solid currentColor;
  transform: rotate(-45deg); transform-origin: 60% 60%;
}
.book[open] summary::before { transform: rotate(45deg); }
.book summary:hover { color: var(--text); }
@media (prefers-reduced-motion: no-preference) {
  .book summary::before { transition: transform var(--d-2) var(--e-out); }
}
.book table { margin-block-start: var(--s-4); }

/* The struck-out first-year price and the "was" price both carry a colour token now, so the
   renewal callout follows dark mode instead of staying cream on a dark page. */
.renewal {
  background: var(--surface); border-inline-start: 3px solid var(--accent);
  padding: var(--s-4) var(--s-5); border-radius: var(--r-2); margin-block: var(--s-5);
}

/* -- terms ------------------------------------------------------------------------------------
   Callouts share one shape across the site: a surface panel with a coloured spine. Three
   different hand-rolled versions of this existed, one of which was hard-coded cream. */
.callout {
  background: var(--surface); border: 1px solid var(--border);
  border-inline-start: 3px solid var(--accent);
  padding: var(--s-4) var(--s-5); border-radius: var(--r-2);
  margin-block: var(--s-5); font-size: var(--t-base);
  display: grid; gap: var(--s-2);
}
.callout--warn { background: var(--warn-soft); border-color: var(--border); border-inline-start-color: var(--warn-ink); }
.callout p { max-width: none; margin: 0; }

/* -- journal ------------------------------------------------------------------------------------
   An index of posts, and -- for now, and this is the state that actually ships -- the empty
   one. "No posts yet." on a bare page is a dead end on a route somebody chose to visit. */
.empty {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-4);
  padding: var(--s-7) var(--s-6);
  display: grid; gap: var(--s-3);
  margin-block: var(--s-4) var(--s-7);
  box-shadow: var(--shadow-1);
}
.empty h2 { font-size: var(--t-xl); }
.empty p { color: var(--muted); }
.empty .actions { margin-block-start: var(--s-3); }

.posts { list-style: none; margin: var(--s-6) 0 var(--s-8); padding: 0; display: grid; gap: 0; }
.posts li { border-block-start: 1px solid var(--border); padding-block: var(--s-5); max-width: none; }
.posts li:first-child { border-block-start: 0; }
.posts a { display: grid; gap: var(--s-2); text-decoration: none; color: inherit; }
.posts a:hover h2 { color: var(--primary-ink); }
.posts h2 { font-size: var(--t-xl); }
.posts p { color: var(--muted); max-width: var(--measure); margin: 0; }
.posts .meta-line { margin-block-start: var(--s-2); color: var(--faint); font-size: var(--t-sm); font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: no-preference) {
  .posts a h2 { transition: color var(--d-1) var(--e-out); }
}

/* -- the legal document ---------------------------------------------------------------------------
   Read in one column and referred to by clause number, so the numbers have to be visible and
   stable. Nested counters rather than <ol> defaults, because "8.3" is what somebody quotes
   back at you and "c." is not.

   These lived in a <style> block inside terms.html, where .draft hard-coded #fff4e5 on
   #b26a00 -- the one panel on the site that stayed cream when the page went dark. */
.legal { max-width: 46rem; counter-reset: clause; }
.legal h2 { margin-block: var(--s-7) var(--s-3); font-size: var(--t-lg); scroll-margin-top: var(--s-8); }
.legal h2::before { counter-increment: clause; content: counter(clause) ". "; color: var(--muted); font-variant-numeric: tabular-nums; }
.legal section { counter-reset: sub; }
.legal section > p { position: relative; padding-inline-start: var(--s-7); margin-block: var(--s-4); max-width: 44rem; }
.legal section > p::before {
  counter-increment: sub; content: counter(clause) "." counter(sub);
  position: absolute; inset-inline-start: 0; color: var(--muted);
  font-variant-numeric: tabular-nums; font-size: var(--t-sm);
}
.legal .plain {
  padding-inline-start: var(--s-7); color: var(--muted);
  font-size: var(--t-base); margin-block: calc(var(--s-2) * -1) var(--s-4);
}
.legal .plain::before { content: none; }
/* legal-counsel's exports carry their own clause numbers in the text ("10. Your rights", "4.7 One trial ..."), and
   cross-references in the Terms point at those numbers, so the counters above would print every number twice and
   number the Schedules as clauses. A page that says it is self-numbered gets no counter at all (7 Oct 2026). */
.legal.self-numbered h2::before,
.legal.self-numbered section > p::before { content: none; counter-increment: none; }
.legal.self-numbered section > p { padding-inline-start: 0; }
.legal.self-numbered section > ul { padding-inline-start: var(--s-5); }
/* A list inside a clause belongs to the clause above it: indented under its text, never numbered on its own. */
.legal section > ul { padding-inline-start: calc(var(--s-7) + var(--s-5)); margin-block: calc(var(--s-2) * -1) var(--s-4); max-width: 44rem; display: grid; gap: var(--s-2); }
.legal .plain strong { color: var(--text); }

.meta {
  margin-block: var(--s-5);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-3); padding: var(--s-4) var(--s-5);
  display: grid; gap: var(--s-1); font-size: var(--t-base);
}
.meta dl { display: grid; grid-template-columns: auto 1fr; gap: var(--s-1) var(--s-4); margin: 0; }
.meta dt { color: var(--muted); }
.meta dd { margin: 0; }

/* Two columns of contents, and a hit target on each line rather than a bare word. */
.toc { columns: 2; column-gap: var(--s-6); padding: 0; list-style: none; font-size: var(--t-base); margin-block: var(--s-3) var(--s-7); }
.toc li { break-inside: avoid; margin-block: 0; max-width: none; }
.toc a { display: block; padding-block: var(--s-2); text-decoration: none; }
.toc a:hover { text-decoration: underline; }
@media (max-width: 40rem) { .toc { columns: 1; } }

.section-label { font-size: var(--t-base); font-weight: 600; color: var(--muted); margin-block: var(--s-6) var(--s-2); font-family: var(--body); letter-spacing: 0.02em; }

/* -- utility ------------------------------------------------------------------------------------ */
[hidden] { display: none !important; }
/* One step of the scale, pushed down. Exists so no page has to reach for an inline style to
   separate two blocks -- an inline style is the one declaration the token system cannot see. */
.pushed-lg { margin-block-start: var(--s-7); }

/* -- responsive ---------------------------------------------------------------------------------- */
@media (max-width: 62rem) {
  .hero.split { grid-template-columns: 1fr; }
}
@media (max-width: 52rem) {
  .proof { grid-template-columns: 1fr; }
  /* The flip only exists to alternate sides. Stacked, there are no sides, and honouring it
     would put the evidence above the claim it is evidence for. */
  .proof.flip .proof-say { order: 0; }
  .proof:not(.flip) .proof-art { justify-content: flex-start; }
  .flow { grid-template-columns: 1fr; }
}
@media (max-width: 34rem) {
  .sf-tiles { grid-template-columns: repeat(3, 1fr); gap: var(--s-1); }
  .track li { grid-template-columns: 2rem 1fr; gap: var(--s-3); }
  .track li::before { width: 2rem; height: 2rem; font-size: var(--t-sm); }
}

/* Ultra-wide. At 50% zoom on a large monitor the page must not become a ribbon of text
   floating in a field of cotton -- the container caps, and the hero stops stretching. */
@media (min-width: 110rem) {
  :root { --page: 78rem; }
}

/* Printing the terms is a real thing a merchant does before signing. */
@media print {
  header.site, footer.site, .skip, .closing { display: none; }
  body { background: #fff; color: #000; font-size: 11pt; }
  a { color: #000; text-decoration: underline; }
  .callout, .art, .card { break-inside: avoid; }
}

/* ==========================================================================
   6. MOTION
   ==========================================================================
   Everything animated on this site lives in this section, and almost every rule in it is
   nested under `.anim` -- a class an inline script in each <head> adds to <html> before
   first paint, and ONLY when the visitor has not asked for reduced motion.

   That gate is the whole design. It means:

     - a rule written here cannot run for someone who asked for stillness, because the class
       it depends on is never added for them. There is no counter-rule to forget.
     - nothing is hidden if the script never runs. `.reveal` starts invisible only inside
       `.anim`, so with JavaScript off, or an error thrown above it, the page is simply the
       static page rather than a blank one. That is the failure mode most reveal-on-scroll
       code gets wrong, and it fails to an empty screen.
     - there is no flash. The gate is inline in <head>, not in deferred site.js, so the
       starting state is set before anything is painted.
   ========================================================================== */

/* -- between pages ------------------------------------------------------------------------
   A cross-document view transition, so moving from the landing page to pricing is a
   dissolve rather than a white flash. It costs nothing where it is unsupported: the browser
   simply navigates. Named parts opt out of the crossfade and are matched across the two
   documents instead, so the header does not fade out and back in when it is the same
   header on both sides. */
@view-transition { navigation: auto; }

header.site { view-transition-name: site-header; }
footer.site { view-transition-name: site-footer; }

@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) { animation: vt-out 180ms var(--e-out) both; }
  ::view-transition-new(root) { animation: vt-in 320ms var(--e-out) 60ms both; }
}
@keyframes vt-out { to { opacity: 0; transform: translateY(-0.5rem); } }
@keyframes vt-in { from { opacity: 0; transform: translateY(0.75rem); } }

/* Someone who asked for stillness gets the navigation, not the dissolve. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* -- the reveal ---------------------------------------------------------------------------
   One mechanism for everything that arrives on scroll. `.reveal` is the resting hidden
   state; site.js adds `.in` when the element crosses into view and never takes it away
   again -- content that re-hides when you scroll back up is a party trick, not a design.

   The stagger is a custom property rather than nth-child, so a container can hand its
   children an index without this stylesheet knowing how many there are. */
.anim .reveal {
  opacity: 0;
  transform: translateY(1.5rem);
}
.anim .reveal.in {
  opacity: 1;
  transform: none;
  transition:
    opacity 700ms var(--e-out) calc(var(--i, 0) * 90ms),
    transform 700ms var(--e-out) calc(var(--i, 0) * 90ms);
}
/* Variants, for where a straight rise is the wrong gesture. */
.anim .reveal.from-right { transform: translateX(2.5rem); }
.anim .reveal.from-left { transform: translateX(-2.5rem); }
.anim .reveal.from-right.in,
.anim .reveal.from-left.in { transform: none; }
.anim .reveal.zoom { transform: scale(0.94); }
.anim .reveal.zoom.in { transform: none; }

/* -- the hero, on load ---------------------------------------------------------------------
   Pure CSS with `both` fill, so it runs the moment the stylesheet applies. No observer, no
   class toggle, nothing to wait for -- above-the-fold motion that waits for JavaScript
   arrives late enough to look like a fault. */
.anim .hero-say > * { animation: rise 800ms var(--e-out) both; }
.anim .hero-say > :nth-child(1) { animation-delay: 60ms; }
.anim .hero-say > :nth-child(2) { animation-delay: 0ms; }   /* the headline leads */
.anim .hero-say > :nth-child(3) { animation-delay: 220ms; }
.anim .hero-say > :nth-child(4) { animation-delay: 320ms; }
.anim .hero-say > :nth-child(5) { animation-delay: 400ms; }

@keyframes rise {
  from { opacity: 0; transform: translateY(1.25rem); }
  to { opacity: 1; transform: none; }
}

/* The headline, line by line, up from behind its own baseline. The mask is the .ln box;
   padding-block with an equal negative margin gives the ascenders and descenders of
   Fraunces somewhere to be without moving the line off its rhythm. */
h1 .ln { display: block; overflow: hidden; padding-block: 0.12em; margin-block: -0.12em; }
h1 .ln > span { display: block; }
.anim .hero h1 .ln > span { animation: line-up 900ms var(--e-out) both; }
.anim .hero h1 .ln:nth-child(1) > span { animation-delay: 90ms; }
.anim .hero h1 .ln:nth-child(2) > span { animation-delay: 190ms; }
@keyframes line-up { from { transform: translateY(105%); } to { transform: none; } }

/* The shopfront arrives from the side it already sits on, out of a slight turn. */
.anim .hero .shopfront { animation: shopfront-in 1100ms var(--e-out) 180ms both; }
@keyframes shopfront-in {
  from { opacity: 0; transform: perspective(1400px) translateX(3rem) rotateY(-9deg) rotateX(3deg); }
  to { opacity: 1; transform: perspective(1400px) rotateY(-3deg) rotateX(1.2deg); }
}
/* Stacked under the words on a narrow screen, it is no longer to one side of anything, so
   it rises like everything else instead of turning. */
@media (max-width: 62rem) {
  .anim .hero .shopfront { animation-name: rise; }
}

/* -- scroll-driven ---------------------------------------------------------------------------
   The two effects genuinely tied to scroll POSITION rather than to a threshold. These run
   off the main thread, which is why they are worth writing as CSS instead of as an observer
   with a scroll handler behind it. Guarded, because a browser without `animation-timeline`
   would otherwise apply the keyframe's `from` state and never leave it. */
@supports (animation-timeline: view()) {
  @media (min-width: 62.0625rem) {
    .anim .shopfront {
      animation: straighten linear both;
      animation-timeline: view();
      animation-range: entry 30% cover 55%;
    }
    @keyframes straighten {
      from { transform: perspective(1400px) rotateY(-3deg) rotateX(1.2deg); }
      to { transform: perspective(1400px) rotateY(0deg) rotateX(0deg); }
    }
  }

  /* The timeline draws itself as you read down it. */
  .anim .track::before {
    animation: draw linear both;
    animation-timeline: view();
    animation-range: entry 25% cover 65%;
  }
  @keyframes draw { from { scale: 1 0; } to { scale: 1 1; } }
}

/* The rule the numbers are threaded on. It sits behind the counters, which carry a surface
   fill of their own, so it reads as one line passing through them rather than between. */
.track { position: relative; }
.track::before {
  content: "";
  position: absolute;
  inset-block: 1.25rem;
  inset-inline-start: 1.25rem;
  inline-size: 2px;
  background: linear-gradient(var(--border), var(--border-strong), var(--border));
  transform-origin: top;
  z-index: 0;
}
.track li::before { position: relative; z-index: 1; }
@media (max-width: 34rem) {
  .track::before { inset-inline-start: 1rem; inset-block: 1rem; }
}

/* -- hover, and being touched ---------------------------------------------------------------
   Interactive states raise contrast rather than merely changing colour, and every one of
   them moves a compositor property only. */
@media (prefers-reduced-motion: no-preference) {
  .flow-step, .card, .empty {
    transition: transform var(--d-2) var(--e-out), box-shadow var(--d-2) var(--e-out),
                border-color var(--d-2) var(--e-out);
  }
  .flow-step:hover, .card:hover {
    transform: translateY(-3px);
    box-shadow: var(--shadow-3);
    border-color: var(--border-strong);
  }

  /* A slow push in on the photograph, inside its own frame. The frame does not move, so
     nothing around it reflows. */
  .good img { transition: transform 900ms var(--e-out); }
  .sf-tile:hover .good img,
  .strip .good:hover img { transform: scale(1.06); }

  /* The primary button catches the light as the pointer crosses it. One sweep, not a loop:
     a shine that repeats on its own is an advertisement for itself. */
  .btn { position: relative; overflow: hidden; isolation: isolate; }
  .btn::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: linear-gradient(105deg, transparent 38%, rgb(255 255 255 / 0.22) 50%, transparent 62%);
    translate: -130% 0;
  }
  .btn:hover::after { translate: 130% 0; transition: translate 700ms var(--e-out); }
  .btn.ghost::after, .btn.quiet::after { background: none; }

  /* The underline grows from the side the reader is coming from. */
  header.site nav a:not(.cta) { position: relative; }
  header.site nav a:not(.cta)::after {
    content: "";
    position: absolute;
    inset-inline: var(--s-3);
    inset-block-end: 0.55rem;
    block-size: 1px;
    background: currentColor;
    scale: 0 1;
    transform-origin: left;
    transition: scale var(--d-2) var(--e-out);
  }
  header.site nav a:not(.cta):hover::after,
  header.site nav a[aria-current="page"]::after { scale: 1 1; }
}

/* -- the slide-out ----------------------------------------------------------------------------
   A <dialog>, not a <div>. The element brings the focus trap, the Escape key, inertness of
   the page behind it and a ::backdrop with it; the same thing hand-rolled is about eighty
   lines of JavaScript and usually gets the focus return wrong.

   `@starting-style` with `transition-behavior: allow-discrete` is what lets it animate in
   AND out. Without the discrete transitions on `display` and `overlay`, `display: none`
   applies the instant it closes and the exit is never seen. */
.drawer {
  margin: 0 0 0 auto;
  border: 0;
  padding: 0;
  block-size: 100dvh;
  max-block-size: 100dvh;
  inline-size: min(21rem, 86vw);
  background: var(--surface);
  color: var(--text);
  border-inline-start: 1px solid var(--border);
  box-shadow: var(--shadow-3);
  overscroll-behavior: contain;
}
.drawer::backdrop { background: rgb(10 12 20 / 0.45); backdrop-filter: blur(2px); }

.drawer-in {
  display: grid;
  align-content: start;
  gap: var(--s-1);
  padding: var(--s-5) var(--s-5) max(var(--s-5), env(safe-area-inset-bottom));
  block-size: 100%;
}
.drawer-top { display: flex; justify-content: space-between; align-items: center; margin-block-end: var(--s-4); }
.drawer a:not(.btn) {
  display: block;
  padding-block: var(--s-3);
  font-family: var(--display);
  font-size: var(--t-xl);
  font-weight: 600;
  letter-spacing: var(--tr-snug);
  color: var(--text);
  text-decoration: none;
  border-block-end: 1px solid var(--border);
}
.drawer a:not(.btn):hover,
.drawer a[aria-current="page"] { color: var(--primary-ink); }
.drawer .btn { margin-block-start: var(--s-5); }
.drawer .note { margin-block-start: auto; max-width: none; }

.icon-btn {
  display: inline-grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  border: 1px solid var(--border);
  border-radius: var(--r-2);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
}
.icon-btn:hover { border-color: var(--border-strong); }
.icon-btn svg { inline-size: 1.25rem; block-size: 1.25rem; fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; }

/* The toggle appears exactly where the inline nav disappears. */
.nav-toggle { display: none; }
@media (max-width: 48rem) {
  .nav-toggle { display: inline-grid; }
  header.site nav.inline { display: none; }
}
@media (min-width: 48.0625rem) {
  .drawer[open] { display: none; }
}

@media (prefers-reduced-motion: no-preference) {
  .drawer {
    transition:
      translate var(--d-3) var(--e-out),
      display var(--d-3) allow-discrete,
      overlay var(--d-3) allow-discrete;
  }
  .drawer[open] { translate: 0 0; }
  .drawer:not([open]) { translate: 100% 0; }
  @starting-style { .drawer[open] { translate: 100% 0; } }

  .drawer::backdrop {
    transition:
      opacity var(--d-3) var(--e-out),
      display var(--d-3) allow-discrete,
      overlay var(--d-3) allow-discrete;
  }
  .drawer[open]::backdrop { opacity: 1; }
  .drawer:not([open])::backdrop { opacity: 0; }
  @starting-style { .drawer[open]::backdrop { opacity: 0; } }

  /* The links arrive after the panel does, so it is a drawer opening rather than a list
     that slid sideways. */
  .drawer[open] .drawer-in > * { animation: rise 420ms var(--e-out) both; }
  .drawer[open] .drawer-in > :nth-child(2) { animation-delay: 90ms; }
  .drawer[open] .drawer-in > :nth-child(3) { animation-delay: 140ms; }
  .drawer[open] .drawer-in > :nth-child(4) { animation-delay: 190ms; }
  .drawer[open] .drawer-in > :nth-child(5) { animation-delay: 240ms; }
  .drawer[open] .drawer-in > :nth-child(6) { animation-delay: 290ms; }
}

/* -- the standing offer -------------------------------------------------------------------------
   On a phone the header's CTA scrolls away and the next one is the length of the page below
   it. This rises once the hero is behind you, and steps aside over the closing section so it
   is never the second "Open a store" button on screen at the same time. */
.cta-bar {
  position: fixed;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: var(--z-sticky);
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-3) var(--s-5) max(var(--s-3), env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: blur(12px) saturate(1.4);
  border-block-start: 1px solid var(--border);
  translate: 0 110%;
}
.cta-bar p { margin: 0; font-size: var(--t-sm); color: var(--muted); max-width: none; }
.cta-bar .btn { flex: none; }
.cta-bar.up { translate: 0 0; }

@media (max-width: 48rem) {
  .cta-bar { display: flex; }
  /* Room for it, so the last line of the footer is not sitting underneath the bar. Scoped
     with :has() to the pages that actually carry one -- terms and signup do not. */
  body:has(.cta-bar) { padding-block-end: 4.75rem; }
}
@media (prefers-reduced-motion: no-preference) {
  .cta-bar { transition: translate var(--d-3) var(--e-out); }
}
@media (prefers-reduced-motion: reduce) {
  .cta-bar { translate: 0 0; }
}
