/* ============================================================================
   grabprompts — DESIGN SYSTEM  (light theme, Evergreen)
   ============================================================================
   THE single source of design tokens, linked site-wide from partials/head.ejs.
   Pages consume variables; no page, view or stylesheet hardcodes a colour.
   Change a value here and it cascades to every surface.

   PALETTE IS LOCKED. Evergreen (#0f9d63 ramp) is the only palette. The R1
   review aids — the second [data-palette="blue"] ramp, the header switcher and
   the ?palette query param — were deleted when the direction was approved.
   There is deliberately no theme-switching mechanism: one palette means one
   thing to keep consistent, and every page is designed against these exact
   values.

   LAYERING
     theme.css  every page — tokens, base, layout, shared components
     app.css    signed-in surfaces only — prompt-app, dashboard, login, locked
   app.css depends on these tokens and is always linked AFTER this file.
   ========================================================================= */

:root {
  /* ---- neutrals ---------------------------------------------------------
     Not pure grey. A whisper of the accent hue runs through the neutrals so
     white surfaces read as "warm and intentional" rather than clinical. */
  --bg:            #ffffff;
  --bg-subtle:     #f6f9f8;   /* page-level tint for alternating bands      */
  --surface:       #ffffff;   /* cards sitting on bg                        */
  --surface-sunk:  #f2f6f5;   /* inputs, code, wells                        */
  --border:        #e3ebe8;
  --border-strong: #cfdcd7;

  --text:          #0e1a15;   /* near-black, green-tinted                   */
  --text-muted:    #5a6b64;   /* body copy that must still pass on white    */
  --text-subtle:   #86948e;   /* captions, meta                             */
  --text-invert:   #ffffff;

  /* ---- accent ramp ------------------------------------------------------ */
  --accent:        #0f9d63;
  --accent-hover:  #0c8553;
  --accent-press:  #0a6e45;
  --accent-soft:   #e6f6ef;   /* tinted fill for pills/badges               */
  --accent-ring:   rgba(15, 157, 99, .28);
  --accent-2:      #0d8f8b;   /* supporting hue for accents on accents      */
  --on-accent:     #ffffff;

  /* ---- semantic --------------------------------------------------------- */
  --success:       #10864f;
  --success-soft:  #e6f6ee;
  --warning:       #b45309;
  --warning-soft:  #fdf3e4;
  --warning-line:  #f4e2c4;
  --danger:        #c2331f;
  --danger-soft:   #fdeeeb;

  /* ---- typography -------------------------------------------------------
     One modular scale (~1.25). Body stays 1rem; only headings scale with the
     viewport, so line lengths stay readable on a phone. */
  --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --text-xs:   0.8125rem;
  --text-sm:   0.9375rem;
  --text-base: 1rem;
  --text-lg:   1.125rem;
  --text-xl:   clamp(1.25rem, 1.1rem + 0.6vw, 1.4rem);
  --text-2xl:  clamp(1.5rem,  1.3rem + 1vw,   1.85rem);
  --text-3xl:  clamp(1.9rem,  1.5rem + 2vw,   2.6rem);
  --text-4xl:  clamp(2.3rem,  1.7rem + 3.2vw, 3.4rem);

  --leading-tight: 1.15;
  --leading-snug:  1.35;
  --leading-body:  1.65;

  --weight-normal: 400;
  --weight-medium: 550;
  --weight-bold:   700;

  --tracking-tight: -0.022em;
  --tracking-wide:   0.14em;

  /* ---- spacing (4px base) ----------------------------------------------- */
  --space-1: 0.25rem;  --space-2: 0.5rem;   --space-3: 0.75rem;
  --space-4: 1rem;     --space-5: 1.5rem;   --space-6: 2rem;
  --space-7: 2.5rem;   --space-8: 3rem;     --space-9: 4rem;
  --space-10: 5rem;    --space-11: 6rem;

  /* ---- radii ------------------------------------------------------------- */
  --radius-sm:   0.375rem;
  --radius-md:   0.625rem;
  --radius-lg:   0.875rem;
  --radius-xl:   1.25rem;
  --radius-pill: 999px;

  /* ---- elevation ---------------------------------------------------------
     Tinted with the ink colour, never pure black — black shadows on white
     look like dirt. Low alpha, generous blur. */
  --shadow-sm: 0 1px 2px rgba(14, 26, 21, .05);
  --shadow-md: 0 2px 4px rgba(14, 26, 21, .04), 0 8px 20px rgba(14, 26, 21, .06);
  --shadow-lg: 0 4px 8px rgba(14, 26, 21, .04), 0 18px 40px rgba(14, 26, 21, .09);

  /* ---- layout ------------------------------------------------------------ */
  --measure:    62rem;   /* page container                                   */
  --measure-sm: 44rem;   /* comfortable reading width for prose              */
  --measure-xs: 30rem;   /* single-column forms                              */

  --transition: 140ms cubic-bezier(.4, 0, .2, 1);

  /* ---- gradient tints -----------------------------------------------------
     The "emphasis" motif: subtle green washes on the boxes that carry weight
     (the emotional turn, the price, the formula strip), against plain white
     body sections. That contrast is what gives the long page rhythm.

     Built from the accent tokens via color-mix — never a literal hex — so
     changing --accent moves every gradient with it. Each user sets a flat
     background-color FIRST and the gradient as background-image, so a browser
     without color-mix drops only the gradient and keeps a correct solid tint. */
  --grad-tint:        linear-gradient(140deg, var(--accent-soft), color-mix(in srgb, var(--accent-soft) 25%, var(--surface)));
  --grad-tint-strong: linear-gradient(140deg, color-mix(in srgb, var(--accent) 16%, var(--surface)), var(--accent-soft));
  --grad-accent:      linear-gradient(135deg, var(--accent), var(--accent-2));

  /* ---- footer (the one dark surface) --------------------------------------
     The footer inverts. These are tokens, not literals in the footer rules,
     for the same reason as every other colour here: §14.2 allows a hex only
     inside this block. --footer-accent is a lighter mint than --accent, which
     is tuned for dark text on white and reads muddy the other way round. */
  /* The scrim behind a modal. Same ink as --text and the shadows, at the
     opacity that lets a thumbnail read as lit rather than merely on top. */
  --overlay: rgba(14, 26, 21, .82);

  --footer-bg:      #0b1712;
  --footer-raised:  #122019;
  --footer-text:    #e8f1ed;
  --footer-muted:   #9aada5;
  --footer-border:  rgba(232, 241, 237, .12);
  --footer-accent:  #4fd39b;

  color-scheme: light;
}

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

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

/* FIGURES CARRY NO MARGIN OF THEIR OWN. The UA stylesheet gives <figure> a
   `margin-inline: 40px`, which is invisible on a wide screen and expensive on
   a phone: the sample frames are grid items in a ONE-column gallery at 360px,
   so 80px came off a 312px content box and every example rendered at 232px —
   74% of the width available to it, inset in a gutter the design never asked
   for. The frames are sized by `aspect-ratio`, so the height shrank with it.
   TWO OF THE THREE FIGURES HAD ALREADY HAND-RESET IT (`.hero-figure`,
   `.lightbox-figure`), which is what makes this an oversight rather than a
   decision — `.shot` is the one nobody thought to reset, and it is the one
   that repeats sixteen times across seven pages. Reset it once, here, so the
   next <figure> added does not have to remember (§5, §14.3). */
figure { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The header is sticky, so an in-page anchor would otherwise land its target
     underneath it. Sticky (not fixed) is deliberate: it participates in normal
     flow, so no page needs a hand-maintained top offset that silently breaks
     the day the header grows a line. */
  scroll-padding-top: 5.5rem;
}

body {
  margin: 0;
  padding: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { margin: 0; line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); font-weight: var(--weight-bold); }
p { margin: 0; }

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-hover); text-decoration: underline; }

:where(a, button, summary, input, select, textarea):focus-visible {
  outline: 3px solid var(--accent-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}

/* ============================================================================
   LAYOUT PRIMITIVES
   ========================================================================= */

main { display: block; }

.wrap { width: 100%; max-width: var(--measure); margin: 0 auto; padding-inline: var(--space-5); }

/* Page shells for the non-landing surfaces. One container rule, three widths,
   so no view invents its own margins. */
.page { width: 100%; max-width: var(--measure); margin: 0 auto; padding: var(--space-7) var(--space-5) var(--space-9); }
.page--narrow { max-width: var(--measure-xs); }
.page--reading { max-width: var(--measure-sm); }
.page > h1 { font-size: var(--text-3xl); margin-bottom: var(--space-3); }

.band { padding-block: var(--space-9); }
.band--tint { background: var(--bg-subtle); border-block: 1px solid var(--border); }

.stack > * + * { margin-top: var(--space-4); }
.prose { max-width: var(--measure-sm); color: var(--text-muted); }
.center { text-align: center; }
.center .prose { margin-inline: auto; }

/* ============================================================================
   HEADER / FOOTER
   ========================================================================= */

.site-head {
  position: sticky; top: 0; z-index: 20;
  background: rgba(255, 255, 255, .88);
  backdrop-filter: saturate(1.6) blur(10px);
  border-bottom: 1px solid var(--border);
}
.nav {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  max-width: var(--measure); margin: 0 auto; padding: var(--space-3) var(--space-5);
}
/* ---- brand: logo + wordmark + tagline, as ONE link ---- */
.brand-link {
  display: inline-flex; align-items: center; gap: var(--space-3);
  min-width: 0;
  color: var(--text); text-decoration: none;
}
.brand-link:hover { text-decoration: none; }
.brand-link:hover .brand-name { color: var(--accent); }

.brand-logo {
  width: 2.75rem; height: 2.75rem;
  flex: 0 0 auto;
  /* The source is a 1024px square drawn to the edges of its own canvas, so it
     needs no cropping — just a matching aspect box so it can never stretch. */
  object-fit: contain;
  display: block;
}

.brand-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.brand-name {
  font-size: var(--text-lg); font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-tight);
  transition: color var(--transition);
}
.brand-tagline {
  font-size: var(--text-xs); font-style: italic; color: var(--text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.nav-links { display: flex; align-items: center; gap: var(--space-3); }
.nav-links > a:not(.btn) { color: var(--text-muted); font-size: var(--text-sm); font-weight: var(--weight-medium); }
.nav-links > a:not(.btn):hover { color: var(--text); text-decoration: none; }
.nav-links .btn .icon { width: 1rem; height: 1rem; vertical-align: 0; }

.nav-user { color: var(--text-subtle); font-size: var(--text-sm); max-width: 14rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Icon-only control (the profile mark on app pages). Square, so it keeps a
   comfortable tap target on a phone. */
.nav-icon {
  display: inline-grid; place-items: center;
  width: 2.4rem; height: 2.4rem;
  border-radius: var(--radius-pill);
  background: var(--accent-soft); color: var(--accent-press);
  transition: background var(--transition), color var(--transition);
}
.nav-icon:hover { background: var(--accent); color: var(--on-accent); text-decoration: none; }
.nav-icon .icon { width: 1.25rem; height: 1.25rem; vertical-align: 0; }

@media (max-width: 40rem) {
  /* The tagline is the first thing to go: the brand and the controls both have
     to survive, the strapline does not. */
  .brand-tagline { display: none; }
  .brand-logo { width: 2.25rem; height: 2.25rem; }
  .nav { padding-inline: var(--space-4); gap: var(--space-2); }
  .nav-links { gap: var(--space-2); }
  .brand-name { font-size: var(--text-base); }
}

/*
 * Below ~34rem the signed-in header runs out of room: brand (~153px) plus
 * Dashboard (~94px) plus Log out (~100px) plus gaps exceeds a 360px viewport,
 * and the row overflows sideways. The labels collapse to their icons — each
 * control keeps an aria-label, so nothing is lost to a screen reader, and the
 * square icon buttons are a better thumb target than the cramped pills were.
 */
@media (max-width: 34rem) {
  .nav-links .btn .btn-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  .nav-links .btn { padding-inline: var(--space-3); }
  .nav-links .btn .icon { width: 1.15rem; height: 1.15rem; }
}
@media (max-width: 52rem) {
  /* The signed-in address is useful, not essential — the controls beside it are. */
  .nav-user { display: none; }
}

/* ---- footer: the one dark surface on the site ----
   MEDIUM height on purpose. It has to carry the legal index a payment gateway
   looks for, and nothing beyond that — a sprawling footer on a three-product
   site is padding pretending to be substance. */
.site-foot {
  margin-top: var(--space-8);
  background: var(--footer-bg);
  color: var(--footer-text);
  font-size: var(--text-sm);
}
.site-foot a { color: var(--footer-text); }
.site-foot a:hover { color: var(--footer-accent); text-decoration: none; }

.foot-inner {
  display: grid; gap: var(--space-6);
  grid-template-columns: 1.3fr 1fr 1.3fr;
  max-width: var(--measure); margin: 0 auto;
  padding: var(--space-6) var(--space-5) var(--space-5);
}
@media (max-width: 52rem) { .foot-inner { grid-template-columns: 1fr 1fr; gap: var(--space-5); } }
@media (max-width: 34rem) { .foot-inner { grid-template-columns: 1fr; } }

.foot-brand-link { display: inline-flex; align-items: center; gap: var(--space-3); text-decoration: none; }
.foot-brand-link img { display: block; width: 2.5rem; height: 2.5rem; object-fit: contain; }
.foot-brand-name { font-size: var(--text-lg); font-weight: var(--weight-bold); letter-spacing: var(--tracking-tight); color: var(--footer-text); }
.foot-descriptor { margin-top: var(--space-2); color: var(--footer-muted); max-width: 20rem; }

.foot-head {
  margin-bottom: var(--space-3);
  font-size: var(--text-xs); font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-wide); text-transform: uppercase;
  color: var(--footer-accent);
}

.foot-links, .foot-contact { margin: 0; padding: 0; list-style: none; }
.foot-links li { margin-bottom: var(--space-2); }
.foot-links a { color: var(--footer-muted); }
.foot-links a:hover { color: var(--footer-accent); }

.foot-address { display: flex; flex-direction: column; font-style: normal; color: var(--footer-muted); margin-bottom: var(--space-3); }
.foot-company { color: var(--footer-text); font-weight: var(--weight-bold); }
.foot-contact li { margin-bottom: var(--space-2); color: var(--footer-muted); }
.foot-contact a { color: var(--footer-text); overflow-wrap: anywhere; }

.foot-bottom {
  border-top: 1px solid var(--footer-border);
}
.foot-bottom > * { max-width: var(--measure); margin-inline: auto; padding-inline: var(--space-5); }
.foot-copy { padding-top: var(--space-4); color: var(--footer-text); font-weight: var(--weight-medium); }
.foot-legalese { padding-block: var(--space-1) var(--space-5); color: var(--footer-muted); font-size: var(--text-xs); max-width: var(--measure); line-height: var(--leading-snug); }

/* ============================================================================
   LEGAL / POLICY PAGES
   ============================================================================
   One layout for all six. Long-form reading, so: a narrow measure, generous
   leading, and numbered headings that can be scanned by someone checking
   whether clause 7 exists.
   ========================================================================= */

.legal { padding-block: var(--space-7) var(--space-9); }

.legal-head { max-width: var(--measure-sm); margin: 0 auto var(--space-6); }
.legal-head h1 { font-size: var(--text-3xl); margin-bottom: var(--space-2); }
.legal-date { color: var(--text-subtle); font-size: var(--text-sm); }

.back-link {
  display: inline-flex; align-items: center; gap: var(--space-2);
  margin-bottom: var(--space-5);
  padding: var(--space-2) var(--space-4) var(--space-2) var(--space-3);
  border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text-muted); font-size: var(--text-sm); font-weight: var(--weight-medium);
  transition: border-color var(--transition), color var(--transition), background var(--transition);
}
.back-link:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); text-decoration: none; }
.back-link .icon { width: 1.05rem; height: 1.05rem; vertical-align: 0; }

