/* ============================================================
   SHOP screen, the ethical boutique.
   All colors derive from tokens.css via local --scene-* aliases.
   Voxel pass: the Personal Nook and Shared Lounge previews are now real
   voxel scenes (app/js/voxel/scenes/shop-nook.js, shop-lounge.js) mounted
   by shop.js via mountVoxel, this file only frames them and keeps the
   premium card's small decorative window (still plain CSS, out of the
   voxel scope of this pass).
   ui-fluide pass (layer 1, D-022 pilot): each preview frame carries a token
   placeholder (--shop-nook-placeholder / --shop-lounge-placeholder) and
   hides its .scene-canvas until shop.js adds .is-baked, masking the
   synchronous bake (D-012) behind a deferred, serialized montage.
   ============================================================ */

.shop-screen {
  display: flex;
  flex-direction: column;
  gap: 16px;

  /* ---- local scene palette, used by the premium-card window only ---- */
  --scene-sun: var(--gold);
  --scene-mtn: color-mix(in srgb, var(--accent-soft) 55%, var(--ink) 25%);

  /* ---- masked-montage placeholders (ui-fluide pass, layer 1, D-022):
     approximate the mood of each voxel scene while it bakes, in tokens only,
     never the scene's real hex codes (app/js/voxel/scenes/shop-nook.js,
     shop-lounge.js). Nook: a daylight window over warm wood. Lounge: warmer
     and more amber, lit by its hearth. The gap to the real bake is assumed
     (see the constructor's report); true fidelity needs sky stops exposed
     by the engine, a shared engine concern, not something to improvise here. */
  --shop-nook-placeholder: linear-gradient(165deg, var(--blue-soft) 0%, var(--bg-2) 55%, var(--gold-soft) 100%);
  --shop-lounge-placeholder: linear-gradient(165deg, var(--warm-soft) 0%, var(--gold-soft) 58%, var(--bg-2) 100%);

  /* premium card, the one monetary purchase, dressed a little differently */
  --scene-premium-ring: color-mix(in srgb, var(--gold) 45%, var(--violet) 55%);
  --scene-premium-sky: linear-gradient(180deg, var(--violet-soft) 0%, var(--blue-soft) 100%);
  --scene-premium-lake: linear-gradient(180deg, var(--blue-soft) 0%, var(--accent-soft) 100%);
  --scene-shimmer: color-mix(in srgb, var(--gold) 55%, var(--card-2) 45%);
  --scene-shimmer-opacity: .5;
}

[data-theme="dark"] .shop-screen {
  /* a full --gold disc is the one thing that glares at night here (D-002) */
  --scene-sun: color-mix(in srgb, var(--gold) 62%, var(--panel) 38%);
  /* --ink turns pale at night: mix against --bg instead so the mountain
     silhouette stays a silhouette instead of a light halo */
  --scene-mtn: color-mix(in srgb, var(--accent-soft) 55%, var(--bg) 25%);
  /* a white-cored gold streak sweeping a dark card is the single most glaring
     thing on the night shop, keep the sheen, drop the highlight (D-002) */
  --scene-shimmer: color-mix(in srgb, var(--gold) 42%, var(--card-2) 58%);
  --scene-shimmer-opacity: .3;

  /* same two placeholders, re-mixed toward --bg so neither reads as a bright
     daylight card sitting in an otherwise dark shop (same move as
     --scene-sun/--scene-mtn above, D-002) */
  --shop-nook-placeholder: linear-gradient(165deg, color-mix(in srgb, var(--blue-soft) 65%, var(--bg) 35%) 0%, var(--bg-2) 55%, color-mix(in srgb, var(--gold-soft) 50%, var(--bg) 50%) 100%);
  --shop-lounge-placeholder: linear-gradient(165deg, color-mix(in srgb, var(--warm-soft) 55%, var(--bg) 45%) 0%, color-mix(in srgb, var(--gold-soft) 50%, var(--bg) 50%) 58%, var(--bg-2) 100%);
}

