/**
 * SBAI product-page frontend (S6, §9). The button wears SB Buttons pill
 * styling when that plugin is active (.sbai-button--pill); it reuses the
 * six --sb-color-button-* tokens + the shared pill radius, each with the
 * same-value fallback the button system itself uses, so this file stays
 * self-contained and load-order-independent. It is intentionally NOT a
 * `.button`, so sb-morph.js never attaches a Processing morph to a modal
 * opener (runbook §22.11). .sbai-button--fallback is the neutral
 * standalone treatment when the buttons plugin is absent. Modal is a
 * centered dialog on desktop and a bottom sheet on mobile.
 */

.sbai-root {
  margin: 0.75rem 0;
}

/* SB Buttons pill — a static, secondary-weight member of the button family:
   outline pill that inverts to solid on hover, one notch smaller than the
   62px Tier-1/Tier-2 buttons so it reads as secondary to Add to Cart. */
.sbai-button--pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  box-sizing: border-box;
  min-height: 52px;
  padding: 0 30px;
  border: 2.5px solid var(--sb-color-button-border, #000);
  border-radius: var(--sb-radius-pill, 100px);
  background: var(--sb-color-button-bg, #fff);
  color: var(--sb-color-button-fg, #000);
  font-family: inherit;
  font-size: 14px;
  font-weight: 900;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  appearance: none;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  transition:
    background 0.12s ease-out,
    color 0.12s ease-out,
    border-color 0.12s ease-out,
    transform 0.12s ease;
}

.sbai-button--pill:not([aria-disabled='true']):hover {
  background: var(--sb-color-button-bg-active, #000);
  color: var(--sb-color-button-fg-active, #fff);
  border-color: var(--sb-color-button-border-active, #fff);
}

.sbai-button--pill:not([aria-disabled='true']):active {
  transform: scale(0.965);
}

.sbai-button--fallback {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.65rem 1.4rem;
  border: 1.5px solid currentcolor;
  border-radius: 999px;
  background: transparent;
  font-weight: 600;
  cursor: pointer;
}

.sbai-button[aria-disabled='true'] {
  opacity: 0.6;
}

.sbai-button__meta {
  font-size: 0.78em;
  opacity: 0.75;
}

.sbai-inline-hint {
  display: block;
  margin-top: 0.4rem;
  font-size: 0.85em;
}

/* The `hidden` attribute is this plugin's ONLY show/hide mechanism (root,
   hint, balance, modal notices). Any author `display` rule outranks the UA
   `[hidden] { display: none }` and silently defeats it — which is exactly
   what `.sbai-inline-hint`'s `display: block` did: the variation hint stayed
   on screen after the variation resolved (S6 staging gate, 2026-07-25).
   Guard every hidden-toggled element here, not case by case. */
.sbai-root[hidden],
.sbai-root [hidden],
.sbai-modal [hidden] {
  display: none;
}

/* ── Modal ─────────────────────────────────────────────────────────── */
.sbai-modal {
  position: fixed;
  inset: 0;
  z-index: 100000;
  display: flex;
  align-items: center;
  justify-content: center;
}

.sbai-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgb(0 0 0 / 55%);
}

.sbai-modal__sheet {
  position: relative;
  width: min(440px, 92vw);
  max-height: 88vh;
  overflow-y: auto;
  background: #fff;
  color: #141414;
  border-radius: 14px;
  padding: 1.5rem 1.4rem 1.6rem;
  box-shadow: 0 18px 60px rgb(0 0 0 / 35%);
}

.sbai-modal__close {
  position: absolute;
  top: 0.5rem;
  right: 0.7rem;
  border: 0;
  background: none;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
}

body.sbai-modal-open {
  overflow: hidden;
}

@media (max-width: 600px) {
  .sbai-modal {
    align-items: flex-end;
  }

  .sbai-modal__sheet {
    width: 100vw;
    max-height: 92dvh;
    border-radius: 14px 14px 0 0;
  }
}

/* ── Modal content ─────────────────────────────────────────────────── */
.sbai-h {
  margin: 0 0 0.6rem;
  font-size: 1.25rem;
}

/*
 * Multi-paragraph message copy (paragraphs() in sbai-product.js). The block
 * theme zeroes `p` margins inside the sheet, so a message written as two
 * paragraphs read as one wrapped line. Adjacent-sibling only: a
 * single-paragraph message is untouched, and no global `p` rule is
 * introduced that could move a surface already signed off.
 */
.sbai-p + .sbai-p {
  margin-top: 0.7rem;
}

.sbai-cta {
  display: inline-block;
  padding: 0.6rem 1.3rem;
  border: 0;
  border-radius: 999px;
  background: #141414;
  color: #fff;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
}

.sbai-cta--ghost {
  background: transparent;
  color: #141414;
  border: 1.5px solid #141414;
}

.sbai-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin: 1rem 0 0;
}

.sbai-notice {
  margin-top: 0.8rem;
  font-size: 0.9em;
  color: #8a2a2a;
}

/* U2: informational notices (share fallback etc.) are neutral, not red. */
.sbai-notice--info {
  color: #141414;
  opacity: 0.8;
}

/* U4: consent button acknowledged state while the upload POST runs. */
.sbai-cta[disabled] {
  opacity: 0.6;
  cursor: default;
}

/* ── Account-created notice (S13-F2 Option C) ───────────────────────────
   Rendered client-side, only when the URL carries sbai_registered=1 —
   inserted as a sibling before each surface's container so hydration
   rewrites never remove it. Neutral informational tone; the <details>
   disclosure follows the sbdr-birthday-field precedent (no JS). */
.sbai-registered {
  margin: 0 0 1.2rem;
  padding: 0.9rem 1.1rem;
  border: 1px solid #d9d4c8;
  background: #faf9f6;
  font-size: 0.92em;
  color: #141414;
}

.sbai-registered p {
  margin: 0;
}

.sbai-registered__wrong {
  margin: 0.7rem 0 0;
}

.sbai-registered__wrong summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 0.95em;
}

.sbai-registered__wrong p {
  margin: 0.5rem 0 0;
  opacity: 0.8;
}

/* ── Reveal Landing Page (§9 — sbai_reveal_landing) ─────────────────── */
.sbai-landing {
  max-width: 560px;
  margin: 0 auto;
  padding: 1rem 0 2rem;
}

.sbai-landing__loading {
  opacity: 0.7;
}

.sbai-landing .sbai-h {
  font-size: 1.5rem;
}

/* Owner adjustment 2026-08-23, seen on staging: both of these lines sat
   tight against what precedes them - the balance under the h2, and the
   lede under the new-looks notice, which carries a top margin but no
   bottom one. Top spacing only; sizes, weights and bottom margins are
   unchanged. */
.sbai-landing__balance {
  margin: 1.4rem 0 0.8rem;
  font-size: 1.05em;
  font-weight: 600;
}

.sbai-landing__lede {
  margin-top: 1.4rem;
}

.sbai-fineprint {
  margin-top: 0.8rem;
  font-size: 0.78em;
  opacity: 0.7;
}

/* S12-O1: "You're trying on: <colour>". Deliberately NOT .sbai-fineprint —
   it sat at 0.78em/0.7 opacity, which is why it was easy to miss. Full
   opacity and the colour itself bold.
   Owner adjustment 2026-08-04, seen on staging: at 1em the line competed
   with the primary button. Reduced ~12% — it supports the button now and
   still clears .sbai-fineprint (0.78em) by a wide margin. Opacity, weight
   and spacing are unchanged; the size was the only problem. */
.sbai-tryon {
  margin-top: 0.9rem;
  font-size: 0.88em;
  opacity: 1;
}

.sbai-tryon strong {
  font-weight: 700;
}

.sbai-upload {
  display: block;
  margin: 1rem 0 0;
  cursor: pointer;
}

.sbai-guide__row {
  display: flex;
  gap: 0.8rem;
  margin: 0.6rem 0;
}

.sbai-guide__row figure {
  flex: 1;
  margin: 0;
  font-size: 0.78em;
  text-align: center;
}

.sbai-guide__row img {
  width: 100%;
  height: auto;
  border-radius: 8px;
  background: #f2f2f2;
}

.sbai-consent {
  padding-left: 1.1rem;
  font-size: 0.92em;
}

.sbai-sample {
  display: block;
  width: 60%;
  margin: 0.8rem auto;
}

.sbai-progress {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  margin: 1rem 0;
}

.sbai-progress__spinner {
  width: 26px;
  height: 26px;
  border: 3px solid #ddd;
  border-top-color: #141414;
  border-radius: 50%;
  animation: sbai-spin 0.9s linear infinite;
}

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

/* S12-O23 (batch ITEM 7): the modal result viewport holds its 3:4 box
   even while empty or while the image is padded to fit, so the
   Look-switch spinner has a stable area to centre in and the layout
   never collapses mid-switch. */
.sbai-result {
  position: relative;
  aspect-ratio: 3 / 4;
}

/* The switch spinner, centred over the current image (gallery parity -
   the base ring is .sbai-progress__spinner above; sbai-gallery.css is
   not enqueued on the product page, so the positioning lives here).
   The second selector is the FIRST-ENTRY spinner renderResult places
   directly in the empty .sbai-result box - same 26px ring, and without
   this it sits top-left of the 3:4 viewport (S12-F50 sub-item). */
.sbai-media .sbai-card__spinner,
.sbai-result > .sbai-progress__spinner {
  position: absolute;
  top: 50%;
  left: 50%;
  margin: -13px 0 0 -13px;
}

/* Visual parity with the gallery card (owner, 2026-08-10, second pass):
   the modal adopts the gallery's EXACT element model (S12-D1 — a 3:4
   viewport, object-fit contain, no background layer). This is what makes
   the per-Look treatment work: the border-radius clips the ELEMENT box,
   and only an image that exactly fills it (the Original master, 3:4)
   reaches the corners — framed Looks (Polaroid, Film Negative) letterbox
   inside it, so their own frame corners are never clipped. The previous
   height:auto + background model hugged and rounded every Look's actual
   pixels, which is why the modal flattened the Look styling. drop-shadow
   (not box-shadow) follows the painted pixels, so each framed artefact
   carries the same grounding shadow it has on the card. */
.sbai-result img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 4;
  object-fit: contain;
  border-radius: 8px;
  filter: drop-shadow(0 2px 5px rgb(0 0 0 / 18%));
}

/* R7.40 (doc 22 FX-04, owner decision 2026-09-03): a Look that declares
   `self_framed` in its look.json (deckle-edge 1.0.1 - a baked scallop
   shadow and a card-white surround meant to blend into whatever holds it)
   carries no drop-shadow; same rule as the gallery card. sbai-product.js
   stamps the generic attribute from the localised self-framed slug list
   when the result paints; no per-Look selector anywhere. */
.sbai-result img[data-sbai-self-framed] {
  filter: none;
}

/* The modal's Look row (owner UX 2026-08-10): no visible label; the
   dropdown owns the full width alone and yields to a ~2/3 : 1/3 split
   when the shared Re-develop control is present, the button centred in
   its share. The image above stays the hero; the rows beneath breathe. */
.sbai-look-row .sbai-look {
  display: block;
  flex: 2 1 0;
  min-width: 0;
  font-size: 0.9em;

  /* Same theme-margin defence as .sbai-card__look-row .sbai-card__look in
     sbai-gallery.css - this surface never received the 2026-08-10 fix, so
     it carried both that bottom margin and an inline select's baseline
     gap. Two classes to outrank the theme; padding zeroed alongside. */
  margin: 0;
  padding: 0;
}

.sbai-look select {
  display: block; /* inline would leave a baseline descender gap beneath */
  width: 100%;
  min-width: 0;
  margin: 0;
  font-size: 0.95em; /* slightly smaller so long Look names fit gracefully */
  text-overflow: ellipsis;
}

.sbai-look-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 1.1rem;
}