.legal-card {
  max-width: var(--measure-sm); margin: 0 auto;
  padding: var(--space-7);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-md);
}
@media (max-width: 40rem) { .legal-card { padding: var(--space-5); } }

.legal-section + .legal-section { margin-top: var(--space-6); padding-top: var(--space-6); border-top: 1px solid var(--border); }
.legal-section h2 { font-size: var(--text-xl); margin-bottom: var(--space-3); }
.legal-n { color: var(--accent); margin-right: var(--space-1); }
.legal-section p { color: var(--text-muted); }
.legal-section p + p { margin-top: var(--space-3); }

.legal-group-label { margin-top: var(--space-4); color: var(--text) !important; font-weight: var(--weight-bold); }
.legal-list { margin: var(--space-3) 0 0; padding: 0; list-style: none; }
.legal-list li { position: relative; margin-bottom: var(--space-2); padding-left: var(--space-5); color: var(--text-muted); }
.legal-list li::before {
  content: ""; position: absolute; left: var(--space-2); top: .62em;
  width: .35rem; height: .35rem; border-radius: var(--radius-pill); background: var(--accent);
}
.legal-list + p { margin-top: var(--space-4); }

.legal-address { display: flex; flex-direction: column; font-style: normal; color: var(--text-muted); margin-bottom: var(--space-4); }
.legal-address span:first-child { color: var(--text); font-weight: var(--weight-bold); }

.legal-contact { margin: 0; padding: 0; list-style: none; }
.legal-contact li { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-2); }
.legal-contact-label { color: var(--text-muted); }
.legal-contact a { overflow-wrap: anywhere; }
.legal-section p { overflow-wrap: break-word; }

/* ============================================================================
   BUTTONS
   ========================================================================= */

/* A VARIANT-LESS .btn IS STILL A BUTTON, and it did not used to be: the base
   rule set a TRANSPARENT border and no background at all, so `class="btn"` on
   its own rendered as a line of bold text floating on the page ground. It read
   as disabled — reported as "looks greyed out but is clickable" — because
   nothing about it said control. Every bare .btn is in the panel (Back to
   client, Cancel, Suggest written brief, and five more); no public page uses
   one, so this rule reaches only those. The variants all set their own
   background and border-color and are declared after it, so primary, ghost and
   locked are untouched — verified by asserting their computed styles, not by
   reading the cascade. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border-strong); border-radius: var(--radius-md);
  color: var(--text);
  font: inherit; font-size: var(--text-sm); font-weight: var(--weight-bold);
  text-align: center; text-decoration: none; cursor: pointer;
  transition: background var(--transition), border-color var(--transition),
              color var(--transition), transform var(--transition), box-shadow var(--transition);
}
/* `text-decoration: none` is load-bearing, not tidy-up: a .btn is often an
   ANCHOR, and the global a:hover adds an underline that this rule is the only
   thing suppressing. It was its entire previous content. */
.btn:hover {
  background: var(--surface-sunk); border-color: var(--accent);
  color: var(--accent); text-decoration: none;
}
.btn.lg { padding: var(--space-4) var(--space-6); font-size: var(--text-base); }
.btn.small { padding: var(--space-2) var(--space-3); font-size: var(--text-xs); }

/* TOUCH TARGETS. A .btn.small is ~34px tall, which is fine for a cursor and
   too small for a thumb — and Copy Prompt, the single most important control
   in the product, is one. Applied by input type rather than by width: a small
   laptop is not a phone, and a large tablet is. */
@media (pointer: coarse) {
  .btn { min-height: 2.75rem; }
  .btn.small { min-height: 2.75rem; padding-inline: var(--space-4); }
  .nav-icon { width: 2.75rem; height: 2.75rem; }
  .back-link { min-height: 2.75rem; }
  .card-full > summary { padding-block: var(--space-2); }
}

.btn.primary { background: var(--accent); color: var(--on-accent); box-shadow: var(--shadow-sm); }
.btn.primary:hover { background: var(--accent-hover); box-shadow: var(--shadow-md); transform: translateY(-1px); }
.btn.primary:active { background: var(--accent-press); transform: translateY(0); }

.btn.ghost { background: var(--surface); border-color: var(--border-strong); color: var(--text); }
.btn.ghost:hover { background: var(--surface-sunk); border-color: var(--accent); color: var(--accent); }

/* LOCKED — a product you do not own yet.
   Amber, taken from the same tokens as the LOCKED pill sitting directly above
   it on the card, so the two read as one state. Deliberately NOT the solid
   accent of the owned card's Open button: those two buttons sit side by side
   in the same grid, and identical styling made "yours" and "not yours"
   indistinguishable at a glance. Outline, not solid, so the paid product still
   carries the only filled button on the page. */
.btn.locked {
  background: var(--warning-soft);
  border-color: var(--warning-line);
  color: var(--warning);
}
.btn.locked:hover {
  background: var(--warning);
  border-color: var(--warning);
  color: var(--text-invert);
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}
.btn.locked:active { transform: translateY(0); box-shadow: var(--shadow-sm); }

.btn.disabled, .btn:disabled { opacity: .55; cursor: default; box-shadow: none; transform: none; }
.btn.disabled:hover, .btn:disabled:hover { background: var(--surface); border-color: var(--border-strong); color: var(--text); transform: none; }

/* ============================================================================
   PILLS / BADGES / EYEBROWS
   ========================================================================= */

.pill, .kicker, .badge, .eyebrow {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-xs); font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-wide); text-transform: uppercase;
}
.kicker { color: var(--accent); margin-bottom: var(--space-3); }
.eyebrow { color: var(--text-subtle); margin-bottom: var(--space-3); }
.pill, .badge {
  align-self: flex-start; padding: var(--space-1) var(--space-3);
  border: 1px solid transparent; border-radius: var(--radius-pill);
  background: var(--accent-soft); color: var(--accent-press);
  letter-spacing: .08em;
}
.badge { margin-bottom: var(--space-3); }

/* ============================================================================
   FORMS  (shared by login and every prompt-app)
   ========================================================================= */

.panel {
  padding: var(--space-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}

.field { margin-bottom: var(--space-4); }
.field:last-of-type { margin-bottom: 0; }

label {
  display: block;
  margin-bottom: var(--space-2);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  color: var(--text);
}
.optional { font-weight: var(--weight-normal); color: var(--text-subtle); }

/* EVERY TEXTUAL INPUT, not four of them. This selector used to list only
   text / email / select / textarea, so `search`, `number`, `date` and `tel`
   inputs fell through to the browser default — which is why the clients search
   box and the quota fields rendered small and unstyled next to everything
   around them. They are the same control and they look it now. */
input[type="text"], input[type="email"], input[type="search"],
input[type="number"], input[type="date"], input[type="tel"],
input[type="url"], input[type="password"], select, textarea {
  width: 100%;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  /* 16px, NOT --text-sm (15px). iOS Safari zooms the whole page when a field
     smaller than 16px takes focus, and then leaves the user zoomed in — on the
     app page that means fighting the viewport between every input. One pixel
     of type size buys the entire mobile input experience. */
  font-size: var(--text-base);
  transition: border-color var(--transition), box-shadow var(--transition);
}
input::placeholder { color: var(--text-subtle); }

input[type="text"]:focus, input[type="email"]:focus, input[type="search"]:focus,
input[type="number"]:focus, input[type="date"]:focus, input[type="tel"]:focus,
input[type="url"]:focus, input[type="password"]:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}

input[aria-invalid="true"] { border-color: var(--danger); }

.error { margin-top: var(--space-3); color: var(--danger); font-size: var(--text-sm); }

.options {
  display: grid; gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
  margin-top: var(--space-4);
}
.options .field { margin-bottom: 0; }
/* A field that spans every column of whatever grid it sits in. Used by
   multiline extras, whose question invites a sentence. */
.field--wide { grid-column: 1 / -1; }

textarea {
  resize: vertical;
  min-height: 4.5rem;
  line-height: var(--leading-snug);
  font-family: inherit;
}

/* A fieldset used purely for grouping — its border and padding are the
   browser's, not ours, and both are unwanted here. The legend is styled to
   match a label so a grouped control and a plain one read the same. */
.field-group { border: 0; padding: 0; margin: 0; min-width: 0; }
.field-group legend {
  padding: 0;
  margin-bottom: var(--space-2);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  color: var(--text);
}

/* ---- segmented option control ----
   An option whose config asks for control:"segmented" (§7 — generic, any
   product may). A radio group, not a dropdown: used for the one setting whose
   choice changes what the buyer has to DO before pasting, which has to be
   visible without opening anything.

   Real <input type="radio">s under the labels, so keyboard arrows, the form
   model and screen readers all work as they already do — the styling is only
   the pill; the control underneath is the browser's. */
.segmented {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: var(--space-1);
  padding: var(--space-1);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface-sunk);
}
@media (max-width: 26rem) {
  /* Two labels of real words do not fit side by side on a small phone, and a
     truncated toggle is a toggle nobody reads. Stack instead. */
  .segmented { grid-auto-flow: row; }
}
.segmented-opt { position: relative; margin: 0; }
/* Off-screen rather than display:none — a hidden input is not focusable, and
   the focus ring is how a keyboard user knows where they are. */
.segmented-opt input {
  position: absolute;
  width: 1px; height: 1px;
  opacity: 0;
  margin: 0;
}
.segmented-opt span {
  display: block;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  line-height: var(--leading-snug);
  text-align: center;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}
.segmented-opt input:checked + span {
  background: var(--surface);
  color: var(--accent-press);
  box-shadow: var(--shadow-sm);
}
.segmented-opt input:focus-visible + span {
  outline: 3px solid var(--accent-ring);
  outline-offset: 2px;
}
@media (pointer: coarse) {
  /* Same rule as .btn (§18.2): a real thumb target, keyed on pointer type. */
  .segmented-opt span { min-height: 2.75rem; display: grid; place-items: center; }
}

@media (pointer: coarse) {
  /* §18.2 again, for the controls a buyer actually taps most. Padding plus
     16px type already clears 44px on every browser measured, but stating the
     floor means a future padding tweak cannot quietly drop under it. */
  select, input[type="text"], input[type="email"], textarea { min-height: 2.75rem; }
}

/* The control a preset menu's "something else" option reveals. Sits under the
   select rather than replacing it, so the buyer can still see — and go back
   to — the list they stepped out of. */
.field-custom { margin-top: var(--space-2); }

/* ---- per-choice help ----
   Always visible, never hover-only: most buyers are on a phone, where there is
   no hover, and the line this carries ("upload 1 product photo") is an
   instruction rather than a nicety. */
.field-help {
  margin-top: var(--space-2);
  color: var(--text-subtle);
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
}

.actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-5); }

/* ============================================================================
   HERO / SECTIONS
   ========================================================================= */

.hero { padding-block: var(--space-10) var(--space-8); max-width: 48rem; }
.hero h1 { font-size: var(--text-4xl); margin-bottom: var(--space-4); }
.hero h1 .hl {
  background: linear-gradient(180deg, transparent 62%, var(--accent-soft) 62%);
  padding-inline: .12em;
}
.hero-sub { font-size: var(--text-lg); color: var(--text-muted); max-width: var(--measure-sm); margin-bottom: var(--space-6); }
.hero-cta { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-bottom: var(--space-4); }

.trust { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-5); color: var(--text-subtle); font-size: var(--text-sm); }
.trust span { display: inline-flex; align-items: center; gap: var(--space-2); }
.trust span::before { content: "✓"; color: var(--success); font-weight: var(--weight-bold); }

.section-head { max-width: var(--measure-sm); margin-bottom: var(--space-6); }
.section-head h2 { font-size: var(--text-2xl); margin-bottom: var(--space-2); }
.section-head p { color: var(--text-muted); }

/* Sales-page sections: rules between blocks rather than tinted bands, so a
   long-form page stays readable without stripes. */
.section { padding-block: var(--space-7); border-top: 1px solid var(--border); }
.section > h2 { font-size: var(--text-2xl); margin-bottom: var(--space-4); }
.section p { max-width: var(--measure-sm); color: var(--text-muted); }

/* ============================================================================
   TILES  (the shared card — landing grid and dashboard grid)
   ========================================================================= */

/* THREE per row on desktop, two at tablet, one on a phone — stated explicitly
   rather than left to auto-fill, so the count is a decision and not a function
   of how many products happen to exist. Matches the prompt-card grid on the
   app page, so both surfaces read the same. */
.grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 62rem) { .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 40rem) { .grid { grid-template-columns: 1fr; } }

.tile {
  display: flex; flex-direction: column;
  padding: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition), border-color var(--transition), transform var(--transition);
}
.tile:hover { box-shadow: var(--shadow-lg); border-color: var(--border-strong); transform: translateY(-2px); }
.tile h2, .tile h3 { font-size: var(--text-xl); margin-bottom: var(--space-2); }
.tile h3 a { color: var(--text); }
.tile h3 a:hover { color: var(--accent); text-decoration: none; }
.tile .blurb { color: var(--text-muted); font-size: var(--text-sm); margin-bottom: var(--space-4); }

.tile-price { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-4); font-size: var(--text-2xl); font-weight: var(--weight-bold); }
.tile-price .rising { font-size: var(--text-sm); font-weight: var(--weight-normal); color: var(--text-subtle); }
.tile-actions { margin-top: auto; }
.tile-actions .btn { width: 100%; }

/* ============================================================================
   LISTS: steps and ticks
   ========================================================================= */

.steps { display: grid; gap: var(--space-4); margin: 0; padding: 0; list-style: none; grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr)); }
.steps li { padding: var(--space-5); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); }
.steps h3 { font-size: var(--text-lg); margin: var(--space-3) 0 var(--space-2); }
.steps p { color: var(--text-muted); font-size: var(--text-sm); }
.steps.compact { grid-template-columns: 1fr; }
.steps.compact li { display: flex; gap: var(--space-4); align-items: baseline; }
.steps.compact p { margin: 0; }

