/** Shopify CDN: Minification failed

Line 3277:0 Unexpected "}"

**/
/* Peptide labels calculator — styles for sections/peptide-labels.liquid.
   Scoped to the STATIC id #pl-root (specificity 0,1,0,0 everywhere) so Dawn's
   base.css never wins a contest — the same rule the jar-builder extraction
   established. Every var() carries a fallback: an unresolvable var() drops its
   whole declaration silently. Mobile-first (min-width queries only), V2 canon
   breakpoints (600/768/900). Editor-driven values arrive as custom properties
   set on #pl-root by the section's tiny inline <style>. */

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

#pl-root {
  position: relative; /* the ambient helix strips anchor to the section */
  isolation: isolate; /* their stacking stays inside this page */
  --acc: var(--pl-acc, #1b4dff);
  --acc-2: var(--pl-acc2, #4d75ff);
  /* glass system tokens (audit: keep the translucency values in one place) */
  --pl-glass: rgba(18, 20, 27, .66);
  --pl-glass-deep: rgba(11, 12, 16, .72);
  --pl-vig: rgba(4, 5, 8, .55);
  /* AA text on dark panels — raw accent lands ~4:1 at small sizes (the jar
     audit's P1); same recipe as the builders' --ez-acc-text */
  --acc-text: color-mix(in srgb, var(--acc, #1b4dff) 78%, #fff);
  /* TEXT TIERS. Eleven hand-rolled white alphas (.85 .8 .72 .7 .62 .6 .55
     .5 .45 .4 .3) collapsed to four, because the ladder was decorative
     rather than meaningful — nobody can see .6 against .62.
     The floor moved for a REAL reason: .45 measures 4.48:1 on a glass card,
     i.e. it FAILS AA, and only scraped 4.52 on the bare page void where the
     audit happened to sample it. .50 is the lowest rung that clears 4.5 on
     BOTH surfaces (5.22 card / 5.34 page), so it is the floor and nothing
     may go under it. Ratios measured on this page, not assumed. */
  --pl-tx:   rgba(255, 255, 255, .72);  /* body                 9.5 / 10.2 */
  --pl-tx-2: rgba(255, 255, 255, .6);   /* secondary            7.0 / 7.3  */
  --pl-tx-3: rgba(255, 255, 255, .55);  /* tertiary             6.1 / 6.3  */
  --pl-tx-4: rgba(255, 255, 255, .5);   /* micro, legal — FLOOR 5.2 / 5.3  */
  /* text sitting ON the accent fill of a selected pill is NOT on this
     ladder: that surface is far brighter than the panels, so body .72
     measures 3.79:1 there and fails. It keeps its own brighter value.
     Caught by re-measuring after the first tokenisation pass collapsed it
     into --pl-tx — the alphas above .72 were compensating for a background,
     not expressing a hierarchy. */
  --pl-tx-on-acc: rgba(255, 255, 255, .88);
  /* PRESS TIMING (owner 2026-08-12: "make the buttons feel more responsive
     and less laggy"). Two numbers, one place, so every control on the page
     presses on the same beat instead of drifting apart.
     Measured first, because the obvious suspects were innocent: the click
     handler costs 5ms, click-to-paint 13.7ms, and the page holds a steady
     60fps with ZERO dropped frames and zero long tasks while pills are
     hammered — even with the ambient helix running. So there was no
     rendering work to optimise; the sluggishness was entirely in the
     feedback TIMING.
     press-in stays instant. press-out was 240ms — the 2026-08-07 house
     settle — which is what read as lag on release: the finger is long gone
     while the button is still fading back. 140ms still settles (it is not a
     hard cut) but lands inside the ~150ms window where a response still
     feels attached to the input. The 08-07 rule itself is UNTOUCHED: card
     surfaces still press by brightness dip, never scale — only the
     settle-back duration changed. */
  --pl-press-in: 70ms;
  --pl-press-out: 140ms;
  /* ⚠️ THE AMBIENT LAYERS LIVE ON ::before, NOT HERE — see the block below.
     They used to sit on this element with background-attachment: fixed, which
     was the page's single biggest scroll cost on iPhone (owner 2026-08-13,
     confirmed by an on-device A/B). Do not move them back. */
  color: #fff;
  font-family: 'Manrope', system-ui, sans-serif;
  /* first section on a spacer-hidden dark page: clear the fixed header pill.
     Tokenised because ::before must cancel this padding exactly to cover the
     full box — one source of truth, or the ambience develops a bald strip at
     the top the day someone retunes the header clearance. */
  --pl-pad-top: 116px;
  --pl-pad-x: 20px;
  padding: var(--pl-pad-top) var(--pl-pad-x) 40px;
  min-height: 100vh;
}
/* ---- the ambient canvas, composited ----------------------------------
   Was `background-attachment: fixed` on #pl-root: three layers (two radial
   pools + a 24px dot grid) held still across a ~5-viewport-tall element.
   Fixed attachment forces the element off the compositor's fast scroll path,
   so every frame re-rasterised all three — and the sticky bar's
   backdrop-filter then had to re-sample that same un-cacheable surface.

   Measured on the owner's iPhone, not guessed: a control build was choppy,
   and this sticky-pseudo build was "the smoothest". (An earlier round was
   invalid — the debug badge was itself a fixed pseudo-element and had been
   accidentally promoting #pl-root in every config.)

   STICKY, not fixed: sticky clips to #pl-root's own box, so the ambience
   stops at the section edge exactly as the fixed background did. A fixed
   pseudo would paint over the footer.

   The margins do two jobs: the negative top/side values pull the layer out
   of #pl-root's padding so it covers the full box, and the bottom value is
   calc'd so the three margins plus the 100vh height net to EXACTLY ZERO flow
   space — otherwise the negative top margin would drag every sibling up by
   the padding amount. */
#pl-root::before {
  content: "";
  display: block;
  position: sticky;
  top: 0;
  height: 100vh;
  margin:
    calc(-1 * var(--pl-pad-top))
    calc(-1 * var(--pl-pad-x))
    calc(var(--pl-pad-top) - 100vh);
  z-index: 0;
  pointer-events: none;
  /* (the 24px dot-grid layer was removed 2026-08-19, owner call — the blue
     pools alone carry the ambience; the helix + margin notes are the texture) */
  background:
    radial-gradient(520px 340px at 12% 4%, rgba(27, 77, 255, .14), transparent 68%),
    radial-gradient(620px 420px at 88% 30%, rgba(27, 77, 255, .08), transparent 70%);
}

#pl-root .pl-col { max-width: 520px; margin: 0 auto; position: relative; z-index: 1; }

/* ambient ASCII double helix — ONE large specimen crossing the page at 45°
   behind the content (cards are opaque; it shows through page-background
   gaps and margins). System mono is a GRAPHIC here, not typography —
   character art needs mono metrics; Manrope stays the only typeface.
   PHONES get it as a STILL frame (owner 2026-08-10: "what happened to the
   helixes on mobile") — full presence, zero animation/battery cost, and no
   motion means no pause-control needed there; JS skips the loop <1024.
   The far bokeh layer stays desktop-only (it doubles the repaint work). */
#pl-root .pl-helix {
  display: block;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-45deg);
  z-index: 0;
  pointer-events: none;
  user-select: none;
  contain: layout paint;
}
#pl-root .pl-helix pre {
  margin: 0;
  font: 12px/11px ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  color: color-mix(in srgb, var(--acc, #1b4dff) 24%, transparent);
}
#pl-root .pl-helix--far { display: none; }
/* base hide sits BEFORE the media block or it would win the cascade at
   desktop too (same specificity, later source order — the 08-04 trap) */
#pl-root .pl-lab { display: none; }
@media (min-width: 1024px) {
  /* FIXED to the viewport like the rest of the ambient canvas (the radial
     pools + dot grid ride a sticky ::before — they were
     background-attachment:fixed until 2026-08-13, when that turned out to be
     the page's biggest scroll cost on iPhone; see the #pl-root::before block)
     — the rotating helix holds its ground while the page scrolls. This one
     stays position:fixed: it is a single small layer with contain:layout
     paint, not a multi-viewport painted surface, so it composites cheaply.
     Centered, then rotated 45°; the
     transform sits on the fixed element itself (ancestor transforms are
     what break position:fixed, its own is fine). Fixed elements never
     create scrollable overflow, so the oversized diagonal needs no clip. */
  #pl-root .pl-helix {
    display: block;
    position: fixed;
    top: 50%;
    left: 50%;
    /* --plax = the scroll-parallax offset (JS); it sits BEFORE the rotation
       in the chain so the slide happens along screen-Y, not the rotated axis */
    transform: translate(-50%, -50%) translateY(var(--plax, 0px)) rotate(-45deg);
    z-index: 0;
    pointer-events: none;
    user-select: none;
    will-change: transform;
    contain: layout paint; /* bound each repaint to the strip itself */
  }
  #pl-root .pl-helix pre {
    margin: 0;
    font: 19px/17px ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
    color: color-mix(in srgb, var(--acc, #1b4dff) 30%, transparent);
  }
  /* depth layer 2: the far specimen — same frames, smaller type, dimmer,
     defocused, mirrored so it reads as counter-rotating, parked in the
     upper-right at a different angle.
     THE DEFOCUS IS A SHADOW, NOT A FILTER (measured 2026-08-10, real Chrome
     at 2225×1018 dpr2 — this WAS the audit's open P1). `filter: blur(1.5px)`
     here cost the whole page ~14ms/frame: it forces a full-surface gaussian
     over this layer's ~901×1315 CSS px box (1802×2630 device px), and the
     hero vial's `pl-float` animation keeps something moving every single
     frame, so the filter re-ran continuously — 60fps → 32fps scrolling,
     39fps idle. Verified NOT the cause: the helix text writes (~3.7ms/tick
     of main thread, zero long tasks), the glass backdrop-filters (softening
     them to 8/10px changed nothing on its own), and the parallax movement
     (pinning the layer still left it at 33fps — the filter re-ran anyway).
     Transparent glyphs painting ONLY a blurred text-shadow give the same
     defocused bokeh at raster time — paid once per repaint (5/sec) instead
     of once per frame (60/sec) — and restore 59fps idle / 46 scrolling.
     Tint went 17% → 20% because a blurred shadow spreads the same alpha
     over a wider area; at 17% the layer read fainter than the filter did.
     DO NOT reintroduce `filter:` on this layer — it is the whole cost. */
  #pl-root .pl-helix--far {
    display: block;
    top: 26%;
    left: 72%;
    transform: translate(-50%, -50%) translateY(var(--plax, 0px)) rotate(28deg) scaleX(-1);
  }
  #pl-root .pl-helix--far pre {
    font: 11px/10px ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
    color: transparent;
    text-shadow: 0 0 4px color-mix(in srgb, var(--acc, #1b4dff) 20%, transparent);
  }
  /* researcher's margin notes */
  #pl-root .pl-lab {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    user-select: none;
    transform: translateY(var(--plax, 0px));
    will-change: transform;
  }
  #pl-root .pl-annot {
    position: absolute;
    font: 11px/1.4 ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
    letter-spacing: .05em;
    color: color-mix(in srgb, var(--acc, #1b4dff) 36%, transparent);
    white-space: nowrap;
    /* the wander cycle fades notes out/in as they relocate (JS) */
    transition: opacity 600ms cubic-bezier(.2, .7, .2, 1);
  }
}
/* ambient pause (WCAG 2.2.2) — a quiet fixed pill, only where the motion
   exists (desktop; JS also keeps it hidden under reduced-motion). Base hide
   sits before the media block (cascade order), and [hidden] must out-rank
   the desktop display (the display-beats-hidden trap). */
#pl-root .pl-amb-pause { display: none; }
@media (min-width: 1024px) {
  #pl-root .pl-amb-pause {
    display: inline-flex;
    align-items: center;
    position: fixed;
    left: 14px;
    bottom: 14px;
    z-index: 2;
    padding: 10px 14px;
    background: var(--pl-glass-deep, rgba(11, 12, 16, .72));
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    border: 1px solid var(--smv2-line, rgba(255, 255, 255, .1));
    border-radius: 999px;
    color: var(--pl-tx);
    font: 700 11px/1 'Manrope';
    letter-spacing: .05em;
    cursor: pointer;
    transition: color 150ms, border-color 150ms,
      filter var(--pl-press-out) cubic-bezier(.2, .7, .2, 1);
  }
  #pl-root .pl-amb-pause[hidden] { display: none; }
  #pl-root .pl-amb-pause::after { content: ""; position: absolute; inset: -7px; border-radius: inherit; } /* 45px hit band */
  #pl-root .pl-amb-pause:active { filter: brightness(.92); transition-duration: var(--pl-press-in); }
}

/* depth vignette — darkens the page corners so the bench work stays the
   bright center; painted OVER the ambient layers (::after follows the child
   elements at z0) and UNDER the content column (z1) */
#pl-root::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(120% 95% at 50% 38%, transparent 58%, var(--pl-vig, rgba(4, 5, 8, .55)) 100%);
}

/* Below 1024 the layout wrappers dissolve (display:contents) so the total bar's
   sticky containing block is the whole column: it pins from the first screen —
   the price is visible during every step, and the bar is a persistent, expected
   element instead of a surprise overlay appearing exactly over the dropzone. */
#pl-root .pl-grid, #pl-root .pl-colwrap, #pl-root [data-pl-form] { display: contents; }

/* ---------- header ---------- */
/* text + floating vial: stacked on phones, side by side from 768 up */
#pl-root .pl-head { display: flex; flex-direction: column; gap: 6px; }
#pl-root .pl-vial {
  align-self: center;
  width: auto;
  height: 210px;
  object-fit: contain;
  margin-top: 16px;
  /* the glow grounds it on the dark canvas; fades out well inside the box */
  filter: drop-shadow(0 26px 44px rgba(27, 77, 255, .28));
  animation: pl-float 6s ease-in-out infinite;
  /* the animation is the page's only perpetual motion, and it is expensive
     out of proportion to itself: five backdrop-filter surfaces (two cards,
     the fee card, the bar, the header pill) re-evaluate their blur on every
     frame something behind them moves, so a vial nobody is looking at was
     keeping the whole glass system awake. JS parks it once the hero leaves
     the viewport — which is the entire time anyone is actually configuring
     an order. */
}
@keyframes pl-float {
  0%, 100% { transform: translateY(0) rotate(-1.5deg); }
  50% { transform: translateY(-12px) rotate(1.5deg); }
}
/* paused, not `animation: none` — none would snap the vial back to its
   untransformed position mid-float; play-state freezes it where it is, so
   scrolling back up resumes rather than jumps */
#pl-root .pl-vial.is-parked { animation-play-state: paused; }
@media (min-width: 768px) {
  #pl-root .pl-head { flex-direction: row; align-items: center; justify-content: space-between; gap: 28px; }
  #pl-root .pl-vial { height: 250px; margin-top: 0; flex: 0 0 auto; }
}
@media (min-width: 1024px) {
  /* Compress the hero band so short laptops (1366×768) see the calculator's
     columns from scroll 0 (owner ask 2026-08-07): smaller vial, bigger h1 —
     the heading carries the hero weight instead of vertical air. The h1 and
     intro overrides live in the LATE ≥1024 block at the bottom of this file:
     the base .pl-h1 `font` shorthand comes after this block in source order
     and would win the tie (the 08-04 cascade trap). */
  #pl-root .pl-vial { height: 210px; margin-right: 24px; }
}

#pl-root .pl-kicker {
  font: 700 12px/1 'Manrope';
  letter-spacing: .22em;
  text-transform: uppercase;
  /* --acc-text, not --acc: raw accent measures 3.31:1 here and fails AA at
     12px. This is the exact failure --acc-text was created for after the jar
     audit ("raw accent lands ~4:1 at small sizes") — the kicker was simply
     never moved onto it. */
  color: var(--acc-text, #7d9aff);
}
#pl-root .pl-h1 {
  margin: 10px 0 0;
  font: 800 clamp(28px, 8vw, 36px)/1 'Manrope';
  letter-spacing: -.03em;
  text-transform: uppercase;
  white-space: pre-line;
  text-wrap: balance;
  /* explicit: Dawn's base.css colors headings from --color-foreground (a store
     setting, dark), which beats the inherited #fff on #pl-root */
  color: #fff;
}
#pl-root .pl-intro {
  margin: 14px 0 0;
  font: 500 15px/1.6 'Manrope';
  color: var(--pl-tx-2);
  max-width: 440px;
}
#pl-root .pl-facts { margin-top: 16px; }
/* proof line under the h1 (hero rework 2026-09-04) */
#pl-root .pl-proof { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin: 12px 0 0; font: 600 13px/1.4 'Manrope'; color: var(--pl-tx-2); }
#pl-root .pl-proof-tx strong { font-weight: 800; color: #fff; }
#pl-root .pl-proof-dot { opacity: .55; }
#pl-root [data-pl-fact-price][hidden] { display: none; }
/* PHONE HERO (rework 2026-09-04): the vial photo joins the first screen. Two
   thirds of landings are Instagram phones, and the photo — the thing they
   tapped — started below the fold behind 70 words. The text wrapper goes
   display:contents so the h1, proof, facts, strip and vial are one grid:
   h1 and proof span, the facts stack in one column beside the vial, the
   sister-company strip drops under both. ≥768 keeps the row layout. */
@media (max-width: 767px) {
  #pl-root .pl-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: auto auto auto auto; column-gap: 14px; row-gap: 0; align-items: start; }
  #pl-root .pl-head-tx { display: contents; }
  #pl-root .pl-kicker { grid-column: 1 / -1; }
  #pl-root .pl-h1 { grid-column: 1 / -1; grid-row: 1; }
  #pl-root .pl-proof { grid-column: 1 / -1; grid-row: 2; }
  #pl-root .pl-intro { grid-column: 1 / -1; }
  #pl-root .pl-facts { grid-column: 1; grid-row: 3; grid-template-columns: 1fr; gap: 0; margin-top: 18px; align-self: center; }
  #pl-root .pl-facts > .smv2-infobox:last-child:nth-child(odd) { grid-column: auto; }
  #pl-root .pl-vial { grid-column: 2; grid-row: 3; height: 176px; margin: 18px 0 0; align-self: center; justify-self: end; }
  #pl-root .pl-powered { grid-column: 1 / -1; grid-row: 4; margin-top: 16px; }
}
/* "Powered by Peptide Labels" strip (2026-08-18) — sits under the facts, wears
   the infobox skin (hairline + .03 white, radius 10) so it reads as a fact
   about the page, not a control. Width follows content, capped so the note
   wraps to two lines at most. When it is a link, the hover only brightens
   the hairline (hover:hover-gated) and the ring is the house focus token. */
#pl-root .pl-powered {
  /* full column width, so its edges line up flush with the fact grid above
     (owner 2026-08-18: it sat ~20px short on phones); content-fit from 600
     where the facts row is wider than the strip needs */
  display: flex; width: 100%; align-items: center; gap: 12px;
  margin-top: 12px; padding: 8px 14px 8px 10px;
  max-width: 100%; box-sizing: border-box;
  border: 1px solid rgba(255, 255, 255, .14); background: rgba(255, 255, 255, .03);
  border-radius: 10px; color: inherit; text-decoration: none;
  transition: border-color 200ms cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-powered-logo {
  /* 68 on phones (owner: "hard to see" at 54) — the mark is dense and its
     navy sits close to the panel; a stronger blue lift separates it */
  width: 68px; height: auto; flex: 0 0 auto; display: block;
  filter: drop-shadow(0 6px 14px rgba(27, 77, 255, .42));
}
@media (min-width: 600px) { #pl-root .pl-powered-logo { width: 58px; } }
#pl-root .pl-powered-tx { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
#pl-root .pl-powered-t {
  font: 700 13px/1.25 'Manrope'; letter-spacing: -.01em; color: rgba(255, 255, 255, .8);
}
#pl-root .pl-powered-t strong { color: #fff; font-weight: 800; }
#pl-root .pl-powered-n {
  font: 500 12px/1.4 'Manrope'; color: var(--pl-tx-2); text-wrap: pretty;
}
@media (min-width: 600px) {
  #pl-root .pl-powered { display: inline-flex; width: auto; }
  #pl-root .pl-powered-n { max-width: 30ch; }
}
@media (hover: hover) {
  #pl-root a.pl-powered:hover { border-color: rgba(255, 255, 255, .28); }
}
#pl-root a.pl-powered:focus-visible { outline: none; box-shadow: var(--smv2-focus, 0 0 0 3px rgba(27, 77, 255, .35)); }

/* ---------- desktop floating badge with the proof-lab finishes ----------
   ≥1024 the strip hides and the badge floats in the hero band beside the
   vial (owner 2026-08-18). Three finishes from ~/CLAUDE/prototypes/
   proof-viewer.html, all answering ONE light (--shx/--shy in 0–100 card
   percent, --pfc = pointer-from-centre 0–1), driven by initPoweredFloat:
   · HOLO  — Trainer-Gallery recipe (simeydotme/pokemon-cards-css): a large
             repeating rainbow the card color-dodges against the badge art,
             with a hard-light ellipse riding the cursor INSIDE the foil's
             stacking context (dodge last, exactly like the prototype).
             Masked to the badge alpha so the sheen never spills onto the
             canvas.
   · GLOSS — "sunpillar" streaks (screen, .24) sliding at x + y*.2 so they
             shear rather than translate; also masked to the alpha.
   · EMBOSS — the SVG specular filter (#plEmbossFX in the section) on the
             image, luminance relief; JS moves its fePointLight to the same
             sheen spot, and the brightness swell makes the raised art breathe.
   Tilt rides --rx/--ry on the card; the float bob is on the wrapper so the
   two transforms never fight. Below 1024: display:none, no cost. */
