/* ==========================================================================
   Apex Cognition — Shared components
   Defined once, reused across Science / Pricing / FAQ so sections don't
   invent their own treatments.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Button
   -------------------------------------------------------------------------- */

.btn {
  --btn-bg: var(--accent);
  --btn-fg: var(--on-accent);

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: 0.875rem 1.5rem;
  border-radius: var(--r-pill);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
  text-decoration: none;
  position: relative;
  isolation: isolate;
  transition:
    transform var(--dur-fast) var(--ease),
    box-shadow var(--dur-fast) var(--ease),
    background-color var(--dur-fast) var(--ease);
}

.btn::after {
  /* Precise mechanical lift: a tightening glow ring, not a bounce. */
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: var(--glow-accent);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
  z-index: -1;
}

.btn:hover::after { opacity: 1; }
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); background: var(--accent-deep); }

.btn[disabled],
.btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

.btn[disabled]::after { opacity: 0; }

.btn--secondary {
  --btn-bg: transparent;
  --btn-fg: var(--text);
  border: 1px solid var(--line-strong);
}

.btn--secondary:hover {
  --btn-bg: rgba(242, 242, 240, 0.05);
  border-color: var(--accent);
}

.btn--amber {
  --btn-bg: var(--accent-3);
  --btn-fg: var(--on-accent);
}

.btn--amber::after { box-shadow: var(--glow-amber); }
.btn--amber:active { background: var(--accent-3-deep); }

.btn--block { width: 100%; }

.btn--ghost {
  --btn-bg: transparent;
  --btn-fg: var(--text-dim);
  padding-inline: 0;
}

.btn--ghost:hover { --btn-fg: var(--text); transform: none; }
.btn--ghost::after { display: none; }

/* --------------------------------------------------------------------------
   Card — shared by Science, Pricing, FAQ
   -------------------------------------------------------------------------- */

.card {
  position: relative;
  background: var(--bg-raised);
  background-image: var(--halftone);
  background-size: var(--halftone-size);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--space-6);
  box-shadow: var(--glow-soft);
  overflow: hidden;
  transition: border-color var(--dur-mid) var(--ease), transform var(--dur-mid) var(--ease);
}

/* Top-edge highlight — the "machined panel" tell from the reference art. */
.card::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(242, 242, 240, 0.22), transparent);
}

.card:hover { border-color: var(--line-strong); }

.card__title {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  margin-bottom: var(--space-3);
}

.card__body { color: var(--text-dim); font-size: var(--fs-sm); line-height: var(--lh-loose); }

.card--accent { border-color: rgba(124, 111, 240, 0.28); }
.card--amber  { border-color: rgba(255, 122, 69, 0.28); }

/* Quiet variant — no halftone, for the trust block and notes. */
.card--flat {
  background-image: none;
  box-shadow: none;
  background: rgba(242, 242, 240, 0.025);
}

/* --------------------------------------------------------------------------
   Accordion (FAQ)
   -------------------------------------------------------------------------- */

.accordion { border-top: 1px solid var(--line); }

.accordion__item { border-bottom: 1px solid var(--line); }

.accordion__trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  width: 100%;
  padding: var(--space-5) 0;
  text-align: left;
  font-family: var(--font-display);
  font-size: clamp(1.0625rem, 0.98rem + 0.4vw, 1.25rem);
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--text);
  transition: color var(--dur-fast) var(--ease);
}

.accordion__trigger:hover { color: var(--accent-text); }

/* Plus/minus built from two bars — rotates rather than bounces. */
.accordion__icon {
  position: relative;
  flex: none;
  width: 14px;
  height: 14px;
}

.accordion__icon::before,
.accordion__icon::after {
  content: "";
  position: absolute;
  background: currentColor;
  transition: transform var(--dur-mid) var(--ease), opacity var(--dur-mid) var(--ease);
}

.accordion__icon::before { inset-inline: 0; top: 50%; height: 1.5px; margin-top: -0.75px; }
.accordion__icon::after  { inset-block: 0; left: 50%; width: 1.5px; margin-left: -0.75px; }

.accordion__trigger[aria-expanded="true"] { color: var(--accent-text); }
.accordion__trigger[aria-expanded="true"] .accordion__icon::after { transform: rotate(90deg); opacity: 0; }