.step-n {
  display: inline-grid; place-items: center; flex: 0 0 auto;
  width: 2.25rem; height: 2.25rem;
  border-radius: var(--radius-md);
  background: var(--accent-soft); color: var(--accent-press);
  font-size: var(--text-sm); font-weight: var(--weight-bold);
}

.ticks { max-width: var(--measure-sm); margin: 0; padding: 0; list-style: none; }
.ticks li { position: relative; margin-bottom: var(--space-3); padding-left: var(--space-6); color: var(--text-muted); }
.ticks li::before { content: "✓"; position: absolute; left: 0; color: var(--success); font-weight: var(--weight-bold); }

/* ============================================================================
   BUY BOX  (sales page)
   ========================================================================= */

.buy-box {
  margin-top: var(--space-6);
  padding: var(--space-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}
.price-big { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-2); font-size: var(--text-3xl); font-weight: var(--weight-bold); letter-spacing: var(--tracking-tight); }
.price-big .muted { font-size: var(--text-sm); font-weight: var(--weight-normal); }
.buy-box .btn { margin-top: var(--space-4); }

/* ============================================================================
   CLOSING / UTILITIES
   ========================================================================= */

.closing { text-align: center; padding-block: var(--space-9); }
.closing h2 { font-size: var(--text-3xl); margin-bottom: var(--space-3); }
.closing p { color: var(--text-muted); margin-bottom: var(--space-5); max-width: none; }

.lede { font-size: var(--text-lg); color: var(--text-muted); }
.meta { font-size: var(--text-sm); }
.muted { color: var(--text-muted); }
.small { font-size: var(--text-sm); }
.empty { color: var(--text-muted); text-align: center; padding-block: var(--space-7); }

.mt-4 { margin-top: var(--space-4); }

code {
  padding: .1rem .35rem;
  border-radius: var(--radius-sm);
  background: var(--surface-sunk);
  font-family: var(--font-mono);
  font-size: .85em;
}

@media (max-width: 40rem) {
  .hero { padding-block: var(--space-8) var(--space-6); }
  .hero-cta .btn { width: 100%; }
  .band { padding-block: var(--space-7); }
  .page { padding-block: var(--space-6) var(--space-8); }
}

/* ============================================================================
   OFFER PAGE  —  long-form sales layout
   ============================================================================
   Shared components, not one page's private styling: any future offer page
   (another currency, another market) gets the same rhythm by reusing these.

   THE PROBLEM THIS SOLVES: the approved copy is deliberately full. Long copy
   converts, but only if it reads as a series of glanceable objects rather than
   a wall. So everything here is doing one of three jobs — cutting text into
   cards, giving each card an icon anchor, or putting air between them.
   ========================================================================= */

/* ---- icons ----
   Inline SVG, currentColor, sized in ems so an icon scales with the text it
   sits beside instead of needing a size per placement. */
.icon { width: 1.15em; height: 1.15em; flex: 0 0 auto; display: inline-block; vertical-align: -.18em; }

.icon-badge {
  display: inline-grid; place-items: center; flex: 0 0 auto;
  width: 2.5rem; height: 2.5rem;
  border-radius: var(--radius-md);
  background: var(--accent-soft); color: var(--accent-press);
}
.icon-badge .icon { width: 1.35rem; height: 1.35rem; vertical-align: 0; }

/* ============================================================================
   HERO
   ========================================================================= */

.offer-hero { padding-block: var(--space-9) var(--space-8); }

/* Copy left, phone right. One column until there is genuinely room for two —
   below that the phone would squeeze the headline into a narrow ribbon. */
/* START-aligned, not centred. Centring the phone against a tall copy column
   pushed it down and left the top-right corner empty, which read as a layout
   mistake rather than as whitespace. Aligned to the top it sits level with the
   first line of the headline and runs down beside the copy. */
.offer-hero-grid { display: grid; gap: var(--space-8); align-items: start; }
@media (min-width: 62rem) {
  .offer-hero-grid { grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr); gap: var(--space-9); }
  /* Optical, not mechanical: the headline's cap-height starts a few px below
     the top of its line box, so the frame is nudged down to look level with
     "Create" rather than measuring level with it. */
  .offer-hero-visual { padding-top: .6rem; }
}

/* ---- THE SALES-PAGE SECTION BODY SIZE — the landing's rule, shared ----
   Same problem the landing had and the same answer: the body prose here read at
   --text-base, and the step, benefit and comparison bodies at --text-sm, so a
   paragraph looked like a footnote beside a card making the same kind of point.

   SAME VALUE AS THE LANDING, POINTED AT THE SAME TOKEN. --text-lg is the one
   source; neither rule hand-tunes a number, and a test reads both rules and
   asserts they still agree, because "match the homepage" is the sort of promise
   that quietly stops being true.

   ALL SEVEN SALES PAGES NOW, through the one view that renders them. This began
   as `.offer-body-lg`, an opt-in modifier a polished offer declared, because
   the first page through the pass must not resize six that had not been
   reviewed. All seven have been, so the flag is gone and `.offer-page` is
   simply on every sales page.

   STILL SCOPED, THOUGH, and that is not leftover caution: `.section-head p` and
   `.prose` belong to the LANDING page as well, so unscoping these rules would
   resize a page this has nothing to do with. The scope is what makes "sales
   pages read bigger" a statement about sales pages.

   HEADINGS ARE NOT BODY and are untouched — a bump that moved them too would
   flatten the hierarchy rather than fix the readability.
   `.solution-body` is already --text-lg and needs no entry here. */
.offer-page .section-head p,
.offer-page .pain-card p,
.offer-page .callout p,
.offer-page .benefit-lead,
.offer-page .benefit-detail,
.offer-page .steps--offer p,
.offer-page .compare-row span:not(.icon-badge),
.offer-page .compare-foot,
.offer-page .guarantee p,
.offer-page .cta-block > p:not(.cta-note),
.offer-page .prose {
  font-size: var(--text-lg);
}

/* ---- a hero holding SEVERAL stacked samples ----
   A 16:9 thumbnail is roughly half the height of a 9:16 status, so one of them
   beside a full sales pitch leaves an obvious hole under it. An offer whose
   format is short declares two images and this stacks them evenly.

   SCOPED TO THE MULTI-IMAGE CASE, not applied to `.offer-hero-visual` itself:
   that element is on all seven sales pages, and giving it a grid gap outright
   would re-space the caption under every single-image and wireframe hero. The
   caption's own margin is zeroed here so the gap is the only spacing rule in
   play, rather than two of them adding up. */
.offer-hero-visual--stack { display: grid; gap: var(--space-4); }
.offer-hero-visual--stack .phone-caption { margin-top: 0; }

/* ============================================================================
   THE LIGHTBOX — click a thumbnail, see it full size
   ============================================================================
   OPT-IN PER OFFER (`lightbox: true` -> the trigger markup and one <dialog>).
   The same reasoning the body scale used: ONE view renders all seven sales
   pages, and a page that gains a script is a page whose "reads with no
   JavaScript" claim has to be re-argued. It is argued for THIS page — the
   thumbnails are the product and looking closer at one is the point — and the
   other six are untouched until someone makes that case for them.

   THE TRIGGER IS A REAL <button>, never a div with a handler: it has to be
   reachable by Tab and operable by Enter and Space, and only a button is all
   three for free. It is deliberately NOT `.btn` — that class carries the
   product's button styling and a 2.75rem tap floor (§18.2) that would push a
   16:9 frame out of shape. */
.zoom-trigger {
  display: block;
  width: 100%; height: 100%;
  padding: 0; border: 0;
  background: none;
  cursor: zoom-in;
  position: relative;
}
.zoom-trigger:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }

/* The affordance. Nothing about an image says "this one opens", so a magnifier
   fades in on hover and on keyboard focus. */
.zoom-hint {
  position: absolute;
  right: .5rem; bottom: .5rem;
  display: grid; place-items: center;
  width: 2rem; height: 2rem;
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-md);
  opacity: 0;
  transition: opacity var(--transition);
}
.zoom-hint .icon { width: 1.1rem; height: 1.1rem; }
.zoom-trigger:hover .zoom-hint,
.zoom-trigger:focus-visible .zoom-hint { opacity: 1; }
/* A device that cannot hover would never show the hint at all, so on touch it
   is simply always there — the badge IS the only cue that the image opens.
   BOTH QUERIES, because they answer different questions and a device can fail
   one while matching the other: `hover: none` is "this pointer cannot hover"
   and `pointer: coarse` is "this pointer is imprecise". A stylus is fine-but-
   hoverless and a TV remote is coarse-but-hovering; either way the hint has to
   be visible, because neither can reveal it. */
@media (hover: none), (pointer: coarse) { .zoom-hint { opacity: 1; } }

/* A CLOSED <dialog> IS HIDDEN, stated by us rather than left to the UA. Every
   browser that implements <dialog> ships `dialog:not([open]) { display: none }`
   — but one that does NOT implement it has no such rule, and then the lightbox
   below renders as a full-width, full-height block in the page flow: an empty
   box with a stray close button under the footer. The script degrades to a
   button that does nothing (§21.1); the markup has to degrade too. */
dialog:not([open]) { display: none; }

/* ---- the dialog ----
   A NATIVE <dialog>, so focus containment, Escape and inertness are the
   platform's job rather than ours. Its UA styles have to be undone first: a
   dialog ships with a border, padding, a white background and an auto width. */
.lightbox {
  width: 100%; max-width: 100%;
  height: 100%; max-height: 100%;
  margin: 0; padding: var(--space-5);
  border: 0;
  background: transparent;
  overscroll-behavior: contain;
}
.lightbox::backdrop { background: var(--overlay); }
.lightbox[open] { display: grid; place-items: center; }
.lightbox-figure {
  display: grid; gap: var(--space-3);
  justify-items: center;
  margin: 0;
  max-width: 100%; max-height: 100%;
}
/* THE IMAGE FITS THE VIEWPORT, both axes. Capping width alone lets a tall
   image run off the bottom of a phone with no way to scroll to it, since the
   page behind is locked. `min()` against the padding keeps the gutter. */
.lightbox-figure img {
  display: block;
  width: auto; height: auto;
  max-width: 100%;
  max-height: calc(100vh - 8rem);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.lightbox-caption { color: var(--text-invert); font-size: var(--text-sm); text-align: center; }
/* The close control. 2.75rem square is the coarse-pointer tap floor (§18.2),
   applied here at every pointer type because a mis-tap that fails to close an
   overlay leaves someone stuck behind it. */
.lightbox-close {
  position: absolute;
  top: var(--space-4); right: var(--space-4);
  display: grid; place-items: center;
  width: 2.75rem; height: 2.75rem;
  padding: 0;
  border: 1px solid var(--footer-border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  font-size: var(--text-lg); line-height: 1;
  cursor: pointer;
}
.lightbox-close:hover { background: var(--surface-sunk); }
.lightbox-close:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
/* Scroll lock: showModal() makes the page inert but does not stop it
   scrolling, and a backdrop that scrolls reads as broken. The script pays the
   scrollbar width back as padding so the page cannot jump sideways. */
body.is-zoomed { overflow: hidden; }

.lightbox[open] { animation: lightbox-in var(--transition); }
@keyframes lightbox-in { from { opacity: 0; } to { opacity: 1; } }
/* Someone who asked the system to stop moving things gets it open, not
   animated — the dialog still works exactly the same. */
@media (prefers-reduced-motion: reduce) {
  .lightbox[open] { animation: none; }
  .zoom-hint { transition: none; }
}

.offer-hero-copy { max-width: 40rem; }

/* Bigger than the site's normal h1: this is the one headline that has to stop
   someone mid-scroll. */
.offer-hero h1 {
  font-size: clamp(2.4rem, 1.6rem + 3.6vw, 3.8rem);
  line-height: 1.08;
  margin-bottom: var(--space-5);
}
/* THE HIGHLIGHT WRAPS. It used to be `white-space: nowrap`, to stop the icon
   being orphaned from its word by the newline icon.ejs emits around its SVG —
   and that guard CLIPPED the LinkedIn headline, whose highlighted phrase plus
   icon measures ~570px against a ~506px copy column. An unbreakable span wider
   than its track does not widen the track (both are `minmax(0, …)`); it
   overflows, and the last word is simply cut off.
   THE ORPHAN IS FIXED WHERE IT IS CAUSED, in the template: the include is
   trimmed, so the icon and its first word are one unit by construction and
   nothing here has to forbid wrapping. The longest unbreakable run is now a
   WORD, and a test measures the longest one on every hero against the column
   it has to fit. */
.offer-hero h1 { overflow-wrap: break-word; }
.offer-hero h1 .hl .icon { color: var(--accent); margin-right: .12em; }
.offer-hero .hero-sub { font-size: var(--text-lg); margin-bottom: var(--space-6); max-width: 34rem; }

.kicker-pill {
  display: inline-flex; align-items: center; gap: var(--space-2);
  margin-bottom: var(--space-4);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--accent-soft); color: var(--accent-press);
  font-size: var(--text-xs); font-weight: var(--weight-bold);
  letter-spacing: .06em; text-transform: uppercase;
}

/* ---- price + CTA ---- */
.price-row { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-3); margin-bottom: var(--space-5); }
.price-tag {
  font-size: clamp(2.6rem, 2rem + 2.4vw, 3.4rem);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-tight);
  line-height: 1;
  color: var(--accent-press);
}
.price-note { color: var(--text-subtle); font-size: var(--text-base); }

/* The CTA must be the loudest object on the page. A soft accent halo does the
   work a pulse animation would, without moving anything. */
.btn.cta {
  padding: var(--space-4) var(--space-7);
  font-size: var(--text-lg);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md), 0 0 0 .35rem var(--accent-ring);
}
.btn.cta:hover { box-shadow: var(--shadow-lg), 0 0 0 .5rem var(--accent-ring); }
.btn.cta:active { box-shadow: var(--shadow-sm), 0 0 0 .25rem var(--accent-ring); }

.cta-note { margin-top: var(--space-3); color: var(--text-muted); font-size: var(--text-sm); }
.cta-block { text-align: center; padding-block: var(--space-8); }
.cta-block h2 { font-size: var(--text-3xl); margin-bottom: var(--space-3); max-width: 34rem; margin-inline: auto; }
.cta-block p { color: var(--text-muted); margin-bottom: var(--space-5); }

@media (max-width: 40rem) {
  .btn.cta { width: 100%; padding-inline: var(--space-4); font-size: var(--text-base); }
  /* The `white-space: normal` that used to sit here released the nowrap above
     on a phone only — which is why the clipping was a DESKTOP bug. The rule it
     released is gone, so this is too. */
}

/* ============================================================================
   PHONE MOTIF
   ============================================================================
   A 9:16 rounded frame with a speaker pill. Used for the hero mock and every
   gallery slot, so a buyer recognises the shape as their own Status before
   reading a word — and so a real screenshot drops in later at exactly the
   aspect it was shot at, with no reflow.
   ========================================================================= */

