/* img2ppt design system — the single source of truth for every brand value.
   Linked by every HTML surface (app, legal, pricing, auth verify, admin).
   email.py mirrors these values inline because email clients strip stylesheets.

   The law that governs this file is docs/design-system.md. Read it before you
   add anything. The short version:

     - No raw hex, px font-size, px radius or one-off shadow anywhere else in
       the codebase. If a value is worth using twice it is a token; if it is
       worth using once it still comes off the scale.
     - Every length is authored at 100% and multiplied by --ui-scale. Author
       the 100% number; never bake the multiplication into the literal.
     - Adding a value here is a design decision, not a convenience. A new token
       needs a reason the existing ones cannot cover.
     - /styleguide renders every token and component below. If you change
       something here, look at that page before you commit.

   Palette: marker yellow on formal white, ink controls (colour option 16,
   chosen 2026-09-12 -- design-system.md 2.1). Yellow goes only where a marker
   pen would; every control is ink. Light theme only, by decision -- see
   design-system.md 2.5. */
:root {
  color-scheme: light;

  /* ---- Global scale ---------------------------------------------------
     One knob for the size of the whole product. Every length below is
     authored at its 100% value and multiplied by this, so the scale, the
     4px space grid and the nine type steps all stay readable as themselves
     -- `calc(24px * var(--ui-scale))` says "24 at 100%", which a rebased
     literal like 26.4px could not.

     1.1 because the site reads better one browser-zoom step up: this is
     that view, at 100% zoom (2026-08-22). Uniform on purpose -- browser
     zoom scales type and space together, and scaling type alone would
     change the design's proportions rather than its size.

     Deliberately NOT scaled, and each for its own reason:
       - hairlines, borders, focus rings and the 1-3px nudges. A 1.1px
         border is not a better 1px border, and 2px of focus ring is an
         accessibility minimum, not a proportion.
       - --tap. A WCAG 2.5.8 floor in CSS px; it does not grow with taste.
       - shadows and durations. Zoom moves shadow offsets by a tenth of a
         pixel (invisible) and does not touch time at all.
       - email.py's inlined values. Email is not this viewport.

     Media queries CANNOT read a custom property, so every breakpoint in
     the page files carries the multiplied number with the arithmetic in a
     comment beside it. Those are the only literals that must be changed by
     hand if this knob moves. */
  --ui-scale: 1.1;

  /* Kalam: the hand-drawn marginalia on the landing page only, and the only
     reason this project loads a second family. Never for UI text, never in the
     app, admin, email or legal pages -- same boundary as .doodle itself
     (design-system.md 3). Upright only; do not ask it for an italic. */
  --font-hand: Kalam, "Segoe Print", "Bradley Hand", cursive;

  /* ---- Surfaces ------------------------------------------------------ */
  --bg: #ffffff;        --bg-2: #faf9f8;         --card: #ffffff;

  /* ---- Text ----------------------------------------------------------
     Measured against #ffffff. Every one of these clears AA 4.5:1 for body
     text, which is why --faint moved off #797775 (4.46 -- a hair short).
     ink 15.52 : body 11.35 : muted 6.46 : faint 4.52 */
  --ink: #242424;       --body: #3b3a39;
  --muted: #605e5c;     --faint: #787674;

  /* ---- Accent: marker yellow ------------------------------------------
     The roles changed with the 2026-09-12 retheme, and getting them wrong is
     the single easiest way to ship an accessibility bug here.

       --accent (#f5c518, 1.63:1 on white) is DECORATION, the same class as
       --gold: marker strokes, tints, the strip. Never text at any size,
       never a meaningful icon, never a border that carries state. If
       removing the colour would lose information, it cannot be this.
       --accent-dark (#917306, 4.51:1) is the colour that must be SEEN:
       state borders, list markers, icon strokes, the focus ring, and the
       wordmark's coloured span.
       --accent-ink is --ink: every formerly-coloured word -- links,
       eyebrows, status text -- is plain ink, and the underline or the
       position carries the affordance (WCAG 1.4.1; already the rule for
       prose links, see .link below). Kept as a token so call sites read
       "this used to be the accent text colour" -- and aliased with var()
       rather than hand-copied, so the identity the design system asserts
       cannot silently drift. */
  --accent: #f5c518;    --accent-dark: #917306;  --accent-ink: var(--ink);
  --accent-soft: #fdf9ea;  --accent-line: #f7e8b0;
  /* Anything drawn ON a dark accent fill -- the ink CTA's label, the navbar's
     wordmark on the strip's #a07f07 core, the burger bars. Named rather than
     bare #fff so "is this on an accent fill?" is answerable by reading the
     declaration. NEVER on --accent itself: white on #f5c518 is 1.63:1. */
  --on-accent: #ffffff;

  /* ---- Brand gradient -------------------------------------------------
     The decorative one: the marker-yellow sweep. Symmetric so the notch
     navbar (which hangs from the strip, centered) always sits on the dark
     zone for contrast. This is the bright brand signature and it is allowed
     to stay bright because NO TEXT EVER SITS ON IT. Putting a label on this
     gradient is a defect.

     The deep stop IS --accent-dark (#917306), deliberately: the navbar's
     white labels sit on this core, and 4.51:1 (4.5075) is what finally
     closed design-system.md 2.1.5 on 2026-09-12 -- the first core of the
     retheme, #a07f07, left them at 3.80. The margin over the 4.5 floor is
     less than a hundredth: do NOT lighten this stop by any amount without
     re-measuring the bar. */
  --grad-light: #fdf4d3;   --grad-mid: #f9dd76;  --grad-deep: #917306;
  /* The deep zone is 25-75% -- half the viewport -- because the AA claim of
     design-system.md 2.1.5 depends on geometry as well as colour: nav.css
     caps the notch bar at 50% of the viewport above its breakpoint, so with
     a 50% deep core the bar can NEVER outgrow the zone its white labels
     need, at any width, signed in or out. (The coral-era 38-62% core was
     narrower than the bar at most widths; its edge labels sat on the blend
     and nobody had measured them.) Below 726px the media query after this
     block flattens the sweep instead -- see it for the arithmetic. */
  --grad: linear-gradient(90deg, var(--grad-light) 0%, var(--grad-mid) 12%,
    var(--grad-deep) 25%, var(--grad-deep) 75%, var(--grad-mid) 88%,
    var(--grad-light) 100%);

  /* The button gradient is a different HUE on purpose: controls are ink in
     this scheme, and the ink gradient carries a white label at 14.16:1 and
     18.88:1 -- every end of every state clears AA several times over, a
     deliberate overcorrection after the coral era shipped a 2.46:1 CTA for
     months. Do not yellow these to "match the strip"; the strip is
     decoration, this is a control, and no yellow can carry a label. */
  --grad-cta: linear-gradient(135deg, #2b2b2b, #111111);
  --grad-cta-hover: linear-gradient(135deg, #242424, #0a0a0a);
  --grad-cta-active: linear-gradient(135deg, #111111, #000000);

  /* ---- Status ---------------------------------------------------------
     Each pairs a 4.5:1+ text colour with its own tint background. --gold is
     1.72:1 and is therefore decoration only: a star fill, never a word,
     never an icon that has to be seen to be understood. Since 2026-09-12 it
     sits INSIDE the brand family (a second yellow, a step warmer than
     --accent) rather than beside it; same decoration-grade rules. Note
     --warn-bg is within 1.04:1 of --accent-soft: 7.2 ("never colour alone")
     means the tint may never be the only carrier, so any two chips that can
     share a view need a non-tint difference -- a word alone is enough only
     when the words differ AND nothing else matches; see index.html's
     .pill.run, which carries a pulsing dot for exactly this reason. */
  --gold: #ffb900;
  --ok: #107c41;   --ok-bg: #e6f2ea;
  --bad: #a4262c;  --bad-bg: #fbeae9;
  --warn: #8a5a00; --warn-bg: #fff4d6;

  /* ---- Third-party brand marks ----------------------------------------
     NOT part of our palette, and never to be used for our own UI -- these
     exist only so the OpenAI / Gemini / Claude marks in the home hero can be
     drawn in their real colours without a raw hex escaping into a page (rule
     1 of docs/design-system.md). Their use is governed by the terms in
     docs/positioning.md 13; read that before putting a mark on a new surface.

     Deliberately NOT the icon vendor's flat hexes -- simple-icons lists
     OpenAI as #412991, which is not a mark anyone has seen. Gemini has no
     token at all: its 2025 mark carries Google's own multi-colour gradient
     and is inlined whole in index.html. --brand-claude was a hair off the
     coral accent the site carried until 2026-09-12; terracotta now reads as
     a distinct colour beside our yellow and ink, but the rule stands: never
     place a third-party mark where it could read as our brand. And the site
     is light-only, so --brand-openai is black; a dark theme needs a pass
     here. */
  --brand-openai: #000000;
  --brand-claude: #d97757;

  /* ---- Lines ----------------------------------------------------------
     --line and --line-soft are decorative separators at 1.19:1; they are
     fine for dividing content and NOT fine as the only boundary of an
     interactive control. WCAG 1.4.11 wants 3:1 for that, which is what
     --line-strong (3.10:1) is for -- inputs, checkboxes, outline buttons.

     Lightened from #8a8886 (3.53:1) on 2026-08-24: a 1px 3.53:1 edge around a
     48px full-width field read as a dark box drawn around empty space. 3.10:1
     is as light as this can legally go -- #979593 is already 2.98:1 and fails
     1.4.11 -- so if an input's edge still looks heavy, the answer is a wider
     field or a filled surface, NOT a lighter value here. See
     design-system.md 2.1.3. */
  --line: #edebe9; --card-line: #edebe9; --line-soft: #f3f2f1;
  --line-strong: #949290;

  /* ---- Type -----------------------------------------------------------
     One stack everywhere: system-first, with Inter as the named fallback that
     the public pages also fetch from Google Fonts (design-system.md 2.2 --
     this comment claimed "no webfont" until 2026-09-12, which §2.2 had
     corrected on 2026-08-22). It reads native next to the Office app we hand
     people a file for.
     The scale is nine steps. Anything not on it is off-scale. */
  --font: -apple-system, "system-ui", Inter, "Segoe UI", "Helvetica Neue",
    Arial, "PingFang SC", "Microsoft YaHei", "Hiragino Sans GB", "Noto Sans SC",
    sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono", monospace;

  --fs-display: calc(44px * var(--ui-scale));  --fs-h1: calc(32px * var(--ui-scale));
  --fs-h2: calc(24px * var(--ui-scale));       --fs-h3: calc(19px * var(--ui-scale));
  --fs-h4: calc(17px * var(--ui-scale));       --fs-body: calc(15px * var(--ui-scale));
  --fs-sm: calc(13px * var(--ui-scale));       --fs-xs: calc(12px * var(--ui-scale));
  --fs-micro: calc(11px * var(--ui-scale));

  /* Tight leading for headings, generous for anything you actually read. */
  --lh-tight: 1.1;   --lh-snug: 1.25;  --lh-normal: 1.45;  --lh-relaxed: 1.6;

  /* Negative tracking on large type only -- it is a correction for optical
     looseness at size, and applying it to body text just hurts legibility. */
  --ls-display: calc(-1.2px * var(--ui-scale));  --ls-h1: calc(-.7px * var(--ui-scale));
  --ls-h2: calc(-.4px * var(--ui-scale));        --ls-h3: calc(-.2px * var(--ui-scale));
  --ls-none: 0;                      --ls-caps: calc(.6px * var(--ui-scale));

  --fw-regular: 400;  --fw-medium: 500;  --fw-semibold: 600;  --fw-bold: 700;
  --fw-heavy: 800;

  /* ---- Space ----------------------------------------------------------
     4px base. Named by its 100% value so an off-scale number is obvious at
     the call site: var(--sp-24) is self-evidently on the scale, 26px is not.
     The name is the step, not the rendered length -- --sp-24 renders at
     24 * --ui-scale. */
  --sp-2:  calc(2px  * var(--ui-scale));  --sp-4:  calc(4px  * var(--ui-scale));
  --sp-6:  calc(6px  * var(--ui-scale));  --sp-8:  calc(8px  * var(--ui-scale));
  --sp-12: calc(12px * var(--ui-scale));  --sp-16: calc(16px * var(--ui-scale));
  --sp-20: calc(20px * var(--ui-scale));  --sp-24: calc(24px * var(--ui-scale));
  --sp-32: calc(32px * var(--ui-scale));  --sp-40: calc(40px * var(--ui-scale));
  --sp-48: calc(48px * var(--ui-scale));  --sp-64: calc(64px * var(--ui-scale));
  --sp-80: calc(80px * var(--ui-scale));  --sp-96: calc(96px * var(--ui-scale));

  /* ---- Shape ----------------------------------------------------------
     Three steps, deliberately. sm for controls, md for containers, full for
     anything pill-shaped. A fourth value is a design decision, not a tweak. */
  /* --r-full is a shape, not a size: 999px means "pill" at any scale. */
  --r-sm: calc(8px * var(--ui-scale));  --r-md: calc(12px * var(--ui-scale));  --r-full: 999px;

  /* ---- Elevation ------------------------------------------------------
     Warm-black shadows (the neutral is #241814, not pure black) so they sit
     on a warm palette without going grey-blue -- tuned under coral, equally
     at home under marker yellow. Four steps: resting, raised, floating,
     showcase.

     Elevation belongs to SURFACES -- a card, a popover, a sheet, a showcase
     still. Controls are flat (2026-08-22): the accent-tinted --sh-accent and
     --sh-accent-lg that used to sit under the primary button are gone, and
     nothing replaces them. See design-system.md 5.1. */
  --sh-1: 0 1px 2px rgba(36, 24, 20, .05);
  --sh-2: 0 4px 12px rgba(36, 24, 20, .08), 0 1px 2px rgba(36, 24, 20, .04);
  --sh-3: 0 12px 28px rgba(36, 24, 20, .16), 0 2px 6px rgba(36, 24, 20, .06);
  --sh-4: 0 24px 64px rgba(36, 24, 20, .24), 0 2px 8px rgba(36, 24, 20, .08);
  /* The halo samples --accent-dark (145,115,6), not the decoration yellow:
     a focus cue is a state and has to be perceivable. */
  --ring: 0 0 0 3px rgba(145, 115, 6, .18);
  --scrim: rgba(36, 24, 20, .5);   /* behind a modal */

  /* The one dark surface in a light-only system: a log or code block, where
     the convention is strong enough that a light one reads as broken. */
  --code-bg: #201f1e;  --code-ink: #d6d2cf;

  /* ---- Motion ---------------------------------------------------------
     The browser's built-in ease-out is too weak to read as deliberate at UI
     durations, so both curves are the strong versions every surface shares --
     a second, hand-rolled bezier somewhere else is a defect, not a variation.
     ease-out for anything entering or leaving, ease-in-out for something
     moving from one on-screen place to another. */
  --ease-out: cubic-bezier(.23, 1, .32, 1);
  --ease-in-out: cubic-bezier(.77, 0, .175, 1);
  --dur-fast: 120ms;   /* colour and opacity on a control you are touching */
  --dur: 200ms;        /* the default: hovers, small reveals */
  --dur-slow: 320ms;   /* sheets, accordions, anything with travel */

  /* ---- Layout --------------------------------------------------------- */
  --w-prose: calc(680px * var(--ui-scale));    /* legal and long-form: ~75 characters */
  --w-page: calc(860px * var(--ui-scale));     /* a standard content page -- pricing, docs */
  --w-content: calc(1100px * var(--ui-scale)); /* the marketing sections */
  --w-narrow: calc(620px * var(--ui-scale));   /* centred section headers */
  /* The one full-bleed step, for a showcase whose whole point is size: the
     before/after gallery. It breaks out of the page container on purpose, so
     it is capped rather than allowed to run to the width of a 5K display. */
  --w-wide: calc(1920px * var(--ui-scale));
  /* A floor in CSS px rather than a proportion, so it does not scale: WCAG
     2.5.8 and Apple both ask for 44, and 44 is what a finger is. */
  --tap: 44px;         /* minimum touch target, coarse pointers */
}

/* The gradient's deep zone is a percentage of the viewport, and the notch
   bar is centred content: below the navbar's own breakpoint the bar grows
   wider than the 38-62% core, and its outer labels -- Sign in, the burger,
   a signed-in email -- slide onto the light sweep, where white is illegible
   (--grad-mid is 1.34:1 under white). So at phone widths the deep zone
   widens to cover any bar the cap allows (the bar may run to 100% minus
   16px when it carries an email; content starts ~20px in, and 2% of even
   the widest phone viewport is inside that), and the strip reads as a
   solid brand band with light tips. Same breakpoint as the navbar's burger
   (660 * --ui-scale -- a media query cannot read the knob, theme.css rule).
   All three notch surfaces sample this one token, so they stay seamless. */
@media (max-width: 726px) { /* 660 * --ui-scale */
  :root {
    --grad: linear-gradient(90deg, var(--grad-light) 0%,
      var(--grad-deep) 2%, var(--grad-deep) 98%, var(--grad-light) 100%);
  }
}

/* ===================================================================== *
 * BASE
 * ===================================================================== */

/* Every in-page jump eases instead of teleporting: the .peek cue under the
   dropzone, the nav's Examples and FAQ links, the footer's. It needs no
   reduced-motion guard of its own -- the block below already forces
   scroll-behavior back to `auto` -- and .sec's scroll-margin-top is what keeps
   the landing clear of the fixed notch bar. */
html { scroll-behavior: smooth; }

body {
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: var(--lh-relaxed);
  color: var(--body);
}

/* One focus ring for the whole product. Never remove it without replacing it.
   No border-radius here on purpose: outline already follows the element's own
   corners, and setting it would flatten pill-shaped controls the moment they
   take focus. */
:where(button, a, input, textarea, select, [tabindex]):focus-visible {
  /* --accent-dark, never --accent: the ring is a state, non-text contrast
     needs 3:1, and 4.51 clears it where 1.63 cannot (design-system.md 2.1). */
  outline: 2px solid var(--accent-dark);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ===================================================================== *
 * BUTTONS — four variants, three sizes, one base.
 *
 * Variant picks the weight in the hierarchy, size picks the room it takes.
 * The rule for choosing between a button and a link is in design-system.md
 * 5.2 and it is about behaviour, not appearance: a control that PERFORMS an
 * action is a button, a control that NAVIGATES is a link. A <button> styled
 * as a link is fine. An <a> styled as a button is fine. An <a href="#"> with
 * a click handler is not.
 * ===================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-8);
  height: calc(40px * var(--ui-scale));
  padding: 0 var(--sp-20);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font-family: inherit;
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}

.btn--sm { height: calc(32px * var(--ui-scale)); padding: 0 var(--sp-12); font-size: var(--fs-sm); }
.btn--lg { height: calc(48px * var(--ui-scale)); padding: 0 var(--sp-24); font-size: var(--fs-h4); }

/* A 40px control is comfortable with a mouse and short of the 44px target
   Apple and WCAG 2.5.8 both ask for on touch. Grow it where the pointer is
   coarse rather than making every desktop button oversized. */
@media (pointer: coarse) {
  /* max(), not a flat --tap: the floor has to stop being the answer once the
     scale carries the button past it, or turning the knob up would shrink
     every touch button back to 44. */
  .btn { height: max(var(--tap), calc(40px * var(--ui-scale))); }
  .btn--sm { height: max(38px, calc(32px * var(--ui-scale))); }
}

/* Flat. The gradient is already the loudest thing on the page, and a tinted
   glow under it was doing the same job twice -- at small sizes it read as a
   blur rather than lift. Hover and active are colour moves only. */
.btn--primary {
  background: var(--grad-cta);
  color: var(--on-accent);
}
.btn--primary:hover { background: var(--grad-cta-hover); }
.btn--primary:active { background: var(--grad-cta-active); }

.btn--secondary {
  background: var(--card);
  color: var(--ink);
  border-color: var(--line-strong);
}
/* Hover border is --accent-dark: a border signalling state must clear 3:1. */
.btn--secondary:hover { background: var(--accent-soft); border-color: var(--accent-dark); }
.btn--secondary:active { background: var(--accent-line); }

.btn--ghost {
  background: none;
  color: var(--accent-ink);
  padding: 0 var(--sp-12);
}
.btn--ghost:hover { background: var(--accent-soft); }
.btn--ghost:active { background: var(--accent-line); }

.btn--danger {
  background: var(--card);
  color: var(--bad);
  border-color: var(--bad);
}
.btn--danger:hover { background: var(--bad-bg); }

/* Disabled is a state, not a variant, so it reads the same on all four.
   :disabled covers <button>, [aria-disabled] covers the <a> case, where the
   attribute is the only thing a screen reader will hear. */
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: .45;
  cursor: default;
  pointer-events: none;
}

/* Busy keeps the label in place and the width stable -- a spinner that
   replaces the text makes the button resize and the layout jump. */
.btn[aria-busy="true"] { cursor: progress; }
.btn[aria-busy="true"]::after {
  content: "";
  width: calc(13px * var(--ui-scale)); height: calc(13px * var(--ui-scale));
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: var(--r-full);
  opacity: .7;
  animation: btn-spin 620ms linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }

/* Full-bleed on narrow screens is a layout choice, not a variant. */
.btn--block { width: 100%; }

/* ===================================================================== *
 * LINKS — three kinds, and which one you use depends on where it sits.
 * ===================================================================== */

/* In running prose, colour alone cannot carry the affordance (WCAG 1.4.1),
   so a prose link is always underlined. The offset keeps the rule clear of
   descenders; the thickening on hover is the feedback. */
.link {
  color: var(--accent-ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  transition: text-decoration-thickness var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.link:hover { text-decoration-thickness: 2px; }

/* A UI link sits somewhere its own position already says "interactive" -- a
   nav bar, a card, a footer column. It may drop the rest-state underline.
   In a paragraph it may not. */
.link--ui {
  text-decoration: none;
  font-weight: var(--fw-medium);
}
.link--ui:hover { text-decoration: underline; text-decoration-thickness: 1px; }

/* A link (or as-link button) that carries an icon beside its label. Keeps the
   two on one baseline and stops the icon wrapping away from the text. */
.link--icon { display: inline-flex; align-items: center; gap: var(--sp-6); }
.link--icon svg { flex: none; }

/* Utility links that must not compete: "skip", "not now", legal footers. */
.link--quiet { color: var(--muted); }
.link--quiet:hover { color: var(--ink); }

/* A link sharing an action row with the primary button. Full-strength link
   text beside the gradient CTA reads as two primaries competing, and the link
   is the one that loses -- so it steps back to --muted and takes a rest-state
   underline, which is what keeps it obviously clickable once the weight stops
   doing that job. On its own, a link is .link, unchanged.

   Adjacency is the condition, so the first two selectors are structural and
   no call site has to remember anything -- a link with a primary button as a
   following sibling, or as a preceding one. .link--beside-btn is the manual
   escape hatch for the case where the two are not siblings (a wrapper div
   between them). Two classes deep on purpose: it has to outrank .link--ui,
   which sets text-decoration: none. */
.link:has(~ .btn--primary),
.btn--primary ~ .link,
.link.link--beside-btn {
  color: var(--muted);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
.link:has(~ .btn--primary):hover,
.btn--primary ~ .link:hover,
.link.link--beside-btn:hover {
  color: var(--ink);
  text-decoration-thickness: 2px;
}

/* Lets a <button> wear the .link classes. Section 5.2 blesses this: the
   element is a button because it performs an action, and the styling is a link
   because it sits in running text or a toolbar. Compose it --
   `class="as-link link link--ui"` -- rather than inventing a fifth variant. */
.as-link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
/* Same state, same reading as .btn:disabled above -- a link-shaped button that
   is out of action has to look out of action too, or it just fails silently
   when clicked. */
.as-link:disabled,
.as-link[aria-disabled="true"] {
  opacity: .45;
  cursor: default;
  pointer-events: none;
}

/* Arrow links. The arrow is a pseudo-element so it never wraps to its own
   line, and it nudges rather than travels -- 3px, not 8. */
.link--arrow::after {
  content: "\2192";
  display: inline-block;
  margin-left: var(--sp-6);
  transition: transform var(--dur) var(--ease-out);
}
.link--arrow:hover::after { transform: translateX(3px); }

/* ===================================================================== *
 * LISTS — the marker is --accent-dark, the text is body. Four kinds.
 * ===================================================================== */

.list { margin: var(--sp-12) 0; padding-left: var(--sp-20); color: var(--body); }
.list > li { margin: var(--sp-6) 0; padding-left: var(--sp-4); }
/* --accent-dark: a marker carries the list's structure, so it must be seen.
   At 1.63:1 the decoration yellow disappears into the page. */
.list > li::marker { color: var(--accent-dark); }

/* Numbered steps. The counter is the accent element, which is why it is drawn
   rather than left to ::marker -- a plain "1." cannot be weighted separately
   from its item text. */
.list--num { list-style: none; padding-left: 0; counter-reset: step; }
.list--num > li {
  position: relative;
  counter-increment: step;
  padding-left: var(--sp-32);
  margin: var(--sp-12) 0;
}
.list--num > li::before {
  content: counter(step);
  position: absolute;
  left: 0; top: 1px;
  width: calc(20px * var(--ui-scale)); height: calc(20px * var(--ui-scale));
  display: grid; place-items: center;
  background: var(--accent-soft);
  color: var(--accent-ink);
  border-radius: var(--r-full);
  font-size: var(--fs-micro);
  font-weight: var(--fw-bold);
  line-height: 1;
}

/* Affirmative lists -- what you get, what is included. The tick is a CSS
   mask so it inherits currentColor and never ships as an image. */
.list--check { list-style: none; padding-left: 0; }
.list--check > li {
  position: relative;
  padding-left: var(--sp-24);
  margin: var(--sp-8) 0;
}
.list--check > li::before {
  content: "";
  position: absolute;
  left: 0; top: .45em;
  width: calc(13px * var(--ui-scale)); height: calc(13px * var(--ui-scale));
  background: var(--ok);
  -webkit-mask: var(--icon-check) center / contain no-repeat;
          mask: var(--icon-check) center / contain no-repeat;
}

/* The negative counterpart. Same geometry, so a two-column comparison of
   "we do / we don't" lines up row for row. */
.list--cross > li::before {
  background: var(--bad);
  -webkit-mask: var(--icon-cross) center / contain no-repeat;
          mask: var(--icon-cross) center / contain no-repeat;
}

.list--plain { list-style: none; padding-left: 0; }
.list--plain > li { margin: var(--sp-8) 0; }

:root {
  --icon-check: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M6.2 12.3 2.4 8.5l1.3-1.3 2.5 2.5 6.1-6.1 1.3 1.3z"/></svg>');
  --icon-cross: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M12.7 4.6 9.3 8l3.4 3.4-1.3 1.3L8 9.3l-3.4 3.4-1.3-1.3L6.7 8 3.3 4.6l1.3-1.3L8 6.7l3.4-3.4z"/></svg>');
}

/* ===================================================================== *
 * TEXT UTILITIES — the scale, addressable.
 * ===================================================================== */

.t-display { font-size: var(--fs-display); font-weight: var(--fw-heavy);
  line-height: var(--lh-tight); letter-spacing: var(--ls-display); color: var(--ink); }
.t-h1 { font-size: var(--fs-h1); font-weight: var(--fw-heavy);
  line-height: var(--lh-tight); letter-spacing: var(--ls-h1); color: var(--ink); }
.t-h2 { font-size: var(--fs-h2); font-weight: var(--fw-bold);
  line-height: var(--lh-snug); letter-spacing: var(--ls-h2); color: var(--ink); }
.t-h3 { font-size: var(--fs-h3); font-weight: var(--fw-bold);
  line-height: var(--lh-snug); letter-spacing: var(--ls-h3); color: var(--ink); }
.t-h4 { font-size: var(--fs-h4); font-weight: var(--fw-bold);
  line-height: var(--lh-snug); color: var(--ink); }
.t-body { font-size: var(--fs-body); line-height: var(--lh-relaxed); color: var(--body); }
.t-sm { font-size: var(--fs-sm); line-height: var(--lh-normal); color: var(--muted); }
.t-xs { font-size: var(--fs-xs); line-height: var(--lh-normal); color: var(--muted); }

/* The one place uppercase is allowed: a short kicker above a heading.
   Uppercase running text is slower to read and we do not ship it. */
.t-eyebrow {
  font-size: var(--fs-micro);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--accent-ink);
}

/* Tabular figures for anything that stacks: prices, credit counts, object
   counts, durations. Proportional digits make a column of numbers ragged. */
.t-num { font-variant-numeric: tabular-nums; }

/* ===================================================================== *
 * PAGE SECTIONS — the marketing furniture more than one public page needs.
 *
 * Added 2026-08-24, when /pricing needed the same section headers, the same
 * FAQ accordion and the same footer the landing page had grown. index.html
 * still carries its own inline copies of everything below and they win on
 * source order, so removing them is a follow-up, not a side effect of this
 * change (design-system.md 10, "Still open"). Until that happens, a value
 * edited in index.html's <style> does NOT reach /pricing — change both.
 *
 * Page-specific variants stay with the page that owns them: .sec--wide in
 * index.html, .pack in pricing.html.
 * ===================================================================== */

/* scroll-margin clears the fixed notch bar, so an anchored jump doesn't
   land with the heading tucked behind it. */
.sec { margin-top: var(--sp-80); scroll-margin-top: calc(64px * var(--ui-scale)); }
.sec-head { text-align: center; max-width: var(--w-narrow);
  margin: 0 auto var(--sp-32); }
.sec h2 { font-size: var(--fs-h1); font-weight: var(--fw-bold);
  letter-spacing: var(--ls-h1); line-height: var(--lh-tight); margin: 0; }
.sec-sub { color: var(--muted); font-size: var(--fs-body);
  font-weight: var(--fw-regular); margin: var(--sp-8) 0 0;
  line-height: var(--lh-relaxed); text-wrap: pretty; }
.sec-head .t-eyebrow { display: block; margin: 0 0 var(--sp-8); }
@media (max-width: 836px) /* 760 * --ui-scale */ {
  .sec { margin-top: var(--sp-48); }
  .sec h2 { font-size: var(--fs-h2); letter-spacing: var(--ls-h2); }
}

/* The highlighter stroke under a phrase in a headline. --gold is 1.72:1 and
   can never be text; as a bar under text it is decoration, which is the only
   thing it is allowed to be.

   nowrap: as a background the bar is drawn per line box, so letting the
   phrase break leaves two highlighter strokes of different lengths sitting
   under each other. Keeping the phrase whole forces the break before it --
   which is also why a headline using this needs a fluid size at the narrow
   end, or the phrase widens the page instead of wrapping.

   One call site today, the landing hero. It stays here rather than back in
   index.html because it is a treatment, not a piece of that page: /pricing
   carried it for a day and gave it up, and the next page to want it should
   find it. */
.gold-u { background: linear-gradient(var(--gold), var(--gold))
  left 100% / 100% calc(5px * var(--ui-scale)) no-repeat;
  padding-bottom: var(--sp-6); white-space: nowrap; }

/* Friction-killers: a centred row of short reassurances, under a headline or
   above a set of prices. Every item supplies its own inline line icon
   (`.ki`) -- the row used to share one tick via ::before, which made three
   different claims read as one repeated one. */
.killers { list-style: none; display: flex; flex-wrap: wrap;
  justify-content: center; gap: var(--sp-8) var(--sp-16);
  margin: var(--sp-16) 0 0; padding: 0;
  color: var(--body); font-size: var(--fs-sm); font-weight: var(--fw-regular); }
.killers li { display: flex; align-items: center; gap: var(--sp-6); }
/* --ink rather than --ok, the colour .list--check gives its tick: three green
   icons under the headline was a second accent colour neither call site
   has a use for. 20px is the small size from design-system.md 3. */
.killers .ki { flex: none; color: var(--ink);
  width: calc(16px * var(--ui-scale)); height: calc(16px * var(--ui-scale)); }
/* A negation slash inside one of these icons needs air around it, or at 20px
   it reads as another rule of the glyph it crosses. Draw the slash twice: a
   wide .ki-cut in the page colour first, then the real stroke over it. Safe
   because these icons only ever sit on --bg; move one onto a card and the
   knock-out has to move with it. */
.killers .ki-cut { stroke: var(--bg); }

/* ===================================================================== *
 * FAQ ACCORDION — <details> only, so it still works with JS off.
 * ===================================================================== */

/* --w-prose, the long-form reading measure: at --w-page the answers ran to
   well over 100 characters a line and the block read as a wall. The step up
   the type scale on both the question and the answer stays -- this is the
   most-read block on the page and it was set two sizes smaller than the
   sections above it. */
.faq { max-width: var(--w-prose); margin: 0 auto; background: var(--card);
  border: 1px solid var(--card-line); border-radius: var(--r-md);
  padding: var(--sp-4) var(--sp-6); box-shadow: var(--sh-1); }
.faq details { border-bottom: 1px solid var(--line-soft); }
.faq details:last-child { border-bottom: 0; }
.faq summary { display: flex; align-items: flex-start; gap: var(--sp-12);
  padding: var(--sp-16) var(--sp-20); cursor: pointer;
  font-size: var(--fs-h4); font-weight: var(--fw-semibold);
  line-height: var(--lh-snug); color: var(--ink); list-style: none;
  border-radius: var(--r-sm);
  transition: background var(--dur-fast) var(--ease-out); }
.faq summary::-webkit-details-marker { display: none; }
/* A tint fill, not a colour move: the questions are already --ink, so the
   coral-era hover recolour became ink-on-ink and said nothing. The soft
   band under the pointer is the same hover language as .btn--ghost and the
   menus. Open state needs no colour of its own -- the rotated chevron and
   the revealed answer carry it. */
.faq summary:hover { background: var(--accent-soft); }
/* The chevron is the open/closed indicator, so it must still rotate for
   someone who has turned animation off -- only the transition goes. */
.faq summary .chev { flex: none; margin-left: auto; margin-top: 3px;
  color: var(--faint); transition: transform var(--dur) var(--ease-out); }
.faq details[open] summary .chev { transform: rotate(180deg); }
.faq-body { padding: 0 var(--sp-20) var(--sp-20); color: var(--body);
  font-size: var(--fs-body);
  font-weight: var(--fw-regular); line-height: var(--lh-relaxed); margin: 0;
  text-wrap: pretty; }
.faq-body + .faq-body { margin-top: var(--sp-8); }
.faq-body a { color: var(--accent-ink); text-decoration: underline;
  text-decoration-thickness: 1px; text-underline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .faq summary .chev { transition: none; }
}

/* ===================================================================== *
 * PAGE FOOTER — the bookend.
 *
 * This was a centred one-line strip with a centred legal paragraph under it:
 * two blobs of different widths, sharing no edge with each other or with the
 * page they close. It is one grid now, and everything in it hangs off the
 * container's left edge -- the brand, the two link columns, the colophon.
 * Nothing below is a new value: --grad draws the rule, .t-eyebrow labels the
 * columns and the beta note, and .link--ui + .link--quiet dress the links
 * (design-system.md 5.2 names both of those for exactly this place).
 * ===================================================================== */

.foot { margin-top: var(--sp-80); }

/* The bookend. nav.css opens the page on a 4px --grad strip pinned to the top
   of the viewport; this closes it on the same mark, at container width.
   Decoration only -- no text ever sits on --grad (design-system.md 2.1.2),
   and none sits on this.
   Deliberately NOT background-attachment: fixed, which is the trick the strip
   and the notch use to share one gradient. This bar has to read
   light-deep-light across its own width; sampling the viewport instead would
   hand it whichever slice of the gradient it happened to be scrolled past,
   and at the bottom of a long page that slice is always the same flat deep
   stop. */
.foot-rule { height: calc(4px * var(--ui-scale)); border-radius: var(--r-full);
  background: var(--grad); }

/* 1fr then auto, auto: the brand takes the slack and the two link columns
   size to their own labels and sit together against the right edge. Three
   even thirds put a column of four short links in the middle of 400px of
   nothing, which reads as a layout that lost something rather than as
   space. */
.foot-grid { display: grid; align-items: start;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: var(--sp-40) var(--sp-64);
  padding: var(--sp-40) 0 var(--sp-32); }

/* A measure, not a column width: the sentence wants ~45 characters so it
   breaks above whatever closes the column instead of running the full width
   of a 1240px container. */
.foot-brand { max-width: calc(420px * var(--ui-scale)); }
/* Baseline rather than centre, the same call nav.css makes for the same
   pair: an 11px badge optically centred against a 35px wordmark sits too
   high, and on the baseline it reads as an annotation of the name. */
.foot-brandline { display: flex; align-items: baseline; gap: var(--sp-8);
  margin: 0; }
/* The badge borrows .t-eyebrow's whole type treatment -- --fs-micro,
   --fw-bold, --ls-caps, uppercase -- and overrides only the colour, the same
   way .sec-head .t-eyebrow overrides only its display and margin. Grey, so
   the one accent-coloured thing in this block is the wordmark: at 12.1px
   bold the badge is small text, and --muted measures 6.46:1 against white,
   comfortably past the 4.5:1 that size needs. */
.foot-brandline .t-eyebrow { color: var(--muted); }
/* The wordmark on a light surface. The navbar's copy is white only because
   it sits on the strip. The coloured span is --accent-dark (4.51:1): the old
   display-size carve-out that licensed a 3.25:1 word at this size is retired
   -- the decoration yellow clears no floor at any size, and --accent-dark
   needs no size condition at all (design-system.md 2.1). email.py's copy of
   the wordmark is plain ink, its version of the same demotion.
   Deliberately large -- this is the masthead of the block, and it is what
   makes the gap between the brand and the link columns read as air rather
   than as a layout that lost a column. At --fs-h3 the same gap just looked
   empty. */
.foot-logo { display: inline-block; font-size: var(--fs-h1);
  font-weight: var(--fw-heavy); letter-spacing: var(--ls-h1);
  line-height: var(--lh-tight);
  color: var(--ink); text-decoration: none; white-space: nowrap; }
.foot-logo em { font-style: normal; color: var(--accent-dark); }

/* text-wrap: pretty, the same fix .sec-sub uses: at this measure the last
   word of the sentence otherwise sets alone on its own line. */
.foot-say { color: var(--muted); font-size: var(--fs-sm);
  font-weight: var(--fw-regular); line-height: var(--lh-relaxed);
  margin: var(--sp-16) 0 0; text-wrap: pretty; }
/* Sized in em off its own sentence, the same way the hero's source marks are:
   this is an inline mark riding a line of text, not one of the 20/24px line
   icons of design-system.md 3. --accent-dark: the heart is part of the
   sentence, and at 1.63:1 the decoration yellow would leave a hole in it. */
.foot-heart { width: 1.2em; height: 1.2em; vertical-align: -.22em;
  color: var(--accent-dark); margin-right: var(--sp-2); }

/* A signature, not a caption: Kalam at the .note size step, tilted on the
   same -2deg as .doodle-right so the page's closing flourish reads as the
   same hand that drew the two in the hero.

   index.html ONLY, and the one part of this footer that is: .hand carries
   --font-hand, which design-system.md 4 fences to the landing page. Every
   other page's brand column ends on .foot-say -- do not add a signature to
   /pricing or the legal pages without amending that rule first.

   transform-origin: left -- rotating about the centre swings the first word
   out past the column's left edge, which breaks the one alignment the whole
   grid exists to hold. */
.foot-sign { display: inline-block; margin: var(--sp-20) 0 0;
  font-size: var(--fs-h4); line-height: var(--lh-snug);
  color: var(--accent-ink);
  transform: rotate(-2deg); transform-origin: left center; }
/* The underline is the marker stroke -- decoration under ink text, which is
   exactly what --accent is for now. */
.foot-sign u { text-decoration: none;
  border-bottom: 2px solid var(--accent); padding-bottom: 1px; }

/* justify-items: start, so each link's hover underline is the width of its
   own label rather than of the column. */
.foot-col { display: grid; justify-items: start; gap: var(--sp-12);
  font-size: var(--fs-sm); }
.foot-col .t-eyebrow { margin: 0 0 var(--sp-4); }
/* Snug, not the body's relaxed 1.6: these are labels in a list, and at
   --lh-relaxed the leading inside a wrapped label opens up wider than the
   --sp-12 gap that is supposed to be separating the labels from each
   other. */
.foot-col a { line-height: var(--lh-snug); }

/* The colophon is a band, not a fourth column: it is the only text here
   nobody reads on purpose, so it sits below a hairline at prose measure, one
   step smaller and one step quieter than everything above it. */
.foot-fine { border-top: 1px solid var(--line); padding: var(--sp-24) 0 0;
  color: var(--faint); font-size: var(--fs-xs);
  font-weight: var(--fw-regular); line-height: var(--lh-relaxed); }
/* The hairline runs the full container width, like the gradient rule above
   it, so the two of them frame the grid between them. The measure belongs to
   the paragraphs -- putting it on the band instead stopped the rule at 748px
   with 500px of container left over, which reads as a bug rather than as an
   edge. */
.foot-fine p { max-width: var(--w-prose); margin: 0 0 var(--sp-8); }
.foot-fine p:last-child { margin: 0; }
/* Outranks the .link--quiet the markup also carries, and does it on purpose.
   This is the one link on the site allowed to skip the rest-state underline,
   which design-system.md 5.2 otherwise requires -- colour alone cannot carry a
   link (WCAG 1.4.1). It is legible without one via WCAG G183 instead: 3:1
   between the link and the text around it, plus a non-colour cue on hover and
   focus. --ink on a --faint band measures 15.52:1 and 4.52:1 against white, so
   3.43:1 against each other; hover adds the underline (.link--ui) and focus
   the ring. That arithmetic is the whole licence, so do not quieten this to
   --muted (1.43:1 against the band) or recolour it --accent-dark (1.00:1 --
   the same luminance as the band) -- either one puts the underline back. */
.foot-fine a { color: var(--ink); }

@media (max-width: 836px) /* 760 * --ui-scale */ {
  .foot { margin-top: var(--sp-48); }
  /* The brand takes the whole first row and the two lists pair up under it.
     max-content, so they stay side by side and left-aligned rather than
     becoming two 50% columns -- at 715px wide that put LEGAL in the dead
     centre of the page. minmax(0, ...) so they shrink and wrap instead of
     overflowing on a 320px phone, the one width where four labels genuinely
     do not fit twice over. */
  .foot-grid { grid-template-columns: repeat(2, minmax(0, max-content));
    justify-content: start; gap: var(--sp-32) var(--sp-48); }
  .foot-brand { grid-column: 1 / -1; max-width: none; }
}
