/* Lucky Popups — storefront stylesheet
   Class names match chrome.js + template builders exactly.
   Colors/opacity set inline by chrome.js are NOT redeclared here.
   z-index: 2147483000 keeps the overlay above virtually everything. */

/* ── Token-Ebene 1: neutraler Fallback = heutiges Aussehen ───────── */
.lp-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2147483000;

  --lp-font: inherit;
  --lp-text: #111;
  --lp-body-text: #111;
  --lp-muted: rgba(0, 0, 0, 0.6);
  --lp-surface: #fff;
  --lp-border: rgba(0, 0, 0, 0.2);
  --lp-accent-soft: rgba(0, 0, 0, 0.06);

  --lp-radius: 8px;
  --lp-shadow: 0 8px 40px rgba(0, 0, 0, 0.25);
  --lp-shadow-slidein: 0 8px 40px rgba(0, 0, 0, 0.25);
  --lp-overlay-opacity: 0.5;

  --lp-btn-bg: #111111;
  --lp-btn-bg-hover: #111111;
  --lp-btn-text: #ffffff;
  --lp-btn-border-w: 0;
  --lp-btn-size: 1rem;
  --lp-btn-pad: 0.65rem 1.25rem;
  --lp-btn-weight: 400;
  --lp-btn-focus-shadow: none;
  --lp-btn-sm-size: 0.875rem;
  --lp-btn-sm-pad: 0.3rem 0.75rem;
  --lp-btn2-bg: #111111;
  --lp-btn2-border: #111111;
  --lp-btn2-text: #ffffff;
  --lp-btn2-border-w: 0;

  --lp-input-border-w: 1px;
  --lp-input-size: 1rem;
  --lp-input-pad: 0.6rem 0.875rem;

  --lp-heading-size: 1.5rem;
  --lp-heading-weight: 700;
  --lp-heading-tracking: normal;
  --lp-heading-leading: 1.25;
  /* --lp-heading-size is a faithful replica of the THEME's heading recipe, and
     a theme sizes headings for a full page. Inside a 560px dialog that scale
     eats the room the content needs — on the game templates it pushed the
     board down to a strip. The step-down lives here, on the consuming side,
     rather than in the preset: the preset must stay a replica (tokens.test.js
     asserts it value for value), and how large a heading should be inside a
     popup is a popup decision, not the theme's. */
  --lp-heading-step: 0.8;

  background: rgba(0, 0, 0, var(--lp-overlay-opacity));
}

/* bar / fullscreen variants: no centering flex */
.lp-overlay:has(.lp-bar),
.lp-overlay:has(.lp-fullscreen) {
  align-items: flex-start;
  justify-content: flex-start;
}

/* Bar-Layout hat keinen Overlay-Schleier (wie der echte Promo-Banner) */
.lp-overlay:has(.lp-bar) {
  background: transparent;
}

/* ── Token-Ebene 2: Preset „lucky-hemp" ─────────────────────────── */
.lp-overlay[data-lp-preset="lucky-hemp"] {
  --lp-font: inherit;              /* Montserrat wird von <html> geerbt */
  --lp-text: #1a202c;
  --lp-body-text: #515759;
  --lp-muted: #969696;
  --lp-surface: #fff;
  --lp-border: #e2e8f0;
  --lp-accent-soft: #e1f7de;

  --lp-radius: .25rem;
  --lp-shadow: 0 25px 50px -12px #00000040;
  --lp-shadow-slidein: 0 10px 15px -3px #0000001a, 0 4px 6px -2px #0000000d;

  --lp-btn-bg: #5da055;
  --lp-btn-bg-hover: #6bb762;
  --lp-btn-text: #f7f7f7;
  --lp-btn-border-w: 2px;
  --lp-btn-size: .75rem;
  --lp-btn-pad: .75rem;
  --lp-btn-weight: 500;
  --lp-btn-focus-shadow: 0 1px 2px #0000000d;
  --lp-btn-sm-size: .75rem;
  --lp-btn-sm-pad: .5rem;
  --lp-btn2-bg: #fff;
  --lp-btn2-border: #969696;
  --lp-btn2-text: #969696;
  --lp-btn2-border-w: 2px;

  --lp-input-border-w: 1px;
  --lp-input-size: 1rem;
  --lp-input-pad: .5rem .75rem;

  --lp-heading-size: 1.5rem;
  --lp-heading-weight: 900;        /* Theme nutzt font-black; 700-Datei wird
                                      synthetisiert — exakt wie auf der Seite */
  --lp-heading-tracking: -.025em;
  --lp-heading-leading: 1.25;
}