#pl-root .pl-pw { display: none; }
@media (min-width: 1024px) {
  #pl-root .pl-powered { display: none; } /* the float carries the message here */
  #pl-root .pl-pw {
    display: flex; flex-direction: column; align-items: center; gap: 10px;
    flex: 0 0 auto; align-self: center;
    color: inherit; text-decoration: none; border-radius: 14px; /* link reset + ring shape */
    margin-left: auto; margin-right: 44px; /* hug the vial, not the text */
    perspective: 700px;
    /* NO float bob (removed 2026-08-18, owner: "goes out of focus sometimes"):
       a translateY through fractional pixels resamples the layer, and the
       badge's crisp chrome lettering showed it where the vial photo hides it.
       The vial keeps the bob; the badge moves by light and by hand-tilt only. */
  }
  #pl-root .pl-pw-card {
    position: relative; width: 196px; isolation: isolate;
    /* rotateX from -y, rotateY from +x, like the prototype's Card */
    /* JS sets --tilt to rotateX()/rotateY() while tracking and REMOVES it at
       rest, so this is literally `none` and the card rasterises crisp; no
       will-change on purpose — a promoted layer would keep sampling the
       resting raster instead of repainting sharp */
    transform: var(--tilt, none);
    /* the light-blue lift shared with the vial; sits under the finishes */
    filter: drop-shadow(0 22px 40px rgba(27, 77, 255, .26));
  }
  #pl-root .pl-pw-img {
    display: block; width: 100%; height: auto; position: relative; z-index: 1;
    user-select: none; -webkit-user-drag: none;
    /* emboss back by owner request ("I liked the embossed look") — the
       holo was the sheen to quiet, not this. Half the prototype's brightness
       swell: a saturated raster, not ink on white. */
    filter: url(#plEmbossFX) brightness(calc(.97 + var(--pfc, .4) * .06));
  }
  #pl-root .pl-pw-foil,
  #pl-root .pl-pw-gloss {
    position: absolute; inset: 0; pointer-events: none;
    -webkit-mask-image: var(--pw-art); mask-image: var(--pw-art);
    -webkit-mask-size: contain; mask-size: contain;
    -webkit-mask-position: center; mask-position: center;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  }
  #pl-root .pl-pw-foil {
    z-index: 2;
    --space: 5%;
    background-image: repeating-linear-gradient(-22deg,
      hsla(283, 49%, 60%, .75) calc(var(--space) * 1),
      hsla(2, 74%, 59%, .75) calc(var(--space) * 2),
      hsla(53, 67%, 53%, .75) calc(var(--space) * 3),
      hsla(93, 56%, 52%, .75) calc(var(--space) * 4),
      hsla(176, 38%, 50%, .75) calc(var(--space) * 5),
      hsla(228, 100%, 77%, .75) calc(var(--space) * 6),
      hsla(283, 49%, 61%, .75) calc(var(--space) * 7));
    background-size: 300% 400%;
    background-position:
      calc((var(--shx, 50) * .26 + 37) * 1%)
      calc((var(--shy, 30) * .34 + 33) * 1%);
    mix-blend-mode: color-dodge;
    filter: brightness(calc((var(--pfc, .4) * .25) + .5)) contrast(2.3) saturate(1);
    /* .22, not the prototype's full strength: this badge is mostly mid-blue,
       exactly where dodge bites hardest — at 1 the mark vanished into rainbow,
       .6 and .3 still read as too much sheen (owner). .22 kept the deep
       blue; nudged to .3 once the emboss glint was dimmed (owner: "bring
       up the holo a tad", round 8). */
    opacity: .3;
  }
  #pl-root .pl-pw-foil::after {
    content: ''; position: absolute; inset: 0;
    background-image: radial-gradient(farthest-corner ellipse
        at calc((var(--shx, 50) * .5 + 25) * 1%) calc((var(--shy, 30) * .5 + 25) * 1%),
      hsl(0, 0%, 100%) 5%,
      hsla(300, 45%, 28%, .5) 40%,
      hsl(0, 0%, 40%) 120%);
    background-size: 400% 500%; background-position: center center;
    mix-blend-mode: hard-light;
    filter: brightness(calc((var(--pfc, .4) * .1) + .15)) contrast(.85) saturate(1.1);
  }
  #pl-root .pl-pw-gloss {
    z-index: 3;
    background-image: repeating-linear-gradient(133deg,
      rgba(255, 255, 255, 0) 0%,
      rgba(255, 255, 255, .65) 3.8%,
      rgba(255, 255, 255, .95) 4.5%,
      rgba(255, 255, 255, .65) 5.2%,
      rgba(255, 255, 255, 0) 10%,
      rgba(255, 255, 255, 0) 12%);
    background-size: 300% 100%;
    background-position: calc((var(--shx, 50) * .6 + var(--shy, 30) * .12) * 1%) 0;
    mix-blend-mode: screen; opacity: .24;
  }
  #pl-root a.pl-pw:focus-visible { outline: none; box-shadow: var(--smv2-focus, 0 0 0 3px rgba(27, 77, 255, .35)); }
  #pl-root .pl-pw-card { display: block; }
  #pl-root .pl-pw-cap {
    display: block; margin: 0; text-align: center; max-width: 220px;
    font: 500 12px/1.4 'Manrope'; color: var(--pl-tx-2); text-wrap: pretty;
  }
  #pl-root .pl-pw-cap-t { display: block; font-weight: 700; color: rgba(255, 255, 255, .8); }
  #pl-root .pl-pw-cap-t strong { color: #fff; font-weight: 800; }
  #pl-root .pl-pw-cap-n { display: block; margin-top: 2px; }
}
/* 3 facts resolve to a 2-col grid on phones — the odd last chip spans the row
   instead of orphaning at half-width beside dead space */
#pl-root .pl-facts > .smv2-infobox:last-child:nth-child(odd) { grid-column: 1 / -1; }
@media (min-width: 600px) {
  #pl-root .pl-facts > .smv2-infobox:last-child:nth-child(odd) { grid-column: auto; }
}
/* HERO FACTS READ AS FACTS, NOT DOORS (Clarity 2026-08-20>26): the hero fact
   row took 22 dead clicks, all mobile, 16 of them on "Minimum 50 labels
   total" alone — the top-left box, the first thing under the headline. The
   cause is sequence, not the token: the art gate opens the visit with three
   bordered rounded panels you tap, and the fact row directly beneath it is
   four bordered rounded panels you cannot. The chip rule already says boxes
   are for reading (DESIGN-LANGUAGE 3.x), so the fix is to take the card
   costume off HERE rather than make facts interactive or restyle
   .smv2-infobox for the five other sections that use it correctly. Border
   and panel fill go; the blue dot stays and becomes what it always was, a
   bullet. Aligned to the first line, not the box centre — two-line facts
   ("Minimum 50 labels / total") hung their dot in the gutter between lines. */
#pl-root .pl-facts { gap: 10px 18px; }
#pl-root .pl-facts > .smv2-infobox {
  min-height: 0; padding: 2px 0;
  border-color: transparent; background: none;
  align-items: flex-start;
}
#pl-root .pl-facts > .smv2-infobox::before { margin-top: 6px; }
/* FACT ROWS (owner 2026-09-04): icon well + bold lead + muted rest, hairline
   rules between rows on phones (a ledger), a hairline column divider from
   600 where the three sit side by side. Still no panel fill — see the
   dead-click note above. */
#pl-root .pl-facts > .pl-fact { gap: 10px; padding: 9px 0; border: 0; border-top: 1px solid rgba(255, 255, 255, .1); align-items: flex-start; }
#pl-root .pl-facts > .pl-fact:first-child { border-top: 0; padding-top: 0; }
#pl-root .pl-facts > .pl-fact::before { display: none; }
#pl-root .pl-fact-ic { flex: none; display: grid; place-items: center; width: 28px; height: 28px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--acc-2, #4d75ff) 45%, transparent); background: color-mix(in srgb, var(--acc, #1b4dff) 14%, transparent);
  color: var(--acc-text, #7d9aff); }
#pl-root .pl-fact-ic svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
#pl-root .pl-fact-tx { display: flex; flex-direction: column; gap: 2px; min-width: 0; padding-top: 3px; }
#pl-root .pl-fact-tx b { font: 800 14px/1.25 'Manrope'; letter-spacing: -.01em; color: #fff; }
#pl-root .pl-fact-tx b b { font: inherit; color: inherit; }
#pl-root .pl-fact-tx span { font: 500 12px/1.4 'Manrope'; color: var(--pl-tx-3); }
#pl-root .pl-fact-tx span b { font-weight: 700; color: var(--pl-tx-2); }
@media (min-width: 600px) {
  #pl-root .pl-facts { gap: 0 20px; grid-template-columns: repeat(3, minmax(0, 1fr)); }
  #pl-root .pl-facts > .pl-fact { border-top: 0; border-left: 1px solid rgba(255, 255, 255, .1); padding: 2px 0 2px 14px; }
  #pl-root .pl-facts > .pl-fact:first-child { border-left: 0; padding-left: 0; }
}

/* ---------- cards ---------- */
/* GLASS panels (owner 2026-08-10): translucent over the ambient canvas so
   the helix reads through them, blurred for legibility. Deliberate canon
   departure for this page — the opaque --smv2-panel returns as the
   @supports fallback where backdrop-filter is unavailable. */
#pl-root .pl-card {
  position: relative; /* registration marks anchor here */
  background: var(--pl-glass, rgba(18, 20, 27, .66));
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14));
  border-radius: var(--smv2-r-lg, 14px);
  padding: 16px;
  margin-top: 14px;
}
/* REGISTRATION MARKS (owner 2026-08-10: "incorporate more of the scientific
   research theme into the cards"). Press-proof corner ticks, not lab
   iconography — the honest technical register for this page is the PRESSROOM,
   not the clinic. That matters legally as well as visually: the disclaimer
   states we sell labels only, so molecular/clinical cues belong in the
   ambience, never on the product controls.
   Drawn on one pseudo-element as eight hard-stop hairlines (two arms per
   corner). Each layer is a solid 1px rule, not a decorative ramp, so this
   stays inside the V2 "flat panel + hairline" rule for cards; using
   linear-gradient here is a drawing technique, not a gradient treatment.
   They sit in the 16px padding band, clear of content, and are ornamental —
   pointer-events:none so they never eat a tap. */
#pl-root .pl-card::after,
#pl-root .pl-fee::after {
  content: '';
  position: absolute;
  inset: 7px;
  pointer-events: none;
  --rm: rgba(255, 255, 255, .22);
  --arm: 9px;
  background:
    linear-gradient(var(--rm), var(--rm)) left top / var(--arm) 1px no-repeat,
    linear-gradient(var(--rm), var(--rm)) left top / 1px var(--arm) no-repeat,
    linear-gradient(var(--rm), var(--rm)) right top / var(--arm) 1px no-repeat,
    linear-gradient(var(--rm), var(--rm)) right top / 1px var(--arm) no-repeat,
    linear-gradient(var(--rm), var(--rm)) left bottom / var(--arm) 1px no-repeat,
    linear-gradient(var(--rm), var(--rm)) left bottom / 1px var(--arm) no-repeat,
    linear-gradient(var(--rm), var(--rm)) right bottom / var(--arm) 1px no-repeat,
    linear-gradient(var(--rm), var(--rm)) right bottom / 1px var(--arm) no-repeat;
}
#pl-root .pl-fee { position: relative; }
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  #pl-root .pl-card, #pl-root .pl-fee { background: var(--smv2-panel, #12141b); }
  #pl-root .pl-bar { background: color-mix(in srgb, var(--smv2-panel, #12141b) 92%, #000); }
}
/* Spot-white note — the row that used to be a two-pill choice. Icon left,
   copy right, so it reads as information rather than another thing to pick
   (the pills it replaced sat in a grid and looked selectable). */
#pl-root .pl-spotnote {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-top: 8px;
}
#pl-root .pl-spotnote-icon {
  flex: none;
  width: 52px;
  height: 52px;
  margin-top: -4px;
}
#pl-root .pl-spotnote p {
  margin: 0;
  font: 500 13px/1.5 'Manrope', system-ui, sans-serif;
  color: var(--pl-tx-2);
}
@media (max-width: 479px) {
  #pl-root .pl-spotnote-icon { width: 44px; height: 44px; }
}

#pl-root .pl-card-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}
#pl-root .pl-num {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--acc, #1b4dff);
  color: #fff;
  font: 800 13px/24px 'Manrope';
  text-align: center;
}
#pl-root .pl-card-title {
  margin: 0;
  font: 800 16px/1.2 'Manrope';
  letter-spacing: -.01em;
  text-transform: uppercase;
  color: #fff; /* same Dawn heading-color override as .pl-h1 */
}
#pl-root .pl-card-note {
  margin: 10px 0 0;
  font: 500 13px/1.5 'Manrope';
  color: var(--pl-tx-2);
}
/* Autosave status: rides the mix-note line, right-aligned. ALWAYS in flow —
   only opacity changes on reveal, so the card never reflows mid-typing.
   Micro-label voice (pl-microlab idiom), one shade quieter than the note. */
#pl-root .pl-notesrow { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; }
#pl-root .pl-notesrow .pl-mix-note { flex: 1; }
/* phones (mobile walk 2026-09-02): the unlit "Autosaved" chip still reserved
   81px of the row, squeezing the mix note to 221px and three ragged lines —
   the note takes the row, the chip drops under it on the right */
@media (max-width: 599px) {
  #pl-root .pl-notesrow { flex-wrap: wrap; gap: 4px 14px; }
  #pl-root .pl-notesrow .pl-mix-note { flex: 1 1 100%; }
  #pl-root .pl-notesrow .pl-draftnote { margin-left: auto; }
}
#pl-root .pl-draftnote {
  flex: 0 0 auto; margin: 10px 0 0;
  font: 700 11px/1 'Manrope'; letter-spacing: .07em; text-transform: uppercase;
  color: var(--pl-tx-4);
  opacity: 0;
  transition: opacity 200ms cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-draftnote.is-on { opacity: 1; }
#pl-root .pl-draftnote-tick { width: 10px; height: 10px; margin-right: 5px; vertical-align: -1px; color: var(--acc-text, #7d9aff); }
@media (prefers-reduced-motion: reduce) {
  #pl-root .pl-draftnote { transition: none; }
}
/* The inspiration escape hatch at the top of the stock card: pill geometry
   because it is tappable, accent tint because it goes somewhere (down to the
   live Instagram module). Sits between the card note and the first option
   label; press is the brightness dip, not a scale (card-surface rule). */
#pl-root .pl-inspo {
  display: inline-flex; align-items: center; gap: 8px;
  margin: 12px 0 4px; min-height: 44px; padding: 10px 16px;
  background: color-mix(in srgb, var(--acc, #1b4dff) 10%, var(--smv2-tint, #181b23));
  border: 1px solid color-mix(in srgb, var(--acc, #1b4dff) 45%, transparent);
  border-radius: 999px;
  font: 700 13px/1.35 'Manrope'; letter-spacing: 0;
  color: var(--acc-text, #7d9aff); text-decoration: none;
  transition: border-color 150ms ease, filter 140ms cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-inspo svg { width: 15px; height: 15px; flex: 0 0 auto; }
#pl-root .pl-inspo:active { filter: brightness(.92); }
#pl-root .pl-inspo:focus-visible { outline: 2px solid var(--acc-2, #4d75ff); outline-offset: 2px; }
@media (hover: hover) {
  #pl-root .pl-inspo:hover { border-color: color-mix(in srgb, var(--acc, #1b4dff) 75%, transparent); filter: brightness(1.08); }
}
/* phones: the label wraps to two lines — as a full-width centred capsule the
   wrap reads intentional instead of ragged */
@media (max-width: 599px) {
  #pl-root .pl-inspo { width: 100%; justify-content: center; text-align: center; }
}
/* The max-peptides note stands where the add button stood, wearing the
   shared infobox (hairline + blue dot — the hero fact chips' vessel;
   "boxes are for reading"). Local overrides only for what two sentences
   need that a chip's short fact doesn't: reading weight and line-height,
   and the dot pinned to the FIRST line (the base chip centres it, which
   on three lines reads as a floating mid-text bullet). */
#pl-root .pl-rowcap {
  margin-top: 12px;
  align-items: flex-start;
  font: 600 13px/1.5 'Manrope';
  letter-spacing: 0;
}
#pl-root .pl-rowcap::before { margin-top: 7px; }
#pl-root .pl-rowcap[hidden] { display: none; }
#pl-root .pl-card-note, #pl-root .pl-fine { max-width: 72ch; }
#pl-root .pl-card-note a { position: relative; color: var(--acc-text, #7d9aff); text-decoration: none; }
/* 44px hit area on the inline links (the "different size → Instagram" escape
   hatch is 18px tall as text): the transparent ::after grows the target
   without moving the glyphs — the .smv2-burger recipe. */
#pl-root .pl-card-note a::after { content: ""; position: absolute; left: -6px; right: -6px; top: 50%; height: 44px; transform: translateY(-50%); }
/* ⚠️ HOVER GATED (2026-08-13). This was the page's only ungated :hover, and
   on touch a hover state has no exit — the underline latched on after a tap
   and sat there until the buyer tapped something else, which reads as the
   link being stuck "on". Same decide-on-pointer-capability rule as the
   @media (hover: none) block on .pl-row-x further down. */
@media (hover: hover) {
  #pl-root .pl-card-note a:hover { text-decoration: underline; }
}
/* touch gets a real press instead: the underline is the acknowledgment, and
   it leaves on pointerup like every other control's dip */
#pl-root .pl-card-note a.pl-down { text-decoration: underline; color: #fff; }
/* 44px effective target (impeccable audit 2026-08-13: the size-request link
   was the ONE control under the repo's 44px floor — 18px tall, inline, sole
   content of its line). House pattern from CLAUDE.md: transparent ::after
   expands the hit area while the visual stays an 18px text line. ±13px of
   overlay reaches only the note text above and card padding below — no
   other control is within reach, so nothing gets shadowed. */
#pl-root .pl-card-note a::after {
  content: '';
  position: absolute;
  left: -6px;
  right: -6px;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
}

/* (the .pl-swatch span system retired 2026-08-07 — every option card now
   carries a real generated icon; the size card got sm-v2-icon-labels.svg) */
/* material options are CARDS (icon over label, reference: Sticker Shuttle):
   generated sticker-style roll icons; the stacked drop-shadow chain draws the
   white die-cut border uniformly so both icons match, plus a soft ground
   shadow. Filter, not border — it follows the alpha silhouette. */
#pl-root .pl-mat-icon {
  flex: 0 0 auto;
  width: 64px;
  height: 64px;
  object-fit: contain;
  filter:
    drop-shadow(1.5px 0 0 #fff) drop-shadow(-1.5px 0 0 #fff)
    drop-shadow(0 1.5px 0 #fff) drop-shadow(0 -1.5px 0 #fff)
    drop-shadow(0 4px 8px rgba(0, 0, 0, .4));
}
/* two materials, always side by side (auto-fit dropped to 1-col at 375:
   the card's 301px inner width is just under two 150px tracks + gap) */
#pl-root .pl-matgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
/* finish cards: same anatomy as the material cards, three across (fixed 3 so no
   orphan card at any width). Icons are the dice logo wearing each finish. */
#pl-root .pl-fingrid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
#pl-root .pl-fin-icon {
  flex: 0 0 auto;
  width: 64px;
  height: 64px;
  object-fit: contain;
  /* no white-outline chain here: the dice SVGs carry their own die-cut border */
  filter: drop-shadow(0 4px 8px rgba(0, 0, 0, .4));
}
/* finish-demo crossfade: both material states stacked, opacity swapped on
   the root's data-pl-mat — interruptible and compositor-only (the old
   src-swap restarted the SVG animations and read as a blink) */
#pl-root .pl-finstack { display: grid; flex: 0 0 auto; }
#pl-root .pl-finstack .pl-fin-icon { grid-area: 1 / 1; transition: opacity 250ms cubic-bezier(.2, .7, .2, 1); }
#pl-root .pl-fin-icon--holo { opacity: 0; }
#pl-root[data-pl-mat="Holographic"] .pl-fin-icon--holo { opacity: 1; }
#pl-root[data-pl-mat="Holographic"] .pl-finstack .pl-fin-icon--matte { opacity: 0; }
@media (max-width: 480px) {
  /* three-up at 375: pill inner width ~81px; tighter padding keeps "Embossing" on one line */
  #pl-root .pl-fingrid .smv2-pill { padding: 12px 6px; }
  #pl-root .pl-fin-icon { width: 56px; height: 56px; }
}
#pl-root .pl-matgrid .smv2-pill,
#pl-root .pl-fingrid .smv2-pill {
  flex-direction: column;
  justify-content: center;
  gap: 8px;
  min-height: 128px;
  padding: 14px 12px;
  border-radius: var(--smv2-r-lg, 14px);
}
/* size cards: vial on the headline, the die in small type under it; the peel
   icon grows with the vial (3ml → 10ml) so the three read as a ladder */
#pl-root .pl-size-name { font-weight: 700; }
#pl-root .pl-size-dims { font: 500 12px/1.2 'Manrope', system-ui, sans-serif; color: rgba(255,255,255,.55); margin-top: -4px; }
/* on the selected pill the dims sit on SOLID BLUE — 55% white measured 2.79:1
   there, and 3ml is the default selection so everyone saw it (audit 2026-08-19).
   88% white on #1b4dff clears 4.5:1 while still reading one step under the name. */
#pl-root .smv2-pill.is-on .pl-size-dims { color: rgba(255,255,255,.88); }
#pl-root .smv2-pill:nth-child(2) .pl-size-icon { width: 56px; height: 56px; }
#pl-root .smv2-pill:nth-child(1) .pl-size-icon { width: 48px; height: 48px; }
@media (max-width: 480px) {
  /* three-up at 375 like the finish cards */
  #pl-root .pl-sizegrid .smv2-pill { padding: 12px 6px; }
}
#pl-root .pl-sizegrid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
#pl-root .pl-sizegrid .smv2-pill { gap: 6px; }
/* corner dies: a small label outline wearing the corner it names */
#pl-root .pl-corner-die {
  flex: 0 0 auto; width: 64px; height: 30px; box-sizing: border-box;
  border: 2px solid currentColor; background: rgba(255,255,255,.06);
  filter: drop-shadow(0 4px 8px rgba(0, 0, 0, .4));
}
#pl-root .pl-corner-die--round { border-radius: 7px; }
#pl-root .pl-corner-die--sharp { border-radius: 0; }
#pl-root .pl-cornergrid .smv2-pill { min-height: 96px; }
/* ── button craft (owner 2026-08-24: "improve the look of the buttons") ──
   The selected flood alone read flat-poster: four solid slabs with no edge.
   Selected cards get a machined edge — one step lighter border, a top-light
   hairline, and a grounded settle shadow — so selection LIFTS instead of
   just flooding. Unselected cards carry a whisper of the same top light so
   the pair reads as one material family. */
#pl-root .pl-matgrid .smv2-pill,
#pl-root .pl-fingrid .smv2-pill,
#pl-root .pl-sizegrid .smv2-pill,
#pl-root .pl-cornergrid .smv2-pill {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06);
  /* the shared list + box-shadow, so the edge GLIDES between states instead
     of popping; filter stays for the brightness press */
  transition: border-color 150ms, background-color 150ms, color 150ms,
    box-shadow 200ms cubic-bezier(.2, .7, .2, 1),
    filter var(--pl-press-out) cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-matgrid .smv2-pill.is-on,
#pl-root .pl-fingrid .smv2-pill.is-on,
#pl-root .pl-sizegrid .smv2-pill.is-on,
#pl-root .pl-cornergrid .smv2-pill.is-on {
  border-color: var(--acc-2, #4d75ff);
  /* inner ring + top light + grounded settle: the machined-edge trio */
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .14),
    inset 0 1px 0 rgba(255, 255, 255, .30),
    0 14px 30px -14px rgba(5, 8, 20, .85);
}
@media (hover: hover) {
  #pl-root .pl-matgrid .smv2-pill:not(.is-on):hover,
  #pl-root .pl-fingrid .smv2-pill:not(.is-on):hover,
  #pl-root .pl-sizegrid .smv2-pill:not(.is-on):hover,
  #pl-root .pl-cornergrid .smv2-pill:not(.is-on):hover {
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12);
  }
}
/* ── the art gate (owner 2026-08-19): full-viewport scrim that BLURS the page,
   one glass card, three doors. Enter: scrim fades while the card rises 8px on
   the house curve (250ms); exit accelerates out (180ms). The scrim carries the
   blur so the card itself stays crisp above it. ── */
