/* =========================================================================
   Datathreads — datathreads.ai
   Hand-written, self-contained. No external fonts, scripts or assets.

   Three voices, because the argument is typographic:
     · grotesque  — our voice
     · serif      — the shopper's prose (what a human reads)
     · monospace  — the record (what a machine reads)

   Palette: paper + ink, indigo as the single accent — a dye, not a gradient.
   ========================================================================= */

/* ---------- 1. Tokens ---------- */

:root {
  --paper:      #F1EFE9;
  --paper-2:    #FBFAF7;
  --ink:        #15140F;
  --ink-2:      #3D3A33;
  --ink-3:      #6E6A5F;
  --rule:       #D9D4C7;
  --rule-2:     #C3BCAB;

  --indigo:     #2A3A8F;
  --indigo-ink: #FFFFFF;
  --indigo-wash:#E3E5F4;

  --flag:       #8E3A20;
  --flag-wash:  #F3E6DF;
  --star:       #B8851F;

  /* The machine panel is a dark surface in BOTH themes — it is the terminal,
     not the page. These seven are deliberately theme-invariant: they are
     never re-declared below, because flipping them would put dark ink on a
     near-black ground. */
  /* The machine palette is theme-INVARIANT on purpose: the two scenes in
     "When a shopper asks an AI" are a terminal, and a terminal does not become
     beige because the page did.

     --panel-* is the theme-AWARE counterpart, for surfaces that are dark only
     because they were drawn that way: the agent's reading of a product, and
     the guardrails band. Those are the site speaking, not a machine, so they
     follow the reader's choice. Values below are the light theme; both dark
     blocks redefine them to the machine colours. */
  --panel-bg:   #FBFAF7;
  --panel-fg:   #15140F;
  --panel-dim:  #6E6A5F;
  --panel-rule: #D9D4C7;
  --panel-hit:  #2A3A8F;
  --panel-flag: #8E3A20;
  --panel-head: #15140F;
  --panel-flag-wash: rgba(142,58,32,.07);

  --mach-bg:    #14161B;
  --mach-fg:    #CBD3DC;
  --mach-dim:   #767E89;
  --mach-rule:  #262B33;
  --mach-hit:   #A8B6FF;
  --mach-flag:  #E8956E;

  /* Four dyes for the product sets. Also theme-invariant: they only ever
     appear on the dark machine ground. Named after the dyestuff, not the
     hex, because the subject is textiles. */
  --dye-linen:  #A8B6FF;
  --dye-cotton: #E8956E;
  --dye-wool:   #D9C07A;
  --dye-silk:   #7FC8AC;

  --linen-1:    #DED7C8;
  --linen-2:    #CFC6B3;
  --linen-3:    #BCB29C;

  --shadow:     0 1px 2px rgba(21,20,15,.05), 0 8px 26px -14px rgba(21,20,15,.22);

  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue",
          Helvetica, Arial, "Liberation Sans", sans-serif;
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua",
           Georgia, "Times New Roman", serif;
  --mono: ui-monospace, Menlo, Monaco, "SF Mono", "Cascadia Mono",
          "DejaVu Sans Mono", Consolas, monospace;

  --wrap: 1080px;
  --wrap-wide: 1240px;
  --pad: clamp(20px, 5vw, 48px);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
  --panel-bg:   #14161B;
  --panel-fg:   #CBD3DC;
  /* #767E89 measured 4.41:1 on this ground — just under WCAG AA's 4.5 for
     normal text, and this token carries the guardrail body copy and the agent
     panel's labels. #7C8490 is 4.79:1. The machine palette keeps the original,
     because its text is larger and it is a terminal, not prose. */
  --panel-dim:  #7C8490;
  --panel-rule: #262B33;
  --panel-hit:  #A8B6FF;
  --panel-flag: #E8956E;
  --panel-head: #F4F2EC;
  --panel-flag-wash: rgba(232,149,110,.09);

    --paper:      #101114;
    --paper-2:    #191B20;
    --ink:        #EBE8DF;
    --ink-2:      #B9B5AA;
    --ink-3:      #8C887D;
    --rule:       #2B2E35;
    --rule-2:     #3C4048;

    --indigo:     #9DAAFF;
    --indigo-ink: #0F1226;
    --indigo-wash:#1D2242;

    --flag:       #E29271;
    --flag-wash:  #33211A;
    --star:       #E8B54A;

    --linen-1:    #4A4639;
    --linen-2:    #5C5747;
    --linen-3:    #6E6754;

    --shadow:     0 1px 2px rgba(0,0,0,.4), 0 10px 30px -16px rgba(0,0,0,.8);
  }
}

:root[data-theme="dark"] {
  --panel-bg:   #14161B;
  --panel-fg:   #CBD3DC;
  /* #767E89 measured 4.41:1 on this ground — just under WCAG AA's 4.5 for
     normal text, and this token carries the guardrail body copy and the agent
     panel's labels. #7C8490 is 4.79:1. The machine palette keeps the original,
     because its text is larger and it is a terminal, not prose. */
  --panel-dim:  #7C8490;
  --panel-rule: #262B33;
  --panel-hit:  #A8B6FF;
  --panel-flag: #E8956E;
  --panel-head: #F4F2EC;
  --panel-flag-wash: rgba(232,149,110,.09);
  --paper:      #101114;
  --paper-2:    #191B20;
  --ink:        #EBE8DF;
  --ink-2:      #B9B5AA;
  --ink-3:      #8C887D;
  --rule:       #2B2E35;
  --rule-2:     #3C4048;

  --indigo:     #9DAAFF;
  --indigo-ink: #0F1226;
  --indigo-wash:#1D2242;

  --flag:       #E29271;
  --flag-wash:  #33211A;
  --star:       #E8B54A;

  --linen-1:    #4A4639;
  --linen-2:    #5C5747;
  --linen-3:    #6E6754;

  --shadow:     0 1px 2px rgba(0,0,0,.4), 0 10px 30px -16px rgba(0,0,0,.8);
}

:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }

/* ---------- 2. Reset & base ---------- */

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.6;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  /* `clip`, not `hidden`: `hidden` would make <body> a scroll container and
     break the sticky header in several browsers. */
  overflow-x: clip;
}

h1, h2, h3, p, ul, ol, dl, dd, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }
a { color: inherit; }
img, svg { max-width: 100%; }

:focus-visible {
  outline: 2px solid var(--indigo);
  outline-offset: 3px;
  border-radius: 2px;
}

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.skip {
  position: absolute; left: 12px; top: -60px; z-index: 100;
  background: var(--ink); color: var(--paper);
  padding: 10px 16px; font-size: 14px; font-weight: 600;
  text-decoration: none; transition: top .15s;
}
.skip:focus { top: 12px; }

.wrap {
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--pad);
}
.wrap--wide { max-width: var(--wrap-wide); }

[id] { scroll-margin-top: 76px; }

/* ---------- 3. Header ---------- */

.site-head {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: saturate(160%) blur(10px);
  border-bottom: 1px solid var(--rule);
}
@supports not (backdrop-filter: blur(1px)) {
  .site-head { background: var(--paper); }
}
.site-head__in {
  display: flex; align-items: center; gap: 20px;
  min-height: 60px;
}
.brand {
  display: inline-flex; align-items: center; gap: 10px;
  text-decoration: none; margin-right: auto;
}
.brand__mark { flex: none; display: block; }
.brand__tile { fill: var(--ink); }
.brand__dt {
  fill: var(--paper);
  font-family: var(--sans);
  font-size: 40px; font-weight: 700; letter-spacing: -.04em;
}
.brand__word { font-weight: 650; letter-spacing: -.03em; font-size: 17px; }

.site-nav { display: flex; align-items: center; gap: 22px; font-size: 14.5px; }
.site-nav a {
  text-decoration: none; color: var(--ink-2);
  padding: 4px 0; border-bottom: 1px solid transparent;
}
.site-nav a:hover { color: var(--ink); border-bottom-color: var(--indigo); }
.site-nav a.site-nav__cta {
  color: var(--ink); font-weight: 600;
  border: 1px solid var(--rule-2); border-radius: 999px; padding: 5px 14px;
}
.site-nav a.site-nav__cta:hover {
  background: var(--ink); color: var(--paper); border-color: var(--ink);
}
@media (max-width: 860px) {
  .site-nav a:not(.site-nav__cta) { display: none; }
}

/* In light, a crescent — the button offers you dark.
   In dark, a full disc — the button offers you light. */
.theme-toggle {
  width: 40px; height: 40px; padding: 0;
  border: 1px solid var(--rule-2); border-radius: 999px;
  background: transparent; cursor: pointer;
  display: grid; place-items: center; flex: none;
}
.theme-toggle:hover { border-color: var(--ink-3); }
/* Sun and moon as real shapes, not a circle with a bite taken out of it.
   The button offers what you GET by pressing it: a moon on a light page, a
   sun on a dark one. Exactly one of the two is ever in the flow. */
.theme-toggle__icon { width: 18px; height: 18px; display: block; }
.theme-toggle__sun { display: none; }
.theme-toggle__sun circle { fill: var(--ink); }
.theme-toggle__sun path {
  fill: none; stroke: var(--ink); stroke-width: 2; stroke-linecap: round;
}
.theme-toggle__moon { fill: var(--ink); }

/* Dark: the sun, and the moon goes. Both blocks, because the media query is
   the system preference and the attribute is the reader overriding it. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle__sun { display: block; }
  :root:not([data-theme="light"]) .theme-toggle__moon { display: none; }
}
:root[data-theme="dark"] .theme-toggle__sun { display: block; }
:root[data-theme="dark"] .theme-toggle__moon { display: none; }
/* And the explicit light choice wins back, for a reader on a dark OS. */
:root[data-theme="light"] .theme-toggle__sun { display: none; }
:root[data-theme="light"] .theme-toggle__moon { display: block; }

/* ---------- 4. Shared ---------- */

.sec { padding-block: clamp(64px, 8vw, 104px); }
.sec--tint { background: var(--paper-2); border-block: 1px solid var(--rule); }

/* Sections built around an animation: the whole argument has to be on screen
   at once, so they trade padding for fit and cap against viewport height. */
.sec--fit { padding-block: clamp(36px, 4.4vh, 60px); }
.sec--fit .sec__lede { margin-top: 12px; font-size: clamp(.96rem, 1.2vw, 1.05rem); }