/* ── Token-Ebene 2b: der Breakpoint, den nur CSS kann ───────────── */
@media (min-width: 1280px) {
  .lp-overlay[data-lp-preset="lucky-hemp"] {
    --lp-btn-size: .875rem;
    --lp-btn-pad: .75rem 1.25rem;
    --lp-btn-sm-pad: .5rem .75rem;
    --lp-heading-size: 1.875rem;
  }
}

/* Preset „neutral" hat bewusst keinen Block — es greift Ebene 1. */

/* ── Dialog base ─────────────────────────────────────────────── */
.lp-dialog {
  position: relative;
  box-sizing: border-box;
  padding: 1.5rem;
  max-width: 560px;
  width: 100%;
  box-shadow: var(--lp-shadow);
  outline: none;
  background: var(--lp-surface);
  color: var(--lp-text);
  border-radius: var(--lp-radius);
  font-family: var(--lp-font);
}

@media (min-width: 768px) {
  .lp-dialog { padding: 2rem; }
}

/* ── Layout variants ─────────────────────────────────────────── */

/* center (default) — already centered by overlay flex */
.lp-dialog.lp-center {
  max-width: 560px;
}
/* Set by the renderer for templates whose content is laid out horizontally.
   At 560px a landscape product card leaves ~130px for the title, which breaks
   most product names over three lines; 720px gets them back to two.
   Only the centered layouts widen. `modal` is the default layout and has no
   rule of its own — it lives on the base `.lp-dialog` — so it has to be named
   here explicitly; slidein, bar and fullscreen keep their own widths. */
.lp-dialog.lp-modal.lp-wide,
.lp-dialog.lp-center.lp-wide {
  max-width: 720px;
}

/* slidein — bottom-right corner */
.lp-dialog.lp-slidein {
  position: fixed;
  bottom: 1.5rem;
  right: 1.5rem;
  max-width: 380px;
  width: 100%;
  box-shadow: var(--lp-shadow-slidein);
  animation: lp-slidein-in 0.3s ease-out;
}

/* bar — full-width top strip */
.lp-dialog.lp-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  width: 100%;
  max-width: none;
  padding: 0.25rem 1.5rem;   /* Theme: py-1, container px-6 */
  box-shadow: none;          /* der echte Promo-Banner hat keinen */
  border-radius: 0;
}

/* fullscreen — fills viewport */
.lp-dialog.lp-fullscreen {
  position: fixed;
  inset: 0;
  max-width: none;
  width: 100%;
  height: 100%;
  border-radius: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

/* ── Close button ────────────────────────────────────────────── */
.lp-close {
  position: absolute;
  top: 0;
  right: 0;
  background: transparent;
  border: none;
  cursor: pointer;
  /* Das × ist U+00D7 und wirkt in Montserrat 400 dünn. 600 ist im Theme als
     echte Schriftdatei geladen (400/500/600/700), rendert also mit kräftigeren
     Strichen statt als synthetisches Faux-Bold. */
  font-size: 1.75rem;
  font-weight: 600;
  line-height: 1;
  padding: 0.25rem 0.75rem;
  color: inherit;
  opacity: 0.6;
  transition: opacity 0.15s;
}
.lp-close:hover {
  opacity: 1;
}

/* ── Template shared ─────────────────────────────────────────── */
.lp-template {
  width: 100%;
}

.lp-template h1,
.lp-template h2,
.lp-template h3 {
  margin: 0 0 0.75rem;
  font-size: calc(var(--lp-heading-size) * var(--lp-heading-step));
  font-weight: var(--lp-heading-weight);
  letter-spacing: var(--lp-heading-tracking);
  line-height: var(--lp-heading-leading);
}

.lp-template p {
  margin: 0 0 1rem;
  line-height: 1.5;
  color: var(--lp-body-text);
}

/* ── Images ──────────────────────────────────────────────────── */
.lp-template img {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: var(--lp-radius);
}

/* ── Forms ───────────────────────────────────────────────────── */
.lp-template form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-top: 0.5rem;
}