#pl-root .pl-gate { position: fixed; inset: 0; z-index: 140; display: grid; place-items: center; padding: 20px; }
/* THE display:grid ABOVE BEATS THE hidden ATTRIBUTE (author > UA styles) — without
   this line the closed gate is an invisible full-viewport overlay that eats every
   real click on the page. Synthetic el.click() tests DON'T catch it because they
   dispatch on the element without hit-testing. Same gotcha as sm-v2.css's
   [data-smv2-mobile][hidden] override. */
#pl-root .pl-gate[hidden] { display: none !important; }
/* BLUR, not dim (owner 2026-08-19: one or the other) — the page frosts out
   behind the card; only a whisper of tint keeps the blur from reading warm.
   Browsers without backdrop-filter get a real dim instead, because for them
   the blur simply would not exist and the card would float on busy content. */
#pl-root .pl-gate-scrim {
  position: absolute; inset: 0;
  background: rgba(11, 12, 15, .55);
  opacity: 0;
  transition: opacity 200ms cubic-bezier(.2, .7, .2, 1);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  #pl-root .pl-gate-scrim {
    /* blur(16px) + a 12% tint smeared the page's bright blues into glowing
       clouds — "hurts my eyes" (owner 2026-08-19). Calmer frost: less blur,
       and the backdrop itself is DARKENED and DESATURATED instead of tinted
       over, so nothing behind the card glows. */
    background: rgba(11, 12, 15, .30);
    -webkit-backdrop-filter: blur(8px) brightness(.6) saturate(.65);
    backdrop-filter: blur(8px) brightness(.6) saturate(.65);
  }
}
#pl-root .pl-gate-card {
  position: relative;
  width: min(440px, 100%);
  max-height: calc(100dvh - 40px);
  overflow: auto;
  background: var(--pl-glass, rgba(18, 20, 27, .9));
  -webkit-backdrop-filter: blur(18px);
  backdrop-filter: blur(18px);
  border: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14));
  border-radius: var(--smv2-r-xl, 22px);
  padding: 24px 22px 18px;
  box-shadow: var(--smv2-e4, 0 32px 80px -24px rgba(0, 0, 0, .8));
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 200ms cubic-bezier(.2, .7, .2, 1), transform 250ms cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-gate.is-open .pl-gate-scrim { opacity: 1; }
#pl-root .pl-gate.is-open .pl-gate-card { opacity: 1; transform: translateY(0); }
#pl-root .pl-gate:not(.is-open) .pl-gate-scrim { transition-duration: 150ms; }
#pl-root .pl-gate:not(.is-open) .pl-gate-card { transition: opacity 150ms cubic-bezier(.4, 0, 1, 1), transform 180ms cubic-bezier(.4, 0, 1, 1); }
/* SERVICE MODE (owner 2026-08-19: "remove the artwork drop completely — we
   1:1 consult after checkout"). data-pl-artmode="service" on the root hides
   every art chip, the header's Artwork column, and the missing-file note;
   the desktop row + header grids drop the 136px art track together (they
   MUST track each other — see the .pl-rowshead pairing note). */
#pl-root[data-pl-artmode="service"] .pl-artbtn,
#pl-root[data-pl-artmode="service"] .pl-rowshead span:nth-child(5),
#pl-root[data-pl-artmode="service"] .pl-artnote,
#pl-root[data-pl-artmode="ai"] .pl-artbtn,
#pl-root[data-pl-artmode="ai"] .pl-rowshead span:nth-child(5) { display: none !important; }
#pl-root .pl-svcnote, #pl-root .pl-readynote, #pl-root .pl-ainote { display: none; }
#pl-root[data-pl-artmode="service"] .pl-svcnote { display: block; }
#pl-root[data-pl-artmode="ready"] .pl-readynote { display: block; }
#pl-root[data-pl-artmode="ready"] .pl-artnote { display: none; }
#pl-root[data-pl-artmode="ai"] .pl-ainote { display: block; }
#pl-root[data-pl-artmode="ai"] .pl-artnote { display: none; }
@media (min-width: 600px) {
  #pl-root[data-pl-artmode="service"] .pl-row,
  #pl-root[data-pl-artmode="ai"] .pl-row { grid-template-columns: 14px minmax(0, 1fr) 104px 112px 28px; }
  #pl-root[data-pl-artmode="service"] .pl-row .pl-row-x,
  #pl-root[data-pl-artmode="ai"] .pl-row .pl-row-x { grid-column: 5; }
  #pl-root[data-pl-artmode="service"] .pl-rowshead,
  #pl-root[data-pl-artmode="ai"] .pl-rowshead { grid-template-columns: 14px minmax(0, 1fr) 104px 112px 28px; }
}

/* ShineBorder, ported from sv-animations' Svelte source: an inset-0 overlay
   whose radial gradient (300% size) drifts 0%->100%->0% while a
   content-box/padding-box mask XOR keeps only the 1px border ring visible.
   House colors — the blue family, not the component's demo pastels. The card's
   own hairline stays underneath as the resting state; under reduced motion the
   drift stops there (a static ring at rest is just a fancier border). */
#pl-root .pl-gate-shine {
  position: absolute; inset: 0; pointer-events: none;
  border-radius: inherit;
  padding: 1px; /* = the ring's width */
  background-image: radial-gradient(transparent, transparent, #1b4dff, #7d9aff, #8fd0ff, transparent, transparent);
  background-size: 300% 300%;
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  animation: pl-shine 14s linear infinite;
}
@keyframes pl-shine {
  0% { background-position: 0% 0%; }
  50% { background-position: 100% 100%; }
  100% { background-position: 0% 0%; }
}
@media (prefers-reduced-motion: reduce) {
  #pl-root .pl-gate-shine { animation: none; }
}
#pl-root .pl-gate-title { margin: 0; font: 800 22px/1.2 'Manrope'; text-transform: uppercase; letter-spacing: -.02em; color: #fff; }
#pl-root .pl-gate-sub { margin: 8px 0 0; font: 500 14px/1.5 'Manrope'; color: var(--pl-tx-2, rgba(255,255,255,.72)); }
#pl-root .pl-gate-doors { display: grid; gap: 10px; margin-top: 18px; }
#pl-root .pl-gate-door {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  display: block; width: 100%; text-align: left;
  background: var(--smv2-raised, #1a1d26);
  border: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14));
  border-radius: var(--smv2-r-lg, 14px);
  padding: 14px 16px;
  color: #fff;
  transition: border-color 150ms ease, background-color 150ms ease, filter var(--pl-press-out, 140ms) cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-gate-door:active { filter: brightness(.88); transition-duration: var(--pl-press-in, 60ms); }
@media (hover: hover) { #pl-root .pl-gate-door:hover { border-color: var(--acc-2, #4d75ff); } }
#pl-root .pl-gate-door:focus-visible { outline: 2px solid var(--acc-2, #4d75ff); outline-offset: 2px; }
#pl-root .pl-gate-door--main {
  background: color-mix(in srgb, var(--acc, #1b4dff) 16%, var(--smv2-raised, #1a1d26));
  border-color: color-mix(in srgb, var(--acc, #1b4dff) 62%, transparent);
}
#pl-root .pl-gate-door b { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font: 700 15px/1.3 'Manrope'; }
#pl-root .pl-gate-door b em { font: 700 13px/1 'Manrope'; font-style: normal; color: var(--ice, #8fd0ff); white-space: nowrap; } /* ice, not --acc-text: the price tags measured 4.2:1 on the raised door (audit 2026-08-19); ice clears 7:1 */
#pl-root .pl-gate-door span { display: block; margin-top: 3px; font: 500 12.5px/1.5 'Manrope'; color: var(--pl-tx-3, rgba(255,255,255,.55)); }
#pl-root .pl-gate-later {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  display: block; margin: 12px auto 0; position: relative;
  background: none; border: 0; padding: 6px 10px;
  font: 600 13px/1 'Manrope'; color: var(--pl-tx-3, rgba(255,255,255,.55));
}
/* signed-in gate: the folded service/AI doors and the link that opens them */
#pl-root .pl-gate-help { display: grid; gap: 10px; }
#pl-root .pl-gate-help[hidden] { display: none; }
#pl-root .pl-gate-more {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  display: block; margin: 14px auto 0; position: relative;
  background: none; border: 0; padding: 6px 10px;
  font: 700 13px/1 'Manrope'; color: var(--acc-2, #4d75ff);
}
#pl-root .pl-gate-more[hidden] { display: none; }
#pl-root .pl-gate-more::after { content: ""; position: absolute; left: -6px; right: -6px; top: 50%; height: 44px; transform: translateY(-50%); }
#pl-root .pl-gate-more:focus-visible { outline: 2px solid var(--acc-2, #4d75ff); outline-offset: 2px; }
#pl-root .pl-gate-more + .pl-gate-later { margin-top: 6px; }
#pl-root .pl-gate-later::after { content: ""; position: absolute; left: -6px; right: -6px; top: 50%; height: 44px; transform: translateY(-50%); }
#pl-root .pl-gate-later:focus-visible { outline: 2px solid var(--acc-2, #4d75ff); outline-offset: 2px; }
body.pl-gate-lock { overflow: hidden; }
/* while the gate is open, lift the page's own section wrapper above the fixed
   header (sm-v2.css holds it at 60): the theme caps section wrappers' z-index,
   so the gate's z-140 was trapped UNDER the header — nav stayed crisp and
   tappable over the veil. Scoped to the lock class so normal stacking returns
   the moment the gate closes. */
body.pl-gate-lock .shopify-section:has(#pl-root) { position: relative; z-index: 70; }
@media (prefers-reduced-motion: reduce) {
  #pl-root .pl-gate-scrim, #pl-root .pl-gate-card { transition: none; }
}
/* the service receipt row's inline Remove — a real button in link clothes */
#pl-root .pl-svc-undo {
  appearance: none; -webkit-appearance: none; cursor: pointer; position: relative;
  background: none; border: 0; padding: 0; margin-left: 6px;
  font: 600 12px/1.5 'Manrope'; color: var(--acc-text, #7d9aff);
}
#pl-root .pl-svc-undo::after { content: ""; position: absolute; left: -8px; right: -8px; top: 50%; height: 44px; transform: translateY(-50%); }

/* (the fee tier cards died with the tier picker — fee model v2, 2026-08-12:
   the card is an itemized receipt now, styles in the fee block below) */
/* (the old .pl-swatch--label proportion swatch retired 2026-08-07 — the size
   card now carries the real peel icon, sm-v2-icon-labels.svg) */

/* option pills: the shared base already transitions border/background/color
   (sm-v2.css); here they also get press feedback and a disabled look — the
   two states the shared base never needed before icon-card pills */
/* two-speed press (the work-reel precedent): snap in while held, settle
   back after. Both durations are --pl-press-in / --pl-press-out now (see
   :root) — the settle came down 240ms → 140ms on 2026-08-12 because the
   long tail was what read as lag on release. STANDING RULE (owner
   2026-08-07) IS UNCHANGED: card-shaped surfaces press via a BRIGHTNESS
   dip, never a scale — scale moved the icons ~0.6px and the slow
   settle-back made the drift visible; "minimal movement from the cards
   themselves." The CTA button is the one deliberate scale exception (and
   a shorter settle makes its 0.99 scale read tighter, not looser). */
#pl-root .smv2-pill {
  transition: border-color 150ms, background-color 150ms, color 150ms,
    filter var(--pl-press-out) cubic-bezier(.2, .7, .2, 1);
}
#pl-root .smv2-pill:not(:disabled):active { filter: brightness(.92); transition-duration: var(--pl-press-in); }
#pl-root .smv2-pill:disabled { opacity: .45; cursor: default; }

/* ---------- tap latency ----------
   THE actual mobile lag, and it was not code we had written: every control
   on this page shipped with `touch-action: auto`, so iOS Safari holds each
   tap for ~300ms to see whether a second one is coming (double-tap zoom)
   before it will dispatch the click. Nothing on the page can respond inside
   that window — the JS is already 5ms and the page already runs at 60fps —
   so the whole delay was the browser waiting on a gesture nobody makes on a
   form. `manipulation` keeps panning and pinch-zoom and drops only the
   double-tap-zoom wait.
   Not a new invention here: this is the house pattern already in
   sections/custom-bag-builder-multi.liquid, custom-cart-items.liquid and
   custom-cart-footer.liquid. The labels page (and jar/tube) never got it.
   Inputs are included deliberately — a mistap on a text field costs the
   same 300ms as a mistap on a button.
   `user-select: none` on the press-y controls stops a firm tap selecting a
   button's label, which on touch pops the selection handles and reads as
   the page fighting back. Text fields are NOT in that list. */
#pl-root .smv2-pill,
#pl-root .pl-add,
#pl-root .pl-addrow,
#pl-root .pl-artbtn,
#pl-root .pl-row-x,
#pl-root .pl-undo-btn,
#pl-root .pl-amb-pause,
#pl-root .pl-tier,
#pl-root .pl-drop,
#pl-root .pl-later,
#pl-root .pl-qsel,
#pl-root .pl-row-name,
#pl-root .pl-row-size,
#pl-root .pl-qty,
#pl-root .pl-input,
#pl-root .pl-notes,
/* the size-request link was the ONE live control this block missed — it was
   still paying the full ~300ms double-tap wait the whole block exists to
   kill (found by the 2026-08-13 audit follow-up, after the 44px ::after
   target made it hittable but left it slow) */
#pl-root .pl-card-note a {
  touch-action: manipulation;
}
#pl-root .smv2-pill,
#pl-root .pl-add,
#pl-root .pl-addrow,
#pl-root .pl-artbtn,
#pl-root .pl-row-x,
#pl-root .pl-undo-btn,
#pl-root .pl-amb-pause,
#pl-root .pl-tier {
  user-select: none;
  -webkit-user-select: none;
}
/* Kill iOS's default grey tap rectangle on the controls that paint their own
   press state (2026-08-13). Without this every tap got TWO acknowledgments —
   our 70ms brightness dip AND WebKit's grey box, which arrives on its own
   schedule and reads as the page stuttering rather than responding.
   ⚠️ Scoped to exactly the press set above (the controls with .pl-down
   twins), never `#pl-root *`: on a control with no press state of its own
   the grey box IS the only feedback, and removing it there would make the
   page feel dead instead of clean. .pl-card-note a is included because it
   gains a .pl-down twin below. House precedent: sm-v2.css `[data-cdw-open]`. */
#pl-root .smv2-pill,
#pl-root .pl-add,
#pl-root .pl-addrow,
#pl-root .pl-artbtn,
#pl-root .pl-row-x,
#pl-root .pl-undo-btn,
#pl-root .pl-amb-pause,
#pl-root .pl-card-note a {
  -webkit-tap-highlight-color: transparent;
}

/* price delta on option pills — filled by JS from the variant table.
   min-height reserves the line even when EMPTY (base options have no delta):
   without it, centered stacks of different heights put sibling labels at
   different y ("Matte" vs "Holographic" — owner catch 2026-08-07). */
#pl-root .pl-delta {
  display: block;
  margin-top: 3px;
  min-height: 11px;
  font: 600 11px/1 'Manrope';
  font-variant-numeric: tabular-nums;
  color: var(--pl-tx-3);
}
#pl-root .smv2-pill.is-on .pl-delta { color: var(--pl-tx-on-acc); }

/* a whole option row that does not apply right now (spot white on matte):
   collapsed to zero height — owner call 2026-08-06 kept it hidden outright;
   owner ask 2026-08-07 ("smoother matte↔holo") upgraded the display flip to
   the nav-accordion glide: grid-template-rows 0fr↔1fr animates the HEIGHT,
   so the finish card grows/shrinks smoothly instead of lurching. The outer
   row is padding-free (fr min-content trap, 08-04); the inner wrapper owns
   overflow. Buttons inside are also disabled by JS while off. */
/* Motion pass 2026-08-19 (owner: "smooth out the spot white / spot UV
   appearing and disappearing"). Measured at a clean 60fps both ways, so the
   roughness was choreography, not jank: open and close used the SAME 300ms
   decelerating curve, and the content only faded — no direction, and an exit
   that "landed softly" like an entrance. Now:
   · OPEN (320ms): the track decelerates open on the house curve while the
     content arrives a beat later (60ms) from 6px above — opacity + a short
     translate, the entrance pattern the rest of the page uses.
   · CLOSE (240ms, shorter than open — exits always are): the content leaves
     FIRST and fast (120ms, accelerating, drifting 4px up), then the track
     closes on a symmetric curve so the neighbours below slide up without the
     soft landing. By the time the row is half closed the content is gone,
     so nothing clips mid-letter. */
#pl-root .pl-optrow {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows 320ms cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-optrow[data-pl-off] {
  grid-template-rows: 0fr;
  transition: grid-template-rows 240ms cubic-bezier(.4, 0, .2, 1) 40ms;
}
#pl-root .pl-optinner {
  overflow: hidden;
  min-height: 0;
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition: opacity 240ms cubic-bezier(.2, .7, .2, 1) 60ms, transform 260ms cubic-bezier(.2, .7, .2, 1) 60ms, visibility 0s;
}
#pl-root .pl-optrow[data-pl-off] .pl-optinner {
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 120ms cubic-bezier(.4, 0, 1, 1), transform 120ms cubic-bezier(.4, 0, 1, 1), visibility 0s 280ms;
}
@media (prefers-reduced-motion: reduce) {
  #pl-root .pl-optrow, #pl-root .pl-optrow[data-pl-off],
  #pl-root .pl-optinner, #pl-root .pl-optrow[data-pl-off] .pl-optinner { transition-duration: 0s; transition-delay: 0s; }
}
#pl-root .pl-offhint { display: none; }
#pl-root .pl-optlabel {
  display: block;
  margin: 12px 0 8px;
  font: 700 11px/1 'Manrope';
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--pl-tx-4);
}