/* Height animated via grid-template-rows — no JS height measuring. */
.accordion__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur-mid) var(--ease);
}

.accordion__panel[data-open="true"] { grid-template-rows: 1fr; }

.accordion__inner { overflow: hidden; }

.accordion__content {
  padding-bottom: var(--space-5);
  padding-right: var(--space-8);
  color: var(--text-dim);
  font-size: var(--fs-sm);
  line-height: var(--lh-loose);
  max-width: var(--measure);
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity var(--dur-mid) var(--ease) 60ms, transform var(--dur-mid) var(--ease) 60ms;
}

.accordion__panel[data-open="true"] .accordion__content { opacity: 1; transform: none; }

/* --------------------------------------------------------------------------
   Waitlist form
   -------------------------------------------------------------------------- */

.waitlist__row {
  display: flex;
  gap: var(--space-3);
  align-items: stretch;
}

.waitlist__field { flex: 1 1 auto; min-width: 0; }

.waitlist__label {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-mono-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: var(--space-2);
}

.waitlist__input {
  width: 100%;
  padding: 0.875rem 1.125rem;
  background: rgba(242, 242, 240, 0.04);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  color: var(--text);
  font-size: var(--fs-sm);
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}

.waitlist__input::placeholder { color: var(--text-faint); }

.waitlist__input:hover { border-color: rgba(242, 242, 240, 0.28); }

.waitlist__input:focus {
  outline: none;
  border-color: var(--accent);
  background: rgba(124, 111, 240, 0.07);
  box-shadow: 0 0 0 3px rgba(124, 111, 240, 0.18);
}

.waitlist__input[aria-invalid="true"] {
  border-color: var(--accent-3);
  background: rgba(255, 122, 69, 0.06);
}

.waitlist__input[aria-invalid="true"]:focus {
  box-shadow: 0 0 0 3px rgba(255, 122, 69, 0.18);
}

/* Optional name field, revealed only when the form asks for it. */
.waitlist__optional { margin-bottom: var(--space-3); }

.waitlist__status {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-top: var(--space-3);
  min-height: 1.25rem;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  line-height: 1.5;
  letter-spacing: 0.01em;
}

.waitlist__status:empty { margin-top: 0; min-height: 0; }

.waitlist__status[data-state="error"]     { color: var(--accent-3); }
.waitlist__status[data-state="success"]   { color: var(--accent-2); }
.waitlist__status[data-state="duplicate"] { color: var(--accent-text); }
.waitlist__status[data-state="pending"]   { color: var(--text-dim); }

/* Success replaces the row entirely — no dead input sitting there. */
.waitlist__done {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-5);
  border: 1px solid rgba(94, 234, 212, 0.3);
  border-radius: var(--r-lg);
  background: rgba(94, 234, 212, 0.05);
}

.waitlist__done-mark {
  flex: none;
  width: 32px; height: 32px;
  border-radius: 50%;
  border: 1px solid var(--accent-2);
  display: grid;
  place-items: center;
  color: var(--accent-2);
}

.waitlist__done-text { font-size: var(--fs-sm); line-height: var(--lh-normal); }
.waitlist__done-text strong { display: block; color: var(--text); font-weight: 600; }
.waitlist__done-text span { color: var(--text-dim); }

@media (max-width: 30rem) {
  .waitlist__row { flex-direction: column; }
  .waitlist__row .btn { width: 100%; }
}

/* --------------------------------------------------------------------------
   Sticky CTA
   -------------------------------------------------------------------------- */

.sticky-cta {
  position: fixed;
  right: var(--gutter);
  bottom: var(--gutter);
  z-index: var(--z-sticky);
  opacity: 0;
  transform: translateY(12px) scale(0.96);
  pointer-events: none;
  transition:
    opacity var(--dur-mid) var(--ease),
    transform var(--dur-mid) var(--ease);
  box-shadow: 0 12px 32px -12px rgba(0, 0, 0, 0.9);
}