.lp-template input[type="email"],
.lp-template input[type="text"] {
  box-sizing: border-box;
  width: 100%;
  -webkit-appearance: none;
  appearance: none;
  padding: var(--lp-input-pad);
  border: var(--lp-input-border-w) solid var(--lp-border);
  border-radius: var(--lp-radius);
  font-size: var(--lp-input-size);
  line-height: 1.5;
  font-family: inherit;
  background: #fff;
  color: var(--lp-text);
}

/* Ergänzung: das Theme definiert keinen Input-Focus, Barrierefreiheit
   braucht einen sichtbaren. Nutzt ausschließlich Brand-Tokens. */
.lp-template input[type="email"]:focus,
.lp-template input[type="text"]:focus {
  outline: none;
  border-color: var(--lp-btn-bg);
  box-shadow: 0 0 0 3px var(--lp-accent-soft);
}

/* ── Buttons ─────────────────────────────────────────────────── */
.lp-btn {
  display: inline-block;
  -webkit-appearance: none;
  appearance: none;
  padding: var(--lp-btn-pad);
  border: var(--lp-btn-border-w) solid var(--lp-btn-bg);
  border-radius: var(--lp-radius);
  font-size: var(--lp-btn-size);
  font-weight: var(--lp-btn-weight);
  line-height: 1.5;
  font-family: inherit;
  text-align: center;
  text-decoration: none;   /* image_text nutzt <a class="lp-btn"> */
  cursor: pointer;
  background: var(--lp-btn-bg);
  color: var(--lp-btn-text);
  transition: background-color 0.15s, border-color 0.15s;
}

.lp-btn:hover {
  background: var(--lp-btn-bg-hover);
  border-color: var(--lp-btn-bg-hover);
}

.lp-btn:focus {
  box-shadow: var(--lp-btn-focus-shadow);
  outline: 2px solid transparent;
  outline-offset: 2px;
}

/* The theme's own :focus is visually near-invisible and suppresses the UA ring.
   Popups are focus-trapped modals, so keyboard users need a real indicator. */
.lp-btn:focus-visible {
  outline: 2px solid var(--lp-btn-bg);
  outline-offset: 2px;
  box-shadow: 0 0 0 3px var(--lp-accent-soft);
}

/* Sekundär: Age-Gate-Ablehnung und Code-Kopieren */
.lp-btn.lp-age-decline,
.lp-btn.lp-copy-btn {
  background: var(--lp-btn2-bg);
  border-color: var(--lp-btn2-border);
  border-width: var(--lp-btn2-border-w);
  color: var(--lp-btn2-text);
}

.lp-btn.lp-age-decline:hover,
.lp-btn.lp-copy-btn:hover {
  background: var(--lp-btn2-bg);
  border-color: var(--lp-btn2-text);
}

/* ── Discount code display ───────────────────────────────────── */
.lp-code {
  display: inline-block;
  font-family: inherit;
  font-weight: 600;
  font-size: 1.1rem;
  letter-spacing: 0.05em;
  padding: 0.5rem 0.75rem;
  background: var(--lp-accent-soft);
  border-radius: var(--lp-radius);
  margin-right: 0.5rem;
}

.lp-copy-btn {
  font-size: var(--lp-btn-sm-size);
  padding: var(--lp-btn-sm-pad);
  vertical-align: middle;
}