.phone {
  position: relative;
  aspect-ratio: 9 / 16;
  padding: .5rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-xl);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}
.phone::before {
  content: "";
  position: absolute; top: .95rem; left: 50%; transform: translateX(-50%);
  width: 22%; height: .25rem;
  border-radius: var(--radius-pill);
  background: var(--border-strong);
  z-index: 1;
}
.phone-screen {
  width: 100%; height: 100%;
  overflow: hidden;
  border-radius: calc(var(--radius-xl) - .35rem);
  background: var(--surface-sunk);
}
.phone-screen img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Hero mock: the three-frame idea, drawn rather than photographed, so the page
   explains the product before any real sample exists. */
.phone--hero { max-width: 17rem; margin-inline: auto; }
.phone-mock {
  display: grid; grid-template-rows: 1.15fr 1fr 1fr auto; gap: .4rem;
  width: 100%; height: 100%;
  padding: 1.6rem .6rem .6rem;
  background: linear-gradient(160deg, var(--accent-soft), var(--surface) 65%);
}
.mock-frame {
  display: grid; align-content: center; gap: .32rem;
  padding: .55rem;
  border-radius: var(--radius-md);
  background: var(--surface);
  border: 1px solid var(--border);
}
.mock-line { height: .42rem; border-radius: var(--radius-pill); background: var(--border-strong); }
.mock-line.is-lead { height: .62rem; background: var(--accent); width: 82%; }
.mock-line.is-short { width: 55%; }
.mock-band {
  display: grid; place-items: center;
  padding: .45rem;
  border-radius: var(--radius-md);
  background: var(--accent);
  color: var(--on-accent);
  font-size: .5rem; font-weight: var(--weight-bold);
  letter-spacing: .12em; text-transform: uppercase;
}
.phone-caption { margin-top: var(--space-3); text-align: center; color: var(--text-subtle); font-size: var(--text-xs); }

/* ============================================================================
   THE THUMBNAIL FRAME (16:9)
   ============================================================================
   The phone's counterpart for a video pack: a 16:9 rounded frame used for the
   hero mock and every gallery slot, so a buyer recognises the shape as their
   own thumbnail before reading a word — and so a real 1280x720 file drops in
   later at exactly the aspect it was shot at, with no reflow.

   Same tokens, same shadows, same hover as .phone. Deliberately NOT a copy of
   any platform's player chrome: a drawn YouTube logo would be Google's
   trademark on a page selling an unaffiliated product. */

.thumb {
  position: relative;
  aspect-ratio: 16 / 9;
  padding: .4rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}
.thumb-screen {
  position: relative;
  width: 100%; height: 100%;
  overflow: hidden;
  border-radius: calc(var(--radius-lg) - .3rem);
  background: var(--surface-sunk);
}
.thumb-screen img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Hero mock: a bold word block over a scene, which is exactly what the pack
   produces — text prominent, but leaving room for the subject (§15A.9). */
.thumb--hero { max-width: 30rem; margin-inline: auto; }
.thumb--hero .thumb-screen {
  /* Flat colour FIRST: where color-mix is unsupported the gradient below is
     dropped, and without this the frame would render transparent (§14). */
  background: var(--accent-soft);
  background: linear-gradient(140deg, var(--accent-soft), var(--surface) 72%);
}
/* The subject: an off-centre disc standing in for a face or product, sitting
   in the space the overlay deliberately leaves clear. */
.thumb-subject {
  position: absolute;
  right: 8%; bottom: 0;
  width: 34%; aspect-ratio: 1;
  border-radius: 50% 50% 0 0;
  background: var(--accent-2);
  opacity: .55;
}
.thumb-overlay {
  position: absolute;
  left: 6%; top: 16%;
  display: grid; gap: .5rem;
  width: 48%;
}
.thumb-word { height: .85rem; border-radius: var(--radius-sm); background: var(--accent); }
.thumb-word.is-lead { height: 1.15rem; width: 100%; }
.thumb-word:not(.is-lead) { width: 68%; background: var(--text); opacity: .8; }
/* The duration chip every video carries — a generic convention, not a logo. */
.thumb-chip {
  position: absolute;
  right: .5rem; bottom: .5rem;
  padding: .1rem .3rem;
  border-radius: var(--radius-sm);
  background: var(--text);
  color: var(--surface);
  font-size: .5rem; font-weight: var(--weight-bold);
  letter-spacing: .04em;
}

@media (max-width: 62rem) {
  /* On a phone the hero stacks, and a 16:9 frame at full width is short
     enough to sit comfortably above the fold with the CTA. */
  .thumb--hero { max-width: 24rem; }
}

/* ============================================================================
   THE FEED-POST FRAME (4:5)
   ============================================================================
   The third product shape, beside .phone (9:16) and .thumb (16:9). Same
   tokens, same shadow, same hover — and the hero mock is literally the
   composition rule those prompts carry: headline at the top, product dominant,
   a bold CTA button at the bottom. */

.post {
  position: relative;
  aspect-ratio: 4 / 5;
  padding: .4rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}
/* PURE GEOMETRY, like .phone-screen and .thumb-screen. It used to carry the
   wireframe's padding, grid and gradient, which is right for grey bars and
   wrong for a photograph — an image would have sat INSET with a tinted border
   around it. The mock's own box (.post-mock) carries that styling now, so a
   real 4:5 sample fills the frame edge to edge with no modifier class and no
   `:has()`. */
.post-screen {
  position: relative;
  width: 100%; height: 100%;
  overflow: hidden;
  border-radius: calc(var(--radius-lg) - .3rem);
  background: var(--surface-sunk);
}
.post-screen img { width: 100%; height: 100%; object-fit: cover; display: block; }

.post--hero { max-width: 20rem; margin-inline: auto; }
/* The wireframe's own box: everything .post-screen used to declare. */
.post-mock {
  display: grid;
  align-content: space-between;
  gap: var(--space-3);
  width: 100%; height: 100%;
  padding: var(--space-4);
  /* Flat colour FIRST: where color-mix is unsupported the gradient below is
     dropped, and without this the frame would render transparent (§14). */
  background: var(--accent-soft);
  background: linear-gradient(160deg, var(--accent-soft), var(--surface) 70%);
}
.post-head { display: grid; gap: .4rem; }
.post-word { height: .7rem; border-radius: var(--radius-sm); background: var(--text); opacity: .8; }
.post-word.is-lead { height: 1rem; width: 85%; background: var(--accent); opacity: 1; }
.post-word:not(.is-lead) { width: 55%; }
/* The product, dominant and centred — the largest element, as the rule says. */
.post-subject {
  justify-self: center;
  width: 58%; aspect-ratio: 1;
  border-radius: var(--radius-lg);
  background: var(--accent-2);
  opacity: .5;
}
.post-cta {
  display: grid; place-items: center;
  padding: var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--on-accent);
  font-size: var(--text-xs); font-weight: var(--weight-bold);
}

@media (max-width: 62rem) { .post--hero { max-width: 17rem; } }

/* TWO ACROSS. Three read as a feed row while the samples were grey
   placeholders; with real posts in them the type inside each one has to be
   readable, and ~300px is where it stops being. Two gives ~460px. */
/* DOUBLED CLASS ON PURPOSE — `.gallery.gallery--post` is (0,2,0). The bare
   `.gallery--post` is (0,1,0), exactly what `.gallery { repeat(4, …) }` weighs,
   and the base rule is declared ~590 lines LATER — so for a long time this
   modifier lost every tie and the 4:5 gallery rendered FOUR across on desktop
   while a test asserting the rule's TEXT passed. Specificity, not order. */
.gallery.gallery--post { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 40rem) { .gallery.gallery--post { grid-template-columns: 1fr; } }