.sticky-cta.is-shown {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

@media (max-width: 40rem) {
  .sticky-cta {
    left: var(--gutter);
    right: var(--gutter);
    width: auto;
    /* Stronger separation: at phone width this floats over body copy, and a
       flat button on dark text reads as an overlap rather than a control. */
    box-shadow:
      0 0 0 6px var(--bg),
      0 16px 40px -12px rgba(0, 0, 0, 1);
  }

  /* Keep the last of the footer clear of the floating button, so nothing is
     permanently trapped underneath it. */
  .site-footer { padding-bottom: calc(var(--space-8) + 2rem); }
}

/* --------------------------------------------------------------------------
   Phone frame (App Showcase placeholders)
   -------------------------------------------------------------------------- */

.phone {
  position: relative;
  width: 100%;
  max-width: 260px;
  margin-inline: auto;
  aspect-ratio: 9 / 19.5;
  border-radius: 32px;
  padding: 8px;
  background: linear-gradient(160deg, #26282E, #121317 40%, #1C1E23);
  box-shadow:
    0 0 0 1px rgba(242, 242, 240, 0.10),
    0 32px 64px -32px rgba(0, 0, 0, 1);
}

.phone__screen {
  position: relative;
  height: 100%;
  border-radius: 25px;
  background: #0C0D11;
  overflow: hidden;
  display: grid;
  place-items: center;
}

/* Notch */
.phone::after {
  content: "";
  position: absolute;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  width: 72px;
  height: 20px;
  border-radius: var(--r-pill);
  background: #0C0D11;
  z-index: 2;
}

/* The placeholder itself — dashed, labeled, deliberately NOT a fake
   screenshot. Replaced wholesale once a real image is set in config.js. */
.phone__placeholder {
  position: absolute;
  inset: 10px;
  border: 1px dashed rgba(242, 242, 240, 0.22);
  border-radius: 18px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-4);
  text-align: center;
  background-image: var(--halftone);
  background-size: 10px 10px;
}

.phone__placeholder-label {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  line-height: 1.5;
  letter-spacing: 0.06em;
  color: var(--text-dim);
}

.phone__placeholder-hint {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-faint);
}

.phone__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

.phone--vip .phone__placeholder { border-color: rgba(255, 122, 69, 0.4); }

/* --------------------------------------------------------------------------
   Tier mark (Pricing) — concentric chrome ring + halftone bars
   -------------------------------------------------------------------------- */

.tier-mark {
  width: 96px;
  height: 96px;
  margin-bottom: var(--space-5);
}

.tier-mark__ring { transform-origin: center; animation: tier-spin 24s linear infinite; }
.tier-mark__ring--rev { animation-direction: reverse; animation-duration: 32s; }

@keyframes tier-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .tier-mark__ring { animation: none; }
}

/* --------------------------------------------------------------------------
   Status pill — "Full — 250/250 spots filled"
   -------------------------------------------------------------------------- */

.status-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: rgba(242, 242, 240, 0.04);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.02em;
  color: var(--text-dim);
}

.status-pill__dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent-3);
  flex: none;
  box-shadow: 0 0 8px var(--accent-3);
}

.status-pill strong { color: var(--text); font-weight: 500; }

/* A tier that has come off capacity reads cyan, not amber. */
.status-pill--open { border-color: rgba(94, 234, 212, 0.35); }
.status-pill--open .status-pill__dot {
  background: var(--accent-2);
  box-shadow: 0 0 8px var(--accent-2);
}

/* --------------------------------------------------------------------------
   Capacity bar
   -------------------------------------------------------------------------- */

.capacity {
  height: 3px;
  border-radius: var(--r-pill);
  background: rgba(242, 242, 240, 0.10);
  overflow: hidden;
}

.capacity__fill {
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--accent-deep), var(--accent));
  transform-origin: left;
  transform: scaleX(0);
  transition: transform var(--dur-xslow) var(--ease);
}

.capacity--amber .capacity__fill {
  background: linear-gradient(90deg, var(--accent-3-deep), var(--accent-3));
}

.is-visible .capacity__fill { transform: scaleX(var(--fill, 1)); }

/* ==========================================================================
   STATE SYSTEM COMPONENTS
   --------------------------------------------------------------------------
   Everything below is tinted by a --tint custom property set on an ancestor
   (usually a state block or a product card), which comes from the --state-*
   tokens. One colour definition in tokens.css, propagated everywhere.
   ========================================================================== */

/* --------------------------------------------------------------------------
   State chip — the hero's four, and The Kit's contents list
   -------------------------------------------------------------------------- */

