/* Swatch picker --------------------------------------------------------- *
   A dropdown whose options carry a colour dot or a small photo, which a
   native <select> cannot draw. It is a <details>: the <summary> is the
   closed field, and one radio per option sits in the list, so the form
   posts an ordinary value and keyboard and screen reader work without
   script. swatch-picker.ts only closes it after a pick and copies the
   chosen row into the summary. The list takes the field's width and
   scrolls itself, never the page. */
.swatch-picker {
  position: relative;
}

.swatch-picker__summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  list-style: none;
  cursor: pointer;
}

.swatch-picker__summary::-webkit-details-marker { display: none; }

/* The dot never shrinks to make room for a long label — the label is what
   truncates (.swatch-picker__current, below). */
.swatch-picker .swatch__dot { flex: none; }

/* An empty dot (no colour, no photo — "no preference", or a group element
   with no photo) draws as a hollow ring rather than the plain .swatch__dot's
   grey fallback, which would otherwise read as the colour grey. Scoped to
   the picker so every other .swatch__dot (product attributes, the
   styleguide's own swatch rows) keeps its solid fallback. */
.swatch-picker .swatch__dot:not([style]) {
  background: transparent;
  border-style: dashed;
}

.swatch-picker__summary::after {
  content: "";
  flex: none;
  margin-left: auto;
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid currentcolor;
  border-bottom: 2px solid currentcolor;
  transform: rotate(45deg);
}

.swatch-picker[open] .swatch-picker__summary::after { transform: rotate(-135deg); }

/* The closed field is one line, same as a <select>'s own value; a long
   label (a group's full name) truncates rather than stretching the field
   or wrapping onto a second line. */