.sec__h2 {
  font-size: clamp(1.7rem, 3.4vw, 2.6rem);
  font-weight: 680; letter-spacing: -.035em; line-height: 1.05;
}
.sec__lede {
  margin-top: 16px; max-width: 58ch;
  font-size: clamp(1rem, 1.4vw, 1.15rem); color: var(--ink-2);
}

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  font: inherit; font-weight: 600; font-size: .96rem;
  padding: 12px 22px; border-radius: 3px;
  text-decoration: none; cursor: pointer;
  border: 1px solid transparent;
  transition: background .16s, color .16s, border-color .16s;
}
.btn--primary { background: var(--ink); color: var(--paper); }
.btn--primary:hover { background: var(--indigo); color: var(--indigo-ink); }
.btn--ghost { border-color: var(--rule-2); color: var(--ink-2); background: transparent; }
.btn--ghost:hover { border-color: var(--ink); color: var(--ink); }
.btn--big { padding: 15px 32px; font-size: 1.05rem; }
.btn--small {
  padding: 6px 14px; font-size: 13px;
  border-color: var(--rule-2); background: transparent; color: var(--ink-2);
}
.btn--small:hover { border-color: var(--ink); color: var(--ink); }

.fineprint { margin-top: 26px; font-size: 13.5px; color: var(--ink-3); }

/* ---------- 5. Hero ---------- */

.hero {
  padding-top: clamp(40px, 5vw, 68px);
  padding-bottom: clamp(46px, 6vw, 78px);
}
.hero__in {
  display: grid; gap: 40px;
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
}
@media (min-width: 980px) {
  .hero__in { grid-template-columns: 1fr 1fr; gap: 56px; }
}
/* Text leads on narrow screens; the chat follows rather than pushing the
   promise below the fold. */
.hero__head { min-width: 0; }
@media (max-width: 979px) {
  .hero__head { order: -1; }
}

/* A category statement, not a label: set on a rule, at reading size. */
.hero__eyebrow {
  display: flex; align-items: center; gap: 13px;
  font-family: var(--mono); font-weight: 600;
  font-size: clamp(13px, 1.5vw, 16px);
  letter-spacing: .17em; text-transform: uppercase;
  color: var(--indigo);
  margin-bottom: 22px;
}
.hero__eyebrow::before {
  content: ""; flex: none;
  width: clamp(22px, 3vw, 34px); height: 2px;
  background: var(--indigo);
}
.hero__h1 {
  font-size: clamp(2.6rem, 7.4vw, 5rem);
  font-weight: 690; letter-spacing: -.045em; line-height: .95;
  text-wrap: balance;
}
/* The second half of the promise sits one step back in the hierarchy —
   the type does the honest work without a disclaimer. */
.hero__h1 span { color: var(--ink-3); }

.hero__lede {
  margin-top: 22px; max-width: 30ch;
  font-family: var(--serif);
  font-size: clamp(1.1rem, 1.8vw, 1.4rem);
  line-height: 1.4; letter-spacing: -.012em;
  color: var(--ink-2);
}
.hero__sub {
  margin-top: 18px; max-width: 46ch;
  font-size: clamp(.95rem, 1.2vw, 1.04rem);
  line-height: 1.6; color: var(--ink-3);
}
.hero__cta {
  margin-top: 28px;
  display: flex; gap: 12px; flex-wrap: wrap;
}

/* ---------- 5b. The chat ----------
   The hero explainer, played as the conversation it actually is: a question,
   a tool call against the catalog, an answer carrying a product that can
   prove what it is, an order, and a review. Deliberately a generic assistant
   window, not any vendor's chrome, since we are not claiming a partnership.
   Default state is the last beat, so with no JS it reads as a finished
   transcript. */

.chat { margin: 0; }

.chat__win {
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--paper-2);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.chat__bar {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--rule);
  background: color-mix(in srgb, var(--ink) 4%, var(--paper-2));
}
.chat__lights { display: flex; gap: 5px; }
.chat__lights i {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--rule-2);
}
.chat__title {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: .13em; text-transform: uppercase; color: var(--ink-3);
}

/* `hidden` must win over any display rule we write.
   The browser's own `[hidden] { display: none }` has the lowest possible
   weight, so `.msg--ai { display: flex }` silently beat it and step two's
   assistant bubble rendered as a stray 19px avatar below the button before
   anyone had searched. One rule, because this bug recurs anywhere a display
   rule meets the attribute. */
[hidden] { display: none !important; }

/* the transcript. Scrolled by the driver so the newest message stays in
   view, exactly as a real chat log behaves. */
.chat__log {
  position: relative;            /* offsetTop of a message resolves to here */
  height: clamp(252px, 33vh, 312px);
  overflow: hidden;
  scroll-behavior: smooth;
  padding: 14px 14px 16px;
  display: flex; flex-direction: column; gap: 12px;
}

.msg { transition: opacity .4s ease, transform .4s ease; }
.chat[data-anim="on"] .msg { opacity: 0; transform: translateY(8px); }
.chat[data-anim="on"] .msg.is-in { opacity: 1; transform: none; }

/* the shopper */
.msg--me { align-self: flex-end; max-width: 84%; }
.msg--me p {
  background: var(--ink); color: var(--paper);
  padding: 8px 13px; border-radius: 13px 13px 3px 13px;
  font-size: .88rem; line-height: 1.4;
}
.chat__type {
  display: inline-block; overflow: hidden; white-space: nowrap;
  vertical-align: bottom; width: 42ch;
}
.chat[data-anim="on"] .chat__type { width: 0; }
.chat[data-anim="on"] .chat__type.is-typing {
  animation: type 1s steps(42) forwards;
}
@keyframes type { to { width: 42ch; } }

