/* Home — the evidence: stat cards, the scrolling wall of real slides, the
   three steps.

   The .slide* rules have no counterpart in the markup: main.js builds those
   cards, and it composes the caption class by concatenation
   (`slide__hook--` + preset). Nothing here can be checked against index.html,
   which is why no unused-CSS tool may be pointed at this file. */

/* ── Stat cards ─────────────────────────────────────────────── */

.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(10px, 1.2vw, 16px); }
.stat { position: relative; background: var(--ink); color: var(--cream); border-radius: 22px; padding: clamp(22px, 2.4vw, 30px); }
/* One size across all four. "Automatic" used to need a per-tile step-down (--fs)
   because it was wider than its content box between the 981px four-column floor
   and about 1200px; giving the mark its own band above the value returned the
   full card width, and it now fits at every breakpoint — measured, not assumed. */
.stat b { display: block; font-family: var(--display); font-weight: 900; font-size: clamp(34px, 4vw, 50px); letter-spacing: -.03em; line-height: 1; color: var(--cream); }
.stat p { margin: 14px 0 0; font-size: 14.5px; font-weight: 600; }
.stat small { display: block; margin-top: 6px; font-size: 13px; color: var(--on-ink-dim); font-weight: 400; line-height: 1.45; }

/* Duotone marks: one solid blue shape carrying the colour, one white line on top
   carrying the detail. Two tones, and both of them are already in the system.

   Two tiles used to say what they meant in colour instead — a pastel green
   numeral on "~2 min", a pastel yellow one on "Automatic" — which were the only
   two appearances of either hue on the site, and pastel fills are exactly what
   this palette dropped. The numerals are all white now and the accent moved to
   the marks, so the strip reads as one thing and the blue is the section's
   single accent rather than a third and fourth colour.

   Blue on --ink is 3.4:1: under the 4.5 a numeral would need, comfortably over
   the 3:1 WCAG 1.4.11 asks of a meaningful graphic. Which is the other reason
   the colour lives on the icon and the text stays white.

   Top right, on its own band rather than floated into the corner: pushed over
   with margin-left rather than positioned absolutely, so it still occupies
   height and the value below it gets the full width of the card — which is what
   lets all four values share one font size. */