/* ---------- header ---------- */

.shop-header {
  display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
}
.shop-header-text h1 { font-size: 24px; letter-spacing: -.3px; margin-top: 4px; }
.shop-header-text p { color: var(--muted); font-weight: 700; font-size: 13.5px; margin-top: 2px; }
.shop-points-pill { font-size: 15px; }
/* the balance is re-rendered on every purchase, tabular figures keep the pill
   from twitching as digits change (same class as every other figure in the app) */
.shop-points-pill b, .shop-item-buy b { letter-spacing: 0; }

.shop-banner {
  display: flex; align-items: center; gap: 10px;
  background: var(--accent-soft);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-lg);
  padding: 10px 16px;
}
.shop-banner-ico { font-size: 18px; flex: 0 0 auto; }
.shop-banner p {
  font-weight: 700; font-size: 13px; color: var(--ink-soft);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ---------- tabs ---------- */

.shop-tabs { display: flex; gap: 8px; flex-wrap: wrap; }
.shop-tab {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 16px; border-radius: var(--r-md);
  background: var(--card); border: 1.5px solid var(--line-soft);
  font-weight: 800; font-size: 13.5px; color: var(--ink-soft);
  transition: border-color .2s ease, background .2s ease, transform .15s ease, color .2s ease;
}
.shop-tab:hover { transform: translateY(-1px); }
/* ui-fluide layer 2: components.css only styles :active on .btn, and tabs
   use their own class, so they had no pressed feedback at all. Placed after
   :hover so the press wins the transform tie while the pointer is held down. */
.shop-tab:active { transform: scale(.97); transition: transform .1s ease-out; }
.shop-tab.selected {
  border-color: var(--accent); background: var(--accent-soft); color: var(--accent);
  box-shadow: var(--shadow-1);
}

/* ---------- previews (voxel scene frames) ---------- */

.shop-previews {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 16px;
}

.shop-preview-card { display: flex; flex-direction: column; gap: 12px; padding: 20px; }
.shop-preview-head h3 { font-size: 16.5px; }
.shop-preview-head p { color: var(--muted); font-weight: 700; font-size: 12.5px; margin-top: 2px; }
.shop-preview-btn { align-self: flex-start; }

/* the voxel canvas mounted inside fills this box (mountVoxel appends a
   position:absolute canvas), a stable 4:3 ratio keeps both frames from
   jumping around as the grid reflows. fit stays 'contain' (VOXEL.md D-014):
   these frames are small, cropping the sky margins would starve the hint. */
.shop-nook-frame, .shop-lounge-frame {
  aspect-ratio: 4 / 3;
  min-height: 0;
  transition: transform .2s ease-out;
}
/* token placeholder (ui-fluide pass, layer 1): visible until the voxel
   canvas fades in below, one gradient per frame so each approximates its
   own scene's ambiance instead of a shared neutral fill */
.shop-nook-frame { background: var(--shop-nook-placeholder); }
.shop-lounge-frame { background: var(--shop-lounge-placeholder); }

/* MAJEUR 1 (review): the preview pulse used to animate box-shadow on a
   large surface with a live canvas underneath. Replaced by an inset ring on
   a ::after, opacity-only: inset because .scene-frame clips overflow, so a
   normal (outward) box-shadow would just get cut off. The ring paints above
   the canvas but below .scene-float (explicit z-index: 1, see
   .shop-nook-empty), and it's a thin inset stroke with a transparent
   center, so it never covers the float's text nor interferes with the
   canvas's own .is-baked opacity reveal (two separate elements, two
   separate opacity transitions). */
.shop-nook-frame::after, .shop-lounge-frame::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: inset 0 0 0 3px var(--accent-soft);
  opacity: 0;
  transition: opacity .2s ease-out;
}
.shop-pulse { transform: scale(1.012); }
.shop-pulse::after { opacity: 1; }

