/* ── product.css ────────────────────────────────────────────────────────────
   The plugin subpage. It borrows the homepage's vocabulary wholesale — the
   sheet, the hairlines, the section band, the shot treatment — and adds only
   what a product needs: a hero with a price, a features list, an offer, a FAQ.

   Variant attributes, all set on <html>:
     [data-phero]  split | stack | bleed
     [data-pfeat]  ledger | margin | runon
     [data-pbuy]   inline | band | rail
     [data-pfaq]   accordion | open | twocol

   Mobile is the same in every variant: one column, copy then image. The
   arrangements begin at 48em (features, FAQ) or 64em (hero, offer).
   ───────────────────────────────────────────────────────────────────────── */

.p-section { border-top: 1px solid var(--rule); }

/* ══ 1. Hero ═════════════════════════════════════════════════════════════ */
.p-hero { padding: var(--s7) var(--gutter) var(--s6); }
.p-eyebrow { color: var(--fg-faint); max-width: none; }
.p-title {
  font-family: var(--font-display);
  font-size: clamp(4.8rem, 12vw, 9.6rem);
  line-height: 0.95;
  letter-spacing: var(--track-tight);
  margin-top: var(--s3);
}
.p-tagline {
  margin-top: var(--s4);
  font-size: var(--fs-lead);
  line-height: var(--lh-snug);
  color: var(--fg-muted);
  max-width: 24ch;
}

.p-price { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--s3); margin-top: var(--s5); }
.p-price-now { font-family: var(--font-display); letter-spacing: var(--track-display); font-size: var(--fs-h2); color: var(--accent); }
.p-price-note {
  font-family: var(--font-data); font-size: var(--fs-data);
  letter-spacing: var(--track-data); text-transform: uppercase; color: var(--fg-faint);
}

.p-buy-row { display: flex; flex-wrap: wrap; gap: var(--s3); margin-top: var(--s5); }
.p-buy {
  display: inline-flex; flex-direction: column; align-items: flex-start; gap: var(--s2);
  padding: var(--s5) var(--s6);
  background: var(--accent); color: var(--accent-fg);
  border: 0; cursor: pointer; text-align: left;
  font-family: var(--font-data); font-size: var(--fs-small);
  font-weight: 600;
  letter-spacing: var(--track-data); text-transform: uppercase;
}
/* No opacity fade here — accent-fg is derived to guarantee WCAG AA contrast
   against the accent background (DESIGN-SYSTEM.md), and fading it back
   toward the accent background undermines that guarantee at the low end of
   per-plugin accent variation. The smaller size/weight alone signals
   hierarchy under the main buy label. */
.p-buy-sub { font-size: var(--fs-data); font-weight: 400; }

/* The plugin's true colour appears in exactly one place: a rule under its
   own screenshot. Everywhere else the page uses the palette accent. */
.p-hero-media { margin-top: var(--s6); padding-bottom: var(--s5); border-bottom: 2px solid var(--p, var(--rule)); }
.p-hero-shot {
  display: block; width: 100%; height: auto;
  border-radius: var(--shot-radius);
  box-shadow: var(--shot-lift-lead), 0 30px 60px -26px var(--p-glow);
}

@media (min-width: 64em) {
  .p-hero { display: grid; align-items: center; gap: var(--s8); padding: var(--s9) var(--s5) var(--s8); }

  /* split — copy left, shot right. */
  [data-phero="split"] .p-hero { grid-template-columns: 1fr 1.35fr; }

  /* stack — title across the top, shot full width, price and buy beneath it.
     The copy block becomes a row so the offer sits on one line. */
  [data-phero="stack"] .p-hero { grid-template-columns: 1fr; gap: var(--s6); }
  [data-phero="stack"] .p-hero-media { order: -1; margin-top: 0; }
  [data-phero="stack"] .p-tagline { max-width: 40ch; }
  [data-phero="stack"] .p-price { margin-top: var(--s4); }

  /* bleed — narrow copy column, shot running past the sheet's right edge. */
  [data-phero="bleed"] .p-hero {
    grid-template-columns: 0.75fr 1.6fr;
    padding-right: 0; overflow: hidden;
  }
  [data-phero="bleed"] .p-hero-media { margin-right: calc(-1 * var(--s7)); }
  [data-phero="bleed"] .p-title { font-size: clamp(4.8rem, 6vw, 7.2rem); }
}