/* ── Success message ─────────────────────────────────────────── */
.lp-success {
  padding: 0.75rem 0;
  font-weight: 500;
}

/* ── Game shell (shared by scratch card, wheel, slot, plinko) ── */
.lp-game-area {
  text-align: center;
}
/* The board and the guard share one grid cell, so the stack grows to whichever
   is taller and both stay centred — the slot's reels are far shorter than the
   form, the wheel is far taller. Only these two share a cell: the result panel
   and the reveal link are siblings of the stack, in normal flow beneath it. */
.lp-game-stack {
  display: grid;
  place-items: center;
}
.lp-game-stack > * {
  grid-area: 1 / 1;
}
/* Near-opaque on purpose: the text has to stay readable whatever segment of the
   board happens to sit behind it. Narrower than the board so the wheel's
   segment ring stays visible around it. */
.lp-game-guard {
  /* Every board has a positioned or stacking-context descendant (the wheel's
     and plinko's position:relative stage, the slot strip's will-change), and
     those always paint after non-positioned in-flow content regardless of
     DOM order. Without its own position + z-index, the guard — a plain
     in-flow grid item — paints first and the board renders on top of the
     form. */
  position: relative;
  z-index: 1;
  width: min(100%, 300px);
  box-sizing: border-box;
  padding: 1.25rem;
  border-radius: var(--lp-radius);
  background: var(--lp-surface);
  box-shadow: var(--lp-shadow);
  transition: opacity 0.2s ease;
}
/* The form sits inside .lp-game-area's text-align: center for the board's
   sake, but every other template's form is left-aligned — match them. The
   email input itself is unaffected (the UA sheet pins text-align: start on
   text inputs), this only matters for the consent label and its wrapped text. */
.lp-game-guard .lp-form {
  text-align: start;
  /* .lp-template form's margin-top: 0.5rem plus the guard's own 1.25rem
     padding would make the card 28px on top vs 20px on the bottom. */
  margin-top: 0;
}
.lp-game-guard--leaving {
  opacity: 0;
  pointer-events: none;
}

/* ── Scratch card ────────────────────────────────────────────── */
.lp-scratch-area {
  text-align: center;
  /* .lp-prize-area's 120px min-height plus its 2 × 1.25rem padding. The wrap
     needs this because its only pre-play child (.lp-foil) is out of flow, so it
     would otherwise collapse to its border and the card would be a hairline.
     Matching the post-play height also stops the card resizing on submit. */
  --lp-scratch-card-h: 160px;
  /* .lp-game-stack's place-items: center implies justify-items: center, which
     sizes each grid item to fit-content in the inline axis instead of
     stretching it. This area's only in-flow child (.lp-canvas-wrap) has only
     an out-of-flow child (.lp-foil) of its own, so its max-content
     contribution is just its 1px borders — the same collapse-to-hairline trap
     as above, in the other axis. Stretch to the grid cell instead. */
  justify-self: stretch;
}
.lp-scratch-hint {
  font-size: 0.75rem;
  color: var(--lp-muted);
  margin: 0 0 0.5rem;
}
/* Wrap is sized by the prize content; the foil canvas sits absolutely on top of it,
   so scratching the foil reveals the prize beneath. */
.lp-canvas-wrap {
  position: relative;
  box-sizing: border-box;
  max-width: 300px;
  min-height: var(--lp-scratch-card-h, 160px);
  margin: 0.5rem auto;
  border-radius: var(--lp-radius);
  overflow: hidden;
  /* A hairline border, not box-shadow: .lp-foil is a positioned descendant that
     paints over the wrap's own background phase, so an inset shadow only shows
     through where the foil has already been scratched away. The border sits
     outside the foil's inset:0 padding-edge box, so it stays visible the whole
     time and actually frames the unscratched card. */
  border: 1px solid rgba(0, 0, 0, 0.08);
}
.lp-prize-area {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 1.25rem 1rem;
  min-height: 120px;
  text-align: center;
}
/* The `display: flex` above is an author declaration, which beats the
   user-agent `[hidden] { display: none }` rule regardless of specificity —
   so setting the `hidden` property/attribute alone does not hide this panel.
   Restate it here so `resultEl.hidden = true` in game-shell.js actually hides
   the prize/discount code until the spin/slot/plinko animation reveals it. */