/* A one-paragraph aside with no heading of its own. */
.callout {
  display: flex; align-items: flex-start; gap: var(--space-4);
  max-width: var(--measure-sm); margin-inline: auto;
  padding: var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
.callout p { color: var(--text-muted); }
.icon-badge--lg { margin-inline: auto; margin-bottom: var(--space-3); }

/* ============================================================================
   THE SQUARE POST FRAME (1:1)
   ============================================================================
   The fourth product shape, beside .phone (9:16), .thumb (16:9) and .post
   (4:5). Same tokens, same shadow, same hover.

   The mock is the LinkedIn composition rule drawn out: statement dominant and
   centred, CTA small and quiet at the bottom, and everything inside a visible
   safe margin — the feed crops the preview, so type near an edge is type
   nobody sees. Restrained on purpose; a loud card here would misrepresent what
   the pack produces. */

.square {
  position: relative;
  aspect-ratio: 1;
  padding: .4rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}
/* PURE GEOMETRY, the fourth frame brought into line. §21.1 flagged this one as
   the last unchecked *-screen and it had the same fault `.post-screen` did:
   padding, a grid and a gradient meant for the wireframe, which would have sat
   a photograph INSET with a tinted border. The mock's own box carries it now. */
.square-screen {
  position: relative;
  width: 100%; height: 100%;
  overflow: hidden;
  border-radius: calc(var(--radius-lg) - .3rem);
  background: var(--surface-sunk);
}
.square-screen img { width: 100%; height: 100%; object-fit: cover; display: block; }

.square--hero { max-width: 21rem; margin-inline: auto; }
/* The wireframe's own box: everything .square-screen used to declare. */
.square-mock {
  display: grid;
  align-content: center;
  justify-items: center;
  gap: var(--space-5);
  width: 100%; height: 100%;
  /* The safe zone, made visible: generous inset on every side. */
  padding: var(--space-7) var(--space-6);
  /* Flat colour FIRST: where color-mix is unsupported the gradient below is
     dropped, and without this the frame would render transparent (§14). */
  background: var(--accent-soft);
  background: linear-gradient(150deg, var(--accent-soft), var(--surface) 68%);
}
.square-statement { display: grid; gap: .5rem; width: 100%; justify-items: center; }
.square-line { height: .72rem; border-radius: var(--radius-sm); background: var(--text); opacity: .82; }
.square-line.is-lead { height: .95rem; width: 100%; background: var(--accent); opacity: 1; }
.square-line:not(.is-lead) { width: 78%; }
.square-line.is-short { width: 46%; }
/* Small and quiet — the opposite of the social pack's bold button, for the
   same reason the prompts say so. */
.square-cta {
  color: var(--text-subtle);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: .04em;
}

@media (max-width: 62rem) { .square--hero { max-width: 17rem; } }

/* A 1:1 gallery sits where the 4:5 one does — three across reads as a feed row
   without shrinking each post to a stamp. */
/* TWO ACROSS, for the same reason the 4:5 gallery is: three read as a feed row
   while the samples were grey, and leave real type unreadable at ~300px. */
/* Doubled for the same reason as `.gallery--post` — see the note there. */
.gallery.gallery--square { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 40rem) { .gallery.gallery--square { grid-template-columns: 1fr; } }

/* ---- the 1.91:1 BANNER frame (landscape ad) ----
   A FIFTH frame, and it exists because the ad pack ships a size none of the
   four covered. `.thumb` is the nearest — a rounded landscape box — but it
   hard-codes 16/9 (1.778) and these files are 1.911, so `object-fit: cover`
   would have trimmed ~7% off the width of every banner. On an ad that is the
   edge the bold call-to-action button sits nearest, which is the one thing
   these samples exist to show. Sharing `.thumb` here would have shared a
   COINCIDENCE — both are landscape — rather than a value (§5).

   PURE GEOMETRY, by construction rather than by repair: there is no banner
   wireframe and there never was, so nothing had to be moved off the screen
   element the way .post-screen and .square-screen did. Chrome belongs to the
   mock, never to the screen (§21.1). */
.banner {
  position: relative;
  aspect-ratio: 1200 / 628;
  padding: .4rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}
.banner-screen {
  position: relative;
  width: 100%; height: 100%;
  overflow: hidden;
  border-radius: calc(var(--radius-lg) - .3rem);
  background: var(--surface-sunk);
}
.banner-screen img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---- the 16:9 STATEMENT mock (X post) ----
   Reuses the .thumb frame — a rounded 16:9 box is geometry, not a brand — and
   adds only what this product's composition rule actually says: statement
   dominant and centred, a clean accent PILL for the CTA (visible by contrast
   and shape, never by shouting), and a visible CENTRE safe zone, because X
   re-crops in threads, replies, quotes and embeds. */
.stmt-screen {
  display: grid;
  place-items: center;
  /* Flat colour FIRST: where color-mix is unsupported the gradient is dropped,
     and without this the frame would render transparent (§14). */
  background: var(--surface-sunk);
  background: linear-gradient(145deg, var(--accent-soft), var(--surface) 70%);
}
/* The safe zone made visible: the mock's content occupies the middle, with
   generous room on every side that a crop can take without loss. */
.stmt-safe {
  display: grid;
  justify-items: center;
  gap: var(--space-3);
  width: 66%;
}
.stmt-line { height: .8rem; width: 100%; border-radius: var(--radius-sm); background: var(--text); opacity: .82; }
.stmt-line.is-lead { height: 1.1rem; background: var(--accent); opacity: 1; }
.stmt-line:not(.is-lead) { width: 72%; }
.stmt-pill {
  margin-top: var(--space-2);
  padding: .2rem .7rem;
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  color: var(--accent-press);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
}


/* ============================================================================
   LANDING PAGE SECTIONS
   ----------------------------------------------------------------------------
   Shared components, in theme.css rather than a page-local sheet (§14.3), and
   built from the same tokens as everything else — no literal colour anywhere
   below (§14.2). Every one of these is MOBILE-FIRST: the single-column shape is
   the base rule and the wider layouts are added at a breakpoint, so a narrow
   phone gets the simple case rather than an override of a complex one.
   ========================================================================= */

/* ---- THE SECTION BODY SIZE — one declaration, one value ----
   The words-that-hit cards read at --text-lg and are the size the page should
   be; every other section's body was smaller (--text-base for the prose,
   --text-sm for the step and feature bodies), so a paragraph looked like a
   footnote beside a card saying the same kind of thing.

   WRITTEN ONCE, HERE. Every selector below is landing-only, and each of them
   gave up its own font-size to this rule — .hits li, .turn and .feature p used
   to set their own. "Match the cards" is now literally one line rather than
   eight values that can drift apart. LINE-HEIGHT stays per-context: the cards
   are short punchy lines and want --leading-snug, paragraphs want room.

   .steps--numbered p and .closing .prose are deliberately more specific than
   the shared .steps p / .closing p they sit inside, because those two ARE used
   by other pages — the sales pages must not change size with the landing. */
.hits li,
.turn,
.prose--lead p,
.steps--numbered p,
.feature p,
.personas li,
.prose--center,
.closing .prose {
  font-size: var(--text-lg);
}

/* The landing hero is the one place a headline is allowed three lines, so it
   gets more room than a sales-page hero and a wider measure. */
.hero--lead { max-width: var(--measure-sm); padding-block: var(--space-9) var(--space-8); }
.hero--lead h1 { line-height: var(--leading-tight); }
.hero--lead .hero-sub { max-width: var(--measure-sm); }

/* ---- the hero, split ----
   MOBILE FIRST: one column, and the copy comes first in SOURCE ORDER, so a
   phone gets the headline and both CTAs before the image rather than below a
   4:5 graphic that would push them off the first screen.

   It must OVERRIDE .hero--lead's max-width, which is why it sits immediately
   after it — same specificity, later wins. Two columns only from 62rem, the
   width at which .wrap stops growing, so the split appears exactly when there
   is room for it and never as a squeeze. */
.hero--split { max-width: none; display: grid; gap: var(--space-7); }
@media (min-width: 62rem) {
  /* 1.2fr / 1fr: the text keeps the longer measure a three-sentence headline
     needs, and minmax(0, …) on both means neither track can be forced wider
     than its share by its content (§18.3's rule, in its grid form). */
  .hero--split {
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
    gap: var(--space-8);
    /* START, not centre or stretch: the two columns are different heights and
       that is fine — their TOPS line up with the kicker, and the image keeps
       whatever height its own width gives it. */
    align-items: start;
  }
}
.hero-figure { margin: 0; }
.hero-figure img {
  display: block;
  width: 100%;
  /* HEIGHT: AUTO IS LOAD-BEARING, and leaving it out was a real bug.
     ------------------------------------------------------------------
     The tag carries width="1122" height="1402" so the browser can reserve the
     box before the bytes arrive (§15L.8). Those attributes are applied as
     PRESENTATIONAL HINTS — as if `width: 1122px; height: 1402px` were written
     in the stylesheet. `width: 100%` below overrides the width hint, but with
     no height declaration the HEIGHT HINT SURVIVED, so the used height stayed
     1402px.
     And `aspect-ratio` only takes effect when one axis is `auto`: with both
     definite it was inert. The hero rendered 407 x 1402 — a 1:3.4 strip — and
     `object-fit: cover` then filled that height from a 4:5 source, cropping
     715px of width and taking the Thumbnail and Carousel chips with it.
     Releasing the height is the whole fix. Do not remove it, and do not "tidy"
     the width/height attributes off the tag instead — those are what prevent
     the layout shift. */
  height: auto;
  /* THE SLOT IS 4:5 WHATEVER ARRIVES. The file is already that ratio, so
     `cover` crops 0.036% of the width — 0.07px a side at 407px — and is really
     just a safety net for a future replacement of a different shape. */
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  background: var(--surface);
}

/* ---- the words-that-hit strip ----
   Four short lines that each land on their own. One column on a phone, two
   from 46rem — never three, because these are sentences and a third column
   makes them wrap mid-thought. */
.hits {
  display: grid; gap: var(--space-4);
  margin: 0; padding: 0; list-style: none;
}
.hits li {
  position: relative;
  padding: var(--space-4) var(--space-5);
  padding-inline-start: var(--space-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  color: var(--text);
  /* Size comes from .landing-body below — see the note there. */
  line-height: var(--leading-snug);
}
/* The accent rule is a border, not a background image: it inherits the radius
   and needs no second colour stop. */
.hits li::before {
  content: "";
  position: absolute; inset-block: var(--space-4); inset-inline-start: var(--space-4);
  width: 3px; border-radius: var(--radius-pill);
  background: var(--accent);
}
@media (min-width: 46rem) { .hits { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ---- the problem section's turn line ----
   The paragraph that answers the two before it, set apart rather than reworded
   into them. */
.prose--lead p { margin-bottom: var(--space-4); }
.prose--lead p:last-child { margin-bottom: 0; }
.turn {
  margin-top: var(--space-5);
  padding: var(--space-4) var(--space-5);
  border-inline-start: 3px solid var(--accent);
  border-radius: var(--radius-md);
  background: var(--accent-soft);
  color: var(--text);
  font-weight: var(--weight-medium);
  max-width: var(--measure-sm);
}

/* ---- numbered How It Works ----
   ONE COLUMN on a phone, two from 46rem. The number sits beside the text
   rather than above it, so a step reads as one line of thought at any width. */
.steps--numbered { grid-template-columns: 1fr; gap: var(--space-4); }
.steps--numbered li { display: flex; gap: var(--space-4); align-items: flex-start; }
.steps--numbered h3 { margin: 0 0 var(--space-2); }
.steps--numbered p { margin: 0; }
@media (min-width: 46rem) {
  .steps--numbered { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---- the why grid ----
   Rides on .grid (3 / 2 / 1), so it inherits the breakpoints the packs grid
   already uses and there is one set of column rules on the page, not two. */
.feature {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition), border-color var(--transition);
}
.feature:hover { box-shadow: var(--shadow-md); border-color: var(--border-strong); }
.feature h3 { font-size: var(--text-lg); margin: 0; }
.feature p { color: var(--text-muted); margin: 0; }
.feature-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.25rem; height: 2.25rem;
  border-radius: var(--radius-md);
  background: var(--accent-soft);
  color: var(--accent-press);
}
.feature-icon .icon { width: 1.25rem; height: 1.25rem; }

/* ---- the packs footnote ---- */
.packs-note {
  margin-top: var(--space-5);
  color: var(--text-muted);
  font-size: var(--text-sm);
  text-align: center;
}

/* ---- the gallery marquee ----
   A continuously scrolling strip, looping seamlessly by duplicating the track
   and travelling exactly half its width: at -50% the second copy sits precisely
   where the first began, so there is no jump to hide.

   THE GAP LIVES ON THE ITEM, not on the flex container, and that is what makes
   the loop seamless. With `gap`, a 24-item track measures 24W + 23G while half
   of it is 12W + 12G — half a gap out, which reads as a stutter once a second.
   A margin on every item makes each occupy exactly W + G, so half the track is
   exactly one copy.

   CHEAP: one composited transform on one element, `will-change` declared, and
   nothing animated that triggers layout or paint. No timer, no script, no
   stored state.

   HORIZONTAL ONLY. §18.7 bans nested VERTICAL scroll boxes on touch because a
   thumb landing in one cannot get back out to the page; this clips sideways and
   the page still scrolls down normally. It cannot widen the page either: the
   viewport is `overflow: hidden` inside .wrap, so the oversized track is
   clipped rather than pushing the document sideways. */
.marquee {
  overflow: hidden;
  /* Room for the focus ring, which a clipped box would otherwise cut. */
  padding-block: 2px;
}
.marquee-track {
  display: flex;
  width: max-content;
  will-change: transform;
  animation: marquee-scroll 60s linear infinite;
}
/* IT DOES NOT PAUSE ON HOVER any more. A pointer resting over the strip — or
   simply parked there on a laptop while someone reads the page — froze the one
   element whose whole job is to show twelve samples, and on a phone the "hover"
   state a tap leaves behind could stick it indefinitely. It scrolls
   continuously now, at every pointer type.
   KEYBOARD FOCUS STILL PAUSES IT, and that is a different input mode rather
   than an exception to the same rule: the strip is `tabindex="0"`, so someone
   who has tabbed INTO it is reading it deliberately and cannot follow a moving
   target. Do not fold this into the hover rule that was removed.
   THE REDUCED-MOTION FALLBACK BELOW IS UNTOUCHED — someone who asked the system
   to stop moving things still gets the manual scroller, not a frozen strip. */
.marquee:focus-within .marquee-track { animation-play-state: paused; }
.marquee:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--radius-md); }
/* The item width is GUARDED: min(15rem, 72vw) is 240px on a 360px phone, inside
   a 312px content box (§18.3's rule applied to a flex basis, not a grid track). */
.marquee-item {
  flex: 0 0 min(15rem, 72vw);
  margin-inline-end: var(--space-5);
}
@keyframes marquee-scroll {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}
/* REDUCED MOTION GETS A MANUAL SCROLLER, not a frozen strip. Someone who asked
   the system to stop moving things should still be able to see all twelve, so
   the animation stops, the duplicate half is removed (it exists only to make a
   loop seamless) and the strip becomes swipeable. */
@media (prefers-reduced-motion: reduce) {
  .marquee {
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scrollbar-width: thin;
  }
  .marquee-track { animation: none; width: auto; }
  .marquee-item { scroll-snap-align: start; }
  /* THE SELECTOR IS DELIBERATELY SPECIFIC. `.marquee-dup` alone is (0,1,0) and
     LOSES to `.shot { display: flex }`, which is declared ~180 lines later at
     the same weight — so the duplicate half stayed visible and a reduced-motion
     reader got 24 frames in the manual scroller, twelve of them `aria-hidden`
     copies. A media query buys no specificity; only the selector does. */
  .marquee-track .marquee-dup { display: none; }
}
.marquee-hint {
  margin-top: var(--space-3);
  color: var(--text-subtle);
  font-size: var(--text-sm);
}
/* The hint describes the movement, which a reduced-motion reader will not see —
   there it becomes the swipe affordance instead, so it stays either way. */

/* ---- who this is for ----
   One column on a phone, two from 46rem. A tick beside each line rather than a
   bullet, so the list reads as qualifications rather than inventory. */
.personas {
  display: grid; gap: var(--space-3);
  margin: 0; padding: 0; list-style: none;
}
.personas li {
  display: flex; gap: var(--space-3); align-items: flex-start;
  color: var(--text-muted);
  line-height: var(--leading-snug);
}
.personas .icon { flex: 0 0 auto; width: 1.15rem; height: 1.15rem; margin-top: .2rem; color: var(--success); }
@media (min-width: 46rem) { .personas { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4) var(--space-6); } }

/* A centred paragraph under a centred heading. */
.prose--center { max-width: var(--measure-sm); margin-inline: auto; text-align: center; }

/* ============================================================================
   SECTIONS
   ========================================================================= */

.section-head--center { max-width: var(--measure-sm); margin-inline: auto; text-align: center; }
.section-head--center p { margin-inline: auto; }

/* ---- problem: friction cards, each anchored by an icon ---- */
.pain-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 48rem) { .pain-grid { grid-template-columns: 1fr; } }

.pain-card {
  display: flex; align-items: flex-start; gap: var(--space-4);
  padding: var(--space-5);
  background-color: var(--surface);
  background-image: var(--grad-tint);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  color: var(--text-muted);
  transition: box-shadow var(--transition), transform var(--transition), border-color var(--transition);
}
.pain-card:hover { box-shadow: var(--shadow-lg); border-color: var(--accent); transform: translateY(-3px); }
.pain-card .icon-badge { background: var(--surface); color: var(--accent-press); box-shadow: var(--shadow-sm); }

/* The turn from problem to solution. Accent-ruled and centred so it reads as a
   beat, not another paragraph. */
.pivot {
  position: relative;
  max-width: var(--measure-sm); margin: var(--space-8) auto 0;
  padding: var(--space-7) var(--space-7) var(--space-7) var(--space-8);
  border: 1px solid var(--accent);
  border-left: 5px solid var(--accent);
  border-radius: var(--radius-xl);
  background-color: var(--accent-soft);
  background-image: var(--grad-tint-strong);
  box-shadow: var(--shadow-lg);
  font-size: var(--text-xl);
  font-weight: var(--weight-medium);
  line-height: var(--leading-snug);
  color: var(--text);
}
/* A quote glyph watermarked behind the line — enough to mark this as the turn
   in the argument without competing with the words. Real inline SVG rather
   than a CSS mask: the icon set already exists, and a mask would mean a
   data-URI copy of the same glyph living in the stylesheet. */
.pivot-mark {
  position: absolute; inset-block-start: var(--space-4); inset-inline-end: var(--space-5);
  color: var(--accent); opacity: .16; pointer-events: none;
}
.pivot-mark .icon { width: 2.75rem; height: 2.75rem; vertical-align: 0; }
@media (max-width: 40rem) {
  .pivot { padding: var(--space-5); font-size: var(--text-lg); }
  .pivot-mark { display: none; }
}

/* ---- solution ---- */
.solution-body { max-width: var(--measure-sm); margin-inline: auto; font-size: var(--text-lg); color: var(--text-muted); text-align: center; }

/* ---- benefits: the ticks are the motif, so they carry real weight ---- */
.benefits { display: grid; gap: var(--space-4); grid-template-columns: repeat(2, minmax(0, 1fr)); margin-bottom: var(--space-7); }
@media (max-width: 48rem) { .benefits { grid-template-columns: 1fr; } }

.benefit {
  display: flex; align-items: flex-start; gap: var(--space-4);
  padding: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition), border-color var(--transition), transform var(--transition);
}
.benefit:hover { box-shadow: var(--shadow-md); border-color: var(--accent); transform: translateY(-2px); }
.benefit-lead { display: block; font-weight: var(--weight-bold); margin-bottom: var(--space-1); }
.benefit-detail { display: block; color: var(--text-muted); font-size: var(--text-sm); }

.tick {
  display: grid; place-items: center; flex: 0 0 auto;
  width: 2.15rem; height: 2.15rem;
  border-radius: var(--radius-pill);
  background-color: var(--accent);
  background-image: var(--grad-accent);
  color: var(--on-accent);
  /* Two rings: a tight soft halo, then a wider translucent one, so the tick
     reads as lit rather than merely coloured. */
  box-shadow: 0 0 0 .3rem var(--accent-soft), 0 0 0 .55rem var(--accent-ring), var(--shadow-sm);
}
.benefit:hover .tick { box-shadow: 0 0 0 .35rem var(--accent-soft), 0 0 0 .7rem var(--accent-ring), var(--shadow-md); }
.tick .icon { width: 1.1rem; height: 1.1rem; vertical-align: 0; stroke-width: 2.6; }

/* ---- how it works: icon + number on each step ---- */
/* Arrow connectors sit in their own grid columns, so they line up with the
   card gap instead of being absolutely positioned over it. */
.steps--offer { grid-template-columns: 1fr auto 1fr auto 1fr; align-items: stretch; gap: var(--space-3); }
.steps--offer > li {
  background-color: var(--surface);
  background-image: var(--grad-tint);
  border-color: var(--border);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition), transform var(--transition), border-color var(--transition);
}
.steps--offer > li:hover { box-shadow: var(--shadow-lg); border-color: var(--accent); transform: translateY(-3px); }
/* The connectors are grid children of the same list, so they would otherwise
   inherit the step card's padding, border and shadow and render as empty boxes. */
.steps--offer > li.step-arrow {
  display: grid; place-items: center;
  padding: 0; background: none; border: 0; box-shadow: none;
  color: var(--accent);
}
.steps--offer > li.step-arrow:hover { transform: none; box-shadow: none; }
.step-arrow .icon { width: 1.6rem; height: 1.6rem; vertical-align: 0; }

.step-top { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin-bottom: var(--space-4); }
.steps--offer .icon-badge {
  width: 2.9rem; height: 2.9rem;
  background-color: var(--accent);
  background-image: var(--grad-accent);
  color: var(--on-accent);
  box-shadow: 0 0 0 .3rem var(--accent-soft);
}
/* The number is set in the accent gradient itself — clipped to the glyph, so
   it reads as a design element rather than as content. */