/* ══ 2. Demo ═════════════════════════════════════════════════════════════ */
.p-demo-body { padding: var(--s6) var(--gutter) var(--s7); }
.p-demo-heading {
  font-family: var(--font-display);
  font-size: clamp(2.8rem, 5vw, 4.4rem);
  line-height: var(--lh-tight);
  letter-spacing: var(--track-tight);
  margin-bottom: var(--s5);
}
/* Both demo videos are 16:9 natively; fixed here rather than left to
   height:auto so the layout doesn't jump once the video's real dimensions
   load in, and so a poster image that isn't itself exactly 16:9
   (freebounce's) still fills the frame instead of letterboxing. */
.p-video {
  display: block; width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--shot-radius);
  box-shadow: var(--shot-lift), 0 14px 30px -14px var(--p-glow);
  background: var(--surface);
}
@media (min-width: 48em) { .p-demo-body { padding: var(--s7) var(--gutter) var(--s8); } }

/* ══ 3. Sketch ═══════════════════════════════════════════════════════════ */
.p-sketch-body { padding: var(--s6) var(--gutter) var(--s7); }
.p-sketch-img { display: block; width: 100%; height: auto; }
@media (min-width: 48em) { .p-sketch-body { padding: var(--s7) var(--gutter) var(--s8); } }

/* ══ 4. Features ═════════════════════════════════════════════════════════ */
.p-features-body { padding: var(--s6) var(--gutter) var(--s7); }
.p-features-heading {
  font-family: var(--font-display);
  font-size: clamp(2.8rem, 5vw, 4.4rem);
  line-height: var(--lh-tight);
  letter-spacing: var(--track-tight);
}
.p-feature-list { list-style: none; margin-top: var(--s5); }
.p-feature {
  padding-block: var(--s4);
  border-top: 1px solid var(--rule);
  font-size: var(--fs-text);
}
@media (min-width: 48em) {
  .p-features-body { padding: var(--s7) var(--gutter) var(--s8); }

  /* ledger — heading above, features as full-width ruled rows. */
  [data-pfeat="ledger"] .p-feature { font-size: var(--fs-h3); font-family: var(--font-display); letter-spacing: var(--track-display); }

  /* margin — heading holds the left, features hung small beside it. */
  [data-pfeat="margin"] .p-features-body {
    display: grid; grid-template-columns: 1.3fr 1fr; gap: var(--s8); align-items: start;
  }
  [data-pfeat="margin"] .p-feature-list { margin-top: 0.6em; }
  [data-pfeat="margin"] .p-feature { font-size: var(--fs-small); color: var(--fg-muted); }

  /* runon — the densest: features on one wrapping line, divided by rules. */
  [data-pfeat="runon"] .p-feature-list {
    display: flex; flex-wrap: wrap; gap: 0;
    border-top: 1px solid var(--rule); margin-top: var(--s6);
  }
  [data-pfeat="runon"] .p-feature {
    border-top: 0; border-left: 1px solid var(--rule);
    padding: var(--s4) var(--s5); flex: 1 1 22rem;
  }
  [data-pfeat="runon"] .p-feature:first-child { border-left: 0; padding-left: 0; }
}

/* ══ 5. Pricing ══════════════════════════════════════════════════════════
   Deliberately not two tiers side by side: the plugin is the offer, and
   All-Access is an aside under it. Nothing to compare, nothing to badge. */