.lp-prize-area[hidden] {
  display: none;
}
.lp-foil {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  cursor: crosshair;
  touch-action: none;
}
.lp-win-message {
  margin: 0;
  font-weight: 600;
}
.lp-prize-label {
  margin: 0;
  font-size: 1.5rem;
  font-weight: var(--lp-heading-weight);
  letter-spacing: var(--lp-heading-tracking);
}
.lp-lose-message {
  margin: 0;
}
.lp-code-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
/* Escape hatch — must stay reachable for keyboard users, but understated so
   scratching remains the obvious action. Not .lp-btn, so it needs its own
   focus ring rather than inheriting the button one. */
.lp-reveal-link {
  display: inline-block;
  margin-top: 0.5rem;
  padding: 0.25rem 0.25rem;
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.75rem;
  color: var(--lp-muted);
  text-decoration: underline;
  cursor: pointer;
}
/* The `display: inline-block` above is an author declaration, which beats the
   user-agent `[hidden] { display: none }` rule regardless of specificity —
   so setting the `hidden` property/attribute alone does not hide this link.
   Restate it here so `.lp-reveal-link.hidden = true` actually hides it. */
.lp-reveal-link[hidden] {
  display: none;
}
.lp-reveal-link:hover {
  color: var(--lp-text);
}
.lp-reveal-link:focus-visible {
  outline: 2px solid var(--lp-btn-bg);
  outline-offset: 2px;
  border-radius: var(--lp-radius);
}

/* ── Recommendations heading ─────────────────────────────────── */
/* Centred copy over a centred grid. The rule under the heading takes the
   merchant's button colour rather than a hard-coded green, so it follows the
   shop's style settings like every other accent in the popup. */
.lp-recs h2,
.lp-recs h3,
.lp-recs > p {
  text-align: center;
}
.lp-recs h2::after,
.lp-recs h3::after {
  content: "";
  display: block;
  width: 72px;
  height: 3px;
  margin: 0.85rem auto 0;
  border-radius: 2px;
  background: var(--lp-btn-bg);
}

/* ── Recommendations grid ────────────────────────────────────── */
/* auto-FIT, not auto-fill. auto-fill keeps the tracks it can fit even when
   there is nothing to put in them, so two products in a four-track grid both
   sit on the left with two empty columns beside them. auto-fit collapses the
   empty tracks and the cards spread across the full row.
   220px min: a card is landscape (image beside the text), and 220px is the
   width where two of them still fit side by side in the 560px dialog. */
.lp-rec-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.75rem;
  margin-top: 0.75rem;
}
.lp-rec-loading {
  grid-column: 1 / -1;
  opacity: 0.7;
  margin: 0.5rem 0;
}
/* stretch, not center: the info column has to span the full card height so
   the price can be pushed to the bottom edge. Grid already gives every card
   in a row the same height, so bottom-anchoring lines the prices and buttons
   up across cards no matter how many lines a product title takes. */
.lp-rec-card {
  display: flex;
  align-items: stretch;
  gap: 0.75rem;
  text-decoration: none;
  color: inherit;
}
.lp-rec-card:hover .lp-rec-title {
  text-decoration: underline;
}
/* Two traps here, both worth spelling out.
   1. `.lp-template img { max-width: 100% }` above is (0,1,1) and beats a bare
      `.lp-rec-img` (0,1,0), so the cap has to be raised to (0,2,0) to land.
   2. A flex item defaults to `min-width: auto`, which for an <img> means "at
      least the intrinsic width". Shopify CDN images are ~1000px wide, so the
      image inflated to the full card and pushed the text out of the dialog.
      `min-width: 0` is what actually holds it. */