/* ---------- quantity tiers ---------- */
#pl-root .pl-tiers { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
#pl-root .pl-tier {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 10px 14px;
  background: var(--smv2-raised, #1a1d26);
  border: 1px solid var(--smv2-line, rgba(255, 255, 255, .1));
  border-radius: var(--smv2-r-md, 12px);
  color: #fff;
  font: 700 14px/1.35 'Manrope';
  text-align: left;
  cursor: pointer;
  transition: border-color 150ms, background-color 150ms,
    filter var(--pl-press-out) cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-tier:active { filter: brightness(.92); transition-duration: var(--pl-press-in); }
#pl-root .pl-tier[aria-pressed="true"] {
  border-color: var(--acc, #1b4dff);
  background: color-mix(in srgb, var(--acc, #1b4dff) 12%, var(--smv2-tint, #181b23));
}
#pl-root .pl-tier-qty { font: 800 15px/1 'Manrope'; }
#pl-root .pl-tier-mid { font: 600 13px/1.35 'Manrope'; color: var(--pl-tx-2); }
#pl-root .pl-tier-mid strong { color: #fff; font-weight: 800; }
#pl-root .pl-tier-save {
  font: 800 11px/1 'Manrope';
  letter-spacing: .04em;
  color: var(--acc-text, #7d9aff);
  white-space: nowrap;
}
#pl-root .pl-qtyrow {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
}
#pl-root .pl-qtyrow label { font: 700 13px/1.35 'Manrope'; color: var(--pl-tx); }
#pl-root .pl-qty {
  width: 110px;
  min-height: 44px;
  padding: 8px 12px;
  background: var(--smv2-raised, #1a1d26);
  border: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14));
  border-radius: var(--smv2-r-sm, 10px);
  color: #fff;
  font: 700 16px/1 'Manrope'; /* 16px floor: below it iOS zooms on focus */
}
#pl-root .pl-qty { transition: border-color 150ms; }
/* fields: keyboard focus earns the shared V2 ring (the bare :focus
   outline-none out-specified it — audit find); mouse keeps border-swap only */
#pl-root .pl-qty:focus { border-color: var(--acc, #1b4dff); }
#pl-root .pl-qty:focus:not(:focus-visible) { outline: none; }

/* ---------- peptide rows ---------- */
/* rowwrap/rowinner = the nav-accordion glide (padding-free outer grid, inner
   owns overflow) so count changes grow rows in and out instead of popping;
   the row's own margin-top lives INSIDE the inner, so the gap collapses too
   (the fee card precedent) */
/* CHOREOGRAPHED, not simultaneous (owner 2026-08-11: "make the animation
   smoother"). Both properties used to run at once on the house ease-out,
   which is heavily front-loaded — measured 99% collapsed by 120ms of a 300ms
   transition, so the row snapped shut and then spent 180ms animating a
   fraction of a pixel. It read as a pop with a long tail.
   Now it is two beats in each direction. OUT: the content fades (150ms),
   THEN the space closes (300ms, delayed past the fade). IN: the space opens
   first, then the content arrives (200ms, delayed 150ms). Content leaves
   before its container and arrives after it — which is what makes a
   collapse read as motion rather than a jump. Same house curve and house
   durations throughout; only the sequencing changed. */
#pl-root .pl-rowwrap {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows 300ms cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-rowwrap[data-pl-off] {
  grid-template-rows: 0fr;
  transition: grid-template-rows 300ms cubic-bezier(.2, .7, .2, 1) 120ms;
}
#pl-root .pl-rowinner {
  overflow: hidden;
  min-height: 0;
  opacity: 1;
  visibility: visible;
  transition: opacity 200ms cubic-bezier(.2, .7, .2, 1) 150ms, visibility 0s;
}
#pl-root .pl-rowwrap[data-pl-off] .pl-rowinner {
  opacity: 0;
  visibility: hidden;
  transition: opacity 150ms cubic-bezier(.2, .7, .2, 1), visibility 0s 420ms;
}
/* a row is ONE always-visible line — name · quantity · art chip · ✕ — on a
   shared grid so the list reads as a table ("easier and simpler to add
   multiple peptides in one go"). Hairline sub-panel, transparent fill so the
   raised inputs inside keep their contrast. Below 600 the art chip drops to
   its own full-width second line (bigger target, nothing truncates). */
#pl-root .pl-row {
  display: grid;
  /* Quantity cell 96 → 112. This is the SECOND time it has been widened for
     the same reason, so here is the actual number: the select spends 10px +
     24px on padding (the 24 is the dropdown arrow) plus 2px of border, so a
     112px cell leaves 76px of text — and "Custom…" renders at 73px, the
     widest thing it ever has to show. At 96px it had 60px and clipped the one
     option that unlocks any quantity. Do not shrink below 112 without
     re-measuring the longest option string. */
  grid-template-columns: 14px minmax(0, 1fr) 112px 28px;
  gap: 8px;
  align-items: center;
  margin-top: 10px;
  padding: 12px;
  /* the peptides ARE the product (owner: "should stand out more") — rows
     ride the RAISED surface over the glass card, strongest sub-panels on
     the page; their inputs drop to the well tone (the cart's --well
     precedent) so controls stay legible inside the raised row.
     Brightened 2026-08-11 (owner: "the rows just need more contrast against
     eachother"). The alternation measured 1.041 between adjacent rows —
     rgb(24,27,35) against rgb(21,22,30), which is three points on near-black
     and invisible in daylight. Raised to 1.224 by lifting THIS tone rather
     than darkening `.is-alt`: pushing the alternates down would have made
     every second peptide read as recessed, and they are all equal. So the
     stripe got stronger and nothing moved backwards. */
  background: rgba(38, 42, 54, .92);
  border: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14));
  border-radius: var(--smv2-r-lg, 14px);
}
/* the art chip used to sit in this group and wear the same well tone as the
   text fields — which is precisely why it disappeared: it is the only ACTION
   in the row, dressed as a passive input. It styles itself now. */
/* .pl-qsel left this group 2026-08-17: it overlays the qty input now, and
   two stacked .85 washes read darker than the field they sit on */
#pl-root .pl-row .pl-row-name,
#pl-root .pl-row .pl-row-size,
#pl-root .pl-row .pl-row-qty {
  background-color: rgba(14, 15, 20, .85);
}
/* a filled-in name reads bold — the peptide identity leads the row */
#pl-root .pl-row-name:not(:placeholder-shown) { font-weight: 700; }
/* phones: the NAME owns its full line (owner 2026-08-10: "i want them to
   fully see peptide name" — beside qty it clipped at ~15 chars; that ruling
   stands, so the vial-size field does NOT share line 1). Line 2 = size
   beside quantity (both short strings), ✕ keeps the top-right corner; the
   art chip drops to a full-width line 3 — a third line was the honest cost
   of the boss's size field (2026-08-12), and the chip is the row's only
   ACTION, so it spends the gain on the biggest target on the row. */
#pl-root .pl-row-name { grid-column: 2 / -1; padding-left: 10px; padding-right: 10px; }
/* phones: a micro-label line sits between the name and the inputs — the
   column header that names these fields on desktop doesn't exist down
   here, and the truncated placeholder was carrying a job placeholders
   can't do (owner 2026-08-24) */
#pl-root .pl-microlab { display: block; grid-row: 2; align-self: end; margin-bottom: -3px;
  font: 700 11px/1 'Manrope'; letter-spacing: .07em; text-transform: uppercase; color: var(--pl-tx-4); }
#pl-root .pl-microlab i { font-style: normal; opacity: .75; letter-spacing: .02em; text-transform: none; }
#pl-root .pl-microlab--size { grid-column: 1 / 3; }
#pl-root .pl-microlab--qty { grid-column: 3; }
#pl-root .pl-row .pl-row-size { grid-row: 3; grid-column: 1 / 3; }
#pl-root .pl-row .pl-row-x { grid-row: 3; grid-column: 4; }
#pl-root .pl-row .pl-qtycell { grid-row: 3; grid-column: 3; }
#pl-root .pl-row .pl-artbtn { grid-row: 4; grid-column: 1 / -1; }
/* Flush second line (owner 2026-08-10). With a single peptide the ✕ is
   hidden, but its 28px track — plus the 8px gap — stayed reserved, so the
   art + quantity line stopped 36px short of the name field above it and the
   row read ragged. Dropping the track lets the 1fr absorb the slack, and the
   space lands on the art chip rather than the quantity cell, which is
   already sized exactly to its longest option. Phones only: on desktop the
   row is one line and that column is a deliberate reservation the header row
   is aligned to — see the .pl-rowshead pairing note. */
@media (max-width: 599px) {
  #pl-root .pl-row:has(.pl-row-x[hidden]) {
    grid-template-columns: 14px minmax(0, 1fr) 112px;
  }
}
@media (min-width: 600px) {
  /* Quantity column 78 → 112 (owner 2026-08-10: "make the qty area a bit
     bigger"). Measured, not guessed: the select's 78px left only 42px of
     usable text space after its 10px/24px padding and border, while
     "Custom…" needs 73px and a five-figure value like 25000 needs 50 — so
     BOTH were being clipped, including the option that unlocks any quantity.
     112px clears the widest of them with room to spare.
     Art column 112 → 136 as well: the chip carries a glyph now and was the
     narrowest cell in the row while being the only thing to act on. Both
     come out of the name field, which had 405px to spare and still holds
     ~40 characters.
     Size column added 2026-08-12 (boss's vial-size field): 104px sits
     between the name and the quantity — "5000mcg" needs ~62px of text at
     16px/600 plus the 20px padding and border, so 104 clears the longest
     real value; the placeholder ellipsizes, which is fine, the header row
     labels the column. It too comes out of the name's slack. */
  #pl-root .pl-row { grid-template-columns: 14px minmax(0, 1fr) 104px 112px 136px 28px; }
  #pl-root .pl-row-name { grid-column: 2; padding-left: 12px; padding-right: 12px; }
  #pl-root .pl-microlab { display: none; } /* the header row names the columns */
  #pl-root .pl-row .pl-row-size { grid-row: 1; grid-column: 3; }
  #pl-root .pl-row .pl-qtycell { grid-row: 1; grid-column: 4; }
  #pl-root .pl-row .pl-artbtn { grid-row: 1; grid-column: 5; }
  #pl-root .pl-row .pl-row-x { grid-row: 1; grid-column: 6; }
}
#pl-root .pl-rows > .pl-rowwrap:first-child .pl-row { margin-top: 0; }
/* the pool dot — this row's swatch in its gauge segment's shade; is-alt
   rows (JS, cart-aware parity shared with the gauge) also get a whisper of
   tint, so a long list reads zebra-striped in the same rhythm as the tube */
/* the swatch became a helix 2026-08-10 (owner) — same job, so the SHADE is
   still the whole point: strokes ride currentColor and the two states below
   set `color` where they used to set `background`. Sized to the 14px grid
   column; the glyph is taller than wide because a helix reads vertically. */
#pl-root .pl-row-dot {
  display: block;
  width: 13px;
  height: 17px;
  color: var(--acc, #1b4dff);
}
#pl-root .pl-row.is-alt .pl-row-dot { color: color-mix(in srgb, var(--acc, #1b4dff) 55%, transparent); }
#pl-root .pl-row.is-alt { background: rgba(26, 29, 38, .45); }
/* column headers for the list (the rows' grid, echoed) — desktop only; on
   phones the rows wrap and the inputs explain themselves */
#pl-root .pl-rowshead { display: none; }
@media (min-width: 600px) {
  #pl-root .pl-rowshead {
    display: grid;
    /* MUST track .pl-row's columns exactly — this row exists only to label
       them. Changing one without the other silently slides every header off
       its column (caught 2026-08-10: Labels sat 58px right of the quantity
       cell, Artwork 24px right of the chip). */
    grid-template-columns: 14px minmax(0, 1fr) 104px 112px 136px 28px;
    gap: 8px;
    margin: 12px 0 6px;
    padding: 0 13px;
    font: 800 11px/1 'Manrope';
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--pl-tx-4);
  }
  /* the size column's unit hint. Lowercase and untracked against its
     uppercase label on purpose: tracked caps would not fit the 104px
     column beside "SIZE", and a hint that shouts as loudly as the label it
     qualifies stops reading as a hint. Lighter weight, no extra colour
     step — --pl-tx-4 is already the micro/legal floor. */
  #pl-root .pl-rowshead i {
    font: 600 11px/1 'Manrope';
    font-style: normal;
    letter-spacing: 0;
    text-transform: none;
  }
}
/* the pool gauge — a graduated tube the order fills. An INSTRUMENT graphic:
   hairline tube, ruler ticks (major + minor graduations), tabular numbers.
   Nothing box- or button-shaped. The fill = one segment per peptide
   (alternating strengths, hatched leading segment for cart labels), and the
   readout rides the fill line. Widths/positions animate on the house curve
   because the skeleton persists across updates. */