.p-pricing-body { padding: var(--s6) var(--gutter) var(--s7); }
.p-pricing-heading {
  font-family: var(--font-display);
  font-size: clamp(2.8rem, 5vw, 4.4rem);
  line-height: var(--lh-tight);
  letter-spacing: var(--track-tight);
}
.p-pricing-sub { margin-top: var(--s2); color: var(--fg-muted); }
.p-offer { margin-top: var(--s6); }
.p-includes { list-style: none; margin-top: var(--s4); }
.p-includes li {
  padding-block: var(--s3);
  border-top: 1px solid var(--rule);
  font-size: var(--fs-small); color: var(--fg-muted);
}
.p-promo-note { margin-top: var(--s4); color: var(--fg-faint); font-size: var(--fs-small); }
.p-alt { margin-top: var(--s7); padding-top: var(--s5); border-top: 1px solid var(--rule); }
.p-alt-label { color: var(--fg-faint); max-width: none; }
.p-alt-price {
  font-family: var(--font-display); letter-spacing: var(--track-display); font-size: var(--fs-h2);
  line-height: 1; margin-top: var(--s2);
}
.p-alt-per { font-family: var(--font-data); font-size: var(--fs-data); color: var(--fg-muted); }
.p-alt-desc { margin-top: var(--s2); color: var(--fg-muted); font-size: var(--fs-small); }
.p-alt-btn {
  margin-top: var(--s5);
  display: inline-flex; padding: var(--s4) var(--s5);
  border: 1px solid var(--rule);
  font-family: var(--font-data); font-size: var(--fs-data);
  letter-spacing: var(--track-data); text-transform: uppercase;
}

@media (min-width: 64em) {
  .p-pricing-body { padding: var(--s7) var(--gutter) var(--s8); }

  /* inline — offer and aside in two columns, aside quieter. */
  [data-pbuy="inline"] .p-pricing-body {
    display: grid; grid-template-columns: 1.4fr 1fr;
    column-gap: var(--s8); align-items: start;
  }
  [data-pbuy="inline"] .p-pricing-heading,
  [data-pbuy="inline"] .p-pricing-sub { grid-column: 1 / -1; }
  [data-pbuy="inline"] .p-alt { margin-top: var(--s6); }

  /* band — the offer runs full width on an inverted band, the way the
     section header does; the aside sits below it, plain. */
  [data-pbuy="band"] .p-offer {
    background: var(--fg); color: var(--ground);
    padding: var(--s6) var(--s5); margin-top: var(--s6);
  }
  [data-pbuy="band"] .p-offer .p-includes li { color: inherit; opacity: 0.75; border-top-color: currentColor; }
  [data-pbuy="band"] .p-offer .p-price-now { color: inherit; }
  [data-pbuy="band"] .p-offer .p-buy { background: var(--ground); color: var(--fg); }
  [data-pbuy="band"] .p-offer .p-promo-note { color: inherit; opacity: 0.75; }

  /* rail — the offer sticks alongside as you read the includes. */
  [data-pbuy="rail"] .p-pricing-body {
    display: grid; grid-template-columns: 1fr 32rem; column-gap: var(--s8); align-items: start;
  }
  [data-pbuy="rail"] .p-pricing-heading,
  [data-pbuy="rail"] .p-pricing-sub { grid-column: 1; }
  [data-pbuy="rail"] .p-offer {
    grid-column: 2; grid-row: 1 / span 3;
    position: sticky; top: var(--s5);
    border: 1px solid var(--rule); padding: var(--s5);
  }
  [data-pbuy="rail"] .p-alt { grid-column: 1; }
}

/* ══ 6. FAQ ══════════════════════════════════════════════════════════════ */
.p-faq-list { list-style: none; }
.p-faq-index { margin-right: var(--s3); }
.p-faq-item { border-top: 1px solid var(--rule); }
.p-faq-item:first-child { border-top: 0; }
/* On the standalone FAQ page, this static rule is replaced by the animated
   stroke-draw line (base.css) — hidden here so the draw-in isn't just
   retracing an already-visible line. prefers-reduced-motion restores it:
   motion.js never builds the SVG overlay in that case, so this is the only
   line those visitors get. */