.step-index {
  font-size: 2.75rem; font-weight: var(--weight-bold); line-height: 1;
  color: var(--accent);
  background: var(--grad-accent);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
  opacity: .35;
}
.steps--offer h3 { margin-top: 0; }

@media (max-width: 48rem) {
  /* Stacked: the arrows would point sideways between vertically stacked cards,
     so they are dropped rather than rotated into a second meaning. */
  .steps--offer { grid-template-columns: 1fr; gap: var(--space-4); }
  .step-arrow { display: none; }
}

/* ---- gallery: phone frames ---- */
/* ALIGN TO THE START, which matters the moment ONE gallery mixes shapes. A
   grid item stretches by default, and a stretched frame has a definite height;
   with `width: 100%` definite too, its own `aspect-ratio` goes INERT and
   `object-fit: cover` starts cropping — the §15L.8 trap, arriving in a grid.
   Every uniform gallery computes equal row heights anyway, so this changes
   nothing for them and is what keeps a mixed one honest. */
.gallery { display: grid; gap: var(--space-5); grid-template-columns: repeat(4, minmax(0, 1fr)); align-items: start; }
@media (max-width: 62rem) { .gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 30rem) { .gallery { grid-template-columns: 1fr; } }

/* A 16:9 gallery needs fewer, wider columns than a 9:16 one — four landscape
   frames across is four postage stamps. */
/* Doubled to match its siblings. This one happens to be declared AFTER the
   base rule and so won on order, which is precisely why the other two going
   unnoticed was possible: two of the three galleries were visibly wrong and
   the third was visibly right, from rules that read identically. */
.gallery.gallery--wide { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 40rem) { .gallery.gallery--wide { grid-template-columns: 1fr; } }

.shot { display: flex; flex-direction: column; gap: var(--space-3); }
.shot .phone, .shot .thumb, .shot .post, .shot .square, .shot .banner { transition: box-shadow var(--transition), transform var(--transition); }
.shot:hover .phone, .shot:hover .thumb, .shot:hover .post, .shot:hover .square, .shot:hover .banner { box-shadow: var(--shadow-lg); transform: translateY(-3px); }
.shot-placeholder {
  display: grid; gap: var(--space-2); place-content: center; justify-items: center;
  width: 100%; height: 100%;
  padding: var(--space-4);
  color: var(--text-subtle); font-size: var(--text-xs); text-align: center;
}
.shot-placeholder .icon { width: 1.6rem; height: 1.6rem; color: var(--border-strong); }
.shot-caption { color: var(--text-subtle); font-size: var(--text-xs); text-align: center; }

/* ---- value comparison ---- */
.compare { display: grid; gap: var(--space-3); max-width: var(--measure-sm); margin-inline: auto; }
.compare-row {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  color: var(--text-muted);
}
.compare-row .icon-badge { background: var(--surface-sunk); color: var(--text-subtle); }
/* The two alternatives are deliberately quieter — flat, sunk, no shadow — so
   the eye lands on the row below them without either being hard to read. */
.compare-row:not(.is-ours) { background: var(--surface-sunk); color: var(--text-subtle); }

/* Ours is the row that should stop the eye: accent border, tinted fill, larger
   type. No struck-out "was" price — nobody was ever charged one, and inventing
   it in the highest-trust spot on the page would be a lie. */
.compare-row.is-ours {
  background-color: var(--accent-soft);
  background-image: var(--grad-tint-strong);
  border-color: var(--accent);
  border-width: 2px;
  border-radius: var(--radius-xl);
  padding: var(--space-6);
  box-shadow: var(--shadow-lg);
  color: var(--accent-press);
  font-size: var(--text-2xl);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-tight);
}
.compare-row.is-ours .icon-badge {
  width: 3rem; height: 3rem;
  background-color: var(--accent);
  background-image: var(--grad-accent);
  color: var(--on-accent);
  box-shadow: 0 0 0 .3rem var(--accent-soft);
}
.compare-foot { max-width: var(--measure-sm); margin: var(--space-5) auto 0; text-align: center; color: var(--text-muted); }

/* ---- guarantee callout ---- */
.guarantee {
  display: flex; align-items: flex-start; gap: var(--space-5);
  max-width: var(--measure-sm); margin: 0 auto;
  padding: var(--space-6);
  background: var(--surface);
  border: 2px solid var(--accent);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
}
.guarantee h2 { font-size: var(--text-xl); margin-bottom: var(--space-3); }
.guarantee p { color: var(--text-muted); }
.shield { flex: 0 0 auto; width: 3.25rem; height: 3.25rem; color: var(--accent); }
.shield .icon { width: 100%; height: 100%; vertical-align: 0; }

@media (max-width: 40rem) {
  .guarantee { flex-direction: column; gap: var(--space-4); padding: var(--space-5); }
}

/* ---- contact strip ---- */
.contact-strip {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: var(--space-3) var(--space-6);
  max-width: var(--measure-sm); margin-inline: auto;
  padding: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  text-align: center;
}
.contact-item { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--text-muted); }
.contact-item .icon { color: var(--accent); }
.contact-item a { font-weight: var(--weight-bold); }

/* ============================================================================
   FORMULA STRIP
   ============================================================================
   The three steps restated as one glanceable line. Its whole job is to make
   "one step" feel true before the reader has parsed the step cards below it,
   so it is the loudest tinted object between the hero and the benefits.
   ========================================================================= */

.formula {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: var(--space-3) var(--space-5);
  max-width: var(--measure-sm); margin-inline: auto;
  padding: var(--space-6) var(--space-5);
  background-color: var(--accent-soft);
  background-image: var(--grad-tint-strong);
  border: 1px solid var(--accent);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
}
.formula-step {
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-tight);
  color: var(--accent-press);
  text-align: center;
}
.formula-arrow { display: inline-grid; place-items: center; color: var(--accent); }
.formula-arrow .icon { width: 1.4rem; height: 1.4rem; vertical-align: 0; }

@media (max-width: 40rem) {
  /* Stacked, so the arrows would point across a line break. Turned to face
     down rather than dropped: here the sequence IS the message. */
  .formula { flex-direction: column; gap: var(--space-3); }
  .formula-arrow { transform: rotate(90deg); }
}

/* ============================================================================
   TRUST STRIP
   ========================================================================= */

.trust-strip {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: var(--space-3) var(--space-6);
  max-width: var(--measure-sm); margin: var(--space-6) auto 0;
  padding: var(--space-4) var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-sm);
}
.trust-item {
  display: inline-flex; align-items: center; gap: var(--space-2);
  color: var(--text-muted); font-size: var(--text-sm); font-weight: var(--weight-medium);
}
.trust-item .icon { color: var(--accent); }

@media (max-width: 48rem) {
  .trust-strip { border-radius: var(--radius-lg); gap: var(--space-3) var(--space-5); }
}

/* ============================================================================
   TESTIMONIALS
   ============================================================================
   Structure now, real reviews later. A placeholder card is styled to look
   UNFILLED — dashed border, sunk background, outlined stars — because a
   placeholder that looks like a finished five-star review is a fabricated
   review, and this page takes money.
   ========================================================================= */

.quotes { display: grid; gap: var(--space-4); grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 62rem) { .quotes { grid-template-columns: 1fr; max-width: var(--measure-sm); margin-inline: auto; } }

.quote-card {
  display: flex; flex-direction: column; gap: var(--space-4);
  padding: var(--space-5);
  background-color: var(--surface);
  background-image: var(--grad-tint);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition), transform var(--transition);
}
.quote-card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }

/* The unfilled state. Deliberately reads as a slot, not as a review. */
.quote-card.is-placeholder {
  background-image: none;
  background-color: var(--surface-sunk);
  border-style: dashed;
  border-color: var(--border-strong);
  box-shadow: none;
}
.quote-card.is-placeholder:hover { transform: none; box-shadow: none; }

.stars { display: inline-flex; gap: .15rem; color: var(--accent); }
.stars .icon { width: 1.1rem; height: 1.1rem; vertical-align: 0; }
/* Solid stars only where the rating is real. An outlined star cannot be
   mistaken for a score somebody actually gave. */
.stars--real .icon { fill: currentColor; }
.stars--placeholder { color: var(--border-strong); }

.quote-text { font-size: var(--text-lg); line-height: var(--leading-snug); color: var(--text); }
.quote-card.is-placeholder .quote-text { color: var(--text-subtle); font-style: italic; }
.quote-who { margin-top: auto; color: var(--text-subtle); font-size: var(--text-sm); }
.quote-who strong { color: var(--text); font-weight: var(--weight-bold); }
.quote-card.is-placeholder .quote-who strong { color: var(--text-subtle); font-weight: var(--weight-normal); }

.quote-flag {
  align-self: flex-start;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--surface);
  border: 1px dashed var(--border-strong);
  color: var(--text-subtle);
  font-size: var(--text-xs); font-weight: var(--weight-bold);
  letter-spacing: .06em; text-transform: uppercase;
}

/* ---- copied state ----
   PERSISTENT and VISUAL ONLY. The button stays fully clickable — it is a record
   of what has been taken, not a lockout — so it keeps a hover and a pointer
   cursor rather than the greyed-out, transform-free treatment .btn.disabled
   gets.

   Tinted rather than faded: opacity would dim the LABEL too, and a memory aid
   nobody can read at a glance is not one. --accent-press on --accent-soft is
   the strongest pairing in the ramp, so "Copied" stays clearly legible while
   the button visibly steps back from the un-copied solid ones beside it. */
.btn.primary.is-copied {
  background: var(--accent-soft);
  color: var(--accent-press);
  border-color: var(--accent);
  box-shadow: none;
  font-weight: var(--weight-medium);
}
.btn.primary.is-copied:hover {
  background: var(--surface);
  color: var(--accent-press);
  border-color: var(--accent);
  box-shadow: var(--shadow-sm);
  transform: translateY(-1px);
}
.btn.primary.is-copied:active { transform: translateY(0); box-shadow: none; }


/* ============================================================================
   THE RESELLER PANEL                                    (gated, unlinked)
   ============================================================================
   Its own shell — not the public header/footer — but the SAME tokens. There is
   no second palette and no panel-only colour: §14.2's zero-hex rule has no
   exception here, and §14.3's "shared components live in theme.css" is why
   these rules sit in this file rather than a panel stylesheet nobody else
   would ever read.
   `.btn`, `.field`, `label`, `input`, `select` and `textarea` are all reused
   from the shared component layer above — which is what makes the panel's form
   controls already 16px (§18.1, no iOS zoom) and already 2.75rem tall under a
   coarse pointer (§18.2) without a line of new CSS.
   ========================================================================= */
.panel-body { background: var(--bg-subtle); min-height: 100vh; }

.panel-bar {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.panel-brand {
  display: inline-flex; align-items: center; gap: var(--space-2);
  color: var(--text); font-weight: var(--weight-bold); text-decoration: none;
}
.panel-brand-mark { display: grid; place-items: center; color: var(--accent); }
.panel-brand-mark .icon { width: 1.25rem; height: 1.25rem; }
/* ALIGNED ON ONE LINE, and `align-items: center` is what does it: the Settings
   menu is a <details>, whose <summary> is a block with its own box, so in a
   flex row of bare <a> elements it sat lower than its neighbours. Every item —
   link and summary alike — is now a flex box of the same height, so they share
   a centre line rather than a baseline that one of them does not have. */
.panel-nav { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.panel-nav > a, .panel-menu > summary {
  display: inline-flex; align-items: center;
  min-height: 2.75rem;
  padding-inline: var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--text-base);
}
.panel-nav > a:hover, .panel-menu > summary:hover {
  background: var(--surface-sunk);
  color: var(--accent);
  text-decoration: none;
}
.panel-nav a {
  color: var(--text-muted); font-size: var(--text-sm);
  font-weight: var(--weight-medium); text-decoration: none;
}
.panel-nav a:hover { color: var(--text); }
.panel-nav a[aria-current="page"] { color: var(--accent); }

.panel-main { padding-block: var(--space-7) var(--space-9); }
.panel-wrap {
  width: 100%; max-width: var(--measure); margin: 0 auto;
  padding-inline: var(--space-5);
}
.panel-wrap--narrow { max-width: var(--measure-sm); }

.panel-head { margin-bottom: var(--space-7); }
.panel-eyebrow {
  margin-bottom: var(--space-2);
  color: var(--text-subtle); font-size: var(--text-xs);
  font-weight: var(--weight-bold); letter-spacing: .08em; text-transform: uppercase;
}
.panel-head h1 { font-size: var(--text-3xl); margin-bottom: var(--space-3); }
.panel-lede { color: var(--text-muted); font-size: var(--text-lg); max-width: 42rem; }

/* Three-up on a wide screen. `minmax(0, 1fr)` cannot overflow at any width
   (§18.3), and one column is the BASE rule — a phone gets the simple case
   rather than an override of a complex one (§15L.4). */
.panel-cards { display: grid; gap: var(--space-5); grid-template-columns: 1fr; }
@media (min-width: 46rem) {
  .panel-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.panel-card {
  padding: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.panel-card h2 { font-size: var(--text-base); margin-bottom: var(--space-3); }
.panel-stat {
  font-size: var(--text-2xl); font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-tight); margin-bottom: var(--space-2);
}
.panel-stat-sm { font-weight: var(--weight-bold); margin-bottom: var(--space-2); }
.panel-note { color: var(--text-subtle); font-size: var(--text-sm); line-height: var(--leading-snug); }
.panel-card p + p { margin-top: var(--space-3); }

/* The uploaded logo. §18.11's bulletproof rule from the start rather than as a
   later repair: a block image at width:100%/height:auto inside a bounded box
   cannot overflow or crop, whatever a reseller uploads and whatever its shape.
   `max-width` caps it on a desktop where 100% would be needlessly large. */
.panel-logo {
  display: block;
  width: 100%; height: auto; max-width: 12rem;
  margin-bottom: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--surface-sunk);
}

.panel-form { display: grid; gap: var(--space-5); }
.panel-form .field { margin-bottom: 0; }
.panel-form input[type="file"] {
  width: 100%;
  font-size: var(--text-base);
  color: var(--text-muted);
}

.panel-flash {
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-5);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
}
/* Flat colour FIRST in both: where color-mix is unsupported the mixed value is
   dropped and the box would otherwise render transparent (§14). */
.panel-flash--ok {
  background: var(--accent-soft);
  background: color-mix(in srgb, var(--accent) 12%, var(--surface));
  color: var(--text);
  border: 1px solid var(--accent);
}
.panel-flash--bad {
  background: var(--surface-sunk);
  background: color-mix(in srgb, var(--danger) 10%, var(--surface));
  color: var(--text);
  border: 1px solid var(--danger);
}