#pl-root .pl-gauge {
  position: relative;
  margin: 4px 0 6px;
  /* phones: deep bottom padding for the STAGGERED scale (see the odd-tick
     rules below) — six two-line labels in ~300px read as one squished mass
     (owner catch 2026-08-10), so alternate rungs drop to a second row on
     longer stems, ruler-style. 6px sides keep end labels inside the card. */
  padding: 20px 6px 78px;
  font-variant-numeric: tabular-nums;
}
#pl-root .pl-gauge-tick[data-pl-g-tick="1"] u,
#pl-root .pl-gauge-tick[data-pl-g-tick="3"] u,
#pl-root .pl-gauge-tick[data-pl-g-tick="5"] u,
#pl-root .pl-gauge-tick[data-pl-g-tick="7"] u,
#pl-root .pl-gauge-tick[data-pl-g-tick="9"] u { height: 34px; }
@media (min-width: 600px) {
  #pl-root .pl-gauge { padding-bottom: 34px; }
  #pl-root .pl-gauge-tick[data-pl-g-tick="1"] u,
  #pl-root .pl-gauge-tick[data-pl-g-tick="3"] u,
  #pl-root .pl-gauge-tick[data-pl-g-tick="5"] u,
  #pl-root .pl-gauge-tick[data-pl-g-tick="7"] u,
  #pl-root .pl-gauge-tick[data-pl-g-tick="9"] u { height: 6px; }
}
#pl-root .pl-gauge-readout {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  white-space: nowrap;
  font: 600 11px/1 'Manrope';
  color: var(--acc-text, #7d9aff);
  transition: left 300ms cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-gauge-readout b { font-weight: 800; }
#pl-root .pl-gauge-tube {
  position: relative;
  display: flex;
  height: 12px;
  border: 1px solid var(--smv2-line, rgba(255, 255, 255, .1));
  border-radius: 999px;
  background: rgba(255, 255, 255, .04);
  overflow: hidden;
  /* the segments animate WIDTH, which is layout — bound that work to this
     tube so a quantity change cannot reflow anything outside it. Width is
     deliberate rather than a transform: the cart segment is painted with a
     repeating-linear-gradient hatch, and scaleX would stretch the hatch
     along with the bar. Layout on six 12px-tall boxes, on discrete input,
     is a cheaper trade than a distorted instrument. */
  contain: layout style;
}
#pl-root .pl-gauge-seg {
  display: block;
  height: 100%;
  background: var(--acc, #1b4dff);
  transition: width 300ms cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-gauge-seg--alt { background: color-mix(in srgb, var(--acc, #1b4dff) 55%, transparent); }
#pl-root .pl-gauge-seg + .pl-gauge-seg { border-left: 1px solid rgba(11, 12, 15, .8); }
/* labels already in the cart: instrument hatching, not a new color */
#pl-root .pl-gauge-seg--cart {
  background: repeating-linear-gradient(135deg,
    color-mix(in srgb, var(--acc, #1b4dff) 45%, transparent) 0 3px,
    transparent 3px 6px);
}
#pl-root .pl-gauge-tick {
  position: absolute;
  top: 34px; /* just under the tube */
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  text-align: center;
}
/* Each tick is absolutely positioned and centre-transformed, so nothing
   constrains its width but its own widest child — which meant the percent
   line wrapped to fit the RUNG label above it. Only ever visible on the
   embossed ladder's last rung, where "86.7% off" is wider than the standard
   ladder's "90% off" and broke with "off" stranded on a second line
   (measured 375px, 2026-08-10). Centred absolute ticks widen symmetrically
   into free space, so nowrap costs nothing here. */
#pl-root .pl-gauge-tick b,
#pl-root .pl-gauge-tick em { white-space: nowrap; }
#pl-root .pl-gauge-tick u {
  display: block;
  width: 1px;
  height: 6px;
  background: rgba(255, 255, 255, .3);
  text-decoration: none;
}
#pl-root .pl-gauge-tick--minor u { height: 4px; background: rgba(255, 255, 255, .15); }
#pl-root .pl-gauge-tick b { font: 800 11px/1 'Manrope'; color: var(--pl-tx); }
#pl-root .pl-gauge-tick em { font: 600 11px/1 'Manrope'; font-style: normal; color: var(--pl-tx-4); }
#pl-root .pl-gauge-tick.is-here u { background: var(--acc, #1b4dff); }
#pl-root .pl-gauge-tick.is-here b { color: var(--acc-text, #7d9aff); }
#pl-root .pl-gauge-tick.is-here em { color: var(--acc-text, #7d9aff); }
/* Tier jump (Clarity, ads week 1): the MAJOR marks take a tap — pooled
   order bumps up to that rung. Pointer affordance only; they stay <span>s
   inside the gauge's role="img" (see the JS comment for the a11y shape).
   The hit pad is a pseudo, not padding: these are absolutely positioned
   centre-transformed labels, and padding would feed the transform new
   box math. ±2px horizontal only — MEASURED at 375px: the rungs sit 37px
   apart but the labels run 34-57px wide, so the elements already overlap
   ~8px and any real horizontal pad grows the zone where the later rung
   steals the earlier one's tap (same z, later DOM paints on top). The
   label glyphs are what people aim at (that is what the recordings show
   being tapped) and they never collide — the stagger (odd rungs on 34px
   stems) keeps them on separate lines. Vertical ±10px is safe: it pads
   toward the tube above and open canvas below. */
#pl-root .pl-gauge-tick[data-pl-g-tick] {
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  -webkit-user-select: none;
}
#pl-root .pl-gauge-tick[data-pl-g-tick]::after {
  content: '';
  position: absolute;
  inset: -10px -2px;
}
#pl-root .pl-gauge-tick[data-pl-g-tick]:active { filter: brightness(.92); transition-duration: var(--pl-press-in); }
/* the art chip is a label-wrapped file input (native picker) */
/* THE ART CHIP (owner 2026-08-10: "make the add art drop stand out more").
   Measured before touching it: at 112×44 it was the SMALLEST thing in the
   row against a 405px name field, wore the faintest border on the page
   (1px dashed white .25), carried no icon, and shared the text fields' well
   tone — the only action in the row, dressed as furniture.
   It now carries an accent tint and an upload glyph. Deliberately NOT solid
   accent: that is the Add-to-cart treatment, and a second solid-blue block
   in the same viewport would compete with the actual checkout button. An
   accent-tinted dashed outline reads one level down — "an action, here" —
   which is the right rank for it. The dash stays because dashed borders are
   the conventional upload/drop affordance; it is the WEIGHT that was wrong,
   not the style. */
#pl-root .pl-artbtn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 44px;
  padding: 10px;
  background: color-mix(in srgb, var(--acc, #1b4dff) 12%, var(--smv2-raised, #1a1d26));
  border: 1px dashed color-mix(in srgb, var(--acc, #1b4dff) 62%, transparent);
  border-radius: var(--smv2-r-sm, 10px);
  transition: border-color 150ms, background-color 150ms,
    filter var(--pl-press-out) cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-artbtn-icon {
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--acc-text, #7d9aff);
}
#pl-root .pl-artbtn-icon--done { display: none; }
#pl-root .pl-artbtn.is-filled .pl-artbtn-icon--add { display: none; }
#pl-root .pl-artbtn.is-filled .pl-artbtn-icon--done { display: block; }
#pl-root .pl-artbtn:active { filter: brightness(.92); transition-duration: var(--pl-press-in); }
#pl-root .pl-artbtn input[type="file"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}
#pl-root .pl-artbtn-txt {
  font: 700 13px/1 'Manrope';
  color: #fff;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* filled reads DONE, not "still asking": solid edge, the glyph swaps to a
   check, and the tint drops away so a completed row stops calling for a tap */
#pl-root .pl-artbtn.is-filled {
  border-style: solid;
  border-color: var(--acc, #1b4dff);
  background: rgba(27, 77, 255, .06);
}
#pl-root .pl-artbtn.is-filled .pl-artbtn-icon { color: var(--acc, #1b4dff); }
#pl-root .pl-artbtn.is-drag {
  border-style: solid;
  border-color: var(--acc, #1b4dff);
  background: rgba(27, 77, 255, .08);
}
/* interaction floor on a chip-sized target: same instant overlay, shorter
   copy (the full "Opening your files…" cannot fit 112px) */
#pl-root .pl-artbtn.is-opening::after {
  animation: pl-overlay-in 120ms cubic-bezier(.2, .7, .2, 1);
  content: "Opening\2026";
  position: absolute;
  inset: -1px;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: inherit;
  background: color-mix(in srgb, var(--smv2-panel, #12141b) 90%, transparent);
  color: var(--acc-text, #7d9aff);
  font: 700 11px/1 'Manrope';
}
/* UNDO strip. Sits where the removed row was in the flow (bottom of the
   list, above the add button) rather than floating over the page: a fixed
   snackbar would collide with the sticky total bar on phones, which is
   exactly where the thumb already is. Warm-neutral, not red — the red was
   the WARNING before the tap; this is the recovery after it, and shouting
   twice would make a completed action look like a failure. */
#pl-root .pl-undo {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
  padding: 10px 12px;
  background: rgba(255, 255, 255, .05);
  border: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14));
  border-radius: var(--smv2-r-md, 12px);
}
/* The strip's SPACE is animated, not just its ink (owner 2026-08-11: the
   entrance was "kinda jarring"). It used to fade+rise 6px while its 58px of
   layout appeared in a SINGLE frame — and it lands right after the removed
   row's 420ms collapse, so the add button glided up 70px and then snapped
   back down 58px in one frame. The fade was never the problem; the
   un-animated space was. Now it opens on the same two-beat idiom as a row
   (space first, content after), so the strip grows into the gap the row
   just left instead of appearing on top of it.
   Keyframes rather than transitions because it enters from display:none and
   has no start frame to interpolate from — grid-template-rows interpolates
   0fr→1fr in a keyframe exactly as it does in the row's transition. The
   6px rise is gone: with the space animating it was a second, competing
   motion, and the row canon moves opacity only. */
#pl-root .pl-undowrap {
  display: grid;
  grid-template-rows: 1fr;
}
#pl-root .pl-undowrap[hidden] { display: none; }
#pl-root .pl-undoclip {
  overflow: hidden;
  min-height: 0;
}
@keyframes pl-undo-open {
  from { grid-template-rows: 0fr; }
  to   { grid-template-rows: 1fr; }
}
@keyframes pl-undo-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* ⚠️ COUPLED TO THE ROW'S EXIT — the 120ms delay is not a taste value. The
   strip is now offered the instant the ✕ is tapped, and the row's space
   starts closing at 120ms (`.pl-rowwrap[data-pl-off]`, same 300ms + 120ms
   delay). Matching both numbers is what makes the two cancel: 68px opens
   here while 70px closes there, over the same window and the same curve, so
   the list above closes up and everything BELOW the strip holds still. Its
   `backwards` fill is what holds the strip at 0fr through the delay.
   Change one of these three values (delay, duration, curve) and you must
   change it on .pl-rowwrap too, or the add button starts see-sawing again. */
#pl-root .pl-undowrap:not([hidden]) {
  animation: pl-undo-open 300ms cubic-bezier(.2, .7, .2, 1) 120ms both;
}
#pl-root .pl-undowrap:not([hidden]) .pl-undoclip {
  animation: pl-undo-in 200ms cubic-bezier(.2, .7, .2, 1) 270ms both;
}
#pl-root .pl-undo-txt {
  flex: 1 1 auto;
  min-width: 0;
  font: 600 13px/1.35 'Manrope';
  color: var(--pl-tx-2);
}
#pl-root .pl-undo-btn {
  flex: none;
  position: relative; /* containing block for the hit-area expander below */
  min-height: 36px;
  padding: 8px 14px;
  background: transparent;
  border: 1px solid var(--acc-2, #4d75ff);
  border-radius: var(--smv2-r-sm, 10px);
  color: var(--acc-text, #7d9aff);
  font: 700 13px/1 'Manrope';
  cursor: pointer;
  /* on the house press ladder (2026-08-13) — this was the one press control
     still running 150ms default ease in BOTH directions, so its dip settled
     visibly slower than every button beside it */
  transition: background-color var(--pl-press-out) cubic-bezier(.2, .7, .2, 1),
              color var(--pl-press-out) cubic-bezier(.2, .7, .2, 1);
}
/* 44px hit target — same expander the row's ✕ uses. This pseudo already
   existed but was INERT: with no inset it had zero size, and the button was
   `position: static`, so it resolved against a distant ancestor instead of
   the button. Measured 2026-08-11 by probing outward from the centre with
   elementFromPoint: 36px tall with no expansion, against the ✕'s working
   43–44px. 36 + 4 + 4 = 44. */
#pl-root .pl-undo-btn::after { content: ""; position: absolute; inset: -4px; }
#pl-root .pl-undo-btn:active {
  background: color-mix(in srgb, var(--acc, #1b4dff) 18%, transparent);
  color: #fff;
  transition-duration: var(--pl-press-in);
}

/* REMOVE (owner 2026-08-11: "make the x more obvious that it removes it").
   Three signals instead of one, because a bare glyph on a transparent square
   was carrying the whole message: it now has a SURFACE so it reads as a
   control rather than decoration, a BIN so it reads as delete rather than
   dismiss, and it turns the error red on hover/press so the destructive
   intent is stated before the tap, not after. Red only on interaction — a
   permanently red control in every row would read as an error state and make
   a valid list look broken. */
#pl-root .pl-row-x {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  background: rgba(255, 255, 255, .05);
  border: 1px solid var(--smv2-line, rgba(255, 255, 255, .1));
  border-radius: 50%;
  color: var(--pl-tx-3);
  cursor: pointer;
  /* all four legs on the press ladder (2026-08-13) — only `filter` was
     tokenised, so the red bin's colour/border/background half settled at
     150ms ease while its brightness dip settled at 140ms on the house
     curve: one control, two speeds, visible on release */
  transition: color var(--pl-press-out) cubic-bezier(.2, .7, .2, 1),
    background-color var(--pl-press-out) cubic-bezier(.2, .7, .2, 1),
    border-color var(--pl-press-out) cubic-bezier(.2, .7, .2, 1),
    filter var(--pl-press-out) cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-row-x svg { width: 15px; height: 15px; }
/* NO HOVER, NO WARNING (owner 2026-08-11: "on mobile lets make the trash bins
   red, since theres no hover"). On a phone the resting state is the ONLY
   state anyone sees, so the destructive signal has to live there. Gated on
   `hover: none` rather than a width — it is the pointer capability that
   decides whether a hover state is reachable, and a small window on a laptop
   still has a mouse.
   Tuned down from the interaction red on purpose: the icon carries the colour
   while the surface stays a whisper. Full error red on every row would read
   as a list full of problems rather than a list with a delete on each line. */
@media (hover: none) {
  #pl-root .pl-row-x {
    color: var(--smv2-error, #ff6b6b);
    border-color: rgba(255, 107, 107, .38);
    background: rgba(255, 107, 107, .08);
  }
}
#pl-root .pl-row-x::after { content: ""; position: absolute; inset: -8px; } /* 44px hit target */
#pl-root .pl-row-x:active {
  filter: brightness(.92);
  transition-duration: var(--pl-press-in);
  color: var(--smv2-error, #ff6b6b);
  border-color: var(--smv2-error, #ff6b6b);
  background: rgba(255, 107, 107, .12);
}
#pl-root .pl-row-x[hidden] { display: none; }
#pl-root .pl-row-name,
#pl-root .pl-row-size {
  width: 100%;
  min-height: 44px;
  padding: 10px 12px;
  background: var(--smv2-raised, #1a1d26);
  border: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14));
  border-radius: var(--smv2-r-sm, 10px);
  color: #fff;
  font: 600 16px/1.35 'Manrope'; /* 16px floor: below it iOS zooms on focus */
  transition: border-color 150ms;
}
#pl-root .pl-row-name:focus,
#pl-root .pl-row-size:focus { border-color: var(--acc, #1b4dff); }
#pl-root .pl-row-name:focus:not(:focus-visible),
#pl-root .pl-row-size:focus:not(:focus-visible) { outline: none; }
#pl-root .pl-row-name::placeholder,
#pl-root .pl-row-size::placeholder { color: var(--pl-tx-4); }
/* the size is part of the label's identity too — filled, it bolds like the name */
#pl-root .pl-row-size:not(:placeholder-shown) { font-weight: 700; }
/* the 104px desktop cell ellipsizes its placeholder rather than clipping it
   mid-glyph; the header row carries the column's name either way */
#pl-root .pl-row-size { text-overflow: ellipsis; }
/* the grid sizes the row's qty input (base .pl-qty is 110px for the old
   standalone use) */
/* INVERTED CELL (owner 2026-08-17: "default able to type, but if you click
   the arrow the drop down appears"). The number input is the surface —
   full-width, right padding clears the arrow zone — and the select is
   collapsed to a 44px overlay on the input's right edge: still the NATIVE
   select (one tap, the OS picker does the work), just wearing only its
   chevron. Its resting option is empty and JS re-empties it after every
   pick, so its own text never needs hiding and repeat picks still fire
   change. data-pl-qcustom is retired. */
#pl-root .pl-row-qty { width: 100%; min-height: 44px; padding: 8px 44px 8px 10px; }
/* the native number spinners would render UNDER the transparent arrow
   overlay on hover (Chrome) — the preset arrow is this cell's stepper */
#pl-root .pl-row-qty::-webkit-outer-spin-button,
#pl-root .pl-row-qty::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
#pl-root .pl-row-qty { -moz-appearance: textfield; }
#pl-root .pl-qtycell { position: relative; display: block; min-width: 0; }
#pl-root .pl-qsel {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 44px; /* the house tap floor — ::after can't pad a replaced element */
  padding: 0;
  appearance: none;
  -webkit-appearance: none;
  background: transparent
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='rgba(255,255,255,.6)' stroke-width='1.5' fill='none'/%3E%3C/svg%3E")
    no-repeat center;
  border: 0;
  border-left: 1px solid var(--smv2-line, rgba(255, 255, 255, .1));
  border-radius: 0 var(--smv2-r-sm, 10px) var(--smv2-r-sm, 10px) 0;
  color: transparent; /* belt-and-braces: the resting option is empty anyway */
  font: 700 16px/1 'Manrope';
  cursor: pointer;
}
#pl-root .pl-qsel:focus { border-left-color: var(--acc, #1b4dff); }
#pl-root .pl-qsel:focus:not(:focus-visible) { outline: none; }
/* "Add another peptide" (owner 2026-08-10: "make it a bit more obvious").
   It was quieter than the rows it creates — a .05 white wash behind a faint
   dashed hairline at 14px, which read as a divider rather than a control.
   Now: raised surface, solid brighter edge, 15px label, and an ACCENT PLUS
   BADGE. The badge is doing the work — one spot of saturated colour is what
   the eye finds, and it stays small enough not to argue with the solid-blue
   Add to cart. Solid edge, not dashed, deliberately: dashed is the upload
   affordance the art chip owns, and the two sit inches apart. */
#pl-root .pl-addrow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  margin-top: 12px;
  min-height: 50px;
  padding: 13px 14px;
  background: color-mix(in srgb, var(--acc, #1b4dff) 15%, var(--smv2-raised, #1a1d26));
  border: 1px solid color-mix(in srgb, var(--acc, #1b4dff) 58%, transparent);
  /* pill geometry = tappable (owner rule, jar add-cards precedent 2026-08-05):
     the tinted rounded-rect read as an info box; the stadium reads as a button */
  border-radius: 999px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .08);
  color: #fff;
  font: 700 15.5px/1 'Manrope';
  cursor: pointer;
  transition: border-color 150ms, background-color 150ms,
    filter var(--pl-press-out) cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-addrow-plus {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: 999px;
  background: var(--acc, #1b4dff);
  color: #fff;
  font: 700 18px/1 'Manrope';
  /* the glyph sits a hair high in the circle at this size */
  padding-bottom: 2px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .30);
}
#pl-root .pl-addrow:active { filter: brightness(.92); transition-duration: var(--pl-press-in); }
#pl-root .pl-addrow[hidden] { display: none; }

/* ---------- artwork ---------- */
#pl-root .pl-drop {
  position: relative;
  display: block;
  border: 1px dashed rgba(255, 255, 255, .25);
  border-radius: var(--smv2-r-md, 12px);
  padding: 16px 14px;
  text-align: center;
  transition: border-color 150ms, background-color 150ms,
    filter var(--pl-press-out) cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-drop.is-filled { border-style: solid; border-color: var(--acc, #1b4dff); }
/* live drag-over: the native input handles the drop; this is the visual
   answer the zone never gave (critique observation) */
#pl-root .pl-drop.is-drag {
  border-style: solid;
  border-color: var(--acc, #1b4dff);
  background: rgba(27, 77, 255, .08);
}
#pl-root .pl-drop input[type="file"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}
#pl-root .pl-drop input[type="file"]:disabled { cursor: not-allowed; }
#pl-root .pl-drop-txt { font: 700 14px/1.4 'Manrope'; color: var(--pl-tx); }
#pl-root .pl-drop-sub { display: block; margin-top: 4px; font: 500 12px/1.4 'Manrope'; color: var(--pl-tx-4); }
#pl-root .pl-drop.is-off { opacity: .45; }
#pl-root .pl-drop:active { filter: brightness(.92); transition-duration: var(--pl-press-in); }
/* "Opening your files…" — the OS file dialog mutates nothing in-page, so a
   bare tap otherwise reads as dead (interaction floor; the builders' recipe) */
@keyframes pl-overlay-in { from { opacity: 0; } }
#pl-root .pl-drop.is-opening::after {
  animation: pl-overlay-in 120ms cubic-bezier(.2, .7, .2, 1);
  content: "Opening your files\2026";
  position: absolute;
  inset: -1px;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: inherit;
  background: color-mix(in srgb, var(--smv2-panel, #12141b) 90%, transparent);
  color: var(--acc-text, #7d9aff);
  font: 700 13px/1 'Manrope';
  letter-spacing: .02em;
}
#pl-root .pl-later {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 12px;
  padding: 12px 14px;
  background: var(--smv2-raised, #1a1d26);
  border: 1px solid var(--smv2-line, rgba(255, 255, 255, .1));
  border-radius: var(--smv2-r-md, 12px);
  cursor: pointer;
  font: 600 13px/1.45 'Manrope';
  color: var(--pl-tx);
  transition: border-color 150ms, filter var(--pl-press-out) cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-later:active { filter: brightness(.92); transition-duration: var(--pl-press-in); }
#pl-root .pl-later input {
  width: 18px;
  height: 18px;
  margin: 1px 0 0;
  accent-color: var(--acc, #1b4dff);
  flex: 0 0 auto;
}
#pl-root .pl-notes {
  width: 100%;
  margin-top: 12px;
  padding: 12px 14px;
  min-height: 64px;
  background: var(--smv2-raised, #1a1d26);
  border: 1px solid var(--smv2-line, rgba(255, 255, 255, .1));
  border-radius: var(--smv2-r-md, 12px);
  color: #fff;
  font: 500 16px/1.45 'Manrope';
  resize: vertical;
}
#pl-root .pl-notes { transition: border-color 150ms; }
#pl-root .pl-notes:focus { border-color: var(--acc, #1b4dff); }
#pl-root .pl-notes:focus:not(:focus-visible) { outline: none; }
#pl-root .pl-notes::placeholder { color: var(--pl-tx-4); }

/* ---------- contact (free consultation) ---------- */
/* Two required fields in front of the button is real friction, so the lede
   has to earn them: it names the consultation BEFORE it asks, which makes the
   ask part of the offer rather than a toll. Sits above the optional notes —
   required before optional — and inside card ② rather than becoming a third
   numbered step, because the page's two-step identity is deliberate. */
/* hairline above the contact block (owner 2026-08-13): the artwork notes end
   one thought and "we check every file…" starts the contact ask — without a
   seam the three notes read as one run-on wall. The border is the divider;
   no <hr> element, so the card's DOM stays flat and the theme's
   div:empty rule has nothing to swallow. */
#pl-root .pl-contact {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--smv2-line, rgba(255, 255, 255, .1));
}
#pl-root .pl-contact-lede {
  margin: 0 0 10px;
  font: 500 13px/1.5 'Manrope';
  color: var(--pl-tx-2);
}
#pl-root .pl-contact-grid { display: grid; gap: 10px; }
@media (min-width: 600px) {
  #pl-root .pl-contact-grid { grid-template-columns: 1fr 1fr; }
}
#pl-root .pl-field { display: block; }
#pl-root .pl-field-lab {
  display: block;
  margin-bottom: 5px;
  font: 700 11px/1 'Manrope';
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--pl-tx-3);
}
#pl-root .pl-input {
  width: 100%;
  min-height: 44px;
  padding: 12px 14px;
  background: var(--smv2-raised, #1a1d26);
  border: 1px solid var(--smv2-line, rgba(255, 255, 255, .1));
  border-radius: var(--smv2-r-md, 12px);
  color: #fff;
  /* 16px floor — anything smaller and iOS zooms the page on focus */
  font: 600 16px/1.2 'Manrope';
  transition: border-color 150ms;
}
#pl-root .pl-input::placeholder { color: var(--pl-tx-4); font-weight: 500; }
#pl-root .pl-input:focus { border-color: var(--acc, #1b4dff); }
#pl-root .pl-input:focus:not(:focus-visible) { outline: none; }
/* the failing field is named by the message AND marked here, so a screen
   reader and an eye land in the same place */
#pl-root .pl-input.is-bad { border-color: var(--smv2-error, #ff6b6b); }
/* required mark + inline error (Clarity 2026-09-03: the bar's message hid
   under the phone keyboard; the error now sits under the field it names) */
#pl-root .pl-field-req {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 4px;
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .14);
  font: 700 11px/1.4 'Manrope';
  letter-spacing: .04em;
  text-transform: uppercase;
  font-style: normal;
  color: rgba(255, 255, 255, .62);
  vertical-align: 1px;
}
#pl-root .pl-field-err {
  display: block;
  margin-top: 6px;
  font: 600 12.5px/1.4 'Manrope';
  color: var(--smv2-error, #ff6b6b);
}
#pl-root .pl-field-err[hidden] { display: none; }

/* ---------- file edits fee ---------- */
/* FEE MODEL v2 (2026-08-12): the tier PICKER is gone — the card is an
   itemized receipt. It is a permanent resident again (there is always the
   per-design fee, so the old "awkward empty box" objection died with the
   idle state); only the spot-UV row glides, riding the shared .pl-optrow
   accordion so it arrives with the finish instead of popping. */
#pl-root .pl-fee {
  margin-top: 14px;
  /* standalone peer card — chrome matches .pl-card, glass included */
  background: var(--pl-glass, rgba(18, 20, 27, .66));
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14));
  border-radius: var(--smv2-r-lg, 14px);
  padding: 16px;
  /* (the reveal nudge and its scroll-margin were retired 2026-08-07 —
     the auto-scroll read as a harsh screen jump; the sticky bar's fee
     line announces the charge the instant it arms) */
}
/* Title only — the head carried a status chip for one afternoon (2026-08-12)
   and no longer does. History worth keeping, because it is a rule now: it
   started as an amber "REQUIRED" (amber is this page's WARNING colour and
   "required" is penalty language — on a fee nobody asked for it announced a
   trap), became "REFUNDABLE" (read as a policy the BUYER could invoke, so it
   invited refund requests on orders where the work was genuinely done), then
   "REFUNDED IF NOT NEEDED", and was finally dropped: any chip summarising the
   refund is necessarily vaguer than the per-line notes it sits above, and two
   voices on one promise is one too many. The refund lives on the fee lines,
   next to the price it applies to (.pl-feeline-back). */
#pl-root .pl-fee-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}
#pl-root .pl-fee-title { font: 800 16px/1.2 'Manrope'; text-transform: uppercase; letter-spacing: -.01em; color: #fff; }
#pl-root .pl-fee-lede {
  margin: 0 0 4px;
  font: 500 13px/1.5 'Manrope';
  color: var(--pl-tx-2);
}
/* A receipt line. GRID, not a flex pair (changed 2026-08-12 — owner: the
   copy "seems trapped and compacted when theres so much space to the
   right"). It was `.pl-feeline-tx` and `.pl-feeline-amt` as flex siblings,
   which locks every line of copy into the column BESIDE the price and
   forbids it from ever using the space underneath one. Measured: 222px of
   dead gap at 1440, and at 375 a wide amount ("3 × $5.00 = $15.00") claimed
   146px of a 301px row and squeezed the description into a 143px column
   four lines tall.
   Now the LABEL and the AMOUNT share row 1, and the description and refund
   note span BOTH columns beneath them: the money still reads against the
   bold label on its own baseline, and the prose gets the whole card width.
   minmax(0, 1fr) on the first track, not a bare 1fr — 1fr floors at
   min-content, so one long unbroken word would shove the price off-card. */
