/* lp1/lp1.css — page-specific components layered on top of lp1/base-v6.css.
   base-v6.css (linked before this file in index.html) is the single source
   of tokens, fonts, .wrap, .sec-head, .eyebrow, .card, .btn/.btn-grad and the
   other shared primitives — this file only adds what base doesn't have, and
   never redeclares a selector base already owns (spec: reconcile in favour
   of base). */
/* A class-level `display` beats the UA stylesheet's [hidden]{display:none},
   so `el.hidden = true` silently did nothing on .dash__cmp / .dash__feed —
   the optimized product stayed on screen while its rows still read
   "Not optimized". Declared at top level: nested inside a media query it was
   simply dropped by the parser. */
[hidden]{display:none !important}
html{-webkit-text-size-adjust:100%}
img{max-width:100%; height:auto; display:block}

/* Texture layers — ported verbatim from lp/feedscore/index.html (spec Part 1.1).
   Absolutely positioned; the parent must carry position:relative + overflow:hidden,
   and the section's own content must sit above at z-index:1. */
.grid-lines{position:absolute;inset:0;
  background-image:linear-gradient(var(--grid-line) 1px,transparent 1px),linear-gradient(90deg,var(--grid-line) 1px,transparent 1px);
  background-size:66px 66px;
  -webkit-mask-image:radial-gradient(ellipse 88% 76% at 50% 24%,#000 8%,transparent 80%);
  mask-image:radial-gradient(ellipse 88% 76% at 50% 24%,#000 8%,transparent 80%);}
.grid-dots{position:absolute;inset:0;
  background-image:radial-gradient(circle at 1px 1px,var(--violet-line) 1.6px,transparent 0);
  background-size:66px 66px;
  -webkit-mask-image:radial-gradient(ellipse 62% 54% at 60% 34%,#000 4%,transparent 72%);
  mask-image:radial-gradient(ellipse 62% 54% at 60% 34%,#000 4%,transparent 72%);
  opacity:.7;}
.grain{position:absolute;inset:0;z-index:-1;pointer-events:none;opacity:.05;mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");}

/* The header is sticky at 69px, and nothing in the page set a scroll offset —
   every in-page anchor landed with its heading tucked underneath it. One rule
   on the sections the nav points at, with a little air on top. */
[id]{scroll-margin-top:88px}

.band{padding-block:96px; background:var(--bg)}
.band--alt{background:var(--band)}
.band--tex{position:relative; overflow:hidden}
.band--tex > .wrap{position:relative; z-index:1}

/* .wrap, .eyebrow, .h2/.sec-head h2, .sub/.sec-head p, .card and .btn/.btn-grad
   all come from base-v6.css now — see file header. The two rules below are
   additive, not overrides: base's .card has no hover state (it was built for
   the static hero scanner mock), and base's .btn-grad already ships the
   sheen sweep — this just layers a pulsing glow on the two primary CTAs. */
.card{transition:box-shadow .25s ease, transform .25s ease, border-color .25s ease}
.card:hover{box-shadow:var(--shadow-m); transform:translateY(-3px); border-color:var(--violet-line)}
.btn-grad.pulse{animation:pulseR 2.6s ease-out infinite}
@keyframes pulseR{
  0%{box-shadow:0 10px 30px rgba(113,111,255,.36),inset 0 1px 0 rgba(255,255,255,.28),0 0 0 0 rgba(113,111,255,.4)}
  70%{box-shadow:0 10px 30px rgba(113,111,255,.36),inset 0 1px 0 rgba(255,255,255,.28),0 0 0 18px rgba(113,111,255,0)}
  100%{box-shadow:0 10px 30px rgba(113,111,255,.36),inset 0 1px 0 rgba(255,255,255,.28),0 0 0 0 rgba(113,111,255,0)}
}

/* Entrance transition — the Framer behaviour, reproduced (spec §2.1).
   The hidden state is gated behind .js: every section on this page carries
   .reveal, so an unconditional opacity:0 would blank the entire page if the
   module 404s, fails to parse, or is blocked. Without JS the page renders in
   full, unanimated. */
.js .reveal{opacity:0; transform:translateY(20px); transition:opacity .6s ease, transform .6s ease}
.js .reveal.is-in{opacity:1; transform:none}
@media (prefers-reduced-motion:reduce){
  .js .reveal{opacity:1; transform:none; transition:none}
}

/* Task (round 3, item 2 — global): section headings rise-and-fade staggered
   eyebrow -> headline -> subline as they enter view, reusing the existing
   .reveal/.is-in mechanism (no new observer) rather than animating the whole
   .sec-head block as one piece. Same specificity as the base .reveal rule
   above; this file loads after base-v6.css so it wins on source order. */
.sec-head.reveal{opacity:1; transform:none}
.sec-head.reveal .eyebrow,.sec-head.reveal .h2,.sec-head.reveal .sub{
  opacity:0; transform:translateY(14px); transition:opacity .55s ease, transform .55s ease}
.sec-head.reveal.is-in .eyebrow{transition-delay:0ms}
.sec-head.reveal.is-in .h2{transition-delay:80ms}
.sec-head.reveal.is-in .sub{transition-delay:160ms}
.sec-head.reveal.is-in .eyebrow,.sec-head.reveal.is-in .h2,.sec-head.reveal.is-in .sub{opacity:1; transform:none}

/* Task 14: visible focus rings — global, zero-specificity so it never fights
   a component's own colors. */
:where(a,button,summary,input,[role="tab"],[role="switch"]):focus-visible{
  outline:2px solid var(--violet); outline-offset:3px; border-radius:6px;
}

.hdr{position:sticky; top:0; z-index:60; background:rgba(255,255,255,.86); -webkit-backdrop-filter:saturate(180%) blur(18px); backdrop-filter:saturate(180%) blur(18px); border-bottom:1px solid var(--line)}
.hdr__in{display:flex; align-items:center; gap:24px; height:68px}
.hdr__nav{display:flex; gap:26px; margin-inline:auto}
.hdr__nav a{color:var(--ink-2); text-decoration:none; font-size:15px; font-weight:500}
.hdr__nav a:hover{color:var(--ink)}
.hdr__cta{padding:10px 20px; font-size:15px}
/* Task 14: iOS safe-area guard — keep the sticky header clear of the notch. */
.hdr{padding-top:env(safe-area-inset-top)}

/* Hero — uses base's .hero-bg (violet wash + grid, masked to fade out
   downward) instead of the flat radial wash this used to carry; .grain
   layers a faint noise texture on top (kept, not a base duplicate). */
.hero{padding-block:64px 0; position:relative; overflow:hidden; isolation:isolate}
.hero__in{position:relative; z-index:1; text-align:center; display:flex; flex-direction:column; align-items:center}
/* "Built for ___" typewriter kicker (client: setsmart.io reference) — the
   first thing to move on the page. Static markup ships with the first
   rotation phrase already written (readable pre-JS, indexable); js/herokicker.js
   takes the line over rather than building it. min-width on the rotating
   span is sized to the longest phrase ("multi-channel retailers", measured
   in-browser at 151px) so the H1 below never shifts as characters type. */
.hero__kicker{display:flex; align-items:baseline; justify-content:center; gap:1px; margin:0 0 14px}
.hero__kicker-pre{color:var(--muted); font-size:14px; font-weight:400}
/* The width reservation stops the centred line jumping as phrases change
   length, but it used to sit on the text itself — which parked the caret at
   the right edge of the reserved box instead of at the end of what had been
   typed. The reservation moves to a slot around both, so the caret follows
   the last character and the line still never shifts. */
.hero__kicker-slot{display:inline-flex; align-items:baseline; min-width:156px; text-align:left}
.hero__kicker-rot{display:inline; min-width:0; color:var(--ink); font-size:14px; font-weight:600}
.hero__kicker-caret{display:inline-block; width:2px; height:14px; margin-left:1px; background:var(--violet);
  animation:blink 1s step-end infinite; transform:translateY(1px)}

.hero__h1{font-size:clamp(38px,6vw,66px); line-height:1.06; letter-spacing:-.03em; font-weight:700; margin:0; max-width:16ch}
.grad{background:linear-gradient(120deg,#5B5BFF 0%,#8B5CF6 42%,#C13FD6 74%,#E5484D 100%); -webkit-background-clip:text; background-clip:text; color:transparent}
.hero__sub{color:var(--muted); font-size:19px; max-width:62ch; margin:22px 0 0}
.hero__cta{margin-top:30px}
.hero__reassure{color:var(--muted); font-size:14px; margin:14px 0 0}
/* Ambient halo behind the headline — base's .faq-glow, repositioned to sit
   tight behind the h1 instead of spanning the whole FAQ block. */
.hero__in .faq-glow{inset:-6% 6% auto; height:60%; opacity:.55; animation:heroHaloDrift 15s ease-in-out infinite}
@keyframes heroHaloDrift{0%,100%{transform:translate(0,0)}50%{transform:translate(2%,3%)}}
/* Grid texture fades in over the first second — nothing that delays the
   headline itself (h1/CTA have no entrance animation of their own). */
.hero-bg::after{opacity:0; animation:heroGridIn 1.1s ease-out .15s forwards}
@keyframes heroGridIn{to{opacity:1}}
.hero__in .shopbadge{margin:0 0 22px}
/* base's .assure/.proof are built for the reference's left-aligned hero
   column; this hero is centered, so only the alignment needs overriding. */
.hero__in .assure{justify-content:center; margin-top:20px}
.hero__in .proof{justify-content:center; border-top:0; margin-top:22px; padding-top:0}
/* Task (client extension, round 3): the heading block that used to sit above
   the step indicator ("Inside the app" / "Select. Optimize. Done.") is gone —
   the mock now reads as a direct continuation of the hero's own headline and
   CTA, so the gap above the steps is tightened rather than left empty. */
.hero-mock{margin-top:34px; padding-bottom:64px}
/* Client restructure: the trust row (avatars + rating + merchant count) now
   closes the fold after the dashboard mock instead of sitting above it —
   "you see the claim, you see the product doing it, you see that other
   people have done it." Base's .proof already ships a border-top divider +
   top padding, which reads correctly here without an override. */
.hero-mock .proof{justify-content:center}
/* Channel pills under the trust row — deliberately the flat .shopbadge pill
   (small mark + label, no card, no status badge) rather than the bordered
   .ch-tile cards the #socialproof channel-sync section uses further down,
   so the two don't read as the same component pasted twice. */
.hero-chans{display:flex; flex-wrap:wrap; justify-content:center; gap:8px 10px; margin-top:18px}
.hero-chans .shopbadge{margin-top:0}

/* Real avatar photographs (round 3, item 6) — the hero stack now uses base's
   own .avs img component directly (see markup); .avhead survives only for
   the larger review-card portrait, sized/shaped the same way but as a plain
   <img>, object-fit:cover instead of an inline-SVG bust. */
.avhead{width:32px; height:32px; border-radius:50%; overflow:hidden; flex:none; display:block;
  object-fit:cover; background:#fff; border:2px solid var(--surface); box-shadow:var(--shadow-s); margin-left:-10px}
.avhead:first-child{margin-left:0}
.avhead--lg{width:44px; height:44px; border:0; margin-left:0; box-shadow:none}

/* Mobile overrides come AFTER the component they modify — see Global Constraints */
@media (max-width:760px){
  .hdr__nav{display:none}
  .hdr__cta{margin-inline-start:auto}
  .hero{padding-block:40px 0}
  .hero__sub{font-size:17px}
  .hero-mock{margin-top:26px; padding-bottom:40px}
  .hero-chans{gap:6px 8px}
  .hero-chans .shopbadge{font-size:11px; padding:4px 9px}
  .hero-chans .shopbadge svg{width:12px; height:12px}
}
@media (prefers-reduced-motion:reduce){
  .hero__kicker-caret{display:none}
}

/* Task: visible 4-step sequence above the dashboard mock — replaces the old
   single caption line so the client can see, at a glance, which step is
   running and how many remain. dashboard.js toggles .is-active/.is-done on
   the <li data-step> markers as the demo cycles; the connecting line and the
   check-mark swap are pure CSS off those two classes. */
.dash-steps{display:flex; align-items:flex-start; justify-content:center; gap:4px;
  max-width:640px; margin:0 auto 22px; padding:0; list-style:none}
.dash-steps__i{flex:1 1 0; min-width:0; display:flex; flex-direction:column; align-items:center;
  text-align:center; gap:8px; position:relative}
.dash-steps__i:not(:last-child)::after{
  content:""; position:absolute; top:11px; left:calc(50% + 17px); right:calc(-50% + 17px);
  height:2px; background:var(--line-strong); z-index:0; transition:background .3s ease}
.dash-steps__dot{width:22px; height:22px; border-radius:50%; flex:none; position:relative;
  display:grid; place-items:center; font-size:11px; font-weight:700; font-family:"JetBrains Mono",monospace;
  background:var(--surface); border:2px solid var(--line-strong); color:var(--muted);
  transition:background .3s ease, border-color .3s ease, color .3s ease}
.dash-steps__lbl{font-size:11.5px; line-height:1.35; color:var(--muted); max-width:14ch;
  transition:color .3s ease, font-weight .3s ease}
.dash-steps__i.is-active .dash-steps__dot{background:var(--violet); border-color:var(--violet);
  color:#fff; box-shadow:0 0 0 5px var(--violet-soft)}
.dash-steps__i.is-active .dash-steps__lbl{color:var(--ink); font-weight:600}
.dash-steps__i.is-done .dash-steps__dot{background:var(--green); border-color:var(--green); color:transparent}
.dash-steps__i.is-done .dash-steps__dot::after{content:"✓"; position:absolute; inset:0; display:grid;
  place-items:center; color:#fff; font-size:11px; font-weight:700}
.dash-steps__i.is-done:not(:last-child)::after{background:var(--green)}
.dash-steps__i.is-done .dash-steps__lbl{color:var(--ink-2)}

@media (max-width:600px){
  .dash-steps{max-width:100%; gap:0}
  .dash-steps__lbl{display:none}
  .dash-steps__i:not(:last-child)::after{left:calc(50% + 14px); right:calc(-50% + 14px)}
}

.dash{overflow:hidden}
.dash__thumb{width:34px; height:34px; border-radius:9px; flex:none; background:var(--band);
  display:grid; place-items:center; overflow:hidden}
.dash__thumb svg{width:26px; height:26px}
.dash__id{font-size:11px; color:var(--muted); flex:none; margin-inline-start:8px}
.dash__pillstate{font-size:11px; font-weight:600; padding:4px 10px; border-radius:999px; flex:none;
  background:var(--band); color:var(--muted); transition:background .3s ease, color .3s ease}
.dash__row.is-run .dash__pillstate{background:var(--violet-soft); color:var(--violet)}
.dash__row.is-done .dash__pillstate{background:var(--green-soft); color:var(--green)}
.dash__prev{border:0; background:var(--band); color:var(--ink-2); font-size:11px; font-weight:600;
  padding:6px 12px; border-radius:999px; cursor:default; flex:none}
.dash__edit{border:0; background:none; color:var(--muted); cursor:default; flex:none; display:grid; place-items:center}
.dash__edit svg{width:15px; height:15px}
/* The "before" side used to end after one sentence, leaving a blank half
   against a full record — the client read that as exaggerated. Showing five
   EMPTY attribute slots is both more honest and a sharper comparison: the
   fields exist on every product, they are simply unfilled. */
/* The pill row read as a bare list of logos with no stated relationship to
   the product. Three words turn it into a claim. */
.hero-chans__lbl{font-size:13px; color:var(--muted); margin-inline-end:4px; white-space:nowrap}
@media (max-width:640px){ .hero-chans__lbl{flex-basis:100%; text-align:center; margin:0 0 4px} }

/* The fourth result is the control: a thin record in the same SERP, so the
   contrast is visible inside Google's own layout rather than only in our
   before/after panels. Muted throughout — it is losing. */
.gcard--thin{}
.gcard--thin .gtitle{color:var(--ink-2)}
.gcard--thin .ph{filter:saturate(.2); background:#fff}
.gcard--thin .gprice,.gcard--thin .gshop,.gcard--thin .grate{color:var(--muted)}
.gcard--thin .gfree{color:var(--muted) !important}
.gcard--thin .pos{color:var(--red) !important; font-weight:600}
.gcard--thin .pos b{color:var(--red)}

/* Hero review — ONE at a time, switching, as on the reference. A continuous
   marquee was the wrong read: it turns testimony into wallpaper the eye stops
   parsing. A switch makes each quote a discrete statement you actually read.
   Items are stacked in the same grid cell so the block's height never changes
   as the sentences differ in length. */
.rq{position:relative; display:grid; margin-top:18px; min-height:44px; place-items:center}
.rq__i{grid-area:1/1; margin:0; display:inline-flex; align-items:center; gap:11px;
  opacity:0; transform:translateY(6px); transition:opacity .45s ease, transform .45s ease;
  pointer-events:none; max-width:660px}
.rq__i.is-on{opacity:1; transform:none}
.rq__i img{width:34px; height:34px; border-radius:50%; object-fit:cover; flex:none}
.rq__i figcaption{font-size:13.5px; color:var(--ink-2); text-align:left}
.rq__i b{display:inline-flex; align-items:center; gap:5px; font-size:12px; color:var(--amber); font-weight:700}
.rq__i b small{color:var(--muted); font-weight:600}
@media (max-width:640px){ .rq__i figcaption{font-size:12.5px} .rq{min-height:60px} }
@media (prefers-reduced-motion:reduce){ .rq__i{transition:none} }

/* Two new roles in the longer card: the situation that opens it, and the
   closing line that lands it. Both sit quieter than the problem quote, which
   stays the card's loudest moment. */
.revcard__sit{font-size:13px; color:var(--muted); line-height:1.55; margin:12px 0 0}
.revcard__close{font-size:13.5px; color:var(--ink-2); line-height:1.55; margin:14px 0 0;
  padding-top:12px; border-top:1px dashed var(--line-strong)}
@media (max-width:1000px){ .revs{grid-template-columns:1fr} }

/* Real product photographs now sit in the same boxes the placeholder SVGs
   occupied. Contain, never cover: a Google result shows the whole product on
   white, and cropping a toy to fill a box is exactly what made the drawn
   version look wrong. */
.gcard .ph img{width:100%; height:100%; max-height:100%; align-self:stretch; justify-self:stretch;
  min-height:0; object-fit:contain; padding:6px}
.dash__thumb img{width:100%; height:100%; object-fit:contain; border-radius:6px}

/* Titles wrap to one, two or three lines, which pushed price, merchant and
   rating to a different height in every card. Reserving three lines lines the
   whole row up without truncating anything. */
.gcard .meta{display:flex; flex-direction:column; gap:6px}
.gcard .gtitle{min-height:3.4em}
.gcard .pos{margin-top:auto; padding-top:6px}

/* Two columns, a handful of words per cell. The three-column version drowned
   the contrast in prose — at a glance a visitor should read the shape of the
   comparison, not a paragraph. */
.vs{max-width:820px; margin-inline:auto; position:relative; background:var(--surface);
  border:1px solid var(--line); border-radius:var(--r-xl); overflow:hidden; box-shadow:var(--shadow-m)}
.vs-head,.vs-row{display:grid; grid-template-columns:1fr 1fr 1.05fr; align-items:stretch}
.vs-head{border-bottom:1px solid var(--line)}
.vs-head .vs-c{justify-content:center; padding:24px 18px}
.vs-head b{font-size:15px; color:var(--muted); font-weight:600}
.vs-head img{height:30px; width:auto}
.vs-row{border-top:1px solid var(--line)}
.vs-row:first-of-type{border-top:0}
.vs-arg{padding:18px 22px; display:flex; align-items:center; font-size:14.5px; font-weight:600; color:var(--ink)}
.vs-c{padding:18px 20px; display:flex; align-items:center; gap:10px; border-left:1px solid var(--line);
  font-size:14px; color:var(--muted)}
.vs-c i{font-style:normal; flex:none; width:20px; height:20px; border-radius:50%; display:grid;
  place-items:center; font-size:11px; font-weight:700; background:var(--red-soft); color:var(--red)}
.vs-c--yes{background:var(--violet-soft); border-left-color:var(--violet-line); color:var(--ink);
  box-shadow:1px 0 0 var(--violet-line)}
/* The grey .vs-row border crosses every column; inside the winning one it
   has to be violet or the column stops reading as a single block. */
.vs-row .vs-c--yes{border-top:1px solid var(--violet-line); margin-top:-1px}
.vs-c--yes i{background:var(--green-soft); color:var(--green)}
.vs-c--yes b{color:var(--violet)}
.vs-head .vs-c--yes{background:linear-gradient(180deg,rgba(113,111,255,.20),var(--violet-soft));
  box-shadow:inset 0 3px 0 var(--violet)}
.vs-row--total .vs-arg{color:var(--muted); font-weight:600}
.vs-fig{font-size:22px; letter-spacing:-.02em; color:var(--muted)}
.vs-fig--win{font-size:26px; color:var(--violet) !important}
/* Decomposing the comparison into stacked label/value/value blocks lost the
   thing that makes it work — reading two columns against each other. It stays
   a table on mobile and scrolls sideways instead, with the header kept so the
   columns are still named once you have swiped. */
@media (max-width:820px){
  /* A table that scrolls sideways has to say so, or the third column simply
     looks cut off. The wrapper carries a fade on its right edge that the
     scroll runs under. */
  /* Two attempts at a fade on this edge both read as a rendering fault
     rather than as an affordance — a gradient sitting over a 26px corner
     radius always will. Dropped: a table whose last column plainly continues
     past the frame says "scroll me" without help. */
  .vs-wrap{position:relative}
  /* Made to fit instead of scroll. Dressing an overflow needed a fade, the
     fade read as a rendering fault twice, and the winning column's own
     border was cut at the frame either way. The cells hold three or four
     words, so the columns can simply be narrower. */
  .vs{overflow:hidden}
  .vs-head,.vs-row{grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,1.06fr)}
  .vs-head .vs-c{padding:14px 8px}
  .vs-head b{font-size:13px}
  .vs-head img{height:22px}
  .vs-arg{padding:12px 10px; font-size:12.5px; line-height:1.3}
  .vs-c{padding:12px 8px; font-size:12.5px; line-height:1.3; gap:6px; align-items:flex-start}
  .vs-c i{width:16px; height:16px; font-size:9px; margin-top:1px}
  .vs-fig{font-size:15px}
  .vs-fig--win{font-size:17px}
}

/* base-v6 reserves 104px at the bottom of every page for the funnel's sticky
   bar. lp1 no longer has one, so that reservation rendered as a white band
   under the footer. */
body{padding-bottom:0}

/* Reviews, rebuilt outcome-first. The previous version stacked three
   same-weight paragraphs, so nothing invited a read and the number — the only
   thing a visitor actually scans for — sat buried in the middle. Now: the
   figure is the headline, one sentence carries the voice, attribution closes.
   Three beats instead of five. */
/* A third call to action, sitting where the argument has just been made and
   before the merchants who lived it — the reader is at peak "yes, that is my
   catalogue" here, and the next scroll was previously another whole section.
   Deliberately unboxed: a line and a button, no card, so it reads as a beat
   in the page rather than a fourth panel competing with the ones around it. */
.midcta{margin-top:52px; text-align:center; display:flex; flex-direction:column; align-items:center}
/* The one under the comparison table needs no lead line — the table just made
   the argument, so the button is the only thing left to say (setsmart.io). */
.midcta--tight{margin-top:34px}
.midcta__l{margin:0 0 18px; font-size:17px; font-weight:600; color:var(--ink); max-width:34ch;
  line-height:1.4; letter-spacing:-.01em}
@media (max-width:560px){
  .midcta__l{font-size:15.5px}
  .midcta__b{width:100%; max-width:340px; justify-content:center}
}

/* ── Who it's for ──────────────────────────────────────────────────────
   Five formats before this one. The last carried a drawing per audience,
   and the drawings were the problem: abstract ones explained nothing, and
   even redrawn with real words they were describing a situation a sentence
   states better. So the drawing goes and the space becomes the argument.

   Each panel now opens by describing the reader to himself — you run a feed
   agency, several clients, catalogues in more than one language, and your
   work stops at the campaign — then pairs every pain with its answer on the
   same row. Paired, not two lists side by side: a reader scanning the left
   column finds the answer to HIS problem opposite it, rather than a menu of
   features next to a menu of complaints. */
.uct{max-width:1000px; margin-inline:auto}
.uct__tabs{display:flex; gap:6px; padding:6px; margin-bottom:22px;
  background:var(--surface); border:1px solid var(--line); border-radius:999px}
.uct__t{flex:1; display:flex; align-items:center; justify-content:center; gap:9px;
  padding:12px 16px; border:0; border-radius:999px; background:transparent; cursor:pointer;
  font:inherit; font-size:14px; font-weight:600; color:var(--muted);
  transition:background .2s ease, color .2s ease}
.uct__t:hover{color:var(--ink)}
.uct__t[aria-selected="true"]{background:linear-gradient(135deg,#6E6BFF,#A855F7); color:#fff;
  box-shadow:0 8px 20px -8px rgba(113,111,255,.6)}
.uct__ic{display:grid; place-items:center; width:19px; height:19px; flex:none}
.uct__ic svg{width:100%; height:100%; fill:currentColor}
.uct__t:focus-visible{outline:3px solid var(--violet); outline-offset:2px}

.uct__p{padding:30px 32px 26px; border:1px solid var(--line); border-radius:var(--r-xl);
  background:var(--surface); animation:uctIn .3s ease both}
@keyframes uctIn{from{opacity:0; transform:translateY(6px)} to{opacity:1; transform:none}}
@media (prefers-reduced-motion:reduce){ .uct__p{animation:none} }
/* [hidden] is set by the script; a class-level display would beat it. */
.uct__p[hidden]{display:none}
.uct__intro{margin:0 0 24px; font-size:16.5px; line-height:1.55; color:var(--ink);
  font-weight:500; max-width:70ch}

/* The two columns are read across, not down. */
.pt{border-top:1px solid var(--line)}
.pt__head{display:grid; grid-template-columns:1fr 1fr; gap:0 28px; padding:14px 0 10px}
.pt__hbad,.pt__hfix{font-size:10.5px; font-weight:700; letter-spacing:.11em;
  text-transform:uppercase; color:var(--muted)}
.pt__hfix{color:var(--green)}
.pt__r{display:grid; grid-template-columns:1fr 1fr; gap:0 28px;
  padding:11px 0; border-top:1px solid var(--line)}
.pt__bad,.pt__fix{position:relative; margin:0; padding-left:25px; font-size:13.5px;
  line-height:1.45}
.pt__bad{color:var(--ink-2)}
.pt__fix{color:var(--ink); font-weight:500}
.pt__bad::before,.pt__fix::before{content:""; position:absolute; left:0; top:.12em;
  width:17px; height:17px; border-radius:50%}
.pt__bad::before{background:var(--red-soft); border:1px solid var(--red-line)}
.pt__fix::before{background:var(--green-soft); border:1px solid var(--green-line)}
.pt__bad::after,.pt__fix::after{content:""; position:absolute; top:.42em; width:8px; height:8px}
.pt__bad::after{left:5px; background:var(--red);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath d='M2 2l6 6M8 2l-6 6' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath d='M2 2l6 6M8 2l-6 6' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") center/contain no-repeat}
.pt__fix::after{left:4.6px; top:.5em; background:var(--green);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 8'%3E%3Cpath d='M1 4.2l2.6 2.6L9 1.4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 8'%3E%3Cpath d='M1 4.2l2.6 2.6L9 1.4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat}

@media (max-width:820px){
  .uct__tabs{flex-direction:column; border-radius:var(--r-l)}
  .uct__t{border-radius:var(--r-m); justify-content:flex-start}
  .uct__p{padding:22px 20px}
  .uct__intro{font-size:15px; margin-bottom:18px}
  /* Two columns measure badly here: at this panel width each cell gets 17
     characters a line for sentences averaging 46, so every cell becomes
     three ragged lines. Stacking gives 41, which reads — but five pairs
     then look like ten alternating items.
     One side at a time solves both. Each line keeps the full width, and the
     pairing survives because a row holds its position across the switch:
     the third problem and the third answer are the same line. */
  .pt-tog{display:flex; gap:5px; padding:5px; margin:0 auto 18px; max-width:320px;
    background:var(--surface); border:1px solid var(--line); border-radius:999px}
  .pt-tog__b{flex:1; padding:10px 12px; border:0; border-radius:999px; background:transparent;
    font:inherit; font-size:13px; font-weight:600; color:var(--muted); cursor:pointer;
    white-space:nowrap; transition:background .2s ease, color .2s ease}
  .pt-tog__b:first-child.is-on{background:var(--red); color:#fff}
  .pt-tog__b:last-child.is-on{background:var(--green); color:#fff}
  .pt-tog__b:focus-visible{outline:3px solid var(--violet); outline-offset:2px}

  .pt__head{display:none}
  .pt{border-top:0}
  .pt__r{grid-template-columns:1fr; gap:0; padding:11px 0; border-top:1px solid var(--line)}
  .pt__r:first-of-type{border-top:0}
  .pt__bad,.pt__fix{font-size:14px; animation:ptSwap .22s ease both}
  .pt__fix{display:none}
  .uct--fix .pt__bad{display:none}
  .uct--fix .pt__fix{display:block}
  @media (prefers-reduced-motion:reduce){ .pt__bad,.pt__fix{animation:none} }
}

/* The long-tail chart. This is the argument the page was missing: the
   merchant is not invisible — he sees impressions, so "you do not show"
   would be contradicted by his own account in three seconds. He is
   UNDER-served. A thin record still matches the obvious query everyone
   bids on; what it never matches is the long precise one, and those are
   both more numerous and cheaper. The arithmetic lives in the labels so the
   panel copy can stay at a headline and three lines. 5,000 and $800 are a
   worked example the visitor can redo on his own numbers, not a claim about
   anyone's account. */
.lt-win{fill:var(--violet)}
.lt-dead{fill:var(--line-strong)}
.lt-axis{stroke:var(--line-strong); stroke-width:1.4}
.lt-brace{stroke:var(--line-strong); stroke-width:1.2; stroke-dasharray:3 3; fill:none}
.lt-lbl{fill:var(--muted); font-size:9.5px; font-weight:700; letter-spacing:.1em}
.lt-tail{fill:var(--muted); font-size:9.5px; font-style:italic}
.lt-big{fill:var(--ink); font-size:24px; font-weight:700; letter-spacing:-.03em}
.lt-cap{fill:var(--ink-2); font-size:11px; font-weight:600}
.lt-note{fill:var(--muted); font-size:9.5px}

/* Text inside the panel drawings. They used to be grey placeholder bars,
   which could have depicted anything — the client could not read them as
   client feeds or as a working day. Real words cost nothing here and the
   drawing explains itself. */
.d-tag{fill:var(--muted); font-size:9px; font-weight:700; letter-spacing:.12em}
.d-txt{fill:var(--ink); font-size:12.5px; font-weight:600}
.d-name{fill:var(--ink); font-size:12.5px; font-weight:600}
.d-sub{fill:var(--muted); font-size:10.5px}
.d-time{fill:var(--violet); font-size:12.5px; font-weight:700;
  font-family:"JetBrains Mono",ui-monospace,monospace}
.d-time--win{fill:var(--violet)}
.d-ok{fill:var(--green-soft); stroke:var(--green-line); stroke-width:1}
.d-tick{fill:none; stroke:var(--green); stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round}

/* panel drawings */
.v-card{fill:var(--surface); stroke:var(--line-strong); stroke-width:1.4}
.v-card--win{fill:var(--violet-soft); stroke:var(--violet)}
.v-ink{fill:var(--ink)} .v-mute{fill:var(--line-strong)}
.v-arrow{stroke:var(--line-strong); stroke-width:1.6; fill:none}
.v-head{fill:var(--violet)}
.v-row{fill:var(--band); stroke:var(--line); stroke-width:1}
.v-sweep{fill:var(--violet); opacity:.10}
.v-dot{fill:var(--violet)}
.v-dotmute{fill:var(--line-strong)}
.v-ring{fill:none; stroke:var(--line-strong); stroke-width:3}
.v-ringlive{fill:none; stroke:var(--violet); stroke-width:3; stroke-linecap:round}
.v-big{fill:var(--ink); font-size:40px; font-weight:700; letter-spacing:-.03em}
.v-small{fill:var(--muted); font-size:12px; font-weight:700; letter-spacing:.12em}

@media (max-width:820px){
  .uct__tabs{flex-direction:column; border-radius:var(--r-l)}
  .uct__t{border-radius:var(--r-m); justify-content:flex-start}
  .uct__p{grid-template-columns:1fr; gap:22px; padding:24px}
  .uct__vis{order:-1}
  .uct__lead{font-size:18px}
}

/* ── The anchor band above the quotes ──────────────────────────────────
   The chosen reference put aggregate usage numbers here — merchants,
   products rewritten. There are none yet, and a fabricated usage statistic
   is a heavier factual claim than a fabricated quote, so these are facts
   about the PRODUCT instead: five highlights per product and ten buyer
   queries come straight out of ai_preview_service, the single feed URL and
   the hourly rebuild out of the feed services. Same anchoring job, nothing
   invented. Swap in real usage numbers the day they exist. */
.anchor{display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:16px; margin-bottom:26px}
.anchor__i{display:flex; align-items:flex-start; gap:15px; padding:20px 22px;
  border:1px solid var(--line); border-radius:var(--r-l); background:var(--surface)}
.anchor__n{flex:none; font-size:34px; line-height:1; font-weight:700; letter-spacing:-.03em;
  font-variant-numeric:tabular-nums;
  background:linear-gradient(135deg,#6E6BFF,#A855F7); -webkit-background-clip:text;
  background-clip:text; color:transparent}
.anchor__n em{font-style:normal; font-size:.42em; font-weight:600; margin-left:3px}
.anchor__l{display:flex; flex-direction:column; gap:4px; font-size:13.5px; font-weight:600;
  color:var(--ink); min-width:0; padding-top:2px}
.anchor__l small{font-size:11.5px; font-weight:400; line-height:1.45; color:var(--muted)}
@media (max-width:980px){ .anchor{grid-template-columns:1fr} }

/* ── Reviews, conventional layout ──────────────────────────────────────
   Client asked for the classic wall setsmart.io runs. Worth recording what
   makes theirs work, because we cannot copy it: their section opens on
   "Note Trustpilot vérifiée · 4.8" — a third party attests the reviews, and
   the wall below inherits that credibility. We have no such rating, and
   writing one would be a false claim about a company that never rated us,
   which is a different order of thing from an illustrative quote.

   So the layout is theirs and the anchor is not: the three product figures
   above the wall do the job the badge does for them, and they are verifiable
   in the code. The disclaimer separates the two kinds of claim. Swap the
   figures for a real platform badge the day one exists — that is the only
   change this section needs to become genuine proof. */
.rv-grid{display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:18px; align-items:stretch}
.rv{margin:0; display:flex; flex-direction:column; padding:24px 24px 22px;
  border:1px solid var(--line); border-radius:var(--r-xl); background:var(--surface);
  transition:transform .25s ease, box-shadow .25s ease}
.rv:hover{transform:translateY(-3px); box-shadow:var(--shadow-m)}
.rv__stars{color:var(--amber); font-size:14px; letter-spacing:2px; line-height:1; margin-bottom:13px}
.rv__q{margin:0; font-size:14px; line-height:1.6; color:var(--ink-2); flex:1 1 auto}
.rv__q::before{content:"\201C"} .rv__q::after{content:"\201D"}
/* Grid rather than a wrapping flex row: on the widest attribution a flex row
   breaks between the avatar and the name and lifts that face out of line. */
.rv__by{display:grid; grid-template-columns:auto minmax(0,1fr); gap:0 11px;
  align-items:center; margin-top:auto; padding-top:20px; flex:0 0 auto}
.rv__by img{width:42px; height:42px; border-radius:50%; object-fit:cover}
.rv__who{display:flex; flex-direction:column; min-width:0}
.rv__who b{font-size:13.5px; line-height:1.25}
.rv__who small{font-size:11.5px; color:var(--muted); line-height:1.35}
.rv__meta{font-size:10.5px !important}
@media (max-width:1040px){ .rv-grid{grid-template-columns:repeat(2,minmax(0,1fr))} }
@media (max-width:680px){ .rv-grid{grid-template-columns:1fr} }


.dash__attlbl{display:block; margin-top:14px; font-size:11px; letter-spacing:.06em; color:var(--muted)}
.dash__attlbl b{color:var(--red)}
.dash__empties{display:flex; flex-wrap:wrap; gap:6px; margin-top:8px}
.dash__empties span{width:64px; height:22px; border-radius:999px; border:1px dashed var(--line-strong); background:var(--surface-2)}
/* The few highlights a thin record does carry are real chips, just muted —
   same treatment as .prec__hl--thin in the before/after block. */
.dash__empties span.is-filled{width:auto; padding:0 9px; border-style:solid; background:var(--band);
  display:grid; place-items:center; font-size:11px; font-weight:600; color:var(--muted)}

.dash__chips{display:flex; flex-wrap:wrap; gap:6px; margin-top:8px}
.dash__chips span{font-size:11px; padding:3px 9px; border-radius:999px; background:var(--green-soft); color:var(--green)}
.dash__desc{color:var(--ink-2); font-size:12.5px; margin:6px 0 0; line-height:1.5}
.dash__bar{display:flex; align-items:center; gap:10px; padding:14px 18px; border-bottom:1px solid var(--line); background:var(--surface-2); flex-wrap:wrap}
.dash__store{font-weight:600; font-size:14px}
.dash__pill{font-size:12px; padding:4px 10px; border-radius:999px; background:var(--band); color:var(--muted)}
.dash__pill--v{background:var(--violet-soft); color:var(--violet)}
.dash__pill--g{background:var(--green-soft); color:var(--green)}
.dash__url{margin-inline-start:auto; border:0; border-radius:999px; padding:8px 16px; font-size:13px; font-weight:600; color:#fff;
  background:linear-gradient(135deg,var(--violet-3),var(--violet-2)); cursor:default; position:relative; overflow:hidden}
/* One-shot shimmer when phase 4 begins (item 8/2) — .dash__url no longer
   collides with the feed panel's own URL row (renamed .dash__feedurl below),
   so this is unambiguously the top-bar "Copy feed URL" button only. */
.dash__url.is-sweep::after{content:""; position:absolute; top:0; left:-70%; width:45%; height:100%;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.55),transparent);
  transform:skewX(-18deg); animation:dashUrlSweep 1.1s ease-out 1}
@keyframes dashUrlSweep{0%{left:-70%}100%{left:130%}}
.dash__body{display:grid; grid-template-columns:172px 1fr; align-items:stretch}
.dash__side{display:flex; flex-direction:column; gap:2px; padding:16px 12px; border-right:1px solid var(--line); background:var(--surface-2)}
.dash__brand{padding:4px 12px 14px; font-size:15px; font-weight:700; color:var(--ink); line-height:1.15}
.dash__brand b{display:block; font-size:11px; font-weight:600; letter-spacing:.04em; color:var(--muted)}
.dash__nav{display:flex; flex-direction:column; gap:2px}
.dash__nav span{padding:9px 12px; border-radius:var(--r-s); font-size:14px; color:var(--ink-2)}
.dash__side .is-on{background:var(--violet); color:#fff; font-weight:600}
.dash__quota{margin-top:16px; padding-top:12px; border-top:1px solid var(--line); display:flex; flex-direction:column; gap:8px}
.dash__qrow{display:flex; align-items:center; justify-content:space-between; padding:0 12px; font-size:12px}
.dash__qrow span{color:var(--muted)}
.dash__qrow b{color:var(--ink); font-weight:600}
.dash__plan{margin:14px 12px 0; padding:10px 12px; border:1px solid var(--line); border-radius:var(--r-m); display:flex; flex-direction:column; gap:2px}
.dash__plan span{font-size:11px; letter-spacing:.04em; color:var(--muted)}
.dash__plan b{font-size:14px; color:var(--ink); font-weight:700}
/* Client bug fix: the mock grew taller in phase 4 (comparison + feed panels
   both visible) than in phases 1-3, so the section jumped and everything
   below it shifted mid-animation. Reserve the tallest state's height
   (measured in-browser: 809.4px) up front so every phase renders inside a
   box that never changes size. */
/* Measured against the tallest phase (comparison + feed panel) after the
   action bar and channel row were removed: natural height now peaks at
   673px. Pinning it here keeps the mock a fixed box — the animation must
   never resize the section under the reader — with no dead space. */
.dash__main{padding:16px; min-width:0; min-height:673px}
.dash__row{display:flex; align-items:center; gap:10px; padding:10px 12px; border:1px solid var(--line); border-radius:var(--r-m); margin-bottom:8px; transition:background .3s ease, border-color .3s ease}
.dash__box{width:17px; height:17px; border:1.5px solid var(--line-strong); border-radius:5px; flex:none}
.dash__title{font-size:14px; flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  transition:opacity .11s ease, transform .11s ease}
/* Client bug fix: the title lengthening from thin to rich now lands as a
   visible fade+rise swap (~320ms) instead of a single-frame text change —
   see js/dashboard.js. */
.dash__title.is-swap{opacity:0; transform:translateY(-3px)}
.dash__row.is-sel{border-color:var(--violet-line); background:var(--violet-soft)}
.dash__row.is-sel .dash__box{background:var(--violet); border-color:var(--violet)}
/* Completed rows recede to a whisper of green; the OPTIMIZED panel keeps
   the full tint. Same hue, three levels of weight, so the focus is
   unambiguous instead of four things shouting equally. */
.dash__row.is-done{border-color:rgba(22,163,74,.18); background:rgba(22,163,74,.045)}
.dash__cmp{display:grid; grid-template-columns:1fr 1fr; gap:12px; margin-top:14px}
.dash__col{padding:14px; border:1px solid var(--line); border-radius:var(--r-m); min-width:0}
.dash__col b{font-size:11px; letter-spacing:.08em; color:var(--muted)}
.dash__col--new{border-color:var(--green); background:rgba(22,163,74,.13);
  box-shadow:0 0 0 3px rgba(22,163,74,.10)}
.dash__thin{color:var(--muted); margin:8px 0 0; font-size:14px}
.dash__rich{margin:8px 0 0; font-size:14px; font-weight:500}
.dash__act{display:flex; align-items:center; gap:14px; justify-content:center; margin-top:14px; padding:10px 16px; border-radius:999px; background:var(--ink); color:#fff; width:max-content; margin-inline:auto}
.dash__act button{border:0; border-radius:999px; padding:7px 16px; font-size:13px; font-weight:600; color:#fff; background:var(--violet); cursor:default}
.dash__sel{font-size:13px}

/* Task (item 4): the feed-URL panel — phase 4's payoff, previously unstyled
   raw stacked text. A distinct bordered panel with a violet-tinted glow so
   it reads as the RESULT, not another row in the list above it. */
.dash__feed{margin-top:14px; padding:20px 22px; border:1.5px solid var(--violet);
  border-radius:var(--r-l); background:linear-gradient(135deg,rgba(113,111,255,.14),rgba(168,85,247,.10));
  box-shadow:0 0 0 4px rgba(113,111,255,.10), 0 10px 30px -12px rgba(113,111,255,.5)}
.dash__feedhead{display:flex; align-items:center; gap:10px; font-size:13.5px; font-weight:500; color:var(--ink); margin-bottom:14px}
.dash__live{display:inline-flex; align-items:center; gap:6px; padding:4px 10px 4px 8px; border-radius:999px;
  background:var(--green-soft); border:1px solid var(--green-line); color:var(--green);
  font-family:"JetBrains Mono",monospace; font-size:10.5px; font-weight:700; letter-spacing:.08em; text-transform:uppercase}
.dash__live::before{content:""; width:6px; height:6px; border-radius:50%; background:var(--green); flex:none;
  animation:dashLivePulse 1.8s ease-in-out infinite}
@keyframes dashLivePulse{0%,100%{opacity:1; box-shadow:0 0 0 0 var(--green-soft)}50%{opacity:.55; box-shadow:0 0 0 4px var(--green-soft)}}
/* The URL row is the hero of the panel — a bordered, slightly inset field so
   it reads as a real, copyable value rather than white-on-violet (item 8:
   this used to collide with .dash__url, the top-bar button's own class). */
.dash__feedurl{display:flex; align-items:center; gap:10px; padding:12px 14px; border:1px solid var(--line-strong);
  border-radius:var(--r-m); background:var(--surface-2); box-shadow:inset 0 1px 2px rgba(23,22,31,.05)}
.dash__feedurl code{flex:1; min-width:0; font-family:"JetBrains Mono",monospace; font-size:12.5px; color:var(--ink-2);
  white-space:nowrap; overflow-x:auto}
.dash__copy{flex:none; border:1px solid var(--line-strong); background:var(--surface); color:var(--ink-2);
  font-size:12px; font-weight:600; padding:6px 13px; border-radius:999px; cursor:default;
  transition:border-color .25s ease, color .25s ease}
.dash__chan{display:flex; flex-wrap:wrap; gap:8px; margin-top:14px}
.dash__chan span{display:inline-flex; align-items:center; gap:6px; padding:5px 11px; border-radius:999px;
  font-size:11.5px; font-weight:600; color:var(--muted); background:var(--band); border:1px solid var(--line);
  filter:grayscale(1); opacity:.55;
  transition:opacity .3s ease, filter .3s ease, background .3s ease, border-color .3s ease, color .3s ease}
.dash__chan span svg{width:13px; height:13px; flex:none}
.dash__chan span.is-on{filter:none; opacity:1; color:var(--violet); background:var(--violet-soft); border-color:var(--violet-line)}
.dash__feednote{margin-top:12px; font-size:11.5px; color:var(--muted)}

/* Mobile overrides come AFTER the component they modify — see Global Constraints */
@media (max-width:760px){
  .dash__body{grid-template-columns:1fr}
  .dash__side{flex-direction:row; overflow-x:auto; border-right:0; border-bottom:1px solid var(--line)}
  .dash__nav{flex-direction:row}
  .dash__nav span{white-space:nowrap}
  /* 673px was measured for the desktop layout and left a screenful of empty
     card under four rows on a phone. Removing the floor outright was worse:
     the mock then grew and shrank as the animation swapped panels. Measured
     for the mobile layout's tallest state instead. */
  /* Height has to be constant or the page jumps under the reader's thumb,
     but nothing can be removed to achieve it — hiding the product rows once
     the comparison appeared took away the very thing being demonstrated.
     A real app screen solves this by scrolling inside a fixed frame, so the
     mock does the same: fixed height, its own scroll, and dashboard.js walks
     it to whichever panel just opened. */
  .dash__main{height:560px; min-height:0; overflow-y:auto; scroll-behavior:smooth;
    scrollbar-width:none; -webkit-overflow-scrolling:touch}
  .dash__main::-webkit-scrollbar{display:none}

  /* The panels are toggled with [hidden], so they cannot transition — they
     appeared in a single frame, which is the "pop". A short entrance on the
     way in gives the eye something to follow down a narrow column. */
  .dash__cmp:not([hidden]),
  .dash__feed:not([hidden]){animation:dashPanelIn .34s ease both}

  /* Four steps in four columns at this width gives four unreadable labels,
     so the visitor cannot tell what the animation is doing. Only the active
     step keeps its label; the rest fall back to dots. */
  .dash-steps{gap:8px; align-items:center}
  .dash-steps__i{flex:0 0 auto}
  .dash-steps__i .dash-steps__lbl{display:none}
  .dash-steps__i.is-active{flex:1 1 auto; flex-direction:row; gap:9px}
  .dash-steps__i.is-active .dash-steps__lbl{display:block; max-width:none; text-align:left;
    font-size:13px; font-weight:600; color:var(--ink)}
  .dash-steps__i:not(:last-child)::after{display:none}
  /* The app's own nav row carries nothing on a phone — the visitor is not
     navigating this mock, he is watching one screen of it work. It was cut
     mid-word anyway, so it goes rather than scrolls. */
  .dash__side{display:none}
  .dash__brand{display:none}
  .dash__quota{display:none}
  .dash__plan{display:none}
  .dash__cmp{grid-template-columns:1fr}
  .dash__id,.dash__prev{display:none}
  .dash__row{gap:8px; padding:9px 10px}
  /* the URL wraps/scrolls inside its own container rather than forcing the
     card wider (spec: item 4, mobile). */
  .dash__feed{padding:16px}
  .dash__feedurl{flex-wrap:wrap}
  .dash__feedurl code{white-space:nowrap; overflow-x:auto; -webkit-overflow-scrolling:touch; max-width:100%}
  .dash__copy{margin-inline-start:auto}
}

/* Note: the old thin proof strip (.proof__in/.faces/.proof__stars/.proof__txt)
   is gone — that content now lives inside the hero, using base's .proof/.avs
   component (spec §1). Section 5's own richer social-proof band is below. */

/* Task 1: before/after side-by-side comparison (replaces the old toggle —
   both states are always visible now, no switch). .tag/.tag--red/.tag--green
   and .pos stay: the case study's browser-chrome rows and the SERP mock
   below still use them. */
.tag{font-size:12px; font-weight:600; padding:5px 12px; border-radius:999px}
.tag--red{background:var(--red-soft); color:var(--red)}
.tag--green{background:var(--green-soft); color:var(--green)}
.pos{font-size:13px; color:var(--muted)}

/* .tgl__sw — still used by the checkout dialog's monthly/yearly billing
   switch (#cyc); the before/after switch that used to share this class was
   removed with the toggle. */
.tgl__sw{width:52px; height:30px; border-radius:999px; border:0; background:var(--line-strong); cursor:pointer; padding:3px; display:flex}
.tgl__sw i{width:24px; height:24px; border-radius:50%; background:#fff; transition:transform .22s ease}
.tgl__sw.is-on{background:var(--green)}
.tgl__sw.is-on i{transform:translateX(22px)}

/* ── Before / After product record ─────────────────────────────────────
   Round 6. Two earlier attempts failed for the same reason: this block was
   dressed as a Google Shopping *result* (price, rating, "5/5 words" badge)
   while the section it lives in argues about the *record*. A word-match
   score only means something under a visible query — and the visible query
   is in the SERP mock further down, which is where the result belongs.
   So each side is now the product record itself: photo, title, description,
   highlights. What the AI touched is what the eye compares. */
.ba{
  display:grid; grid-template-columns:1fr auto 1fr; gap:clamp(16px,2.6vw,38px);
  align-items:stretch; margin-top:8px;
}
.ba__side{display:flex; flex-direction:column; align-items:center; gap:14px; min-width:0}

.ba__lbl{
  display:inline-flex; align-items:center; gap:8px;
  font-size:11.5px; font-weight:700; letter-spacing:.08em; text-transform:uppercase;
  padding:5px 13px; border-radius:999px;
  border:1px solid var(--line-strong); background:var(--surface); color:var(--muted);
  /* The AFTER label carries a 20px wordmark and the BEFORE label carries
     11.5px text; without a floor the two sides start 2px apart. */
  min-height:32px;
}
/* The wordmark ships at its natural 1000x300; without an explicit height it
   renders at intrinsic size and detonates the section. */
.ba__lbl img{height:20px; width:auto; display:block}
.ba__lbl--bad{border-color:var(--red-line); background:var(--red-soft); color:var(--red)}
.ba__lbl--ok{border-color:var(--violet-line); background:var(--violet-soft); color:var(--violet)}

/* ---- the record card ---- */
.prec{
  position:relative; width:100%; max-width:360px; margin:0 auto; flex:1 1 auto;
  border:1px solid var(--line); border-radius:var(--r-l); background:var(--surface);
  padding:16px 16px 18px; display:flex; flex-direction:column; gap:14px;
}
.prec--win{
  border-color:var(--violet-line);
  background:linear-gradient(180deg,var(--violet-soft),transparent 42%),var(--surface);
  box-shadow:0 0 0 1px var(--violet-line) inset, 0 30px 60px -24px rgba(113,111,255,.45);
}
/* The left card carries the same badge slot as the right one — without it
   every field below sat 30px higher than its counterpart. */
.prec__pill--thin{background:var(--band) !important; color:var(--muted) !important;
  border:1px solid var(--line-strong); box-shadow:none !important; font-weight:600}
.prec__pill{
  align-self:flex-start; margin:-2px 0 -4px; border:1px solid transparent;
  display:inline-flex; align-items:center; gap:6px; white-space:nowrap;
  background:linear-gradient(135deg,#6E6BFF,#A855F7); color:#fff;
  font-size:11px; font-weight:700; padding:5px 13px; border-radius:999px;
  box-shadow:0 8px 18px rgba(113,111,255,.4);
}
.prec__ph{
  height:150px; background:#fff; border:1px solid var(--line); border-radius:var(--r-m);
  display:grid; place-items:center; overflow:hidden; padding:12px;
}
.prec__ph img{width:100%; height:100%; max-height:100%; min-height:0;
  align-self:stretch; justify-self:stretch; object-fit:contain}

.prec__f{display:flex; flex-direction:column; gap:6px; min-width:0}
.prec__k{
  display:flex; align-items:center; gap:6px;
  font-size:10.5px; font-weight:700; letter-spacing:.09em; text-transform:uppercase;
  color:var(--muted);
}
.prec__k b{font-weight:700; letter-spacing:0; text-transform:none; font-size:11.5px; color:var(--ink-2)}
/* The green tick is the "the AI filled this one" marker — drawn, not an
   emoji, so it keeps its weight against the label's letter-spacing. */
.prec__k--ok::before{
  content:""; flex:0 0 auto; width:13px; height:13px; border-radius:50%;
  background:var(--green);
  -webkit-mask:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><path d='M2.6 6.2l2.2 2.2 4.6-4.6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>") center/contain no-repeat,
    linear-gradient(#000,#000);
  -webkit-mask-composite:xor; mask-composite:exclude;
}
.prec__k--ok{color:var(--green)}

/* Both records reserve the same height per field, so Title, Description and
   Product highlights start at the same y in either card. Expressed in em
   (2 lines of title, 4 of description) rather than pixels, so the alignment
   survives a font-size change. The empty space this leaves on the left is
   the point: the field exists, nobody filled it. */
.prec__t{margin:0; font-size:14.5px; line-height:1.4; color:var(--ink); font-weight:600;
  min-height:2.8em}
.prec__d{margin:0; font-size:12.5px; line-height:1.5; color:var(--ink-2); min-height:6em}
.prec__d--thin{color:var(--muted)}

.prec__hl{display:flex; flex-wrap:wrap; align-items:center; gap:6px}
.prec__hl span{
  font-size:11px; font-weight:600; padding:4px 9px; border-radius:999px;
  background:var(--violet-soft); border:1px solid var(--violet-line); color:var(--violet);
}
.prec__hl i{width:52px; height:23px; border-radius:999px;
  border:1px dashed var(--line-strong); display:block}
/* The "before" record has two of five filled: real chips, but muted, so the
   contrast with the right-hand card is content and not colour alone. */
.prec__hl--thin span{background:var(--band); border-color:var(--line-strong); color:var(--muted)}

/* ---- the middle: the AI pass, animated ---- */
.ba__mid{
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px;
  max-width:184px; text-align:center;
}
.ba__orb{
  width:38px; height:38px; border-radius:50%; display:grid; place-items:center;
  font-size:17px; color:#fff; background:linear-gradient(135deg,#6E6BFF,#A855F7);
  box-shadow:0 10px 26px -6px rgba(113,111,255,.6);
  animation:baOrb 2.4s ease-in-out infinite;
}
@keyframes baOrb{0%,100%{transform:scale(1)} 50%{transform:scale(1.09)}}
/* A spark travelling left-to-right is the only thing on screen that states
   the direction of the transformation now that the arrow is gone. */
.ba__rail{position:relative; width:100%; height:2px; border-radius:2px;
  background:linear-gradient(90deg,var(--line),var(--violet-line),var(--line)); overflow:hidden}
.ba__rail i{position:absolute; top:-2px; left:0; width:26px; height:6px; border-radius:3px;
  background:linear-gradient(90deg,transparent,var(--violet),transparent);
  animation:baSpark 2.4s linear infinite}
@keyframes baSpark{from{transform:translateX(-30px)} to{transform:translateX(184px)}}

.ba__midl{margin:0; font-size:12px; font-weight:700; letter-spacing:.06em;
  text-transform:uppercase; color:var(--violet)}
/* Four passes cycling in one slot: stacked absolutely so the rail above them
   never shifts as the captions change length. */
.ba__pass{list-style:none; margin:0; padding:0; position:relative; width:100%; height:34px}
.ba__pass li{
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  font-size:11.5px; line-height:1.35; color:var(--muted); opacity:0;
  /* One inline wrapper per caption: a flex container turns each run of text
     into an anonymous item and drops the whitespace between them, which is
     what rendered "Filling0 → 5highlights". */
  animation:baPass 9.6s ease-in-out infinite;
}
.ba__pass li>span{display:block; text-align:center}
.ba__pass li b{color:var(--ink); font-weight:700; white-space:nowrap}
.ba__pass li:nth-child(1){animation-delay:0s}
.ba__pass li:nth-child(2){animation-delay:2.4s}
.ba__pass li:nth-child(3){animation-delay:4.8s}
.ba__pass li:nth-child(4){animation-delay:7.2s}
@keyframes baPass{0%{opacity:0; transform:translateY(5px)} 4%,21%{opacity:1; transform:none} 25%,100%{opacity:0}}

@media (prefers-reduced-motion:reduce){
  .ba__orb,.ba__rail i{animation:none}
  .ba__pass{height:auto; display:flex; flex-direction:column; gap:5px}
  .ba__pass li{position:static; animation:none; opacity:1}
}

/* ── The causal chain under the SERP mock ──────────────────────────────
   This slot used to hold three big percentages (+35 / +22 / +18) with a
   hedge underneath saying they were "typical ranges merchants report".
   A figure that needs a disclaimer to survive is not doing any work, and
   the client read them exactly as what they were — unsourced. A mechanism
   needs no footnote: it explains *why* the product pays for itself, and
   every link here is one we can defend. Note the wording of link four:
   Shopping has no Quality Score and no keywords, so "cheaper CPC" would be
   the same unkeepable promise as the #1 rank. "More clicks, same spend" is
   the benefit the merchant actually feels, and it is true. */
.chain{margin-top:48px}
.chain__h{margin:0 0 30px; text-align:center; font-size:clamp(19px,2.2vw,26px);
  font-weight:500; letter-spacing:-.015em; color:var(--ink)}

.chain__row{list-style:none; margin:0; padding:0; display:grid;
  grid-template-columns:repeat(5,1fr); gap:12px; align-items:stretch}
.chain__n{position:relative; display:flex; flex-direction:column; align-items:center;
  text-align:center; gap:12px; padding:18px 12px 18px;
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r-l)}
/* The arrow lives between two nodes, drawn in the gap rather than inside a
   cell, so it never steals width from the text. */
.chain__n:not(:last-child)::after{
  content:""; position:absolute; top:50%; right:-12px; width:12px; height:1.5px;
  background:var(--violet-line); z-index:1}
.chain__n:not(:last-child)::before{
  content:""; position:absolute; top:50%; right:-12px; transform:translate(0,-3.5px);
  border-left:6px solid var(--violet); border-top:4px solid transparent;
  border-bottom:4px solid transparent; z-index:2}
/* The labels alone have to read as the argument — record, visibility,
   clicks, sales, ROAS — because that line is what a scanner takes away.
   The sub-line under each carries the mechanism, which is where the honesty
   lives: "the visitor found what they typed" rather than a promised lift. */
.chain__n b{font-size:14px; font-weight:700; color:var(--ink); line-height:1.3}
.chain__n small{font-size:11.5px; line-height:1.45; color:var(--muted); max-width:26ch}

/* Five stacked paragraphs of 11.5px grey is a wall, not a diagram — the
   client could not read it. Each link now carries a drawn miniature of the
   idea and a label, nothing else. SVG rather than generated raster: these
   are charts and interfaces, they have to stay crisp at any size and follow
   the light/dark tokens, which a flat image cannot do. */
.chain__vis{display:block; width:100%; max-width:150px; margin-bottom:2px}
.chain__vis svg{display:block; width:100%; height:auto}
.cf-card{fill:var(--surface); stroke:var(--line-strong); stroke-width:1.4}
.cf-card--hit{fill:var(--violet-soft); stroke:var(--violet)}
.cf-ink{fill:var(--ink)}
.cf-mute{fill:var(--line-strong)}
.cf-fill{fill:var(--violet)}
.cf-chip{fill:var(--violet-soft); stroke:var(--violet-line); stroke-width:1.2}
.cf-chip--dim{fill:var(--band); stroke:var(--line-strong)}
.cf-stroke{fill:none; stroke:var(--muted); stroke-width:1.6; stroke-linecap:round}
.cf-stroke--up{stroke:var(--violet); stroke-width:1.8}
.cf-dash{fill:none; stroke:var(--line-strong); stroke-width:1.2; stroke-dasharray:3 3}
.cf-cursor{fill:var(--ink)}
.cf-budget{fill:var(--band); stroke:var(--line-strong); stroke-width:1.2}
.cf-dot{fill:var(--violet)}
.cf-dot--old{fill:var(--line-strong)}
.cf-bar--flat{fill:var(--line-strong)}
.cf-bar--win{fill:var(--violet)}
.cf-funnel{fill:var(--band); stroke:var(--line-strong); stroke-width:1.6; stroke-linejoin:round}
.cf-lbl{font-size:7.5px; font-weight:700; letter-spacing:.09em; fill:var(--muted)}
.cf-lbl--win{fill:var(--violet)}
.chain__n--end{border-color:var(--violet-line);
  background:linear-gradient(180deg,var(--violet-soft),transparent 60%),var(--surface);
  box-shadow:0 0 0 1px var(--violet-line) inset, 0 24px 50px -26px rgba(113,111,255,.5)}
.chain__n--end b{color:var(--violet)}

/* The whole argument is that the spend does NOT change, so the budget is
   drawn as one continuous rail under every link rather than said in words. */
.chain__rail{position:relative; margin-top:14px; padding-top:16px; text-align:center}
.chain__railb{position:absolute; top:0; left:2%; right:2%; height:2px; border-radius:2px;
  background:repeating-linear-gradient(90deg,var(--line-strong) 0 7px,transparent 7px 13px)}
.chain__raill{font-size:11.5px; font-weight:600; letter-spacing:.04em; color:var(--muted)}
.chain__note{margin:18px 0 0; text-align:center; font-size:12px; color:var(--muted)}

/* Links resolve left to right, so the eye reads the causality as a sequence
   rather than as five cards that happen to sit in a row. */
.chain.is-in .chain__n{animation:chainIn .5s cubic-bezier(.2,.8,.3,1) both;
  animation-delay:calc(var(--i) * .16s)}
@keyframes chainIn{from{opacity:0; transform:translateY(10px)} to{opacity:1; transform:none}}
@media (prefers-reduced-motion:reduce){ .chain.is-in .chain__n{animation:none} }

@media (max-width:1040px){
  .chain__row{grid-template-columns:repeat(2,1fr); gap:12px 26px}
  /* Arrows only make sense along a single row; at two columns the reading
     order wraps and a right-pointing arrow would lie about it. */
  .chain__n::after,.chain__n::before{display:none}
  .chain__n--end{grid-column:1 / -1}
}
@media (max-width:560px){
  .chain__row{grid-template-columns:1fr}
  /* A flex row put the drawing, the label and the sub-line in three columns,
     which squeezed the sentence into a four-line ribbon. Grid instead, with
     the drawing spanning both rows, so label and sub-line share the whole
     remaining width. There is no wrapper element around the two of them —
     the grid supplies the grouping the markup does not have. */
  .chain__n{display:grid; grid-template-columns:100px minmax(0,1fr);
    align-items:center; column-gap:15px; row-gap:3px; padding:16px; text-align:left}
  .chain__vis{grid-column:1; grid-row:1 / span 2; margin:0; max-width:100px}
  .chain__n b{grid-column:2; grid-row:1; align-self:end}
  .chain__n small{grid-column:2; grid-row:2; align-self:start; max-width:none}
}

/* Part 6: the bridging line between the product record and the SERP mock
   was a single muted caption doing a heading's job — promoted to a real
   .sec-head (eyebrow + h2 + sub), same treatment as every other section
   head on the page, so the SERP mock beneath it reads as its answer.
   Only the top spacing needs a page-specific tweak (this sec-head sits
   mid-section, not at the section's own top). */
.problem-bridge{margin-top:44px}

@media (max-width:760px){
  .ba{grid-template-columns:1fr; gap:18px}
  /* Stacked: the middle sits between the two cards, so the rail reads as a
     downward hand-off instead of a sideways one. */
  .ba__mid{max-width:none; width:100%; padding-top:0}
  .ba__rail{max-width:220px}
  @keyframes baSpark{from{transform:translateX(-30px)} to{transform:translateX(220px)}}
}

/* Task: Part 3 — Google Shopping SERP mock, ported from lp/feedscore/index.html
   (.gserp/.g-bar/.g-input/.g-tabs/.g-terms/.g-cards/.gcard family, plus the
   .qtok/.kmatch/.hl helpers it depends on). Wired to the existing #pstoggle
   switch via [data-state] — see js/stepper.js. */
.gserp{margin-top:26px; background:var(--surface); border:1px solid var(--line); border-radius:var(--r-l); box-shadow:var(--shadow-l); overflow:hidden}
.g-bar{display:flex; align-items:center; gap:14px; padding:16px 20px}
.g-input{
  flex:1; height:44px; border-radius:99px; border:1px solid var(--line-strong); background:var(--bg);
  display:flex; align-items:center; gap:10px; padding:0 18px; font-size:14.5px; color:var(--ink);
  box-shadow:0 1px 5px rgba(23,22,31,.06);
}
.g-input .mic{margin-left:auto; display:flex; gap:12px; color:var(--muted)}
.g-input .mic svg{width:15px; height:15px}
.g-tabs{display:flex; gap:24px; padding:0 22px; border-bottom:1px solid var(--line)}
.g-tabs span{padding:11px 0; font-size:13.5px; color:var(--muted); position:relative; display:flex; align-items:center; gap:6px}
.g-tabs span svg{width:14px; height:14px}
.g-tabs span.on{color:#1A73E8; font-weight:600}
.g-tabs span.on::after{content:""; position:absolute; left:0; right:0; bottom:-1px; height:3px; background:#1A73E8; border-radius:3px 3px 0 0}
.g-terms{display:flex; align-items:center; gap:8px; flex-wrap:wrap; padding:14px 22px 0; font-size:12px; color:var(--muted)}
.g-terms b{font-weight:600; color:var(--ink-2)}
.qtok{padding:3px 9px; border-radius:6px; font-size:11.5px; font-weight:600; color:#5A4708; background:rgba(255,205,80,.55); border:1px solid rgba(233,162,27,.4)}
/* The matched-word chips light up one by one on entry (item 2) — .gserp
   already carries .reveal, so this reuses that same mechanism instead of a
   second observer. */
.gserp.reveal .qtok{opacity:0; transform:translateY(6px); transition:opacity .4s ease, transform .4s ease}
.gserp.reveal.is-in .qtok{opacity:1; transform:none}
.gserp.reveal.is-in .qtok:nth-of-type(1){transition-delay:60ms}
.gserp.reveal.is-in .qtok:nth-of-type(2){transition-delay:150ms}
.gserp.reveal.is-in .qtok:nth-of-type(3){transition-delay:240ms}
.gserp.reveal.is-in .qtok:nth-of-type(4){transition-delay:330ms}
.gserp.reveal.is-in .qtok:nth-of-type(5){transition-delay:420ms}
.g-body{padding:18px 22px 22px}
.g-spon{display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted); margin-bottom:14px}
.g-spon b{color:var(--ink-2); font-weight:600}
/* Four results now, including the thin control in third place — a rigid
   3-column grid pushed the fourth onto its own row where it read as an
   afterthought and clipped. Auto-fit keeps them on one row when there is
   room and wraps evenly when there is not. */
.g-cards{display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); justify-content:center; gap:12px}
@media (max-width:1080px){ .g-cards{grid-template-columns:repeat(2,minmax(0,290px))} }
@media (max-width:620px){ .g-cards{grid-template-columns:minmax(0,290px)} }
.gcard{width:100%; max-width:290px; border:1px solid var(--line); border-radius:var(--r-m); overflow:hidden; background:var(--bg); display:flex; flex-direction:column; transition:box-shadow .3s ease}
.gcard:hover{box-shadow:var(--shadow-m)}
/* Client correction (round 4): the photo block was rendering ~300px tall —
   roughly double a real Google Shopping result. aspect-ratio stays for the
   graceful shrink on very narrow cards, but max-height pins it to ~176px
   across the whole 250-290px card-width target, matching lp/feedscore. The
   svg is capped well under the box so today's placeholder icon sits in
   white space instead of bleeding to the edges; a future real
   <img src="/assets/products/....jpg"> drops into the same box/padding with
   object-fit:contain and needs no layout change. */
/* Fixed height, no aspect-ratio: the two together made the box compute at
   232px and then clip to 176, cropping the product. */
.gcard .ph{height:176px; background:#fff; overflow:hidden; display:grid; place-items:center; position:relative; padding:16px}
.gcard .ph svg{width:78%; height:78%}
.gcard .ph img{width:100%; height:100%; max-height:100%; align-self:stretch; justify-self:stretch; min-height:0; object-fit:contain}
.kmatch{
  position:absolute; top:8px; right:8px; z-index:3; padding:3px 8px; border-radius:99px;
  font-family:"JetBrains Mono",monospace; font-size:10.5px; font-weight:600; letter-spacing:-.02em;
  box-shadow:0 3px 10px rgba(23,22,31,.18); white-space:nowrap; color:#fff;
}
.kmatch.ok{background:var(--green)}
.kmatch.bad{background:var(--red)}
.gcard .meta{padding:11px 12px 13px; display:flex; flex-direction:column; gap:6px}
.gtitle{font-size:13px; line-height:1.38; color:var(--glink); font-weight:500}
.hl{background:rgba(255,205,80,.62); border-radius:3px; padding:0 2px; color:#17161F; font-weight:600}
.gprice{font-size:15px; font-weight:700; color:var(--ink); font-family:"JetBrains Mono",monospace; letter-spacing:-.04em}
.gshop{font-size:12px; color:var(--muted)}
.grate{display:flex; align-items:center; gap:5px; font-size:12px; color:var(--muted)}
.grate .s{color:#E9A21B; letter-spacing:-1.5px; font-size:12px}
.gfree{font-size:12px; color:var(--green); font-weight:600}
.gcard .pos{font-size:11px}

@media (max-width:600px){
  .g-tabs{gap:16px; padding:0 16px}
  .g-tabs span:nth-child(4),.g-tabs span:nth-child(5){display:none}
  .g-bar{padding:13px 14px; gap:10px}
  .g-body{padding:14px 16px 18px}
  .g-terms{padding:11px 16px 0}
  .g-spon{margin-bottom:10px}
  .g-cards{grid-template-columns:repeat(2,minmax(0,290px)); justify-content:center}
  .g-cards>*:nth-child(3){display:none}
}

.ctamid{padding-block:60px}
.ctamid__in{display:flex; flex-direction:column; align-items:center; text-align:center}
/* .stars comes from base-v6.css (em+small, spec: reconcile in favour of
   base) — reviews and the socialproof rating block both use that exact
   markup now, so no page-specific override is needed here any more. */
.revs-head{display:flex; flex-wrap:wrap; align-items:center; justify-content:center; gap:14px 28px; margin-bottom:34px; padding:20px 26px; border:1px solid var(--line); border-radius:var(--r-l); background:var(--surface)}
.revs-head__score{display:flex; align-items:baseline; gap:8px}
.revs-head__score b{font-size:34px; font-weight:700; font-family:"JetBrains Mono",monospace}
.revs-head__score span{color:var(--muted); font-size:13px}

/* Part 1 rebuild: six taller identification-story cards instead of ten
   thin quote cards — a two-column grid at desktop (three columns would
   cram cards built to be read, not skimmed), one column on mobile. An
   ambient halo behind the grid, same technique as .faq-wrap/.faq-glow. */
.revs-wrap{position:relative}
.revs-wrap .faq-glow{inset:-6% 10% auto; height:62%; opacity:.35}
.revs{display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:18px; align-items:start}
/* Gentle stagger on entry rather than all six cards animating at once
   (item 2 — restraint rule: never two animated things in one viewport). */
.revs.reveal .revcard{opacity:0; transform:translateY(12px); transition:opacity .5s ease, transform .5s ease}
.revs.reveal.is-in .revcard{opacity:1; transform:none}
.revs.reveal .revcard:nth-child(1){transition-delay:0ms}
.revs.reveal .revcard:nth-child(2){transition-delay:80ms}
.revs.reveal .revcard:nth-child(3){transition-delay:160ms}
.revs.reveal .revcard:nth-child(4){transition-delay:240ms}
.revs.reveal .revcard:nth-child(5){transition-delay:320ms}
.revs.reveal .revcard:nth-child(6){transition-delay:400ms}

/* Each card: header (photo + identity, specific enough to be one real
   business), the problem stated with a number, the mechanical change, and
   a visually separated result strip — the card's payoff. */
.revcard{margin:0; padding:26px; min-width:0; display:flex; flex-direction:column; gap:14px}
.revcard__head{display:flex; align-items:flex-start; gap:12px}
.revcard__head .avhead--lg{margin-top:1px}
.revcard__id{display:flex; flex-direction:column; gap:2px; min-width:0}
.revcard__id b{font-size:15px}
.revcard__id small{color:var(--ink-2); font-size:12.5px; line-height:1.3}
.revcard__meta{color:var(--muted)!important; font-size:11.5px}
.revcard__verified{align-self:flex-start; margin-inline-start:auto; flex:none; font-size:10.5px; font-weight:600; color:var(--green); background:var(--green-soft); border:1px solid var(--green-line); border-radius:999px; padding:3px 9px; white-space:nowrap}
.revcard p{font-size:14.5px; margin:0; line-height:1.55}
.revcard__problem{color:var(--ink)}
.revcard__change{font-size:13px; color:var(--ink-2); line-height:1.5}
.revcard__change-l{color:var(--violet); font-weight:700}
.revcard__result{display:flex; flex-wrap:wrap; gap:8px 20px; padding:14px 16px; border-radius:var(--r-m);
  background:var(--violet-soft); border:1px solid var(--violet-line)}
.revcard__result .mono{display:inline-flex; align-items:center; gap:6px; font-size:14.5px; font-weight:700; color:var(--violet)}
.revcard__result .mono i{font-style:normal; color:var(--muted); font-weight:400}
.revcard__foot{display:flex; align-items:center; padding-top:4px; border-top:1px solid var(--line); margin-top:2px}
.revcard__platform{font-size:11px; color:var(--muted); font-weight:600; letter-spacing:.02em}
.disclaim{text-align:center; color:var(--muted); font-size:13px; margin-top:24px}

@media (max-width:900px){
  .revs{grid-template-columns:1fr}
}

/* Social proof (spec §5, rebuilt round 4 — client: "pas lisible, refait et
   améliore, ça doit être visuel à fond"). Three composed rows instead of a
   flat heading + pills + scattered rating line, built from vocabulary that
   already exists in base-v6.css:
   - .sp-channels: the platform logos are the visual anchor, so .shopbadge
     is enlarged into its own generously-spaced row instead of small pills.
   - .sp-stats: the rating and store count become large JetBrains Mono
     display figures, each boxed in a reskinned .tile "stat tile" card.
   - .sp-trust-grid: the three trust markers move off the scattered .tile
     chip row and into their own delineated card strip using .checks/.chk
     (icon + label, no description slot needed).
   - .sp-wrap + .faq-glow: an ambient halo behind the block, same technique
     as .faq-wrap/.faq-glow, so the section doesn't sit on flat ground. */
.sp-wrap{position:relative}
.sp-wrap .faq-glow{inset:-8% 8% auto; height:68%; opacity:.4}

.sp-channels{display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:14px 20px; margin-bottom:48px}
.sp-channels .shopbadge{margin-top:0; font-size:15px; font-weight:600; padding:13px 24px; border-radius:14px; gap:10px; box-shadow:var(--shadow-s); transition:transform .25s ease, box-shadow .25s ease}
.sp-channels .shopbadge svg{width:20px; height:20px}
.sp-channels .shopbadge:hover{transform:translateY(-3px); box-shadow:var(--shadow-m)}

.sp-stats{display:flex; flex-wrap:wrap; justify-content:center; gap:18px; margin-bottom:48px}
.sp-stats .tile{width:auto; height:auto; opacity:1; filter:none; overflow:visible;
  display:flex; flex-direction:column; align-items:center; gap:8px; text-align:center;
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r-l);
  box-shadow:var(--shadow-s); padding:28px 46px; min-width:210px;
  transition:transform .25s ease, box-shadow .25s ease}
.sp-stats .tile:hover{transform:translateY(-3px); box-shadow:var(--shadow-m)}
/* base's .tile b is a small absolutely-positioned, opacity:0 count-badge
   (class+type = 0,1,1) — it outranks a bare .sp-stat__n (0,1,0), so the
   figure needs the ancestor-qualified selector below to actually win. */
.sp-stats .tile b.sp-stat__n{
  position:static; opacity:1; transform:none; background:none; padding:0;
  font-size:clamp(38px,4.4vw,50px); font-weight:700; letter-spacing:-.03em; line-height:1; color:var(--ink)
}
.sp-stats .stars{font-size:15px}
.sp-stat__cap{font-size:13px; color:var(--muted)}

.sp-trust-grid{max-width:900px; margin:0 auto; grid-template-columns:repeat(3,1fr)}
.sp-trust-grid .chk-h{padding:20px 22px}
.sp-trust-grid .chk-ic{background:var(--green-soft); border-color:var(--green-line); color:var(--green)}
.sp-trust-grid .chk-t{font-size:14.5px}

@media (max-width:640px){
  .sp-channels{gap:10px 14px; margin-bottom:36px}
  .sp-channels .shopbadge{font-size:13px; padding:10px 16px; border-radius:12px}
  .sp-channels .shopbadge svg{width:16px; height:16px}
  .sp-stats{gap:12px; margin-bottom:36px}
  .sp-stats .tile{padding:22px 24px; min-width:150px}
  .sp-stat__n{font-size:34px}
  .sp-trust-grid{grid-template-columns:1fr}
}

/* Channel-sync section (spec Part 8) — ported from lp/upsell/index.html's
   #auto ("One connection, every channel"): a live rotating status pill, then
   a 5-tile channel grid, each with its own logo, name and a green SYNCED
   badge. Wired to lp1's own .reveal/.is-in mechanism instead of the source's
   .fx-s/.in — .chg.is-in replaces every .chg.in selector below. */
.chg{max-width:1000px; margin:0 auto}
.ch-evt{display:flex; align-items:center; gap:10px; width:fit-content; margin:0 auto 26px;
  padding:9px 16px; border-radius:99px; background:var(--surface); border:1px solid var(--line);
  box-shadow:var(--shadow-s); font-size:13px; font-weight:600; color:var(--ink-2)}
.ch-evt i{width:7px; height:7px; border-radius:50%; background:var(--green); flex:none; animation:blip 1.5s ease-in-out infinite}
.ch-evt em{font-style:normal; font-family:"JetBrains Mono",monospace; font-size:11.5px; color:var(--muted)}
.ch-evt span{position:relative; display:inline-grid}
.ch-evt span b{grid-area:1/1; font-weight:600; opacity:0}
.chg.is-in .ch-evt span b{animation:chEvt 16s infinite}
.chg.is-in .ch-evt span b:nth-child(2){animation-delay:4s}
.chg.is-in .ch-evt span b:nth-child(3){animation-delay:8s}
.chg.is-in .ch-evt span b:nth-child(4){animation-delay:12s}
@keyframes chEvt{0%{opacity:0; transform:translateY(5px)}3%{opacity:1; transform:none}22%{opacity:1; transform:none}25%{opacity:0; transform:translateY(-5px)}100%{opacity:0}}
@keyframes blip{0%,100%{opacity:1}50%{opacity:.25}}

.ch-row{display:grid; grid-template-columns:repeat(5,1fr); gap:14px}
.ch-tile{display:flex; flex-direction:column; align-items:center; gap:11px; padding:24px 14px 18px;
  border-radius:var(--r-l); background:var(--surface); border:1.5px solid var(--line);
  box-shadow:var(--shadow-s); transition:border-color .5s ease, box-shadow .5s ease, transform .5s cubic-bezier(.2,.8,.3,1)}
.chg.is-in .ch-tile{animation:chPulse 6s cubic-bezier(.4,0,.2,1) infinite}
.chg.is-in .ch-tile.c2{animation-delay:.22s}
.chg.is-in .ch-tile.c3{animation-delay:.44s}
.chg.is-in .ch-tile.c4{animation-delay:.66s}
.chg.is-in .ch-tile.c5{animation-delay:.88s}
@keyframes chPulse{
  0%,100%{border-color:var(--line); box-shadow:var(--shadow-s); transform:none}
  8%{border-color:var(--violet-line); box-shadow:0 10px 30px rgba(113,111,255,.2); transform:translateY(-4px)}
  30%{border-color:var(--green-line); box-shadow:0 8px 24px rgba(22,163,74,.14); transform:translateY(-2px)}
  55%{border-color:var(--line); box-shadow:var(--shadow-s); transform:none}}
.ch-logo{width:44px; height:44px; display:grid; place-items:center; flex:none}
.ch-logo svg{width:36px; height:36px}
.ch-tile b{font-size:13.5px; font-weight:600; letter-spacing:-.015em; color:var(--ink); text-align:center; line-height:1.3}
.ch-st{position:relative; display:inline-grid; height:22px}
.ch-st span{grid-area:1/1; display:flex; align-items:center; gap:5px; justify-content:center;
  font-size:10.5px; font-weight:700; letter-spacing:.05em;
  border-radius:99px; padding:3px 10px; white-space:nowrap}
.ch-st .s-idle{color:var(--muted); background:var(--surface-2); border:1px solid var(--line)}
.ch-st .s-run{color:var(--violet); background:var(--violet-soft); border:1px solid var(--violet-line); opacity:0}
.ch-st .s-ok{color:var(--green); background:var(--green-soft); border:1px solid var(--green-line); opacity:0}
.ch-st svg{width:10px; height:10px}
.chg.is-in .ch-tile .s-idle{animation:chIdle 6s cubic-bezier(.4,0,.2,1) infinite}
.chg.is-in .ch-tile .s-run{animation:chRun 6s cubic-bezier(.4,0,.2,1) infinite}
.chg.is-in .ch-tile .s-ok{animation:chOk 6s cubic-bezier(.4,0,.2,1) infinite}
.chg.is-in .ch-tile.c2 .s-idle,.chg.is-in .ch-tile.c2 .s-run,.chg.is-in .ch-tile.c2 .s-ok{animation-delay:.22s}
.chg.is-in .ch-tile.c3 .s-idle,.chg.is-in .ch-tile.c3 .s-run,.chg.is-in .ch-tile.c3 .s-ok{animation-delay:.44s}
.chg.is-in .ch-tile.c4 .s-idle,.chg.is-in .ch-tile.c4 .s-run,.chg.is-in .ch-tile.c4 .s-ok{animation-delay:.66s}
.chg.is-in .ch-tile.c5 .s-idle,.chg.is-in .ch-tile.c5 .s-run,.chg.is-in .ch-tile.c5 .s-ok{animation-delay:.88s}
@keyframes chIdle{0%,6%{opacity:1}9%,100%{opacity:0}}
@keyframes chRun{0%,6%{opacity:0}9%,24%{opacity:1}28%,100%{opacity:0}}
@keyframes chOk{0%,24%{opacity:0}28%,92%{opacity:1}96%,100%{opacity:0}}
@media (prefers-reduced-motion:reduce){
  .chg.is-in .ch-tile,.chg.is-in .ch-tile span,.chg.is-in .ch-evt span b{animation:none!important}
  .ch-st .s-ok{opacity:1} .ch-st .s-idle,.ch-st .s-run{opacity:0}
}

.perm-note{display:flex; align-items:center; justify-content:center; gap:9px; margin-top:20px; font-size:13px; color:var(--muted)}
.perm-note svg{width:15px; height:15px; color:var(--violet); flex:none}

@media(max-width:860px){.ch-row{grid-template-columns:repeat(3,1fr)}}
@media(max-width:520px){.ch-row{grid-template-columns:repeat(2,1fr)}}

/* Case study — spec §4: a browser-chrome frame around three real product
   rows (thumbnail + before/after title, description and attribute chips),
   then the aggregate figures as a .gauge + stat tiles instead of bullets. */

/* Part 2 rebuild — the case study is now a three-act story on a vertical
   spine instead of a comparison table + stat boxes + ring gauge. */

/* Opening beat — smaller marker, no number, sits above act 1. */

/* Act 1 evidence — the existing #problem .gcard/.kmatch/.pos component,
   reused verbatim rather than a second SERP mock (spec: Part 2). */

/* ── Case study, four beats ────────────────────────────────────────────
   Client: "faut vraiment que l'internaute comprenne le problème qu'avait
   l'annonceur, qu'il suive ce qu'a fait l'annonceur, quels résultats, et
   comment il utilise MagicFeedPro au jour le jour — c'est vraiment ça le
   point pour se différencier de ce qui s'est dit avant."

   The constraint that shaped this is the second half of that sentence. The
   page already owns the record before/after (#problem), the auction result
   (the SERP mock), the mechanism (the chain), the four steps (#how), the
   Feed Studio screen and the dashboard. Grepping the whole page for
   recurring use returned nothing: "hourly" 0, "each week" 0, "day to day" 0,
   "ongoing" 0. Every version of this section so far described a one-time
   rewrite, which is also the weakest possible argument for a subscription.
   So beat 04 is the section's real subject and the other three are kept
   deliberately lean to make room for it — beat 01 states the situation
   without redrawing a product card, beat 02 lists Alice's decisions rather
   than the product's features. */
.beats{display:flex; flex-direction:column; gap:14px; margin-top:36px}
.beat{display:grid; grid-template-columns:216px minmax(0,1fr); gap:26px;
  padding:26px 28px; border:1px solid var(--line); border-radius:var(--r-xl);
  background:var(--surface)}
.beat__k{display:flex; flex-direction:column; gap:8px}
.beat__n{font-family:"JetBrains Mono",ui-monospace,monospace; font-size:12px; font-weight:700;
  letter-spacing:.1em; color:var(--violet)}
.beat__h{margin:0; font-size:18px; font-weight:700; letter-spacing:-.02em; color:var(--ink);
  line-height:1.25}
.beat__c{min-width:0}
.beat__p{margin:0; font-size:14px; line-height:1.6; color:var(--ink-2)}
.beat__p--lead{margin-bottom:16px; color:var(--ink); font-weight:500}

.beat__stats{display:flex; flex-wrap:wrap; gap:10px; margin-top:16px}
.beat__stats span{display:flex; flex-direction:column; gap:2px; padding:11px 16px;
  border-radius:var(--r-m); background:var(--band); border:1px solid var(--line);
  font-size:11.5px; color:var(--muted)}
.beat__stats b{font-size:19px; font-weight:700; color:var(--ink); letter-spacing:-.02em;
  font-variant-numeric:tabular-nums}

.beat__did{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:11px}
.beat__did li{position:relative; padding-left:22px; font-size:13.5px; line-height:1.55;
  color:var(--ink-2)}
/* A tick rather than a bullet: these are decisions already taken, not a
   checklist the reader has to work through. */
.beat__did li::before{content:""; position:absolute; left:0; top:.52em; width:11px; height:2px;
  border-radius:2px; background:var(--violet)}
.beat__did b{color:var(--ink); font-weight:600}
.beat__did em{font-style:italic; color:var(--violet)}

.beat__res{display:flex; flex-direction:column; gap:15px}
.beat__r{display:flex; align-items:flex-start; gap:16px}
.beat__rn{flex:0 0 108px; font-size:29px; line-height:1; font-weight:700; letter-spacing:-.03em;
  font-variant-numeric:tabular-nums; color:var(--violet)}
.beat__rn i{font-style:normal; font-size:.6em; color:var(--muted); margin:0 5px}
.beat__rl{display:flex; flex-direction:column; gap:3px; font-size:13.5px; font-weight:600;
  color:var(--ink); min-width:0; padding-top:3px}
.beat__rl small{font-size:11.5px; font-weight:400; line-height:1.5; color:var(--muted)}

/* Beat 04 is the one nothing else on the page says, so it is the only one
   that carries the brand field. */
.beat--now{border-color:var(--violet-line);
  background:linear-gradient(180deg,var(--violet-soft),transparent 38%),var(--surface);
  box-shadow:0 0 0 1px var(--violet-line) inset, 0 30px 62px -30px rgba(113,111,255,.45)}
.beat__now{list-style:none; margin:0; padding:0; display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px 22px}
/* Exactly two grid children per row — the icon and one wrapper. A bare text
   node after the <b> would become a third, anonymous grid item and land in an
   implicit column, which is what collapsed this list into vertical letters.
   Same trap as the two whitespace bugs earlier in this round. */
.beat__now li{display:grid; grid-template-columns:auto minmax(0,1fr); gap:10px;
  font-size:13px; line-height:1.5; color:var(--muted)}
.beat__now li>span:last-child{display:block; min-width:0}
.beat__now b{color:var(--ink); font-weight:600; display:block}
.beat__ic{display:grid; place-items:center; width:22px; height:22px; border-radius:7px;
  background:var(--violet-soft); border:1px solid var(--violet-line); color:var(--violet);
  font-size:12px; line-height:1}
.beat__q{display:flex; gap:13px; align-items:center; margin:20px 0 0; padding:15px 18px;
  border:1px solid var(--line); border-radius:var(--r-l); background:var(--surface)}
.beat__q img{width:40px; height:40px; border-radius:50%; object-fit:cover; flex:none}
.beat__q p{margin:0 0 5px; font-size:13.5px; font-style:italic; line-height:1.5; color:var(--ink)}
.beat__q footer{font-size:12px; color:var(--muted)}
.beat__q footer b{color:var(--ink-2); font-weight:600}

@media (max-width:880px){
  .beat{grid-template-columns:1fr; gap:16px; padding:22px}
  .beat__k{flex-direction:row; align-items:baseline; gap:12px}
  .beat__now{grid-template-columns:1fr}
  .beat__rn{flex-basis:88px; font-size:25px}
}


/* Act 3 — paired bars, before muted / after in brand violet, on an
   ambient halo instead of a ring gauge. Reuses base's unused .row/.bar
   scanner-mock primitives (spec: port their vocabulary), extended with a
   third value column and a reveal-gated fill instead of a fixed inline
   width. */

/* The fill was gated on .cs-act--3, the numbered act that round 6 removed.
   It now rides the panel's own reveal, or the bars stay at zero width. */

/* Closing beat — the founder's own voice, short and unglamorous. */

@media (max-width:640px){
  .cs-metrics{grid-template-columns:1fr}
}

@media (max-width:900px){
  .revs{grid-template-columns:1fr}
  .cs-stats{grid-template-columns:1fr}
  .cs-tiles{grid-template-columns:1fr 1fr}
}
@media (max-width:600px){
  .cs-tiles{grid-template-columns:1fr}
}

/* Part 4 rebuild — a real numbered progression instead of four flat
   outlined boxes. Same visual grammar as the hero mock's .dash-steps (a
   dot + connecting line that fills as steps complete), reused here as an
   actual interactive tablist instead of a decorative marker row. */
.how-steps{display:flex; align-items:flex-start; justify-content:center; gap:2px;
  max-width:640px; margin:0 auto 34px; padding:0}
.how-steps__i{flex:1 1 0; min-width:0; position:relative}
.how-steps__i:not(:last-child)::after{
  content:""; position:absolute; top:19px; left:calc(50% + 24px); right:calc(-50% + 24px);
  height:2px; background:var(--line-strong); z-index:0; transition:background .35s ease}
.how-steps__i.is-done:not(:last-child)::after{background:var(--green)}
.how-steps__i button{all:unset; box-sizing:border-box; cursor:pointer; display:flex; flex-direction:column;
  align-items:center; gap:9px; width:100%; text-align:center; padding:2px 4px 6px; border-radius:var(--r-m);
  transition:transform .2s ease}
.how-steps__i button:hover{transform:translateY(-2px)}
.how-steps__dot{position:relative; z-index:1; width:38px; height:38px; border-radius:50%; flex:none;
  display:grid; place-items:center; font-family:"JetBrains Mono",monospace; font-size:14px; font-weight:700;
  background:var(--surface); border:2px solid var(--line-strong); color:var(--muted);
  transition:background .3s ease, border-color .3s ease, color .3s ease, box-shadow .3s ease, transform .3s ease}
.how-steps__lbl{font-size:12.5px; line-height:1.32; color:var(--muted); max-width:13.5ch;
  transition:color .3s ease, font-weight .3s ease}
.how-steps__i.is-active .how-steps__dot{background:var(--violet); border-color:var(--violet); color:#fff;
  box-shadow:0 0 0 6px var(--violet-soft); transform:scale(1.08)}
.how-steps__i.is-active .how-steps__lbl{color:var(--ink); font-weight:700}
.how-steps__i.is-done .how-steps__dot{background:var(--green); border-color:var(--green); color:transparent}
.how-steps__i.is-done .how-steps__dot::after{content:"✓"; position:absolute; inset:0; display:grid;
  place-items:center; color:#fff; font-size:13px; font-weight:700}
.how-steps__i.is-done .how-steps__lbl{color:var(--ink-2)}

/* The panel is now a bordered card so the step's sentence and its
   illustration read as one unit, not copy floating above a stray image. */
.step__panels{position:relative; padding:0; max-width:760px; margin:0 auto}
.step__panels::before{content:""; position:absolute; inset:6% 12%; z-index:0; border-radius:50%;
  background:radial-gradient(ellipse at center, rgba(113,111,255,.12), transparent 68%); filter:blur(40px); pointer-events:none}
.step__p{display:none; position:relative; z-index:1; flex-direction:column; align-items:center;
  border:1px solid var(--line); border-radius:var(--r-xl); background:var(--surface);
  box-shadow:var(--shadow-m); padding:30px 30px 0; overflow:hidden}
.step__p.is-on{display:flex; animation:stepIn .4s ease both}
.step__p p{text-align:center; color:var(--ink-2); font-size:15.5px; line-height:1.55; max-width:54ch; margin:0 0 22px}
/* Capped so heading + steps + panel fit one 1440x900 viewport (client:
   the illustration currently dominates) — verified in-browser. */
.step__p img{display:block; max-height:300px; width:auto; max-width:100%; height:auto;
  object-fit:contain; margin-inline:auto; border-radius:var(--r-m) var(--r-m) 0 0; background:var(--band)}
@keyframes stepIn{from{opacity:0; transform:translateX(var(--dir,16px))} to{opacity:1; transform:none}}
@media (prefers-reduced-motion:reduce){ .step__p.is-on{animation:none} }

@media (max-width:820px){
  .how-steps{gap:0}
  .how-steps__i:not(:last-child)::after{left:calc(50% + 20px); right:calc(-50% + 20px)}
  .how-steps__lbl{display:none}
  .how-steps__dot{width:32px; height:32px; font-size:12.5px}
  .step__p{padding:22px 20px 0}
  .step__p img{max-height:220px}
}

/* .faq (grid), .faq-glow (ambient halo) and the bare details/summary rules
   all come from base-v6.css now (spec §10) — the <details> markup below was
   rewritten to match base's shape (a .q-ic icon in the summary, no .card
   wrapper, since base's own `details{...}` already gives it the glassy
   surface + hover/open states). */

/* Closing CTA (round 3, item 1) — the page's last argument, rebuilt as a
   full dark section instead of a single line on flat white. Ground ported
   from #studio's Feed Studio band (violet gradient + grid texture + ambient
   halo); this is the SAME "Start free — no charge today" button that used to
   sit in the flat .ctamid mid-page band — relocated here rather than added,
   so the page-wide count stays at exactly two (hard constraint). */
.close-cta{position:relative; overflow:hidden; padding-block:104px; color:#fff; text-align:center;
  background:
    radial-gradient(ellipse 74% 62% at 50% -8%, rgba(147,112,255,.55), transparent 62%),
    radial-gradient(ellipse 60% 50% at 92% 104%, rgba(236,72,153,.2), transparent 60%),
    linear-gradient(165deg,#241B5E 0%,#332876 46%,#20174F 100%)}
.close-cta::before{content:""; position:absolute; inset:0; pointer-events:none; z-index:0;
  background-image:linear-gradient(rgba(255,255,255,.06) 1px,transparent 1px),
    linear-gradient(90deg,rgba(255,255,255,.06) 1px,transparent 1px);
  background-size:72px 72px;
  -webkit-mask-image:radial-gradient(ellipse 70% 60% at 50% 12%,#000 15%,transparent 76%);
  mask-image:radial-gradient(ellipse 70% 60% at 50% 12%,#000 15%,transparent 76%)}
.close-cta__bg{position:absolute; inset:0; pointer-events:none; z-index:0}
.close-cta__halo{position:absolute; width:620px; height:620px; left:50%; top:-22%; transform:translateX(-50%);
  border-radius:50%; filter:blur(120px); opacity:.5;
  background:radial-gradient(circle,#8B5CF6,transparent 68%)}
/* Part 5: the closing argument was one narrow centred column — the headline
   wrapped four lines and the section ran past a full viewport. Restructured
   as two columns (roughly 55/45, both vertically centred): copy on the
   left with room to breathe, the decision block (trial strip + button +
   trust row) as one contained unit on the right. Layout change only —
   ground, halo, grid texture, button and copy are untouched. */
.close-cta__in{position:relative; z-index:1; max-width:1080px; margin-inline:auto;
  display:grid; grid-template-columns:1.2fr 1fr; gap:56px; align-items:center; text-align:left}
.cc-col--copy{display:flex; flex-direction:column; align-items:flex-start}
.cc-col--decision{display:flex; flex-direction:column; align-items:stretch}
.cc-eyebrow{background:rgba(255,255,255,.08); border-color:rgba(255,255,255,.18); color:#E9D5FF}
.close-cta__h{font-size:clamp(28px,3.4vw,40px); line-height:1.18; letter-spacing:-.02em; font-weight:700;
  margin:0; max-width:15ch; text-align:left}
.cc-acc{background:linear-gradient(100deg,#C4B5FD,#8B7BFF 55%,#FDBA74);
  -webkit-background-clip:text; background-clip:text; color:transparent}
.close-cta__sub{margin:18px 0 0; font-size:16px; line-height:1.55; color:rgba(255,255,255,.7); max-width:38ch; text-align:left}
.cc-steps{display:flex; flex-direction:column; gap:10px}
.cc-step{padding:12px 18px; border-radius:var(--r-m); background:rgba(255,255,255,.05);
  border:1px solid rgba(255,255,255,.12); text-align:left}
.cc-step b{display:block; font-size:12.5px; font-weight:700; color:#fff; letter-spacing:-.01em}
.cc-step span{display:block; margin-top:3px; font-size:12px; color:rgba(255,255,255,.6); line-height:1.4}
.cc-cta{margin-top:20px; width:100%}
.cc-trust{margin-top:28px; padding-top:24px; border-top:1px solid rgba(255,255,255,.14);
  display:flex; flex-direction:column; align-items:center; gap:12px; text-align:center}
.cc-trust .stars em{color:#FBBF24}
.cc-trust .stars small{color:rgba(255,255,255,.6)}
.cc-trust__count{font-size:13.5px; color:rgba(255,255,255,.72)}
.cc-trust__count b{color:#fff; font-weight:700}
.cc-marks{display:flex; flex-wrap:wrap; gap:8px; justify-content:center}
.cc-marks .shopbadge{margin-top:0}

/* Mobile: collapse to a single column, copy first then the decision block —
   same order as desktop reads (argument, then the ask). */
@media (max-width:860px){
  .close-cta__in{grid-template-columns:1fr; gap:34px; text-align:center}
  .cc-col--copy{align-items:center}
  .close-cta__h{max-width:22ch; text-align:center}
  .close-cta__sub{max-width:48ch; text-align:center; margin-inline:auto}
}
@media (max-width:600px){
  .close-cta{padding-block:72px}
  .cc-steps{flex-direction:column; align-items:stretch}
  .cc-step{min-width:0}
}

.ft{padding-block:52px 28px; background:var(--band)}
.ft__top{display:grid; grid-template-columns:1.6fr 1fr 1fr 1fr; gap:32px}
.ft__brand img{margin-bottom:14px}
.ft__brand p{color:var(--muted); font-size:13.5px; line-height:1.6; max-width:32ch; margin:0}
.ft__badges{display:flex; flex-wrap:wrap; gap:8px; margin-top:14px}
.ft__badges .shopbadge{margin-top:0; font-size:11px; padding:5px 11px}
.ft__col{display:flex; flex-direction:column; gap:11px; min-width:0}
.ft__col b{font-size:12px; letter-spacing:.06em; text-transform:uppercase; color:var(--muted)}
.ft__col a{color:var(--ink-2); text-decoration:none; font-size:14px}
.ft__col a:hover{color:var(--violet)}
.ft__legal{display:flex; align-items:center; justify-content:space-between; gap:16px; flex-wrap:wrap;
  margin-top:40px; padding-top:22px; border-top:1px solid var(--line-strong)}
.ft__legal small{color:var(--muted); font-size:13px}
@media (max-width:900px){
  .ft__top{grid-template-columns:1fr 1fr; row-gap:32px}
  .ft__brand{grid-column:1/-1}
}
@media (max-width:760px){
  .ft-cta__in{flex-direction:column; text-align:center}
  .ft__top{grid-template-columns:1fr 1fr}
  .ft__legal{flex-direction:column; text-align:center}
}
/* Task 14: iOS safe-area guard — clear the home-indicator strip. */
.ft{)}

/* Sticky conversion bar (spec §12) — .sticky/.sticky-in/.sk-* come from
   base-v6.css; the only addition needed is pushing the CTA to the far edge
   since base's own .sk-form (a search field) isn't part of this page. */
.sticky .btn{margin-inline-start:auto}

/* Task 2: by-hand vs. MagicFeedPro comparison table — ported from
   lp/upsell/index.html's `#compare` table (grep WHAT CHANGES / .cmp-*): three
   columns, red-✗ middle column on white, a violet-gradient header with a
   floating "Your upgrade" ribbon on the right column, green-✓ cells. */
/* Round 4 client fix: .cmp-badge sits at top:-12px relative to the header's
   .yes cell, which is flush with .cmp's own top edge — with zero top padding
   here, overflow:hidden (needed to keep the violet column's corners rounded)
   was clipping the ribbon's rounded top clean off. Padding-top gives the
   ribbon room to float above the header without exceeding the clip box. */
.cmp{max-width:960px; margin:0 auto; border:1px solid var(--line); border-radius:var(--r-xl);
  overflow:hidden; background:var(--surface); box-shadow:var(--shadow-m),
  36px 0 70px -34px rgba(113,111,255,.4)}
.cmp-head,.cmp-row{display:grid; grid-template-columns:1.2fr 1fr 1fr}
.cmp-head{background:var(--surface-2); border-bottom:1px solid var(--line)}
.cmp-head .cmp-cell{position:relative; display:flex; flex-direction:column; align-items:center;
  justify-content:center; gap:4px; padding:52px 18px 18px; text-align:center; border-left:1px solid var(--line)}
.cmp-head .cmp-arg{border-left:0}
.cmp-head .cmp-cell.no b{font-size:15px; font-weight:600; color:var(--ink-2)}
/* Part 7a: re-diagnosed with real computed geometry — the ribbon (.cmp-badge)
   was not actually being clipped (verified: its top sits ~10px inside the
   card's padded top at both 1440 and 390px). The corner mismatch some
   browsers show is that this cell paints a hard rectangular corner right at
   the card's own rounded corner and relies entirely on the card's
   overflow:hidden to round it — give the cell its own matching corner radius
   too, so the round corner is correct even if a browser's clip is imprecise. */
.cmp-head .cmp-cell.yes{
  padding-top:52px; border-left:0; border-top-right-radius:calc(var(--r-xl) - 1px);
  background:linear-gradient(165deg,#7A78FF,#9F4FE0); color:#fff;
}
.cmp-mark{height:26px; width:auto; margin-bottom:2px; filter:brightness(0) invert(1)}
.cmp-head .cmp-cell.yes em{font-style:normal; font-family:"JetBrains Mono",monospace; font-size:11.5px; color:rgba(255,255,255,.82)}
.cmp-badge{position:absolute; top:12px; left:50%; transform:translateX(-50%); white-space:nowrap;
  padding:5px 14px; border-radius:999px; background:#fff; color:#5B5BFF; font-size:9.5px; font-weight:800;
  letter-spacing:.08em; text-transform:uppercase; box-shadow:0 4px 12px rgba(23,22,31,.22); z-index:2}
.cmp-row{border-top:1px solid var(--line)}
.cmp-row:nth-child(even) .cmp-arg{background:var(--surface-2)}
.cmp-arg{padding:17px 20px; display:flex; align-items:center}
.cmp-arg b{font-size:14px; font-weight:600; color:var(--ink); line-height:1.4}
.cmp-cell{padding:17px 20px; display:flex; align-items:flex-start; gap:10px; border-left:1px solid var(--line)}
.cmp-cell span:not(.cmp-mk){font-size:13.5px; line-height:1.5; color:var(--ink-2)}
/* Part 7b: the two sides now read as opposed at a glance, not as one table
   with different icons. "By hand" gets a visibly tired, greyed treatment;
   MagicFeedPro's column runs the same violet gradient unbroken from header
   to last row (every .yes cell shares the header's gradient, not a faint
   tint), white text, and the ✗/✓ marks carry more weight. */
.cmp-row .cmp-cell.no{background:var(--band)}
.cmp-row .cmp-cell.no span:not(.cmp-mk){color:var(--muted)}
/* Header stays saturated; the column body drops to a light tint of the same
   hue. Painting both the same violet made the header invisible as a header
   and forced white body text, which read heavier than the left column. */
.cmp-cell.yes{background:var(--violet-soft); border-left-color:var(--violet-line)}
.cmp-cell.yes span:not(.cmp-mk){color:var(--ink); font-weight:500}
/* Higher specificity than the rule above, which otherwise wins over
   .cmp-badge's own color and paints "Your upgrade" white-on-white. */
.cmp-head .cmp-cell.yes .cmp-badge{color:#5B5BFF}
.cmp-mk{width:23px; height:23px; border-radius:7px; flex:none; display:grid; place-items:center; margin-top:-1px}
.cmp-mk svg{width:13px; height:13px}
.cmp-mk.no{background:var(--red-soft); border:1.5px solid var(--red-line); color:var(--red)}
.cmp-mk.yes{background:#fff; border:1px solid #fff; color:#16A34A; box-shadow:0 0 0 3px rgba(255,255,255,.22)}

/* Cost row — the whole argument landed in one figure per side. */
.cmp-row--cost{border-top:1.5px solid var(--line-strong)}
.cmp-row--cost .cmp-arg b{font-size:13px; color:var(--muted); font-weight:600}
.cmp-cost{font-family:"JetBrains Mono",monospace; font-size:21px; font-weight:700}
.cmp-row--cost .cmp-cell.no .cmp-cost{color:var(--ink-2)}
.cmp-row--cost .cmp-cell.yes .cmp-cost{color:var(--violet)}
.cmp-row--cost .cmp-cell{align-items:center}

/* Rows reveal in sequence; the check-mark column pops in slightly after its
   own row (item 2). --rd is set per row and inherited by its .cmp-mk, so the
   icon's delay is always "this row's delay + a little more" without
   duplicating the stagger table. */
.cmp.reveal .cmp-row{--rd:0ms; opacity:0; transform:translateY(10px);
  transition:opacity .45s ease var(--rd), transform .45s ease var(--rd)}
.cmp.reveal .cmp-row:nth-of-type(2){--rd:70ms}
.cmp.reveal .cmp-row:nth-of-type(3){--rd:140ms}
.cmp.reveal .cmp-row:nth-of-type(4){--rd:210ms}
.cmp.reveal .cmp-row:nth-of-type(5){--rd:280ms}
.cmp.reveal .cmp-row:nth-of-type(6){--rd:350ms}
.cmp.reveal .cmp-row:nth-of-type(7){--rd:420ms}
.cmp.reveal.is-in .cmp-row{opacity:1; transform:none}
.cmp.reveal .cmp-mk{opacity:0; transform:scale(.4);
  transition:opacity .3s ease calc(var(--rd) + 160ms), transform .3s ease calc(var(--rd) + 160ms)}
.cmp.reveal.is-in .cmp-mk{opacity:1; transform:none}

@media (max-width:820px){
  .cmp-head,.cmp-row{grid-template-columns:1fr}
  .cmp-head .cmp-arg{display:none}
  .cmp-arg{background:var(--ink); padding:12px 18px}
  .cmp-arg b{color:#fff}
  .cmp-cell{border-left:0; border-top:1px solid var(--line)}
}

/* Task 3: Feed Studio — ported from lp/upsell/index.html's `#studio` (grep
   Feed Studio / INTRODUCING): dark violet gradient ground with a grid/dot
   texture and slow light sweep, a small app-icon tile, an "Introducing"
   eyebrow, the Feed Studio wordmark, and a mock — a product table (ID,
   thumbnail, Title, Price, one highlighted dynamic column) on the left, an
   AI chat panel (prompt chips + a canned answer + a Run composer) on the
   right. Placed right after the dashboard mock, before #problem, so the
   page's own light/dark rhythm goes light → dark → light → dark → light. */
.fs{position:relative; overflow:hidden; padding-block:88px; color:#fff;
  background:
    radial-gradient(ellipse 78% 58% at 12% -4%, rgba(147,112,255,.55), transparent 62%),
    radial-gradient(ellipse 62% 48% at 88% 6%, rgba(56,189,248,.28), transparent 64%),
    radial-gradient(ellipse 90% 60% at 52% 108%, rgba(236,72,153,.22), transparent 62%),
    linear-gradient(165deg,#2C2170 0%,#3B2C8E 46%,#2A1F66 100%)}
.fs::before{content:""; position:absolute; inset:0; pointer-events:none; z-index:0;
  background-image:linear-gradient(rgba(255,255,255,.06) 1px,transparent 1px),
    linear-gradient(90deg,rgba(255,255,255,.06) 1px,transparent 1px);
  background-size:72px 72px;
  -webkit-mask-image:radial-gradient(ellipse 70% 60% at 50% 0%,#000 20%,transparent 78%);
  mask-image:radial-gradient(ellipse 70% 60% at 50% 0%,#000 20%,transparent 78%)}
.fs-bg{position:absolute; inset:0; pointer-events:none}
.fs-bg i{position:absolute; border-radius:50%; filter:blur(90px); opacity:.4}
.fs-bg .fo1{width:480px; height:480px; left:-8%; top:-16%; background:radial-gradient(circle,#6E6BFF,transparent 68%)}
.fs-bg .fo2{width:420px; height:420px; right:-6%; bottom:-14%; background:radial-gradient(circle,#A855F7,transparent 68%)}
.fs-sheen{position:absolute; top:-20%; bottom:-20%; left:-35%; width:34%; z-index:0; pointer-events:none;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.12),transparent);
  transform:skewX(-14deg); animation:fsSheen 11s ease-in-out infinite}
@keyframes fsSheen{0%,62%{left:-35%; opacity:0}68%{opacity:1}100%{left:118%; opacity:0}}
/* The band is violet, so the gradient button would sit on its own colour and
   disappear. A solid white pill is the highest-contrast thing that can go on
   this ground, and it inverts the page's usual button rather than inventing a
   third style. */
.fs-cta{position:relative; z-index:1; margin-top:32px; display:flex; justify-content:center}
.btn-onviolet{background:#fff; color:#241C4F; box-shadow:0 10px 30px -10px rgba(0,0,0,.45)}
.btn-onviolet:hover{background:#F4F2FF; color:#1B1440}
.btn-onviolet .arw{color:inherit}
.btn-onviolet:focus-visible{outline:3px solid #fff; outline-offset:3px}
@media (max-width:560px){ .fs-cta .btn{width:100%; max-width:340px} }

.fs-in{position:relative; z-index:1}
.fs .sec-head h2{color:#fff}
.fs .sec-head p{color:rgba(255,255,255,.64); max-width:640px}
.fs .sec-head p b{color:#fff; font-weight:600}
.fs .sec-head p [data-fact]{color:#fff; font-weight:700}
.fs-acc{background:linear-gradient(100deg,#C4B5FD,#8B7BFF 55%,#E9D5FF);
  -webkit-background-clip:text; background-clip:text; color:transparent}
.fs-brand{display:flex; align-items:center; justify-content:center; gap:14px; margin-bottom:18px}
.fs-brand-ic{width:52px; height:52px; border-radius:16px; flex:none; display:grid; place-items:center;
  color:#fff; background:linear-gradient(150deg,#6E6BFF,#A855F7); box-shadow:0 12px 32px rgba(124,94,255,.5)}
.fs-brand-ic svg{width:26px; height:26px}
.fs-brand-tx{display:flex; flex-direction:column; align-items:flex-start; line-height:1}
.fs-brand-tx em{font-style:normal; font-size:11.5px; font-weight:700; letter-spacing:.16em;
  text-transform:uppercase; color:rgba(255,255,255,.52); margin-bottom:6px}
.fs-brand-tx b{font-size:clamp(26px,3.6vw,38px); font-weight:700; letter-spacing:-.03em; color:#fff}

.fstu{position:relative; max-width:1080px; margin:0 auto}
/* Ambient aurora behind the frame + the sweep/beam layers the animation
   toggles on Run — ported from lp/upsell/index.html (spec Part 3). */
.fstu-aur{position:absolute; inset:-16% -8%; z-index:0; pointer-events:none; filter:blur(90px); opacity:.6}
.fstu-aur i{position:absolute; border-radius:50%; display:block}
.fstu-aur .a1{width:44%; height:70%; left:-6%; top:-18%; background:radial-gradient(circle,rgba(147,112,255,.85),transparent 68%); animation:fstuDrift 19s ease-in-out infinite}
.fstu-aur .a2{width:38%; height:64%; right:-4%; top:-10%; background:radial-gradient(circle,rgba(56,189,248,.62),transparent 68%); animation:fstuDrift 23s ease-in-out infinite reverse}
.fstu-aur .a3{width:46%; height:60%; left:26%; bottom:-24%; background:radial-gradient(circle,rgba(236,72,153,.45),transparent 70%); animation:fstuDrift 27s ease-in-out infinite}
@keyframes fstuDrift{0%,100%{transform:translate3d(0,0,0) scale(1)}50%{transform:translate3d(4%,-5%,0) scale(1.12)}}
.fstu-frame{position:relative; z-index:1; padding:1.5px; border-radius:24px;
  background:linear-gradient(150deg,rgba(255,255,255,.28),rgba(255,255,255,.06))}
.fstu-win{position:relative; border-radius:22.5px; overflow:hidden;
  background:linear-gradient(180deg,#211A4C,#1A1440); box-shadow:0 40px 90px rgba(8,4,28,.55)}
.fstu-sheen{position:absolute; top:0; bottom:0; left:-40%; width:36%; z-index:6; pointer-events:none; opacity:0;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.14),transparent); transform:skewX(-14deg)}
.fstu.fstu-sweep .fstu-sheen{animation:fstuSheen 1.15s cubic-bezier(.4,0,.5,1)}
@keyframes fstuSheen{0%{left:-40%; opacity:1}100%{left:120%; opacity:0}}
.fstu-bar{display:flex; align-items:center; gap:12px; padding:12px 16px; background:rgba(255,255,255,.04);
  border-bottom:1px solid rgba(255,255,255,.09)}
.fstu-dots{display:flex; gap:6px; flex:none}
.fstu-dots i{width:9px; height:9px; border-radius:50%; background:rgba(255,255,255,.18); display:block}
.fstu-app{font-size:13px; font-weight:600; letter-spacing:-.01em; flex:none}
.fstu-acts{margin-left:auto; display:flex; align-items:center; gap:8px; flex:none}
/* Below ~420px the two buttons in the mock's title bar are wider than the
   frame and get cut. The secondary one goes; "Get feed URL" is the one that
   carries the meaning of the screen. */
@media (max-width:440px){
  .fstu-ghost{display:none}
  .fstu-cta{font-size:11px; padding:6px 10px}
  .fstu-app{font-size:12px}
}
.fstu-ghost{padding:7px 13px; border-radius:9px; font-size:11.5px; font-weight:600; color:rgba(255,255,255,.72);
  background:rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.1)}
.fstu-cta{padding:7px 14px; border-radius:9px; font-size:11.5px; font-weight:700; color:#fff;
  background:linear-gradient(135deg,#6E6BFF,#A855F7)}
.fstu-body{display:grid; grid-template-columns:1.5fr 1fr}
.fstu-table{position:relative; overflow:hidden; display:flex; flex-direction:column; border-right:1px solid rgba(255,255,255,.09); min-width:0}
.fstu-beam{position:absolute; top:0; bottom:0; left:-20%; width:16%; z-index:4; pointer-events:none; opacity:0;
  background:linear-gradient(90deg,transparent,rgba(167,139,250,.42),transparent)}
.fstu.fstu-scan .fstu-beam{animation:fstuBeam 1.5s cubic-bezier(.45,0,.55,1)}
@keyframes fstuBeam{0%{left:-20%; opacity:1}100%{left:104%; opacity:0}}
.fstu-th,.fstu-tr{display:grid; grid-template-columns:70px 34px minmax(0,1fr) 62px minmax(0,1.1fr);
  align-items:center; gap:8px; padding:0 16px}
.fstu-th{height:36px; background:rgba(255,255,255,.05); border-bottom:1px solid rgba(255,255,255,.09);
  font-size:9.5px; font-weight:700; letter-spacing:.09em; text-transform:uppercase; color:rgba(255,255,255,.42)}
.fstu-dyh{display:inline-flex; align-items:center; gap:5px}
.fstu-dyh .sp{font-style:normal; color:#C4B5FD; font-size:10px}
.fstu-dyh b{font-weight:700; background:linear-gradient(100deg,#C4B5FD,#7DD3FC);
  -webkit-background-clip:text; background-clip:text; color:transparent; text-transform:none; letter-spacing:0}
.fstu-tr{position:relative; height:44px; border-bottom:1px solid rgba(255,255,255,.055); transition:background .3s ease, box-shadow .3s ease}
.fstu-tr.on{background:rgba(167,139,250,.12); box-shadow:inset 0 0 0 1px rgba(167,139,250,.35)}
.fstu-tr.hit{background:rgba(167,139,250,.16); box-shadow:inset 0 0 0 1px rgba(167,139,250,.45)}
.fstu-id{font-size:10.5px; color:rgba(255,255,255,.38)}
.fstu-im{width:26px; height:26px; border-radius:8px; display:grid; place-items:center; background:rgba(255,255,255,.06)}
.fstu-im svg{width:16px; height:16px}
.fstu-ti{font-size:12px; color:rgba(255,255,255,.9); white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.fstu-tr.on.t-title .fstu-ti{color:rgba(255,255,255,.4); text-decoration:line-through}
.fstu-pr{font-size:11px; color:rgba(255,255,255,.55); text-align:right; font-family:"JetBrains Mono",monospace}
.fstu-dy{min-width:0; display:flex; align-items:center; gap:7px; font-family:"JetBrains Mono",monospace; font-size:10.5px}
.fstu-dy .o{font-style:normal; color:rgba(255,255,255,.3); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  transition:opacity .3s, color .3s}
.fstu-tr.on .fstu-dy .o{opacity:.4; text-decoration:line-through}
.fstu-tr.t-title .fstu-dy .o{display:none}
.fstu-dy .n{display:none; min-width:0; font-weight:600; padding:3px 8px; border-radius:7px; background:rgba(52,211,153,.15);
  border:1px solid rgba(52,211,153,.3); color:#A7F3D0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.fstu-tr.on .fstu-dy .n{display:block; animation:fstuPop .42s cubic-bezier(.2,1.5,.4,1) both}
@keyframes fstuPop{0%{opacity:0; transform:translateY(5px) scale(.94)}100%{opacity:1; transform:none}}
.fstu-more{display:flex; align-items:center; gap:9px; padding:11px 16px; font-family:"JetBrains Mono",monospace;
  font-size:10.5px; color:rgba(255,255,255,.4); opacity:0; transition:opacity .45s ease}
.fstu-more.in{opacity:1}
.fstu-more i{width:5px; height:5px; border-radius:50%; background:#A78BFA; flex:none}
.fstu-tf{margin-top:auto; display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding:11px 16px; background:rgba(255,255,255,.04); border-top:1px solid rgba(255,255,255,.09);
  font-size:10.5px; color:rgba(255,255,255,.42)}
.fstu-count{display:inline-flex; align-items:center; gap:6px; padding:4px 11px; border-radius:99px;
  font-size:10.5px; background:rgba(52,211,153,.15); border:1px solid rgba(52,211,153,.32); color:#A7F3D0;
  transition:background .4s, border-color .4s, color .4s}
.fstu-count b{font-size:12px; font-weight:700}
.fstu-count.done{background:rgba(52,211,153,.22); border-color:rgba(52,211,153,.44); color:#A7F3D0}

.fstu-ai{padding:16px 18px 18px; display:flex; flex-direction:column; gap:12px; min-width:0;
  background:linear-gradient(180deg,rgba(255,255,255,.03),transparent)}
.fstu-ai-h{display:flex; align-items:center; gap:9px}
.fstu-ai-ic{width:24px; height:24px; border-radius:8px; flex:none; display:grid; place-items:center;
  font-size:12px; color:#fff; background:linear-gradient(150deg,#6E6BFF,#A855F7)}
.fstu-ai-h b{font-size:13px; font-weight:600}
.fstu-step{margin-left:auto; font-size:10.5px; color:rgba(255,255,255,.4); font-family:"JetBrains Mono",monospace}
.fstu-ai-p{font-size:11.8px; line-height:1.5; color:rgba(255,255,255,.5)}
/* Round 6: three prompts were cut, so six chips have to occupy the height
   nine used to. Growing the gap (and the chip's own padding) fills the
   column instead of leaving a hole above the bottom-anchored answer box. */
.fstu-chips{display:flex; flex-direction:column; gap:11px}
.fstu-chip{position:relative; overflow:hidden; padding:10px 12px; border-radius:10px; font-size:11.5px;
  line-height:1.35; background:rgba(255,255,255,.045); border:1px solid rgba(255,255,255,.1); color:rgba(255,255,255,.68);
  transition:color .3s, background .3s, border-color .3s}
.fstu-chip.on{color:#fff; background:rgba(167,139,250,.14); border-color:rgba(167,139,250,.4)}
.fstu-chip.was{color:rgba(255,255,255,.38)}
.fstu-ans{margin-top:auto; padding:11px 13px; border-radius:12px; min-height:78px;
  background:rgba(255,255,255,.045); border:1px solid rgba(255,255,255,.1)}
.fstu-ans-l{display:block; font-family:"JetBrains Mono",monospace; font-size:9px; letter-spacing:.1em;
  text-transform:uppercase; color:#C4B5FD; margin-bottom:5px}
.fstu-ans p{font-size:12px; line-height:1.45; color:rgba(255,255,255,.88); margin:0}
.fstu-ans p b{color:#fff; font-weight:700}
.fstu-ans.think p{color:rgba(255,255,255,.42)}
.fstu-ans.think p::after{content:""; display:inline-block; width:3px; height:3px; border-radius:50%;
  background:currentColor; margin-left:5px; vertical-align:2px; animation:blink .9s step-end infinite}
.fstu-ans-ok{display:block; margin-top:7px; font-family:"JetBrains Mono",monospace; font-size:9.5px; color:#6EE7B7;
  opacity:0; transition:opacity .4s}
.fstu-ans-ok.in{opacity:1}
.fstu-inp{display:flex; align-items:center; gap:8px; padding:9px 11px; border-radius:11px;
  background:rgba(255,255,255,.05); border:1px solid rgba(255,255,255,.14)}
.fstu-typed{flex:1; min-width:0; font-size:11.5px; color:rgba(255,255,255,.85); white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis; font-family:"JetBrains Mono",monospace}
.fstu-caret{width:1.5px; height:13px; flex:none; background:#C4B5FD; animation:blink 1s step-end infinite}
.fstu-run{flex:none; padding:5px 13px; border-radius:8px; font-size:11px; font-weight:700; color:#fff;
  background:linear-gradient(135deg,#6E6BFF,#A855F7); transition:transform .2s, box-shadow .2s}
.fstu-run.go{transform:scale(1.07); box-shadow:0 0 0 4px rgba(167,139,250,.28)}

/* Feed Studio window — light surface (ported verbatim, client correction:
   lp/upsell/index.html around its own "FEED STUDIO — CLAIR" / "ILOTS CLAIRS"
   comments). The outer .fs section stays dark violet; the mock window itself
   reads as a real light-UI screenshot floating on that dark ground — table
   AND the AI panel are both light, only the accent chips/legend sitting
   outside the window stay on the dark section's own translucent-white
   vocabulary. .fstu-win locks its own light token values so the page's dark
   --ink/--surface tokens never leak in and turn this white-on-white. */
.fstu-win{
  color-scheme:light;
  --ink:#17161F; --ink-2:#3F3B54; --muted:#6B6880;
  --surface:#FFFFFF; --surface-2:#F5F3FB; --band:#F7F7FA;
  --line:#E4E1EF; --line-strong:#CFCADF;
  background:linear-gradient(180deg,#FFFFFF 0%,#F8F8FC 100%); color:var(--ink)}
.fstu-bar{background:linear-gradient(180deg,#FCFCFE,#F4F4FA); border-bottom:1px solid var(--line)}
.fstu-dots i{background:var(--line-strong)}
.fstu-app{color:var(--ink)}
.fstu-ghost{background:#fff; border-color:var(--line); color:var(--ink-2); box-shadow:var(--shadow-s)}
.fstu-table{border-right:1px solid var(--line)}
.fstu-th{background:var(--surface-2); border-bottom-color:var(--line); color:var(--muted)}
.fstu-dyh b{background:linear-gradient(100deg,#5B5BFF,#0EA5E9); -webkit-background-clip:text; background-clip:text}
.fstu-dyh .sp{color:var(--violet)}
.fstu-sheen{background:linear-gradient(100deg,transparent,rgba(113,111,255,.12),transparent)}
.fstu-beam{background:linear-gradient(90deg,transparent,rgba(113,111,255,.20),transparent)}
.fstu-tr{border-bottom-color:var(--line)}
.fstu-tr.on{background:var(--violet-soft); box-shadow:inset 0 0 0 1px var(--violet-line)}
.fstu-tr.hit{background:var(--violet-soft); box-shadow:inset 0 0 0 1px var(--violet-line)}
.fstu-tr.on.t-title .fstu-ti{color:var(--muted); text-decoration:line-through}
.fstu-id{color:var(--muted)}
.fstu-im{background:var(--band)}
.fstu-im.i0{background:rgba(245,158,11,.15)}
.fstu-im.i1{background:rgba(14,165,233,.14)}
.fstu-im.i2{background:rgba(113,111,255,.15)}
.fstu-im.i3{background:rgba(236,72,153,.13)}
.fstu-ti{color:var(--ink)}
.fstu-pr{color:var(--ink-2)}
.fstu-dy .o{color:var(--muted)}
.fstu-dy .n{background:var(--green-soft); border-color:var(--green-line); color:#146C43}
.fstu-more{color:var(--muted)}
.fstu-more i{background:#A78BFA; box-shadow:0 0 0 3px var(--violet-soft)}
.fstu-tf{background:var(--surface-2); border-top-color:var(--line); color:var(--muted)}
.fstu-count{background:var(--violet-soft); border-color:var(--violet-line); color:var(--violet)}
.fstu-count.done{background:var(--green-soft); border-color:var(--green-line); color:#146C43}
.fstu-ai{background:linear-gradient(180deg,var(--surface-2),#fff 55%)}
.fstu-ai-h b{color:var(--ink)}
.fstu-step{color:var(--muted)}
.fstu-ai-p{color:var(--muted)}
.fstu-chip{background:#fff; border-color:var(--line); color:var(--ink-2); box-shadow:0 1px 2px rgba(23,22,31,.04)}
.fstu-chip.on{color:var(--ink); border-color:var(--violet-line); box-shadow:var(--shadow-m);
  background:linear-gradient(100deg,var(--violet-soft),rgba(168,85,247,.09));
  animation:fstuChipPulse 2.6s ease-in-out infinite}
.fstu-chip.was{color:var(--muted)}
@keyframes fstuChipPulse{0%,100%{box-shadow:var(--shadow-m),0 0 0 0 rgba(167,139,250,.35)}
  50%{box-shadow:var(--shadow-m),0 0 0 6px rgba(167,139,250,0)}}
.fstu-ans{background:#fff; border-color:var(--line); box-shadow:var(--shadow-s)}
.fstu-ans-l{color:var(--violet)}
.fstu-ans p{color:var(--ink-2)}
.fstu-ans p b{color:var(--ink)}
.fstu-ans.think p{color:var(--muted)}
.fstu-ans-ok{color:var(--green)}
.fstu-inp{background:#fff; border:1px solid var(--line-strong); box-shadow:var(--shadow-s)}
.fstu-typed{color:var(--ink)}
.fstu-caret{background:var(--violet)}

@media (max-width:820px){
  .fstu-body{grid-template-columns:1fr}
  /* On a phone the instruction has to come first: the panel is what the
     visitor is meant to understand, and the table below it is the
     consequence. On desktop they sit side by side so the order is moot. */
  .fstu-ai{order:1; border-bottom:1px solid var(--line)}
  .fstu-table{order:2; border-right:0; border-bottom:0}
  .fstu-th,.fstu-tr{grid-template-columns:56px 30px minmax(0,1fr) 54px minmax(0,90px)}
}
@media (max-width:560px){
  .fs{padding-block:64px}
  .fs-brand{gap:10px}
  .fs-brand-ic{width:44px; height:44px; border-radius:13px}
  .fs-brand-ic svg{width:22px; height:22px}
  .fstu-th span:nth-child(4),.fstu-tr .fstu-pr{display:none}
  .fstu-th,.fstu-tr{grid-template-columns:52px 28px minmax(0,1fr) minmax(0,84px)}
}

/* ── Checkout dialog, on the setsmart.io pattern ───────────────────────
   The structural borrowing is not the styling, it is the decision count:
   their offer screen shows ONE plan, so the visitor decides once instead of
   comparing four tiers before they have agreed to anything. Our four tiers
   are not deleted — the spec keeps price off the page entirely, so the
   picker has to live somewhere — they move behind a "See all plans"
   disclosure. Screen two then asks for the email, and the payment fields
   only appear once it is valid, which is Paddle's native inline behaviour
   rather than anything we simulate. */
.co{border:0; border-radius:var(--r-xl); padding:0; width:min(470px,calc(100vw - 28px));
  box-shadow:0 30px 80px -20px rgba(23,22,31,.35); max-height:calc(100vh - 40px)}
.co::backdrop{background:rgba(23,22,31,.55); backdrop-filter:blur(3px)}
.co__s{padding:30px 30px 0; max-height:calc(100vh - 40px); overflow-y:auto}
.co h2{margin:0 0 8px; font-size:23px; letter-spacing:-.02em}
.co__x{position:absolute; top:14px; right:14px; width:30px; height:30px; border:0; background:none;
  font-size:16px; color:var(--muted); cursor:pointer; border-radius:50%; line-height:1}
.co__x:hover{background:var(--band); color:var(--ink)}

/* offer screen */
.co__ohead{display:flex; align-items:center; gap:10px}
.co__oname{font-size:21px; font-weight:700; letter-spacing:-.02em; color:var(--ink)}
.co__otrial{font-size:10.5px; font-weight:700; letter-spacing:.07em; text-transform:uppercase;
  color:#fff; background:var(--ink); border-radius:999px; padding:4px 9px}
.co__osub{margin:7px 0 0; font-size:14px; color:var(--muted)}
/* The figure the visitor is here for: zero today, at a size nothing else on
   the screen competes with. */
.co__price{display:flex; align-items:baseline; gap:8px; margin-top:20px}
.co__pnow{font-size:44px; line-height:1; font-weight:700; letter-spacing:-.04em; color:var(--ink)}
.co__ptoday{font-size:15px; color:var(--muted)}
.co__pthen{margin:9px 0 0; font-size:14px; color:var(--ink-2)}
.co__pthen b{color:var(--ink); font-weight:700}
/* Rectangular rather than the page's pill, as on the reference: inside a
   dialog the pill reads as a floating marketing button, where a squared
   block reads as the form's submit — which is what it is here. */
.co__go{width:100%; margin-top:20px; border-radius:12px; height:56px; font-size:16px}
.co__ok{border-radius:12px}
.co__row input{border-radius:12px}
/* The four tiers are keyed on one variable — catalogue size — so the dialog
   asks that one question instead of showing four prices to arbitrate. On a
   free trial the tier has no consequence today anyway, so it should not be
   the first decision on the screen. */
.co__pick{display:block; margin:0 0 16px; font-size:15px; font-weight:600; color:var(--ink)}
.co__slider{padding:0 2px}
.co__slider input[type="range"]{-webkit-appearance:none; appearance:none; width:100%;
  height:6px; border-radius:3px; background:var(--line); outline:none; cursor:pointer}
.co__slider input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none; appearance:none;
  width:24px; height:24px; border-radius:50%; cursor:pointer;
  background:linear-gradient(135deg,#6E6BFF,#A855F7); border:3px solid #fff;
  box-shadow:0 2px 10px rgba(113,111,255,.55)}
.co__slider input[type="range"]::-moz-range-thumb{width:20px; height:20px; border-radius:50%;
  cursor:pointer; background:linear-gradient(135deg,#6E6BFF,#A855F7); border:3px solid #fff;
  box-shadow:0 2px 10px rgba(113,111,255,.55)}
.co__slider input[type="range"]:focus-visible{outline:3px solid var(--violet); outline-offset:4px}
.co__ticks{display:flex; justify-content:space-between; margin-top:9px;
  font-size:11.5px; color:var(--muted); font-variant-numeric:tabular-nums}
.co__sizeout{margin:12px 0 0; font-size:14px; color:var(--ink-2)}
.co__sizeout b{color:var(--ink); font-weight:700}

.co__cyc{display:flex; align-items:center; gap:10px; font-size:13.5px; color:var(--muted);
  margin:16px 0 20px; padding-bottom:20px; border-bottom:1px solid var(--line)}

/* Two columns of what you get: the reference's offer screen is mostly this,
   and it is what stops a trial popup reading as a paywall. */
.co__feats{list-style:none; margin:22px 0 0; padding:20px 0 0; border-top:1px solid var(--line);
  display:grid; grid-template-columns:1fr 1fr; gap:9px 18px}
/* The three quota lines are what the tier actually buys, so they lead the
   list and carry the brand colour rather than the generic tick. */
.co__feats li.is-quota{font-weight:600; color:var(--ink)}
.co__feats li.is-quota::before{background:var(--violet-soft)}
.co__feats li.is-quota::after{border-left-color:var(--violet); border-bottom-color:var(--violet)}
.co__feats li{position:relative; padding-left:20px; font-size:12.5px; line-height:1.42; color:var(--ink-2)}
.co__feats li::before{content:""; position:absolute; left:0; top:.28em; width:13px; height:13px;
  border-radius:50%; background:var(--green-soft)}
.co__feats li::after{content:""; position:absolute; left:3.4px; top:.55em; width:5px; height:2.6px;
  border-left:1.5px solid var(--green); border-bottom:1.5px solid var(--green); transform:rotate(-45deg)}

.co__fine{display:flex; align-items:center; justify-content:center; gap:7px;
  color:var(--green); font-size:13px; font-weight:500; margin:20px 0 22px}
.co__shield,.co__lock,.co__card{display:inline-block; flex:none; background:currentColor}
.co__shield{width:13px; height:14px;
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2l8 3.5V12c0 5-3.4 8.6-8 10-4.6-1.4-8-5-8-10V5.5z' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='M8.6 12l2.4 2.4 4.4-4.6' fill='none' stroke='black' stroke-width='2'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2l8 3.5V12c0 5-3.4 8.6-8 10-4.6-1.4-8-5-8-10V5.5z' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='M8.6 12l2.4 2.4 4.4-4.6' fill='none' stroke='black' stroke-width='2'/%3E%3C/svg%3E") center/contain no-repeat}
.co__lock{width:11px; height:13px;
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect x='4' y='10' width='16' height='11' rx='2' fill='black'/%3E%3Cpath d='M8 10V7a4 4 0 0 1 8 0v3' fill='none' stroke='black' stroke-width='2'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect x='4' y='10' width='16' height='11' rx='2' fill='black'/%3E%3Cpath d='M8 10V7a4 4 0 0 1 8 0v3' fill='none' stroke='black' stroke-width='2'/%3E%3C/svg%3E") center/contain no-repeat}
.co__card{width:22px; height:17px; color:var(--line-strong);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 20'%3E%3Crect x='1.5' y='2' width='21' height='16' rx='2.5' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='M1.5 7.5h21' stroke='black' stroke-width='2'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 20'%3E%3Crect x='1.5' y='2' width='21' height='16' rx='2.5' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='M1.5 7.5h21' stroke='black' stroke-width='2'/%3E%3C/svg%3E") center/contain no-repeat}

/* the sign-in bar is flush to the dialog's bottom edge, as on the reference */
.co__foot{margin:0 -30px; padding:15px 30px; border-top:1px solid var(--line);
  background:var(--band); text-align:center; font-size:13px; color:var(--muted);
  border-radius:0 0 var(--r-xl) var(--r-xl)}
.co__foot a{color:var(--ink); font-weight:600; text-decoration:none}
.co__foot a:hover{text-decoration:underline}

/* pay screen */
.co__back{border:0; background:none; color:var(--muted); font:inherit; font-size:14px;
  cursor:pointer; padding:0 0 14px}
.co__back:hover{color:var(--ink)}
.co__lede{color:var(--muted); font-size:13.5px; line-height:1.5; margin:0 0 16px}
.co__sum{display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding:14px 16px; border-radius:var(--r-m); background:var(--band); margin-bottom:20px;
  font-size:14px}
.co__sum b{font-weight:600}
.co__sum span{font-weight:700}
.co__lbl{display:block; font-size:13px; font-weight:600; margin-bottom:7px}
.co__lbl--pay{margin-top:18px}
.co__row{display:flex; gap:8px}
.co__row input{flex:1; min-width:0; padding:13px 15px; border:1.5px solid var(--line-strong);
  border-radius:var(--r-m); font-size:16px; font-family:inherit}
.co__row input:focus{outline:0; border-color:var(--violet); background:var(--violet-soft)}
.co__ok{flex:none; padding:0 20px; border:0; border-radius:var(--r-m); background:var(--ink);
  color:#fff; font:inherit; font-weight:600; cursor:pointer}
.co__ok:hover{background:#000}
.co__pay{margin-top:0; min-height:104px; border:1.5px dashed var(--line-strong);
  border-radius:var(--r-m); display:flex; flex-direction:column; align-items:center;
  justify-content:center; gap:10px; padding:16px}
.co__ph{color:var(--muted); font-size:13px; text-align:center}
.co__frame{width:100%}
.co__trust{display:flex; flex-wrap:wrap; justify-content:center; gap:8px 18px; margin:16px 0 0;
  font-size:12.5px; color:var(--muted)}
.co__trust span{display:inline-flex; align-items:center; gap:6px}
.co__trust span:first-child{color:var(--green)}
.co__terms{margin:12px 0 16px; text-align:center; font-size:12px; color:var(--muted)}
.co__terms a{color:var(--ink-2); text-decoration:underline}
.co__s[data-screen="pay"] .co__go{margin-top:0; margin-bottom:22px}

@media (max-width:520px){
  .co__s{padding:26px 20px 0}
  .co__foot{margin:0 -20px; padding:14px 20px}
  .co__feats{grid-template-columns:1fr}
  .co__pnow{font-size:38px}
}

/* Task: honour prefers-reduced-motion for transform-based interactions too —
   reveals, the stepper and the dashboard already respect it, but hover/press
   transforms did not. */
@media (prefers-reduced-motion: reduce){
*,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}
}

/* ── Closing band ──────────────────────────────────────────────────────
   Removed at round 5, asked back at round 6 with castli.fr as the model: a
   dark band between the FAQ and the footer that restates the benefit and
   gives the reader a last CTA once every objection has been answered.
   Deliberately hard-coded dark rather than tokenised — the band is dark by
   design in both themes, so it must not flip with the palette. */
.closing{position:relative; overflow:hidden; padding:clamp(72px,9vw,116px) 0;
  /* Same construction as the Feed Studio band (.fs) rather than a second
     dark-section language: layered colour radials over a diagonal ramp,
     a masked grid for texture, blurred orbs for depth, one slow sheen.
     The first two attempts here were a flat ramp — measurably lighter the
     second time, but a single field of colour with nothing happening in it,
     which is what "pas d'effet whaou" meant. */
  background:
    radial-gradient(ellipse 76% 62% at 14% -6%, rgba(147,112,255,.52), transparent 62%),
    radial-gradient(ellipse 58% 46% at 86% 4%, rgba(56,189,248,.24), transparent 64%),
    radial-gradient(ellipse 88% 58% at 54% 110%, rgba(236,72,153,.24), transparent 62%),
    linear-gradient(162deg,#2E2377 0%,#3D2E92 44%,#291E63 100%)}
/* The grid is the texture you can actually see: 72px rules at 6% white,
   masked to fade out before the edges so it never looks like a table. */
.closing::before{content:""; position:absolute; inset:0; pointer-events:none; z-index:0;
  background-image:linear-gradient(rgba(255,255,255,.07) 1px,transparent 1px),
    linear-gradient(90deg,rgba(255,255,255,.07) 1px,transparent 1px);
  background-size:72px 72px;
  -webkit-mask-image:radial-gradient(ellipse 74% 68% at 50% 0%,#000 18%,transparent 80%);
  mask-image:radial-gradient(ellipse 74% 68% at 50% 0%,#000 18%,transparent 80%)}
.closing__orbs{position:absolute; inset:0; pointer-events:none; z-index:0}
.closing__orbs i{position:absolute; border-radius:50%; filter:blur(92px); opacity:.5; display:block}
.closing__orbs .co1{width:460px; height:460px; left:-6%; top:-24%; background:radial-gradient(circle,#6E6BFF,transparent 68%)}
.closing__orbs .co2{width:400px; height:400px; right:-4%; bottom:-26%; background:radial-gradient(circle,#A855F7,transparent 68%)}
.closing__orbs .co3{width:340px; height:340px; left:46%; bottom:-34%; background:radial-gradient(circle,#EC4899,transparent 70%); opacity:.34}
/* One slow pass of light, same timing as the Feed Studio frame. */
.closing__sheen{position:absolute; top:-20%; bottom:-20%; left:-35%; width:30%; z-index:0; pointer-events:none;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.10),transparent);
  transform:skewX(-14deg); animation:fsSheen 13s ease-in-out infinite}
@media (prefers-reduced-motion:reduce){ .closing__sheen{animation:none; opacity:0} }
.closing__in{position:relative; z-index:2; text-align:center;
  display:flex; flex-direction:column; align-items:center}
.closing__kicker{margin:0 0 20px; font-family:"JetBrains Mono",ui-monospace,monospace;
  font-size:11.5px; font-weight:600; letter-spacing:.16em; color:#A6A3FF}
.closing__h{margin:0; font-size:clamp(30px,4.4vw,50px); line-height:1.08; letter-spacing:-.025em;
  font-weight:700; color:#fff; max-width:16ch}
.closing__sub{margin:20px 0 0; font-size:16.5px; line-height:1.55; color:#BAB7CC; max-width:48ch}
.closing__cta{margin-top:32px}
.closing__note{margin:16px 0 0; font-size:12.5px; color:#8B889C}

@media (max-width:600px){
  .closing__sub{font-size:15px}
  .closing__cta{width:100%; max-width:340px; justify-content:center}
}

/* ── Declared last on purpose ────────────────────────────────────────────
   Both blocks below were being outranked by rules declared later in this
   file, which is why the losing card stayed white and the step panel kept
   its old entrance. Source order settles it. */

/* The losing Shopping result must LOOK like it is losing: a grey border and a
   red badge were not enough for the contrast to read before the words do. */
.g-cards .gcard.gcard--thin{background:var(--red-soft); border-color:var(--red-line);
  box-shadow:0 0 0 3px rgba(229,72,77,.08)}
.g-cards .gcard--thin .ph{background:#fff}

/* The stepper auto-advances, but the panel used to swap instantly — nothing
   moved, so visitors assumed they had to click. It now arrives from the
   direction of travel, and the active tab pops. */
#how .step__p.is-on{animation:stepSlide .24s cubic-bezier(.2,.8,.3,1) both}
@keyframes stepSlide{from{opacity:0; transform:translateX(28px)} to{opacity:1; transform:none}}
#how .step__tabs button[aria-selected="true"]{animation:stepPop .24s ease both}
@keyframes stepPop{0%{transform:scale(.97)} 60%{transform:scale(1.02)} 100%{transform:scale(1)}}
@media (prefers-reduced-motion:reduce){
  #how .step__p.is-on,#how .step__tabs button[aria-selected="true"]{animation:none}
}


/* ── Readability floors on small screens ───────────────────────────────
   Audited at 320/360/390/414/768/834/1024/1280/1440/1920: no horizontal
   page overflow at any width. What the sweep did find was page copy set
   below 12px and footer links with a 22px tap height. The type inside the
   app mocks is left alone deliberately — a mock whose labels are 15px stops
   reading as an application. */
/* 900px, not 760: an iPad in portrait is 768 wide and was still being served
   the desktop sizes. */
@media (max-width:900px){
  /* Field labels in the before/after records, plus the figure inside them. */
  .prec .prec__k,
  .prec .prec__k b{font-size:12px}
  .prec .prec__pill{font-size:12px}
  .ba .ba__lbl{font-size:12.5px}
  .ba__pass li>span{font-size:12px}
  /* .hero-chans .shopbadge is two classes deep and was winning over a plain
     .shopbadge floor — matched here at equal weight. */
  .hero-chans .shopbadge,
  .cc-marks .shopbadge,
  .ft__badges .shopbadge{font-size:12px}
  .disclaim{font-size:12.5px}
  /* Footer links were 22px tall. Under a thumb that is a miss, and it was
     the same on tablet, which the old 760px cut-off never reached. */
  .ft__col{gap:2px}
  .ft__col a{padding:9px 0; font-size:15px}
  .ft__legal a{padding:8px 0; display:inline-block}
  /* The header nav is still a row of links at tablet width, and they were
     the smallest touch targets on the page at 23px. */
  .hdr__nav a{padding:10px 0}

  /* The chain's sub-lines and the budget rail label are page copy. */
  .chain__n small,
  .chain__raill{font-size:12px}

  /* Review attributions: the store and the vertical were 11.5 and 10.5px,
     which is where the reader checks whether the person is like them. */
  .rv__who small{font-size:12.5px}
  .rv .rv__meta{font-size:12px !important}
  .uct__list li{font-size:14px}
  /* The attribute chips in the before/after records. */
  .prec__hl span{font-size:12px}
  .dash-steps__lbl{font-size:12px}

  /* Captions drawn inside the SVGs scale with the drawing, so a 7.5px label
     in a 400-unit viewBox renders around 5px once the panel is phone-width.
     Raised at the source rather than scaling the whole illustration. */
  .cf-lbl{font-size:10px}
  .v-cap{font-size:12px}
  .lt-lbl,.lt-tail,.lt-note{font-size:12px}
  .lt-cap{font-size:13px}
}


/* ── Swipe instead of stack ────────────────────────────────────────────
   Three blocks were built to be read side by side, and stacking them on a
   phone turned each into a screen and a half of scroll. They scroll
   sideways with snap points instead, which is the native mobile idiom —
   and in the SERP mock's case it is also what Google Shopping itself does
   on a phone, so the mock gets more accurate rather than less. */
/* A swipe worked but never announced itself — nothing told the reader a
   second card existed. Two labelled buttons do, and only one record is on
   screen at a time, which is also the comparison the section is making. */
.ba-tog{display:none}
@media (max-width:820px){
  .ba-tog{display:flex; gap:5px; padding:5px; margin:0 auto 18px; max-width:280px;
    background:var(--surface); border:1px solid var(--line); border-radius:999px}
  .ba-tog__b{flex:1; padding:10px 14px; border:0; border-radius:999px; background:transparent;
    font:inherit; font-size:13.5px; font-weight:600; color:var(--muted); cursor:pointer;
    transition:background .2s ease, color .2s ease}
  .ba-tog__b.is-on{background:var(--ink); color:#fff}
  .ba-tog__b:focus-visible{outline:3px solid var(--violet); outline-offset:2px}

  .ba{display:block; grid-template-columns:none}
  .ba__side{width:100%}
  .ba .prec{max-width:none}
  /* One side at a time; the switch decides which. */
  .ba__side--after{display:none}
  .ba.ba--after .ba__side--before{display:none}
  .ba.ba--after .ba__side--after{display:flex}
  /* The card that arrives fades up rather than replacing the other in one
     frame, so the switch reads as one record changing and not as two
     unrelated cards taking turns. */
  .ba__side{animation:baSwap .2s ease both}
  @media (prefers-reduced-motion:reduce){ .ba__side{animation:none} }
  /* Three labels were saying the same thing: the switch, the BEFORE/AFTER
     pill, and the card's own "Not optimized" badge. The switch is the label
     on mobile, so the pill goes.
     The middle block goes too. Between two cards on a desktop it reads as
     the transformation; stacked on a phone it landed under the record on one
     side and above it on the other, so it read as neither. Switching is what
     shows the change here — thin title against rich title, 2 of 5 against
     5 of 5 — and the gesture says what the caption used to.
     display:none, not removed: it still carries the attributesBefore and
     attributesAfter figures the coherence gate reads out of the HTML. */
  .ba__lbl{display:none}
  .ba__mid{display:none}
}
@media (max-width:620px){
  /* Snapping to centre left a sliver of the previous and next card on both
     edges, which reads as a rendering fault rather than as a carousel. */
  /* justify-content:center survives from the desktop rule, and on a flex
     container that overflows, centring splits the overflow across BOTH
     edges — the left part then cannot be scrolled to at all. scrollLeft
     reads 0 while the first card sits at -42px. Reset to flex-start. */
  .g-cards{display:flex; grid-template-columns:none; justify-content:flex-start; gap:12px;
    overflow-x:auto; scroll-snap-type:x mandatory; -webkit-overflow-scrolling:touch;
    scroll-padding-left:2px; padding-bottom:8px; scrollbar-width:none}
  .g-cards::-webkit-scrollbar{display:none}
  /* At 86% the row looked like one card with something broken either side.
     Narrow enough that the next two are plainly there, which is what makes
     it read as a row of results rather than a layout fault. */
  .g-cards>*{flex:0 0 47%; min-width:150px; scroll-snap-align:start; max-width:none}
  /* Snap is mandatory, which yanks a programmatic scroll back to the nearest
     card. It comes off only while serpsweep.js is demonstrating. */
  .g-cards.is-sweeping{scroll-snap-type:none}
  .g-cards .ph{height:104px; padding:8px}
  .g-cards .gtitle{font-size:12px; min-height:0}
  .g-cards .gprice{font-size:13px}
  .g-cards .meta{padding:9px 10px 11px; gap:4px}
  .g-cards .kmatch{font-size:9px; padding:2px 6px}
}


/* The CTA labels are long ("Get more visibility on Google Shopping"), and at
   16.5px they wrap to two lines inside a phone-width button. Dropping the
   size keeps every call to action on one line, which is how the hero reads
   and how a button should. */
@media (max-width:560px){
  .btn{font-size:14.5px; padding:15px 20px; gap:7px}
  .co__go{font-size:15px}
}

@keyframes dashPanelIn{from{opacity:0; transform:translateY(8px)} to{opacity:1; transform:none}}
@media (prefers-reduced-motion:reduce){
  .dash__cmp:not([hidden]),.dash__feed:not([hidden]){animation:none}
}

/* Ten rows of a product table is most of a phone screen before the reader
   reaches the part that explains it. Five is enough to read as a catalogue. */
@media (max-width:760px){
  .fstu-tr:nth-child(n+7){display:none}
}

/* The hero's flash reviews rotate in a fixed-height slot, so a quote that
   wraps pushes the row taller and the strip jitters as it cycles. Held to
   one line, with the copy shortened to match rather than truncated. */
@media (max-width:760px){
  .rq__i figcaption{white-space:nowrap; overflow:hidden; text-overflow:ellipsis; font-size:12.5px}
}

@keyframes baSwap{from{opacity:0; transform:translateY(6px)} to{opacity:1; transform:none}}

@keyframes ptSwap{from{opacity:0; transform:translateY(5px)} to{opacity:1; transform:none}}
/* The switch is a mobile device only — above 820px both columns are on
   screen at once and there is nothing to switch between.
   Scoped to a min-width query rather than left as a bare rule: a bare
   .pt-tog{display:none} sitting at the end of the file outranks the
   .pt-tog{display:flex} inside the mobile query on source order alone, and
   the switch never appears. */
@media (min-width:821px){ .pt-tog{display:none} }