#faq .p-faq-item[data-stroke-draw] { border-top-color: transparent; }
@media (prefers-reduced-motion: reduce) {
  #faq .p-faq-item[data-stroke-draw] { border-top-color: var(--rule); }
}
.p-faq-q {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s4);
  width: 100%; padding: var(--s5) var(--gutter);
  background: none; border: 0; cursor: pointer; text-align: left;
  font-size: var(--fs-h3); font-family: var(--font-display); letter-spacing: var(--track-display); color: inherit;
}
/* A plus that becomes a minus. Two rules, no icon set, no rotation. */
.p-faq-mark { position: relative; flex: none; width: 1.4rem; height: 1.4rem; }
.p-faq-mark::before,
.p-faq-mark::after {
  content: ""; position: absolute; background: var(--fg-muted);
  left: 0; right: 0; top: 50%; height: 1px;
}
.p-faq-mark::after { transform: rotate(90deg); }
.p-faq-q[aria-expanded="true"] .p-faq-mark::after { display: none; }
/* Height animates via max-height (0 → a generous cap) rather than the
   `hidden` attribute — `hidden` is display:none, which can't be
   transitioned. Tried grid-template-rows (0fr -> 1fr) first, but a grid
   item's padding doesn't shrink with its track the way it does under
   max-height + overflow:hidden, so a "closed" item still showed a sliver
   of its first line — max-height clamps the whole border-box, padding
   included, which is what actually gets to 0. The cap (60rem) only has to
   clear the longest real answer; overflow:hidden makes the exact number
   harmless. */
.p-faq-a {
  max-height: 0;
  overflow: hidden;
  color: var(--fg-muted);
  max-width: 62ch;
}
/* Specificity note: base.css's theme-crossfade rule (`.sheet
   *:not(img):not(video)`) sets the whole `transition` shorthand at
   (0,1,2) specificity — higher than a bare `.p-faq-a` (0,1,0) — and a
   shorthand fully replaces rather than merges, so a same-or-lower-
   specificity `transition` here would just get silently dropped. Scoping
   through the parent is what wins the fight. */
.p-faq-item .p-faq-a { transition: max-height var(--dur-reveal) var(--ease-reveal); }
.p-faq-a.is-open { max-height: 60rem; }
.p-faq-a-inner { padding: 0 var(--gutter) var(--s5); }
@media (prefers-reduced-motion: reduce) {
  .p-faq-a { transition: none; }
}
@media (min-width: 48em) {
  /* No cap here: .p-faq-q/.p-faq-a-inner already use --gutter directly, so
     its own 1.6rem→3.2rem step applies without restating it smaller. */

  /* open — no button affordance, every answer showing. */
  [data-pfaq="open"] .p-faq-mark { display: none; }
  [data-pfaq="open"] .p-faq-q { cursor: default; }

  /* twocol — all open, in two columns. Items keep their own rules, so the
     column break never lands mid-answer. */
  [data-pfaq="twocol"] .p-faq-list { columns: 2; column-gap: var(--s8); padding: var(--s5); }
  [data-pfaq="twocol"] .p-faq-item { break-inside: avoid; border-top: 0; margin-bottom: var(--s5); }
  [data-pfaq="twocol"] .p-faq-q { padding: 0 0 var(--s3); font-size: var(--fs-text); }
  [data-pfaq="twocol"] .p-faq-a-inner { padding: 0; font-size: var(--fs-small); }
  [data-pfaq="twocol"] .p-faq-mark { display: none; }
}

/* ══ 7. Compatibility ════════════════════════════════════════════════════
   A ruled row, not pills: these are facts about the product, not tags. */