.state-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: rgba(242, 242, 240, 0.03);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-decoration: none;
  transition:
    color var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease),
    background var(--dur-fast) var(--ease);
}

.state-chip:hover,
.state-chip:focus-visible {
  color: var(--text);
  border-color: color-mix(in srgb, var(--tint) 55%, transparent);
  background: color-mix(in srgb, var(--tint) 10%, transparent);
}

/* The impact map's chips are <button>s rather than the hero's anchors, because
   they select rather than navigate. Reset the UA button styling so the two read
   as the same component. */
button.state-chip {
  font-family: var(--font-mono);
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

/* Selected. Stronger than :hover on purpose — hover is transient, this is
   the current state of the section. */
.state-chip[aria-pressed="true"] {
  color: var(--text);
  border-color: color-mix(in srgb, var(--tint) 80%, transparent);
  background: color-mix(in srgb, var(--tint) 18%, transparent);
}

.state-chip__dot {
  width: 7px;
  height: 7px;
  flex: none;
  border-radius: 50%;
  background: var(--tint);
  box-shadow: 0 0 10px var(--tint);
}

/* --------------------------------------------------------------------------
   Packaging — vector product art
   --------------------------------------------------------------------------
   The <symbol> definitions live once in index.html; these are the fills.
   Label text sits on top as real HTML so the typography is selectable,
   translatable and sharp at any size rather than baked into paths.
   -------------------------------------------------------------------------- */

.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

.pack {
  position: relative;
  display: block;
  width: 100%;
  max-width: 128px;
  margin-inline: auto;
  filter: drop-shadow(0 18px 30px rgba(0, 0, 0, 0.6));
}

.pack__art { display: block; width: 100%; height: auto; }

.pack__cap  { fill: rgba(242, 242, 240, 0.17); }
.pack__rule { fill: rgba(242, 242, 240, 0.16); }

/* The face gradient is defined with the sprite in index.html — see the note
   there for why the highlight is a fill and not a CSS overlay. */
.pack__body {
  fill: url(#pack-face);
  stroke: rgba(242, 242, 240, 0.17);
  stroke-width: 1.2;
}

.pack__band { fill: color-mix(in srgb, var(--tint, var(--accent)) 26%, transparent); }

.pack__label {
  position: absolute;
  top: 42%;
  left: 12%;
  right: 12%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
  line-height: 1.15;
}

.pack__brand {
  font-family: var(--font-mono);
  font-size: 0.5rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.pack__name {
  font-family: var(--font-display);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
}

/* Widths chosen so tin, tube and bottle all render at the SAME height (~181px)
   rather than the same width — six cards showing one range should read as one
   set of products shot the same way. Each follows from its symbol's aspect
   ratio against the bottle's 120×170 at 128px: the tin is 170×120, the tube
   90×170. Change the bottle's max-width above and these two follow. */
.pack--gum  { max-width: 256px; }
.pack--gum  .pack__label { top: 40%; }
.pack--pick { max-width: 96px; }
.pack--pick .pack__label { top: 40%; left: 4%; right: 4%; }
.pack--pick .pack__name  { font-size: 0.66rem; }
.pack--kit  { max-width: 100%; }
.pack--kit  .pack__label { top: 30%; }

/* Real photography, once PRODUCT_SHOTS points at a file. renderProductShots()
   replaces the frame's contents outright, so there's no vector art left to
   style — a photo just fills the box. */
.pack__img { display: block; width: 100%; height: auto; border-radius: var(--r-md); }

/* --------------------------------------------------------------------------
   The rendered vessel — js/sections/product-vessels.js
   --------------------------------------------------------------------------
   Four state cards get the real 3D bottle instead of the vector stand-in. The
   canvas is absolutely positioned over the flat art, which stays in the DOM
   and only fades: it is the fallback for reduced motion, low power, no WebGL
   and no CDN, and in every one of those the canvas is simply never appended.

   Note what does NOT need doing here. The flat art is faded, not removed, so
   it goes on holding the slot's height — which is why there is no
   aspect-ratio on .pack. Pinning one would also fight renderProductShots()
   the day a real photo lands with a different shape.
   -------------------------------------------------------------------------- */

.pack__vessel {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity var(--dur-mid) var(--ease);
}

/* Set on the first drawn frame, so the slot is never momentarily empty. */
.pack__vessel.is-ready { opacity: 1; }

/* Retired by a class on <html>, matching the kit box's .kit-box-live — see
   css/sections.css. .pack__img is included for the same reason it is there:
   a founder adding photography shouldn't get a photo showing through a
   rendered bottle. The label goes too, or "Apex / Push" floats over the tub. */
.product-vessels-live .pack[data-vessel] .pack__art,
.product-vessels-live .pack[data-vessel] .pack__img,
.product-vessels-live .pack[data-vessel] .pack__label {
  opacity: 0;
  transition: opacity var(--dur-mid) var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  /* Belt and braces: initProductVessels() already returns here, so the class
     is never added. If anything ever changes that, the cards must not end up
     showing nothing at all. */
  .product-vessels-live .pack[data-vessel] .pack__art,
  .product-vessels-live .pack[data-vessel] .pack__img,
  .product-vessels-live .pack[data-vessel] .pack__label { opacity: 1; }
  .pack__vessel { display: none; }
}

/* --------------------------------------------------------------------------
   The rendered carry packs — js/sections/carry-packs.js
   --------------------------------------------------------------------------
   Focus Gum and Focus Toothpicks as 3D slide boxes that open once. Same
   contract as .pack__vessel above: appended by JS, faded in on the first drawn
   frame, and simply never created in any degraded path — so the flat art below
   is the fallback without needing an opinion here.

   Where this one differs, and why it isn't just .pack__vessel again:

   The two carry slots are different SHAPES and both are too small for a slide
   box that opens. .pack--gum is 256px wide and letterbox, .pack--pick is 96px
   and portrait; both flat arts render ~181px tall, which is what the max-width
   comment further up is for.

   Those max-widths cannot be the fix. The flat art is faded, not removed, and
   it is still what holds the slot's height — that is why .pack has no
   aspect-ratio. Widening .pack--pick to 380px would take the invisible SVG to
   ~700px tall and blow the card apart.

   So the CANVAS carries the stage and each pack's own box is left alone. The
   stage is much wider than either slot — which takes an explicit `max-width:
   none`, see below, and did not work at all until it got one — and bleeds a
   little above and below by --carry-stage-pad, reserved back as margin on the
   pack so nothing overlaps the meta row above or the card body below.
   Expressing the bleed as a negative inset rather than a fixed height matters:
   the pack shrinks with the viewport, and this way the stage follows it
   instead of drifting out of step with the margin holding room for it.

   The pad is small because WIDTH is what binds the fit, not height — the boxes
   render about 94% of the stage's width and only 60% of its height, so making
   the stage taller buys nothing. It exists as headroom for retuning: push
   CARRY_BOXES.pose.restTilt much past 38 and the assembly's projected height
   starts to bind instead, at which point this is the dial that keeps the box
   from shrinking to compensate.

   .product-card has no overflow: hidden, so the pick card's much wider stage
   spills into the card's own padding — which is where the empty space already
   was. The viewport clamp is what stops that becoming an overflow at 375px.

   All of this only applies once a box has actually drawn — and drawn on THIS
   card, see .is-live below. Nothing here is keyed off .pack__carry merely
   existing, because in every degraded path it doesn't.
   -------------------------------------------------------------------------- */

.pack__carry {
  position: absolute;
  top: calc(-1 * var(--carry-stage-pad, 1rem));
  bottom: calc(-1 * var(--carry-stage-pad, 1rem));
  left: 50%;
  width: min(var(--carry-stage-w, 380px), 100vw - 7rem);
  /* Opting out of the `img, svg, canvas, video { max-width: 100% }` reset in
     base.css. That cap resolves against the containing block — .pack — so it
     silently held this canvas to 256px on the gum card and 96px on the picks
     card, and the width above did nothing at all. The pick box rendered at
     about 90px for three rounds because of this one line.

     This is the only element on the page that is SUPPOSED to be wider than its
     parent, which is exactly why the blanket reset is wrong for it and right
     for everything else — .pack__vessel and #kit-canvas both want 100%, so the
     cap is a no-op there and nothing else was ever affected. Don't "tidy" this
     away when you see a canvas overflowing its slot: that is the feature. */
  max-width: none;
  transform: translateX(-50%);
  opacity: 0;
  transition: opacity var(--dur-mid) var(--ease);
}

/* Reserve exactly what the canvas bleeds, and only once it exists.

   `.is-live` is set on the PACK by carry-packs.js, on that pack's own first
   drawn frame — not as a class on <html>. A root class meant one card drawing
   retired both cards' flat art and reserved both cards' margin, so the second
   card sat empty with its layout already shifted for a canvas that had not been
   built yet. Scoped to .pack[data-carry], so it can't collide with the
   unrelated .hiw__node.is-live over in sections.css. */
.pack[data-carry].is-live {
  margin-block: var(--carry-stage-pad, 1rem);
}

/* Set on the first drawn frame — which is the CLOSED box, before the open
   begins. The fade and the animation must not overlap: a box fading up while
   it is already sliding never reads as having been shut. */
.pack__carry.is-ready { opacity: 1; }

.pack[data-carry].is-live .pack__art,
.pack[data-carry].is-live .pack__img,
.pack[data-carry].is-live .pack__label {
  opacity: 0;
  transition: opacity var(--dur-mid) var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  /* Belt and braces, as above: initCarryPacks() already returns here. */
  .pack[data-carry].is-live .pack__art,
  .pack[data-carry].is-live .pack__img,
  .pack[data-carry].is-live .pack__label { opacity: 1; }
  .pack__carry { display: none; }
  .pack[data-carry].is-live { margin-block: 0; }
}

/* --------------------------------------------------------------------------
   Sold-out pill, price, card meta row
   -------------------------------------------------------------------------- */

.soldout-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.3rem var(--space-3);
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
  white-space: nowrap;
}

/* Same amber dot as .status-pill__dot in the tiers — one out-of-stock signal
   across the page. The label stays faint and uppercase; only the dot carries
   over, so the compact card meta rows don't reflow. */
.soldout-pill::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent-3);
  box-shadow: 0 0 8px var(--accent-3);
}