.sbai-look-row .sbai-redevelop {
  flex: 1 1 0;
  justify-content: center;
  min-width: 0;
  white-space: nowrap;
}

/* S13 — the /claim campaign page (R6.1 presentation). */

/*
 * O9 (S13.1): the hero is height-bounded so a portrait image can never
 * push the campaign name, reward line and Claim CTA below the fold —
 * observed twice at the S13 gate on desktop, worst on phones. 45svh
 * (small-viewport height: the browser-chrome-visible case, which is the
 * state a shared link opens in) leaves the rest of the first screen for
 * the content the page exists to show; object-fit keeps the crop clean.
 * Acceptance stays with the S13.1 gate: name + reward + CTA visible
 * together without scrolling on a mid-size phone, judged on a real
 * share. The svh unit falls back to vh where unsupported.
 */
.sbai-claim__hero {
  display: block;
  max-width: 100%;
  height: auto;
  max-height: 45vh;
  max-height: 45svh;
  width: auto;
  object-fit: contain;
  border-radius: 10px;
  margin-bottom: 1rem;
}

.sbai-claim__rewards {
  list-style: none;
  margin: 1rem 0;
  padding: 0;
  font-weight: 600;
}

.sbai-claim__rewards li {
  margin: 0.3rem 0;
}

/* S6.1-E3 (batch 3): "Take a photo" is meaningful only where a camera
   opens. Hide it on fine-pointer hover devices (desktops) — a viewport
   +pointer media query, deliberately not a JS capability probe. */
@media (hover: hover) and (pointer: fine) {
  .sbai-upload--camera {
    display: none;
  }
}