.swatch-picker__current {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.swatch-picker__list {
  position: absolute;
  z-index: 20;
  top: calc(100% + var(--space-1));
  left: 0;
  right: 0;
  max-height: min(60vh, 22rem);
  overflow-y: auto;
  margin: 0;
  padding: var(--space-1);
  background: var(--surface-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-2);
}

.swatch-picker__option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

/* The label wraps onto a second line rather than squeezing the dot, which
   keeps flex: none above; min-width: 0 is what lets a flex item shrink
   below its text's content width at all. */
.swatch-picker__option span:last-child {
  min-width: 0;
  overflow-wrap: break-word;
}

.swatch-picker__option:hover,
.swatch-picker__option:has(input:focus-visible) { background: var(--primary-soft); }

/* The tint alone is 1.2:1 against the menu — a hover, not a focus. The
   keyboard's option also carries the ring, inside so the list's scroller
   does not clip it. */
.swatch-picker__option:has(input:focus-visible) {
  outline: 2px solid var(--focus);
  outline-offset: -2px;
}

.swatch-picker__option:has(input:checked) { font-weight: 700; }

.swatch-picker__option input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

/* Gallery ------------------------------------------------------------- */

.gallery {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.gallery__panel {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--surface);
  border: 1px solid var(--line);
  /* A size container, so the arrows below can measure the panel's width
     in cqi. Nothing else in the panel reads a container unit. */
  container-type: inline-size;
}

/* The panel's own prev/next, on the photo's edges. A desk with a pointer
   only: a K-pop cover is tall and the thumbnail strip is often below the
   fold there, so this is the step through the photos that stays in sight.
   A phone swipes the strip, and a finger on the photo's edge would have
   hidden a corner of the cover.

   They stand where the middle of a square cover would be — half the
   panel's width down — not at half the photo's height: the photos of one
   product are not all the same height, and arrows at 50% jumped up and
   down with every step. The min() keeps them on a photo wider than it is
   tall, where half the width would be below the bottom edge. */
.gallery__arrow {
  display: none;
  position: absolute;
  top: min(50cqi, 50%);
  z-index: 1;
  width: var(--control-height);
  height: var(--control-height);
  transform: translateY(-50%);
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: color-mix(in srgb, var(--surface-raised) 88%, transparent);
  color: var(--ink);
  box-shadow: var(--shadow-1);
  opacity: 0.85;
  transition: opacity var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

.gallery__arrow--prev { left: var(--space-3); }
.gallery__arrow--next { right: var(--space-3); }

.gallery__arrow:hover {
  opacity: 1;
  border-color: var(--primary);
  color: var(--primary);
}

@media (min-width: 1024px) and (hover: hover) {
  .gallery__arrow {
    display: grid;
  }
}

.gallery__panel img {
  display: block;
  width: 100%;
  height: auto;
  /* Switching photos — a thumbnail pressed, a version picked — fades the
     old one out, then the new one in from a hair larger, so the swap reads
     as a turn of the page rather than a flicker. The script (gallery.ts)
     holds .is-switching for the fade-out and drops it once the new photo
     has loaded. */
  transition:
    opacity var(--dur-fast) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
}

.gallery__panel img.is-switching {
  opacity: 0;
  transform: scale(1.03);
}

@media (prefers-reduced-motion: reduce) {
  .gallery__panel img {
    transition: none;
  }
}

/* A product with no photo draws its placeholder here. The placeholder fills
   its box, and the panel has no height of its own to give it — a photo lends
   the panel its height, a drawing does not — so it is given the square a
   cover has, or the panel collapses to its border. */
.gallery__panel > .placeholder {
  aspect-ratio: 1 / 1;
  height: auto;
}

.gallery__thumbs {
  display: flex;
  gap: var(--space-3);
  overflow-x: auto;
  padding: var(--space-1);
  scrollbar-width: none;
}

.gallery__thumb {
  flex: 0 0 5rem;
  aspect-ratio: 1;
  padding: 0;
  overflow: hidden;
  border-radius: var(--radius-md);
  border: 2px solid transparent;
  background: var(--surface);
  opacity: 0.7;
  transition: opacity var(--dur-fast), border-color var(--dur-fast);
}

.gallery__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.gallery__thumb:hover {
  opacity: 1;
}

.gallery__thumb[aria-selected="true"] {
  opacity: 1;
  border-color: var(--primary);
  box-shadow: var(--glow-pink);
}

/* Attribute table ----------------------------------------------------- */

.attr-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}

.attr-table tr + tr {
  border-top: 1px solid var(--line);
}

.attr-table th,
.attr-table td {
  padding: var(--space-3) var(--space-2);
  text-align: left;
  vertical-align: middle;
}

.attr-table th {
  width: 40%;
  font-weight: 700;
  color: var(--ink-soft);
}

.attr-table th > svg {
  /* base.css makes every svg a block; inline it so it sits ahead of the name
     rather than on a line of its own above it. */
  display: inline-block;
  width: 1.25em;
  height: 1.25em;
  color: var(--secondary);
  vertical-align: -0.25em;
  margin-inline-end: var(--space-2);
}

/* Item list (what a product carries, one line each) ------------------
   The package contents and an album's tracks are the same kind of thing in
   a description — a list of what you get — so they share one look: the name
   in bold ink, its note (a count, a page number, a length) right after it in
   the prose colour. No rules between lines and no column for the note: it is
   read as a list, not a spec sheet. The size comes from the text around it,
   so a block placed in a description matches the paragraphs it stands in. */
.item-list {
  list-style: disc;
  padding-inline-start: 1.4em;
  margin-bottom: 1em;
}

.item-list--numbered {
  list-style: decimal;
}

.item-list__name {
  font-weight: 700;
  color: var(--ink);
}

.item-list__note {
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

/* Tracklist: an item list, numbered, with the total under it. */
.tracklist {
  margin-bottom: 1em;
  /* A search hit for a track links to /products/:slug#tracklist, and
     client.ts also scrollIntoView()s here; without this the sticky 4rem
     navbar covers the top of the list on arrival. */
  scroll-margin-top: 5rem;
}

.tracklist > :last-child {
  margin-bottom: 0;
}

.tracklist__total {
  margin-bottom: 1em;
}

/* Product facts (availability, delivery, lowest price) ---------------- */

.facts {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

.facts li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.facts .icon {
  color: var(--secondary);
}

/* Product page layout ------------------------------------------------- */

.product-layout {
  display: grid;
  gap: var(--space-8);
  grid-template-columns: 1fr;
}

@media (min-width: 1024px) {
  .product-layout {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-12);
  }
}

/* Each column is as tall as its own content, not as the row: a grid item
   stretches by default, and the gallery's ambient glow — inset from the
   gallery's box — then ran the full height of the description beside it,
   a blurred stain 700px below the thumbnails. */
.product-layout > * {
  min-width: 0;
  align-self: start;
}

.product-info {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.product-info__group {
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: calc(var(--label-tracking) * 1.5);
  text-transform: var(--label-transform);
  color: var(--secondary);
}

/* The eyebrow is the product's category, so it is usually a link back. */
a.product-info__group:hover {
  color: var(--primary);
}

/* The product page has no hero; its h1 is the hero. It takes the brand
   treatment (.brand-text) at a hero-ish size, and wraps like a hero title. */
.product-info__title {
  font-size: clamp(var(--text-3xl), 4vw, var(--text-4xl));
  line-height: var(--leading-hero);
}

/* The title's light scales with --title-glow (0..1 from the product's look).
   Each rule below equals the class it scales at 1, so a product without a
   look draws exactly what it drew. Scoped to the product title: the hero's
   titles keep their own light. */
.product-info__title.brand-text {
  filter:
    drop-shadow(0 0 calc(18px * var(--title-glow, 1)) color-mix(in srgb, var(--primary) calc(45% * var(--title-glow, 1)), transparent))
    drop-shadow(0 0 calc(42px * var(--title-glow, 1)) color-mix(in srgb, var(--secondary) calc(30% * var(--title-glow, 1)), transparent));
}

/* In the light theme the brand gradient is pink to violet, which on a
   three-line product name read as a 2015 poster; there the title is plain
   ink with the same soft glow, and the gradient stays the wordmark's and
   the hero's. The dark theme's gradient is near-white and keeps its place. */
[data-theme="light"] .product-info__title.brand-text {
  background: none;
  -webkit-background-clip: initial;
  background-clip: initial;
  color: var(--ink);
  filter: drop-shadow(0 0 calc(12px * var(--title-glow, 1)) color-mix(in srgb, var(--primary) calc(25% * var(--title-glow, 1)), transparent));
}

.product-info__title.hero__title--neon {
  text-shadow:
    0 0 4px color-mix(in srgb, var(--ink) 60%, transparent),
    0 0 calc(12px * var(--title-glow, 1)) color-mix(in srgb, var(--neon) calc(100% * var(--title-glow, 1)), transparent),
    0 0 calc(28px * var(--title-glow, 1)) color-mix(in srgb, var(--neon) calc(70% * var(--title-glow, 1)), transparent),
    0 0 calc(56px * var(--title-glow, 1)) color-mix(in srgb, var(--neon) calc(40% * var(--title-glow, 1)), transparent);
}

.product-info__title.hero__title--holo {
  filter: drop-shadow(0 0 calc(14px * var(--title-glow, 1)) color-mix(in srgb, var(--white) calc(22% * var(--title-glow, 1)), transparent));
}

[data-theme="light"] .product-info__title.hero__title--holo {
  filter: none;
}

/* Page accent (spec docs-archive:docs/superpowers/specs/2026-09-14-product-look-design.md): the product asked for
   one colour instead of the shop's pink and blue. The class overrides the
   tokens the block's controls read and nothing else — the CTA's fill and
   glow, the selected chip and thumb, the eyebrow, the focus ring and the
   brand title's glow all follow on their own. Fill and ink are separate
   tokens because the light theme cannot write in a pastel (tokens.css).
   --primary-soft, --glow-pink, --glow-blue and --focus are re-derived here
   too: a custom property inherits the value --primary/--secondary already
   had substituted into it at :root, not a live reference, so leaving them
   out would keep the shop's own pink/blue soft fill, glow and focus ring
   under an accent. The percentages mirror tokens.css's dark-theme formula
   (--primary-soft 14%, --glow-pink 45%, --glow-blue 30%). */
.product-look--accent-azure {
  --primary: var(--accent-azure-ink);
  --secondary: var(--accent-azure-ink);
  --primary-fill: var(--accent-azure);
  --primary-fill-deep: var(--accent-azure-deep);
  --primary-soft: color-mix(in srgb, var(--accent-azure-ink) 14%, transparent);
  --glow-pink: 0 0 18px color-mix(in srgb, var(--accent-azure-ink) 45%, transparent);
  --glow-blue: 0 0 42px color-mix(in srgb, var(--accent-azure-ink) 30%, transparent);
  --focus: var(--accent-azure-ink);
}

.product-look--accent-violet {
  --primary: var(--accent-violet-ink);
  --secondary: var(--accent-violet-ink);
  --primary-fill: var(--accent-violet);
  --primary-fill-deep: var(--accent-violet-deep);
  --primary-soft: color-mix(in srgb, var(--accent-violet-ink) 14%, transparent);
  --glow-pink: 0 0 18px color-mix(in srgb, var(--accent-violet-ink) 45%, transparent);
  --glow-blue: 0 0 42px color-mix(in srgb, var(--accent-violet-ink) 30%, transparent);
  --focus: var(--accent-violet-ink);
}

.product-look--accent-mint {
  --primary: var(--accent-mint-ink);
  --secondary: var(--accent-mint-ink);
  --primary-fill: var(--accent-mint);
  --primary-fill-deep: var(--accent-mint-deep);
  --primary-soft: color-mix(in srgb, var(--accent-mint-ink) 14%, transparent);
  --glow-pink: 0 0 18px color-mix(in srgb, var(--accent-mint-ink) 45%, transparent);
  --glow-blue: 0 0 42px color-mix(in srgb, var(--accent-mint-ink) 30%, transparent);
  --focus: var(--accent-mint-ink);
}

.product-look--accent-gold {
  --primary: var(--accent-gold-ink);
  --secondary: var(--accent-gold-ink);
  --primary-fill: var(--accent-gold);
  --primary-fill-deep: var(--accent-gold-deep);
  --primary-soft: color-mix(in srgb, var(--accent-gold-ink) 14%, transparent);
  --glow-pink: 0 0 18px color-mix(in srgb, var(--accent-gold-ink) 45%, transparent);
  --glow-blue: 0 0 42px color-mix(in srgb, var(--accent-gold-ink) 30%, transparent);
  --focus: var(--accent-gold-ink);
}

/* Product stage: the scene behind the top block, when the product asked
   for one (README rule 1). The wrapper is the positioned, overflow-hidden
   parent .spotlights, .ocean and .atmosphere need, drawn as a lit card
   inside the container rather than a full-bleed band. The padding is for
   the gallery's glow, which paints outside its box (inset -8% -6%) and
   would be sawn off by overflow: hidden exactly as the carousel track once
   was; the negative inline margin hands that room back so the columns keep
   their place. --gradient-hero is dark in the dark theme and near-white in
   the light one, so the text stays at var(--ink) in both. The bleed must
   never exceed the container's own inline padding (base.css .container:
   --space-4 below 1024px, --space-8 from 1024px up), or the stage pushes
   the page wider than the viewport. */
.product-stage {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--gradient-hero);
  padding: var(--space-8) var(--space-6);
  margin-inline: calc(-1 * var(--space-4));
}

@media (min-width: 1024px) {
  .product-stage {
    margin-inline: calc(-1 * var(--space-6));
  }
}

.product-stage > .product-layout {
  position: relative;
  z-index: 1;
}

/* The three scenes carry themselves (base.css .atmosphere, components.css
   .spotlights / .ocean); the modifiers exist so the vocabulary test can pin
   a name to a rule, and so a scene-specific tweak has a home. */
.product-stage--spotlights,
.product-stage--atmosphere {
  color: var(--ink);
}

.product-info__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
}

/* The wishlist heart beside the call to action: an outline until the
   customer's list holds the product, then filled. */
.wish-heart {
  display: inline-grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}
.wish-heart:hover { border-color: var(--primary); color: var(--primary); }
.wish-heart[aria-pressed="true"] { color: var(--primary); }
.wish-heart[aria-pressed="true"] .icon { fill: currentcolor; }

/* Ambient gallery: a blurred, saturated copy of the cover behind the panel,
   so the album's own colours light the page the way the stage lights the
   home page. The copy is whatever the panel shows — a photo or a placeholder
   — so a product with no photo still gets its glow. No motion: it is a
   still light, and prefers-reduced-motion has nothing to switch off. */
.gallery--ambient {
  position: relative;
  isolation: isolate;
}

.gallery--ambient .gallery__panel {
  position: relative;
  border-color: var(--line-strong);
}

.gallery__glow {
  position: absolute;
  inset: -8% -6% 14%;
  z-index: -1;
  overflow: hidden;
  border-radius: 50%;
  filter: blur(64px) saturate(1.7);
  /* --gallery-glow is 0..1 from the product's look; at 1 these are the
     numbers the page always had. Blur and saturation do not scale — a
     dimmer light, not a sharper one. */
  opacity: calc(0.8 * var(--gallery-glow, 1));
  pointer-events: none;
}

.gallery__glow > * {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Below the two-column layout the gallery spans the page, and the glow's
   sideways spill runs past the screen's edge; it keeps its spill above and
   below only. */
@media (max-width: 1023px) {
  .gallery__glow {
    inset-inline: 0;
  }
}

[data-theme="light"] .gallery__glow {
  opacity: calc(0.55 * var(--gallery-glow, 1));
}

/* A coloured glow: the product asked for a colour instead of its cover, so
   the glow node holds a flat fill and the blur above turns it into the same
   soft pool. The palette is the carousel's six, on the same tokens. */
.gallery__glow-fill {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--gallery-glow-color, var(--primary));
}

.gallery--glow-primary   { --gallery-glow-color: var(--primary); }
.gallery--glow-secondary { --gallery-glow-color: var(--secondary); }
.gallery--glow-violet    { --gallery-glow-color: var(--accent-violet); }
.gallery--glow-mint      { --gallery-glow-color: var(--accent-mint); }
.gallery--glow-gold      { --gallery-glow-color: var(--warning); }
.gallery--glow-white     { --gallery-glow-color: var(--white); }

/* The gallery's half of the page transition in base.css: the photo a tile
   was clicked on travels into this panel. It lives here rather than in
   base.css so the core never styles a class that loads this pack. */
@media (min-width: 1024px) and (prefers-reduced-motion: no-preference) {
  .gallery__panel {
    view-transition-name: product-photo;
  }
}

/* =====================================================================
   Version picker dialog — asked when "Dodaj do koszyka" is pressed with no
   version picked. A native <dialog> wearing the shared .dialog shell, as a
   sheet on a phone (components.css); the pills inside are the row's own,
   as buttons.
   ===================================================================== */
.pick-dialog {
  width: min(720px, 94vw);
}

.pick-dialog__hint {
  margin: 0;
  padding: var(--space-4) var(--space-5) 0;
  color: var(--ink-muted);
  font-size: var(--text-sm);
}

.pick-dialog .chips {
  padding: var(--space-4) var(--space-5) var(--space-6);
}

/* The dialog's pills are buttons; they must not look like buttons. */
.pick-dialog .chip {
  font-family: inherit;
}


/* The buy bar (buyBar in the product view, shown by buy-bar.ts): the price
   and the call to action pinned to the bottom of a phone's screen while the
   page's own button is scrolled off the top. Slides in from below; aria-
   hidden and visibility keep it out of the tab order while it is away. A
   desk never gets it — there the page is two columns and the button is a
   glance away. */
.buy-bar {
  position: fixed;
  inset: auto 0 0 0;
  z-index: var(--z-navbar);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4) calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
  background: color-mix(in srgb, var(--stage) 92%, transparent);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
  box-shadow: var(--shadow-2);
  transform: translateY(100%);
  visibility: hidden;
  transition: transform var(--dur-base) var(--ease-out), visibility 0s var(--dur-base);
}

.buy-bar.is-shown {
  transform: none;
  visibility: visible;
  transition: transform var(--dur-base) var(--ease-out), visibility 0s;
}

.buy-bar__price {
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  font-weight: 700;
  font-variant-numeric: var(--figures);
  white-space: nowrap;
}

.buy-bar .btn {
  flex: 1;
  max-width: 16rem;
}

/* The bar covers the bottom of the page while it shows; the page makes
   room, so the footer's last line is never under it. */
body:has(.buy-bar.is-shown) {
  padding-bottom: 5rem;
}

@media (min-width: 1024px) {
  .buy-bar {
    display: none;
  }
}