.price {
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
}

.price__period {
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 400;
  color: var(--text-dim);
}

/* Top strip above the pack, carrying stock state and nothing else. The price
   shared this row briefly and the two read as a pair of competing labels with
   the art stranded beneath them; the price now sits with the name, which is the
   thing it's actually describing. */
.product-card__meta {
  display: flex;
  justify-content: flex-end;
}

/* --------------------------------------------------------------------------
   Product card
   -------------------------------------------------------------------------- */

.product-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-6);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background:
    radial-gradient(120% 90% at 50% 0%, color-mix(in srgb, var(--tint, var(--accent)) 7%, transparent), transparent 70%),
    var(--halftone) 0 0 / var(--halftone-size),
    var(--bg-raised);
  transition: border-color var(--dur-fast) var(--ease);
}

.product-card:hover {
  border-color: color-mix(in srgb, var(--tint, var(--accent)) 32%, var(--line));
}

.product-card__body { display: flex; flex-direction: column; }

/* Name and unit left, price right on the name's own baseline. A two-column grid
   with explicit placement rather than nested flex, so the markup can stay in
   reading order — name, unit, price — while the price still renders up on the
   first row. */
.product-card__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  column-gap: var(--space-4);
}

.product-card__head .product-card__name { grid-area: 1 / 1; }
.product-card__head .product-card__unit { grid-area: 2 / 1; }
.product-card__head .price              { grid-area: 1 / 2; }