.p-compat { display: grid; gap: var(--s3); padding: var(--s5) var(--gutter); }
.p-compat-label { color: var(--fg-faint); max-width: none; }
.p-compat-list {
  list-style: none; display: flex; flex-wrap: wrap; gap: var(--s3);
  font-family: var(--font-data); font-size: var(--fs-data);
  text-transform: uppercase;
  color: var(--fg-muted);
}
/* v3 workstream 6 — a hairline box and wider tracking turns each fact into
   a printed panel label (silkscreen on hardware) rather than a plain word
   in a ruled row. */
.p-compat-list li {
  padding: var(--s1) var(--s3);
  border: 1px solid var(--rule);
  letter-spacing: 0.14em;
}
@media (min-width: 48em) {
  .p-compat { grid-template-columns: auto 1fr; gap: var(--s6); align-items: baseline; }
}

/* ══ 8. The other plugins ════════════════════════════════════════════════
   Stands in for a catalogue page. Compact — shot, name, tagline, price — and
   the whole cell is the link, matching the homepage.
   ═════════════════════════════════════════════════════════════════════ */
.p-more-row {
  display: grid;
  gap: 1px;
  background: var(--rule);
  border-top: 1px solid var(--rule);
}
.p-more-item {
  display: grid;
  align-content: start;
  gap: var(--s2);
  background: var(--ground);
  color: inherit;
  padding: var(--s5) var(--gutter);
  min-height: 0;              /* overrides the 44px tap floor on <a> */
}
/* Fixed height, not fixed width: `contain` fits each shot to its own aspect
   ratio, so equal widths would still land the names at different baselines. */
.p-more-shot {
  width: 100%; height: 11rem; object-fit: contain; object-position: left bottom;
  margin-bottom: var(--s4);
  border-radius: var(--shot-radius);
}
.p-more-name { font-family: var(--font-display); letter-spacing: var(--track-display); font-size: var(--fs-h3); }
.p-more-tag { color: var(--fg-muted); max-width: 28ch; }
.p-more-price { color: var(--accent); }
@media (min-width: 48em) {
  .p-more-row { grid-template-columns: repeat(3, 1fr); }
  .p-more-item { padding: var(--s6) var(--s5); }
}

/* ══ The mobile buy bar ══════════════════════════════════════════════════
   Below 64em only. Always present rather than revealed on scroll: no
   listener, no animation, and nothing that can be missed.
   ═════════════════════════════════════════════════════════════════════ */
.p-sticky { display: none; }