/* ---- panel: lists, filters, chips (Phase 2) + the brief reference (Phase 3) ---- */
.panel-head--row {
  display: flex; flex-wrap: wrap; gap: var(--space-4);
  align-items: flex-start; justify-content: space-between;
}
.panel-section-title { font-size: var(--text-xl); margin: var(--space-7) 0 var(--space-4); }
.panel-empty {
  padding: var(--space-6); text-align: center;
  color: var(--text-subtle); background: var(--surface);
  border: 1px dashed var(--border-strong); border-radius: var(--radius-lg);
}
.panel-filters {
  display: grid; gap: var(--space-4); align-items: end;
  grid-template-columns: 1fr; margin-bottom: var(--space-6);
}
@media (min-width: 46rem) {
  .panel-filters { grid-template-columns: 2fr 1fr 1fr auto; }
}
.panel-filters .field { margin-bottom: 0; }
.panel-filters p { margin: 0; }

/* THE LIST IS A <ul>, NOT A <table>, and that is the mobile decision: a table
   at 360px is either a horizontal scroll box (§18.7 forbids nested scrollers on
   touch) or unreadable. These rows are a grid that becomes one column on a
   phone — the same information, stacked, with no scroller. */
.panel-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }
.panel-row {
  display: grid; gap: var(--space-2) var(--space-4); align-items: center;
  grid-template-columns: 1fr;
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
@media (min-width: 46rem) {
  .panel-row { grid-template-columns: minmax(0, 2fr) minmax(0, 1.5fr) minmax(0, 1.5fr); }
}
.panel-row.is-archived { opacity: .68; }
.panel-row-main { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: baseline; }
.panel-row-name { font-weight: var(--weight-bold); color: var(--text); text-decoration: none; }
.panel-row-name:hover { color: var(--accent); }
.panel-row-sub, .panel-row-meta { color: var(--text-subtle); font-size: var(--text-sm); }
.panel-row-platforms { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.panel-chip {
  padding: .15rem var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--surface-sunk);
  color: var(--text-muted);
  font-size: var(--text-xs);
}
.panel-tag {
  padding: .1rem var(--space-2); border-radius: var(--radius-pill);
  background: var(--surface-sunk); color: var(--text-subtle);
  font-size: var(--text-xs); font-weight: var(--weight-bold);
}
.panel-tag--ok { background: var(--accent-soft); color: var(--text); }
.panel-cap-pill {
  display: inline-block; padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--surface-sunk); color: var(--text-muted);
  font-size: var(--text-sm); font-weight: var(--weight-bold);
}
.panel-checks { display: grid; gap: var(--space-3); grid-template-columns: 1fr; }
@media (min-width: 34rem) { .panel-checks { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.panel-check { display: flex; gap: var(--space-2); align-items: center; font-size: var(--text-base); }
.panel-card--wide { margin-top: var(--space-6); }

/* ── PHASE 4/5: the work log and the dashboard ───────────────────────────── */

/* A big number with its target beside it, so "9 / 20" reads as one figure. */
.panel-stat-of { font-size: var(--text-lg); color: var(--text-subtle); font-weight: var(--weight-normal); }
.board-count { font-weight: var(--weight-bold); }

/* THE BOARD ROW CARRIES ITS STATE ON THE LEFT EDGE, not as a background wash:
   a tinted row competes with the tag that already names the state, and a
   dashboard of coloured blocks is harder to scan than a list with a rule. */
.panel-row--board { border-left: 3px solid var(--border); }
.panel-row--board.is-risk { border-left-color: var(--danger); }
.panel-row--board.is-behind { border-left-color: var(--warning); }
.panel-row--board.is-on-track,
.panel-row--board.is-met,
.panel-row--board.is-over { border-left-color: var(--accent); }

.panel-tag--risk { background: var(--danger-soft); color: var(--danger); }
.panel-tag--warn { background: var(--warning-soft); color: var(--warning); }

/* THE MANUAL ENTRY FORM. Three stacked fieldsets on a phone, and the trio of
   count/status/date side by side once there is room — at 360px they stack,
   which is the width a reseller actually logs from. */
.log-row {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--surface);
}
.log-row > legend {
  font-size: var(--text-sm); font-weight: var(--weight-bold);
  padding-inline: var(--space-2);
}
.log-row-grid, .quota-grid {
  display: grid; gap: var(--space-3); grid-template-columns: 1fr;
}
@media (min-width: 34rem) {
  .log-row-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .quota-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 52rem) {
  .quota-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.log-row .field, .quota-grid .field { margin-bottom: 0; }

/* Correcting an entry is a <details>, so it costs no script and is closed by
   default — the log is for reading far more often than for editing. */
.log-edit { margin-top: var(--space-3); }
.log-edit > summary {
  cursor: pointer; font-size: var(--text-sm); color: var(--text-subtle);
}
.log-edit > summary:hover { color: var(--accent); }
.log-edit form { margin-top: var(--space-3); }
@media (pointer: coarse) {
  /* The disclosure is a tap target like any other (§18.2). */
  .log-edit > summary { padding-block: var(--space-2); min-height: 2.75rem; }
}
.panel-row--log { align-items: start; }

/* ── PHASE 6/7: plans, and the metric trends ─────────────────────────────── */

.metric-grid { display: grid; gap: var(--space-5); grid-template-columns: 1fr; }
@media (min-width: 46rem) { .metric-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 70rem) { .metric-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.metric-card { display: flex; flex-direction: column; }

/* THE CHART IS SIZED BY CSS AND SHAPED BY ITS viewBox, so it scales to any
   width with no measurement and cannot overflow — the rule §18.11 sets for
   images, applied to the one drawing in the panel. `height` is fixed so a
   preserveAspectRatio="none" line does not stretch to absurd proportions. */
.metric-chart {
  display: block; width: 100%; max-width: 100%; height: 5rem;
  margin: var(--space-3) 0 var(--space-1);
  color: var(--accent);
  stroke: currentColor;
  fill: currentColor;
  overflow: visible;
}
.metric-range { text-align: right; }

.metric-list { list-style: none; margin: var(--space-3) 0 0; padding: 0; }
.metric-list li {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: var(--space-2);
  padding-block: var(--space-2);
  border-top: 1px solid var(--border);
  font-size: var(--text-sm);
}
.metric-list li.is-archived { opacity: .55; }
.metric-date { color: var(--text-subtle); font-variant-numeric: tabular-nums; }
.metric-value { font-weight: var(--weight-bold); font-variant-numeric: tabular-nums; }
.metric-list form { margin: 0; }

/* ════════════════════════════════════════════════════════════════════════
   PANEL RESTRUCTURE — a workflow-first layout
   ════════════════════════════════════════════════════════════════════════ */

/* COMFORTABLE FIELDS EVERYWHERE IN THE PANEL. The base rule already sets 16px
   type (§18.1); what it did not set is a HEIGHT, so a select rendered at
   whatever the platform felt like — around 34px on Android, under the 44px
   §18.2 requires of every other control. Applied by height rather than by
   padding so a select, a date picker and a text box all land on the same line.
   Scoped to .panel-body: the pack page and the sales pages are not this
   restructure's business. */
.panel-body input[type="text"], .panel-body input[type="email"],
.panel-body input[type="search"], .panel-body input[type="number"],
.panel-body input[type="date"], .panel-body input[type="tel"],
.panel-body select, .panel-body textarea {
  min-height: 2.75rem;
  padding: var(--space-3) var(--space-4);
}
.panel-body textarea { min-height: 5.5rem; }
.panel-body label { font-size: var(--text-base); }

/* BUTTONS TOO, AND UNCONDITIONALLY. §18.2 gives .btn.small its 44px only under
   `pointer: coarse`, which is right for the sales pages — a mouse does not need
   it. The panel is a tool resellers run on a phone all day, and the row's main
   action ("Open") was a .btn.small measuring 39px on a narrow window with a
   fine pointer. Comfortable everywhere beats comfortable on the devices we
   thought to ask about. */
.panel-body .btn { min-height: 2.75rem; }
.panel-body .btn.small { min-height: 2.75rem; padding-inline: var(--space-4); }

/* ── THE SETTINGS MENU ────────────────────────────────────────────────────
   A <details> disclosure, not a scripted dropdown: the panel's established
   no-JS pattern (the log's "Correct this" is the same control). It opens on
   click, closes on Escape and is keyboard-reachable without a line of
   JavaScript, and with the script blocked it still opens. */
.panel-menu { position: relative; }
.panel-menu > summary {
  list-style: none; cursor: pointer;
  gap: var(--space-1);
  color: var(--text-muted);
  font-weight: var(--weight-bold);
}
.panel-menu > summary::-webkit-details-marker { display: none; }
.panel-menu > summary:hover { color: var(--text); }
.panel-menu[open] > summary { color: var(--accent); }
/* WHERE YOU ARE, without the menu having to be open to say it. The menu no
   longer renders open on its own pages (it caused a close-then-reopen flicker
   on every item), so the summary carries the current-section marker instead. */
.panel-menu.is-current > summary { color: var(--accent); }
.panel-menu-items {
  position: absolute; right: 0; top: calc(100% + var(--space-1));
  z-index: 20;
  min-width: 12rem;
  display: grid;
  padding: var(--space-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}
.panel-menu-items a {
  padding: var(--space-3);
  min-height: 2.75rem;
  display: flex; align-items: center;
  border-radius: var(--radius-sm);
  color: var(--text); font-size: var(--text-base); text-decoration: none;
}
.panel-menu-items a:hover { background: var(--surface-sunk); color: var(--accent); }
.panel-menu-items a[aria-current="page"] { color: var(--accent); }
@media (max-width: 34rem) {
  /* On a phone the menu spans the screen rather than hanging off an edge it
     would otherwise overflow (§18.3's rule, applied to an absolute box). */
  .panel-menu { position: static; }
  .panel-menu-items { left: var(--space-4); right: var(--space-4); min-width: 0; }
}

/* ── HOME: TRIAGE ─────────────────────────────────────────────────────────
   The at-risk few, not every channel. A row is a link to the client, so the
   whole thing is the target rather than a word inside it. */
.triage { display: grid; gap: var(--space-3); list-style: none; margin: 0; padding: 0; }
.triage-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3);
  padding: var(--space-4);
  min-height: 3.5rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text); text-decoration: none;
}
.triage-row:hover { border-color: var(--accent); color: var(--text); text-decoration: none; }
.triage-row.is-risk { border-left-color: var(--danger); }
.triage-row.is-behind { border-left-color: var(--warning); }
.triage-who { font-weight: var(--weight-bold); }
.triage-count { margin-left: auto; font-weight: var(--weight-bold); font-variant-numeric: tabular-nums; }
.triage-go { color: var(--text-subtle); }
.panel-allclear {
  padding: var(--space-5);
  background: var(--accent-soft);
  border-radius: var(--radius-md);
  color: var(--text);
}

/* ── CLIENTS LIST: an obvious Open button, not a hover-link ─────────────── */
.client-row { align-items: center; }
.client-open { margin-left: auto; }
/* ── 3 & 4. THE CLIENT ROW ────────────────────────────────────────────────
   Name over business, the stats readable, and Open pinned right and centred.
   The row was a single line competing for width; stacking the two text lines
   frees the horizontal room the button and the chips needed. */
.client-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.client-row .panel-row-main { flex: 1 1 12rem; display: grid; gap: var(--space-1); }
.client-row .panel-row-name { font-size: var(--text-lg); }
.client-row .panel-row-sub { font-size: var(--text-base); color: var(--text-subtle); }

/* BIGGER THAN A FOOTNOTE. "21/61 this month" is the number a reseller scans
   the list for; at --text-sm it read as metadata about the row rather than as
   the point of it. */
.client-summary {
  color: var(--text-muted);
  font-size: var(--text-base);
  line-height: var(--leading-snug);
  display: grid; gap: var(--space-1);
  text-align: right;
}
.client-summary .client-behind { color: var(--warning); font-size: var(--text-sm); }
.client-summary strong { font-size: var(--text-lg); color: var(--text); }
.client-open { margin-left: auto; flex: 0 0 auto; }
@media (max-width: 34rem) {
  /* On a phone the row stacks; Open goes full width at the bottom where a
     thumb already is, rather than floating right on a line of its own. */
  .client-summary { text-align: left; }
  .client-open { margin-left: 0; width: 100%; }
  .client-open .btn { width: 100%; }
}

/* ── CLIENT HUB: channels are the work, so they are the biggest thing ───── */
.chan-list { display: grid; gap: var(--space-4); list-style: none; margin: 0; padding: 0; }
.chan {
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: var(--radius-md);
}
.chan.is-risk { border-left-color: var(--danger); }
.chan.is-behind { border-left-color: var(--warning); }
.chan.is-met, .chan.is-over, .chan.is-on-track { border-left-color: var(--accent); }
.chan-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.chan-name { font-size: var(--text-lg); font-weight: var(--weight-bold); text-transform: capitalize; }
.chan-count { margin-left: auto; font-weight: var(--weight-bold); font-variant-numeric: tabular-nums; }
.chan-note { color: var(--text-subtle); font-size: var(--text-sm); margin-bottom: var(--space-3); }
.chan-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.chan-actions .btn { flex: 1 1 auto; }
@media (min-width: 34rem) { .chan-actions .btn { flex: 0 0 auto; } }

/* The inline target, which replaced a separate six-field block. Same route,
   same override behaviour — it just lives beside the channel it governs. */
.chan-target { margin-top: var(--space-3); }
.chan-target > summary {
  cursor: pointer; font-size: var(--text-sm); color: var(--text-subtle);
  min-height: 2.75rem; display: flex; align-items: center;
}
.chan-target > summary:hover { color: var(--accent); }
.chan-target form {
  display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: end;
  margin-top: var(--space-2);
}
.chan-target .field { margin-bottom: 0; flex: 1 1 10rem; }
.chan-target p { margin: 0; }

/* The plan picker sits on the client page beside its targets. */
.plan-pick { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: end; }
.plan-pick .field { margin-bottom: 0; flex: 1 1 14rem; }

/* A BRIEF SETTING IS A RANGE, drawn as a checkbox group. Checkboxes and not a
   multi-select for the reason the channel picker already records: a native
   multi-select on a phone is a trap. */
.brief-set {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin-top: var(--space-4);
  background: var(--surface);
}
.brief-set > legend {
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  padding-inline: var(--space-2);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.brief-set-state {
  color: var(--text-muted);
  font-size: var(--text-sm);
  margin-bottom: var(--space-3);
  min-height: 1.4em;
}
.panel-checks--dense { gap: var(--space-2); }
@media (min-width: 34rem) {
  .panel-checks--dense { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 52rem) {
  .panel-checks--dense { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* THE MASTER CHECKBOX. Visually the head of the group, not one of its members:
   a rule above the options rather than a seventh option among them, or it reads
   as another value the planner might pick. */
.panel-check--master {
  font-weight: var(--weight-bold);
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-3);
  border-bottom: 1px solid var(--border);
}

.brief-suggest {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}
.brief-suggest .panel-note { flex: 1 1 16rem; margin: 0; }

/* THE AI BUTTON. A secondary action that must not read as a disabled one — it
   takes the accent's tint and border rather than the neutral surface a plain
   .btn now carries, so it is visibly the thing to press without competing with
   the page's one filled button (Save brief). */
.btn--ai {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-press);
}
.btn--ai:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  box-shadow: var(--shadow-sm);
}

/* THE SPINNER IS ZERO-WIDTH UNTIL THE CALL STARTS, so the button does not
   change size when it becomes busy — a control that resizes under the cursor
   is how a second click lands somewhere else. */
.brief-spinner { display: none; }
.btn.is-busy .brief-spinner {
  display: inline-block;
  width: 0.9em; height: 0.9em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: brief-spin 0.7s linear infinite;
}
@keyframes brief-spin { to { transform: rotate(360deg); } }
/* Someone who asked the system to stop moving things gets a static ring; the
   button text already says what is happening. */
@media (prefers-reduced-motion: reduce) {
  .btn.is-busy .brief-spinner { animation: none; }
}

.panel-flash--busy {
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  color: var(--text);
}

/* TAP TARGETS ON THE CHECKBOX GROUPS, keyed on pointer type exactly as §18.2
   keys the buttons — a small laptop is not a phone and a large tablet is. The
   whole label is the target, so the row is what has to be 44px, not the box. */
@media (pointer: coarse) {
  .panel-check { min-height: 2.75rem; }
}

/* The written brief, shown beside the pack form while posting. A <details>, so
   it costs no JavaScript and is collapsed by default on a phone. */
.brief-ref {
  max-width: var(--measure); margin: 0 auto var(--space-5);
  padding: var(--space-4) var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.brief-ref summary { cursor: pointer; font-weight: var(--weight-bold); }
.brief-ref-item { margin-top: var(--space-4); }
.brief-ref-item h3 { font-size: var(--text-sm); color: var(--text-subtle); margin-bottom: var(--space-1); }
.brief-ref-item p { color: var(--text-muted); white-space: pre-wrap; }
.brief-ref-empty { margin-top: var(--space-3); color: var(--text-subtle); }

/* ---- panel: Plan Posting + the compose modal (Phase 3.5) ---- */
.panel-plan-form {
  display: grid; gap: var(--space-4); align-items: end;
  grid-template-columns: 1fr; margin-bottom: var(--space-6);
}
@media (min-width: 34rem) { .panel-plan-form { grid-template-columns: 10rem auto; } }
.panel-plan-form .field { margin-bottom: 0; }
.panel-plan-form p { margin: 0; }
.panel-row--idea { align-items: start; }
.panel-row--idea .panel-row-sub { display: block; margin-top: var(--space-1); }
.panel-logged-note {
  margin-bottom: var(--space-4); padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--accent-soft);
  background: color-mix(in srgb, var(--accent) 12%, var(--surface));
  color: var(--text); font-size: var(--text-sm);
}

/* THE COMPOSE MODAL. A native <dialog> for the reasons §21.1 chose one for the
   lightbox — the platform owns focus containment, Escape and inertness. It
   holds the SHARED pack-form partial, so the form inside inherits every rule
   the app page's form already has. */
.pack-modal {
  width: 100%; max-width: var(--measure);
  max-height: 92vh;
  margin: 0 auto;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  overflow: auto;
  overscroll-behavior: contain;
}
.pack-modal::backdrop { background: var(--overlay); }

/* A DONE IDEA IS DIMMED, NOT HIDDEN. The reseller asked for four ideas and
   should still see four — what changed is that one is handled. Removing it
   would make the list shrink under them and lose the sense of progress the
   heading is counting. */
.panel-row.is-done { opacity: .6; }
.panel-row.is-done .panel-row-name { text-decoration: line-through; }
.btn.is-done-mark {
  background: var(--accent-soft);
  border-color: var(--accent-soft);
  color: var(--text-muted);
  cursor: default;
}
.btn.is-done-mark:hover {
  background: var(--accent-soft);
  border-color: var(--accent-soft);
  color: var(--text-muted);
}
.panel-progress {
  font-size: var(--text-sm);
  font-weight: var(--weight-normal);
  color: var(--text-subtle);
  margin-left: var(--space-2);
}

/* CAPTURE ON CLOSE — small, centred, one question.

   `margin: auto` IS WHAT CENTRES IT, and only once the dialog is in the top
   layer: a modal dialog's containing block is the viewport, so auto margins
   centre it on both axes. panel-forms.js calls showModal() to put it there —
   a bare `<dialog open>` is NON-modal, sits in the normal flow and renders no
   ::backdrop at all, which is why the ideas list used to show through it.
   Without the script the rules below still draw a legible centred card; it is
   just inline, which is a degraded modal rather than a broken form. */
.capture-dialog {
  width: 100%; max-width: var(--measure-xs);
  margin: auto;
  max-height: 90vh; overflow: auto;
  padding: var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-md);
}
.capture-dialog::backdrop { background: var(--overlay); }
@media (max-width: 34rem) {
  /* Full-bleed on a phone: a 30rem card with viewport margins leaves the date
     picker and both buttons fighting for one thumb-width. */
  .capture-dialog { max-width: 100%; max-height: 100vh; border-radius: 0; padding: var(--space-4); }
}
.capture-dialog h2 { font-size: var(--text-lg); margin-bottom: var(--space-2); }
.capture-dialog .field { margin-top: var(--space-4); }
.capture-actions {
  display: flex; flex-wrap: wrap; gap: var(--space-3);
  margin-top: var(--space-5);
}
.pack-modal-close-row {
  display: flex; justify-content: flex-end;
  padding: var(--space-3) var(--space-4) 0;
}
.pack-modal-body { padding: var(--space-4) var(--space-5) var(--space-7); }
/* The modal is a scroll container, so on touch it must not be a NESTED one
   inside another (§18.7). It is the page's only scroller here: the panel body
   behind it is inert while a modal dialog is open. */
@media (max-width: 48rem) {
  .pack-modal {
    max-width: 100%; max-height: 100vh; border: 0; border-radius: 0;
  }
  .pack-modal-body { padding: var(--space-3) var(--space-4) var(--space-7); }
}

/* ============================================================================
   MOBILE: STATIC, EAGER, UNBREAKABLE                          (<= 48rem)
   ============================================================================
   Everything above this block is the desktop design and is untouched by it.
   What follows replaces the two CLEVER layouts with the most boring ones CSS
   has, because both of them broke on real iPhones while every measurement here
   said they were fine — twice. A layout that cannot be verified on the engine
   it ships to should not be the layout that ships to it.

   THE INVARIANT, and it is a CSS guarantee rather than a measurement: a
   block-level image at `width: 100%; height: auto; max-width: 100%` inside a
   single-column flow CANNOT overflow its container and CANNOT crop. There is
   no aspect-ratio box to fight, no `object-fit` deciding what to trim, no
   transform moving it, and nothing deferred that might never arrive. Whatever
   the file's shape — 9:16, 16:9, 4:5, 1:1 or 1.91:1 — it renders whole and the
   row simply gets as tall as it needs.

   WHAT IS DELIBERATELY GONE BELOW THIS WIDTH: the marquee animation, the
   duplicated track, the aspect-ratio frames, `object-fit: cover`, lazy
   loading and the tap-to-zoom. Each was a mechanism that had to WORK for the
   page to look right; none of them is now load-bearing on a phone.
   ========================================================================= */
@media (max-width: 48rem) {
  /* ---- 1. the landing marquee becomes a static grid ----
     No animation, no duplicate, no clipping viewport. TWO across, because this
     strip exists to show RANGE at a glance rather than to be read — twelve
     samples one-up would add some five thousand pixels to the homepage — and
     because two columns here still draw each frame wider than the marquee's
     own 240px item ever did on a phone. */
  .marquee {
    overflow: visible;
    padding-block: 0;
  }
  .marquee-track {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-4);
    align-items: start;
    width: auto;
    animation: none;
    will-change: auto;
  }
  /* The duplicate half exists ONLY to hide the loop's seam. There is no loop
     here, so it is twelve aria-hidden copies of what is already on screen. */
  .marquee-track .marquee-dup { display: none; }
  /* The gap lives on the grid now, not on the item (§15L.5's rule applies to
     the flex track, and there is no flex track at this width). */
  .marquee-item { margin-inline-end: 0; }

  /* ---- 2. every sample gallery becomes a single-column stack ----
     One image per row, full width. These ARE the product on a sales page, so
     they are read rather than glanced at.
     EVERY MODIFIER IS NAMED at its own (0,2,0) weight: this block is last in
     the file, so an equal-specificity rule wins on order, but a bare `.gallery`
     alone would LOSE to `.gallery.gallery--post { repeat(2, …) }` between 40rem
     and 48rem and quietly put two columns back on a phone-sized tablet. */
  .gallery,
  .gallery.gallery--post,
  .gallery.gallery--square,
  .gallery.gallery--wide { grid-template-columns: 1fr; }

  /* ---- 3. the frames stop constraining the image ----
     THIS IS THE PART THAT MATTERS. A frame declares `aspect-ratio` and its
     screen is `height: 100%`, so the image was sized by the BOX and `cover`
     decided what to trim. Releasing both hands the decision to the file: the
     box becomes whatever shape the image is, and there is nothing left to
     crop. Enumerated rather than matched with `[class$="-screen"]`, so the
     rule says exactly which frames it governs. */
  .phone, .thumb, .post, .square, .banner { aspect-ratio: auto; }
  .phone-screen, .thumb-screen, .post-screen, .square-screen, .banner-screen { height: auto; }
  .phone-screen img, .thumb-screen img, .post-screen img,
  .square-screen img, .banner-screen img {
    display: block;
    width: 100%;
    height: auto;
    max-width: 100%;
    object-fit: fill;
  }
  /* The hero mock is capped narrow on desktop so it reads as a device beside
     the pitch; on a phone it is simply the first sample and takes the width. */
  .phone--hero, .thumb--hero, .post--hero, .square--hero { max-width: none; }
  /* The notch is chrome for a device mock that is no longer being drawn at
     device size, and it sits ON the image now that the frame is not 9:16. */
  .phone::before { display: none; }

  /* ---- 4. no tap-to-zoom ----
     The image is already full width and full height here, so the lightbox has
     nothing larger to show — and a tap target covering an entire full-bleed
     image is a trap on a page someone is scrolling through. `pointer-events`
     is the STRUCTURAL half: the tap cannot reach the handler even if the
     script loaded and bound it. lightbox.js carries the matching guard for a
     keyboard activating the button, which pointer-events does not cover. */
  .zoom-trigger {
    pointer-events: none;
    cursor: default;
  }
  .zoom-hint { display: none; }
}