/* Grey rounded tile, matching the product cards in the shop's listing.
   `contain` rather than `cover`: the tile has padding, so the point is to show
   the whole product standing on the surface instead of cropping it to a
   square. box-sizing is set here because this stylesheet sets it per element,
   not globally — without it the padding would widen the tile past its cap. */
.lp-recs .lp-rec-img {
  flex: 0 0 auto;
  width: 40%;
  min-width: 0;
  max-width: 120px;
  box-sizing: border-box;
  padding: 0.4rem;
  background: rgba(0, 0, 0, 0.055);
  aspect-ratio: 1 / 1;
  object-fit: contain;
  border-radius: var(--lp-radius);
  display: block;
  /* the card stretches its children; the tile has to stay square */
  align-self: center;
}
/* min-width: 0 so a long product title wraps instead of stretching the track
   and pushing the second card out of the row. */
.lp-rec-info {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.375rem;
  min-width: 0;
}
.lp-rec-title {
  font-size: 0.875rem;
  font-weight: 700;
  line-height: 1.25;
}
/* Pushes everything after the title to the bottom of the card, so price and
   button line up across cards however many lines a title takes. Anchored on
   the title rather than on the price: with "show price" off the price element
   isn't rendered at all, and the button would lose its anchor with it. */
.lp-recs .lp-rec-title {
  margin-bottom: auto;
}
.lp-rec-price {
  font-weight: 700;
  font-size: 0.875rem;
}
/* Colors, radius and weight stay the merchant's button tokens; only the size
   steps down. The tokens are sized for a page-width call to action, and that
   scale does not fit next to a thumbnail in a half-dialog card. */
.lp-rec-btn {
  padding: 0.5rem 0.9rem;
  font-size: 0.8125rem;
}

/* ── Age gate actions ────────────────────────────────────────── */
.lp-age-gate-actions {
  display: flex;
  gap: 0.75rem;
  margin-top: 1rem;
}

/* ── Announcement bar text ───────────────────────────────────── */
.lp-announcement-bar {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  font-size: 0.75rem;
  max-width: 1280px;
  margin: 0 auto;
}

@media (min-width: 768px) {
  .lp-announcement-bar { font-size: 1rem; }
}

/* ── Spin to win ─────────────────────────────────────────────── */
.lp-wheel-stage {
  position: relative;
  width: min(80vw, 320px);
  aspect-ratio: 1;
  margin: 0.75rem auto;
}
.lp-wheel-disc {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 3px solid var(--lp-surface);
  box-shadow: var(--lp-shadow);
  will-change: transform;
}
/* Carries the idle rotation only. The disc inside carries the spin, so the
   two transforms compose instead of overwriting each other.
   Not gated on anything: unlike the slot strip and plinko ball, nothing ever
   switches this animation off before play() runs, so it is still rotating
   when freezeIdle reads its angle. freezeIdle is the only thing that
   stops this. */
.lp-wheel-idle {
  position: absolute;
  inset: 0;
  animation: lp-wheel-idle 30s linear infinite;
  will-change: transform;
}
@keyframes lp-wheel-idle {
  to {
    transform: rotate(360deg);
  }
}
.lp-wheel-label {
  position: absolute;
  inset: 0;
  display: flex;
  justify-content: center;
  pointer-events: none;
}
.lp-wheel-label-text {
  display: block;
  max-width: 40%;
  margin-top: 6%;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.15;
  color: var(--lp-btn-text);
  text-align: center;
  overflow-wrap: anywhere;
}
/* The pointer is a triangle pinned outside the disc so it reads as a stopper. */
.lp-wheel-pointer {
  position: absolute;
  top: -6px;
  left: 50%;
  width: 0;
  height: 0;
  transform: translateX(-50%);
  border-left: 10px solid transparent;
  border-right: 10px solid transparent;
  border-top: 18px solid var(--lp-text);
}
.lp-wheel--pointer-right .lp-wheel-pointer {
  top: 50%;
  left: auto;
  right: -6px;
  transform: translateY(-50%);
  border-top: 10px solid transparent;
  border-bottom: 10px solid transparent;
  border-right: 18px solid var(--lp-text);
  border-left: 0;
}