.product-card__name {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 600;
  letter-spacing: var(--tracking-display);
}

.product-card__unit {
  margin-top: var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.06em;
  color: var(--text-faint);
}

.product-card__blurb {
  margin-top: var(--space-3);
  font-size: var(--fs-sm);
  line-height: var(--lh-loose);
  color: var(--text-dim);
}

/* --------------------------------------------------------------------------
   Warning block — the nicotine SKU only
   --------------------------------------------------------------------------
   Deliberately does NOT reuse .card: this is not another product panel, and
   it should not visually rhyme with the supplement trust claims.
   -------------------------------------------------------------------------- */

.warning-block {
  margin-top: var(--space-5);
  padding: var(--space-5);
  border: 1px solid rgba(255, 122, 69, 0.42);
  border-radius: var(--r-md);
  background: rgba(255, 122, 69, 0.06);
}

.warning-block__heading {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--accent-3);
}

.warning-block__mark {
  width: 14px;
  height: 14px;
  flex: none;
  border: 1.5px solid var(--accent-3);
  border-radius: 3px;
  position: relative;
}

.warning-block__mark::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 2px;
  width: 1.5px;
  height: 5px;
  margin-left: -0.75px;
  background: var(--accent-3);
}

.warning-block__mark::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 2px;
  width: 1.5px;
  height: 1.5px;
  margin-left: -0.75px;
  background: var(--accent-3);
}