#pl-root .pl-feeline {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 12px;
  align-items: baseline;
  padding-top: 8px;
}
#pl-root .pl-feeline-name {
  grid-column: 1;
  grid-row: 1;
  font: 700 14px/1.35 'Manrope';
  color: var(--pl-tx);
}
#pl-root .pl-feeline-desc {
  grid-column: 1 / -1;
  grid-row: 2;
  margin-top: 2px;
  font: 500 12px/1.5 'Manrope';
  color: var(--pl-tx-3);
}
/* The refund promise. Deliberately NOT fine-print grey (--pl-tx-4 is the
   legal floor, and burying a refund down there is what makes a fee read as
   a trick): it sits one step BRIGHTER than the description it follows,
   because it is the most reassuring fact on the card. Drawn return arrow,
   not a glyph — see snippets/peptide-labels-refund.liquid.

   ⚠️ It is NOT --acc-text, and must never go back. Clarity 2026-08-13: this
   note was the page's ONLY real dead-click cluster — one paid visitor tapped
   "Already built it in? Refu…" three times, then every other fee line, then
   left (session qa4esc, 6 dead clicks in 40s, nothing after). Measured cause:
   --acc-text IS this page's link colour (see .pl-card-note a), so the note
   rendered the EXACT same #4d74ff as a real link, at weight 600 vs the link's
   500, with a leading icon — more link-like than the actual links. cursor
   is the only thing that differed, and ~98% of this store's traffic is touch,
   where cursor does not exist. Brightness carries the emphasis now; accent
   colour on this page means "you can tap this". */
#pl-root .pl-feeline-back {
  grid-column: 1 / -1;
  grid-row: 3;
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin-top: 6px;
  font: 600 12px/1.45 'Manrope';
  color: var(--pl-tx);
}
#pl-root .pl-feeline-back svg {
  flex: none;
  width: 13px;
  height: 13px;
  margin-top: 1px; /* optical: aligns the arrow's shaft to the first line's x-height */
}
#pl-root .pl-feeline-amt {
  grid-column: 2;
  grid-row: 1;
  justify-self: end;
  font: 800 15px/1.35 'Manrope';
  font-variant-numeric: tabular-nums;
  color: #fff;
  white-space: nowrap;
}
#pl-root .pl-fine { margin: 10px 0 0; font: 500 12px/1.5 'Manrope'; color: var(--pl-tx-4); }

/* ---------- sticky total bar ---------- */
#pl-root .pl-bar {
  position: sticky;
  bottom: 12px;
  z-index: 5;
  margin-top: 16px;
  /* glassier and darker than the cards — it floats over content, so it
     needs more of its own presence */
  background: var(--pl-glass-deep, rgba(11, 12, 16, .72));
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  /* The bar itself no longer enters or leaves — it is sticky from page load
     (2026-08-12). These transitions remain for the height changes it still
     makes as its CONTENT changes (the fee line, the next-rung nudge, and the
     action gate opening). */
  transition: opacity 300ms cubic-bezier(.2, .7, .2, 1),
    transform 300ms cubic-bezier(.2, .7, .2, 1), visibility 0s;
  border: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14));
  border-radius: var(--smv2-r-cta, 16px);
  padding: 12px 14px;
  box-shadow: var(--smv2-e3, 0 18px 44px rgba(0, 0, 0, .5));
}
/* phones: the money leads on its own line and the description wraps at
   full width beneath it — side-by-side, a long pooled description wrapped
   into a squished column beside the 22px total (owner catch 2026-08-10).
   From 600 up the row returns to the desc-left / money-right baseline. */
#pl-root .pl-bar-row {
  display: flex;
  flex-direction: column-reverse;
  align-items: flex-start;
  gap: 3px;
}
/* total + count travel together: the count is what the total is FOR, so it
   sits on the total's baseline rather than joining the rate line. It keeps
   body-text weight (.72) — brighter than the .6 supporting line, because a
   price with no quantity beside it is not a fact anyone can check. */
#pl-root .pl-bar-money {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
#pl-root .pl-bar-count { font: 600 13px/1.35 'Manrope'; color: var(--pl-tx); }
/* Each fee in the bar's "Includes …" line is ONE unbreakable unit, so the
   line can only break BETWEEN fees — never inside a phrase. Without this the
   browser broke wherever the edge fell and produced "…spot UV / edits",
   which reads as a rendering fault rather than a wrap (owner 2026-08-12).
   The leading "+" sits INSIDE the second bit, so nowrap carries it down with
   the fee it belongs to and it can never strand at the end of line 1. */
#pl-root .pl-barfee-bit { white-space: nowrap; }
#pl-root .pl-bar-lines { min-width: 0; }
/* The fee joins the rate on ONE line (owner 2026-08-10: "a lot of dead
   space right here"). It used to be its own row, which cost 20px of blank
   whenever there was no fee — the `[hidden] { min-height }` reservation
   that used to live in the ≥1024 block existed purely so the bar would not
   resize when Embossing was toggled. Sharing the rate's line is the better
   answer to the same problem: the row height no longer depends on whether
   a fee exists, so there is nothing to reserve AND nothing to jump.
   ⚠️ ALL widths now, phones included (owner 2026-08-13: the bar was "too
   dense"). The old rule kept phones stacked because width was the scarce
   thing there — but that was when the stack held three prose facts. With
   the shipping sentence moved INSIDE the progress rail, rate + fee are the
   only quiet prose left, they fit 375 side by side ("$0.30 each · 70% off ·
   Includes $5.00 file prep" ≈ 280px of 305), and flex-wrap still degrades a
   long multi-fee list to a second line instead of clipping. One quiet line
   instead of two is the density win. */
#pl-root .pl-bar-lines {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 5px;
}
#pl-root .pl-bar-fee { margin-top: 0; }
#pl-root .pl-bar-fee::before { content: '·'; margin-right: 5px; opacity: .55; }
/* the desc is empty on the 0%-off rung now that the rate lives in the count
   ("500 labels × $1.00") — without these the fee line leads with a stray
   joiner dot after an invisible sibling */
#pl-root .pl-bar-desc:empty { display: none; }
#pl-root .pl-bar-desc:empty + .pl-bar-fee::before { content: none; }
@media (min-width: 600px) {
  #pl-root .pl-bar-row {
    flex-direction: row;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
  }
  /* right-hand group reads "100 labels  $151.67" — count first, total last
     and hard against the edge, which is where the eye lands returning from
     the left-hand rate line */
  #pl-root .pl-bar-money { flex-direction: row-reverse; }
}
/* WEIGHT carries the hierarchy, not five shades of grey (2026-08-12 pass —
   "easier to comprehend"). Before this the bar stacked THREE supporting
   lines all at weight 600, separated only by .72 → .60 → .55 alpha: a
   0.05 step is invisible at 12px on glass, so the eye got three equal
   statements and had to read all of them to find the one it wanted.
   Now the count (600, --pl-tx) leads the supporting group and the two
   explain-the-number lines drop to 500 — the design language's body
   de-emphasis step — so the ranking survives the squint test without
   inventing new colour tiers. */
/* the discount is a BADGE now (owner 2026-09-04: "the % off should be on
   the same line or at least have bigger emphasis") — it is the one fact in
   the bar that sells, and as grey prose it vanished. Savings green, not
   brand blue: a blue pill next to the price reads as a button. */
#pl-root .pl-bar-desc {
  display: inline-block;
  font: 800 12px/1 'Manrope';
  letter-spacing: .01em;
  font-variant-numeric: tabular-nums;
  color: var(--smv2-success-2, #7fd6a4);
  background: rgba(127, 214, 164, .12);
  border: 1px solid rgba(127, 214, 164, .32);
  border-radius: 999px;
  padding: 5px 9px 4px;
  vertical-align: middle;
}
#pl-root .pl-bar-total { font: 800 22px/1 'Manrope'; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
#pl-root .pl-bar-was {
  font: 600 14px/1 'Manrope';
  font-variant-numeric: tabular-nums;
  color: var(--pl-tx-3);
  text-decoration: line-through;
  text-decoration-color: rgba(255, 255, 255, .5);
  text-decoration-thickness: 1.5px;
}
#pl-root .pl-bar-was[hidden] { display: none; }
/* explicit 0 bottom — it is a <p>, and the inherited bottom margin was still
   there, growing the shared line by 11px the moment a fee appeared.
   4px top: the rate and the fees are ONE thought (how this total was built),
   so they sit tight together — see the 10px below that separates them from
   the nudge, which is a different thought. */
#pl-root .pl-bar-fee {
  margin: 2px 0 0;
  font: 500 12px/1.35 'Manrope';
  font-variant-numeric: tabular-nums;
  color: var(--pl-tx-3);
}
/* it is a <button> now (see the section comment): strip the UA chrome, then
   put back exactly enough affordance to say "this goes somewhere" — a hairline
   underline under the amounts only, so the bar stays one quiet line. Hover is
   gated: ~82% of this page is touch. */
#pl-root button.pl-bar-fee {
  padding: 0; border: 0; background: none; text-align: left; cursor: pointer;
  -webkit-appearance: none; appearance: none;
  position: relative;
}
/* 44px hit area on a 16px line (audit 2026-09-04): the button was made a
   button for the dead clicks it took, then given no tap target — on a phone
   the tap missed, or landed on Add to cart 20px below. Same transparent
   ::after recipe as .pl-card-note a. */
#pl-root button.pl-bar-fee::after { content: ""; position: absolute; left: -6px; right: -6px; top: 50%; height: 44px; transform: translateY(-50%); }
#pl-root .pl-barfee-bit {
  text-decoration: underline;
  text-decoration-color: rgba(255, 255, 255, .26);
  text-underline-offset: 2px;
  transition: text-decoration-color 150ms cubic-bezier(.2, .7, .2, 1),
              color 150ms cubic-bezier(.2, .7, .2, 1);
}
@media (hover: hover) {
  #pl-root button.pl-bar-fee:hover { color: rgba(255, 255, 255, .82); }
  #pl-root button.pl-bar-fee:hover .pl-barfee-bit { text-decoration-color: rgba(255, 255, 255, .5); }
}
#pl-root button.pl-bar-fee:focus-visible { outline: 2px solid var(--acc-2, #4d75ff); outline-offset: 3px; border-radius: 4px; }
/* SHIPPING — its own row, not a fourth grey line (owner 2026-08-13: "i feel
   like it should be more obvious"). Sharing the fee's group made it read as
   more fine print under a price, which is the wrong category twice over: the
   free tier is a reason to buy more, and the $10 is a charge the buyer is
   entitled to see coming.
   Obviousness comes from POSITION (own row), SIZE (13 vs 12), WEIGHT (700 vs
   500), BRIGHTNESS and an ICON — deliberately not from colour.
   ⚠️ Never --acc-text: accent on this page means "you can tap this", which
   the refund note learned the expensive way (see .pl-feeline-back). And NOT
   --smv2-success (#1a8f4c) either — measured against the panel it lands far
   under the 4.5:1 floor this page holds itself to at 12–13px, which is the
   exact failure --acc-text was created for. A lighter success token would
   have to be added to sm-v2.css first, and that is a design-language change,
   not a decision this page gets to make on its own. */
#pl-root .pl-bar-ship {
  display: block;
  margin: 10px 0 0;
  font: 700 13px/1.3 'Manrope';
  font-variant-numeric: tabular-nums;
  color: var(--pl-tx);
}
/* ⚠️ An explicit `display` BEATS the hidden attribute (the theme gotcha in
   CLAUDE.md — the same one [data-smv2-mobile][hidden] exists for). Without
   this the JS's `barShip.hidden = true` on an empty order leaves the last
   shipping figure sitting there. Caught by measuring: hiding the line changed
   the bar height by exactly 0px. */
#pl-root .pl-bar-ship[hidden] { display: none; }
/* QUIETER BAR ON PHONES (critique 2026-09-04): the two things a buyer checks
   before tapping are the total and the button, so on phones they come first
   and everything else — rate, discount, fee mention, shipping, the next-rung
   nudge — drops to one muted line beneath. The shipping rail becomes plain
   text here; the desktop bar is untouched. */
@media (max-width: 599px) {
  #pl-root .pl-bar { display: flex; flex-direction: column; }
  /* phones: the badge sits ON the price line ("$65.00  [50% off]"), the
     count and the fee mention share the line beneath. Both wrappers go
     display:contents so the four pieces are one flex row; the ::before is a
     full-width break between the two lines (order 3). */
  #pl-root .pl-bar-row { order: 1; flex-direction: row; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; }
  #pl-root .pl-bar-lines, #pl-root .pl-bar-money { display: contents; }
  #pl-root .pl-bar-row::before { content: ""; flex-basis: 100%; height: 0; order: 4; }
  #pl-root .pl-bar-total { order: 1; }
  #pl-root .pl-bar-was { order: 2; }
  #pl-root .pl-bar-desc { order: 3; align-self: center; }
  #pl-root .pl-bar-count { order: 5; }
  #pl-root .pl-bar-fee { order: 6; }
  #pl-root .pl-bar-desc:empty + .pl-bar-fee::before { content: '·'; }
  #pl-root .pl-actwrap { order: 2; }
  #pl-root .pl-bar-ship { order: 3; margin: 8px 0 0; font: 600 12px/1.4 'Manrope'; color: rgba(255, 255, 255, .6); }
  #pl-root .pl-bar-ship.is-free { color: var(--smv2-success-2, #7fd6a4); }
  #pl-root .pl-bar-next { order: 4; }
  #pl-root .pl-bar-warn { order: 5; }
  #pl-root .pl-msg { order: 6; }
  #pl-root .pl-srsum { order: 7; }
  #pl-root .pl-bar-fee, #pl-root .pl-bar-fee .pl-barfee-bit { font-size: 12px; color: rgba(255, 255, 255, .6); }
  #pl-root .pl-ship-track { background: transparent; border: 0; padding: 0; min-height: 0; height: auto; border-radius: 0; box-shadow: none; }
  #pl-root .pl-ship-fill { display: none; }
  #pl-root .pl-ship-railtx { position: static; transform: none; display: block; text-align: left; color: inherit; font: inherit; text-shadow: none; }
}
#pl-root .pl-ship-tx { display: block; }
/* the display-beats-[hidden] gotcha, per layer: tx shows only in edit mode,
   the track only outside it — each needs its own guard */
#pl-root .pl-ship-tx[hidden] { display: none; }
/* earned: full white, the brightest this line ever gets */
#pl-root .pl-bar-ship.is-free { color: #fff; }
/* SparklesText stars (see peptide-labels.js sparklesStart): the component's
   0.8s opacity/scale/rotate loop, verbatim values */
#pl-root .pl-spark {
  position: absolute; pointer-events: none; z-index: 2;
  animation: pl-spark .8s ease-in-out infinite;
  opacity: 0;
}
@keyframes pl-spark {
  0% { opacity: 0; transform: scale(0) rotate(75deg); }
  50% { opacity: 1; transform: scale(var(--spk-s, .8)) rotate(120deg); }
  100% { opacity: 0; transform: scale(0) rotate(150deg); }
}
@media (prefers-reduced-motion: reduce) {
  #pl-root .pl-spark { display: none; }
}

/* ---- the progress rail (owner 2026-08-13: "like an xp bar", then "too
   dense" — so the WORDS moved inside it, game-style: one element tells the
   whole shipping story instead of a bold sentence plus a separate track).
   20px tall so 11px text sits inside it; widths animated in place, the same
   technique .pl-gauge already uses one card above, so the two progress
   ideas on this page move alike rather than each inventing a motion. */
#pl-root .pl-ship-track {
  position: relative;
  display: block;
  height: 24px; /* was 20 — the caption inside needed 12px type to read (owner 2026-08-13: "a bit hard to read") */
  border-radius: 999px;
  background: rgba(255, 255, 255, .10);
}
/* the display:block-beats-[hidden] gotcha again, one level down — without
   this, edit mode (which hides the track) still paints an empty rail */
#pl-root .pl-ship-track[hidden] { display: none; }
/* the caption, topmost layer — the truck slides UNDER it. White at 11px/700
   holds AA on all three surfaces it can sit over: the empty track on glass
   (~15:1), the accent fill (same white-on-#1b4dff pair as the ATC button,
   ~6.7:1), and the truck's near-black disc. nowrap, no ellipsis: the string
   is money, and a truncated figure is worse than a wrapped one — at 320px
   the longest form (~227px at 11px) still fits the ~250px rail. */
#pl-root .pl-ship-railtx {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: grid;
  place-items: center;
  padding: 0 10px;
  font: 700 12px/1 'Manrope';
  font-variant-numeric: tabular-nums;
  letter-spacing: .01em;
  color: #fff;
  white-space: nowrap;
  /* the caption crosses three surfaces AND the fill's hard leading edge cuts
     straight through characters mid-word — per-surface contrast was fine, the
     CHURN is what read as hard. The shadow gives every glyph one constant
     dark ground of its own, so the edge passes behind the text instead of
     through it. */
  text-shadow: 0 1px 2px rgba(4, 5, 8, .8);
}
#pl-root .pl-ship-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 0;
  border-radius: 999px;
  background: var(--acc, #1b4dff);
  transition: width 300ms cubic-bezier(.2, .7, .2, 1);
}
/* (The truck marker that rode the fill's leading edge was removed
   2026-08-13 — the centred caption sat on top of it, and the owner called
   it: "u cant even see it anyways". The fill's own rounded edge is the
   progress marker now. If a marker ever returns, the words must leave the
   rail first — the two cannot share the centre.) */
@media (prefers-reduced-motion: reduce) {
  #pl-root .pl-ship-fill { transition: none; }
}
/* pooled next-rung nudge — positive, so it wears the accent text color.
   ⚠️ 14px against the fee line's 2px: PROXIMITY IS MEANING, and the old
   values (4px inside the group, 4px between groups) bonded the nudge to the
   fee line — so "what you could do next" read as part of "what you are being
   charged". Note these are MARGINS, not the rendered gap: half-leading from
   both line boxes adds ~5px to each, so a first attempt of 4/10 rendered as
   9px vs 10px and was invisible. 2/14 renders roughly 7px vs 14px — a 2×
   ratio, which is the point at which grouping actually reads. */
#pl-root .pl-bar-next {
  position: relative; /* anchors the 44px ::after hit area below */
  margin-top: 14px;
  font: 600 12px/1.35 'Manrope';
  font-variant-numeric: tabular-nums;
  color: var(--acc-text, #7d9aff);
  /* a real <button> since the tier-jump round — strip the UA chrome and
     keep the exact type the <p> had, so the proximity ladder above holds */
  display: block;
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: 0;
  padding: 0;
  text-align: inherit;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  position: relative;
}
/* the nudge is a real button drawn as a 12px line (16px tall): give it a 44px
   target without touching the proximity ladder above it */
#pl-root .pl-bar-next::after { content: ""; position: absolute; left: -8px; right: -8px; top: 50%; height: 44px; transform: translateY(-50%); }
/* hit pad, not height: the bar is 112px at 375 BECAUSE every line is thin —
   ±10px under a 12px line lands ~36px of target without growing the bar.
   Bounded above by the fee line (2px margin = grouping that must not be
   tap-shadowed) so the pad leans downward, toward the collapse spacer. */
#pl-root .pl-bar-next::after {
  content: '';
  position: absolute;
  left: -8px;
  right: -8px;
  top: -6px;
  bottom: -14px;
}
#pl-root .pl-bar-next:active { filter: brightness(.92); transition-duration: var(--pl-press-in); }
#pl-root .pl-bar-next[hidden] { display: none; }
/* the nudge now ENTERS (bottom-of-page reveal) — display:none -> block
   restarts the keyframe on every unhide for free */
#pl-root .pl-bar-next:not([hidden]) { animation: pl-barnext-in 200ms cubic-bezier(.2, .7, .2, 1); }
@keyframes pl-barnext-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  #pl-root .pl-bar-next:not([hidden]) { animation: none; }
}
#pl-root .pl-bar-warn {
  margin-top: 8px;
  font: 600 13px/1.45 'Manrope';
  color: var(--smv2-warn-2, #ffce6b);
}
#pl-root .pl-add {
  display: block;
  width: 100%;
  margin-top: 10px;
  padding: 18px;
  background: var(--acc, #1b4dff);
  border: 0;
  border-radius: var(--smv2-r-cta, 16px);
  color: #fff;
  font: 800 17px/1 'Manrope';
  cursor: pointer;
  transition: background-color 150ms, transform var(--pl-press-out) cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-add:not(:disabled):active { transform: scale(.99); transition-duration: var(--pl-press-in); }
#pl-root .pl-add:disabled { opacity: .55; cursor: default; }
/* same machined edge as the selected cards — the CTA and the selection
   language are one material */
#pl-root .pl-add {
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .12),
    inset 0 1px 0 rgba(255, 255, 255, .28),
    0 14px 30px -14px rgba(5, 8, 20, .85);
}
/* Both sentinels are EMPTY divs — Dawn's div:empty display:none (theme
   gotcha #1) would vanish them and their observers would never fire, so
   they need explicit geometry. -2px margin keeps them off the layout. */
#pl-root [data-pl-bar-sentinel],
#pl-root [data-pl-atc-sentinel] {
  display: block;
  width: 100%;
  height: 2px;
  margin-bottom: -2px;
  pointer-events: none;
}
/* (`.pl-bar.is-waiting` — which hid the ENTIRE bar until its flow home
   scrolled up — was removed 2026-08-12. The owner reverted to an
   always-sticky bar so the price is permanently visible; what is gated now
   is the ACTION below, not the readout.) */