/* ── Slot machine ────────────────────────────────────────────── */
.lp-slot-stage {
  --lp-slot-cell: 72px;
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  margin: 0.75rem auto;
}
.lp-slot-reel {
  width: var(--lp-slot-cell);
  height: var(--lp-slot-cell);
  overflow: hidden;
  border: 1px solid var(--lp-border);
  border-radius: var(--lp-radius);
  background: var(--lp-accent-soft);
}
.lp-slot-strip {
  will-change: transform;
  animation: lp-slot-idle 2.4s ease-in-out infinite;
}
@keyframes lp-slot-idle {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 3px; }
}
.lp-slot-cell {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--lp-slot-cell);
  height: var(--lp-slot-cell);
  font-size: 2rem;
  line-height: 1;
}
@media (max-width: 480px) {
  .lp-slot-stage {
    --lp-slot-cell: 56px;
  }
  .lp-slot-cell {
    font-size: 1.5rem;
  }
}

/* ── Plinko ──────────────────────────────────────────────────── */
/* The slot row's block size, declared on the template root because the guard
   card needs it too and is a SIBLING of the stage, not a descendant — a
   declaration on .lp-plinko-stage would not reach it. One source: the row
   enforces these as its own metrics, the guard offsets itself by their sum.
   Reserved generously (3rem fits three wrapped lines at 0.6875rem/1.15) — if a
   prize label ever needs a fourth, the row grows past the reserve and the card
   starts to cover it, so the reserve is the real cap on label length. */
.lp-plinko {
  --lp-plinko-slot-h: 3rem;
  --lp-plinko-slot-gap: 0.25rem;
}
.lp-plinko-stage {
  width: min(100%, 380px);
  margin: 0.75rem auto;
}
/* Pegs and ball are absolutely positioned from plinko.js on one shared
   lattice — no flex layout here, or they would sit on different grids.
   The aspect ratio is row-independent on purpose: horizontal bin spacing and
   vertical segment spacing are both 1/(rows+1), so a fixed ratio keeps the
   peg field evenly spaced at every prize count. */
.lp-plinko-board {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: var(--lp-radius);
  background: var(--lp-accent-soft);
  /* Not clipped: the resting ball sits at top: 0% with a -7px margin, so
     `overflow: hidden` sliced it in half and the drop point read as a smudge on
     the rim rather than a ball waiting to fall. Nothing else reaches an edge —
     pegs live strictly inside (pegRowY is never 0 or 1) and the slot row is a
     sibling — so letting it show costs nothing. At the end of the drop the same
     overhang puts the ball in the mouth of the bin it landed in. */
  overflow: visible;
}
.lp-plinko-peg {
  position: absolute;
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border-radius: 50%;
  /* NOT --lp-muted. That token is grey (#969696 under lucky-hemp) and the board
     is a soft accent tint (#e1f7de) — 6px of grey on pale green was invisible
     at arm's length, which left the pre-play board looking like an empty
     rectangle. The peg field IS the thing that says "plinko", so it has to
     carry real contrast. Both presets tint the board light, so a dark wash
     reads on either without needing a per-preset token. */
  background: rgba(0, 0, 0, 0.32);
}
.lp-plinko-ball {
  position: absolute;
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px;
  border-radius: 50%;
  background: var(--lp-btn-bg);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
  will-change: left, top, transform;
  animation: lp-plinko-idle 1.6s ease-in-out infinite;
}
@keyframes lp-plinko-idle {
  0%, 100% { scale: 1; }
  50%      { scale: 1.12; }
}
/* A highlight that rides along with the spin, so the rotation is visible on
   an otherwise featureless circle. */
.lp-plinko-ball::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 3px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.65);
}
/* Horizontal padding is set inline by plinko.js from BOARD_INSET so the slot
   centres line up with the bin centres the ball lands on. */