/* the tool call against the catalog */
.msg--tool {
  display: grid; align-self: flex-start;
  font-family: var(--mono); font-size: 10.5px;
  letter-spacing: .04em; color: var(--ink-3);
  padding-left: 2px;
}
.tool__run, .tool__done {
  grid-area: 1 / 1; display: flex; align-items: center; gap: 7px;
  opacity: 0; transition: opacity .3s ease;
}
.chat[data-beat="2"] .tool__run { opacity: 1; }
.chat[data-beat="3"] .tool__done,
.chat[data-beat="4"] .tool__done,
.chat[data-beat="5"] .tool__done { opacity: 1; }
.tool__spin {
  width: 10px; height: 10px; border-radius: 50%; flex: none;
  border: 1.5px solid var(--rule-2); border-top-color: var(--indigo);
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.tool__ok {
  width: 10px; height: 10px; border-radius: 50%; flex: none;
  background: var(--indigo); position: relative;
}
.tool__ok::after {
  content: ""; position: absolute; left: 3px; top: 1.5px;
  width: 3px; height: 5px;
  border: solid var(--indigo-ink); border-width: 0 1.5px 1.5px 0;
  transform: rotate(42deg);
}

/* the assistant */
.msg--ai { display: flex; gap: 9px; align-self: flex-start; max-width: 94%; }
.msg__ai {
  width: 19px; height: 19px; border-radius: 6px; flex: none;
  background: var(--ink); position: relative; margin-top: 1px;
}
.msg__ai::after {
  content: ""; position: absolute; inset: 5px;
  border-radius: 2px; background: var(--paper-2);
}
.msg__body { min-width: 0; }
.msg__line { font-size: .88rem; line-height: 1.45; color: var(--ink); }

/* the products it can actually return, and the one a person picks */
.prods { margin-top: 9px; display: grid; gap: 6px; }
.prod {
  display: flex; gap: 9px; align-items: center;
  border: 1px solid var(--rule-2); border-radius: 6px;
  padding: 7px; background: var(--paper);
  transition: opacity .35s ease, border-color .35s ease, box-shadow .35s ease;
}
.prod__sw {
  width: 38px; height: 38px; border-radius: 4px; flex: none;
  background-image:
    repeating-linear-gradient(90deg, var(--linen-3) 0 1px, transparent 1px 5px),
    repeating-linear-gradient(0deg,  var(--linen-3) 0 1px, transparent 1px 5px);
  background-color: var(--linen-1);
}
.prod__sw[data-sw="2"] { background-color: var(--linen-2); }
.prod__sw[data-sw="3"] { background-color: var(--linen-3); }
.prod__meta { min-width: 0; }
.prod__t {
  font-size: .82rem; font-weight: 650; letter-spacing: -.015em;
  line-height: 1.25;
}
.prod__t em {
  font-style: normal; font-weight: 400; color: var(--ink-3);
  margin-left: 5px;
}
.prod__chips { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 5px; }
.prod__chips b {
  font-family: var(--mono); font-size: 8.5px; font-weight: 600;
  letter-spacing: .03em; padding: 2px 5px; border-radius: 3px;
  background: var(--indigo); color: var(--indigo-ink);
}

/* the pick: only once the shopper has chosen, at beat 4 */
.chat[data-beat="4"] .prod--pick,
.chat[data-beat="5"] .prod--pick {
  border-color: var(--indigo);
  box-shadow: 0 0 0 2px var(--indigo-wash);
}
.chat[data-beat="4"] .prod:not(.prod--pick),
.chat[data-beat="5"] .prod:not(.prod--pick) { opacity: .38; }

/* what came of it */
.msg--note {
  align-self: stretch; margin-top: 2px;
  border: 1px solid var(--rule-2); border-radius: 6px;
  padding: 11px 13px 13px; background: var(--paper);
}
.note__k {
  font-family: var(--mono); font-size: 9.5px;
  letter-spacing: .15em; text-transform: uppercase; color: var(--ink-3);
}
.note__checks { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.note__checks li {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .03em;
  padding: 3px 7px; border-radius: 999px;
  border: 1px solid var(--indigo); color: var(--indigo);
}
.note__checks li::before { content: "\2713\00a0"; }
.note__stars { display: flex; gap: 3px; margin-top: 10px; }
.note__stars svg {
  width: 17px; height: 17px; fill: var(--rule-2);
  transform: scale(.8);
  transition: fill .3s ease, transform .3s cubic-bezier(.3,1.4,.5,1);
}
.chat[data-beat="5"] .note__stars svg { fill: var(--star); transform: none; }
.chat[data-beat="5"] .note__stars svg:nth-child(2) { transition-delay: .09s; }
.chat[data-beat="5"] .note__stars svg:nth-child(3) { transition-delay: .18s; }
.chat[data-beat="5"] .note__stars svg:nth-child(4) { transition-delay: .27s; }
.chat[data-beat="5"] .note__stars svg:nth-child(5) { transition-delay: .36s; }
.note__q {
  margin-top: 7px; font-family: var(--serif);
  font-size: .95rem; color: var(--ink);
}

/* beat dots + caption */
.chat__foot {
  margin-top: 13px;
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px 14px; flex-wrap: wrap;
}
.chat__dots { display: flex; gap: 2px; margin-left: -6px; }
/* The bar is 4px, but the button is finger-sized: the hit area is padding,
   and the visible mark is drawn with a pseudo-element. */
.chat__dots button {
  width: 34px; height: 34px; padding: 0; border: 0;
  background: transparent; cursor: pointer;
  display: grid; place-items: center;
}
.chat__dots button::after {
  content: ""; width: 22px; height: 4px; border-radius: 2px;
  background: var(--rule-2);
  transition: background-color .25s ease;
}
.chat__dots button:hover::after { background: var(--ink-3); }
.chat__dots button[aria-current="true"]::after { background: var(--indigo); }
.chat__cap { font-size: 12px; color: var(--ink-3); max-width: 34ch; }

@media (prefers-reduced-motion: reduce) {
  .chat__log { scroll-behavior: auto; }
  .tool__spin { border-top-color: var(--rule-2); }
}

/* ---------- 6. The demo ---------- */

.demo { margin-top: clamp(20px, 3vh, 34px); }
.demo__stage { position: relative; }

.demo__panels {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(18px, 3.4vw, 46px);
  align-items: start;
}
@media (max-width: 860px) {
  .demo__panels { grid-template-columns: 1fr; }
}

.panel { position: relative; transition: opacity .5s ease; }
.panel__label {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .11em;
  text-transform: uppercase; color: var(--ink-3);
  padding-bottom: 8px; margin-bottom: 11px;
  border-bottom: 1px solid var(--rule);
}

/* -- shopper's view -- */

.pdp {
  background: var(--paper-2);
  border: 1px solid var(--rule);
  padding: 0 0 16px;
  box-shadow: var(--shadow);
}
.pdp__media { line-height: 0; border-bottom: 1px solid var(--rule); }
/* Capped against viewport height and cropped rather than letterboxed, so the
   photograph never pushes the machine record below the fold. */
.weave {
  display: block; width: 100%;
  height: clamp(130px, 15vh, 158px);
}
.weave__ground { fill: var(--linen-2); }
.weave__cell   { fill: var(--linen-1); }
.weave__well   { fill: var(--linen-3); }
.weave__thread { stroke: var(--linen-3); stroke-width: .35; opacity: .5; fill: none; }
.weave__lit    { stop-color: #fff; stop-opacity: .22; }
.weave__mid    { stop-color: #fff; stop-opacity: 0; }
.weave__shade  { stop-color: #000; stop-opacity: .16; }
.weave__fold   { fill: #000; opacity: .07; }

.pdp__title {
  margin: 14px 20px 0;
  font-family: var(--serif);
  font-size: clamp(1.2rem, 1.9vw, 1.5rem);
  font-weight: 600; letter-spacing: -.015em; line-height: 1.2;
}
.pdp__copy {
  margin: 8px 20px 0;
  font-family: var(--serif); font-size: .95rem; line-height: 1.5;
  color: var(--ink-2);
}

/* evidence marks */
.ev {
  position: relative; border-radius: 2px;
  padding: 1px 2px; margin: 0 -2px;
  transition: background-color .45s ease;
}
.ev::after {
  content: ""; position: absolute; left: 2px; right: 2px; bottom: -1px;
  height: 2px; background: var(--indigo);
  transform: scaleX(0); transform-origin: left;
  transition: transform .5s cubic-bezier(.2,.7,.3,1);
}

/* the buried metafield — one line, not a card */
.buried {
  margin-top: 10px; padding: 10px 13px;
  border: 1px dashed var(--rule-2);
  font-family: var(--mono); font-size: 12px; line-height: 1.5;
  color: var(--ink-2);
  opacity: 0; transform: translateY(6px);
  transition: opacity .45s ease, transform .45s ease, border-color .45s ease;
}
.buried__key { color: var(--indigo); }
.buried__val { word-break: break-word; }
.buried__hint {
  display: block; margin-top: 6px;
  font-family: var(--sans); font-size: 11.5px; color: var(--ink-3);
}

/* -- machine's view -- */

/* The agent's reading. It WAS pinned to the machine palette, so it stayed a
   dark slab on a light page — the site talking about itself in a voice the
   reader did not pick. It uses --panel-* now and follows the theme. */
.panel--machine .fields {
  background: var(--panel-bg);
  border: 1px solid var(--panel-rule);
  padding: 6px 0;
}
.field {
  display: grid;
  grid-template-columns: 7em 1fr;
  gap: 0 14px;
  align-items: baseline;
  padding: 9px 16px;
  border-bottom: 1px solid var(--panel-rule);
  font-family: var(--mono); font-size: 13px;
}
.field:last-child { border-bottom: 0; }
.field dt { color: var(--panel-dim); letter-spacing: .02em; }
/* Both readings share one grid cell, so the row is as tall as the taller of
   the two and neither can overflow the column on a narrow screen. */
.field dd { margin: 0; color: var(--panel-fg); display: grid; min-height: 1.3em; }
.field__empty, .field__value {
  grid-area: 1 / 1;
  transition: opacity .35s ease, transform .35s ease;
}
.field__empty { color: var(--panel-dim); }
.field__value { color: var(--panel-hit); opacity: 0; transform: translateY(4px); }
.field__src {
  grid-column: 2;
  font-family: var(--sans); font-size: 11.5px; line-height: 1.4;
  color: var(--panel-dim);
  opacity: 0; max-height: 0; overflow: hidden;
  transition: opacity .35s ease, max-height .35s ease, margin-top .35s ease;
}
.field--unresolved .field__value { color: var(--panel-flag); }

/* -- threads overlay -- */
.demo__threads {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; overflow: visible; z-index: 3;
}
.demo__threads path {
  fill: none; stroke: var(--indigo);
  stroke-width: 1.25; stroke-linecap: round; opacity: .85;
}
@media (max-width: 860px) { .demo__threads { display: none; } }

/* -- scan line -- */
.demo__scan {
  position: absolute; left: 0; right: 0; top: 0; height: 2px;
  background: linear-gradient(90deg, transparent, var(--indigo), transparent);
  opacity: 0; pointer-events: none; z-index: 4;
}

/* -- controls -- */
.demo__controls {
  margin-top: 18px;
  display: flex; align-items: flex-start; gap: 16px 22px; flex-wrap: wrap;
  justify-content: center;
}
.steps {
  display: flex; gap: 10px; flex-wrap: wrap; justify-content: center;
}
.steps button {
  font: inherit; text-align: center; cursor: pointer;
  background: transparent; border: 0; border-top: 2px solid var(--rule);
  padding: 11px 14px 0;
  color: var(--ink-3); font-size: 14px; line-height: 1.35;
  display: block; max-width: 22ch;
  transition: color .2s, border-color .2s;
}
.steps button b {
  display: block; font-family: var(--mono); font-size: 11.5px;
  letter-spacing: .1em; font-weight: 600; margin-bottom: 3px;
}
.steps button:hover { color: var(--ink); border-top-color: var(--rule-2); }
.steps button[aria-current="step"] { color: var(--ink); border-top-color: var(--indigo); }

/* The four steps are two halves: what the catalog does on its own, and what
   changes once we read it. Labelling the pair is the whole point — without it
   a viewer cannot tell which steps are the product. */
.stepset { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.stepset__k {
  font-family: var(--mono); font-size: 10px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-3);
}
.stepset--dt .stepset__k {
  color: var(--indigo);
  display: flex; align-items: center; gap: 7px;
}
.stepset--dt .stepset__k::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--indigo); flex: none;
}
/* a hairline divider between the two halves, hidden once they stack */
.stepset--dt { position: relative; }
@media (min-width: 760px) {
  .stepset--dt::before {
    content: ""; position: absolute; left: -17px; top: 6px; bottom: 2px;
    width: 1px; background: var(--rule);
  }
}

.demo__cap { margin-top: 14px; text-align: center; }
.demo__cap-text {
  font-size: 14px; line-height: 1.55; color: var(--ink-3);
  max-width: 66ch; margin-inline: auto;
}

/* -- step states -- */

.demo[data-step="1"] .panel--machine { opacity: .3; }
.demo[data-step="1"] .demo__scan {
  opacity: 1;
  animation: scan 2.2s cubic-bezier(.4,0,.5,1) forwards;
}
@keyframes scan {
  from { transform: translateY(0); opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  to   { transform: translateY(var(--scan-h, 420px)); opacity: 0; }
}

.demo[data-step="2"] .panel--human { opacity: .42; }
.demo[data-step="2"] .field { animation: blip .5s ease both; }
.demo[data-step="2"] .field:nth-child(2) { animation-delay: .08s; }
.demo[data-step="2"] .field:nth-child(3) { animation-delay: .16s; }
.demo[data-step="2"] .field:nth-child(4) { animation-delay: .24s; }
.demo[data-step="2"] .field:nth-child(5) { animation-delay: .32s; }
@keyframes blip {
  0%   { background: transparent; }
  35%  { background: var(--panel-flag-wash); }
  100% { background: transparent; }
}

.demo[data-step="3"] .ev::after,
.demo[data-step="4"] .ev::after { transform: scaleX(1); }
.demo[data-step="3"] .ev,
.demo[data-step="4"] .ev { background: var(--indigo-wash); }

.demo[data-step="3"] .buried,
.demo[data-step="4"] .buried { opacity: 1; transform: none; border-color: var(--indigo); }

.demo[data-step="3"] .field:not(.field--unresolved) .field__empty,
.demo[data-step="4"] .field:not(.field--unresolved) .field__empty { opacity: 0; }
.demo[data-step="3"] .field:not(.field--unresolved) .field__value,
.demo[data-step="4"] .field:not(.field--unresolved) .field__value {
  opacity: 1; transform: none;
}
.demo[data-step="3"] .field:not(.field--unresolved) .field__src,
.demo[data-step="4"] .field .field__src { opacity: 1; max-height: 4em; margin-top: 5px; }

/* The unresolved row swaps at step 4 too, to "none found".
   It used to keep showing the blank marker, which was fine while that marker
   was a dash but reads wrong now the words are explicit: step 4 is called "And
   the gaps, named", and a row still saying "empty" names nothing. "none found"
   says we looked, which is the claim the sentence under it then qualifies. */
.demo[data-step="4"] .field--unresolved .field__empty { opacity: 0; }
.demo[data-step="4"] .field--unresolved .field__value { opacity: 1; transform: none; }

.demo[data-step="3"] .field:nth-child(1) .field__value,
.demo[data-step="4"] .field:nth-child(1) .field__value { transition-delay: .05s; }
.demo[data-step="3"] .field:nth-child(2) .field__value,
.demo[data-step="4"] .field:nth-child(2) .field__value { transition-delay: .25s; }
.demo[data-step="3"] .field:nth-child(3) .field__value,
.demo[data-step="4"] .field:nth-child(3) .field__value { transition-delay: .45s; }
.demo[data-step="3"] .field:nth-child(4) .field__value,
.demo[data-step="4"] .field:nth-child(4) .field__value { transition-delay: .65s; }

.demo[data-step="4"] .field--unresolved {
  background: var(--panel-flag-wash);
  box-shadow: inset 2px 0 0 var(--panel-flag);
}
.demo[data-step="4"] .field--unresolved dt,
.demo[data-step="4"] .field--unresolved .field__src { color: var(--panel-flag); }

/* ---------- 7. The gap ----------
   The ratio is the argument, so the bar carries it and the words stay out of
   the way. Warp threads across the whole width; only the first 6.7% are dyed. */


/* ---------- 7b. What changes ----------
   Outcomes first, mechanism second. The shopper's words stay in serif even
   inside the machine panel: the point is that a human sentence has to
   survive as machine constraints. */

.outs {
  margin-top: 26px;
  display: grid; gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr));
}
.outs li { background: var(--paper-2); padding: 18px 20px 20px; }
.outs__n {
  font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  color: var(--indigo); letter-spacing: .14em; margin-bottom: 8px;
}
.outs h3 {
  font-size: 1.05rem; font-weight: 650; letter-spacing: -.022em;
  margin-bottom: 8px;
}
.outs li p:not(.outs__n) { font-size: .9rem; color: var(--ink-2); }

/* -- the two animated scenes -- */

.scenes {
  margin-top: 22px;
  display: grid; gap: 24px;
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 1040px) {
  .scenes { grid-template-columns: 1fr 1fr; gap: 26px; }
}
.scene { display: flex; flex-direction: column; margin: 0; }
.scene__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 8px 14px; flex-wrap: wrap; margin-bottom: 10px;
}
.scene__h { font-size: 1.06rem; font-weight: 650; letter-spacing: -.022em; }
.scene__tabs { display: flex; gap: 5px; }
.scene__tabs button {
  font: inherit; font-family: var(--mono); font-size: 10.5px;
  letter-spacing: .07em; text-transform: uppercase;
  padding: 5px 11px; border-radius: 999px;
  border: 1px solid var(--rule-2); background: transparent;
  color: var(--ink-3); cursor: pointer;
  transition: color .2s, border-color .2s, background-color .2s;
}
.scene__tabs button:hover { color: var(--ink); border-color: var(--ink-3); }
.scene__tabs button[aria-current="true"] {
  background: var(--ink); color: var(--paper); border-color: var(--ink);
}
.scene__cap { margin-top: 9px; font-size: 12px; color: var(--ink-3); }

.ask, .ads {
  background: var(--mach-bg);
  border: 1px solid var(--mach-rule);
  padding: 16px 17px 18px;
  flex: 1;
}
.ask__label {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--mach-dim);
}
.ask__label--sub {
  margin-top: 16px; padding-top: 12px;
  border-top: 1px solid var(--mach-rule);
}
.ask__q {
  margin-top: 9px;
  font-family: var(--serif);
  font-size: clamp(1rem, 1.5vw, 1.16rem);
  line-height: 1.32; color: #F4F2EC;
}
.ask__checks { margin-top: 10px; display: grid; gap: 1px; }
.ask__checks li {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 14px; padding: 7px 0;
  border-bottom: 1px solid var(--mach-rule);
  font-family: var(--mono); font-size: 12.5px;
}
.ask__checks li:last-child { border-bottom: 0; }
.ask__c { color: var(--mach-fg); }

/* Each verdict occupies one grid cell so the row never reflows as it
   resolves; only opacity changes, staggered by row via --r. */
.ask__r { display: grid; text-align: right; }
.ask__r > span {
  grid-area: 1 / 1; white-space: nowrap; font-weight: 600;
  opacity: 0; transition: opacity .35s ease;
  transition-delay: calc(var(--r, 0) * 170ms);
}
.ask__pend { color: var(--mach-dim); font-weight: 400; font-style: italic; }
.ask__no   { color: var(--mach-flag); }
.ask__yes  { color: var(--mach-hit); }

.scene[data-state="ask"]    .ask__pend { opacity: 1; }
.scene[data-state="before"] [data-check="attr"]  .ask__no  { opacity: 1; }
.scene[data-state="before"] [data-check="known"] .ask__yes { opacity: 1; }
.scene[data-state="after"]  .ask__yes { opacity: 1; }

.ask__out {
  margin-top: 12px; padding-top: 11px;
  border-top: 1px solid var(--mach-rule);
  display: grid;
}
.ask__out-line {
  grid-area: 1 / 1; opacity: 0;
  transition: opacity .4s ease .55s;
  font-size: .9rem; line-height: 1.5;
}
.ask__out-line b { display: block; font-weight: 650; }
.ask__out-before { color: var(--mach-flag); }
.ask__out-after  { color: var(--mach-hit); }
.scene[data-state="before"] .ask__out-before { opacity: 1; }
.scene[data-state="after"]  .ask__out-after  { opacity: 1; }

/* -- the feed, dyed into product sets -- */

.ads__grid {
  margin-top: 10px;
  display: grid; grid-template-columns: repeat(12, 1fr); gap: 4px;
}
@media (min-width: 700px) {
  /* 18 across, 2 deep: same 36 tiles, roughly a third the height */
  .ads__grid { grid-template-columns: repeat(18, 1fr); gap: 3px; }
}
.ads__grid li {
  aspect-ratio: 1; border-radius: 2px;
  background: var(--mach-rule);
  transition: background-color .5s ease;
  transition-delay: calc(var(--i, 0) * 16ms);
}
.scene[data-state="after"] .ads__grid li[data-dye="linen"]  { background: var(--dye-linen); }
.scene[data-state="after"] .ads__grid li[data-dye="cotton"] { background: var(--dye-cotton); }
.scene[data-state="after"] .ads__grid li[data-dye="wool"]   { background: var(--dye-wool); }
.scene[data-state="after"] .ads__grid li[data-dye="silk"]   { background: var(--dye-silk); }

.ads__out {
  margin-top: 12px; padding-top: 11px;
  border-top: 1px solid var(--mach-rule);
  display: grid; align-items: start;
}
.ads__before, .ads__after {
  grid-area: 1 / 1; opacity: 0; transition: opacity .4s ease .45s;
}
.scene[data-state="before"] .ads__before { opacity: 1; }
.scene[data-state="after"]  .ads__after  { opacity: 1; }
.ads__before {
  color: var(--mach-flag); font-size: .9rem; line-height: 1.5;
}
.ads__before b { display: block; font-weight: 650; }
.ads__after { display: flex; flex-wrap: wrap; gap: 7px 8px; }
.ads__after li {
  display: flex; align-items: center; gap: 7px;
  font-family: var(--mono); font-size: 11px; color: var(--mach-fg);
  padding: 5px 10px; border-radius: 999px;
  border: 1px solid var(--mach-rule);
}
.ads__after li::before {
  content: ""; width: 8px; height: 8px; border-radius: 2px; flex: none;
}
.ads__after li[data-dye="linen"]::before  { background: var(--dye-linen); }
.ads__after li[data-dye="cotton"]::before { background: var(--dye-cotton); }
.ads__after li[data-dye="wool"]::before   { background: var(--dye-wool); }
.ads__after li[data-dye="silk"]::before   { background: var(--dye-silk); }
.ads__after b { color: #F4F2EC; font-weight: 650; }

.pull {
  margin-top: 22px; max-width: 64ch;
  font-size: clamp(1.02rem, 1.5vw, 1.18rem); line-height: 1.5;
  color: var(--ink-2);
  padding-left: 20px; border-left: 3px solid var(--indigo);
}
.pull strong { color: var(--ink); font-weight: 650; }

/* ---------- 7c. Where it goes ----------
   Five steps, one line each. Each step spreads across the measure as
   number / name / what it does, so the block has the same width as the
   heading above it instead of hanging under it as a narrow column.
   Step one carries the accent because it is the only entry point. */


/* once there is room, the step becomes a single line across the measure */
@media (min-width: 680px) {
}


/* ---------- 8. How it works ---------- */

.how {
  margin-top: 44px;
  display: grid; gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
}
.how li { background: var(--paper); padding: 26px 24px 30px; }
.how__n {
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  color: var(--indigo); letter-spacing: .14em; margin-bottom: 14px;
}
.how h3 {
  font-size: 1.16rem; font-weight: 650; letter-spacing: -.02em;
  margin-bottom: 8px;
}
.how li p:not(.how__n) { font-size: .93rem; color: var(--ink-2); }

.how__foot {
  margin-top: 34px; max-width: 62ch;
  font-size: 1rem; line-height: 1.55; color: var(--ink-2);
  padding-left: 18px; border-left: 2px solid var(--indigo);
}

/* ---------- 8c. The three layers ---------- */

.layers { margin-top: 38px; max-width: 880px; }
.layer {
  display: grid; gap: 4px 28px;
  grid-template-columns: minmax(0, 1fr);
  padding: 20px 22px;
  border: 1px solid var(--rule);
  background: var(--paper);
}
@media (min-width: 720px) {
  .layer { grid-template-columns: 200px 1fr; align-items: start; }
}
.layer__k { font-weight: 650; letter-spacing: -.015em; }
.layer__k span {
  display: block; font-family: var(--mono); font-weight: 400;
  font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-3); margin-top: 3px;
}
.layer__v { color: var(--ink-2); font-size: .95rem; }
.layer--us {
  background: var(--ink); color: var(--paper); border-color: var(--ink);
}
.layer--us .layer__k span { color: color-mix(in srgb, var(--paper) 58%, transparent); }
.layer--us .layer__v { color: color-mix(in srgb, var(--paper) 86%, transparent); }
.layer--us .layer__v strong { color: var(--paper); }

.layer__arrow {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-3);
  padding: 8px 0 8px 24px; position: relative;
}
.layer__arrow::before {
  content: ""; position: absolute; left: 10px; top: 0; bottom: 0;
  width: 1px; background: var(--rule-2);
}

