/* =========================================================================
   shop.css — catalogue cards with a living 3D stage on each.
   Loaded only by shop.html. No raw colour literals: theme tokens only.

   COLLISION RESOLVED: the donor redeclared `.pill` here with a different
   shape and font to the site-wide `.pill`, and both sheets load on this page,
   so which one won depended on link order. The shop variant is `.shop-pill`.
   ========================================================================= */

/* ---------- shelf tabs ----------
   Owner ruling 2026-08-09: *"i dont want this at the bottom. make well
   designed sub tabs."* The bodies shelf sat under the whole dinosaur
   catalogue, which on a 22-card shop is a scroll nobody makes.

   This is the SITE'S OWN `.tabs` component (site.css) with three additions and
   no new visual idea: room above the shelf, a count badge, and a phone rule.
   A second tab look on one site is how a site stops looking like one site.

   ★ NOT STICKY, on purpose. The living previews are drawn into ONE canvas
     pinned over the viewport (shopview.mjs) which sits above the page and
     below the nav - a tab bar that scrolled with the reader would slide under
     that canvas and have dinosaurs drawn across it. */
/* HUGS ITS TABS. `.tabs` is a block flex row, which on a 1280px page draws a
   full-width tray with two buttons huddled in the left end of it - it reads as
   an unfinished toolbar rather than as a set of shelves. Shrink-to-fit turns
   the same component into a segmented control. `max-width` keeps the
   component's own sideways scroll for a phone. */
.shop-tabs {
  display: inline-flex;
  max-width: 100%;
  margin-bottom: 26px;
}

/* The number of things on the shelf, from the same array the grid is built
   from. It rides INSIDE the button so it moves with the label, and it is the
   quieter of the two inks so the name still reads first.
   ★ A REAL ELEMENT, NOT A ::after - the shared component already spends its
     ::after on the rule under the selected tab. */
.shop-tab-count {
  margin-left: 8px;
  padding: 2px 6px;
  border-radius: var(--r-pill);
  background: var(--surface-3);
  color: var(--fg-mute);
  font: 600 10.5px/1 var(--font-mono);
  font-variant-numeric: tabular-nums;
  vertical-align: 1px;
}
/* The selected tab's own fill IS --surface-3, so the badge inverts there or it
   disappears into its own button. */
.shop-tab[aria-selected="true"] .shop-tab-count {
  background: var(--surface); color: var(--fg-dim);
}

@media (max-width: 560px) {
  /* One row that scrolls sideways beats a wrapped bar whose second line looks
     like a different control. `.tabs` already scrolls; this only stops the
     badge from squeezing the labels. */
  .shop-tabs { margin-bottom: 20px; }
  .shop-tab { padding-left: 12px; padding-right: 12px; }
  .shop-tab-count { margin-left: 6px; }
}

/* A shelf is a panel now, so its head is the first thing in it and never needs
   the gap that used to separate two shelves stacked on one page. */
.shop-shelf > .shop-sec-head { margin-top: 0; }
.shop-sec-head { margin-top: 42px; }
.shop-sec-head:first-child { margin-top: 0; }

.items {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 14px;
  margin-top: 18px;
}

.item {
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  overflow: hidden;
  transition: border-color .16s, transform .16s;
}
.item:hover { border-color: var(--border-2); transform: translateY(-1px); }

.item-body { display: flex; flex-direction: column; gap: 8px; padding: 13px 14px 14px; }

.item-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.item-top h3 { font-size: 16.5px; }

.shop-pill {
  font: 600 10.5px/1 var(--font-mono);
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--fg-dim);
  padding: 4px 8px; border-radius: var(--r-pill);
  border: 1px solid var(--border-2);
  white-space: nowrap;
}

.item-tag {
  font: 600 10.5px/1 var(--font-mono);
  letter-spacing: .14em; text-transform: uppercase;
  padding: 4px 8px; border-radius: 4px;
  border: 1px solid var(--border-2); color: var(--fg-dim);
}
.item-tag.rare { color: var(--warn); border-color: var(--warn-dim); }

.item-meta { font-size: 13px; color: var(--fg-dim); margin: 0; }