/* ============================================================================
   /white-label — the reseller subscription's two new components
   ----------------------------------------------------------------------------
   Everything else on that page reuses what is already here: .wrap, .band,
   .section-head, .btn, .hero--lead, .steps--numbered, .benefits, .turn,
   .personas, .ticks, .packs-note and .closing, all with the tokens they already
   use (§14.3 — if two pages need the same thing to look the same, it lives
   here, and a page-local copy is how two surfaces drift apart). Only the plans
   table and the FAQ had no existing component, so only they are added.

   MOBILE-FIRST, like every block above (§15L.4): the single-column shape is the
   BASE rule and the wider layouts arrive at a breakpoint, so a phone gets the
   simple case rather than an override of a complex one. No literal colour
   anywhere below (§14.2).
   ========================================================================= */

/* ---- the plans table ----
   ONE COLUMN to 46rem, then three across. Deliberately NOT riding on .grid
   (3/2/1): three tiers compared against each other want to stay a row of three
   or become a stack, and .grid's two-column middle step would orphan the
   Agency card on its own line at tablet width. */
.plans {
  display: grid; gap: var(--space-4);
  grid-template-columns: 1fr;
  margin: 0; padding: 0; list-style: none;
}
/* THREE ACROSS ONLY WHEN THREE FIT, and 52rem is a MEASURED number rather than
   the 46rem the rest of this file uses. A price is one unbreakable string —
   "₹1,999/mo" has no space to wrap at — and at --text-3xl it needs ~234px,
   while a third of the content box at 46rem is 179px. minmax(0, 1fr) stops a
   TRACK being forced wider than its share, but an unbreakable string inside the
   track still overflows it (§18.3's rule reaches the track, not its contents),
   and it pushed the whole page sideways by up to 13px between 736px and 792px.

   Measured in a real browser across 320–1400px rather than reasoned about:
   zero overflow at every width once the split moves here. Raising the
   breakpoint is the fix at the CAUSE — `overflow-x: hidden` would have hidden
   the evidence (§18.10) and hyphenating a price is worse than a stacked card. */
@media (min-width: 52rem) {
  /* minmax(0, 1fr) so a long price string cannot force a column wider than its
     share and push the page sideways (§18.3's rule in its grid form). */
  .plans { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.plan {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition), border-color var(--transition);
}
.plan:hover { box-shadow: var(--shadow-md); border-color: var(--border-strong); }

/* The middle tier carries the accent border. A RING, not a different
   background: the three cards are being compared, and recolouring one of them
   changes what the comparison looks like as well as which is recommended. */
.plan--featured { border-color: var(--accent); box-shadow: var(--shadow-md); }

.plan-name { font-size: var(--text-lg); font-weight: var(--weight-bold); margin: 0; }
.plan-price {
  font-size: var(--text-3xl); font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
  margin: 0;
}
.plan-clients { color: var(--text-muted); margin: 0; }
/* margin-top:auto pins the button to the bottom of every card, so three cards
   of different text length still line their CTAs up. */
.plan-go { margin: auto 0 0; padding-top: var(--space-4); }
.plan-go .btn { width: 100%; }

.plan-includes { margin-top: var(--space-6); }
.plan-includes-label {
  font-size: var(--text-sm); font-weight: var(--weight-bold);
  color: var(--text-muted);
  margin-bottom: var(--space-3);
}

/* ---- the FAQ ----
   A definition list of plain headings and paragraphs — no accordion, no script,
   no state (§18.11: on a phone, prefer a layout with no mechanism in it). It is
   also what lets the FAQPage schema be honest: every answer is visible. */
.faq { margin: 0; padding: 0; }
.faq-item {
  padding-block: var(--space-5);
  border-top: 1px solid var(--border);
}
.faq-item:first-child { border-top: 0; padding-top: 0; }
.faq-q {
  font-size: var(--text-lg); font-weight: var(--weight-bold);
  line-height: var(--leading-snug);
  margin-bottom: var(--space-2);
}
.faq-a {
  /* A <dd> ships with margin-inline-start: 40px from the UA stylesheet, which
     on a 360px phone is 40px off a 312px content box — the same class of bug
     §18.8 records for <figure>. Reset where it is used rather than worked
     around. */
  margin: 0;
  max-width: var(--measure-sm);
  color: var(--text-muted);
  font-size: var(--text-lg);
  line-height: var(--leading-body);
}