.layers__foot {
  margin-top: 30px; max-width: 62ch;
  font-size: 1rem; color: var(--ink-2);
  padding-left: 18px; border-left: 2px solid var(--indigo);
}

/* ---------- 8d. What you keep ---------- */

.keep { margin-top: 34px; max-width: 760px; }
.keep__scroll {
  overflow-x: auto; border: 1px solid var(--rule);
  -webkit-overflow-scrolling: touch;
}
.keep table {
  border-collapse: collapse; width: 100%; min-width: 480px;
  font-size: 13.5px; font-variant-numeric: tabular-nums;
}
.keep th, .keep td { padding: 12px 15px; text-align: right; }
.keep thead th {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-3); font-weight: 600;
  border-bottom: 1px solid var(--rule-2); white-space: nowrap;
}
.keep tbody th { text-align: left; font-weight: 600; white-space: nowrap; }
.keep tbody tr { border-bottom: 1px solid var(--rule); }
.keep tbody tr:last-child { border-bottom: 0; }
.keep .is-marked { background: var(--flag-wash); }
.keep .neg { color: var(--flag); font-weight: 650; }
.keep__cap { margin-top: 18px; }
.keep__read {
  font-size: 1rem; color: var(--ink-2);
  padding-left: 16px; border-left: 2px solid var(--indigo);
}
.keep .fineprint { margin-top: 14px; }