.item-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: auto; padding-top: 4px;
  flex-wrap: wrap;
}
.item-price { font: 600 15.5px/1 var(--font-ui); color: var(--fg); white-space: nowrap; }
.item-price.price-ok  { color: var(--ok); }
.item-price.price-bad { color: var(--bad); }
/* THE PRIME LINE. Rendered only for a species that carries a prime price, and
   it is a SECOND price cell on the same card - hence its own row, its own tag
   and its own pencil, so a glance never confuses the two numbers. Styled the
   moment it shipped: an unstyled block is a white box on a dark page. */
.item-prime {
  display: flex; align-items: center; gap: 8px;
  margin-top: 6px; padding-top: 6px;
  border-top: 1px dashed var(--border);
}
.item-prime-tag {
  font: 600 10.5px/1 var(--font-ui); letter-spacing: .08em; text-transform: uppercase;
  color: var(--accent-hi); border: 1px solid var(--edge);
  border-radius: var(--r-xs); padding: 3px 6px; white-space: nowrap;
}
.item-prime .item-price { margin-left: auto; }

/* ---------- the body shelf ----------
   A corpse has no specimen to walk, so its card carries a plain size plate
   instead of a 3D stage. Same card shell, same price row, same pencil - only
   the picture is different, because inventing a preview for a thing the island
   spawns would be a picture nobody can check. */
/* .items already owns the grid; a body card is narrower than a specimen card
   because it has no stage to fill, so only the track size is restated. */
.items-corpse { grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
.item-corpse { display: flex; flex-direction: column; }
.corpse-size {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 16 / 9;
  border-bottom: 1px solid var(--border);
  background: var(--surface-3);
}
.corpse-size span {
  font: 700 34px/1 var(--font-ui);
  letter-spacing: -.02em;
  color: var(--accent-hi);
}
.item-corpse .item-foot { justify-content: flex-end; }

.item-buy { font-size: 12px; color: var(--fg-mute); text-align: right; }
.item-buy code {
  font-family: var(--font-mono); font-size: 11.5px; color: var(--accent-hi);
  background: var(--surface-3); padding: 2px 5px; border-radius: var(--r-xs);
}

/* ---------- the buy action ----------
   One row per lane, directly under the price that lane charges, so the number
   and the button that takes it are never separated by anything. The reason
   line under a blocked button is the whole point of the block: a control that
   refuses without saying why reads as broken. */
.item-actions {
  display: flex; flex-direction: column; align-items: stretch; gap: 6px;
  margin-top: 10px;
}
.item-actions .buy-go { width: 100%; padding: 10px 16px; font-size: 13.5px; }
.buy-why {
  font: 400 12px/1.45 var(--font-ui);
  color: var(--fg-mute);
}
/* The prime lane's action sits tight under the prime price row it belongs to,
   so a card with two lanes reads as two offers rather than four loose parts. */
.item-prime + .item-actions { margin-top: 8px; }

/* ---------- the confirm dialog ----------
   NO ENTRANCE ANIMATION, deliberately: a modal that animates in has to be
   given a reduced-motion twin that renders the finished picture, and the
   finished picture is all this dialog ever needs to show. */
.buy-dialog {
  border: none; background: transparent; padding: 0;
  max-width: min(440px, calc(100vw - 32px));
  color: var(--fg);
}
.buy-dialog::backdrop { background: var(--scrim-modal); }
.buy-panel {
  background: var(--surface); border: 1px solid var(--border-2);
  border-radius: var(--r); box-shadow: var(--shadow-panel);
  padding: 22px 22px 20px;
  display: flex; flex-direction: column; gap: 12px;
}
.buy-panel h3 { font-size: 18px; margin: 0; }
.buy-what {
  margin: -6px 0 0; font: 600 15px/1.3 var(--font-ui); color: var(--accent-hi);
}

.buy-picks { display: flex; flex-direction: column; gap: 8px; }
.buy-picks-legend {
  margin: 0; font-size: 12.5px; color: var(--fg-dim);
}
.buy-pick { display: flex; align-items: center; gap: 10px; }
.buy-pick > span {
  flex: none; width: 62px;
  font: 500 11px/1 var(--font-mono); letter-spacing: .1em;
  text-transform: uppercase; color: var(--fg-mute);
}
.buy-pick select { flex: 1 1 auto; padding: 8px 10px; font-size: 13.5px; }
.buy-pick select.buy-pick-clash { border-color: var(--warn); }

.buy-lines {
  display: grid; grid-template-columns: 1fr auto; gap: 6px 14px;
  margin: 2px 0 0; padding: 12px 0 0;
  border-top: 1px solid var(--border);
}
.buy-lines dt { font-size: 13px; color: var(--fg-dim); }
.buy-lines dd {
  margin: 0; text-align: right;
  font: 600 13.5px/1.3 var(--font-ui); color: var(--fg);
}

.buy-note {
  margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--fg-dim);
}
.buy-note:empty { display: none; }
.buy-error {
  margin: 0; padding: 9px 11px; border-radius: var(--r-xs);
  font-size: 13px; line-height: 1.5;
  color: var(--bad); background: var(--surface-2);
  border: 1px solid var(--bad-dim);
}