/* THE ACTION GATE. House 0fr↔1fr collapse (same recipe as .pl-optrow,
   .pl-rowwrap, .pl-undowrap) rather than [hidden], because this element is
   pinned to the bottom of the screen: dropping ~63px of button in without
   animating the SPACE would jolt the whole bar upward in a single frame —
   the exact defect the undo strip was rebuilt to fix on 2026-08-11.
   Staged to the row canon: the space opens first (300ms), the content
   arrives after it (200ms, delayed 150ms). visibility:hidden while closed
   pulls the button out of the tab order, so it is never a focusable
   control the buyer cannot see. */
#pl-root .pl-actwrap {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows 300ms cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-actwrap[data-pl-off] { grid-template-rows: 0fr; }
#pl-root .pl-actinner {
  overflow: hidden;
  min-height: 0;
  opacity: 1;
  visibility: visible;
  transition: opacity 200ms cubic-bezier(.2, .7, .2, 1) 150ms, visibility 0s;
}
#pl-root .pl-actwrap[data-pl-off] .pl-actinner {
  opacity: 0;
  visibility: hidden;
  transition: opacity 150ms cubic-bezier(.2, .7, .2, 1), visibility 0s 300ms;
}
/* reduced motion: no glide, just appear — the gate is informational, and a
   held-open 300ms grid animation is the kind of thing PRM exists to skip */
@media (prefers-reduced-motion: reduce) {
  #pl-root .pl-actwrap,
  #pl-root .pl-actinner { transition: none; }
}
/* SR-only settled-order summary (see the JS note). The clip pattern, not
   [hidden] or display:none — an aria-live region removed from rendering is
   removed from the accessibility tree with it, and announces nothing. */
#pl-root .pl-srsum {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}
#pl-root .pl-msg {
  margin: 10px 0 0;
  font: 600 13px/1.45 'Manrope';
  color: var(--smv2-error, #ff6b6b);
}
#pl-root .pl-msg[hidden] { display: none; }
/* one-shot rejection shake, restarted by showMsg() on every failed submit so
   a repeat tap always gets a visible answer */
/* damped oscillation: each swing decays and ease-in-out zeroes the velocity
   at every reversal, so it reads as a settle rather than a zigzag */
#pl-root .pl-msg.is-hit { animation: pl-msg-hit 300ms ease-in-out; }
@keyframes pl-msg-hit {
  0% { transform: translateX(0); }
  18% { transform: translateX(-5px); }
  42% { transform: translateX(4px); }
  64% { transform: translateX(-2px); }
  84% { transform: translateX(1px); }
  100% { transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
  #pl-root .pl-msg.is-hit { animation: pl-msg-blink 300ms cubic-bezier(.2, .7, .2, 1); }
  @keyframes pl-msg-blink {
    0% { opacity: 1; }
    40% { opacity: 0.35; }
    100% { opacity: 1; }
  }
}
#pl-root .pl-assure {
  margin: 10px 0 0;
  font: 600 12px/1.5 'Manrope';
  color: var(--pl-tx-3);
  text-align: center;
}

/* PHONES — the bar runs two states (owner 2026-08-10: "easier to understand
   / digest on mobile"). Measured before touching it: at 375x812 the pinned
   bar was 244px, THIRTY PERCENT of the viewport, and only 53px of that was
   the button — the rest was four stacked greys the eye could not rank, two
   of which (the fee breakdown and the assurance) wrapped to two lines each.
   While the bar FLOATS it now carries only what a pinned bar is for — what
   this costs, what it buys, the next-rung nudge, and the button. The two
   explanatory lines return the instant it LANDS at its flow home, which is
   the decision moment and where occlusion stops costing anything.
   Nothing is deleted and nothing moves off the page: the fee already has
   its own card above, and the assurance keeps its full terms wording (it
   carries "all sales final after approval" — never trim that to fit).
   Desktop/tablet never collapse; the two-column layout has the room. */
@media (max-width: 599px) {
  /* collapsed by HEIGHT, not display:none — deliberate. The JS needs to know
     how tall this bar WOULD be expanded in order to decide when it has
     landed, and `scrollHeight` still reports the real content height through
     an overflow-hidden zero-height box. display:none reports 0, which forced
     the earlier scroll-latch guesswork that mis-fired during load. They stay
     in the accessibility tree either way, which is the right call: a screen
     reader should hear the fee and the terms regardless of scroll position. */
  /* ⚠️ THE FEE LINE IS NO LONGER COLLAPSED HERE (owner 2026-08-12: "we need
     to add the fee line so people know where the extra is coming from").
     It was hidden while floating under the 08-10 rule, which was correct
     when the bar was 244px and the total was only on screen at the decision
     moment. Two things changed: the bar is now sticky from page load, so the
     total is visible for the WHOLE scroll, and fee model v2 means every
     order carries at least $5 — so a floating bar without this line showed
     an unexplained number for minutes at a time. The height it costs was
     bought back by gating the button.
     The ASSURANCE stays collapsed: it is terms, not arithmetic, and it has
     nothing to explain about the number above it. */
  #pl-root .pl-bar:not(.is-landed) .pl-assure {
    height: 0;
    margin-top: 0;
    overflow: hidden;
    opacity: 0;
  }
  #pl-root .pl-bar-fee,
  #pl-root .pl-assure { transition: opacity 200ms cubic-bezier(.2, .7, .2, 1); }
  /* 11px = the scale's micro/legal step — it is fine print at the moment of
     purchase, not a line anyone reads twice */
  #pl-root .pl-assure { font-size: 11px; }
}

/* ---------- footer copy ---------- */
#pl-root .pl-disclaimer {
  margin: 24px auto 0;
  max-width: 440px;
  font: 500 12px/1.6 'Manrope';
  color: var(--pl-tx-4);
  text-align: center;
}

/* ---------- empty state (product missing/draft-hidden from storefront) ---------- */
#pl-root .pl-empty {
  margin-top: 16px;
  padding: 26px 20px;
  background: var(--smv2-panel, #12141b);
  border: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14));
  border-radius: var(--smv2-r-card, 18px);
  font: 600 15px/1.6 'Manrope';
  color: var(--pl-tx);
}

@media (hover: hover) {
  /* tier jump: a hovered rung previews the .is-here treatment — "this is
     where you'd land" — and the bar nudge underlines like the link it now is */
  #pl-root .pl-gauge-tick[data-pl-g-tick]:hover u { background: var(--acc, #1b4dff); }
  #pl-root .pl-gauge-tick[data-pl-g-tick]:hover b,
  #pl-root .pl-gauge-tick[data-pl-g-tick]:hover em { color: var(--acc-text, #7d9aff); }
  #pl-root .pl-bar-next:hover { text-decoration: underline; text-underline-offset: 2px; }
  #pl-root .pl-tier.is-static:hover { border-color: var(--smv2-line, rgba(255, 255, 255, .1)); }
  #pl-root .pl-tier.is-here:hover { border-color: var(--acc, #1b4dff); }
  #pl-root .pl-add:not(:disabled):hover { background: var(--acc-2, #4d75ff); transform: scale(1.01); }
  #pl-root .pl-later:hover { border-color: var(--smv2-line-2, rgba(255, 255, 255, .14)); }
  #pl-root .pl-drop:hover { border-color: var(--pl-tx-4); }
  #pl-root .pl-artbtn:hover {
    border-color: var(--acc-2, #4d75ff);
    background: color-mix(in srgb, var(--acc, #1b4dff) 20%, var(--smv2-raised, #1a1d26));
  }
  #pl-root .pl-addrow:hover {
    border-color: var(--acc-2, #4d75ff);
    background: color-mix(in srgb, var(--acc, #1b4dff) 10%, var(--smv2-raised, #1a1d26));
  }
  #pl-root .pl-row-x:hover {
    color: var(--smv2-error, #ff6b6b);
    border-color: var(--smv2-error, #ff6b6b);
    background: rgba(255, 107, 107, .12);
  }
  #pl-root .pl-amb-pause:hover { color: #fff; border-color: var(--smv2-line-2, rgba(255, 255, 255, .14)); }
}

#pl-root :focus-visible {
  outline: 2px solid var(--acc-2, #4d75ff);
  outline-offset: 2px;
}
/* A label-wrapped file input is invisible (opacity:0 over the whole chip),
   and opacity applies to an element's OUTLINE too — so the shared ring above
   paints on something nobody can see and keyboard focus lands silently.
   WCAG 2.4.7. The bag and jar builders already carry this on their drop
   zones (assets/custom-bag-easy.css, assets/jar-builder.css); this page was
   the only one that never inherited it, so the treatment matches theirs
   exactly rather than inventing a second focus language.
   `:focus-within` on the visible wrapper is the fix — not `:focus-visible`,
   which the wrapper never matches because the wrapper is not what receives
   focus. */
#pl-root .pl-artbtn:focus-within,
#pl-root .pl-drop:focus-within {
  outline: 2px solid var(--acc-2, #4d75ff);
  outline-offset: 2px;
}

/* inert fallback while the add-to-cart chain runs (interaction floor):
   inert blocks pointer + keyboard where supported (iOS 15.5+), this class
   covers the rest. The veil itself lives in sm-v2.css (.smv2-atcveil*). */
#pl-root.is-adding { pointer-events: none; cursor: progress; }

/* ---- .pl-down: the press state, driven from pointerdown (owner 2026-08-13:
   "the buttons still feel laggy/delayed on my phone" — live, Safari, the
   pill cards). The :active rules above NEVER FIRED on iOS touch: WebKit
   only engages :active during touch when a touchstart/pointer handler
   exists on an ancestor, and this page registered none — so the 70ms press
   dip existed for mice only, and a phone tap got zero acknowledgment until
   the selection repainted. That unacknowledged gap IS the reported lag;
   dispatch itself was already fast (the 08-12 touch-action fix).
   pointerdown paints at finger contact with no WebKit heuristics, and
   pointercancel clears it the moment a tap becomes a scroll. The :active
   rules STAY for mouse/keyboard — both paths paint the same declarations,
   so a device that fires both just agrees with itself. Twins of every
   :active body on the page: */
#pl-root .smv2-pill:not(:disabled).pl-down,
#pl-root .pl-tier.pl-down,
#pl-root .pl-artbtn.pl-down,
#pl-root .pl-addrow.pl-down,
#pl-root .pl-drop.pl-down,
#pl-root .pl-amb-pause.pl-down,
#pl-root .pl-gauge-tick[data-pl-g-tick].pl-down,
#pl-root .pl-bar-next.pl-down {
  filter: brightness(.92);
  transition-duration: var(--pl-press-in);
}
#pl-root .pl-undo-btn.pl-down {
  background: color-mix(in srgb, var(--acc, #1b4dff) 18%, transparent);
  color: #fff;
  transition-duration: var(--pl-press-in);
}
#pl-root .pl-row-x.pl-down {
  filter: brightness(.92);
  transition-duration: var(--pl-press-in);
  color: var(--smv2-error, #ff6b6b);
  border-color: var(--smv2-error, #ff6b6b);
  background: rgba(255, 107, 107, .12);
}
#pl-root .pl-add:not(:disabled).pl-down { transform: scale(.99); transition-duration: var(--pl-press-in); }

@media (prefers-reduced-motion: reduce) {
  /* no wildcard kill here (audit 2026-09-04): `#pl-root * { transition-duration: .01ms }`
     flattened the transitions that CARRY state (fee card open/close, the
     bar's landed state, inline errors). Reduced motion means no movement,
     not no feedback; the decorative motion is parked by the targeted
     blocks throughout this file. */
  #pl-root .pl-vial { animation: none; }
  #pl-root .pl-add:active, #pl-root .pl-add:hover, #pl-root .pl-add.pl-down { transform: none; }
  /* both glides + the crossfade are transitions — the blanket rule above
     flattens them; only keyframe animations need explicit opt-outs */
  /* ⚠️ .pl-artbtn, not .pl-drop — the live art control is
     <label class="pl-artbtn" data-pl-drop>, so the old selector matched
     NOTHING and the "Opening your files…" overlay animated straight through
     a reduced-motion preference. Both listed now; .pl-drop is dead markup
     whose CSS still exists. (2026-08-13) */
  #pl-root .pl-artbtn.is-opening::after,
  #pl-root .pl-drop.is-opening::after { animation: none; }
  #pl-root .pl-undowrap:not([hidden]),
  #pl-root .pl-undowrap:not([hidden]) .pl-undoclip { animation: none; }
}

/* ---------- desktop ---------- */
@media (min-width: 768px) {
  /* keep the tokens and the padding in lockstep — ::before cancels them by
     name, so a raw padding value here would silently uncover the top strip */
  #pl-root { --pl-pad-top: 146px; --pl-pad-x: 60px; padding: var(--pl-pad-top) var(--pl-pad-x) 72px; }
  #pl-root .pl-col { max-width: 680px; }
  #pl-root .pl-card, #pl-root .pl-fee { padding: 20px; }
  #pl-root .pl-bar-total { font-size: 26px; }
}

/* ≥1024: two columns — the label stock (step 1) sticky on the left, the
   peptide list + fee + checkout flowing on the right. The form joins the
   grid via display:contents so its wrapper sits beside the stock column.
   No equal-height machinery: the peptide list owns the page's height and
   the sticky stock card keeps step 1's choices in view alongside it. */
@media (min-width: 1024px) {
  #pl-root .pl-col { max-width: 1280px; }
  #pl-root .pl-grid {
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: 16px;
    /* default align-items:stretch ON PURPOSE — the stock column must span
       the full row height or its sticky card has no room to stick in */
    margin-top: 14px;
  }
  #pl-root .pl-colwrap { display: block; } /* base dissolves them; here they are the columns */
  #pl-root .pl-colwrap > .pl-card:first-child { margin-top: 0; }
  /* stock follows the reader down the peptide list (86px clears the fixed
     header pill); its own height still fits a 768px laptop viewport */
  #pl-root .pl-colwrap--stock .pl-card { position: sticky; top: 86px; }
  /* (the fee card's desktop "permanent resident" era ended 2026-08-10 —
     an idle card sized for its active content read as an awkward empty box
     ("looks a bit awkward when theres no selection"), and the flowing
     two-column layout no longer needs the stillness it bought. The base
     accordion glide now applies at every width: no finish, no card.) */
  /* (the fee/next `[hidden] { min-height: 16.2px }` reservations were removed
     2026-08-10 — owner: "a lot of dead space right here". They held a blank
     16.2px line open so revealing the fee or the nudge would not resize the
     bar, but they charged that height ALWAYS, and with neither showing the
     bar carried 40px of empty. The fee now shares the rate's line (see the
     ≥600 block), so its row height is constant with or without it. The nudge
     keeps a real reveal — it appears and disappears with the pooled total,
     and a bar that grows by one line when you cross a rung is honest
     feedback, not a jump to be hidden.) */
  /* hero compression, part 2 (see the vial block near the top): these sit
     here because the base .pl-h1/.pl-intro rules would out-order them there */
  #pl-root .pl-h1 { font-size: 44px; }
  #pl-root .pl-intro { max-width: 560px; }
  /* two cards side by side reading 1 → 2: the in-card number chips are
     honest at every width now, so they stay (the old column headers died
     with the three-column layout) */
}

/* ---------- designs on file (2026-09-02, specimen pass 09-02) ----------
   Signed-in customers see their past label designs above the peptide list
   and pour one into a row with a tap. Each chip is a SPECIMEN: the label
   art is the hero at label-true proportions with the die's own corners, a
   record kicker (order · run) sits over the name, the paid tags are read-
   only boxes (boxes read, pills tap) and the accent badge is the single
   tappable cue — it becomes a check once the design is on the list. Flat
   panel + hairline per canon; the hover is a 1px lift on the house curve,
   gated on hover:hover, and the material name rides the thumbnail frame as
   a tint only (no rainbow, no glow). Past the visible cap the rest fold
   behind "Show all"; past six a search box appears. */
#pl-root .pl-onfile { margin-top: 14px; }
#pl-root[data-pl-editing] .pl-onfile { display: none; } /* one line = one design */
#pl-root .pl-onfile-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
#pl-root .pl-onfile-count {
  display: inline-block;
  margin-left: 4px;
  padding: 1px 6px;
  border-radius: 4px;
  background: rgba(255, 255, 255, .08);
  color: #fff;
  font: 700 10.5px/1.5 'Manrope';
  letter-spacing: 0;
  vertical-align: 1px;
}
#pl-root .pl-onfile-note { font: 500 13px/1.4 'Manrope'; color: var(--smv2-text-2, rgba(255, 255, 255, .62)); }
#pl-root .pl-onfile-search {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  min-height: 44px;
  padding: 0 12px;
  background: var(--well, rgba(14, 16, 22, .9));
  border: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14));
  border-radius: 10px;
  color: rgba(255, 255, 255, .55);
  transition: border-color 150ms;
}
#pl-root .pl-onfile-search:focus-within { border-color: color-mix(in srgb, var(--acc, #1b4dff) 70%, transparent); }
#pl-root .pl-onfile-search svg { flex: none; width: 16px; height: 16px; }
#pl-root .pl-onfile-search input {
  flex: 1;
  min-width: 0;
  padding: 10px 0;
  background: transparent;
  border: 0;
  color: #fff;
  font: 600 16px/1.2 'Manrope'; /* 16px: iOS zooms anything smaller */
  outline: none;
}
/* Dawn's global :focus-visible ring (a pale outline plus a background-colour
   box-shadow halo) lands on this input as a thick white box (owner 2026-09-02).
   The field's own focus state is the wrapper's accent border, so the inner
   ring goes; the wrapper's :focus-within below carries the visible focus. */
#pl-root .pl-onfile-search input:focus,
#pl-root .pl-onfile-search input:focus-visible { outline: none; box-shadow: none; }
#pl-root .pl-onfile-search:focus-within { border-color: var(--acc-2, #4d75ff); box-shadow: 0 0 0 2px color-mix(in srgb, var(--acc, #1b4dff) 35%, transparent); }
#pl-root .pl-onfile-search input::placeholder { color: rgba(255, 255, 255, .42); }
#pl-root .pl-onfile-search input::-webkit-search-cancel-button { filter: invert(1) brightness(.8); }
#pl-root .pl-onfile-list {
  display: grid;
  /* one chip per line: the spec + paid tag row needs the width, and a
     specimen reads better wide than tiled */
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 420px), 1fr));
  gap: 8px;
  margin-top: 10px;
}
#pl-root .pl-onfile-chip {
  position: relative;
  display: grid;
  grid-template-columns: 100px minmax(0, 1fr) 24px;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 72px;
  padding: 10px 12px 10px 10px;
  background: rgba(38, 42, 54, .92);
  border: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14));
  border-radius: var(--smv2-r-lg, 14px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06);
  color: #fff;
  text-align: left;
  font-family: 'Manrope';
  cursor: pointer;
  transition: border-color 150ms, background-color 150ms, transform 150ms cubic-bezier(.2, .7, .2, 1),
    box-shadow 150ms, filter var(--pl-press-out) cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-onfile-chip[hidden] { display: none; }
#pl-root .pl-onfile-chip:active { filter: brightness(.92); transform: none; transition-duration: var(--pl-press-in); }
@media (hover: hover) {
  #pl-root .pl-onfile-chip:hover {
    border-color: color-mix(in srgb, var(--acc, #1b4dff) 58%, transparent);
    transform: translateY(-1px);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06), var(--smv2-e2, 0 6px 18px rgba(0, 0, 0, .35));
  }
  #pl-root .pl-onfile-chip:hover .pl-onfile-go { background: color-mix(in srgb, var(--acc, #1b4dff) 85%, #fff); }
}
#pl-root .pl-onfile-chip.is-used {
  border-color: color-mix(in srgb, var(--acc, #1b4dff) 70%, transparent);
  background: color-mix(in srgb, var(--acc, #1b4dff) 12%, rgba(38, 42, 54, .92));
}
#pl-root .pl-onfile-chip:disabled { opacity: .45; cursor: not-allowed; }
/* the label itself: label-true 100×45 with the die's corners; the frame
   carries the material as a tint (holographic cooler, matte neutral) */
#pl-root .pl-onfile-art {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100px;
  height: 45px;
  border-radius: 6px;
  overflow: hidden;
  background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(255, 255, 255, .16);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .10);
  color: rgba(255, 255, 255, .38);
}
#pl-root .pl-onfile-art.is-sharp { border-radius: 2px; }
#pl-root .pl-onfile-chip[data-pl-onfile-mat="Holographic"] .pl-onfile-art {
  border-color: color-mix(in srgb, var(--acc, #1b4dff) 45%, rgba(255, 255, 255, .16));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18);
}
#pl-root .pl-onfile-art img { display: block; width: 100%; height: 100%; object-fit: cover; }
#pl-root .pl-onfile-art-empty { width: 22px; height: 22px; }
#pl-root .pl-onfile-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
#pl-root .pl-onfile-kicker {
  font: 700 11px/1.3 'Manrope';
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .5);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#pl-root .pl-onfile-name { font: 800 15px/1.2 'Manrope'; letter-spacing: -.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#pl-root .pl-onfile-meta { font: 500 12px/1.3 'Manrope'; color: var(--smv2-text-2, rgba(255, 255, 255, .62)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#pl-root .pl-onfile-tags { display: flex; flex-wrap: wrap; gap: 4px 5px; margin-top: 4px; }
#pl-root .pl-onfile-tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 5px;
  border-radius: 4px; /* a box: read, not tapped */
  background: rgba(127, 214, 164, .10);
  border: 1px solid rgba(127, 214, 164, .22);
  font: 700 11px/1.3 'Manrope';
  letter-spacing: .04em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--smv2-success-2, #7fd6a4); /* the dashboard's "paid" green */
}
#pl-root .pl-onfile-tag svg { flex: none; }
/* the specs wear the same box in the neutral tone — one vocabulary, two
   meanings: white = what it is, green = what is already paid */