/* ---------- 9. Guardrails ---------- */

/* A band that reads as a separate surface, not a separate theme.
   It used the machine palette, so it stayed a black slab on a light page —
   and the site's four promises about a brand's own data were the one place
   that ignored the reader's choice. --panel-* follows the theme. */
.sec--dark {
  background: var(--panel-bg);
  color: var(--panel-fg);
  border-block: 1px solid var(--panel-rule);
}
.sec--dark .sec__h2 { color: var(--panel-head); }
/* The dark band never flips with the theme, so anything inside it must use the
   theme-invariant --mach-* palette. .sec__lede defaults to --ink-2, which in
   LIGHT theme is #3D3A33 on a #14161B ground: about 1.3:1, unreadable. It
   looked fine in dark, which is how it shipped. */
.sec--dark .sec__lede { color: var(--panel-fg); }

/* ---------- 10. Pricing ---------- */

.price {
  margin-top: 44px;
  display: grid; gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
}
.price li {
  background: var(--paper);
  padding: 28px 24px 30px;
  display: flex; flex-direction: column;
}
.price h3 {
  font-size: 1.2rem; font-weight: 650; letter-spacing: -.022em;
  margin-bottom: 8px;
}
.price li p:not(.price__n) { font-size: .93rem; color: var(--ink-2); }
.price__n {
  margin-top: auto; padding-top: 22px;
  font-family: var(--mono); font-size: 1.02rem; font-weight: 600;
  letter-spacing: -.01em; color: var(--ink);
}
/* The one-time implementation fee sits on its own line rather than trailing
   the monthly price, where it wrapped mid-phrase and read as part of it. */
.price__n em {
  display: block; margin-top: 4px;
  font-style: normal; font-weight: 400; color: var(--ink-3);
}

/* "Free" is processed as a category, not as a small number, so it gets to be
   the loudest price on the row. */
/* The free tier is where the page asks, now that the closing section is gone:
   it sits beside the thing it is free relative to, which is a better place to
   ask than the foot of a long scroll. */
.price__cta { margin-top: 14px; }
.price__cta .btn { width: 100%; }
.price__cta .btn[disabled] { opacity: .6; cursor: progress; }

/* ---------- the subscribe form ----------
   One component, two homes: under the pricing bands, and in the report's
   right-hand column. It asks for the store, an email and the terms, and only
   then opens Stripe. */