.buy-actions { display: flex; gap: 10px; justify-content: flex-end; }
.buy-actions .btn { padding: 10px 18px; font-size: 13.5px; }
/* The one busy signal, and it is not a spinner: a spinner has to be stopped on
   every path, and one left running is how a finished purchase looks stuck. */
.buy-dialog[data-busy] .buy-panel { cursor: progress; }
.buy-dialog[data-busy] .buy-picks { opacity: .55; }

@media (max-width: 460px) {
  .buy-pick { flex-direction: column; align-items: stretch; gap: 4px; }
  .buy-pick > span { width: auto; }
}

/* ---------- the living stage ---------- */
.shopview {
  position: relative; height: 218px; flex: none;
  background: var(--stage-floor);
  border-bottom: 1px solid var(--border);
}
/* The specimens are drawn by ONE canvas pinned over the whole page, not by
   a canvas per card — a browser only grants a page about sixteen WebGL
   contexts, so a renderer per card caps how many cards can ever be alive.
   shopview.mjs also sets these inline, deliberately: the element is created
   by script and must land correctly even against a stale cached stylesheet. */
.shopstage-canvas {
  position: fixed; inset: 0; width: 100%; height: 100%;
  z-index: 5;              /* above the cards, below the sticky nav and livebar */
  pointer-events: none;    /* never eats a click on the card underneath */
}

/* idle = waiting for the observer; a calm poster, never a blank */
.shopview[data-view="idle"]::after,
.shopview[data-view="missing"]::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 46px; height: 46px; transform: translate(-50%, -50%);
  border-radius: var(--r-round);
  border: 1px solid var(--border-2);
  background: var(--grad-poster);
}
.shopview[data-view="missing"]::before {
  content: attr(data-missing-label);
  position: absolute; left: 0; right: 0; top: calc(50% + 34px);
  text-align: center;
  font: 500 11px/1 var(--font-mono); letter-spacing: .14em; text-transform: uppercase;
  color: var(--fg-mute);
}

.shopview[data-view="gated"] { display: grid; place-items: center; }
.shopview-gate {
  display: inline-block; max-width: 26ch; text-align: center;
  font: 600 12.5px/1.5 var(--font-ui); color: var(--fg-dim);
  padding: 10px 14px; border: 1px solid var(--border-2); border-radius: var(--r-s);
  background: var(--scrim-panel);
}
.shopview-gate:hover { color: var(--fg); border-color: var(--edge); }

/* ---------- admin price editing ---------- */
.price-edit {
  display: inline-grid; place-items: center;
  width: 26px; height: 26px; margin-left: -4px;
  background: transparent; border: 1px solid var(--border);
  border-radius: 6px; color: var(--fg-mute); cursor: pointer;
}
.price-edit:hover { color: var(--fg); border-color: var(--edge); }
.price-input {
  width: 108px; padding: 5px 8px;
  background: var(--surface-3); color: var(--fg);
  border: 1px solid var(--edge); border-radius: 6px;
  font: 600 14px/1 var(--font-ui);
}
.admin-note { margin-top: 26px; font-size: 13px; }

/* the price sits with its pencil */
.item-foot .item-price + .price-edit { flex: none; }

@media (max-width: 640px) {
  .items { grid-template-columns: 1fr 1fr; }
  .shopview { height: 172px; }
}
@media (max-width: 460px) {
  .items { grid-template-columns: 1fr; }
}