/* the voxel canvas (engine.js: <canvas class="scene-canvas">) bakes
   synchronously (200-340ms/scene, D-012) inside mountVoxel(); shop.js defers
   the mount itself (rAF + setTimeout 0, serialized) and only adds .is-baked
   to the frame once mountVoxel() has actually returned -- by then the canvas
   already holds real painted pixels (engine.js's mountVoxel ends with the
   synchronous `rebuild(); start();`). Until .is-baked lands, this rule keeps
   the canvas invisible so the token placeholder above shows through. */
.shop-nook-frame .scene-canvas,
.shop-lounge-frame .scene-canvas {
  opacity: 0;
  transition: opacity .22s cubic-bezier(0, 0, 0.2, 1);
}
.shop-nook-frame.is-baked .scene-canvas,
.shop-lounge-frame.is-baked .scene-canvas {
  opacity: 1;
}

.shop-nook-empty {
  left: 50%; bottom: 12px; transform: translateX(-50%);
  font-size: 12px; font-weight: 800; color: var(--ink-soft);
  white-space: nowrap; z-index: 1;
}

/* ---------- body: main grid + rail ---------- */

.shop-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(240px, 300px);
  gap: 16px;
  align-items: start;
}

.shop-main { display: flex; flex-direction: column; gap: 16px; min-width: 0; }

.shop-section-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.shop-section-head h2 { font-size: 18px; }

.shop-sort {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; border-radius: var(--r-md);
  background: var(--card); border: 1px solid var(--line-soft);
  font-weight: 800; font-size: 13px; color: var(--ink-soft);
  box-shadow: var(--shadow-1);
}
.shop-sort select {
  appearance: none; border: none; background: transparent; color: var(--ink);
  font-weight: 800; font-size: 13px; padding-right: 16px;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: right 2px top 55%, right 7px top 55%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  cursor: pointer;
}

.shop-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.shop-chip {
  padding: 7px 14px; border-radius: 999px;
  background: var(--card); border: 1.5px solid var(--line-soft);
  font-weight: 800; font-size: 12.5px; color: var(--ink-soft);
  transition: border-color .2s ease, background .2s ease, color .2s ease, transform .15s ease;
}
.shop-chip:hover { transform: translateY(-1px); }
/* ui-fluide layer 2: same gap as .shop-tab above, same fix */
.shop-chip:active { transform: scale(.97); transition: transform .1s ease-out; }
.shop-chip.selected { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }

.shop-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
}

.shop-empty { grid-column: 1 / -1; text-align: center; padding: 24px 0; font-weight: 700; font-size: 13.5px; }

/* ui-fluide layer 3: staggered card entrance. shop.js only adds this class
   (and its per-card animation-delay, inline) when the grid's content SET
   changed since the last paint -- never on a points-only tick, and never on
   a reorder alone (MAJEUR 3, review: sort keeps the same set, so it
   renders instantly with no entrance) -- so already-visible cards never
   replay it. Reduced motion only needs to zero the residual animation-delay
   (below): the global net already clamps animation-duration, and nobody
   awaits this animation's end in JS, so the stronger `animation: none`
   (Piège ui-fluide: forbidden when an animationend IS awaited) was never
   needed here either way. */
@keyframes shop-item-enter {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}
.shop-item-enter {
  animation: shop-item-enter .22s cubic-bezier(0, 0, 0.2, 1) both;
}

.shop-item-card {
  display: flex; flex-direction: column; gap: 12px; align-items: stretch;
  padding: 20px 16px;
  transition: transform .2s ease-out, box-shadow .2s ease-out;
}
.shop-item-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-2); }