.sub {
  display: grid; gap: 8px;
  max-width: 460px;
  border: 1px solid var(--rule-2); border-radius: 8px;
  padding: 22px; background: var(--paper-2);
}
#subscribe-form:not(:empty) { margin-top: 26px; }
.sub__band {
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-3);
}
.sub__price {
  font-size: 1.3rem; font-weight: 680; letter-spacing: -.03em; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.sub__includes { margin: 4px 0 10px; display: grid; gap: 4px; }
.sub__includes li {
  font-size: .9rem; color: var(--ink-2);
  padding-left: 18px; position: relative;
}
.sub__includes li::before {
  content: ""; position: absolute; left: 0; top: .55em;
  width: 10px; height: 2px; background: var(--indigo);
}
.sub label { font-size: .92rem; font-weight: 600; color: var(--ink); }
.sub input[type="text"], .sub input[type="email"] {
  font: inherit; font-size: 1rem;
  width: 100%; min-height: 46px; padding: 10px 13px;
  color: var(--ink); background: var(--paper);
  border: 1px solid var(--rule-2); border-radius: 3px;
}
.sub input:focus-visible { border-color: var(--indigo); }
.sub input[readonly] { color: var(--ink-2); background: var(--paper-2); }
.sub__hint { font-size: 12.5px; color: var(--ink-3); margin-top: -2px; }
.sub__terms {
  display: flex; align-items: flex-start; gap: 10px;
  font-weight: 400; color: var(--ink-2); line-height: 1.45;
  font-size: .9rem; margin: 6px 0 2px;
}
.sub__terms input { flex: none; width: 18px; height: 18px; margin: 1px 0 0; accent-color: var(--indigo); }
.sub__terms a { color: var(--indigo); }
.sub .btn { width: 100%; margin-top: 4px; }
.sub .btn[disabled] { opacity: .6; cursor: progress; }
.sub__status { font-size: .92rem; color: var(--flag); font-weight: 600; }
.sub__status:empty { display: none; }
.sub__fine { font-size: 12.5px; color: var(--ink-3); }

.price__n--free {
  font-size: 1.6rem; font-weight: 680; letter-spacing: -.03em;
  color: var(--indigo);
}
.price__n span {
  font-weight: 400; font-size: .76em; color: var(--ink-3);
  letter-spacing: .04em;
}

/* ---------- 10b. Risk reversal ---------- */

.guarantee {
  margin-top: 26px;
  font-size: clamp(1.18rem, 2.1vw, 1.5rem);
  font-weight: 650; letter-spacing: -.025em; line-height: 1.3;
  color: var(--ink);
  padding: 18px 22px;
  background: var(--indigo-wash);
  border-left: 3px solid var(--indigo);
}

/* ---------- 11. Close ---------- */


/* ---------- 12. Footer ---------- */

.site-foot {
  border-top: 1px solid var(--rule);
  padding-block: 32px 40px;
  background: var(--paper-2);
}
.site-foot__in {
  display: flex; gap: 14px 28px; flex-wrap: wrap;
  align-items: baseline; justify-content: space-between;
}
.site-foot__brand { font-weight: 650; letter-spacing: -.03em; font-size: 1.05rem; }
.site-foot__meta {
  font-size: 13px; color: var(--ink-3);
  display: flex; gap: 10px; flex-wrap: wrap; align-items: center;
}
.site-foot__meta a { color: var(--ink-2); }

/* ---------- 13. Behavioural pieces ----------
   Each of these exists for one reason, noted inline. Nothing here invents
   scarcity, urgency or proof: the levers are all things that are true. */

/* Self-relevance: the statistic above is about a hypothetical store until
   this line makes it about theirs, and ties it to the free offer. */

/* Offer the action at peak motivation, not only in the footer. */
.midcta {
  margin-top: 30px; padding-top: 24px;
  border-top: 1px solid var(--rule);
  display: flex; align-items: center; gap: 16px 22px; flex-wrap: wrap;
}
.midcta__why {
  font-size: 1rem; color: var(--ink-2); max-width: 46ch;
}

/* The cost of not doing step one. */

/* Anticipated effort is what actually stalls a decision, so name it. */
.how__effort {
  margin-top: 30px; max-width: 62ch;
  font-size: clamp(1.02rem, 1.6vw, 1.2rem);
  font-weight: 650; letter-spacing: -.02em; color: var(--ink);
  padding-left: 18px; border-left: 3px solid var(--indigo);
}

/* Put the monthly number in a category where it is already small. */
.anchor {
  margin-top: 22px; max-width: 64ch;
  font-size: .97rem; line-height: 1.55; color: var(--ink-2);
}

/* Discharge risk before money: four lines, dark band, ahead of pricing. */
/* The four cards inside the band. Every colour here was a hard-coded light
   value or a machine token, so in light theme the band's own cards stayed
   black with near-white text. They follow --panel-* with the band. */
.nevers {
  margin-top: 30px;
  display: grid; gap: 1px;
  background: var(--panel-rule); border: 1px solid var(--panel-rule);
  grid-template-columns: repeat(auto-fit, minmax(min(215px, 100%), 1fr));
}
.nevers li {
  background: var(--panel-bg); padding: 18px 18px 20px;
  display: flex; flex-direction: column; gap: 5px;
}
.nevers b {
  font-size: 1rem; font-weight: 650; letter-spacing: -.018em; color: var(--panel-head);
  padding-left: 22px; position: relative;
}
.nevers b::before {
  content: ""; position: absolute; left: 0; top: .52em;
  width: 13px; height: 2px; background: var(--panel-hit);
}
/* .72 opacity on a light ground washes out to roughly 3:1; the dim token is
   already the right weight and keeps its contrast in both themes. */
.nevers span { font-size: .89rem; color: var(--panel-dim); }
.nevers__foot {
  margin-top: 22px; font-size: .97rem; color: var(--panel-head);
  padding-left: 18px; border-left: 2px solid var(--panel-hit);
}


/* ---------- 13a2. The ask: the form as the next turn ----------
   Borrows the hero's chat shell (.chat__win, .chat__bar, .msg, .msg--ai,
   .msg--tool) rather than building a parallel one, because the whole point is
   that this IS the same conversation, now about the customer's store. Only what differs
   lives here. */

/* NOT `.ask` — that class already belongs to the shopper-question scene in
   "When a shopper asks an AI", which paints a dark machine panel. This block
   was originally named `.ask` too, and because it sits LATER in the file it
   overrode that scene's `background: var(--mach-bg)` with `--paper-2` while
   leaving its text at `--mach-fg`: pale grey on near-white, unreadable, in
   both themes. A cascade collision between two blocks that never knew about
   each other. */
.askform {
  padding-block: clamp(44px, 6vw, 76px);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  background: var(--paper-2);
}
.askform__in {
  display: grid; gap: 32px;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}
@media (min-width: 980px) {
  .askform__in { grid-template-columns: 1fr 1fr; gap: 56px; align-items: center; }
  /* The fine print belongs under the window it describes, not under the
     heading, so it is pushed into the second column at this width. */
  .askform__fine { grid-column: 2; }
}
/* Both grid children need min-width:0 or a long placeholder in the input sets
   the column's floor and the page scrolls sideways at 360px. */
.askform__head, .askform__chat { min-width: 0; }
.askform__chat { margin: 0; }
.askform__eyebrow {
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase; color: var(--indigo);
  margin-bottom: 14px;
}
.askform__h {
  font-size: clamp(1.75rem, 4vw, 2.6rem);
  font-weight: 690; letter-spacing: -.04em; line-height: 1.05;
  text-wrap: balance;
}
.askform__lede {
  margin-top: 16px; max-width: 46ch;
  font-size: clamp(1rem, 1.5vw, 1.12rem);
  line-height: 1.5; color: var(--ink-2);
}
.askform__free {
  margin-top: 16px; font-size: .97rem; font-weight: 600; color: var(--indigo);
}
.askform__fine { font-size: 13px; color: var(--ink-3); }
.askform__fine a { color: var(--ink-2); }

/* The hero's log is a fixed-height scroller driven by thread.js. This one is
   a form: it grows with its content and must never clip an input or hide an
   error message. */
.chat__log--ask {
  height: auto;
  overflow: visible;
  padding: 16px 16px 18px;
}

/* The assistant's mark in THIS section is a circle, not the hero's rounded
   square. At 19px a dark square with a lighter square inset reads as a
   checkbox, and beside the line "Which store should I look for?" that is
   exactly what it looked like: a box to tick before a form. The hero gets away
   with it because five messages and a chat window establish the metaphor
   first; here there is one line. */
.askform .msg__ai { border-radius: 50%; }
.askform .msg__ai::after {
  inset: auto; left: 50%; top: 50%;
  width: 6px; height: 6px; margin: -3px 0 0 -3px; border-radius: 50%;
}

/* The customer's turn. .msg--me paints every paragraph inside it as a dark bubble, which
   would swallow the labels and the button, so this aligns like a reply without
   inheriting that. */
.msg--compose {
  align-self: stretch;
  max-width: 100%;
  padding-left: 28px;          /* clears the assistant's avatar gutter */
}
.msg--compose .scan-form { max-width: none; margin: 0; }

/* What the customer sells, in the customer's own words, as chips under the assistant's line. */
.types { margin-top: 8px; display: flex; flex-wrap: wrap; gap: 6px; }
.types li {
  font-family: var(--mono); font-size: 11px; color: var(--ink-2);
  padding: 4px 9px; border-radius: 999px;
  border: 1px solid var(--rule-2); background: var(--paper);
}

/* The other phrases we could have suggested. A button, not a link: it changes
   a field on this page rather than going anywhere. */
.scan-form__swap {
  font-size: 12.5px; color: var(--ink-3); margin-top: -2px;
  display: grid; gap: 3px;
}
/* Where the prefill came from, said before the alternatives are offered. */
.scan-form__source { color: var(--ink-2); }
.scan-form__swaps { display: block; }
.scan-form__swap:empty { display: none; }
.linkish {
  font: inherit; color: var(--indigo); background: none; border: 0;
  padding: 0; cursor: pointer; text-decoration: underline;
}
.linkish:hover { color: var(--ink); }

/* The in-flight line. The hero reveals .tool__run by beat; here it is shown
   and hidden by scan.js toggling `hidden` on the message, so the opacity gate
   that belongs to the animated transcript has to be lifted. */
.tool__run--live { opacity: 1; }
.askform__live[hidden] { display: none; }

/* The reply stays in the DOM so its live region announces. Collapse the bubble
   — avatar included — while there is nothing to say. */
.askform__reply:has(.scan-form__status:empty) { display: none; }
.askform__reply .scan-form__status { font-size: .9rem; }
.askform__reply .scan-form__status--ok {
  background: none; border-left: 0; padding: 0; color: var(--ink);
}

/* ---------- 13a4. The report's two columns ----------
   Findings on the left, the decision beside them. The offer used to sit below
   a "save this report" form, so acting on a number meant scrolling away from
   it and giving an email twice. */

.report__split { display: grid; gap: 32px; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 900px) {
  .report__split { grid-template-columns: minmax(0, 1fr) 320px; gap: 40px; align-items: start; }
  /* Sticky so the bands stay beside whichever finding the customer is reading. */
  .report__aside { position: sticky; top: 84px; }
}
.report__findings { min-width: 0; }
.types--report { margin: 10px 0 14px; }

/* Run another term from the report itself. "Your terms, re-run" is the
   product, so a second one should not mean going back and retyping a domain
   we already resolved. */
.again { margin: 14px 0 18px; }
.again__form { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.again__label {
  flex: 1 0 100%; font-size: .9rem; font-weight: 600; color: var(--ink);
}
.again__form input {
  font: inherit; font-size: .95rem; flex: 1 1 220px; min-width: 0;
  min-height: 40px; padding: 8px 12px;
  color: var(--ink); background: var(--paper-2);
  border: 1px solid var(--rule-2); border-radius: 3px;
}
.again__form input:focus-visible { border-color: var(--indigo); }
.again__form .btn { flex: none; }
.again__form .btn[disabled] { opacity: .6; cursor: progress; }
.again__status { margin-top: 8px; font-size: .9rem; color: var(--ink-2); }
.again__status:empty { display: none; }
/* The one sentence that names the customer's gap. Sized above body copy because it is
   the finding, not context for one. */
/* The first paragraph of a report section.
   ----
   This was 600 weight at up to 1.2rem, from when it was one short line under
   the headline. It is now the body of six sections — "your products carry no
   options at all", "86 of your 102 products carry no rating", "your category
   defines 7 fields" — and bolding a whole paragraph emphasises nothing while
   making the report read as shouted. Normal weight, and the size steps down to
   sit with .report__ctx, which it is now a sibling of rather than a lead. */
.report__lead {
  margin: 10px 0 12px;
  font-size: clamp(1rem, 1.4vw, 1.1rem); line-height: 1.55;
  color: var(--ink); letter-spacing: -.005em;
  max-width: 60ch;
}
.report__aside {
  min-width: 0;
  border: 1px solid var(--rule-2); border-radius: 8px;
  padding: 20px; background: var(--paper-2);
}
.report__aside-h { font-size: 1.1rem; font-weight: 660; letter-spacing: -.02em; }
.report__aside-p { margin-top: 8px; font-size: .9rem; color: var(--ink-2); line-height: 1.5; }
.report__bands { margin-top: 16px; display: grid; gap: 1px; background: var(--rule); border: 1px solid var(--rule); }
.band {
  background: var(--paper); padding: 12px 14px;
  display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; align-items: center;
}
.band__name { font-weight: 650; font-size: .97rem; }
.band__price {
  grid-column: 2; grid-row: 1; text-align: right;
  font-family: var(--mono); font-size: .92rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.band__note { grid-column: 1; font-size: 12.5px; color: var(--ink-3); }
.band .btn { grid-column: 2; grid-row: 2; }
.btn--small { padding: 6px 12px; font-size: 13px; }
.report__sub:not(:empty) { margin-top: 16px; }
.report__sub .sub { max-width: none; padding: 16px; }

@media (min-width: 720px) {
  }

/* ---------- 13a3. The handoff after checkout ----------
   Deliberately quiet. The customer has just paid, so nothing here sells anything; the
   page's whole job is to be honest about a wait and then get out of the way. */

.welcome { padding-block: clamp(56px, 8vw, 104px); }
.welcome__in { max-width: 560px; }
.welcome__eyebrow {
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase; color: var(--indigo);
  margin-bottom: 14px;
}
.welcome__h {
  font-size: clamp(1.8rem, 4.5vw, 2.7rem);
  font-weight: 690; letter-spacing: -.04em; line-height: 1.05;
  text-wrap: balance; margin-bottom: 28px;
}

.welcome__steps { display: grid; gap: 12px; margin-bottom: 24px; }
.step {
  display: flex; align-items: center; gap: 12px;
  font-size: 1rem; color: var(--ink-3);
}
/* An unmarked step is plainly not done: an empty ring, no motion. Nothing
   here animates until the server says the work actually started. */
.step__dot {
  width: 14px; height: 14px; border-radius: 50%; flex: none;
  border: 2px solid var(--rule-2); background: transparent;
  position: relative;
}
.step.is-active { color: var(--ink); }
.step.is-active .step__dot {
  border-color: var(--indigo);
  border-top-color: transparent;
  animation: spin .8s linear infinite;
}
.step.is-done { color: var(--ink); }
.step.is-done .step__dot { border-color: var(--indigo); background: var(--indigo); }
.step.is-done .step__dot::after {
  content: ""; position: absolute; left: 3px; top: 0;
  width: 3px; height: 7px;
  border: solid var(--indigo-ink); border-width: 0 2px 2px 0;
  transform: rotate(42deg);
}
/* A spinning ring is motion for its own sake to anyone who has asked for less
   of it; the colour change alone still distinguishes the active step. */
@media (prefers-reduced-motion: reduce) {
  .step.is-active .step__dot { animation: none; border-top-color: var(--indigo); }
}

.welcome__status { font-size: 1rem; color: var(--ink-2); line-height: 1.5; }
.welcome__go { margin-top: 28px; }
.welcome__mail {
  margin-top: 32px; padding-top: 22px; border-top: 1px solid var(--rule);
  font-size: .97rem; color: var(--ink-2); line-height: 1.55;
}
.welcome__help { margin-top: 16px; font-size: 13px; color: var(--ink-3); }
.welcome__help a, .welcome__mail a { color: var(--ink-2); }

/* `.gets` is an auto-fit grid, which is right for the seven-item "What you
   get" list but leaves a visible empty cell when THREE cards land in two
   columns — the grid's rule colour shows through as a grey block. This list is
   always one or three, never two. */
#discovery .gets { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 860px) {
  #discovery .gets { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* A line break that is right at reading width and wrong at phone width, where
   it left "you." alone on its own line. Below the breakpoint the heading wraps
   on its own. */
.wide-only { display: none; }
@media (min-width: 700px) { .wide-only { display: inline; } }

/* The sentence that names the loop, set apart from the three cards it sums up. */
.discovery__loop {
  margin-top: 26px; padding-left: 18px;
  border-left: 2px solid var(--indigo);
  font-size: clamp(1rem, 1.5vw, 1.1rem); line-height: 1.55; color: var(--ink-2);
  max-width: 62ch;
}
.discovery__loop b { color: var(--ink); font-weight: 650; }

/* The discovery loop, drawn as a ring because the point is that it returns. */
.loop { margin: 34px auto 0; max-width: 300px; }
.loop__svg { width: 100%; height: auto; display: block; overflow: visible; }
.loop__ring {
  fill: none; stroke: var(--rule-2); stroke-width: 1.5;
  stroke-dasharray: 3 7; stroke-linecap: round;
}
.loop__node { fill: var(--paper); stroke: var(--indigo); stroke-width: 2; }
.loop__dot { fill: var(--indigo); }
.loop__t {
  font-family: var(--mono); font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; fill: var(--ink-3);
}
.loop__cap {
  margin-top: 12px; text-align: center;
  font-size: .92rem; color: var(--ink-2);
}
/* A travelling dot is motion for its own sake to anyone who asked for less of
   it. The ring and its four labels carry the meaning without it. */
@media (prefers-reduced-motion: reduce) {
  .loop__dot { display: none; }
}
@media (min-width: 900px) {
  /* Beside the steps rather than above them, so the ring and the words it
     labels are read together. */
  .how--looped { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 44px; align-items: center; }
}

/* ---------- 13b. ICP additions ---------- */

/* The open loop, right under the CTA: it is about their store, not a stat. */
.hero__hook {
  margin-top: 20px; max-width: 46ch;
  font-size: clamp(.98rem, 1.3vw, 1.08rem);
  font-weight: 600; color: var(--indigo); letter-spacing: -.01em;
}
/* Self-selection, not proof: a reader decides in one line if this is for them. */
.hero__icp {
  margin-top: 8px; font-size: 12.5px; color: var(--ink-3);
  font-family: var(--mono); letter-spacing: .04em;
}

/* Scope. The answer to "so this is an attributes tool?" */
.gets {
  margin-top: 34px;
  display: grid; gap: 1px;
  background: var(--rule); border: 1px solid var(--rule);
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
}
.gets li { background: var(--paper); padding: 20px 20px 22px; }
.gets h3 {
  font-size: 1.02rem; font-weight: 650; letter-spacing: -.02em;
  margin-bottom: 6px; padding-left: 22px; position: relative;
}
.gets h3::before {
  content: ""; position: absolute; left: 0; top: .52em;
  width: 13px; height: 2px; background: var(--indigo);
}
.gets p { font-size: .9rem; color: var(--ink-2); }
/* the closing tile is the reassurance, so it reads as the ground not a feature */
.gets li.gets__end { background: var(--indigo-wash); }
.gets li.gets__end h3::before { background: var(--ink); }

/* Objection handling. Two-sided messaging earns trust with a sceptic. */
.qa {
  margin-top: 34px;
  display: grid; gap: 1px;
  background: var(--rule); border: 1px solid var(--rule);
  grid-template-columns: repeat(auto-fit, minmax(min(290px, 100%), 1fr));
}
.qa > div { background: var(--paper); padding: 20px 22px 22px; }
.qa dt {
  font-size: 1.02rem; font-weight: 650; letter-spacing: -.02em;
  margin-bottom: 7px;
}
.qa dd { margin: 0; font-size: .92rem; color: var(--ink-2); }

/* ---------- 13c. Legal pages ----------
   Long-form reading: one narrow column, generous leading, and headings that
   stay quiet so the text is the thing. Shares every token with the site. */

.legal { padding-block: clamp(44px, 6vw, 72px) clamp(64px, 8vw, 104px); }
.legal .wrap { max-width: 760px; }

.legal__eyebrow {
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase; color: var(--indigo);
  margin-bottom: 14px;
}
.legal__h1 {
  font-size: clamp(2rem, 4.4vw, 3rem);
  font-weight: 690; letter-spacing: -.04em; line-height: 1.03;
}
.legal__meta {
  margin-top: 14px; font-family: var(--mono); font-size: 12px;
  color: var(--ink-3); letter-spacing: .02em;
}
.legal__lede {
  margin-top: 24px; font-size: clamp(1.02rem, 1.5vw, 1.16rem);
  line-height: 1.6; color: var(--ink-2);
  padding-left: 18px; border-left: 3px solid var(--indigo);
}

.legal__toc {
  margin-top: 34px; padding: 20px 22px 22px;
  background: var(--paper-2); border: 1px solid var(--rule);
}
.legal__toc-k {
  font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-3);
  margin-bottom: 12px;
}
.legal__toc ol {
  display: grid; gap: 7px 22px; counter-reset: toc;
  grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
}
.legal__toc li { counter-increment: toc; font-size: .92rem; }
.legal__toc li::before {
  content: counter(toc) ". ";
  font-family: var(--mono); font-size: .82em; color: var(--ink-3);
}
.legal__toc a { color: var(--ink-2); text-decoration: none; border-bottom: 1px solid var(--rule-2); }
.legal__toc a:hover { color: var(--ink); border-bottom-color: var(--indigo); }

.legal__sec { margin-top: clamp(34px, 4vw, 48px); }
.legal__sec h2 {
  font-size: clamp(1.14rem, 1.8vw, 1.36rem);
  font-weight: 650; letter-spacing: -.022em; line-height: 1.25;
  padding-bottom: 10px; margin-bottom: 14px;
  border-bottom: 1px solid var(--rule);
}
.legal__sec p { font-size: 1rem; line-height: 1.65; color: var(--ink-2); }
.legal__sec p + p { margin-top: 13px; }
.legal__sec strong { color: var(--ink); font-weight: 650; }
.legal__sec a { color: var(--indigo); }

.legal__sec ul { margin-top: 13px; display: grid; gap: 9px; }
.legal__sec ul li {
  font-size: 1rem; line-height: 1.6; color: var(--ink-2);
  padding-left: 20px; position: relative;
}
.legal__sec ul li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: 10px; height: 1px; background: var(--indigo);
}
.legal__never li::before { background: var(--flag); }

.legal__table {
  margin-top: 16px; width: 100%;
  border-collapse: collapse; font-size: .92rem;
  border: 1px solid var(--rule);
}
.legal__table th, .legal__table td {
  padding: 10px 13px; text-align: left; vertical-align: top;
  border-bottom: 1px solid var(--rule);
}
.legal__table thead th {
  font-family: var(--mono); font-size: 10px; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3);
  background: var(--paper-2); white-space: nowrap;
}
.legal__table tbody th { font-weight: 650; color: var(--ink); white-space: nowrap; }
.legal__table td { color: var(--ink-2); }
.legal__table tbody tr:last-child th,
.legal__table tbody tr:last-child td { border-bottom: 0; }