#pl-root .pl-onfile-tag--spec {
  background: rgba(255, 255, 255, .06);
  border-color: rgba(255, 255, 255, .14);
  color: rgba(255, 255, 255, .74);
}
#pl-root .pl-onfile-go {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 999px;
  background: var(--acc, #1b4dff);
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .30);
  transition: background-color 150ms;
}
#pl-root .pl-onfile-plus { font: 700 18px/1 'Manrope'; padding-bottom: 2px; }
#pl-root .pl-onfile-check { display: none; width: 14px; height: 14px; }
#pl-root .pl-onfile-chip.is-used .pl-onfile-plus { display: none; }
#pl-root .pl-onfile-chip.is-used .pl-onfile-check { display: block; }
/* the fold: one quiet text button, blue = tappable (small blue text reads
   at #4d75ff on this ground); 44px hit area without a 44px box */
#pl-root .pl-onfile-more {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
  padding: 0;
  background: none;
  border: 0;
  color: #4d75ff;
  font: 700 13.5px/1.2 'Manrope';
  cursor: pointer;
}
#pl-root .pl-onfile-more::after { content: ''; position: absolute; inset: -12px 0; }
#pl-root .pl-onfile-more::before {
  content: '';
  width: 7px;
  height: 7px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 150ms cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-onfile.is-open .pl-onfile-more::before { transform: translateY(2px) rotate(225deg); }
#pl-root .pl-onfile-more[hidden] { display: none; }
#pl-root .pl-onfile-none { margin: 10px 0 0; font: 500 13px/1.4 'Manrope'; color: var(--smv2-text-2, rgba(255, 255, 255, .62)); }
#pl-root .pl-onfile-none[hidden] { display: none; }
/* chips lifted by "Show all" settle in rather than pop */
@keyframes plOnfileIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
#pl-root .pl-onfile.is-open .pl-onfile-chip { animation: plOnfileIn 200ms cubic-bezier(.2, .7, .2, 1) both; }
@media (prefers-reduced-motion: reduce) {
  #pl-root .pl-onfile-chip, #pl-root .pl-onfile-more::before { transition: none; animation: none; }
  #pl-root .pl-onfile.is-open .pl-onfile-chip { animation: none; }
}
/* phones (owner 2026-09-02: "the spacing of the reusing label designs is a
   little off on mobile"): the three-column chip left the 100×45 specimen
   floating beside a 144px text column whose tags wrapped four deep, so every
   chip ran 120–145px tall with dead space around the art. Here the art and
   the kicker + name share the top band and the tags take the full width
   underneath — one or two tag lines, chip ~90px, nothing floating. */
@media (max-width: 599px) {
  #pl-root .pl-onfile { margin-bottom: 16px; }
  #pl-root .pl-onfile-chip {
    grid-template-columns: 100px minmax(0, 1fr) 24px;
    grid-template-rows: auto auto auto;
    row-gap: 0;
    column-gap: 12px;
    min-height: 0;
  }
  #pl-root .pl-onfile-art { grid-column: 1; grid-row: 1 / 3; }
  #pl-root .pl-onfile-txt { display: contents; }
  #pl-root .pl-onfile-kicker { grid-column: 2; grid-row: 1; align-self: end; }
  #pl-root .pl-onfile-name { grid-column: 2; grid-row: 2; align-self: start; margin-top: 2px; }
  #pl-root .pl-onfile-go { grid-column: 3; grid-row: 1 / 3; }
  #pl-root .pl-onfile-tags { grid-column: 1 / -1; grid-row: 3; margin-top: 10px; }
  #pl-root .pl-onfile-tag { font-size: 11px; }
}
/* the row caption: green while the setup is still covered, plain once the
   buyer's changes put a fee back on the table */
#pl-root .pl-row-onfile {
  grid-column: 1 / -1;
  grid-row: 5;
  font: 600 12px/1.35 'Manrope';
  color: var(--smv2-success-2, #7fd6a4);
  margin-top: -2px;
}
#pl-root .pl-row-onfile[data-state="new"] { color: rgba(255, 255, 255, .78); }
#pl-root .pl-row-onfile[hidden] { display: none !important; }
/* BATCH # + EXPIRY, per peptide (Ryan 2026-09-04; owner: "the toggle
   definitely has to live on the peptide"). A slim OPTION BAR on the row's
   second line: a real switch (knob drawn on :has(:checked)), the label, the
   live price tag. When on, the bar's lower half opens with the two labelled
   fields, sharing the bar's edge (top corners on the switch line, bottom
   corners on the fields), so the pair reads as ONE object inside the row.
   Phones: rows 5-6, full width. Desktop: rows 2-3 under the name column. */
#pl-root .pl-row-batch {
  grid-row: 5; grid-column: 1 / -1;
  display: flex; align-items: center; gap: 10px;
  min-height: 44px; padding: 8px 12px; cursor: pointer; user-select: none;
  background: rgba(14, 15, 20, .55);
  border: 1px solid rgba(255, 255, 255, .08);
  border-radius: 10px;
  font: 600 13px/1.3 'Manrope'; color: var(--pl-tx-2);
  transition: border-color 150ms, color 150ms;
  /* no OS tap flash over the whole bar (owner 2026-09-04): the switch's own
     knob and track animate, that is the feedback */
  -webkit-tap-highlight-color: transparent;
}
#pl-root .pl-row-batch[hidden] { display: none !important; }
#pl-root .pl-switch { position: relative; flex: none; width: 36px; height: 20px; border-radius: 999px; background: rgba(255, 255, 255, .34); transition: background-color 150ms; } /* .34: the off track measured 1.77:1 against the bar at .18 — UI parts need 3:1 (audit 2026-09-04) */
#pl-root .pl-switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; transition: transform 150ms cubic-bezier(.2, .7, .2, 1); }
#pl-root .pl-row-batch-tx { flex: 0 1 auto; min-width: 0; white-space: nowrap; } /* price tag rides right after the text (owner 2026-09-04), not at the far edge; one line always */
@media (max-width: 359px) { #pl-root .pl-row-batch { font-size: 12px; gap: 8px; } }
#pl-root .pl-row-batch-fee { font-style: normal; font-weight: 700; color: #9db3ff; white-space: nowrap; } /* one step lighter than --acc-text: 4.35:1 on the bar at 13px missed AA (audit 2026-09-04) */
#pl-root .pl-row-batch:has(.pl-row-batch-on:checked) { color: #fff; border-color: color-mix(in srgb, var(--acc, #1b4dff) 45%, transparent); border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
#pl-root .pl-row-batch:has(.pl-row-batch-on:checked) .pl-switch { background: var(--acc, #1b4dff); }
#pl-root .pl-row-batch:has(.pl-row-batch-on:checked) .pl-switch::after { transform: translateX(16px); }
#pl-root .pl-row-batch:has(.pl-row-batch-on:focus-visible) { outline: 2px solid var(--acc-2, #4d75ff); outline-offset: 2px; }
@media (hover: hover) { #pl-root .pl-row-batch:hover { border-color: rgba(255, 255, 255, .2); } }
#pl-root .pl-row-batchfields {
  grid-row: 6; grid-column: 1 / -1;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px;
  margin-top: -9px; padding: 10px 12px 12px;
  background: rgba(14, 15, 20, .55);
  border: 1px solid color-mix(in srgb, var(--acc, #1b4dff) 45%, transparent); border-top: 0;
  border-radius: 0 0 10px 10px;
}
#pl-root .pl-row-batchfields[hidden] { display: none !important; }
#pl-root .pl-batchcell { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
#pl-root .pl-batchlab { font: 700 11px/1 'Manrope'; letter-spacing: .07em; text-transform: uppercase; color: var(--pl-tx-4); padding-left: 2px; }
#pl-root .pl-row-batchno, #pl-root .pl-row-exp {
  width: 100%; min-height: 44px; padding: 10px 12px; min-width: 0;
  border-radius: 10px; border: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14));
  background-color: rgba(255, 255, 255, .05); color: #fff;
  font: 600 16px/1.2 'Manrope'; /* 16px: iOS zoom floor */
}
#pl-root .pl-row-batchno::placeholder, #pl-root .pl-row-exp::placeholder { color: rgba(255, 255, 255, .34); font-weight: 500; }
/* the date input: dark picker chrome, our padding on iOS (which ignores it
   without appearance:none), the calendar glyph legible on the dark well */
#pl-root .pl-row-exp { color-scheme: dark; -webkit-appearance: none; appearance: none; }
#pl-root .pl-row-exp::-webkit-date-and-time-value { text-align: left; min-height: 1.2em; }
#pl-root .pl-row-exp::-webkit-calendar-picker-indicator { opacity: .75; cursor: pointer; }
/* fine pointers get the V2 popover calendar (JS): the native glyph goes, the
   whole field is the trigger. Touch keeps the native picker + glyph. */
@media (hover: hover) and (pointer: fine) {
  #pl-root .pl-row-exp { cursor: pointer; }
  #pl-root .pl-row-exp::-webkit-calendar-picker-indicator { display: none; }
}
/* the popover lives on <body> (row wrappers clip), so no #pl-root scope */
.pl-cal {
  position: fixed; z-index: 90; width: 280px; padding: 12px;
  background: var(--smv2-panel, #12141b);
  border: 1px solid rgba(255, 255, 255, .14); border-radius: 14px;
  box-shadow: var(--smv2-e3, 0 24px 48px -20px rgba(0, 0, 0, .6));
  font-family: 'Manrope', system-ui, sans-serif; color: #fff;
  animation: plCalIn 150ms cubic-bezier(.2, .7, .2, 1);
}
@keyframes plCalIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.pl-cal-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.pl-cal-head b { font: 800 14px/1 'Manrope'; letter-spacing: -.01em; }
.pl-cal-head b span { font-weight: 600; color: rgba(255, 255, 255, .62); }
.pl-cal-nav { display: grid; place-items: center; width: 32px; height: 32px; padding: 0; border-radius: 999px; border: 1px solid rgba(255, 255, 255, .14); background: rgba(255, 255, 255, .03); color: #fff; cursor: pointer; transition: border-color 150ms; }
.pl-cal-nav svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.pl-cal-nav:disabled { opacity: .3; cursor: default; }
@media (hover: hover) { .pl-cal-nav:not(:disabled):hover { border-color: rgba(255, 255, 255, .34); } }
.pl-cal-nav:focus-visible, .pl-cal-day:focus-visible { outline: 2px solid var(--acc-2, #4d75ff); outline-offset: 2px; }
.pl-cal-dow, .pl-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.pl-cal-dow span { text-align: center; font: 700 11px/1 'Manrope'; letter-spacing: .04em; text-transform: uppercase; color: rgba(255, 255, 255, .45); padding: 6px 0; }
.pl-cal-day { height: 34px; padding: 0; border: 1px solid transparent; border-radius: 999px; background: none; color: rgba(255, 255, 255, .85); font: 600 13px/1 'Manrope'; font-variant-numeric: tabular-nums; cursor: pointer; transition: background-color 150ms, border-color 150ms; }
@media (hover: hover) { .pl-cal-day:not(:disabled):hover { background: rgba(255, 255, 255, .08); } }
.pl-cal-day.is-today { border-color: rgba(255, 255, 255, .28); }
.pl-cal-day.is-sel { background: var(--acc, #1b4dff); border-color: var(--acc, #1b4dff); color: #fff; font-weight: 800; }
.pl-cal-day:disabled { color: rgba(255, 255, 255, .22); cursor: default; }
@media (prefers-reduced-motion: reduce) { .pl-cal { animation: none; } }
/* phones: the half-width cell is ~150px and Android's date input clipped the
   year behind its calendar glyph ("12/31/202"). The whole field opens the
   picker on touch anyway, so the glyph goes and the padding tightens. */
@media (max-width: 599px) {
  #pl-root .pl-row-exp { padding-left: 10px; padding-right: 8px; }
  #pl-root .pl-row-exp::-webkit-calendar-picker-indicator { display: none; }
}
#pl-root .pl-row-batchno:focus:not(:focus-visible), #pl-root .pl-row-exp:focus:not(:focus-visible) { outline: none; }
@media (min-width: 768px) {
  #pl-root .pl-row .pl-row-batch { grid-row: 2; grid-column: 2 / -1; }
  #pl-root .pl-row .pl-row-batchfields { grid-row: 3; grid-column: 2 / -1; gap: 12px; }
  #pl-root .pl-batchcell { flex-direction: row; align-items: center; gap: 10px; }
  #pl-root .pl-batchlab { flex: none; padding-left: 0; }
  #pl-root .pl-batchcell input { flex: 1 1 auto; }
}
}
}
}
}
@media (min-width: 600px) {
  #pl-root .pl-row-onfile { grid-row: 4; grid-column: 2 / -1; padding-left: 12px; margin-top: -4px; } /* rows 2-3 are the batch bar + fields since 2026-09-04 */
}
/* the fee card's UV row, once every row keeps its paid layer */
#pl-root [data-pl-uvrow][data-pl-paid] .pl-feeline-amt { color: var(--smv2-success-2, #7fd6a4); }

/* ---------- the designs sheet (past six designs, 2026-09-02) ----------
   Same stage as the art gate (fixed, z-140, frosted scrim, section z-lift via
   body.pl-gate-lock). A bottom sheet on phones, a centred panel from 600px.
   Rows reuse the chip vocabulary (label-true art, spec + paid tags); the
   checkbox is the row's whole hit area. */
#pl-root .pl-sheet { position: fixed; inset: 0; z-index: 140; display: grid; place-items: end center; }
#pl-root .pl-sheet[hidden] { display: none !important; }
#pl-root .pl-sheet.is-open .pl-gate-scrim { opacity: 1; }
#pl-root .pl-sheet-panel {
  position: relative;
  display: flex; flex-direction: column;
  width: 100%; max-height: 92dvh;
  background: var(--smv2-panel, #12141b);
  border: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14));
  border-bottom: 0;
  border-radius: var(--smv2-r-xl, 22px) var(--smv2-r-xl, 22px) 0 0;
  box-shadow: var(--smv2-e4, 0 32px 80px -24px rgba(0, 0, 0, .8));
  opacity: 0; transform: translateY(16px);
  transition: opacity 200ms cubic-bezier(.2, .7, .2, 1), transform 250ms cubic-bezier(.2, .7, .2, 1);
}
#pl-root .pl-sheet.is-open .pl-sheet-panel { opacity: 1; transform: none; }
#pl-root .pl-sheet-head { padding: 16px 18px 12px; border-bottom: 1px solid var(--smv2-line, rgba(255, 255, 255, .1)); }
#pl-root .pl-sheet-titlerow { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
#pl-root .pl-sheet-title { margin: 0; font: 800 18px/1.15 'Manrope'; letter-spacing: -.02em; text-transform: uppercase; color: #fff; }
#pl-root .pl-sheet-x {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 44px; height: 44px; margin: -8px -10px -8px 0;
  background: none; border: 0; border-radius: 999px; color: rgba(255, 255, 255, .7);
}
#pl-root .pl-sheet-x svg { width: 22px; height: 22px; }
#pl-root .pl-sheet-x:focus-visible { outline: 2px solid var(--acc-2, #4d75ff); outline-offset: 2px; }
#pl-root .pl-sheet-search { margin-top: 10px; }
#pl-root .pl-sheet-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 4px 18px 12px; overscroll-behavior: contain; }
#pl-root .pl-sheet-group { margin-top: 14px; }
#pl-root .pl-sheet-group[hidden] { display: none; }
#pl-root .pl-sheet-ghead { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 32px; margin-bottom: 4px; }
#pl-root .pl-sheet-gk { font: 700 11px/1.3 'Manrope'; letter-spacing: .1em; text-transform: uppercase; color: rgba(255, 255, 255, .55); }
#pl-root .pl-sheet-addall {
  appearance: none; -webkit-appearance: none; cursor: pointer; position: relative;
  background: none; border: 0; padding: 4px 0;
  font: 700 13px/1 'Manrope'; color: var(--acc-2, #4d75ff);
}
#pl-root .pl-sheet-addall::after { content: ""; position: absolute; left: -8px; right: -8px; top: 50%; height: 44px; transform: translateY(-50%); }
#pl-root .pl-sheet-addall[aria-pressed="true"] { color: rgba(255, 255, 255, .6); }
#pl-root .pl-sheet-addall:focus-visible { outline: 2px solid var(--acc-2, #4d75ff); outline-offset: 2px; }
#pl-root .pl-sheet-row {
  display: grid; grid-template-columns: 22px 100px minmax(0, 1fr) auto; align-items: center; gap: 12px;
  min-height: 64px; padding: 8px 10px; margin-top: 6px;
  background: rgba(38, 42, 54, .92);
  border: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14));
  border-radius: var(--smv2-r-lg, 14px);
  cursor: pointer;
  transition: border-color 150ms, background-color 150ms;
}
#pl-root .pl-sheet-row[hidden] { display: none; }
#pl-root .pl-sheet-row:has(.pl-sheet-check:checked) { border-color: color-mix(in srgb, var(--acc, #1b4dff) 70%, transparent); background: color-mix(in srgb, var(--acc, #1b4dff) 12%, rgba(38, 42, 54, .92)); }
#pl-root .pl-sheet-row.is-onlist { cursor: default; opacity: .72; }
#pl-root .pl-sheet-check { width: 20px; height: 20px; margin: 0; accent-color: var(--acc, #1b4dff); cursor: inherit; }
#pl-root .pl-sheet-row:has(.pl-sheet-check:focus-visible) { outline: 2px solid var(--acc-2, #4d75ff); outline-offset: 2px; }
#pl-root .pl-sheet-onlist { font: 700 11px/1.3 'Manrope'; letter-spacing: .07em; text-transform: uppercase; color: rgba(255, 255, 255, .5); margin-top: 2px; }
#pl-root .pl-sheet-onlist[hidden] { display: none; }
#pl-root .pl-sheet-qty { font: 700 12px/1 'Manrope'; color: rgba(255, 255, 255, .55); font-variant-numeric: tabular-nums; }
#pl-root .pl-sheet-foot { display: flex; gap: 10px; padding: 12px 18px calc(12px + env(safe-area-inset-bottom, 0px)); border-top: 1px solid var(--smv2-line, rgba(255, 255, 255, .1)); background: var(--smv2-panel, #12141b); }
#pl-root .pl-sheet-cancel, #pl-root .pl-sheet-commit {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  min-height: 50px; padding: 0 18px; border-radius: 999px;
  font: 700 15px/1 'Manrope'; transition: filter var(--pl-press-out) cubic-bezier(.2, .7, .2, 1), opacity 150ms;
}
#pl-root .pl-sheet-cancel { flex: 0 0 auto; background: rgba(255, 255, 255, .07); border: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14)); color: #fff; }
#pl-root .pl-sheet-commit { flex: 1 1 auto; background: var(--acc, #1b4dff); border: 1px solid transparent; color: #fff; }
#pl-root .pl-sheet-commit:disabled { opacity: .45; cursor: not-allowed; }
#pl-root .pl-sheet-commit:not(:disabled):active, #pl-root .pl-sheet-cancel:active { filter: brightness(.92); transition-duration: var(--pl-press-in); }
#pl-root .pl-sheet-commit:focus-visible, #pl-root .pl-sheet-cancel:focus-visible { outline: 2px solid var(--acc-2, #4d75ff); outline-offset: 2px; }
@media (min-width: 600px) {
  #pl-root .pl-sheet { place-items: center; padding: 20px; }
  #pl-root .pl-sheet-panel { width: min(560px, 100%); max-height: min(86dvh, 760px); border-bottom: 1px solid var(--smv2-line-2, rgba(255, 255, 255, .14)); border-radius: var(--smv2-r-xl, 22px); transform: translateY(8px); }
  #pl-root .pl-sheet-foot { border-radius: 0 0 var(--smv2-r-xl, 22px) var(--smv2-r-xl, 22px); }
}
@media (prefers-reduced-motion: reduce) {
  #pl-root .pl-sheet-panel, #pl-root .pl-sheet-row { transition: none; }
}

/* LABEL CONTRAST (owner 2026-09-04: "the contrast of important text such as
   Amount or Labels doesn't make it obvious enough"). The uppercase micro
   labels — the row's AMOUNT / LABELS on phones, the desktop column headers,
   the option-group labels (LABEL MATERIAL…), the contact and batch field
   labels — all sat on --pl-tx-4 (.5 white): 4.8:1 on the raised row, which
   passes AA on paper and reads faint in daylight at 11px. Lifted to .72
   white (≈9:1) and the two that name a field a buyer must fill go to 12px.
   Appended last on purpose: same specificity as the originals, later wins. */
#pl-root .pl-microlab, #pl-root .pl-optlabel, #pl-root .pl-field-lab, #pl-root .pl-batchlab, #pl-root .pl-rowshead span { color: rgba(255, 255, 255, .72); }
#pl-root .pl-microlab, #pl-root .pl-optlabel { font-size: 12px; }
#pl-root .pl-microlab i { opacity: .8; }