.shop-item-thumb {
  position: relative;
  aspect-ratio: 1.2; border-radius: var(--r-md);
  display: grid; place-items: center;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink) 6%, transparent);
  background: linear-gradient(135deg, var(--bg-2) 0%, var(--card-2) 100%);
  overflow: hidden;
}
.shop-item-card[data-tone="warm"] .shop-item-thumb { background: linear-gradient(135deg, var(--warm-soft) 0%, var(--card-2) 100%); }
.shop-item-card[data-tone="accent"] .shop-item-thumb { background: linear-gradient(135deg, var(--accent-soft) 0%, var(--card-2) 100%); }
.shop-item-card[data-tone="gold"] .shop-item-thumb { background: linear-gradient(135deg, var(--gold-soft) 0%, var(--card-2) 100%); }
.shop-item-card[data-tone="blue"] .shop-item-thumb { background: linear-gradient(135deg, var(--blue-soft) 0%, var(--card-2) 100%); }
.shop-item-card[data-tone="violet"] .shop-item-thumb { background: linear-gradient(135deg, var(--violet-soft) 0%, var(--card-2) 100%); }

.shop-item-thumb span {
  font-size: clamp(30px, 6vw, 38px);
  filter: drop-shadow(0 3px 5px color-mix(in srgb, var(--ink) 20%, transparent));
  transition: transform .25s ease-out;
}
.shop-item-card:hover .shop-item-thumb span { transform: translateY(-5px) scale(1.07); }

.shop-item-name { font-weight: 800; font-size: 13.5px; text-align: center; }

.shop-item-buy { width: 100%; }
.shop-item-owned { background: var(--accent-soft); color: var(--accent); cursor: default; }
.shop-item-owned:hover { filter: none; }

/* cosmetics: a badge + sober line instead of a disabled buy button, so
   ownership reads as "yours" rather than "action unavailable" */
.shop-item-badge { align-self: center; }
.shop-item-note { font-size: 11.5px; font-weight: 700; text-align: center; margin-top: -6px; }

/* ---------- support tab: real-money SKUs, no gameplay effect (D-015) ---------- */