@media (max-width: 620px) {
  .legal__table, .legal__table thead, .legal__table tbody,
  .legal__table tr, .legal__table th, .legal__table td { display: block; }
  .legal__table thead { display: none; }
  .legal__table tbody tr { border-bottom: 1px solid var(--rule-2); padding: 6px 0; }
  .legal__table tbody tr:last-child { border-bottom: 0; }
  .legal__table th, .legal__table td { border-bottom: 0; padding: 4px 13px; }
}

/* ---------- 13d. Small screens ----------
   Everything above is fluid, so this only handles the few places where a
   desktop affordance stops making sense on a phone rather than merely
   getting smaller. */

@media (max-width: 700px) {
  /* The typewriter is a fixed 42ch line. On a narrow bubble it would be
     clipped mid-sentence, so below this width the query simply wraps and
     appears with the message instead of typing. */
  .chat__type,
  .chat[data-anim="on"] .chat__type,
  .chat[data-anim="on"] .chat__type.is-typing {
    width: auto;
    white-space: normal;
    animation: none;
  }

  /* constraint rows stack rather than squeeze the verdict off the edge */
  .ask__checks li { flex-wrap: wrap; gap: 2px 14px; }
  .ask__r { text-align: left; }

  /* the header has only brand, one button and the toggle down here */
  .site-head__in { gap: 12px; }
}

@media (max-width: 420px) {
  /* keep long mono strings from forcing a horizontal scroll */
  .buried, .field { overflow-wrap: anywhere; }
  .field { grid-template-columns: 6em 1fr; gap: 0 10px; }
  .prod__t { overflow-wrap: anywhere; }
  .site-foot__meta { gap: 8px; }
}

/* ---------- 13e. Consent banner ----------
   Only ever rendered for EEA/UK/CH visitors, by thread.js. Accept and Decline
   are deliberately identical in size, weight and colour: a Decline that is
   quieter than Accept is a dark pattern and is not valid consent. */

.consent {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 90;
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px 24px; flex-wrap: wrap;
  padding: 16px clamp(20px, 5vw, 48px);
  background: var(--ink); color: var(--paper);
  border-top: 2px solid var(--indigo);
}
.consent__msg {
  font-size: .92rem; line-height: 1.5; max-width: 74ch;
  color: color-mix(in srgb, var(--paper) 88%, transparent);
}
.consent__msg a { color: var(--paper); text-decoration: underline; }
.consent__actions { display: flex; gap: 10px; flex: none; }
.consent__btn {
  font: inherit; font-size: .9rem; font-weight: 600;
  padding: 10px 22px; min-height: 44px;
  border-radius: 3px; cursor: pointer;
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--paper);
  transition: background-color .16s, color .16s;
}
.consent__btn:hover { background: transparent; color: var(--paper); }
.consent__btn:focus-visible { outline: 2px solid var(--indigo); outline-offset: 3px; }

@media (max-width: 620px) {
  .consent { padding: 14px 20px 16px; }
  .consent__actions { width: 100%; }
  .consent__btn { flex: 1; }
}

/* ---------- 13f. Store scan form ----------
   The close section's ask. It sits on the page ground, not the dark band, so
   the flipping tokens are right here. Status colours reuse --indigo (sent)
   and --flag (refused), both AA on --paper in either theme. */

.scan-form {
  max-width: 460px; margin: 0 auto;
  display: grid; gap: 8px;
  text-align: left;
}
.scan-form label { font-size: .92rem; font-weight: 600; color: var(--ink); }
.scan-form input[type="text"],
.scan-form input[type="email"] {
  font: inherit; font-size: 1rem;
  width: 100%; min-height: 48px; padding: 11px 14px; margin-bottom: 8px;
  color: var(--ink); background: var(--paper-2);
  border: 1px solid var(--rule-2); border-radius: 3px;
}
.scan-form input::placeholder { color: var(--ink-3); opacity: 1; }
.scan-form input:focus-visible { border-color: var(--indigo); }
/* Off-screen rather than display:none: some bots skip fields that are
   hidden, and this one exists to be filled by them. */
.scan-form__trap {
  position: absolute; left: -10000px; top: auto;
  width: 1px; height: 1px; overflow: hidden;
}
.scan-form .btn { width: 100%; }
.scan-form .btn[disabled] { opacity: .6; cursor: progress; }
.scan-form__status {
  font-size: .95rem; line-height: 1.5; color: var(--ink-2);
}
.scan-form__status:empty { display: none; }
.scan-form__status a { color: var(--indigo); }
.scan-form__status--ok {
  color: var(--ink); padding: 12px 14px;
  background: var(--indigo-wash); border-left: 3px solid var(--indigo);
}
.scan-form__status--error { color: var(--flag); font-weight: 600; }

/* ---------- 13g. The catalog report ----------
   One narrow column at every width, like the legal pages: fifty cards read
   top to bottom, and nothing may scroll sideways at 360px. The three voices
   carry over: the fact is the record (mono), the quote is the store's own
   prose (serif). */

.report { padding-block: clamp(40px, 6vw, 72px) clamp(64px, 8vw, 104px); }
.report .wrap { max-width: 760px; overflow-wrap: anywhere; }
/* The report grew a second column, and 760px split two ways left the findings
   at about 300px — narrower than the single column had been. The wrap widens
   only where the split actually applies. */
@media (min-width: 900px) {
  .report .wrap { max-width: var(--wrap); }
}