.warning-block ul {
  margin: var(--space-4) 0 0;
  padding-left: var(--space-5);
  display: grid;
  gap: var(--space-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--text-dim);
}

.warning-block strong { color: var(--text); font-weight: 600; }

/* --------------------------------------------------------------------------
   Ingredient list
   -------------------------------------------------------------------------- */

.ingredients {
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.ingredients li {
  padding: 0.25rem var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: rgba(242, 242, 240, 0.025);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--text-dim);
}

/* The carry cards print the per-unit dose in the same chip. The number is the
   thing being claimed — it's what separates the pick from the gum — so it takes
   full-strength text while the ingredient name stays dim. */
.ingredients--dose b {
  color: var(--text);
  font-weight: 600;
}

/* --------------------------------------------------------------------------
   Tier mark variants
   -------------------------------------------------------------------------- */

/* À la carte: no membership, so no colour and no counter-rotation. */
.tier-mark--light { opacity: 0.8; }
.tier-mark--light .tier-mark__ring { animation-duration: 40s; }

/* VIP: the mark reads as live rather than merely heavier. A short charge
   arc runs the outer ring, the core breathes, and the bars flicker faintly.
   Every one of these is disabled under prefers-reduced-motion below. */
.tier-mark--vip .tier-mark__charge {
  /* r=46 -> circumference ~289. A 26-unit dash is a short travelling arc. */
  stroke-dasharray: 26 263;
  filter: drop-shadow(0 0 5px var(--accent-3));
  animation: vip-charge 5.5s linear infinite;
}

.tier-mark--vip .tier-mark__core {
  transform-origin: center;
  animation: vip-breathe 3.4s ease-in-out infinite;
  filter: drop-shadow(0 0 6px var(--accent-3));
}

.tier-mark--vip .tier-mark__bars {
  animation: vip-flicker 7s steps(1, end) infinite;
}

@keyframes vip-charge {
  from { stroke-dashoffset: 289; }
  to   { stroke-dashoffset: 0; }
}

@keyframes vip-breathe {
  0%, 100% { transform: scale(1);    opacity: 0.85; }
  50%      { transform: scale(1.18); opacity: 1; }
}

/* Deliberately shallow and slow — a hint of instability, never a strobe. */
@keyframes vip-flicker {
  0%, 96%, 100% { opacity: 1; }
  97%           { opacity: 0.72; }
  98%           { opacity: 1; }
  99%           { opacity: 0.85; }
}

@media (prefers-reduced-motion: reduce) {
  .tier-mark--vip .tier-mark__charge,
  .tier-mark--vip .tier-mark__core,
  .tier-mark--vip .tier-mark__bars {
    animation: none;
  }
  /* Without the travelling animation the dash would freeze mid-arc, which
     reads as a rendering bug. Show the full ring instead. */
  .tier-mark--vip .tier-mark__charge { stroke-dasharray: none; opacity: 0.5; }
}

/* --------------------------------------------------------------------------
   Quality item
   -------------------------------------------------------------------------- */

.quality-item {
  position: relative;
  padding: var(--space-6);
  padding-top: var(--space-7);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--halftone) 0 0 / var(--halftone-size), var(--bg-raised);
}

.quality-item__index {
  position: absolute;
  top: var(--space-5);
  right: var(--space-5);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.1em;
  color: var(--accent-text);
  opacity: 0.55;
}

.quality-item__label {
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.quality-item p {
  margin-top: var(--space-3);
  font-size: var(--fs-sm);
  line-height: var(--lh-loose);
  color: var(--text-dim);
}

/* The caveat card. Same box as its siblings so the grid stays an even 3×2, but
   dashed and unfilled — it's a disclosure, not a practice. */
.quality-item--note {
  border-style: dashed;
  border-color: var(--line-strong);
  background: none;
  padding-top: var(--space-6); /* no index badge to clear */
}