@media (max-width: 63.99em) {
  .p-sticky {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 80;
    display: flex; align-items: center; gap: var(--s4);
    padding: var(--s3) var(--gutter);
    padding-bottom: max(var(--s3), env(safe-area-inset-bottom));
    background: var(--ground);
    border-top: 1px solid var(--rule);
  }
  .p-sticky-name {
    font-family: var(--font-display); letter-spacing: var(--track-display); font-size: var(--fs-h3);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .p-sticky-price { color: var(--accent); font-size: var(--fs-text); }
  .p-sticky-btn { margin-left: auto; flex: none; padding: var(--s3) var(--s5); }

  /* Clear the bar so it never covers the last section. */
  .p-more { padding-bottom: 7.2rem; }

  /* The lab bar owns the bottom of the screen in dev; do not stack the two. */
  body:has(.labbar) .p-sticky { display: none; }
  body:has(.labbar) .p-more { padding-bottom: 0; }
}

/* ══ Buy button: the detected platform ═══════════════════════════════════
   One button when the platform is known, the explicit pair when it is not.
   The sub-line names the platform, because this value selects the file the
   customer is emailed — it must never be something they have to infer.

   [data-buy] on <html> picks how the platform line arrives:
     reveal  fades and slides up once
     settle  cycles the other platform, then lands on theirs
     type    wipes in left to right
     none    static
   All four are static under prefers-reduced-motion.
   ═════════════════════════════════════════════════════════════════════ */
/* Grid, not flex-wrap: a flex item's "flex-basis:100%, wrap onto its own
   line" trick only forces a break when the line is already crowded enough
   to overflow — in a wide container (the bundle picker's summary card) the
   detected line and the button both fit side by side with room to spare,
   so nothing wraps and the button ends up stranded wherever there was
   space. Two explicit grid rows put the detected line and the actions row
   in fixed places regardless of how much width is on offer, and the button
   sits in column 1 either way, flush with the row's own left edge. */
.p-buy-row {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  column-gap: var(--s3);
  row-gap: var(--s2);
}

/* ── The "platform detected" line — a terminal readout above the button,
   not one of the [data-buy] variants above: it always plays the same way,
   independent of how the sub-label under the button animates in. ────────── */
.p-buy-detected {
  grid-column: 1 / -1;
  display: flex;
  align-items: baseline;
  margin: 0;
  font-family: var(--font-data);
  font-size: var(--fs-data);
  letter-spacing: var(--track-data);
  text-transform: uppercase;
  color: var(--fg-faint);
}
.p-buy-detected-text {
  display: inline-block;
  overflow: hidden;
  white-space: nowrap;
  vertical-align: text-bottom;
}
.p-buy-cursor {
  display: inline-block;
  width: 0.55em;
  height: 0.85em;
  margin-left: 1px;
  background: currentColor;
  vertical-align: text-bottom;
}
@media (prefers-reduced-motion: no-preference) {
  .p-buy-detected-text {
    animation: buy-decode var(--dur-detect) steps(19, end) forwards;
  }
  .p-buy-cursor {
    animation: buy-cursor-blink 0.8s step-end infinite;
    animation-delay: var(--dur-detect);
  }
}
@keyframes buy-decode {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}
@keyframes buy-cursor-blink { 50% { opacity: 0; } }

/* The escape hatch. Permanently visible and never hover-only: detection is a
   convenience, and the customer always has the last word on which build they
   are buying. */
.p-buy-switch {
  background: none;
  border: 0;
  padding: var(--s2) 0;
  color: var(--fg-muted);
  font-family: var(--font-data);
  font-size: var(--fs-data);
  letter-spacing: var(--track-data);
  text-transform: uppercase;
  text-decoration: underline;
  text-underline-offset: 0.3em;
  cursor: pointer;
}
.p-buy-switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

@media (prefers-reduced-motion: no-preference) {
  /* ── reveal ─────────────────────────────────────────────────────────── */
  [data-buy="reveal"] .p-buy-row[data-detected]:not([data-detected="none"]) .p-buy-sub {
    animation: buy-reveal var(--dur-throw) var(--ease-throw) both;
  }
  @keyframes buy-reveal {
    from { opacity: 0; transform: translateY(0.4em); }
    to   { opacity: 0.7; transform: none; }
  }

  /* ── settle — the other platform scrolls past before theirs lands ────── */
  [data-buy="settle"] .p-buy-row[data-detected]:not([data-detected="none"]) .p-buy-sub {
    animation: buy-settle var(--dur-detect) var(--ease-throw) both;
  }
  @keyframes buy-settle {
    0%   { opacity: 0;   transform: translateY(-1.1em); }
    35%  { opacity: 0.35; transform: translateY(-0.5em); }
    70%  { opacity: 0.5; transform: translateY(0.18em); }
    100% { opacity: 0.7; transform: none; }
  }

  /* ── type — a wipe, not a per-character timer ────────────────────────── */
  [data-buy="type"] .p-buy-row[data-detected]:not([data-detected="none"]) .p-buy-sub {
    animation: buy-type var(--dur-detect) steps(14, end) both;
  }
  @keyframes buy-type {
    from { clip-path: inset(0 100% 0 0); }
    to   { clip-path: inset(0 0 0 0); }
  }
}

@media (max-width: 63.99em) {
  /* In the sticky bar the switch would crowd the button; the full row above
     still carries it, so the bar keeps only the button. */
  .p-sticky .p-buy-switch { display: none; }
  .p-sticky .p-buy-row { margin-left: auto; flex: none; }
  .p-sticky .p-buy-detected { display: none; }
}