.stat__icon {
  display: block; margin: 0 0 15px auto;
  width: clamp(24px, 2.4vw, 30px); height: auto;
}
.stat__icon-fill { fill: var(--blue-deep); }
.stat__icon-dot,
.stat__icon-line { fill: none; stroke: var(--cream); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.stat__icon-dot { fill: var(--cream); stroke: none; }
/* The detail line on a shape that is mostly filled — a fold, a ferrule —
   wants to be lighter than an outline, or it cuts the shape in half. */
.stat__icon-line--hair { stroke-width: 1.5; }

/* ── The wall of real slides ────────────────────────────────── */

.wall { padding-block: var(--gap); background: var(--cream-2); overflow: hidden; }
.wall__rows { display: grid; gap: clamp(12px, 1.4vw, 20px); margin-top: clamp(34px, 4vw, 54px); transform: rotate(-1.6deg); width: 108%; margin-left: -4%; }
.row { display: flex; overflow: hidden; }
/* A real loop, not a ping-pong. main.js measures one repeat of the row's card set
   and hands it back as --travel, so the track slides exactly that far and restarts
   on a pixel-identical frame. The old -50% landed half a gap short of a whole
   repeat, and that 10px jump is what read as a row running past its last card;
   ping-ponging away from the seam cost the wall its motion instead of fixing it.
   --dur is derived from a fixed px/s speed, so a phone and a 4K display drift at
   the same pace. Linear, because easing stretched over a cycle this long doesn't
   read as a soft turn — it reads as the wall stalling. */
.row__track {
  display: flex; gap: clamp(12px, 1.4vw, 20px); flex: none;
  animation: slide var(--dur, 60s) linear infinite;
}
.row--b .row__track { animation-direction: reverse; }
/* Nothing to watch while the band is off screen, and letting the clock run there
   only means arriving to find the rows parked at some arbitrary offset. */
.wall.is-idle .row__track { animation-play-state: paused; }
@keyframes slide { to { transform: translateX(calc(var(--travel, 50%) * -1)); } }
@media (prefers-reduced-motion: reduce) {
  /* Decorative band: it stands still as a collage. Stated here because the global
     reduce block would otherwise get there by running a 1µs animation. */
  .row__track { animation: none !important; }
  /* The card under the pointer still comes forward — that is a state, not motion —
     but it arrives rather than growing into it. */
  .slide { transition: none; }
}

/* ── Taking the wall over ───────────────────────────────────── */

/* The same bargain the two phones make (assets/js/takeover.js), in the shape a drifting
   band can take it — see the take-over block in wall.js.

   A pointer on a card stops the drift and lifts that card out of the rest. The others
   fade rather than the band dimming as a whole: it is the CARD being read that matters,
   and a veil over everything would dim it too. */
.wall.is-hot .row__track { animation-play-state: paused; }
.wall.is-hot .slide { opacity: .42; }
.wall.is-hot .slide.is-hot { opacity: 1; transform: scale(1.04); z-index: 1; }

/* A finger gets the rows themselves. They are scrollable from the start on a touch
   screen — the browser settles which element a gesture scrolls as the gesture begins, so
   a row that only became scrollable on the first touch would swallow that first swipe —
   and wall.js stops the drift and folds it into the scroll position on that touch.
   position: relative and overflow-y: hidden for the reasons every other sideways strip
   on this site carries them: without the first the track widens the page, and without
   the second a diagonal swipe slices the row's own tiles off. */
@media (pointer: coarse) {
  .wall .row {
    position: relative; overflow-x: auto; overflow-y: hidden;
    overscroll-behavior-x: contain; scrollbar-width: none;
  }
  .wall .row::-webkit-scrollbar { display: none; }
}

.slide {
  position: relative; flex: none;
  transition: opacity .3s ease-out, transform .3s ease-out;
  width: clamp(140px, 15vw, 196px); aspect-ratio: 9/16;
  border-radius: 16px; overflow: hidden;
  /* The photo is lazy, so this is what a card looks like before it arrives: a
     quiet placeholder sitting on the band, not the near-black hole --ink left. */
  background: #E7E4E0;
  box-shadow: 0 12px 30px -18px rgba(25,27,30,.45);
  /* 1cqw = 1% of the card's width, which is exactly how the product sizes
     captions (fractions of canvas width). The app establishes the same
     container for the same reason — see MediaEditor.css .meditor__canvas. */
  container-type: inline-size;
}
.slide img { width: 100%; height: 100%; object-fit: cover; }

/* Only the two shadow-only presets get a scrim; classic carries a stroke and
   boxed sits on a solid box, so neither needs help off the photo. */
.slide__scrim { position: absolute; inset: 0; background: linear-gradient(to top, rgba(0,0,0,.45) 0%, rgba(0,0,0,.06) 44%, transparent 60%); }

/* The four real caption presets, ported from frontend/src/lib/slideshowStyle.js
   (TEXT_STYLE_PRESETS) with the geometry from slideshowExport.js. All four are
   TikTok Sans, centred, no letter-spacing — as the canvas compositor draws them.
   --x/--y are the product's free-position fractions, set per card. */
.slide__hook {
  position: absolute; margin: 0;
  left: calc(var(--x, .5) * 100%); top: calc(var(--y, .84) * 100%);
  translate: -50% -50%;
  width: 84cqw;                    /* the compositor's 0.08 side padding, both sides */
  font-family: var(--post); text-align: center;
}
.slide__hook--classic {
  color: #fff; font-weight: 600; font-size: 6cqw; line-height: 1.25;
  /* the canvas stroke is centred on the glyph edge, so CSS takes half of 0.008 */
  -webkit-text-stroke: .4cqw #000; paint-order: stroke fill;
}
.slide__hook--pop {
  color: #F5E39B; font-weight: 600; font-size: 6.2cqw; line-height: 1.25;
  text-shadow: 0 .25cqw .8cqw rgba(0,0,0,.5);
}
.slide__hook--boxed {
  color: #000; font-weight: 600; font-size: 6cqw; line-height: 1.46;  /* 1 + 2*0.23 */
}
.slide__hook--boxed span {
  background: #fff; box-decoration-break: clone; -webkit-box-decoration-break: clone;
  padding: .23em .385em; border-radius: .154em;
}
.slide__hook--punch {
  color: #F5A3A3; font-weight: 700; font-size: 7cqw; line-height: 1.25;
  text-shadow: 0 .25cqw .8cqw rgba(0,0,0,.6);
}
.wall__note { margin: clamp(30px, 3.4vw, 46px) auto 0; text-align: center; font-size: 13.5px; color: var(--ink-50); max-width: 60ch; }

/* ── Steps ──────────────────────────────────────────────────── */

.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(16px, 1.8vw, 24px); }
/* The number is an index, so it sits in the corner and stays out of the reading
   column; everything that IS the content — art, title, body, timing — is centred
   on one axis so the three cards scan as a set. */