.lp-plinko-slots {
  display: flex;
  gap: 2px;
  margin-top: var(--lp-plinko-slot-gap);
}
.lp-plinko-slot {
  flex: 1 1 0;
  min-width: 0;
  /* The reserved band, so a one-line and a two-line label sit on the same
     baseline box and the row's height is predictable for the guard's offset. */
  min-height: var(--lp-plinko-slot-h);
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  padding: 0.35rem 0.1rem;
  border-radius: var(--lp-radius);
  background: var(--lp-accent-soft);
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.15;
  text-align: center;
  overflow-wrap: anywhere;
}
.lp-plinko-slot--hit {
  outline: 2px solid var(--lp-btn-bg);
  outline-offset: 1px;
}
/* The shared guard is centred in the stack, which works for the wheel (its
   segment ring shows around the card) and the slot (its reels are shorter than
   the card). A plinko board has nothing that sticks out: a centred card covered
   the whole peg field and left a green frame, so the first thing a visitor saw
   was an email form on a coloured rectangle with no hint that there was a game
   at all. Anchoring it to the BOARD's bottom edge — end of the stack, less the
   reserved slot row — clears the top of the pyramid and the resting ball, and
   keeps the prize labels (the reason to hand over an address) uncovered. */
.lp-plinko .lp-game-guard {
  align-self: end;
  margin-bottom: calc(var(--lp-plinko-slot-h) + var(--lp-plinko-slot-gap));
  width: min(100%, 300px);
  padding: 1rem;
}
/* One line, not the shared column. The card's HEIGHT is what decides how much
   of the pyramid survives: stacked (input over button) it is ~135px and buries
   every peg row but the top two, so the board still read as an empty rectangle.
   On one line it is ~81px, which clears three rows — six pegs instead of three
   — at exactly the same popup height. Widening the card to 300px to fit the row
   costs flank, and flank is worth less than rows: the peg field reads by its
   horizontal bands. */
.lp-plinko .lp-game-guard .lp-form {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}
.lp-plinko .lp-game-guard .lp-form input[type="email"] {
  /* width: 100% from the shared input rule would claim the whole row and push
     the button off it, undoing the height saving. */
  flex: 1 1 8rem;
  width: auto;
  min-width: 0;
}
.lp-plinko .lp-game-guard .lp-form .lp-btn {
  flex: 0 0 auto;
}
/* Consent text belongs under the row, but the DOM order is input, label,
   button — `order` moves it past the button without the shell needing to know
   that plinko lays its form out differently. */
.lp-plinko .lp-game-guard .lp-form label {
  order: 1;
  flex: 1 0 100%;
}

/* ── Mobile ──────────────────────────────────────────────────── */
@media (max-width: 480px) {
  /* .lp-wide must be listed explicitly: .lp-dialog.lp-center.lp-wide is more
     specific than .lp-dialog.lp-center, so its 720px would survive here and
     overflow the phone. */
  .lp-dialog.lp-center,
  .lp-dialog.lp-center.lp-wide,
  .lp-dialog.lp-slidein {
    max-width: 100%;
    width: 100%;
    bottom: 0;
    right: 0;
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
  }

  .lp-overlay:has(.lp-center),
  .lp-overlay:has(.lp-slidein) {
    align-items: flex-end;
  }

}

/* ── Animations ──────────────────────────────────────────────── */
@keyframes lp-slidein-in {
  from {
    transform: translateY(1rem);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

/* ── Reduced motion ──────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .lp-dialog,
  .lp-close,
  .lp-btn {
    transition: none;
    animation: none;
  }
  .lp-dialog.lp-slidein {
    animation: none;
  }
  .lp-wheel-disc,
  .lp-slot-strip {
    transition: none;
  }
  .lp-wheel-idle {
    animation: none;
  }
  .lp-slot-strip,
  .lp-plinko-ball {
    animation: none;
  }
  .lp-game-guard {
    transition: none;
  }
}