.report__eyebrow {
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase; color: var(--indigo);
  margin-bottom: 14px;
}
.report__msg {
  font-size: clamp(1.3rem, 3vw, 1.75rem);
  font-weight: 650; letter-spacing: -.03em; line-height: 1.25;
  max-width: 36ch;
}
/* one line under a heading: "nothing was charged", a cancelled checkout */
.report__note {
  margin-top: 10px;
  max-width: 48ch;
  color: var(--ink-3);
}
/* a slow thread drawn under a scan that is still running */
.report__msg--pending::after {
  content: ""; display: block; margin-top: 22px;
  width: 64px; height: 2px; background: var(--indigo);
  animation: report-pulse 1.6s ease-in-out infinite alternate;
}
@keyframes report-pulse { from { width: 24px; } to { width: 120px; } }

.report__actions { margin-top: 26px; display: flex; flex-wrap: wrap; gap: 12px; }

.report__h1 {
  font-size: clamp(1.7rem, 4.4vw, 2.7rem);
  font-weight: 690; letter-spacing: -.04em; line-height: 1.08;
}
.report__ctx {
  margin-top: 16px; max-width: 60ch;
  font-size: clamp(1rem, 1.4vw, 1.1rem); color: var(--ink-2);
}
.report__h2 {
  margin-top: clamp(36px, 5vw, 52px);
  font-size: 1.15rem; font-weight: 650; letter-spacing: -.02em;
}

.report__cards {
  margin-top: 16px;
  /* minmax(0, 1fr) rather than the implicit `auto`. A grid item's default
     min-width is auto, which sizes the track to its WIDEST content — so the
     result rail's flex children stretched the whole section sideways instead
     of scrolling inside it. .report__split already does this for the same
     reason; this row needed it too. */
  display: grid; gap: 1px; grid-template-columns: minmax(0, 1fr);
  background: var(--rule); border: 1px solid var(--rule);
}
.report-card { background: var(--paper-2); padding: 18px 18px 20px; min-width: 0; }
.report-card__t { font-size: 1.02rem; font-weight: 650; letter-spacing: -.015em; line-height: 1.35; }
.report-card__t a { color: var(--ink); text-decoration-color: var(--rule-2); text-underline-offset: 3px; }
.report-card__t a:hover { text-decoration-color: var(--indigo); }
.report-card__facts { margin: 12px 0 0; display: grid; gap: 12px; }
.report-card__fact {
  display: grid; grid-template-columns: auto 1fr; gap: 2px 12px;
  padding-left: 12px; border-left: 2px solid var(--indigo);
}
.report-card__k, .report-card__v { font-family: var(--mono); font-size: .88rem; }
.report-card__k { color: var(--ink-3); grid-column: 1; }
.report-card__k::after { content: " \2192"; }
/* Every value is pinned to column 2.
   Without this, a fact with one <dt> and several <dd> — which is what the
   "who is" competitor list is — auto-places the extra values back into
   column 1, so names appear under the label and the longest one widens the
   `auto` column until the value column has almost no width left. A single
   unbroken product title then renders ONE CHARACTER PER LINE. Seen on a real
   report for `willage.life`, 2 October.
   `min-width: 0` lets the 1fr column shrink below its content, and
   `overflow-wrap` breaks a long token instead of forcing the grid wider. */
.report-card__v {
  grid-column: 2;
  margin: 0; color: var(--ink); font-weight: 600;
  min-width: 0; overflow-wrap: anywhere;
}
.report-card__quote {
  grid-column: 1 / -1; margin: 2px 0 0;
  font-family: var(--serif); font-size: 1rem; line-height: 1.5; color: var(--ink-2);
}
.report-card__src { grid-column: 1 / -1; margin: 0; font-size: 12.5px; color: var(--ink-3); }
/* the gap itself: quiet, but never mistaken for a loading row */
.report-card__none { margin-top: 8px; font-size: .92rem; color: var(--flag); }

.report__foot { margin-top: 28px; font-size: 13.5px; line-height: 1.55; color: var(--ink-3); }
.report__foot a { color: var(--ink-2); }

@media (max-width: 420px) {
  .report__actions .btn { width: 100%; }
  }

/* ---------- 14. Reduced motion ---------- */

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

/* ── The answer set, as a shopping assistant shows it ─────────────────────────
   A horizontal rail of product cards instead of two lists of titles. The
   merchant's own products are marked and lead the row, so it reads as one
   result set with their position in it; when they are absent the row opens
   with a gap tile, which states the finding in one object.

   Images are served from cdn.shopify.com, the only media host the catalog
   uses (40 of 40 URLs measured across four verticals) and the one host added
   to img-src for it. Every other field came back from the same query.        */

.rail { margin: 14px 0 0; min-width: 0; }

.rail__head {
  font-family: var(--mono);
  font-size: .82rem;
  color: var(--ink-3);
  letter-spacing: .02em;
  text-transform: lowercase;
  margin: 0 0 8px;
}

.rail__strip {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  /* The rail is the one place the page scrolls sideways on purpose, so it
     carries its own padding rather than letting a card clip at the edge. */
  padding: 2px 2px 10px;
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
  /* The scroll container has to be allowed to be narrower than its contents,
     or the flex row simply makes it wider and nothing ever scrolls. */
  min-width: 0;
  max-width: 100%;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}

.pcard {
  flex: 0 0 148px;
  scroll-snap-align: start;
  background: var(--paper);
  border: 1px solid var(--rule-2);
  border-radius: 10px;
  padding: 8px 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

/* Theirs. The only coloured thing in the row, so the eye finds it first. */
.pcard--mine { border-color: var(--indigo); background: var(--indigo-wash); }

.pcard__frame {
  aspect-ratio: 1 / 1;
  border-radius: 6px;
  overflow: hidden;
  background: var(--paper-2);
}

.pcard__img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* No image, or one that failed to load. A flat tile, never a broken glyph. */
.pcard__frame--empty,
.pcard__frame--absent {
  background: repeating-linear-gradient(
    -45deg, transparent 0 7px, var(--rule-2) 7px 8px);
  opacity: .5;
}
.pcard__frame--absent { border: 1px dashed var(--rule-2); background: none; opacity: 1; }

.pcard--absent { border-style: dashed; background: none; }

.pcard__mine {
  align-self: flex-start;
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--indigo-ink);
  background: var(--indigo);
  border-radius: 4px;
  padding: 1px 5px;
}

.pcard__t {
  margin: 0;
  font-size: .84rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink);
  /* Two lines, so a long title cannot change the row's height. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The brand, which is the answer to "who came back instead of me" and so must
   not be the faintest thing on the card. It was muted below the price, which
   inverted the hierarchy: the shop matters more here than the money does. */
.pcard__by {
  margin: 0;
  font-size: .76rem;
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.pcard__meta {
  margin: auto 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: 2px 8px;
  font-family: var(--mono);
  font-size: .76rem;
}
/* One bold thing per card, and it is the product title. The price was also
   600, which gave every card two competing emphases. */
.pcard__price { color: var(--ink-3); font-variant-numeric: tabular-nums; }
.pcard__rating { color: var(--ink-3); font-variant-numeric: tabular-nums; }

.rail__foot {
  margin: 2px 0 0;
  font-size: .76rem;
  line-height: 1.45;
  color: var(--ink-3);
}

@media (max-width: 560px) {
  .pcard { flex-basis: 132px; }
}

@media (prefers-reduced-motion: reduce) {
  .rail__strip { scroll-behavior: auto; }
}

/* Clickable cards and rank. The whole card is the hit area, which is how a
   shopping result behaves; cards without a storefront url render as plain
   divs and must look identical apart from the cursor. */

.pcard { position: relative; }

.pcard--link { text-decoration: none; color: inherit; cursor: pointer; }
.pcard--link:hover { border-color: var(--indigo); }
.pcard--link:hover .pcard__t { text-decoration: underline; text-underline-offset: 2px; }
.pcard--link:focus-visible {
  outline: 2px solid var(--indigo);
  outline-offset: 2px;
  border-color: var(--indigo);
}

/* Where in the catalogue's order this result came. Sits on the image so it
   costs no height, and is the one thing readable at a glance while scrolling. */
.pcard__rank {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 1;
  font-family: var(--mono);
  font-size: .68rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: .02em;
  color: var(--paper);
  background: rgba(21, 20, 15, .72);
  border-radius: 4px;
  padding: 1px 5px;
}

/* Theirs, so the rank badge matches the card's own marker rather than fading
   into the wash. */
.pcard--mine .pcard__rank { background: var(--indigo); color: var(--indigo-ink); }

/* ── The subscribe dialog ─────────────────────────────────────────────────────
   A native <dialog>, centred, so choosing a band cannot land below the fold.
   showModal() handles focus, Escape, inertness and the backdrop; the only
   thing left to us is the look and stopping the page behind it scrolling.   */

.submodal {
  /* <dialog> defaults to an auto-margin box; these centre it in both axes and
     keep it off the edges on a small screen. */
  margin: auto;
  width: min(440px, calc(100vw - 32px));
  max-height: min(86vh, 760px);
  padding: 0;
  border: 1px solid var(--rule-2);
  border-radius: 14px;
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 24px 60px rgba(21, 20, 15, .28), 0 2px 8px rgba(21, 20, 15, .12);
  overflow: hidden;
}

.submodal::backdrop { background: rgba(21, 20, 15, .55); }

.submodal__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 18px 12px;
  border-bottom: 1px solid var(--rule-2);
}

.submodal__h {
  margin: 0;
  font-size: 1.04rem;
  font-weight: 650;
  letter-spacing: -.015em;
}

.submodal__x {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  font-size: 1.3rem;
  line-height: 1;
  color: var(--ink-3);
  background: none;
  border: 1px solid transparent;
  border-radius: 8px;
  cursor: pointer;
}
.submodal__x:hover { color: var(--ink); border-color: var(--rule-2); }
.submodal__x:focus-visible { outline: 2px solid var(--indigo); outline-offset: 1px; }

/* The form is the only thing that scrolls, so a long one cannot push the
   close button off screen. */
.submodal__body {
  padding: 16px 18px 20px;
  overflow-y: auto;
  max-height: calc(min(86vh, 760px) - 60px);
  -webkit-overflow-scrolling: touch;
}

/* showModal() makes the page inert but does not stop it scrolling behind the
   backdrop, which on iOS reads as the dialog drifting. */
.is-modal, .is-modal body { overflow: hidden; }

/* On a phone it reads better as a sheet than a small centred card. */
@media (max-width: 520px) {
  .submodal {
    width: 100vw;
    max-width: 100vw;
    max-height: 92vh;
    margin: auto auto 0;
    border-radius: 14px 14px 0 0;
    border-bottom: none;
  }
  .submodal__body { max-height: calc(92vh - 60px); }
}

@media (prefers-reduced-motion: no-preference) {
  .submodal[open] { animation: submodal-in .16s ease-out; }
  @keyframes submodal-in {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
  }
}