.shop-grid.shop-grid--support {
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
.shop-support-item-card {
  display: flex; flex-direction: column; gap: 10px;
  padding: 18px 16px;
}
.shop-support-item-head { display: flex; align-items: center; gap: 12px; }
.shop-support-item-ico { font-size: 28px; flex: 0 0 auto; }
.shop-support-item-name { font-weight: 800; font-size: 14px; }
.shop-support-item-price { font-weight: 900; font-size: 13px; color: var(--accent); margin-top: 1px; }
.shop-support-item-desc { color: var(--ink-soft); font-weight: 600; font-size: 12.5px; }
.shop-support-item-buy { margin-top: auto; width: 100%; }

/* ---------- rail ---------- */

.shop-rail { display: flex; flex-direction: column; gap: 16px; }

.shop-support-card { padding: 20px; }
.shop-support-card h3 { font-size: 14.5px; margin-bottom: 12px; }
.shop-promise-list { display: flex; flex-direction: column; gap: 12px; list-style: none; }
.shop-promise-list li { display: flex; align-items: flex-start; gap: 9px; font-weight: 700; font-size: 12.5px; color: var(--ink-soft); }
.shop-promise-ico { flex: 0 0 auto; font-size: 15px; }

.shop-premium-card {
  position: relative;
  display: flex; flex-direction: column; gap: 12px;
  padding: 20px;
  background: linear-gradient(160deg, var(--card) 0%, var(--violet-soft) 140%);
  border-color: color-mix(in srgb, var(--scene-premium-ring) 45%, var(--line-soft));
  box-shadow: var(--shadow-1), 0 0 0 1px color-mix(in srgb, var(--scene-premium-ring) 30%, transparent);
  overflow: hidden;
}
.shop-premium-card::before {
  content: '';
  position: absolute; inset: -30% -60%;
  background: linear-gradient(75deg, transparent 42%, var(--scene-shimmer) 49%, transparent 56%);
  opacity: var(--scene-shimmer-opacity);
  transform: translateX(-130%);
  animation: shop-premium-shimmer 5.5s ease-in-out infinite;
  pointer-events: none;
}
@keyframes shop-premium-shimmer {
  0%, 55%, 100% { transform: translateX(-130%); }
  80% { transform: translateX(130%); }
}
.shop-premium-card > * { position: relative; z-index: 1; }
.shop-premium-card h3 { font-size: 15.5px; margin-top: 2px; }
.shop-premium-card p { color: var(--ink-soft); font-weight: 700; font-size: 12.5px; }

.shop-premium-window {
  margin-top: 2px; height: 86px; border-radius: var(--r-md);
  overflow: hidden; position: relative;
  box-shadow: var(--shadow-1), inset 0 0 0 2px color-mix(in srgb, var(--scene-premium-ring) 35%, transparent);
}
.shop-premium-sky { position: absolute; inset: 0 0 45% 0; background: var(--scene-premium-sky); }
.shop-premium-sun {
  position: absolute; top: 10%; right: 12%; width: 15%; aspect-ratio: 1;
  border-radius: 999px; background: var(--scene-sun); opacity: .9;
}
.shop-premium-mtn-back {
  position: absolute; left: -6%; right: -6%; bottom: 26%; height: 36%;
  background: color-mix(in srgb, var(--scene-mtn) 65%, var(--violet-soft) 35%);
  clip-path: polygon(0% 100%, 16% 30%, 34% 72%, 52% 12%, 70% 66%, 88% 24%, 100% 100%);
  opacity: .55;
}
.shop-premium-mtn-front {
  position: absolute; left: -6%; right: -6%; bottom: 20%; height: 32%;
  background: var(--scene-mtn);
  clip-path: polygon(0% 100%, 12% 42%, 30% 84%, 48% 22%, 66% 78%, 84% 32%, 100% 100%);
}
.shop-premium-lake { position: absolute; inset: 55% 0 0 0; background: var(--scene-premium-lake); }

.shop-premium-btn {
  margin-top: 4px;
  background: var(--violet); color: var(--accent-ink);
  box-shadow: var(--shadow-pop), var(--shadow-1);
}

/* ---------- responsive ---------- */

@media (max-width: 860px) {
  .shop-body { grid-template-columns: 1fr; }
  .shop-rail { flex-direction: row; flex-wrap: wrap; }
  .shop-rail > * { flex: 1 1 260px; }
}

@media (prefers-reduced-motion: reduce) {
  .shop-premium-card::before {
    animation: none;
  }
  .shop-tab:hover {
    transform: none;
    border-color: var(--accent);
  }
  .shop-chip:hover {
    transform: none;
    border-color: var(--accent);
  }
  .shop-tab:active,
  .shop-chip:active {
    transform: none;
  }
  .shop-item-card:hover {
    transform: none;
  }
  .shop-item-card:hover .shop-item-thumb span {
    transform: none;
  }
  .shop-pulse {
    transform: none;
  }
  /* ui-fluide layer 3: the global net (base.css) clamps animation-duration
     to .01ms already, but never touches animation-delay -- without this, a
     card scheduled 280ms out would still sit invisible for 280ms before
     snapping into place. Zeroing the inline per-card delay here removes
     that residual wait; !important is required to beat the inline style
     shop.js sets on the card itself. */
  .shop-item-enter {
    animation-delay: 0s !important;
  }
  /* the global net (base.css) clamps transition-duration to .01ms already,
     but never touches a residual transition-delay; this screen sets none
     explicitly on its one JS-gated reveal, so appearance is immediate the
     instant .is-baked lands, no fade, no residual delay left to the net. */
  .shop-nook-frame .scene-canvas,
  .shop-lounge-frame .scene-canvas {
    transition: none;
  }
}

@media (max-width: 480px) {
  .shop-header { flex-direction: column; align-items: flex-start; }
  .shop-rail { flex-direction: column; }
  .shop-section-head { flex-direction: column; align-items: flex-start; }
  .shop-banner p { white-space: normal; }
}