.step {
  position: relative;
  background: #fff; border: 1px solid var(--line); border-radius: 24px;
  padding: clamp(46px, 4vw, 54px) clamp(22px, 2.4vw, 30px) clamp(24px, 2.6vw, 32px);
  box-shadow: var(--shadow);
  display: flex; flex-direction: column; align-items: center; text-align: center;
}
.step__n {
  position: absolute; top: clamp(16px, 1.6vw, 20px); left: clamp(16px, 1.6vw, 20px);
  display: inline-grid; place-items: center; width: 32px; height: 32px;
  border-radius: 999px; border: 1.5px solid var(--ink); color: var(--ink);
  font-family: var(--display); font-weight: 900; font-size: 14.5px;
}
/* Holds its square from the start so the mark landing shifts nothing. Empty
   until main.js mounts the animation — the numeral carries the step until then. */
.step__art { display: block; width: 108px; aspect-ratio: 1; margin: 0 auto 6px; }
.step__art svg { display: block; width: 100%; height: 100%; }
/* A slow 6px float while the card is hovered, riding on top of the Lottie's own
   motion. The global prefers-reduced-motion block already collapses this. */
@keyframes step-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }
.step:hover .step__art { animation: step-bob 2s ease-in-out infinite; }
.step .h3 { margin-bottom: 8px; }
.step p { margin: 0 0 16px; color: var(--ink-70); font-size: 15.5px; max-width: 30ch; }
.step small { margin-top: auto; font-size: 13px; font-weight: 600; color: var(--ink-50); }
/* The way out of the summary and into the full list. */
.steps__more { margin: clamp(24px, 3vw, 36px) 0 0; text-align: center; }

/* ── Breakpoints ─────────────────────────────────────────────── */

@media (max-width: 980px) {
  .stats { grid-template-columns: repeat(2, 1fr); }
  .steps { grid-template-columns: 1fr 1fr; }
  .steps > :last-child { grid-column: 1 / -1; }
}
@media (max-width: 720px) {
  /* Not on phones at all (Josep's call): between the hero and the formats the four
     tiles were one more thing to swipe before the product shows up. The strip rules
     below stay for any page that uses .stats outside this band. */
  .stats-strip { display: none; }
  /* The band's lede on a phone: the longer lede above it goes (design C, below). */
  .wall__note { max-width: 32ch; font-size: var(--m-lede); line-height: 1.5; color: var(--ink-70); }
  .stats, .steps { grid-template-columns: 1fr; }
  .steps > :last-child { grid-column: auto; }
  /* One column, and the card is suddenly three times wider than its value needs:
     the band the mark sits in becomes an empty row above every tile. So here it
     comes out of flow and into the corner instead — no value is long enough to
     reach it at this width. */
  .stat__icon { position: absolute; top: clamp(22px, 2.4vw, 30px); right: clamp(22px, 2.4vw, 30px); margin: 0; width: 28px; }

  /* Phones: the four tiles stacked were ~680px of dark blocks between the hero and
     the formats. A swipeable strip keeps every word and costs one tile of height;
     the next tile peeks in to say "swipe". position: relative so nothing absolutely
     positioned inside can escape the scroller and widen the page (the formats bug).
     overflow-y: hidden because overflow-x: auto turns the other axis into a scroller
     too: the strip was 10px taller inside than out, so a slightly diagonal swipe
     scrolled it down and sliced the top off every tile. The reveal keeps its fade but
     not its 18px rise, which a clipping scroller would cut while it plays. */
  .stats {
    position: relative;
    display: flex; gap: 12px; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory; scroll-padding-inline: var(--gut);
    margin-inline: calc(var(--gut) * -1); padding: 2px var(--gut) 8px;
    scrollbar-width: none;
  }
  .stats::-webkit-scrollbar { display: none; }
  .stat { flex: 0 0 min(80vw, 300px); scroll-snap-align: start; }
  .stats .reveal { transform: none; }

  /* The three steps as three rows on the grey band: the index, the title and the time.
     The art and the sentence under each title are desktop-only (design C). The white
     card is what stands out from the band, so it drops its outline. The art stays
     unloaded: marks.js mounts it on intersection, which a display:none box never has. */
  .steps { gap: 10px; }
  .step {
    display: grid; grid-template-columns: 32px minmax(0, 1fr) auto; align-items: center; column-gap: 14px;
    text-align: left; padding: 16px; border-color: transparent; border-radius: 18px;
  }
  .step__art, .step p { display: none; }
  .step__n { position: static; width: 32px; height: 32px; border: 0; background: var(--ink); color: var(--cream); font-size: var(--m-small); }
  .step .h3 { margin: 0; font-size: var(--m-lede); line-height: 1.3; }
  .step small { margin: 0; font-size: var(--m-small); white-space: nowrap; }
  .steps__more { margin-top: 16px; }
  .steps__more .btn { padding: 10px 0; border: 0; background: none; color: var(--blue-deep); font-size: var(--m-body); }
}

/* ── Phones: the page in bands ──────────────────────────────────
   Design "C · One screen, one job" (Josep, 2026-09-15), after a phone review found the
   page cluttered: seven tag-heading-paragraph stacks in a row, 45 bordered shapes, half
   the page explaining the product and six buttons in five styles. At 720px and below
   every section is one band with one heading and one visual. White and grey bands
   alternate instead of tags, most ledes go, and one sticky "Paste your store link" bar
   stands in for the mid-page buttons. See it run and What's in it are desktop-only (the
   demo is in the menu, the features page is linked from the steps). Desktop keeps all
   of it, which is why nothing here sits outside the media query except two hidden-by-
   default elements that only phones show.

   Ids, not classes, for everything page-wide: they exist only on index.html, and they
   outrank the .section, .wall + .section and hero padding rules without depending on
   which stylesheet loads first. The formats chips live in home-formats.css, where they
   have to follow that file's 820px block. */
.stickycta { display: none; }
.faq__more { display: none; margin: 16px 0 0; text-align: center; }
.faq__all { padding: 10px 0; border: 0; background: none; color: var(--blue-deep); font-size: var(--m-body); font-weight: 700; }

@media (max-width: 720px) {
  #see, #features { display: none; }

  #top { padding-bottom: 48px; }
  #formats, #work, #how, #pricing, #faq { padding-block: 48px; }
  #formats, #how, #faq { background: var(--cream-2); }
  #work { background: var(--cream); }
  #work .wall__rows { margin-top: 24px; }
  #formats .pill, #work .pill, #how .pill, #pricing .pill, #faq .pill { display: none; }
  #formats .section__head .lede, #work .lede, #how .section__head .lede, #pricing .section__head .lede { display: none; }

  /* The hero, centred like every band under it. The audience line stays but loses its
     capsule, and the note under the button moves up to be the lede ("three finished
     posts from one link"), replacing the four-line paragraph and the demo link, whose
     target is hidden here. */
  #top .hero__grid > :first-child { display: flex; flex-direction: column; align-items: center; text-align: center; }
  #top .pill--date { padding: 0; border: 0; background: none; box-shadow: none; font-size: var(--m-small); letter-spacing: 0; text-transform: none; color: var(--ink-50); }
  #top .hero__title { margin-top: 12px; }
  #top .hero__sub, #top .hero__watch, #top .hero__tick { display: none; }
  #top .hero__note { max-width: 32ch; margin-top: 16px; font-size: var(--m-lede); line-height: 1.5; }
  #top .console, #top .console__error { order: 1; align-self: stretch; }

  /* The FAQ: white rows on the grey band, the .faq__key questions first, and the rest
     behind one button once pricing.js has marked the list .is-short. */
  #faq details { border-color: #fff; }
  .faq__key { order: -1; }
  .faq__list.is-short details:not(.faq__key) { display: none; }
  .faq__list.is-short + .faq__more { display: block; }

  /* hero.js adds .is-on between the hero's button and the closing band. visibility,
     not just the transform, so the hidden bar's link is out of the tab order too. It
     steps aside while the cookie notice holds the same corner of the screen. */
  .stickycta {
    display: block; position: fixed; left: 0; right: 0; bottom: 0; z-index: 80;
    padding: 12px var(--gut) calc(12px + env(safe-area-inset-bottom));
    background: rgba(255,255,255,.92); -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
    border-top: 1px solid var(--line);
    transform: translateY(110%); visibility: hidden;
    transition: transform .3s ease-out, visibility 0s linear .3s;
  }
  .stickycta.is-on { transform: none; visibility: visible; transition: transform .3s ease-out, visibility 0s; }
  body:has(.consent.is-open) .stickycta { transform: translateY(110%); visibility: hidden; }
}
