/* ╔══════════════════════════════════════════════════════════════════╗
   ║  GENERATED FILE — DO NOT EDIT.                                     ║
   ║  Built by tools/build-css.mjs from css/main.css.                   ║
   ║  Edit css/_base.css or css/components/*.css, then re-run:          ║
   ║      node tools/build-css.mjs                                      ║
   ╚══════════════════════════════════════════════════════════════════╝ */

/* main.css — generated manifest. Build: node tools/build-css.mjs
   @import order == cascade order. Numeric prefix == cascade position.
   Edit a component file or add a new @import here, then rebuild. */

/* ── sections / components (cascade-ordered) ── */
/* Afacad — score-orb numerals only (see .ztor-score__value). Imported
   here so all 16 pages get it through the one shared stylesheet. */
@import url('https://fonts.googleapis.com/css2?family=Afacad:wght@600;700&display=swap');

/* ============================================================
   Ztor 2.0 — Components
   Mirrors Figma component sets:
   - Button (Type × Size × State)
   - IconButton (State + Icon swap)
   - Header (State=Before Login | After Login)
   ============================================================ */

/* Global fluid media + mobile overflow guard (responsive system).
   Low-specificity max-width keeps media inside its column WITHOUT overriding
   component sizing (e.g. cover images that set height:100%). We deliberately
   do NOT set a global height:auto — that previously broke object-fit:cover
   images (hero, posters) by outranking their component height. overflow-x:
   clip on phones is a safety net; clip (not hidden) preserves sticky. */
:where(img, video, svg, canvas, iframe) { max-width: 100%; }
@media (max-width: 767.98px) {
  html { overflow-x: clip; }
  body { overflow-x: clip; max-width: 100%; }
}

/* ─────────────────────────────────────────
   BUTTON
   variants: primary | ghost | yellow-ghost | destructive
   sizes:    lg | md | sm
   states:   default | :hover | :active | :disabled
   ───────────────────────────────────────── */

.btn {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  cursor: pointer;
  white-space: nowrap;
  transition: box-shadow 150ms var(--ease-out),
              background 150ms var(--ease-out),
              border-color 150ms var(--ease-out),
              color 150ms var(--ease-out),
              transform 120ms var(--ease-out);
}

/* Physical press — the button meets the finger */
.btn:active:not(:disabled) { transform: scale(0.97); }

/* glass-wearing buttons: the ::before lens IS the surface (07 §5) — kill the
   UA ButtonFace default (opaque #F0F0F0) that otherwise renders under the
   glass and reads as a solid light pill on dark surfaces */
.btn.liquid-glass {
  background: transparent;
  color: var(--text-primary);
}

.btn:disabled { cursor: not-allowed; }
.btn:focus-visible {
  outline: 2px solid var(--yellow-500);
  outline-offset: 2px;
}

/* ── Sizes ── */
.btn--lg {
  padding: var(--space-4) 36px;
  font-family: var(--font-cjk-display);
  font-size: var(--fs-h5);
  line-height: var(--lh-body);
  font-weight: var(--fw-bold);
}

.btn--md {
  padding: var(--space-3) 28px;
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body-sm);
  font-weight: var(--fw-medium);
}

.btn--sm {
  padding: var(--space-2) var(--space-4);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  font-weight: var(--fw-medium);
}

/* ── PRIMARY ──
   Flat solid yellow (brand orange) — the loud, bright CTA. LG adds soft top
   highlight (4px inner shadow). Hover: + outer yellow glow. Pressed: glow.
*/
.btn--primary {
  background: var(--yellow-500);
  color: var(--yellow-ink);
  border-color: transparent;
}

.btn--primary.btn--lg {
  box-shadow: inset 0 1px 4px rgba(255, 255, 255, 0.9);
}

.btn--primary:hover:not(:disabled) {
  background: var(--yellow-400);
  box-shadow: 0 6px 20px rgba(255, 163, 63, 0.20);
}

.btn--primary.btn--lg:hover:not(:disabled) {
  background: var(--yellow-400);
  box-shadow: inset 0 1px 4px rgba(255, 255, 255, 0.9),
              0 6px 20px rgba(255, 163, 63, 0.20);
}

.btn--primary:active:not(:disabled) {
  background: var(--yellow-600);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.18);
}

.btn--primary:disabled { opacity: 0.4; }

/* ── GHOST — liquid glass: 1:1 with the 21st.dev ".btn-liquid" button ──
   The SECONDARY action beside the solid-orange primary (探索片庫, 查看參賽作品…).
   Their exact recipe (no sheen, no spectral rim — those were my additions, removed):
     .btn-liquid       → button: transparent, white-95% label, text-shadow,
                          hover scale(1.03) / active scale(0.96), 300ms ease-out.
     .btn-liquid-lens  → ::before: white-5% fill + blur(8px) url(#liquid-glass)
                          saturate(150%) [url() added in 52-…] + the 10-layer
                          specular box-shadow (= --glass-shadow), 400ms transition. */
.btn--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-primary);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.30);
  transition: transform 300ms var(--ease-out);
}
.btn--ghost::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: var(--glass-fill);                 /* white 5% — no sheen */
  box-shadow: var(--glass-shadow);
  backdrop-filter: var(--glass-backdrop);
  -webkit-backdrop-filter: var(--glass-backdrop);
  transition: background-color 400ms cubic-bezier(1, 0, 0.4, 1),
              box-shadow        400ms cubic-bezier(1, 0, 0.4, 1);
}

.btn--ghost:hover:not(:disabled)  { transform: scale(1.03); }   /* their hover grow */
.btn--ghost:active:not(:disabled) { transform: scale(0.96); }   /* their press */

.btn--ghost:disabled {
  color: var(--text-disabled);
  text-shadow: none;
}
.btn--ghost:disabled::before {
  background: var(--glass-bg-disabled);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* ── YELLOW-GHOST — orange liquid glass (the "Vote" CTA) ──
   Same 21st.dev machinery as .btn--ghost (white-5% lens → here orange-tinted),
   the 10-layer specular box-shadow, blur(8px) url(#liquid-glass) saturate(150%)
   [url() added in 52-…], hover scale(1.03) / active scale(0.96). Orange comes
   from the fill + the label; the specular highlights stay white = glass shine. */
.btn--yellow-ghost {
  background: transparent;
  border-color: transparent;
  color: var(--yellow-500);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.30);
  transition: transform 300ms var(--ease-out);
}
.btn--yellow-ghost::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: var(--glass-fill-orange);
  box-shadow: var(--glass-shadow);
  backdrop-filter: var(--glass-backdrop);
  -webkit-backdrop-filter: var(--glass-backdrop);
  transition: background-color 400ms cubic-bezier(1, 0, 0.4, 1),
              box-shadow        400ms cubic-bezier(1, 0, 0.4, 1);
}

.btn--yellow-ghost:hover:not(:disabled) { transform: scale(1.03); }
.btn--yellow-ghost:hover:not(:disabled)::before { background: var(--glass-fill-orange-hover); }
.btn--yellow-ghost:active:not(:disabled) { transform: scale(0.96); }

.btn--yellow-ghost:disabled {
  color: rgba(255, 163, 63, 0.4);
  text-shadow: none;
}
.btn--yellow-ghost:disabled::before {
  background: rgba(255, 163, 63, 0.04);
  box-shadow: inset 0 0 0 1px rgba(255, 163, 63, 0.15);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* ── DESTRUCTIVE ──
   default/hover: hollow red glass (Artlist-style)
   pressed: solid red + ink text (state escalation)
*/
.btn--destructive {
  background: linear-gradient(0deg,
    rgba(239, 68, 68, 0) 0%,
    rgba(239, 68, 68, 0.20) 2.45%,
    rgba(239, 68, 68, 0) 126.14%);
  border-color: transparent;
  color: var(--error-500);
  text-shadow: none;
  box-shadow:
    rgba(239, 68, 68, 0.9) 1.1px 2.2px 0.5px -1.8px inset,
    rgba(239, 68, 68, 0.9) -1px -2.2px 0.5px -1.8px inset,
    rgba(0, 0, 0, 0.10) 2px 3px 2px 0 inset;
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}

.btn--destructive:hover:not(:disabled) {
  background: linear-gradient(0deg,
    rgba(239, 68, 68, 0) 0%,
    rgba(239, 68, 68, 0.34) 2.45%,
    rgba(239, 68, 68, 0) 126.14%);
  text-shadow: rgba(239, 68, 68, 0.4) 0 0 10px;
}

.btn--destructive:active:not(:disabled) {
  background: var(--error-500);
  color: var(--error-ink);
  text-shadow: none;
  box-shadow:
    rgba(255, 255, 255, 0.5) 1.1px 2.2px 0.5px -1.8px inset,
    rgba(0, 0, 0, 0.3) 0 4px 12px;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.btn--destructive:disabled {
  background: rgba(239, 68, 68, 0.05);
  color: rgba(239, 68, 68, 0.4);
  text-shadow: none;
  box-shadow:
    rgba(239, 68, 68, 0.3) 1.1px 2.2px 0.5px -1.8px inset,
    rgba(239, 68, 68, 0.3) -1px -2.2px 0.5px -1.8px inset;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* ─── Quiet — glassmorphic low-key button.
       Use for cancel / dismiss / tertiary actions inside modals,
       dialogs, banners, or any surface where the action shouldn't
       compete with the primary CTA.

       Composition (3 properties together = the "glass" feel):
       - Translucent black fill (10/16/22/5%) — depth without opacity
       - Translucent white edge (24/32/32/10%) — defines the shape
       - 4px backdrop-blur — frosts the layer behind it

       Text shifts secondary→primary on hover/active so users feel
       the interaction even though the surface change is subtle. */
.btn--quiet {
  background: rgba(0, 0, 0, 0.10);
  border-color: rgba(255, 255, 255, 0.24);
  color: var(--text-secondary);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

.btn--quiet:hover:not(:disabled) {
  background: rgba(0, 0, 0, 0.16);
  border-color: rgba(255, 255, 255, 0.32);
  color: var(--text-primary);
}

.btn--quiet:active:not(:disabled) {
  background: rgba(0, 0, 0, 0.22);
  border-color: rgba(255, 255, 255, 0.32);
  color: var(--text-primary);
}

.btn--quiet:disabled {
  background: rgba(0, 0, 0, 0.05);
  border-color: rgba(255, 255, 255, 0.10);
  color: var(--text-disabled);
  cursor: not-allowed;
}

/* reusable .liquid-glass primitive — its header maps all 3 pieces of the effect */
/* ╔══════════════════════════════════════════════════════════════════════╗
   ║  LIQUID GLASS — the reusable material primitive                        ║
   ║                                                                        ║
   ║  Drop `.liquid-glass` on ANY element (button, pill, nav, card) to give ║
   ║  it the standard frosted-glass material. The content inside is yours.  ║
   ║                                                                        ║
   ║  THE EFFECT = 3 CO-LOCATED PIECES (it looks like "just frost" if you   ║
   ║  read only one of them — that is the trap):                            ║
   ║   1. KNOBS  → the --lg-* control panel in  tokens.css   (tune here)    ║
   ║   2. CLASS  → this file                                  (the recipe)  ║
   ║   3. SVG    → #liquid-glass filter injected by assets/liquid-glass.js  ║
   ║              (+ map assets/liquid-glass-map.webp). This is the          ║
   ║              REFRACTION layer — injected at RUNTIME, present in NO      ║
   ║              .css/.html file. Do NOT conclude "transparent" just        ║
   ║              because the CSS only shows frost.                          ║
   ║                                                                        ║
   ║  Variants:  .liquid-glass--orange  → the "Vote" CTA tint               ║
   ║             .liquid-glass--nav     → FIXED / WIDE bars (top + bottom    ║
   ║                                      nav). SAME material as the base —   ║
   ║                                      only invisible plumbing differs:    ║
   ║                                      frost on the element (§9) + a       ║
   ║                                      width-sized refraction filter so a  ║
   ║                                      fixed wide bar composites & doesn't ║
   ║                                      over-throw. Same glass, not a look. ║
   ║                                                                        ║
   ║  The existing .btn--ghost / .btn--yellow-ghost / .glass-tabs / .botnav ║
   ║  read the SAME --lg-* panel via the --glass-* aliases, so they already  ║
   ║  match this primitive. Use this class for any NEW glass surface.        ║
   ╚══════════════════════════════════════════════════════════════════════╝ */

.liquid-glass {
  position: relative;
  isolation: isolate;                 /* keep the lens behind THIS element's own content */
}
.liquid-glass::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;             /* the host element sets the shape */
  background: var(--lg-fill);
  box-shadow: var(--lg-shine);
  -webkit-backdrop-filter: var(--lg-frost);
          backdrop-filter: var(--lg-frost);
  pointer-events: none;               /* a full-cover pseudo must never eat clicks */
}

/* SHEEN — the GLOSSY reflection: a broad white top-light that fades down the glass,
   like light on a wet/polished surface. Separate from the edge bevel (--lg-shine).
   On its own pseudo (no transform/backdrop) so it stays crisp; sits above the frost,
   below the content. Tune strength with --lg-sheen. */
.liquid-glass::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(255, 255, 255, var(--lg-sheen, 0.18)) 0%,
    rgba(255, 255, 255, calc(var(--lg-sheen, 0.18) * 0.3)) 16%,
    rgba(255, 255, 255, 0) 48%);
}

/* Chromium with the refraction filter live → add the SVG displacement.
   Non-Chromium / iOS keep the plain frost above (see assets/liquid-glass.js). */
.glass-refract .liquid-glass::before {
  /* var(--lg-refract) = this element's OWN size-matched lens (set by liquid-glass.js);
     falls back to the shared filter before JS runs / for un-processed elements. */
  -webkit-backdrop-filter: blur(var(--lg-blur)) var(--lg-refract, url(#liquid-glass)) saturate(var(--lg-sat));
          backdrop-filter: blur(var(--lg-blur)) var(--lg-refract, url(#liquid-glass)) saturate(var(--lg-sat));
}

/* ORANGE tint — the "Vote" CTA */
.liquid-glass--orange::before { background: var(--lg-fill-orange); }

/* FIXED / WIDE bars (top + bottom nav) — the SAME glass material as the base
   .liquid-glass: SAME --lg-fill (white-5%), SAME --lg-shine, SAME --lg-frost (8px).
   It is NOT a different look. The only two differences are invisible plumbing that
   fixed positioning physically requires:
     (1) the frost sits on the ELEMENT (+ translateZ) instead of the ::before lens —
         a fixed element's backdrop-filter only composites that way; a ::before on a
         fixed element renders see-through (NAV_SCROLL_BEHAVIOR.md §9).
     (2) the url() refraction is a per-element filter SIZED to the bar (built in
         liquid-glass.js for [data-lg-refract]) — the SAME #liquid-glass recipe + map,
         just sized so a wide box doesn't over-throw and go see-through.
   So nav + button are the same fill, same frost, same shine, same refraction. */
.liquid-glass--nav {
  background: var(--lg-fill);               /* SAME white-5% as the button */
  -webkit-backdrop-filter: var(--lg-frost); /* frost-only fallback; refraction added under .glass-refract below */
          backdrop-filter: var(--lg-frost);
  transform: translateZ(0);                 /* §9: own GPU layer so a fixed bar composites */
  isolation: auto;
  /* NO box-shadow here on purpose: transform + backdrop-filter on the SAME box
     wash out an inset box-shadow → the bar reads MATTE (verified). So the specular
     shine moves to the ::before lens below, which has no transform/backdrop and
     stays crisp. Same split the product uses: frost on .botnav, shine on
     .botnav__list. */
}
/* SHINE lens — re-enabled for fixed bars (base hid it). Carries ONLY the specular
   --lg-shine (no fill, no frost, no transform) so the gloss renders crisp on top of
   the element's frost → the nav now reads as shiny as the button. inset / z-index /
   border-radius / pointer-events are inherited from .liquid-glass::before above. */
.liquid-glass--nav::before {
  display: block;
  background: none;                /* fill is on the element */
  -webkit-backdrop-filter: none;   /* frost is on the element */
          backdrop-filter: none;
}
.glass-refract .liquid-glass--nav::before {
  -webkit-backdrop-filter: none;   /* keep the shine lens frost-free when refraction is on */
          backdrop-filter: none;
}
/* refraction on the nav ELEMENT (Chromium): its OWN size-matched lens via --lg-refract */
.glass-refract .liquid-glass--nav {
  -webkit-backdrop-filter: blur(var(--lg-blur)) var(--lg-refract, url(#liquid-glass)) saturate(var(--lg-sat));
          backdrop-filter: blur(var(--lg-blur)) var(--lg-refract, url(#liquid-glass)) saturate(var(--lg-sat));
}

/* ─────────────────────────────────────────
   ICON BUTTON
   40×40 glass pill, swap-in icon child
   ───────────────────────────────────────── */

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--glass-bg-default);
  border: 1px solid var(--glass-stroke-default);
  color: var(--text-primary);
  cursor: pointer;
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  transition: background 150ms var(--ease-out),
              border-color 150ms var(--ease-out),
              transform 120ms var(--ease-out);
}

.icon-btn:hover:not(:disabled) {
  background: var(--glass-bg-hover);
  border-color: var(--glass-stroke-hover);
}

.icon-btn:active:not(:disabled) {
  background: var(--glass-bg-pressed);
  border-color: var(--glass-stroke-pressed);
  transform: scale(0.95);
}

.icon-btn:disabled {
  background: var(--glass-bg-disabled);
  border-color: var(--glass-stroke-disabled);
  color: var(--text-disabled);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  cursor: not-allowed;
}

.icon-btn svg {
  width: 24px;
  height: 24px;
  stroke: currentColor;
  fill: none;
}

/* ─────────────────────────────────────────
   FAB — floating action button
   Circular ghost-glass action anchored to the
   viewport corner (e.g. 發表貼文 on 社群).
   Reuses the .btn--ghost glass treatment but is
   a perfect circle with a centred icon, lifted
   off the page with a soft drop-shadow.
   Markup: <button class="fab btn btn--ghost">
   ───────────────────────────────────────── */
.fab {
  position: fixed;
  right: 32px;
  bottom: 32px;
  z-index: var(--z-fab);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 60px;
  height: 60px;
  padding: 0;
  border-radius: var(--radius-circle, 50%);
  /* drop-shadow (not box-shadow) so it composes with the ghost
     edge-highlight box-shadow instead of overriding it. */
  filter: drop-shadow(0 6px 20px rgba(0, 0, 0, 0.45));
  transition: filter 150ms ease, transform 150ms ease,
              background 150ms ease, box-shadow 150ms ease;
}

.fab:hover:not(:disabled) {
  transform: translateY(-2px);
  filter: drop-shadow(0 10px 26px rgba(0, 0, 0, 0.5));
}

.fab:active:not(:disabled) {
  transform: translateY(0);
  filter: drop-shadow(0 4px 14px rgba(0, 0, 0, 0.45));
}

.fab__icon {
  width: 26px;
  height: 26px;
  background-color: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
  mask: var(--icon) center / contain no-repeat;
}

/* ─────────────────────────────────────────
   AI MOSAIC
   AI 原力創作計畫 section: 2-col layout — copy + CTA on left, mosaic on right.
   Mosaic = 1 big tile (top, full row) + 4 small tiles (2×2 below).
   ───────────────────────────────────────── */

.ai-mosaic {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-20);
  align-items: center;
}

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

.ai-mosaic__ctas {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-3);
  flex-wrap: wrap;
}

.ai-mosaic__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

/* Each tile is 16:9 (matches 1.0 image spec 1056×594).
   Big tile spans both columns; small tiles fill 1 col each. */
.ai-mosaic__tile {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  overflow: hidden;
  background: var(--bg-tertiary);
  text-decoration: none;
  color: inherit;
  transition: transform 220ms var(--ease-out), border-color 220ms var(--ease-out);
}

.ai-mosaic__tile:hover {
  transform: scale(1.02);
  border-color: var(--border-default);
}

.ai-mosaic__tile-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
}

/* Same frosted "text on photo" treatment as .media-glass, fitted to the tile:
   a progressive backdrop-blur + ink tint, masked to fade up off the image so
   the status badge + title stay legible over ANY photo. (This ::before already
   paints above the img, so backdrop-filter blurs the photo behind it.) */
.ai-mosaic__tile::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  -webkit-backdrop-filter: var(--lg-frost-media);   /* 22px media-card frost — one knob */
          backdrop-filter: var(--lg-frost-media);
  background: linear-gradient(to bottom, transparent 0%, rgba(10, 10, 10, 0.30) 55%, rgba(10, 10, 10, 0.62) 100%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, rgba(0, 0, 0, 0.5) 52%, #000 76%);
          mask-image: linear-gradient(to bottom, transparent 0%, rgba(0, 0, 0, 0.5) 52%, #000 76%);
  pointer-events: none;
}
/* No backdrop-filter (old engines): a stronger tint carries legibility. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ai-mosaic__tile::before {
    background: linear-gradient(to bottom, transparent 0%, rgba(10, 10, 10, 0.62) 40%, rgba(10, 10, 10, 0.88) 100%);
  }
}

.ai-mosaic__tile--big {
  grid-column: 1 / -1;
  border-radius: var(--radius-lg);
}

.ai-mosaic__tile-meta {
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: 14px;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
}

.ai-mosaic__tile-status {
  backdrop-filter: blur(var(--lg-blur));               /* 8px chip frost — off the master knob */
  -webkit-backdrop-filter: blur(var(--lg-blur));
}

.ai-mosaic__tile-title {
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: 14px;
  line-height: 1.3;
  color: var(--text-primary);
  letter-spacing: -0.005em;
  text-wrap: balance;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.4);
}

.ai-mosaic__tile--big .ai-mosaic__tile-title {
  font-size: 22px;
  letter-spacing: -0.01em;
}

/* AI campaign row — standard section layout (header on top + one row of cards),
   matching the other homepage sections. Reuses .ai-mosaic__tile visuals but
   lays all campaigns out as equal 16:9 cards in a single row. */
.ai-campaign-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-6);
}
/* Larger cards in a 3-up row → bump the overlaid title for legibility. */
.ai-campaign-row .ai-mosaic__tile-title { font-size: 17px; }
@media (max-width: 900px) { .ai-campaign-row { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .ai-campaign-row { grid-template-columns: 1fr; } }

/* ─────────────────────────────────────────
   AI MOSAIC — RESPONSIVE
   Phone  : max-width 767.98px
   Tablet : 768px – 1023.98px
   ───────────────────────────────────────── */

/* ── Tablet (768px – 1023.98px) ─────────────────────────────────────────── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  /* Tighten the two-column gap on tablet */
  .ai-mosaic { gap: var(--space-10); }

  /* Mosaic grid: tighter inner gap */
  .ai-mosaic__grid { gap: var(--space-2); }

  /* Big tile title slightly smaller */
  .ai-mosaic__tile--big .ai-mosaic__tile-title { font-size: 18px; }

  /* Campaign row: 2-up on tablet */
  .ai-campaign-row {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-5);
  }
  .ai-campaign-row .ai-mosaic__tile-title { font-size: 15px; }
}

/* ── Phone (≤767.98px) ──────────────────────────────────────────────────── */
@media (max-width: 767.98px) {
  /* Stack copy above mosaic: single column, copy first */
  .ai-mosaic {
    grid-template-columns: 1fr;
    gap: var(--space-8);
  }

  /* Mosaic grid stays 2-col internally — good use of phone width */
  .ai-mosaic__grid { gap: var(--space-2); }

  /* Big tile title: legible on phone */
  .ai-mosaic__tile--big .ai-mosaic__tile-title { font-size: 16px; }
  .ai-mosaic__tile-title { font-size: 13px; }

  /* Tile meta: tighter positioning on phone */
  .ai-mosaic__tile-meta {
    left: var(--space-3);
    right: var(--space-3);
    bottom: var(--space-3);
    gap: var(--space-1);
  }

  /* CTAs: stack on small screens */
  .ai-mosaic__ctas {
    flex-direction: column;
    gap: var(--space-2);
  }

  /* Campaign row: single column on phone */
  .ai-campaign-row {
    grid-template-columns: 1fr;
    gap: var(--space-4);
  }
  .ai-campaign-row .ai-mosaic__tile-title { font-size: 15px; }
}

/* ── Small phone (≤479.98px) ────────────────────────────────────────────── */
@media (max-width: 479.98px) {
  .ai-mosaic__grid { gap: var(--space-1); }
  .ai-mosaic__tile--big .ai-mosaic__tile-title { font-size: 14px; }
  .ai-mosaic__tile-title { font-size: 12px; }
  .ai-mosaic__tile-meta {
    left: var(--space-2);
    right: var(--space-2);
    bottom: var(--space-2);
  }
}

/* ── Touch: remove hover scale transform ─────────────────────────────────── */
@media (hover: none) {
  .ai-mosaic__tile:active { transform: none; }
}

/* ── Reduced-motion ─────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .ai-mosaic__tile { transition: none; }
}

/* ─────────────────────────────────────────
   CLASSICS — 片單推薦「忘不了的經典」
   4-up quote cards (single composed image ported from V1) + title below.
   Card image ratio ≈ 0.91:1 (1199×1312 source).
   ───────────────────────────────────────── */
.classics-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-6);
}
.classic-card {
  display: flex;
  flex-direction: column;
  gap: 14px;
  text-decoration: none;
  color: inherit;
}
.classic-card__img {
  width: 100%;
  aspect-ratio: 1199 / 1312;
  object-fit: cover;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-subtle);
  display: block;
  transition: transform 250ms ease;
}
.classic-card:hover .classic-card__img { transform: translateY(-4px); }
.classic-card__title {
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: 18px;
  line-height: 1.3;
  color: var(--text-primary);
  margin: 0;
}
@media (max-width: 900px) { .classics-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 520px) { .classics-grid { grid-template-columns: 1fr; } }

/* ─────────────────────────────────────────
   CLASSICS — RESPONSIVE
   Phone  : max-width 767.98px
   Tablet : 768px – 1023.98px
   ───────────────────────────────────────── */

/* ── Tablet (768px – 1023.98px) ─────────────────────────────────────────── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  /* 2-up on tablet: cards are comfortable at ~half the container width */
  .classics-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-5);
  }

  /* Slightly smaller title at this density */
  .classic-card__title { font-size: var(--fs-body); }
}

/* ── Phone (≤767.98px) ──────────────────────────────────────────────────── */
@media (max-width: 767.98px) {
  /* Single column: one card per row, portrait image fills the width */
  .classics-grid {
    grid-template-columns: 1fr;
    gap: var(--space-5);
  }

  .classic-card { gap: var(--space-3); }

  /* Title: allow 2-line wrap for long CJK titles */
  .classic-card__title {
    font-size: var(--fs-body);
    line-height: 1.4;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  /* Lift on touch: swap hover lift for :active feedback */
  .classic-card:active .classic-card__img { transform: none; }
}

/* ── Small phone (≤479.98px) ────────────────────────────────────────────── */
@media (max-width: 479.98px) {
  .classics-grid { gap: var(--space-4); }
  .classic-card__title { font-size: var(--fs-body-sm); }
}

/* ── Touch: remove hover-only lift transform ─────────────────────────────── */
@media (hover: none) {
  .classic-card .classic-card__img { transform: none; }
}

/* ── Reduced-motion ─────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .classic-card__img { transition: none; }
}

/* ─────────────────────────────────────────
   CO-CREATION GRID  (.cocreation-grid)
   Owns the grid layout (base + all responsive) in one file so the cascade
   is predictable. Card internals live in cocreation-card.css.
   ───────────────────────────────────────── */

/* Base — 3-up desktop grid */
.cocreation-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-6);
}

/* ─────────────────────────────────────────
   RESPONSIVE — CO-CREATION GRID
   (card internals handled in cocreation-card.css)
   ───────────────────────────────────────── */

/* ── Tablet (768–1023) ── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  .cocreation-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-5);
  }
}

/* ── Phone (<768): VERTICAL single-column stack (no horizontal scroll).
   Per L's directive — horizontal scroll is reserved for the content-dense
   Spotlight film rails; everything else stacks. Cards go full-width. ── */
@media (max-width: 767.98px) {
  .cocreation-grid {
    grid-template-columns: 1fr;        /* one full-width card per row */
    gap: var(--space-4);
  }

  /* Overlay variant: also a single column (was masonry columns) */
  .cocreation-grid--overlay { column-count: initial; column-gap: 0; }
}

/* ── Small phone (<480): tighten the vertical rhythm ── */
@media (max-width: 480px) {
  .cocreation-grid { gap: var(--space-3); }
}

/* ─────────────────────────────────────────
   HEADER
   Mirrors Figma node 485:1139 (State=Before Login | After Login)
   Structure:
     header
       header/nav   (gap 40)  → header/logo + header/nav-links (gap 36)
       header/actions (gap 24) → search-icon + lang-icon + buttons-cluster
   ───────────────────────────────────────── */

.header {
  position: sticky;
  top: 0;
  left: 0;
  right: 0;
  z-index: var(--z-header);
  height: var(--header-height);
  background: rgba(10, 10, 10, 0.72);
  backdrop-filter: blur(calc(var(--lg-blur) * 1.25));        /* 10px — off the master --lg-blur knob */
  -webkit-backdrop-filter: blur(calc(var(--lg-blur) * 1.25));
  border-bottom: 1px solid var(--border-subtle);
}

/* ── Artlist-style soft veil (ported from Dolly's sidebar build, 2026-06) ──
   has-rail pages drop the flat fill + hairline; a tint+blur layer extends
   24px below the bar, fades out via mask (no seam), and only fades in once
   scrolled — page-top stays fully crisp. Pinned-tabbar pages use a flat
   material matching the tabbar so bar + tabbar read as one shelf. */
body.has-rail .header {
  background: none;
  /* Blur on the ELEMENT (not the masked ::before) — Safari does NOT reliably
     render backdrop-filter on a masked pseudo, which made the header look
     un-frosted on iOS. The ::before below now only carries the gradient tint+fade.
     Frost weight restored to the original origin/L value (12px); the heavy-frost
     pass had pushed it to 16px. */
  backdrop-filter: blur(calc(var(--lg-blur) * 1.5));        /* 12px — off the master --lg-blur knob */
  -webkit-backdrop-filter: blur(calc(var(--lg-blur) * 1.5));
  border-bottom: none;
}
body.has-rail .header::before {
  content: '';
  position: absolute;
  inset: 0 0 -24px 0;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(23, 23, 23, 0.7) 0%, rgba(23, 23, 23, 0) 100%);
  /* blur lives on the .header element (above); this pseudo is gradient tint + fade only.
     Tint colour restored to the original origin/L veil (was darkened to 18/0.78 by the
     heavy-frost pass). */
  -webkit-mask-image: linear-gradient(180deg,
    rgba(0,0,0,1) 0%, rgba(0,0,0,1) 66%,
    rgba(0,0,0,0.96) 74%, rgba(0,0,0,0.82) 81%,
    rgba(0,0,0,0.58) 88%, rgba(0,0,0,0.28) 94%, rgba(0,0,0,0) 100%);
          mask-image: linear-gradient(180deg,
    rgba(0,0,0,1) 0%, rgba(0,0,0,1) 66%,
    rgba(0,0,0,0.96) 74%, rgba(0,0,0,0.82) 81%,
    rgba(0,0,0,0.58) 88%, rgba(0,0,0,0.28) 94%, rgba(0,0,0,0) 100%);
}
/* Veil is STATIC — always on, never scroll-revealed. The old scroll-timeline
   fade (header-veil-in: opacity 0→1 over the first 120px) meant a fresh page
   loaded at scroll-0 with the veil hidden while the page you navigated FROM had
   it shown; the cross-document View Transition then snapped between those two
   header states, which read as the nav-bar "flash". Killing the reveal makes
   the header identical at every scroll position and across every navigation —
   the chrome never animates. (L, 2026-06-26: "remove all scroll reveal of nav bar".) */
body.has-rail:has(.community-tabbar) .header::before {
  inset: 0;
  background: rgba(10, 10, 10, 0.82);
  /* blur is on the .header element; this just sets the flat community tint */
  -webkit-mask-image: none;
          mask-image: none;
}

.header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;   /* hard floor between nav cluster and actions when space runs out */
  height: 100%;
  padding: 0 var(--container-padding-x);
}

.header__nav {
  display: flex;
  align-items: center;
  gap: clamp(20px, 2.8vw, 40px);   /* fluid: 40px at ≥1430, shrinks with viewport */
  min-width: 0;   /* allow the left cluster to shrink — see .header__nav-links note */
}

.header__logo {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  height: 30px;
}

.header__logo-img {
  height: 30px;
  width: auto;
  display: block;
}

.header__nav-links {
  display: flex;
  align-items: center;
  /* Fluid gap + fluid link font (below) keep all 7 links genuinely fitting
     down to ~800px wide, so the overflow rail is a last-resort safety net,
     not the normal state — a rail that clips mid-glyph reads as a bug. */
  gap: clamp(12px, 2.5vw, 36px);
  /* Responsive guard: without min-width:0 a flex child refuses to shrink
     below its content width, so at narrow viewports the nav plows into
     .header__actions (the two clusters overlap instead of compressing).
     With it, the nav shrinks and overflows into a swipeable rail. */
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;          /* Firefox */
}
.header__nav-links::-webkit-scrollbar { display: none; }

.header__nav-link {
  position: relative;
  padding: 8px 4px;
  font-family: var(--font-cjk-display);
  font-size: clamp(13px, 1.15vw, 16px);   /* 16px at ≥1390 → 13px floor on small screens */
  line-height: 24px;
  font-weight: var(--fw-regular);
  color: var(--neutral-400);
  white-space: nowrap;
  transition: color 200ms ease;
}

.header__nav-link:hover { color: var(--neutral-white); }

.header__nav-link.is-active {
  color: var(--neutral-white);
  /* Weight stays regular on purpose: bolding the active item changes its
     width, which shifts the neighbouring links and makes the nav "jump"
     when the active page changes. Active is signalled by colour + the
     glowing underline below instead. */
}

.header__nav-link.is-active::after {
  content: '';
  position: absolute;
  left: 4px;
  right: 4px;
  bottom: 0;
  height: 2px;
  border-radius: 1px;
  background: var(--yellow-500);
  box-shadow: 0 0 8px rgba(255, 163, 63, 0.55);
}

.header__actions {
  display: flex;
  align-items: center;
  gap: clamp(12px, 1.6vw, 24px);   /* fluid, matches nav cluster scaling */
  flex-shrink: 0;
}

/* Bare 24×24 icon button (search / language) — no glass pill
   Uses mask-image so the SVG (white-fill) can be recolored on hover.
   Set the icon via inline style: style="--icon: url(<icon>.svg);" (search/
   language are inlined as data-URIs by tools/_inline-header-icons.mjs). */
.header__icon-btn {
  display: inline-block;
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  cursor: pointer;
  background-color: var(--neutral-300);
  -webkit-mask: var(--icon) center / 24px 24px no-repeat;
          mask: var(--icon) center / 24px 24px no-repeat;
  transition: background-color 200ms ease;
}

.header__icon-btn:hover { background-color: var(--neutral-white); }

/* Buttons cluster (right of icons) */
.header__buttons {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* (The cart-slot margin RESERVE that used to live here is gone: the cart icon now
   ships in the static HTML — see each page's .header__actions — so it's present at
   first paint and held across navigations by the view-transition. No JS injection
   ⇒ no pop-in, no layout shift, so no reserve is needed.) */

/* ── Header responsive condensation ──
   Gaps and nav type scale fluidly via clamp() (see rules above). The
   nav must FIT at every width ≥768 — the overflow rail clips mid-glyph
   and reads as a bug, so it stays a last-resort safety net only.
   ≤1200px: 計畫與價格 pill collapses to its popcorn icon (aria-label
            keeps it accessible), tighter login padding. The pill label
            is the single widest flexible item — collapsing it early is
            what buys the nav its room.
   ≤1024px: gap floors.
   ≤860px:  smallest nav type (12px).
   ≤768px:  nav links hidden entirely (MOBILE TWEAKS block at end of
            file) — desktop pages have mobile-*.html counterparts. */
@media (max-width: 1200px) {
  /* Doubled class selectors: the chevron's base rule is defined LATER in
     this file, so equal specificity would lose by source order. */
  .popcorn-section .popcorn-section__label,
  .popcorn-section .popcorn-section__chevron { display: none; }
  .header__login-btn { padding: 0 14px; }
}

@media (max-width: 1024px) {
  .header__nav { gap: 16px; }
  .header__nav-links { gap: 12px; }
  .header__actions { gap: 12px; }
  .header__inner { padding: 0 20px; }
}

@media (max-width: 900px) {
  .header__nav-link { font-size: 12px; }
  .header__nav-links {
    gap: 10px;
    /* Below ~830px the 7 CJK labels can't physically fit — the rail
       scrolls. The mask fades the cut edge so it reads as "more to
       scroll", never as a glyph sliced in half. padding-right keeps
       the last link clear of the fade when scrolled to the end. */
    padding-right: 28px;
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
            mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
  }
}

@media (max-width: 768px) {
  .header__inner { padding: 0 16px; }
}

.header__buttons--logged-in { gap: 12px; }

/* PopcornSection — Before Login (計畫與價格 pill) — same 21st.dev .btn-liquid glass
   as .btn--ghost: white-5% lens on ::before (z-index:-1) + the 10-layer specular
   box-shadow + blur(8px) url(#liquid-glass) saturate(150%) [url() added in 52-…].
   NO sheen, NO rim. Hover scale(1.03) / press scale(0.96), exactly theirs. */
.popcorn-section {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 40px;
  padding: 0 16px;
  border: none;
  border-radius: var(--radius-pill);
  cursor: pointer;
  text-decoration: none;
  color: var(--neutral-white);
  background: transparent;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.30);
  transition: transform 300ms var(--ease-out);
}
.popcorn-section::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: var(--glass-fill);                 /* white 5% — no sheen */
  box-shadow: var(--glass-shadow);
  backdrop-filter: var(--glass-backdrop);
  -webkit-backdrop-filter: var(--glass-backdrop);
  transition: background-color 400ms cubic-bezier(1, 0, 0.4, 1),
              box-shadow        400ms cubic-bezier(1, 0, 0.4, 1);
}

.popcorn-section:hover  { transform: scale(1.03); }
.popcorn-section:active { transform: scale(0.96); }

.popcorn-section__balance {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.popcorn-section__label {
  font-family: var(--font-cjk-display);
  font-size: 14px;
  line-height: 20px;
  font-weight: var(--fw-regular);
  color: var(--neutral-white);
  white-space: nowrap;
}

.popcorn-section__icon {
  width: 20px;
  height: 20px;
  display: block;
  flex-shrink: 0;
}

.popcorn-section__chevron {
  width: 18px;
  height: 18px;
  display: block;
  flex-shrink: 0;
}

/* PopcornSection — After Login (popcorn + balance + deposit) */
.popcorn-section--logged-in {
  gap: 6px;
  padding: 0 4px 0 12px;
}

.popcorn-section__amount {
  font-family: var(--font-latin);
  font-size: 14px;
  line-height: 1;
  font-weight: var(--fw-medium);
  color: #eae9f7;
  text-align: right;
  /* Reserve a stable column wide enough for 5 digits + thousands separator
     ("99,999") so the pill doesn't grow/jump as the balance changes.
     tabular-nums keeps every digit the same width. */
  min-width: 3.6em;
  font-variant-numeric: tabular-nums;
}

.popcorn-section__deposit {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-pill);
  background: var(--yellow-500);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  padding: 0;
  cursor: pointer;
  flex-shrink: 0;
  transition: filter 150ms ease;
}

.popcorn-section__deposit:hover { filter: brightness(1.04); }

/* plus.svg is white-fill → recolor to yellow-ink via mask on ::before */
.popcorn-section__deposit::before {
  content: '';
  display: block;
  width: 16px;
  height: 16px;
  background-color: var(--yellow-ink);
  -webkit-mask: url('assets/icons/plus.svg') center / contain no-repeat;
          mask: url('assets/icons/plus.svg') center / contain no-repeat;
}

/* btn-login-signup — yellow pill */
.header__login-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 40px;
  padding: 0 20px;
  background: var(--yellow-500);
  color: var(--yellow-ink);
  border: none;
  border-radius: var(--radius-pill);
  font-family: var(--font-cjk-text);
  font-size: 14px;
  line-height: 20px;
  font-weight: var(--fw-medium);
  cursor: pointer;
  box-shadow: inset 0 1px 4px rgba(255, 255, 255, 0.9);
  transition: background 120ms var(--ease-out),
              box-shadow 120ms var(--ease-out);
}

.header__login-btn:hover {
  background: var(--yellow-400);
}

.header__login-btn:active {
  background: var(--yellow-600);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.16);
}

/* btn-notification (After Login) — plain 24px icon matching search / language:
   the bell is a recolorable mask (Neutral/300 → White on hover), the unread
   red-dot is a fixed ::after circle so it never recolors with the bell. The
   baked-in <img> is hidden — bell shape comes from notification_regular.svg. */
.header__notification-btn {
  position: relative;
  display: inline-block;
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}

.header__notification-btn img { display: none; }

/* Match the 語言↔通知 spacing (header__actions 24px); buttons group gap is 12px
   so add 12px to reach 24px before the popcorn pill — avatar gap stays 12px. */
.header__notification-btn { margin-right: 12px; }

.header__notification-btn::before {
  content: "";
  position: absolute;
  inset: 0;
  background-color: var(--neutral-300);
  /* 兩層 mask 相交:鈴鐺形狀 ∩「紅點周圍挖空圈」— 縫隙是真透明,
     不用深色 ring 模擬(透明 header 下 ring 會在亮背景現形)。*/
  -webkit-mask-image: url('assets/icons/notification_regular.svg'),
    radial-gradient(circle at calc(100% - 4px) 4px, transparent 6px, #000 6.5px);
  -webkit-mask-size: 24px 24px, 100% 100%;
  -webkit-mask-position: center, center;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-composite: source-in;
          mask-image: url('assets/icons/notification_regular.svg'),
    radial-gradient(circle at calc(100% - 4px) 4px, transparent 6px, #000 6.5px);
          mask-size: 24px 24px, 100% 100%;
          mask-position: center, center;
          mask-repeat: no-repeat;
          mask-composite: intersect;
  transition: background-color 200ms ease;
}
.header__notification-btn:hover::before { background-color: var(--neutral-white); }

/* Unread red dot — matches the SVG's circle (cx20 cy4 r4 in a 24 grid). The
   dark ring (header bg colour) recreates the gap the original baked-in icon had
   between the dot and the bell outline. */
.header__notification-btn::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #EF4444;
}
/* 全部已讀後隱藏 bell 紅點 */
.header__notification-btn.is-read::after { display: none; }

/* Avatar (After Login) — 40×40 圓形容器，內含用戶頭照
   未上傳時 fallback 到 assets/icons/avatar.svg */
.avatar {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-pill);
  border: none;
  padding: 0;
  background: none;
  cursor: pointer;
  flex-shrink: 0;
  overflow: hidden;
}

.avatar__img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  border-radius: inherit;
}

/* ─────────────────────────────────────────
   HEADER DROPDOWNS — language picker + profile menu
   Shared glass shell, anchored under the trigger button.
   .header__popover-anchor wraps trigger + dropdown so the
   dropdown can be position:absolute relative to the trigger.
   ───────────────────────────────────────── */

.header__popover-anchor {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.header-dropdown {
  /* position:fixed（非 absolute）：JS 一載入就把這顆選單 portal 到 <body> 尾端，
     absolute 會相對視窗、被 top:100% 推到頁面底下而撐出多餘捲動高度（短頁 footer
     下方憑空多出一段空白）。fixed 不計入頁面捲動高，也和 JS 打開時設的 fixed 一致。 */
  position: fixed;
  top: calc(100% + 16px);
  right: 0;
  background: rgba(19, 19, 19, 0.85);
  backdrop-filter: blur(calc(var(--lg-blur) * 1.5));        /* 12px — off the master --lg-blur knob */
  -webkit-backdrop-filter: blur(calc(var(--lg-blur) * 1.5));
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--radius-md);
  z-index: var(--z-dropdown);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 160ms ease, transform 160ms ease, visibility 160ms;
  /* Clip horizontally; scroll vertically when the menu is taller than the
     viewport. max-height is set inline by JS so it always fits below the
     trigger with a small bottom gap. */
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.07) transparent;
}

.header-dropdown::-webkit-scrollbar {
  width: 6px;
}
.header-dropdown::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.07);
  border-radius: 3px;
}
.header-dropdown::-webkit-scrollbar-thumb:hover {
  background: rgba(255, 255, 255, 0.14);
}
.header-dropdown::-webkit-scrollbar-track {
  background: transparent;
}

.header-dropdown.is-open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

/* ─── Language picker — premium glass popover ───
   A considered floating object, not a system menu. Double-bezel: the panel
   is the machined tray (16px radius, top-edge highlight, layered shadow) and
   each row is an inner core (12px radius) that floats inside the 6px padding,
   giving concentric curves. The active tick is driven ENTIRELY by the
   --active class (i18n.js owns it) via a pseudo-element, so the check FOLLOWS
   the chosen language instead of being welded to one row — the old bug. */
.header-dropdown--lang {
  width: 248px;
  padding: 6px;
  border-radius: var(--radius-xl);                       /* 16px outer tray */
  background: rgba(20, 20, 22, 0.72);
  backdrop-filter: var(--lg-frost-pop);            /* 24px — language popover (frost fallback; Chromium adds the bend in 52-…) */
  -webkit-backdrop-filter: var(--lg-frost-pop);
  border: 1px solid rgba(255, 255, 255, 0.10);
  box-shadow:
    0 20px 48px -16px rgba(0, 0, 0, 0.72),               /* ambient lift */
    0 4px 12px rgba(0, 0, 0, 0.32),
    inset 0 1px 0 rgba(255, 255, 255, 0.08);             /* machined top edge */
  transform-origin: top right;                           /* grow from the globe */
  transition:
    opacity 220ms cubic-bezier(0.32, 0.72, 0, 1),
    transform 220ms cubic-bezier(0.32, 0.72, 0, 1),
    visibility 220ms;
  /* the checkmark glyph, masked so it inherits the brand accent */
  --lang-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='11' viewBox='0 0 14 11' fill='none'%3E%3Cpath d='M1 5.5L5 9.5L13 1.5' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

.header-dropdown--lang:not(.is-open) {
  transform: translateY(-8px) scale(0.96);
}

.lang-dropdown__item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 48px;                                      /* ≥44px touch target */
  padding: 12px 14px;
  background: transparent;
  border: none;
  border-radius: var(--radius-lg);                       /* 12px inner core */
  font-family: var(--font-cjk-display);
  font-size: 15px;
  font-weight: 500;
  line-height: 20px;
  color: var(--text-secondary);
  text-align: left;
  cursor: pointer;
  transition:
    background-color 200ms cubic-bezier(0.32, 0.72, 0, 1),
    color 200ms cubic-bezier(0.32, 0.72, 0, 1);
}

.lang-dropdown__item + .lang-dropdown__item {
  margin-top: 2px;
}

.lang-dropdown__item:hover {
  background: rgba(255, 255, 255, 0.055);
  color: var(--text-primary);
}

.lang-dropdown__item:focus-visible {
  outline: none;
  background: rgba(255, 255, 255, 0.055);
  color: var(--text-primary);
  box-shadow: inset 0 0 0 1.5px rgba(255, 163, 63, 0.55);
}

/* Popup active row — a selected chip, not a coloured block: a whisper-soft
   warm plate defined by a crisp inset accent hairline. The tick + accent label
   carry the state. Scoped to the popup so the chip never leaks to the drawer. */
.header-dropdown--lang .lang-dropdown__item--active,
.header-dropdown--lang .lang-dropdown__item--active:hover {
  background: rgba(255, 163, 63, 0.07);
  box-shadow: inset 0 0 0 1px rgba(255, 163, 63, 0.22);
  color: var(--yellow-500);
  font-weight: 600;
}

/* Mobile drawer shares the [data-lang] active class but is a different surface:
   the active language reads via accent text alone — no chip, no ring, no tick. */
.mdrawer__link.lang-dropdown__item--active {
  color: var(--yellow-500);
  font-weight: 600;
}

.lang-dropdown__item-label {
  flex: 1;
  min-width: 0;
}

/* The old inline SVG tick was welded to the 繁體中文 row only, so it could
   never follow a switch to English. Retired in favour of the pseudo below. */
.lang-dropdown__check {
  display: none;
}

/* Checkmark slot on EVERY row (reserves width so labels never shift), painted
   only on the active row and cross-fading in when the language changes. */
.lang-dropdown__item::after {
  content: "";
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  background-color: var(--yellow-500);
  -webkit-mask: var(--lang-check) center / 14px 11px no-repeat;
  mask: var(--lang-check) center / 14px 11px no-repeat;
  opacity: 0;
  transform: scale(0.6);
  transition:
    opacity 220ms cubic-bezier(0.32, 0.72, 0, 1),
    transform 220ms cubic-bezier(0.32, 0.72, 0, 1);
}

.lang-dropdown__item--active::after {
  opacity: 1;
  transform: scale(1);
}

@media (prefers-reduced-motion: reduce) {
  .header-dropdown--lang,
  .lang-dropdown__item,
  .lang-dropdown__item::after {
    transition-duration: 1ms;
  }
  .header-dropdown--lang:not(.is-open) {
    transform: none;
  }
  .lang-dropdown__item::after {
    transform: none;
  }
}

/* ─── Profile menu (375 wide) ─── */
.header-dropdown--profile {
  width: 375px;
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 24px;
  /* Stronger glass — the wider panel needs more blur to feel rich
     against busy backgrounds. */
  background: rgba(19, 19, 19, 0.72);
  backdrop-filter: var(--lg-frost-profile);        /* 28px — profile popover (frost fallback; Chromium adds the bend in 52-…) */
  -webkit-backdrop-filter: var(--lg-frost-profile);
}

/* Clickable area that links to the user's own profile page.
   Negative margin lets the hover bg extend slightly past the head's
   normal content edges so the affordance reads as a contained tile. */
.profile-menu__head-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px;
  margin: -8px;
  border-radius: 8px;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  transition: background-color 150ms ease;
}

.profile-menu__head-row:hover {
  background: rgba(255, 255, 255, 0.05);
}

.profile-menu__head-row:hover .profile-menu__head-chevron {
  color: var(--text-primary);
  transform: translateX(2px);
}

.profile-menu__avatar {
  width: 64px;
  height: 64px;
  border-radius: var(--radius-pill);
  overflow: hidden;
  flex-shrink: 0;
}

.profile-menu__avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.profile-menu__info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.profile-menu__name {
  font-family: var(--font-cjk-text);
  font-weight: var(--fw-bold);
  font-size: 16px;
  line-height: 24px;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.profile-menu__role {
  font-family: var(--font-latin);
  font-size: 12px;
  line-height: 18px;
  color: var(--text-tertiary);
}

.profile-menu__head-chevron {
  width: 24px;
  height: 24px;
  color: var(--text-tertiary);
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color 150ms ease, transform 150ms ease;
}

.profile-menu__actions {
  display: flex;
  gap: 16px;
  margin-top: 20px;        /* extra room so head-row hover bg doesn't crowd the pills */
}

/* Action pills here use .btn .btn--quiet .btn--sm; stretch to fill row. */
.profile-menu__actions .btn { flex: 1; }

.profile-menu__section {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.profile-menu__section-title {
  font-family: var(--font-cjk-text);
  font-size: 14px;
  line-height: 25px;
  color: var(--text-tertiary);
}

.profile-menu__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.profile-menu__list-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 0;
  background: transparent;
  border: none;
  color: var(--text-primary);
  font-family: var(--font-cjk-text);
  font-size: 16px;
  line-height: 27px;
  text-align: left;
  width: 100%;
  cursor: pointer;
  transition: color 120ms ease;
}

.profile-menu__list-item:hover { color: var(--yellow-500); }
.profile-menu__list-item:hover .profile-menu__list-icon { color: var(--yellow-500); }

.profile-menu__list-item-inner {
  display: inline-flex;
  align-items: center;
  gap: 12px;
}

/* Icon glyph rendered via CSS mask so it inherits the parent's currentColor
   (so hover yellow flows from list-item color → icon bg). The SVG file path
   comes from the inline --icon CSS variable on each instance. */
.profile-menu__list-icon {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  display: inline-block;
  background-color: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
          mask: var(--icon) center / contain no-repeat;
  color: var(--text-primary);
  transition: background-color 120ms ease, color 120ms ease;
}

/* Smaller variant used inside .btn--quiet pills (e.g. 設定). */
.profile-menu__pill-icon {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  display: inline-block;
  background-color: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
          mask: var(--icon) center / contain no-repeat;
}

.profile-menu__list-value {
  font-family: var(--font-latin);
  font-size: 14px;
  color: var(--text-tertiary);
  flex-shrink: 0;
}

.profile-menu__divider {
  height: 1px;
  background: rgba(255, 255, 255, 0.08);
  border: none;
  margin: 0;
}

.profile-menu__version {
  font-family: var(--font-latin);
  font-size: 14px;
  line-height: 16px;
  color: #707177;
}

/* ─────────────────────────────────────────
   觀看歷史 DRAWER (logged-in)
   Right-aligned slide-out below the header. V1-style
   continue-watching list (poster + title + progress bar).
   Trigger: header [data-watch-history-trigger]; footer 更多 → 我的片庫.
   ───────────────────────────────────────── */

/* Header trigger icon — white on hover like the notification bell. The
   logged-in buttons group gaps 12px; add 12px so watch↔通知 reaches the
   24px header__actions icon rhythm (mirrors .header__notification-btn). */
.header__icon-btn--watch { margin-right: 12px; }
.header__icon-btn--watch:hover { background-color: var(--neutral-white); }

.watch-drawer {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  pointer-events: none;
  /* Clip the closed panel (translateX(100%)) — without this the parked
     drawer extends scrollWidth and the page gains phantom horizontal
     scroll on narrow windows. */
  overflow: hidden;
}
.watch-drawer.is-open { pointer-events: auto; }

/* Scrim starts below the header so the header stays clear (ref: 通知 drawer). */
.watch-drawer__scrim {
  position: absolute;
  inset: var(--header-height) 0 0 0;
  background: rgba(0, 0, 0, 0.45);
  opacity: 0;
  transition: opacity 280ms ease;
}
.watch-drawer.is-open .watch-drawer__scrim { opacity: 1; }

.watch-drawer__panel {
  position: absolute;
  top: var(--header-height);
  right: 0;
  /* Height hugs content (5 items + 更多); caps at viewport on short screens. */
  max-height: calc(100vh - var(--header-height));
  width: min(420px, 100%);
  display: flex;
  flex-direction: column;
  background: rgba(19, 19, 19, 0.96);
  backdrop-filter: blur(calc(var(--lg-blur) * 1.5));   /* 12px panel frost — off the master knob */
  -webkit-backdrop-filter: blur(calc(var(--lg-blur) * 1.5));
  border-left: 1px solid var(--border-subtle);
  /* Rounded bottom finish (panel floats above the viewport bottom). */
  border-bottom-left-radius: var(--radius-lg);
  border-bottom-right-radius: var(--radius-lg);
  /* Shadow ONLY when open — the closed panel parks off-canvas (translateX(100%))
     but a -24px/60px shadow still bleeds leftward into the viewport's right edge,
     darkening whatever sits there (invisible on dark sections, a grey vignette on
     the yellow zebra stripes). Gating it on .is-open keeps the closed drawer
     shadow-free on every page. */
  box-shadow: none;
  transform: translateX(100%);
  transition: transform var(--dur-panel-exit, 280ms) var(--ease-emphasized),
              box-shadow var(--dur-panel-exit, 280ms) var(--ease-emphasized);
}
.watch-drawer.is-open .watch-drawer__panel {
  transform: translateX(0);
  box-shadow: -24px 0 60px rgba(0, 0, 0, 0.5);
}

.watch-drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 24px 24px 16px;
  flex-shrink: 0;
}
.watch-drawer__title {
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: 22px;
  line-height: 1.3;
  color: var(--text-primary);
  margin: 0;
}
/* Circular close button with a faint X (ref: mobile profile menu close). */
.watch-drawer__close {
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  padding: 0;
  border: none;
  cursor: pointer;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.08);
  transition: background 200ms ease;
}
.watch-drawer__close:hover { background: rgba(255, 255, 255, 0.14); }
.watch-drawer__close-icon {
  width: 16px;
  height: 16px;
  background-color: var(--text-tertiary);
  -webkit-mask: url('assets/icons/close.svg') center / contain no-repeat;
          mask: url('assets/icons/close.svg') center / contain no-repeat;
}

.watch-drawer__list {
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 4px 24px 8px;
  display: flex;
  flex-direction: column;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.07) transparent;
}
.watch-drawer__list::-webkit-scrollbar { width: 6px; }
.watch-drawer__list::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.07); border-radius: var(--radius-pill); }

.watch-item {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 0;
  text-decoration: none;
  color: inherit;
}
.watch-item__cover {
  flex: 0 0 auto;
  width: 52px;
  aspect-ratio: 2 / 3;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--bg-secondary);
}
.watch-item__cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.watch-item__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.watch-item__title {
  font-family: var(--font-cjk-text);
  font-weight: var(--fw-semibold);
  font-size: 15px;
  line-height: 1.4;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color 200ms ease;
}
.watch-item:hover .watch-item__title { color: var(--yellow-500); }
.watch-item__progress {
  display: flex;
  align-items: center;
  gap: 12px;
}
.watch-item__bar {
  flex: 1;
  height: 4px;
  background: rgba(255, 255, 255, 0.10);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.watch-item__bar-fill {
  display: block;
  height: 100%;
  /* 品牌黃 + 光暈，與募資卡進度條 (.cocreation-card__bar-fill) 一致 */
  background: var(--yellow-500);
  box-shadow: 0 0 8px rgba(255, 163, 63, 0.35);
  border-radius: var(--radius-pill);
}
.watch-item__pct {
  flex: 0 0 auto;
  min-width: 34px;
  text-align: right;
  font-family: var(--font-cjk-text);
  font-size: 13px;
  color: var(--text-tertiary);
}

.watch-drawer__foot {
  flex-shrink: 0;
  padding: 16px 24px 24px;
}
/* Styling comes from .btn.btn--quiet.btn--md; only stretch to full width. */
.watch-drawer__more { width: 100%; }
.watch-drawer__more-arrow {
  width: 16px;
  height: 16px;
  background-color: currentColor;
  -webkit-mask: url('assets/icons/chevron_right.svg') center / 16px 16px no-repeat;
          mask: url('assets/icons/chevron_right.svg') center / 16px 16px no-repeat;
}

/* ─────────────────────────────────────────
   通知 DRAWER (logged-in)
   Right-aligned slide-out below the header — same shell as 觀看歷史 but
   FULL-HEIGHT & scrollable (notification feed). Trigger: 通知 bell.
   ───────────────────────────────────────── */
.notif-drawer {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  pointer-events: none;
}
.notif-drawer.is-open { pointer-events: auto; }

.notif-drawer__scrim {
  position: absolute;
  inset: var(--header-height) 0 0 0;
  background: rgba(0, 0, 0, 0.45);
  opacity: 0;
  transition: opacity 280ms ease;
}
.notif-drawer.is-open .notif-drawer__scrim { opacity: 1; }

.notif-drawer__panel {
  position: absolute;
  top: var(--header-height);
  right: 0;
  bottom: 0;                 /* full height, feed scrolls internally */
  width: min(420px, 100%);
  display: flex;
  flex-direction: column;
  background: rgba(19, 19, 19, 0.96);
  backdrop-filter: blur(calc(var(--lg-blur) * 1.5));   /* 12px panel frost — off the master knob */
  -webkit-backdrop-filter: blur(calc(var(--lg-blur) * 1.5));
  border-left: 1px solid var(--border-subtle);
  /* Shadow ONLY when open — a closed off-canvas drawer must not bleed its
     leftward shadow into the viewport's right edge (greys the yellow stripes).
     See the matching note in 10-drawer.css (watch-drawer). */
  box-shadow: none;
  transform: translateX(100%);
  transition: transform 320ms cubic-bezier(0.4, 0, 0.2, 1),
              box-shadow 320ms cubic-bezier(0.4, 0, 0.2, 1);
}
.notif-drawer.is-open .notif-drawer__panel {
  transform: translateX(0);
  box-shadow: -24px 0 60px rgba(0, 0, 0, 0.5);
}

.notif-drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 24px 24px 16px;
  flex-shrink: 0;
}
.notif-drawer__title {
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: 22px;
  line-height: 1.3;
  color: var(--text-primary);
  margin: 0;
}
.notif-drawer__close {
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  padding: 0;
  border: none;
  cursor: pointer;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.08);
  transition: background 200ms ease;
}
.notif-drawer__close:hover { background: rgba(255, 255, 255, 0.14); }
.notif-drawer__close-icon {
  width: 16px;
  height: 16px;
  background-color: var(--text-tertiary);
  -webkit-mask: url('assets/icons/close.svg') center / contain no-repeat;
          mask: url('assets/icons/close.svg') center / contain no-repeat;
}

/* Head right-side actions: 全部已讀 + close. */
.notif-drawer__head-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}
.notif-drawer__mark-read {
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  font-family: var(--font-cjk-text);
  font-size: 14px;
  font-weight: var(--fw-medium);
  color: var(--text-secondary);
  transition: color 200ms ease;
}
.notif-drawer__mark-read:hover { color: var(--yellow-500); }

.notif-drawer__list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 4px 24px 16px;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.07) transparent;
}
.notif-drawer__list::-webkit-scrollbar { width: 6px; }
.notif-drawer__list::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.07); border-radius: var(--radius-pill); }

/* ── Notification item — [unread dot] [thumb] [title + time] ── */
.notif-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 0;
  text-decoration: none;
  color: inherit;
}
.notif-item + .notif-item { border-top: 1px solid var(--border-subtle); }

/* Unread dot — always reserves the column; coloured only when unread. */
.notif-item__dot {
  flex: 0 0 auto;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: transparent;
}
.notif-item--unread .notif-item__dot { background: var(--error-500); }

.notif-item__thumb {
  flex: 0 0 auto;
  width: 48px;
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--bg-secondary);
}
.notif-item__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Brand (ztor) / user thumbs — icon centred on a dark tile. */
.notif-item__thumb--brand,
.notif-item__thumb--user {
  display: grid;
  place-items: center;
  background: var(--bg-tertiary);
}
/* ztor wordmark via mask so it renders neutral (品牌黃在此太搶眼). */
.notif-item__brand-logo {
  width: 72%;
  height: 100%;
  background-color: var(--neutral-400);
  -webkit-mask: url('assets/icons/ztor_logo.svg') center / contain no-repeat;
          mask: url('assets/icons/ztor_logo.svg') center / contain no-repeat;
}
.notif-item__thumb--user img { width: 52%; height: 52%; opacity: 0.55; }

.notif-item__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.notif-item__title {
  font-family: var(--font-cjk-text);
  font-weight: var(--fw-regular);
  font-size: 14px;
  line-height: 1.45;
  color: var(--text-primary);
  transition: color 200ms ease;
}
.notif-item:hover .notif-item__title { color: var(--yellow-500); }
.notif-item__time {
  font-family: var(--font-cjk-text);
  font-size: 12px;
  color: var(--text-tertiary);
}

/* ─────────────────────────────────────────
   個人頁 (profile.html) — header(info+bio) + tabs + 貼文流
   貼文流為「文字左 + 媒體右」版型;action 重用 .post-card__stat/__bookmark/__more.
   ───────────────────────────────────────── */
/* 內文欄位 960（置中）：container = 960 + 左右各 48 內距 = 1056。 */
.section--profile .container { max-width: 1056px; }
.profile-head {
  display: flex;
  gap: var(--space-8);
  align-items: flex-start;
  padding: var(--space-2) 0 36px;
}
.profile-head__avatar {
  flex: 0 0 auto;
  width: 132px;
  height: 132px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--bg-tertiary);
  display: grid;
  place-items: center;
}
.profile-head__avatar img { width: 54%; height: 54%; opacity: 0.5; }
.profile-head__info { flex: 1; min-width: 0; padding-top: 6px; }
.profile-head__title-row { display: flex; align-items: baseline; gap: var(--space-3); }
.profile-head__name {
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: 28px;
  line-height: 1.25;
  color: var(--text-primary);
  margin: 0;
}
.profile-head__role {
  font-family: var(--font-cjk-text);
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--text-tertiary);
}
.profile-head__stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  margin-top: 14px;
  font-family: var(--font-cjk-text);
  font-size: 14px;
  color: var(--text-tertiary);
}
.profile-stat strong {
  color: var(--text-primary);
  font-weight: var(--fw-bold);
  margin-right: var(--space-1);
}
.profile-head__online {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: var(--space-3) 0 0;
  font-family: var(--font-cjk-text);
  font-size: 13px;
  color: var(--text-tertiary);
}
.profile-head__online-icon {
  width: 14px;
  height: 14px;
  background-color: var(--text-tertiary);
  -webkit-mask: url('assets/icons/history.svg') center / contain no-repeat;
          mask: url('assets/icons/history.svg') center / contain no-repeat;
}
.profile-head__bio {
  max-width: 780px;
  margin: var(--space-4) 0 0;
  font-family: var(--font-cjk-text);
  font-weight: var(--fw-regular);
  font-size: 15px;
  line-height: 1.7;
  color: var(--text-secondary);
}

/* tabs */
.profile-tabs {
  display: flex;
  gap: var(--space-1);
  border-bottom: 1px solid var(--border-subtle);
}
.profile-tab {
  position: relative;
  padding: 14px var(--space-5);
  border: none;
  background: none;
  cursor: pointer;
  font-family: var(--font-cjk-text);
  font-size: 15px;
  font-weight: var(--fw-medium);
  color: var(--text-tertiary);
  transition: color 200ms ease;
}
.profile-tab:hover { color: var(--text-secondary); }
.profile-tab--active { color: var(--yellow-500); }
.profile-tab--active::after {
  content: "";
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: -1px;
  height: 2px;
  background: var(--yellow-500);
}

/* 貼文流 — 文字左 + 媒體右 */
.profile-feed { display: flex; flex-direction: column; }
.profile-post {
  display: flex;
  gap: 28px;
  align-items: flex-start;
  padding: 28px 0;
}
.profile-post + .profile-post { border-top: 1px solid var(--border-subtle); }
.profile-post__body { flex: 1; min-width: 0; }
.profile-post__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.profile-post__avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--bg-tertiary);
}
.profile-post__author {
  font-family: var(--font-cjk-text);
  font-size: 14px;
  font-weight: var(--fw-medium);
  color: var(--text-primary);
}
.profile-post__date {
  font-family: var(--font-cjk-text);
  font-size: 13px;
  color: var(--text-tertiary);
}
.profile-post__title {
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: 20px;
  line-height: 1.4;
  color: var(--text-primary);
  margin: 0 0 10px;
}
.profile-post__excerpt {
  margin: 0 0 var(--space-4);
  font-family: var(--font-cjk-text);
  font-size: 14px;
  line-height: 1.7;
  color: var(--text-secondary);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.profile-post__actions {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}
.profile-post__translate {
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  font-family: var(--font-cjk-text);
  font-size: 13px;
  color: var(--text-tertiary);
  transition: color 150ms ease;
}
.profile-post__translate:hover { color: var(--text-secondary); }
.profile-post__actions-spacer { flex: 1; }
.profile-post__media {
  flex: 0 0 auto;
  position: relative;
  width: 208px;
  aspect-ratio: 16 / 10;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--bg-tertiary);
}
.profile-post__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Play badge for video posts (pure CSS, no icon dependency). */
.profile-post__media--video::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
}
.profile-post__media--video::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-40%, -50%);
  width: 0;
  height: 0;
  border-left: 13px solid #fff;
  border-top: 8px solid transparent;
  border-bottom: 8px solid transparent;
}

/* ─────────────────────────────────────────
   DEV-ONLY: header state toggle
   切換 body[data-auth] 控制顯示 Before/After Login header
   出貨前移除（這支 class 與底部 button）
   ───────────────────────────────────────── */

body[data-auth="logged-out"] .header[data-state="after-login"],
body[data-auth="logged-in"]  .header[data-state="before-login"] {
  display: none;
}

/* ─── DEV-ONLY · 出貨前整段移除 ─────────────────────────
   Header auth-state toggle styles. Pairs with the
   [data-dev-only] button in index.html and the matching JS
   IIFE. Remove all three together. */
.dev-toggle {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: var(--z-dev);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 40px;
  padding: 0 var(--space-4);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: var(--radius-pill);
  background: rgba(20, 20, 20, 0.85);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  color: var(--text-primary);
  font-family: var(--font-cjk-text);
  font-size: 13px;
  font-weight: var(--fw-medium);
  cursor: pointer;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

.dev-toggle::before {
  content: '';
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--neutral-400);
}

body[data-auth="logged-in"] .dev-toggle::before { background: var(--yellow-500); }

.dev-toggle:hover { background: rgba(30, 30, 30, 0.9); }

/* Phone: keep the dev toggle available (same users as desktop) but relocate
   it to the top-right under the compact header, so it never collides with the
   floating bottom nav. Compact size; below the drawer, above page content. */
@media (max-width: 767.98px) {
  /* the top-right corner now belongs to the sticky filter nav, and the bottom centre to
     the floating bottom nav — park the dev toggle on the right edge ABOVE the bottom-nav
     reserve, where it collides with neither. (DEV-ONLY — removed before shipping anyway.) */
  .dev-toggle {
    top: auto;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 112px);
    right: 12px;
    height: 32px;
    padding: 0 var(--space-3);
    font-size: 12px;
    z-index: 950;
  }
}
/* ─── DEV-ONLY · 3-chip state bar (dev-state.js) ───────────
   auth · saved address · saved card. Same parking + glass as .dev-toggle. */
.dev-bar {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: var(--z-dev);
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 4px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: var(--radius-pill);
  background: rgba(20, 20, 20, 0.85);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}
.dev-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 32px;
  padding: 0 var(--space-3);
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-tertiary);
  font-family: var(--font-cjk-text);
  font-size: 12px;
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: background 160ms ease, color 160ms ease;
}
.dev-chip__dot {
  width: 7px;
  height: 7px;
  border-radius: var(--radius-pill);
  background: var(--neutral-400);
  transition: background 160ms ease;
}
.dev-chip.is-on { color: var(--text-primary); background: rgba(255, 255, 255, 0.07); }
.dev-chip.is-on .dev-chip__dot { background: var(--yellow-500); }
.dev-chip:hover { color: var(--text-primary); }

@media (max-width: 767.98px) {
  .dev-bar {
    bottom: calc(env(safe-area-inset-bottom, 0px) + 112px);
    right: 12px;
    z-index: 950;
  }
  .dev-chip { height: 30px; padding: 0 var(--space-2); font-size: 11px; }
}

/* collapsed-by-default (2026-07-16): with the exchange action chips the bar
   outgrew its pill and smeared across content. One DEV toggle; chips wrap
   inside a capped panel only while open. */
.dev-chip { white-space: nowrap; }
.dev-chip--toggle { color: var(--text-secondary); font-weight: var(--fw-bold); letter-spacing: 0.08em; }
.dev-bar.is-collapsed .dev-chip:not(.dev-chip--toggle) { display: none; }
.dev-bar:not(.is-collapsed) {
  flex-wrap: wrap;
  justify-content: flex-end;
  max-width: min(92vw, 560px);
}
/* ─── /DEV-ONLY ─────────────────────────────────────────── */

/* ─────────────────────────────────────────
   FILMS RAIL — 第三章·電影庫預覽
   橫向 rail with 3 tabs (最新影片 / 現正熱播 / Ztor. 精選).
   Each panel = a horizontally scrollable track of .film-card.
   Nav arrows reuse hero-carousel glass style: hover-to-show.
   ───────────────────────────────────────── */

.films-rail {
  position: relative;
}

/* Tabs row — sits between section header and the rail */
.films-rail__tabs {
  display: flex;
  gap: var(--space-1);
  margin-top: calc(var(--space-10) * -1 + var(--space-5));
  margin-bottom: var(--space-8);
  border-bottom: 1px solid var(--border-subtle);
}

.films-rail__tab {
  position: relative;
  padding: var(--space-3) 18px;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  color: var(--text-tertiary);
  font-family: var(--font-cjk-text);
  font-size: 14px;
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: color 200ms ease, border-color 200ms ease;
}

.films-rail__tab:hover { color: var(--text-secondary); }

.films-rail__tab[aria-selected="true"] {
  color: var(--yellow-500);
  border-bottom-color: var(--yellow-500);
}

/* Panel — only the selected one renders the track. Fade-in on switch
   so tab activations feel responsive. */
.films-rail__panel { display: none; }
.films-rail__panel.is-active {
  display: block;
  animation: filmsRailFade 260ms ease both;
}

@keyframes filmsRailFade {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Track — horizontal scroll container, snap to start.
   --rail-cols controls how many cards perfectly fill the row at the
   default desktop width. Card width uses calc() so the row always
   spans flush from the section's left edge to the right.
   Modifier .films-rail__track--ranked drops cols to 5 (Top 5 shelf). */
.films-rail__track {
  --rail-cols: 6;
  --rail-gap: 20px;
  display: flex;
  gap: var(--rail-gap);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none; /* Firefox */
  padding: var(--space-1) 2px var(--space-4);
  margin: -4px -2px 0;   /* offset padding so cards align with section edges */
}
.films-rail__track::-webkit-scrollbar { display: none; }

/* Ranked rails reserve extra left room so the first card's huge "1" (which
   sits at left:-8px) clears the track's overflow clip instead of being cut.
   The matching negative margin keeps the cards aligned to the section edge. */
.films-rail__track--ranked {
  --rail-cols: 5;
  /* The #1 digit overhangs the first card's left edge (rank is at left:-8px),
     and the track's overflow-x clip would cut it. Plain padding-left doesn't
     help — scroll-snap (mandatory + align:start) snaps the first card to the
     scrollport edge and eats the padding. So:
       margin-left:-24  → move the clip edge var(--space-6) left of the container content
       scroll-padding-left:24 → snap the first card back to the content edge
                                (cards stay aligned with the section header)
     Net: the clip sits left of the digit's overhang, so it's no longer cut,
     and the row is still aligned. -24 stays within the container padding, so
     no horizontal overflow on ≤1440 screens. */
  margin-left: -24px;
  padding-left: var(--space-6);
  scroll-padding-left: 24px;
}

/* Nav arrows — same glass + hover-to-show as hero carousel.
   Vertical position is set dynamically via JS to align with the poster's
   vertical center (the active panel's first card). The .is-enabled class
   is toggled by JS based on whether scrolling further in that direction
   is still possible — only enabled buttons reveal on hover. */
.films-rail__nav {
  position: absolute;
  /* `top` is set inline by JS once the rail mounts / on resize / on tab change. */
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  border-radius: var(--radius-pill);
  border: none;
  padding: 0;
  cursor: pointer;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  background: linear-gradient(0deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.18) 2.45%,
    rgba(255, 255, 255, 0) 126.14%);
  box-shadow:
    rgba(255, 255, 255, 0.9) 1.1px 2.2px 0.5px -1.8px inset,
    rgba(255, 255, 255, 0.9) -1px -2.2px 0.5px -1.8px inset,
    rgba(0, 0, 0, 0.10) 2px 3px 2px 0 inset;
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  transition: opacity 250ms ease, background 200ms ease;
}

.films-rail:hover .films-rail__nav.is-enabled,
.films-rail__nav.is-enabled:focus-visible {
  opacity: 1;
  pointer-events: auto;
}

.films-rail__nav.is-enabled:hover {
  background: linear-gradient(0deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.30) 2.45%,
    rgba(255, 255, 255, 0) 126.14%);
}

.films-rail__nav--prev { left: -22px; }
.films-rail__nav--next { right: -22px; }

.films-rail__nav img {
  width: 22px;
  height: 22px;
  display: block;
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.5));
}

/* ─────────────────────────────────────────
   FILMS RAIL — RESPONSIVE
   Phone  : max-width 767.98px
   Tablet : 768px – 1023.98px
   ───────────────────────────────────────── */

/* ── Touch devices: hide nav arrows (swipe is the paradigm) ─────────────── */
@media (hover: none) {
  .films-rail__nav { display: none; }
}

/* ── Tablet (768px – 1023.98px) ─────────────────────────────────────────── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  /* Tabs: tighten horizontal padding slightly */
  .films-rail__tab { padding: 10px 14px; }

  /* Track: tighter gap on tablet */
  .films-rail__track { --rail-gap: 16px; }

  /* Nav arrows: stay in but pull in slightly — 1024 container is narrower */
  .films-rail__nav--prev { left: -16px; }
  .films-rail__nav--next { right: -16px; }
}

/* ── Phone (≤767.98px) ──────────────────────────────────────────────────── */
@media (max-width: 767.98px) {
  /* Tabs row: scroll horizontally if tabs overflow (many tabs, narrow viewport).
     Bottom border stays full-width via negative margin compensation. */
  .films-rail__tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    /* Pull flush with the container edges so active border reads full-width */
    margin-left: calc(var(--container-padding-x) * -1);
    margin-right: calc(var(--container-padding-x) * -1);
    padding-left: var(--space-4);
    padding-right: var(--space-4);
    margin-top: calc(var(--space-8) * -1 + var(--space-4));
    margin-bottom: var(--space-5);
  }
  .films-rail__tabs::-webkit-scrollbar { display: none; }

  /* Tab items: smaller text, tighter padding; keep 44px touch target height */
  .films-rail__tab {
    padding: 11px 14px;
    font-size: 13px;
    white-space: nowrap;
    /* Explicit min height so border-box includes the tap target */
    min-height: 44px;
  }

  /* Track: tighter gap; smaller cards from film-card responsive rules */
  .films-rail__track {
    --rail-gap: 12px;
    /* Pull the track flush with section edges for immersive edge-to-edge scroll */
    margin-left: calc(var(--container-padding-x) * -1);
    margin-right: calc(var(--container-padding-x) * -1);
    padding-left: var(--space-4);
    padding-right: var(--space-4);
    /* Add right padding so the last card doesn't clip at the edge */
    scroll-padding-left: var(--space-4);
  }

  /* Ranked track on phone: adjust the ranked overhang offset */
  .films-rail__track--ranked {
    margin-left: calc(var(--container-padding-x) * -1);
    padding-left: var(--space-8);
    scroll-padding-left: var(--space-8);
  }

  /* Panel fade-in — same arrival tier as desktop (2026-07-03: the 200ms/4px
     "snappier on phone" downgrade read cheap next to every other reveal) */
  .films-rail__panel.is-active {
    animation: filmsRailFadeMobile var(--dur-xs, 450ms) var(--ease-settle, ease-out) both;
  }
}

@keyframes filmsRailFadeMobile {
  from { opacity: 0; transform: translateY(var(--reveal-y-sm-m, 10px)); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── Small phone (≤479.98px) ────────────────────────────────────────────── */
@media (max-width: 479.98px) {
  .films-rail__tabs { padding-left: var(--space-3); }
  .films-rail__track {
    --rail-gap: 10px;
    padding-left: var(--space-3);
    padding-right: var(--space-3);
    scroll-padding-left: var(--space-3);
  }
  .films-rail__track--ranked {
    padding-left: var(--space-6);
    scroll-padding-left: var(--space-6);
  }
}

/* ── Reduced-motion ─────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .films-rail__panel.is-active { animation: none; opacity: 1; }
  @keyframes filmsRailFadeMobile { from { opacity: 1; } to { opacity: 1; } }
}

/* ─────────────────────────────────────────
   FILM CARD
   2:3 poster + popcorn-price badge (br) + body (title / meta / score)
   Hover: poster lifts + bottom darken overlay reveals.
   Variant .film-card--ranked: huge outlined number behind the poster (Top 5).
   ───────────────────────────────────────── */

/* CONSISTENCY FIX (2026-06-16): the poster is a FIXED intrinsic width, so the
   card is the SAME size on every page. It used to be computed from --rail-cols
   ÷ the track's container width, which DRIFTED — 172px inside the home page's
   1440 container vs 191px on full-bleed library pages (same component, same
   viewport). The rail now simply fits however many 184px cards fit; the card
   itself never changes size. (Mobile gets a smaller token below — responsive,
   not drift.) */
.film-card {
  --card-w: 184px;
  position: relative;
  flex: 0 0 var(--card-w);
  width: var(--card-w);
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
}

.film-card__poster {
  position: relative;
  aspect-ratio: 2 / 3;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--bg-tertiary);
  border: 1px solid var(--border-subtle);
  transition: transform 220ms var(--ease-out), border-color 220ms var(--ease-out);
  z-index: 1; /* above ranked number */
}

.film-card:hover .film-card__poster {
  transform: translateY(-4px);
  border-color: var(--border-default);
}

.film-card__poster img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Hover darken — reveals price badge and stays subtle on idle */
.film-card__poster::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    rgba(0, 0, 0, 0) 55%,
    rgba(0, 0, 0, 0.55) 100%);
  opacity: 0.6;
  transition: opacity 250ms ease;
  pointer-events: none;
}

.film-card:hover .film-card__poster::after { opacity: 1; }

/* Popcorn price — bottom-right glass pill (mirrors 1.0) */
.film-card__price {
  position: absolute;
  right: 8px;
  bottom: 8px;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2) var(--space-1) 6px;
  border-radius: var(--radius-pill);
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(var(--lg-blur));               /* 8px chip frost — off the master knob */
  -webkit-backdrop-filter: blur(var(--lg-blur));
  font-family: var(--font-cjk-text);
  font-size: 12px;
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  z-index: 2;
}

.film-card__price-icon {
  width: 14px;
  height: 14px;
  display: inline-block;
  background-color: var(--neutral-white);
  -webkit-mask: url('assets/icons/popcorn_single.svg') center / contain no-repeat;
          mask: url('assets/icons/popcorn_single.svg') center / contain no-repeat;
}

/* ── 繼續觀看(resume)變體 — 租借中影片:海報底部黃進度條 + 左下剩餘時間.
   沿用桌機 2/3 海報比例(.film-card__poster);ref: mobile-screening.html.
   剩餘時間 badge 鏡像 .film-card__price(改放左下). ── */
.film-card__resume-time {
  position: absolute;
  left: 8px;
  bottom: 8px;
  z-index: 2;
  padding: var(--space-1) 10px;
  border-radius: var(--radius-pill);
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(var(--lg-blur));               /* 8px chip frost — off the master knob */
  -webkit-backdrop-filter: blur(var(--lg-blur));
  font-family: var(--font-cjk-text);
  font-size: 12px;
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
}
.film-card__progress {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 4px;
  background: rgba(255, 255, 255, 0.25);
  z-index: 2;
}
.film-card__progress-fill {
  display: block;
  height: 100%;
  background: var(--yellow-500);
}

/* Body — title + meta row (year/tag on left, score on right) */
.film-card__body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: var(--space-3) var(--space-1) 0;
}

.film-card__title {
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: 15px;
  line-height: 1.35;
  color: var(--text-primary);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.film-card__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  min-height: 28px; /* match score ring height to keep rail rhythm */
}

.film-card__meta-text {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-cjk-text);
  font-size: 11px;
  color: var(--text-tertiary);
  min-width: 0;
}

.film-card__meta-tag {
  display: inline-block;
  padding: 1px 6px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-secondary);
  white-space: nowrap;
  flex-shrink: 0;
}

/* Ranked variant — outlined number sits behind the poster on the left.
   The padding-left reserves a slot for the digit so part of it stays
   visible to the left of the poster. Sized to also fit double-digit
   "10" — the leading "1" reads, the "0" tucks behind the poster.

   container-type makes each ranked card a size container, so the digit
   (font-size/offsets in cqi below) scales WITH the card. Fixed px here
   breaks the moment the card shrinks — the digits detach from their
   posters and float over the section. */
.film-card--ranked {
  /* Wider than a normal card by exactly the rank gutter, so the POSTER inside
     is still 184px — matches every other rail (the gutter holds the big #1). */
  --card-w: calc(184px + 56px);
  padding-left: var(--space-14);   /* px on purpose: an element's own cqi can't reference itself */
  container-type: inline-size;
}

.film-card__rank {
  position: absolute;
  left: -9.5cqi;
  bottom: -8.5cqi;    /* lower-left: base sits just below the poster bottom so it
                         reads in the strip under the image. 現正熱播 offsets via
                         a scoped rule. */
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-black);
  /* cqi resolves against the card's CONTENT box (card minus the 56px
     digit slot). 127cqi reproduces the original 240px at the 245px
     reference card, then scales with it. */
  font-size: 127cqi;
  line-height: 0.85;
  color: transparent;
  /* SOLID grey stroke (was white-22% translucent). Pre-composited to the same
     tone the translucent stroke rendered on the dark section (~#404040 over
     neutral-900), but OPAQUE — so the overlapping 1+0 strokes of "10" no longer
     double up into a darker patch at their crossing. color-mix keeps it
     token-based; the plain --neutral-600 line is the no-color-mix fallback. */
  -webkit-text-stroke: 2px var(--neutral-600);
  -webkit-text-stroke: 2px color-mix(in srgb, var(--neutral-600) 74%, var(--neutral-900));
          text-stroke: 2px var(--neutral-600);
          text-stroke: 2px color-mix(in srgb, var(--neutral-600) 74%, var(--neutral-900));
  pointer-events: none;
  user-select: none;
  z-index: 0;
  letter-spacing: -0.05em;
}
.film-card__rank--double {
  left: -44.5cqi;
  /* +2px over the base -0.08em nudges the "0" right so it clears the "1"
     (calc keeps the em base scaling with the card, adds a fixed gap). */
  letter-spacing: calc(-0.08em + 2px);
}

/* ─────────────────────────────────────────
   FILM CARD — RESPONSIVE
   Phone  : max-width 767.98px
   Tablet : 768px – 1023.98px
   Container queries handle the card when it appears in different
   layouts (narrow grid cell vs wide list vs carousel slot).
   ───────────────────────────────────────── */

/* ── Container query setup ──────────────────────────────────────────────────
   The card's OUTER wrapper (the scrollable track or grid) must carry
   container-type. film-card--ranked already sets it on itself; here we
   add it to the base card so child container queries can fire inside any
   layout that hosts .film-card. We use a local --card-cq flag so callers
   that need to opt out can override. */

/* Standard card: make it a size container when hosted inside a known grid/
   rail wrapper. Since the card itself IS the container root, child queries
   (@container) reference the card's own inline size. */
.film-card {
  container-type: inline-size;
  container-name: film-card;
}

/* ── Container: narrow card (≤120px) — ultra-compact carousel slot ──────── */
@container film-card (max-width: 120px) {
  .film-card__body { padding: var(--space-2) var(--space-1) 0; gap: var(--space-1); }
  .film-card__title { font-size: 11px; }
  .film-card__meta { display: none; } /* too narrow; title alone is enough */
  .film-card__price { right: 4px; bottom: 4px; font-size: 10px; padding: 2px 5px 2px var(--space-1); }
  .film-card__price-icon { width: 11px; height: 11px; }
}

/* ── Container: small card (121px – 160px) — tight grid / sidebar ───────── */
@container film-card (min-width: 121px) and (max-width: 160px) {
  .film-card__body { padding: var(--space-2) var(--space-1) 0; gap: var(--space-1); }
  .film-card__title { font-size: 12px; -webkit-line-clamp: 2; }
  .film-card__meta-tag { display: none; } /* drop tag, keep year + score */
}

/* ── Container: normal card (161px+) — default carousel / library ───────── */
@container film-card (min-width: 161px) {
  /* Title: 2-line clamp on narrower real estate (phone-sized card in a grid) */
  .film-card__title { -webkit-line-clamp: 2; }
}

/* ── Touch devices: price badge is always visible (no hover) ────────────── */
@media (hover: none) {
  /* Darken overlay fixed at a comfortable reading opacity — no hover needed */
  .film-card__poster::after { opacity: 0.75; }
  /* Price badge always visible at full opacity */
  .film-card__price { opacity: 1; }
  /* Kill the lift transform on touch tap; the browser's :active handles feedback */
  .film-card:active .film-card__poster { transform: none; }
}

/* ── Tablet (768px – 1023.98px) ─────────────────────────────────────────── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  /* Slightly tighter card on tablet so more fit in the rail */
  .film-card { --card-w: 160px; }

  /* Ranked: scale down the digit gutter proportionally */
  .film-card--ranked {
    --card-w: calc(160px + 48px);
    padding-left: var(--space-12);
  }
}

/* ── Phone (≤767.98px) ──────────────────────────────────────────────────── */
@media (max-width: 767.98px) {
  /* Smaller poster fits 2+ cards comfortably in phone viewport */
  .film-card { --card-w: 136px; }

  /* Title: show 2 lines on phone — the single-line clamp loses CJK titles */
  .film-card__title {
    font-size: 13px;
    -webkit-line-clamp: 2;
  }

  /* Body tighter on phone */
  .film-card__body { padding: var(--space-2) var(--space-1) 0; gap: var(--space-1); }

  /* Meta text smaller but still legible */
  .film-card__meta-text { font-size: 10px; gap: var(--space-1); }

  /* Price badge: always visible on phone (no hover) */
  .film-card__poster::after { opacity: 0.75; }
  .film-card__price {
    opacity: 1;
    font-size: 11px;
    padding: 3px 6px 3px 5px;
  }

  /* Ranked: tighter gutter on phone so digit doesn't overwhelm */
  .film-card--ranked {
    --card-w: calc(136px + 40px);
    padding-left: var(--space-10);
  }

  /* Resume-time badge: stays readable at phone size */
  .film-card__resume-time { font-size: 11px; padding: 3px var(--space-2); }
}

/* ── Small phone (≤479.98px) ────────────────────────────────────────────── */
@media (max-width: 479.98px) {
  .film-card { --card-w: 120px; }

  .film-card__title { font-size: 12px; }

  .film-card--ranked {
    --card-w: calc(120px + 36px);
    padding-left: 36px;
  }
}

/* ── Skeleton / loading state ───────────────────────────────────────────── */
.film-card--skeleton .film-card__poster {
  background: var(--skeleton-surface);
  animation: film-card-skeleton-pulse 1.4s ease-in-out infinite;
}
.film-card--skeleton .film-card__title,
.film-card--skeleton .film-card__meta {
  border-radius: var(--radius-sm);
  background: var(--skeleton-surface);
  color: transparent;
  animation: film-card-skeleton-pulse 1.4s ease-in-out infinite;
}
.film-card--skeleton .film-card__title { height: 15px; width: 80%; }
.film-card--skeleton .film-card__meta  { height: 12px; width: 55%; }

@keyframes film-card-skeleton-pulse {
  0%, 100% { opacity: 0.45; }
  50%       { opacity: 0.8;  }
}

/* ── Missing image fallback ─────────────────────────────────────────────── */
.film-card__poster--no-image {
  background: var(--bg-tertiary);
  display: flex;
  align-items: center;
  justify-content: center;
}
.film-card__poster--no-image::before {
  content: '';
  width: 32px;
  height: 32px;
  background-color: var(--text-tertiary);
  -webkit-mask: url('assets/icons/film_placeholder.svg') center / contain no-repeat;
          mask: url('assets/icons/film_placeholder.svg') center / contain no-repeat;
  opacity: 0.4;
}

/* ── Sold-out / unavailable state ───────────────────────────────────────── */
.film-card--unavailable .film-card__poster { opacity: 0.45; filter: grayscale(0.6); }
.film-card--unavailable .film-card__price {
  background: rgba(0, 0, 0, 0.7);
  color: var(--text-tertiary);
}

/* ── Reduced-motion ─────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .film-card__poster,
  .film-card__poster::after {
    transition: none;
  }
  .film-card--skeleton .film-card__poster,
  .film-card--skeleton .film-card__title,
  .film-card--skeleton .film-card__meta {
    animation: none;
    opacity: 0.55;
  }
}

/* ─────────────────────────────────────────
   ZTOR SCORE — 評分 ring (Metacritic-style conic arc)
   Restored from the web build's ring (replaces the liquid glass orb).
   Arc length = score%; the tier sets the colour:
     --gold   → 85–100  iridescent rainbow arc + glow (premium "光")
     --green  → 70–84   success
     --orange → 50–69   warning
     --red    → 0–49    error
     --empty  → 評分不足 → faint full track + "--"
   Markup (unchanged): <span class="ztor-score ztor-score--green" style="--score:77">
                         <span class="ztor-score__value">77</span></span>
   Animation: index.html §3.5 counts BOTH --score (arc) AND the numeral
   from 0 → target on viewport entry (replayable). Without JS the inline
   --score renders the ring filled — the count-up is enhancement only.
   ───────────────────────────────────────── */

.ztor-score {
  --score: 0;
  --ring-color: var(--neutral-500);
  --track-color: rgba(255, 255, 255, 0.10);
  --inner-bg: var(--bg-primary);   /* matches the section bg behind the ring */
  position: relative;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background:
    conic-gradient(var(--ring-color) calc(var(--score) * 1%), var(--track-color) 0);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

/* Inner cut-out — leaves a 2px ring of the conic showing. */
.ztor-score::before {
  content: '';
  position: absolute;
  inset: 2px;
  background: var(--inner-bg);
  border-radius: 50%;
}

.ztor-score__value {
  position: relative;
  z-index: 1;
  /* Afacad (main's score numeral face, decision 2026-06-11) — taller,
     slightly condensed digits that read as a "score". */
  font-family: 'Afacad', var(--font-latin);
  font-size: 12px;
  font-weight: var(--fw-bold);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--text-primary);
}

.ztor-score--green  { --ring-color: var(--success-500); }
.ztor-score--orange { --ring-color: var(--warning-500); }
.ztor-score--red    { --ring-color: var(--error-500); }

/* ── Top tier (85–100) · iridescent rainbow arc + glow ──────────────────
   Two stacked conics: the full rainbow sits underneath; a track conic ON
   TOP turns transparent up to the score% (revealing the rainbow along the
   filled arc) then paints --track-color over the remainder — so it works
   at any score. A slow hue rotation (--ztor-spin, registered so it can
   interpolate) gives the living "光" shimmer; a soft two-colour glow adds
   the premium feel. Falls back to a static rainbow where @property is
   unsupported; reduced-motion freezes the shimmer. */
@property --ztor-spin { syntax: '<angle>'; inherits: false; initial-value: 0deg; }

.ztor-score--gold {
  background:
    conic-gradient(transparent calc(var(--score) * 1%), var(--track-color) 0),
    conic-gradient(from var(--ztor-spin, 0deg),
      #ff4d6d, #ff9a3d, #ffe14d, #4dffa1, #36d1ff, #9b6bff, #ff4d8d, #ff4d6d);
  box-shadow:
    0 0 6px rgba(255, 140, 90, 0.45),
    0 0 13px rgba(140, 100, 255, 0.28);
}
/* The shimmer only runs while the orb is in view (index.html adds .is-live
   on viewport entry, removes it on exit) — static rainbow everywhere else,
   so a long page never spins a dozen off-screen conics. */
.ztor-score--gold.is-live { animation: ztor-spin 5s linear infinite; }
@keyframes ztor-spin { to { --ztor-spin: 360deg; } }

@media (prefers-reduced-motion: reduce) {
  .ztor-score--gold.is-live { animation: none; }
}

.ztor-score--empty {
  --score: 100;
  --ring-color: rgba(255, 255, 255, 0.10);
}
.ztor-score--empty .ztor-score__value {
  color: var(--text-disabled);
  font-size: 12px;
  letter-spacing: -0.05em;
}

/* On a yellow surface stripe, a dark inner disc keeps the white numeral
   legible (the ring still reads via its tier colour). */
.section-bg-surface .ztor-score { --inner-bg: var(--bg-secondary); }

/* ─────────────────────────────────────────
   COMMUNITY — 第四章·社群
   3-col grid of article preview cards. Each card pairs a 1920×1024
   landscape cover with a short headline/desc and an author meta row.
   Image spec is fixed (no portrait variants on the homepage for now);
   later, post-detail / community list pages can introduce portrait
   covers + blur-fill, or a true masonry layout.
   ───────────────────────────────────────── */

.community-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-6);
  align-items: start;   /* tiles keep their natural height — landscape stays short, portrait stands taller; tops align */
}

.community-card {
  display: flex;
  flex-direction: column;
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: transform 250ms ease, border-color 250ms ease;
}

.community-card:hover {
  transform: translateY(-4px);
  border-color: var(--border-default);
}

/* Cover — SAME treatment as the community-page feed: a fixed-max-height stage,
   contain (never cropped). LANDSCAPE is untouched — it fills the width at its
   own (short) height. Only PORTRAIT is capped at --cover-max and shrunk to fit,
   with the blurred copy behind filling its side gaps (ambient bleed). The sharp
   image drives the tile height, so landscape tiles aren't letterboxed. */
.community-card__cover {
  position: relative;
  width: 100%;
  overflow: hidden;
  background: #000;
  line-height: 0;
}
.community-card__cover-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.15);
  filter: blur(24px) brightness(0.6);
  z-index: 0;
}
.community-card__cover-img {
  position: relative;             /* in flow → drives the tile height */
  z-index: 1;
  display: block;
  width: 100%;
  height: auto;
  max-height: var(--cover-max, clamp(240px, 30vh, 340px));   /* portrait cap */
  object-fit: contain;            /* whole image, never cropped */
  margin-inline: auto;
  transition: transform 500ms ease;
}
.community-card:hover .community-card__cover-img {
  transform: scale(1.03);
}

/* Header — author chip left, date right. Sits between the cover and body,
   inherits the social-post pattern from 1.0 (avatar + name + date). */
.community-card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) 22px 0;
}

.community-card__date {
  font-family: var(--font-cjk-text);
  font-size: 12px;
  color: var(--text-tertiary);
  white-space: nowrap;
  flex-shrink: 0;
}

/* Body — title → desc → optional chips → meta row (stats + bookmark). */
.community-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3) 22px 18px;
  flex: 1;
}

/* Topic chips — subdued, low-contrast pills that surface campaign /
   programme affiliations (e.g. AI 創作 posts pointing to specific
   contests). Designed to be readable but not compete with title/author. */
.community-card__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 2px;
}

.community-card__chip {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-subtle);
  background: rgba(255, 255, 255, 0.03);
  color: var(--text-tertiary);
  font-family: var(--font-cjk-text);
  font-size: 11px;
  font-weight: var(--fw-regular);
}

.community-card__title {
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: 19px;
  line-height: 1.4;
  color: var(--text-primary);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  transition: color 200ms ease;
}

.community-card:hover .community-card__title {
  color: var(--yellow-500);
}

.community-card__desc {
  font-family: var(--font-cjk-text);
  /* Figma spec: Body/Small/zh · Regular · 14/23 · Text/Tertiary */
  font-weight: var(--fw-regular);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body-sm);
  color: var(--text-tertiary);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Meta row — stats cluster left, bookmark action right. Pinned to the
   bottom of the body via margin-top: auto so action rows align across
   cards in the same grid row regardless of title/desc/chip length. */
.community-card__meta {
  margin-top: auto;
  padding-top: 6px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.community-card__author {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  font-family: var(--font-cjk-text);
  font-size: 12px;
  color: var(--text-tertiary);
}

/* Avatar — small circular user image. Falls back to a neutral tile bg
   while the image loads. */
.community-card__avatar {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-pill);
  background: var(--bg-tertiary);
  object-fit: cover;
  display: block;
  flex-shrink: 0;
}

.community-card__author-text {
  font-family: var(--font-cjk-text);
  font-size: 12px;
  color: var(--text-tertiary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.community-card__author-name {
  color: var(--text-secondary);
  font-weight: var(--fw-medium);
}

/* Stats cluster — heart/comment counts + bookmark + share icons. */
.community-card__stats {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  color: var(--text-tertiary);
}

.community-card__stat {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-family: var(--font-cjk-text);
  font-size: 12px;
  color: var(--text-tertiary);
}

.community-card__stat-icon,
.community-card__action-icon {
  width: 20px;
  height: 20px;
  display: inline-block;
  background-color: currentColor;
  flex-shrink: 0;
}

.community-card__stat-icon--heart   { -webkit-mask: url('assets/icons/heart.svg')   center / contain no-repeat;
                                               mask: url('assets/icons/heart.svg')   center / contain no-repeat; }
.community-card__stat-icon--comment { -webkit-mask: url('assets/icons/comment.svg') center / contain no-repeat;
                                               mask: url('assets/icons/comment.svg') center / contain no-repeat; }
.community-card__stat-icon--share   { -webkit-mask: url('assets/icons/share.svg')   center / contain no-repeat;
                                               mask: url('assets/icons/share.svg')   center / contain no-repeat; }
.community-card__action-icon--bookmark { -webkit-mask: url('assets/icons/bookmark_regular.svg') center / contain no-repeat;
                                                  mask: url('assets/icons/bookmark_regular.svg') center / contain no-repeat; }

/* Active (bookmarked) state — toggle the .is-bookmarked class on the
   button to swap to the filled variant and highlight in brand yellow. */
.community-card__action.is-bookmarked .community-card__action-icon--bookmark {
  -webkit-mask: url('assets/icons/bookmark_active.svg') center / contain no-repeat;
          mask: url('assets/icons/bookmark_active.svg') center / contain no-repeat;
  background-color: var(--yellow-500);
}

/* Action buttons are clickable bare icons — hover lifts contrast. */
.community-card__action {
  background: none;
  border: none;
  padding: var(--space-1);
  margin: -4px;
  cursor: pointer;
  color: var(--text-tertiary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color 150ms ease;
}

.community-card__action:hover { color: var(--text-secondary); }


/* ── Community card · OVERLAY variant (UGC-safe) ───────────────────────────
   These photos are USER-uploaded — any aspect ratio. So we NEVER crop to a
   fixed box: the card follows the photo's natural ratio (news-hero model) and
   the .media-glass panel overlays the bottom. The grid is MASONRY so the mixed
   heights read as an intentional feed (Instagram/Pinterest), not broken rows.
   📐 PHOTO SPEC (uploads): any ratio; recommend ≥1080px on the long edge, JPG
      ≤ ~700 KB. Keep key subject/faces out of the very bottom (the text sits
      there). One class on the grid (.community-grid--overlay) converts every card. */
.community-grid--overlay {
  display: block;
  column-count: 3;
  column-gap: var(--space-6);
}
@media (max-width: 900px) { .community-grid--overlay { column-count: 2; } }
@media (max-width: 600px) { .community-grid--overlay { column-count: 1; } }
.community-grid--overlay .community-card {
  position: relative;
  display: block;
  width: 100%;
  margin-bottom: var(--space-6);
  break-inside: avoid;              /* keep a card whole within its masonry column */
  background: var(--bg-card);       /* surface the text flows onto below a short photo */
}
.community-grid--overlay .community-card__cover { position: relative; aspect-ratio: auto; line-height: 0; }
/* Image drives its own height at NATURAL ratio — no object-fit crop, ever. */
.community-grid--overlay .community-card__cover img { position: relative; width: 100%; height: auto; }
/* The text panel FLOWS (not absolute → never clips). A negative margin lifts it
   onto the photo's lower edge for the glass dissolve; whatever doesn't fit on a
   short (landscape) photo simply continues onto the card surface below. */
.community-grid--overlay .community-card__overlay {
  position: relative;
  z-index: 1;
  margin-top: -132px;                 /* ride up onto the photo's lower band */
  padding: var(--space-20) 18px 18px;            /* top ~80px = the blur dissolve zone over the photo */
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.community-grid--overlay .community-card__header { padding: 0; }
.community-grid--overlay .community-card__body { flex: 0 0 auto; gap: 6px; padding: 0; }
.community-grid--overlay .community-card__desc {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* Text rides the scrim → force light; the scrim guarantees contrast. */
.community-grid--overlay .community-card__title { color: #fff; }
.community-grid--overlay .community-card:hover .community-card__title { color: var(--yellow-500); }
.community-grid--overlay .community-card__desc,
.community-grid--overlay .community-card__date,
.community-grid--overlay .community-card__author,
.community-grid--overlay .community-card__stats,
.community-grid--overlay .community-card__stat,
.community-grid--overlay .community-card__action { color: rgba(255, 255, 255, 0.85); }
.community-grid--overlay .community-card__author-name { color: #fff; }
/* Chips → glass pills on the photo. */
.community-grid--overlay .community-card__chip {
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.30);
  color: #fff;
}
/* Avatar ring so it reads on any photo. */
.community-grid--overlay .community-card__avatar {
  box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.5);
}

/* ─────────────────────────────────────────
   RESPONSIVE — COMMUNITY GRID + CARDS
   ───────────────────────────────────────── */

/* ── Tablet (768–1023) ── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  .community-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-5);
  }

  .community-grid--overlay {
    column-count: 2;
    column-gap: var(--space-5);
  }
}

/* ── Phone (<768) ── */
@media (max-width: 767.98px) {
  /* Grid: single column */
  .community-grid {
    grid-template-columns: 1fr;
    gap: var(--space-4);
  }

  /* Masonry overlay grid: single column */
  .community-grid--overlay {
    column-count: 1;
    column-gap: 0;
  }

  /* Cover image: relax portrait cap on narrow screens so tall images
     don't waste space — let them breathe within a sane ceiling. */
  .community-card__cover-img {
    max-height: clamp(180px, 56vw, 260px);
  }

  /* Header row: tighten padding to match card body rhythm */
  .community-card__header {
    padding: var(--space-3) var(--space-4) 0;
    gap: var(--space-2);
  }

  /* Body padding: breathe on narrow but stay inset */
  .community-card__body {
    padding: var(--space-3) var(--space-4) var(--space-4);
    gap: var(--space-2);
  }

  /* Title: step down one notch so long CJK titles wrap cleanly */
  .community-card__title {
    font-size: var(--fs-h5);
    line-height: 1.45;
  }

  /* Meta row: let it wrap on very narrow screens */
  .community-card__meta {
    flex-wrap: wrap;
    gap: var(--space-2);
  }

  /* Action buttons: expand hit area to ≥ 44px on touch */
  .community-card__action {
    padding: var(--space-3);
    margin: calc(-1 * var(--space-3));
  }

  /* Stats gap: tighter on phone */
  .community-card__stats {
    gap: var(--space-3);
  }

  /* Overlay variant overlay block: slightly shallower ride-up on narrow */
  .community-grid--overlay .community-card__overlay {
    margin-top: -100px;
    padding: 60px var(--space-4) var(--space-4);
  }
}

/* ── Small phone (<480) ── */
@media (max-width: 480px) {
  .community-card__header {
    padding: var(--space-3) var(--space-3) 0;
  }

  .community-card__body {
    padding: var(--space-3) var(--space-3) var(--space-4);
  }

  /* Desc: already 1-line clamp, allow wrap if clamp is removed by JS */
  .community-card__desc {
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
  }

  /* Author text: truncate more aggressively */
  .community-card__author {
    max-width: 60%;
  }
}

/* ── Reduced motion ── */
@media (prefers-reduced-motion: reduce) {
  .community-card,
  .community-card__cover-img {
    transition: none;
  }
}

/* ─────────────────────────────────────────
   COMMUNITY PAGE — 社群貼文流 (community.html)
   V1 /posts structure in V2 dress: a single centred post feed (~720) under a
   visual tab bar. Each post = author row → title → cover → body (更多 expand)
   → action row (♥ 留言 分享 + 收藏). Reuses the community-card stat icons.
   ───────────────────────────────────────── */

/* Centred content column — narrow feed per L (long-form posts ~720). */
.post-feed {
  max-width: 720px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

/* Sticky tab bar — full-bleed dark/blur background that pins below the header
   on scroll; the tabs themselves are centred to the feed width (720 + the
   48px container gutter each side, so they line up with the post cards). */
.community-tabbar {
  position: sticky;
  top: var(--header-height);
  z-index: 20;
  margin-bottom: var(--space-5);
  background: rgba(10, 10, 10, 0.82);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border-subtle);
}

/* ── MOBILE: the tab bar sheds the full-bleed strip and wears the SAME liquid-glass
   pill as every other page's nav (45-sticky-tabs recipe: --lg-fill lens + --lg-frost
   + --glass-shadow + hairline rim; refraction via liquid-glass.js — the selector is
   in REFRACT_SELECTORS, consumed mobile-only in 52-liquid-glass-refraction.css).
   Always-glass: the "original" (in-flow) and "snapped" (stuck) states are the same
   material, matching the rest of the site. The strip's sliding underline is a strip
   idiom — inside the pill, active = the glass-tabs chip highlight instead.
   Desktop (≥768) keeps the full-bleed strip + underline untouched. */
@media (max-width: 767.98px) {
  .community-tabbar {
    background: none;
    border-bottom: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .community-tabbar .community-rail {
    display: flex;
    justify-content: center;
  }
  .community-tabbar .feed-tabs {
    isolation: isolate;
    /* HUG the tabs — width:max-content self-sizes even when the rail's display
       is overridden (the logged-out grid/block rule outranks the flex above);
       margin-inline centres it in that case. max-width + scroll = EN-locale safety. */
    width: max-content;
    margin-inline: auto;
    max-width: 100%;
    overflow-x: auto;
    scrollbar-width: none;
    gap: 2px;
    padding: 5px 8px;                          /* proven snapped-pill metrics (45-sticky-tabs) */
    border: var(--glass-border);
    border-color: rgba(255, 255, 255, 0.10);
    border-radius: var(--radius-pill);
    box-shadow: var(--glass-shadow);
    -webkit-backdrop-filter: var(--lg-frost);
            backdrop-filter: var(--lg-frost);
  }
  .community-tabbar .feed-tabs::-webkit-scrollbar { display: none; }
  .community-tabbar .feed-tabs::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;
    background: var(--lg-fill);                /* the white-5% lens — same material as 我的投票 */
  }
  .community-tabbar .feed-tabs::after { display: none; }   /* strip underline off inside the pill */
  /* Tabs = FULL .glass-tabs__item parity (25-glass-tabs.css, phone values) —
     same type, same metrics, same chip. Keep in sync with that file. */
  .community-tabbar .feed-tab {
    flex: 0 0 auto;
    height: 44px;
    padding: 0 var(--space-5);
    border-radius: var(--radius-pill);
    white-space: nowrap;
    font-family: var(--font-cjk-display);
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-regular);
    color: var(--text-secondary);
    transition: background-color 160ms ease, color 160ms ease;
  }
  .community-tabbar .feed-tab:hover { color: var(--text-primary); }
  /* Active = the COMPLETE yellow-ghost glass chip (= .glass-tabs__item--active):
     gradient + GLOWING YELLOW INSET RIM + dark inner depth. The rim is what makes
     the chip read as lit glass, not flat paint — never ship the gradient alone.
     NO per-item backdrop — the pill carries the one glass layer. */
  .community-tabbar .feed-tab--active,
  .community-tabbar .feed-tab--active:hover {
    color: var(--yellow-500);
    font-weight: var(--fw-bold);
    background: linear-gradient(0deg,
      rgba(255, 163, 63, 0) 0%,
      rgba(255, 163, 63, 0.18) 2.45%,
      rgba(255, 163, 63, 0) 126.14%);
    box-shadow:
      rgba(255, 163, 63, 0.9) 1.1px 2.2px 0.5px -1.8px inset,
      rgba(255, 163, 63, 0.9) -1px -2.2px 0.5px -1.8px inset,
      rgba(0, 0, 0, 0.10) 2px 3px 2px 0 inset;
  }
}
/* Small phone ≤480: track glass-tabs' tighten exactly (25-glass-tabs.css). */
@media (max-width: 480px) {
  .community-tabbar .feed-tab {
    padding: 0 var(--space-4);
    font-size: var(--fs-caption);
  }
}
/* Tabs + feed share one centred rail. The rail owns the 720 width + gutters.
   Logged-out it becomes a 2-col grid (feed + register aside) so the whole
   pair shifts left (Threads-style) while the tabs stay aligned to the feed
   column above the post cards. */
.community-rail {
  max-width: calc(720px + var(--container-padding-x) * 2);
  margin: 0 auto;
  padding: 0 var(--container-padding-x);
}
body[data-auth="logged-out"] .community-rail {
  max-width: calc(720px + 32px + 320px + var(--container-padding-x) * 2);
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: var(--space-8);
  align-items: start;
}



/* Shop sub-nav panels (商品/套組/拍賣): the [hidden] attribute must hide the
   grid, but .shop-grid's `display:grid` (author CSS) out-ranks the UA
   `[hidden]{display:none}`. This wins it back so inactive panels stay hidden. */
.shop-grid[hidden] { display: none !important; }


/* SKELETON LOADER -> extracted to css/components/ds-skeleton.css (build inlines it). */

/* ── Register aside — logged-out only. A low-key panel beside the feed that
   nudges sign-up (Threads-style). Quiet surface + subtle border + ghost CTA
   so it guides without competing with the feed. Sticky as you scroll. ── */
.community-aside {
  position: sticky;
  top: calc(var(--header-height) + 84px);
  align-self: start;
  background: var(--bg-secondary);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: 28px var(--space-6);
}
.register-card__title {
  font-family: var(--font-cjk-display);
  font-size: 19px;
  line-height: 1.4;
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  text-align: center;
}
.register-card__body {
  margin-top: 10px;
  font-family: var(--font-cjk-text);
  font-size: 14px;
  line-height: 1.65;
  color: var(--text-secondary);
}
.register-card__cta {
  margin-top: var(--space-5);
  width: 100%;
}
.register-card__note {
  margin-top: 14px;
  font-family: var(--font-cjk-text);
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-tertiary);
  text-align: center;
}
.register-card__note a {
  color: var(--text-secondary);
  text-decoration: none;
}
.register-card__note a:hover { color: var(--text-primary); }

/* Narrow viewports: drop back to single-column feed, hide the aside (it's a
   nice-to-have guide, the feed is primary). */
@media (max-width: 1100px) {
  body[data-auth="logged-out"] .community-rail {
    display: block;
    max-width: calc(720px + var(--container-padding-x) * 2);
  }
  .community-aside { display: none !important; }
}

/* Post card — boxed feed item (V1-style): surface bg + subtle border + radius. */
.post-card {
  display: flex;
  flex-direction: column;
  padding: var(--space-6);
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
}

/* Author row */
.post-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: 14px;
}
.post-card__author {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: inherit;
  min-width: 0;
}
.post-card__avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}
.post-card__author-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.post-card__author-name {
  font-family: var(--font-cjk-text);
  font-size: 14px;
  font-weight: var(--fw-medium);
  color: var(--text-primary);
}
.post-card__date {
  font-family: var(--font-cjk-text);
  font-size: 12px;
  color: var(--text-tertiary);
}
.post-card__more {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: none;
  background: none;
  border-radius: var(--radius-pill);
  color: var(--text-tertiary);
  cursor: pointer;
  transition: color 150ms ease, background-color 150ms ease;
}
.post-card__more:hover { color: var(--text-primary); background: var(--white-alpha-8); }

/* Title */
.post-card__title {
  margin: 0 0 14px;
  font-family: var(--font-cjk-display);
  font-size: 24px;
  line-height: 1.4;
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}

/* Cover — fixed 16:9 frame (Figma Library v4: 618:348). Any aspect fits via
   blur-fill: a blurred, zoomed copy of the image fills the frame behind a fully
   contained foreground copy. So portrait posters / vertical video thumbs show
   complete and centred, with their own colour bleeding to the edges. */
/* Fixed-HEIGHT stage, never cropped: the sharp image drives the stage height
   (capped at --cover-max), so a LANDSCAPE still fills the width at its own
   height, and a PORTRAIT stands tall up to the cap — shrunk to fit, never over-
   shrunk. The blurred copy behind fills whatever the sharp image leaves (the
   side gaps on portraits, top/bottom on ultra-wides) with the photo's own light
   — ambient bleed, no dead bars, no crop. */
.post-card__cover {
  position: relative;
  width: 100%;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: #000;
  line-height: 0;
}
.post-card__cover-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.15);          /* hide blur edge bleed */
  filter: blur(28px) brightness(0.6);
}
.post-card__cover-img {
  position: relative;              /* in flow → drives the stage height */
  z-index: 1;
  display: block;
  width: 100%;
  height: auto;
  max-height: var(--cover-max, clamp(420px, 62vh, 600px));  /* portrait cap */
  object-fit: contain;             /* whole image, never cropped */
  margin-inline: auto;
  transition: transform 300ms ease;
}
.post-card:hover .post-card__cover-img { transform: scale(1.02); }

/* Video state (capability for Type=Video / Short Video): centred play button
   over the cover. Add .post-card__cover--video on the cover to enable. */
.post-card__cover--video::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 56px;
  height: 56px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: rgba(10, 10, 10, 0.55)
    url('assets/icons/media-play.svg') center / 22px no-repeat;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  pointer-events: none;
}

/* Body — 3 lines by default, then 更多 expands it with a calm animated height
   (the JS sets the exact px target; this is the Animation Director's spec —
   240–300ms ease-out, a single quiet reveal on a feed element). */
.post-card__body {
  margin: var(--space-4) 0 0;
  font-family: var(--font-cjk-text);
  font-size: 15px;
  line-height: 1.8;                /* CJK long-form reading — denser glyphs need more leading than Latin */
  letter-spacing: 0.01em;          /* a hair of tracking so the characters breathe */
  color: var(--text-secondary);
  max-height: calc(1.8em * 3);     /* 3-line clamp — kept in sync with the line-height */
  overflow: hidden;
  transition: max-height 160ms cubic-bezier(0.32, 0.72, 0, 1);   /* collapse — exit faster than entry */
}
.post-card.is-expanded .post-card__body {
  max-height: 240em;               /* no-JS fallback ceiling; JS sets the exact height */
  transition: max-height 240ms cubic-bezier(0.23, 1, 0.32, 1);   /* expand — calm ease-out entry */
}
/* Calm feed element — respect reduced motion: resize instantly, no grow. */
@media (prefers-reduced-motion: reduce) {
  .post-card__body { transition: none; }
}

/* 更多 / 收合 toggle — quiet text button, hidden when there's nothing to clip. */
.post-card__expand {
  align-self: flex-start;
  margin-top: 6px;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0;
  border: none;
  background: none;
  font-family: var(--font-cjk-text);
  font-size: 14px;
  color: var(--text-tertiary);
  cursor: pointer;
  transition: color 150ms ease;
}
.post-card__expand:hover { color: var(--text-primary); }
.post-card__expand-chev {
  width: 14px;
  height: 14px;
  background-color: currentColor;
  -webkit-mask: url('assets/icons/chevron_right.svg') center / contain no-repeat;
          mask: url('assets/icons/chevron_right.svg') center / contain no-repeat;
  transform: rotate(90deg);
  transition: transform 200ms ease;
}
.post-card.is-expanded .post-card__expand-chev { transform: rotate(-90deg); }

/* Action row */
.post-card__actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 18px;
}
.post-card__stats {
  display: inline-flex;
  align-items: center;
  gap: var(--space-5);
}
.post-card__stat,
.post-card__bookmark {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: var(--space-1);
  margin: -4px;
  border: none;
  background: none;
  font-family: var(--font-cjk-text);
  font-size: 13px;
  color: var(--text-tertiary);
  cursor: pointer;
  transition: color 150ms ease;
}
.post-card__stat:hover,
.post-card__bookmark:hover { color: var(--text-secondary); }
.post-card__stat-icon,
.post-card__bookmark-icon {
  width: 20px;
  height: 20px;
  display: inline-block;
  background-color: currentColor;
  flex-shrink: 0;
}
.post-card__stat-icon--heart   { -webkit-mask: url('assets/icons/heart.svg')   center / contain no-repeat;
                                          mask: url('assets/icons/heart.svg')   center / contain no-repeat; }
.post-card__stat-icon--comment { -webkit-mask: url('assets/icons/comment.svg') center / contain no-repeat;
                                          mask: url('assets/icons/comment.svg') center / contain no-repeat; }
.post-card__stat-icon--share   { -webkit-mask: url('assets/icons/share.svg')   center / contain no-repeat;
                                          mask: url('assets/icons/share.svg')   center / contain no-repeat; }
.post-card__bookmark-icon      { -webkit-mask: url('assets/icons/bookmark_regular.svg') center / contain no-repeat;
                                          mask: url('assets/icons/bookmark_regular.svg') center / contain no-repeat; }

/* ─────────────────────────────────────────
   RESPONSIVE — COMMUNITY PAGE / POST FEED
   NOTE: Original @media (max-width: 768px) block replaced + expanded below.
   ───────────────────────────────────────── */

/* ── Phone (<768) ── */
@media (max-width: 767.98px) {
  /* Feed: full-width rail with tighter gap */
  .post-feed {
    max-width: 100%;
    gap: var(--space-4);
  }

  /* Rail: collapse padding to standard container gutter */
  .community-rail {
    padding: 0 var(--space-4);
  }

  /* Post card: less padding on narrow screens */
  .post-card {
    padding: var(--space-4);
    border-radius: var(--radius-md);
  }

  /* Author head: allow wrap if name is very long */
  .post-card__head {
    gap: var(--space-2);
    margin-bottom: var(--space-3);
  }

  /* Author name */
  .post-card__author-name {
    font-size: var(--fs-caption);
  }

  /* Title: was 24px → step to 18px */
  .post-card__title {
    font-size: 18px;
    line-height: 1.4;
    margin-bottom: var(--space-3);
  }

  /* Cover: allow max-height to breathe more on phone */
  .post-card__cover-img {
    max-height: clamp(200px, 62vw, 420px);
  }

  /* Body text: already 15px / 1.8 — keep but tighten clamp to 3 lines */
  .post-card__body {
    margin-top: var(--space-3);
  }

  /* Actions row: wrap when many stats won't fit on one line */
  .post-card__actions {
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-4);
  }

  /* Stats row: tighter gap */
  .post-card__stats {
    gap: var(--space-4);
  }

  /* Stat/bookmark: 44px touch targets */
  .post-card__stat,
  .post-card__bookmark {
    padding: var(--space-3);
    margin: calc(-1 * var(--space-3));
    min-height: 44px;
  }

  /* Tab bar: tighten padding */
  .feed-tab {
    padding: 10px var(--space-3);
    font-size: var(--fs-caption);
  }

  /* More (⋯) button: 44px touch target */
  .post-card__more {
    width: 44px;
    height: 44px;
  }
}

/* ── Small phone (<480) ── */
@media (max-width: 480px) {
  .post-card {
    padding: var(--space-3);
  }

  .community-rail {
    padding: 0 var(--space-3);
  }

  /* Avatar slightly smaller on very narrow */
  .post-card__avatar {
    width: 34px;
    height: 34px;
  }
}

/* ─────────────────────────────────────────
   NEWS — 第五章·新聞與公告
   Magazine layout: 1 hero card (image-led, larger) on the left + 3 list
   cards (horizontal compact, image-left/content-right) stacked on the
   right. No social stats — news is editorial chrome, not user posts.
   Each card carries:  tag (yellow) → title → optional excerpt → meta
   (source · date).
   ───────────────────────────────────────── */

.news-grid {
  display: grid;
  /* 左頭條略窄、右側清單較寬 → 頭條維持比例縮小、右欄 fill，兩欄等高
     minmax(0, …) lets the fr columns shrink below their content's
     intrinsic width — without it the list cards force the page to
     overflow horizontally on narrow windows. */
  grid-template-columns: minmax(0, 4fr) minmax(0, 5fr);
  gap: var(--space-6);
}

/* Below ~1080 the two columns can't both breathe — stack them. */
@media (max-width: 1080px) {
  .news-grid { grid-template-columns: 1fr; }
}

.news-card {
  display: flex;
  text-decoration: none;
  color: inherit;
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: transform 250ms ease, border-color 250ms ease;
}

.news-card:hover {
  transform: translateY(-4px);
  border-color: var(--border-default);
}

/* Hero variant — the photo owns the whole card; the body is a glass
   panel that dissolves upward into the image (progressive blur:
   backdrop-filter faded by a gradient mask — masking runs after the
   filter, so the blur itself melts away rather than ending on a seam.
   Refs: joshwcomeau.com/css/backdrop-filter, kennethnym.com progressive
   blur, devslovecoffee Apple progressive blur). */
.news-card--hero {
  flex-direction: column;
  position: relative;
  /* full row height: the right column's three list cards set the row,
     the hero matches them edge to edge (aspect-ratio here would defeat
     the grid stretch and leave a hole under the card) */
  height: 100%;
}
@media (max-width: 1080px) {
  /* stacked: no sibling column to match — the photo's ratio drives */
  .news-card--hero { height: auto; aspect-ratio: 1920 / 1340; }
}

.news-card--hero .news-card__cover {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
}
.news-card--hero .news-card__cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.news-card--hero .news-card__body {
  position: absolute;
  inset: auto 0 0 0;
  isolation: isolate;   /* keeps the glass pseudos above the cover, below the text */
  padding: var(--space-16) 26px 26px;  /* tall top padding IS the dissolve zone */
  gap: var(--space-3);
}
/* The glass: blur strongest at the foot, gone by the panel's top edge. */
.news-card--hero .news-card__body::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -2;
  backdrop-filter: var(--lg-frost-media);           /* 22px media-card frost — one knob */
  -webkit-backdrop-filter: var(--lg-frost-media);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, rgba(0,0,0,0.5) 30%, black 62%);
          mask-image: linear-gradient(to bottom, transparent 0%, rgba(0,0,0,0.5) 30%, black 62%);
  pointer-events: none;
}
/* The tint: quiet ink ramp so the title reads on any photograph. */
.news-card--hero .news-card__body::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(to bottom,
    transparent 0%,
    rgba(10, 10, 10, 0.30) 34%,
    rgba(10, 10, 10, 0.62) 100%);
  pointer-events: none;
}
/* No backdrop-filter (old engines): the tint alone carries legibility. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .news-card--hero .news-card__body::after {
    background: linear-gradient(to bottom,
      transparent 0%,
      rgba(10, 10, 10, 0.62) 30%,
      rgba(10, 10, 10, 0.88) 100%);
  }
}

.news-card--hero .news-card__title {
  font-size: 24px;
  line-height: 1.35;
}

.news-card--hero .news-card__excerpt {
  -webkit-line-clamp: 3;
}

/* List variant — divider-only style, no card chrome. Image left
   (≈ 40% width), content right. Cards stack inside .news-list with
   subtle horizontal dividers between them. */
.news-card--list {
  flex-direction: row;
  align-items: stretch;
  background: transparent;
  border: none;
  border-radius: 0;
  overflow: visible;
  gap: 18px;
  padding: var(--space-5) 0;
}

.news-card--list:hover {
  transform: none;
  border-color: transparent;
}

.news-card--list .news-card__cover {
  flex: 0 0 40%;
  aspect-ratio: 1920 / 1024;
  border-radius: var(--radius-md);
  overflow: hidden;
}

.news-card--list .news-card__body {
  flex: 1;
  padding: var(--space-1) 0;
  gap: 10px;
  justify-content: center;
}

.news-card--list .news-card__title {
  font-size: 17px;
  line-height: 1.4;
}

/* One-line excerpt under the title for editorial breathing room. */
.news-card--list .news-card__excerpt {
  display: -webkit-box;
  -webkit-line-clamp: 1;
  margin: 0;
}

/* Cover (shared) */
.news-card__cover {
  position: relative;
  overflow: hidden;
  background: var(--bg-tertiary);
}

.news-card__cover img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 500ms ease;
}

.news-card:hover .news-card__cover img { transform: scale(1.03); }

/* Body (shared) */
.news-card__body {
  display: flex;
  flex-direction: column;
  flex: 1;
}

.news-card__tag {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  gap: var(--space-2);
  padding: 6px var(--space-3);
  border: 0.8px solid rgba(255, 163, 63, 0.3);
  border-radius: var(--radius-pill);
  background:
    linear-gradient(90deg, rgba(255, 163, 63, 0.12), rgba(255, 163, 63, 0.12)),
    rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  color: var(--yellow-500);
  font-family: var(--font-cjk-text);
  font-size: 12px;
  line-height: 18px;
  font-weight: var(--fw-medium);
}

.news-card__title {
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  transition: color 200ms ease;
}

/* Hover affordance — title turns brand yellow on either hero or list card. */
.news-card:hover .news-card__title {
  color: var(--yellow-500);
}

.news-card__excerpt {
  font-family: var(--font-cjk-text);
  /* Figma spec: Body/Small/zh · Regular · 14/23 · Text/Tertiary */
  font-weight: var(--fw-regular);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body-sm);
  color: var(--text-tertiary);
  margin: 0;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.news-card__meta {
  margin-top: auto;
  padding-top: var(--space-3);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-cjk-text);
  font-size: 12px;
  color: var(--text-tertiary);
}

.news-card__meta-source { color: var(--text-secondary); font-weight: var(--fw-medium); }

/* Right column wrapper — stacks the 3 list cards with thin dividers in
   between (no card chrome on individual cards). */
.news-list {
  display: flex;
  flex-direction: column;
}

.news-list > .news-card--list:not(:last-child) {
  border-bottom: 1px solid var(--border-subtle);
}

.news-list > .news-card--list:first-child  { padding-top: 0; }
.news-list > .news-card--list:last-child   { padding-bottom: 0; }

/* Card variant — the rail beside the hero shows each item as a standalone
   surface card (bg + hairline border + radius) instead of hairline-divided
   rows. Same component, different container treatment. */
.news-list--cards { gap: 14px; }
.news-list--cards > .news-card--list {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  overflow: hidden;        /* clip cover to the card's rounded corners */
  padding: 0;              /* cover hugs the edges; padding lives on the body */
  gap: var(--space-4);
}
.news-list--cards > .news-card--list:not(:last-child) { border-bottom: none; }
/* Cover hugs the card top/bottom/left edge — no gap above/below the image. */
.news-list--cards > .news-card--list .news-card__cover {
  align-self: stretch;
  aspect-ratio: auto;
  border-radius: 0;
}
/* Breathing room only on the text side. */
.news-list--cards > .news-card--list .news-card__body {
  padding: 14px var(--space-4) 14px 0;
}

/* ─────────────────────────────────────────
   RESPONSIVE — NEWS GRID + CARDS
   NOTE: @media (max-width: 1080px) for .news-grid and .news-card--hero
   already exists above — those are kept. Rules below add/repair phone
   and tablet behaviour only.
   ───────────────────────────────────────── */

/* ── Tablet (768–1023) ── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  /* Hero stacks above list — already handled by 1080 breakpoint, but
     refine the hero aspect-ratio for the mid range */
  .news-card--hero {
    aspect-ratio: 16 / 9;
  }

  /* List card thumbnail: slightly wider on tablet for better balance */
  .news-card--list .news-card__cover {
    flex: 0 0 44%;
  }

  .news-card--list .news-card__title {
    font-size: var(--fs-h5);
  }
}

/* ── Phone (<768) ── */
@media (max-width: 767.98px) {
  /* Hero card: portrait-friendly ratio on phone */
  .news-card--hero {
    aspect-ratio: 4 / 3;
  }

  .news-card--hero .news-card__body {
    padding: var(--space-8) var(--space-4) var(--space-4);
    gap: var(--space-2);
  }

  .news-card--hero .news-card__title {
    font-size: var(--fs-h5);
    line-height: 1.4;
  }

  .news-card--hero .news-card__excerpt {
    -webkit-line-clamp: 2;
  }

  /* List card: stack vertically (thumbnail on top, text below) */
  .news-card--list {
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-4) 0;
  }

  .news-card--list .news-card__cover {
    flex: none;
    width: 100%;
    aspect-ratio: 16 / 9;
    border-radius: var(--radius-md);
  }

  .news-card--list .news-card__body {
    flex: 1;
    padding: 0;
    gap: var(--space-2);
  }

  .news-card--list .news-card__title {
    font-size: var(--fs-h5);
    line-height: 1.4;
    -webkit-line-clamp: 3;
  }

  /* Restore excerpt on phone — more context helps scanability */
  .news-card--list .news-card__excerpt {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }

  .news-card__meta {
    padding-top: var(--space-2);
  }

  /* Cards variant: full bleed cover on phone */
  .news-list--cards > .news-card--list {
    flex-direction: column;
    padding: 0;
  }

  .news-list--cards > .news-card--list .news-card__cover {
    flex: none;
    width: 100%;
    aspect-ratio: 16 / 9;
    align-self: auto;
    border-radius: 0;
  }

  .news-list--cards > .news-card--list .news-card__body {
    padding: var(--space-4);
  }

  /* Hover lift: disable on touch (avoids stuck states) */
  .news-card:hover {
    transform: none;
  }
}

/* ── Small phone (<480) ── */
@media (max-width: 480px) {
  .news-card--hero {
    aspect-ratio: 1 / 1;
  }

  .news-card--hero .news-card__title {
    font-size: var(--fs-body);
  }

  .news-card__tag {
    font-size: 11px;
    padding: var(--space-1) var(--space-2);
  }
}

/* ── Reduced motion ── */
@media (prefers-reduced-motion: reduce) {
  .news-card,
  .news-card__cover img {
    transition: none;
  }
}

/* ─────────────────────────────────────────
   ACTIVITY — 第六章·同場加映
   3-col grid of lightweight feature cards: icon → title → desc → CTA.
   Each card has a subtle yellow ambient glow in the top-right to hint
   at the playful / supplementary tone of this section.
   Variant .activity-card--disabled drops opacity and removes hover lift
   (used for 敬請期待 / coming-soon CTAs).
   ───────────────────────────────────────── */

.activity-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-6);
}

.activity-card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: var(--space-8);
  min-height: 280px;
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  text-decoration: none;
  color: inherit;
  overflow: hidden;
  transition: transform 250ms ease, border-color 250ms ease;
}

.activity-card:hover {
  transform: translateY(-4px);
  border-color: var(--border-default);
}

/* Yellow ambient glow at top-right — playful tone marker.
   Circle center is anchored exactly on the card's top-right corner so
   only the bottom-left quadrant of the circle bleeds into the card. */
.activity-card::before {
  content: '';
  position: absolute;
  top: -130px;
  right: -130px;
  width: 260px;
  height: 260px;
  background: radial-gradient(circle,
    rgba(255, 163, 63, 0.20) 0%,
    rgba(255, 163, 63, 0.07) 30%,
    transparent 70%);
  pointer-events: none;
  z-index: 0;
}

.activity-card > * { position: relative; z-index: 1; }

/* Icon chip — outline icon inside a yellow-tinted rounded square. */
.activity-card__icon {
  width: 56px;
  height: 56px;
  border-radius: var(--radius-md);
  background: rgba(255, 163, 63, 0.08);
  border: 1px solid rgba(255, 163, 63, 0.22);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--yellow-500);
  margin-bottom: var(--space-6);
}

/* Glass variant — chip keeps the base yellow tint as the "halo" behind
   the icon; the class is kept on the markup as a hook for future
   chip-level glass effects (e.g. backdrop-filter blur, refraction). */

.activity-card__icon-img {
  width: 32px;
  height: 32px;
  display: inline-block;
}

.activity-card__title {
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: 22px;
  line-height: 1.4;
  color: var(--text-primary);
  margin: 0 0 var(--space-3);
}

.activity-card__desc {
  font-family: var(--font-cjk-text);
  /* Figma spec: Body/Small/zh · Regular · 14/23 · Text/Tertiary */
  font-weight: var(--fw-regular);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body-sm);
  color: var(--text-tertiary);
  margin: 0;
  flex: 1;
}

.activity-card__cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: var(--space-6);
  font-family: var(--font-cjk-text);
  font-size: 14px;
  font-weight: var(--fw-medium);
  color: var(--yellow-500);
  transition: gap 200ms ease;
}

.activity-card:hover .activity-card__cta { gap: 10px; }

.activity-card__cta-arrow {
  width: 14px;
  height: 14px;
  display: inline-block;
  background-color: currentColor;
  -webkit-mask: url('assets/icons/arrow_right.svg') center / contain no-repeat;
          mask: url('assets/icons/arrow_right.svg') center / contain no-repeat;
}

/* Disabled / coming-soon CTA — muted text, no arrow, no hover gap shift. */
.activity-card__cta--disabled {
  color: var(--text-disabled);
}

.activity-card--disabled {
  cursor: default;
  opacity: 0.55;
}

.activity-card--disabled:hover {
  transform: none;
  border-color: var(--border-subtle);
}

.activity-card--disabled:hover .activity-card__cta { gap: 6px; }

/* Disabled glow — swap the yellow ambient for a neutral one so the
   dimmed card doesn't compete tonally with the active siblings. */
.activity-card--disabled::before {
  background: radial-gradient(circle,
    rgba(255, 255, 255, 0.10) 0%,
    rgba(255, 255, 255, 0.03) 30%,
    transparent 70%);
}

/* ─── Photo variant (--photo) — full-bleed cover + scrim, text pinned bottom.
   Drops the icon chip; the cover image carries the visual. Scrim gradient
   matches .news-card--overlay for a consistent light/dark rhythm.
   Ported from the dolly 2.0 build (2026-06-16). ─── */
.activity-card--photo {
  justify-content: flex-end;
  aspect-ratio: 3 / 2;          /* events art is 1536×1024 */
  min-height: 0;
  padding: var(--space-6);
}
.activity-card__cover {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 300ms ease;
}
.activity-card--photo:hover .activity-card__cover { transform: scale(1.03); }
/* Repurpose ::before (the yellow glow) as the image→text scrim. */
.activity-card--photo::before {
  inset: 0;
  top: 0;
  right: 0;
  width: auto;
  height: auto;
  background: linear-gradient(180deg,
    rgba(10, 10, 10, 0)    34%,
    rgba(10, 10, 10, 0.46) 58%,
    rgba(10, 10, 10, 0.82) 80%,
    rgba(10, 10, 10, 0.94) 100%);
  z-index: 1;
}
/* Stacking: cover(0) → scrim(1) → text(2). */
.activity-card--photo > * { z-index: 2; }
.activity-card--photo .activity-card__cover { z-index: 0; }
.activity-card--photo .activity-card__desc {
  flex: 0 1 auto;
  color: rgba(255, 255, 255, 0.78);   /* matches news overlay excerpt */
}
/* Second row: desc left, text CTA right, on one line. */
.activity-card__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}
.activity-card__row .activity-card__cta {
  margin-top: 0;
  flex-shrink: 0;
  white-space: nowrap;
}
/* Disabled photo card: keep the card solid, mute the image instead —
   full-card opacity would read as a loading glitch over a photo. */
.activity-card--photo.activity-card--disabled { opacity: 1; }
.activity-card--photo.activity-card--disabled .activity-card__cover {
  filter: grayscale(1) brightness(0.72);
}
.activity-card--photo.activity-card--disabled:hover .activity-card__cover {
  transform: none;
}

/* ─────────────────────────────────────────
   RESPONSIVE — ACTIVITY GRID + CARDS
   ───────────────────────────────────────── */

/* ── Tablet (768–1023) ── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  /* 2-col on tablet */
  .activity-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-5);
  }

  .activity-card {
    padding: var(--space-6);
    min-height: 240px;
  }
}

/* ── Phone (<768) ── */
@media (max-width: 767.98px) {
  /* Single column */
  .activity-grid {
    grid-template-columns: 1fr;
    gap: var(--space-4);
  }

  /* Standard activity card: tighter padding, no need for min-height */
  .activity-card {
    padding: var(--space-6);
    min-height: 0;
  }

  /* Icon chip: slightly smaller */
  .activity-card__icon {
    width: 48px;
    height: 48px;
    margin-bottom: var(--space-5);
  }

  .activity-card__icon-img {
    width: 26px;
    height: 26px;
  }

  .activity-card__title {
    font-size: var(--fs-h4);
    margin-bottom: var(--space-3);
  }

  /* CTA row: 44px touch target */
  .activity-card__cta {
    margin-top: var(--space-5);
    min-height: 44px;
    align-items: center;
  }

  /* Photo card: tighter aspect on phone (was 3/2, allow a touch more height) */
  .activity-card--photo {
    aspect-ratio: 4 / 3;
    padding: var(--space-4);
  }

  /* Photo card row (desc + CTA): stack on small screens */
  .activity-card__row {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
  }

  .activity-card__row .activity-card__cta {
    margin-top: var(--space-2);
  }

  /* Hover lift: no stuck states on touch */
  .activity-card:hover {
    transform: none;
  }
}

/* ── Small phone (<480) ── */
@media (max-width: 480px) {
  .activity-card {
    padding: var(--space-5);
  }

  .activity-card--photo {
    aspect-ratio: 1 / 1;
    padding: var(--space-4);
  }
}

/* ── Reduced motion ── */
@media (prefers-reduced-motion: reduce) {
  .activity-card,
  .activity-card__cover {
    transition: none;
  }
}

/* ─────────────────────────────────────────
   SITE FOOTER
   5-col layout: brand block (logo + tagline + email + social) on the
   left, 4 nav columns on the right. Below the grid: a divider, then
   a chain of legal links, then the © row, then a small legal notice.
   ───────────────────────────────────────── */

.site-footer {
  background: var(--bg-primary);
  padding: clamp(var(--space-16), 8vw, var(--space-24)) 0 var(--space-8);
  border-top: 1px solid var(--border-subtle);
  color: var(--text-secondary);
  margin-top: auto; /* sticky footer：把 footer 推到 main 底部，短頁不浮上來 */
}

.site-footer__top {
  display: grid;
  /* 品牌欄 + 5 個 nav 欄(平台/社群/合作/條款/支援)。
     新增合作欄後從 4 → 5 nav 欄,故 1fr 軌由 4 → 5。 */
  grid-template-columns: 2fr 1fr 1fr 1fr 1fr 1fr;
  gap: var(--space-12);
}

/* Brand block (col 1) — logo + tagline + body + email + socials. */
.site-footer__brand {
  display: flex;
  flex-direction: column;
  gap: 18px;
  max-width: 360px;
}

.site-footer__logo {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  line-height: 1;
  text-decoration: none;
  height: 40px;
}

.site-footer__logo-img {
  height: 40px;
  width: auto;
  display: block;
}

.site-footer__tagline {
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: 18px;
  line-height: 1.5;
  color: var(--text-primary);
  margin: 0;
}

.site-footer__desc {
  font-family: var(--font-cjk-text);
  font-weight: var(--fw-regular);
  font-size: 13px;
  line-height: 1.75;
  color: var(--text-tertiary);
  margin: 0;
}

.site-footer__email {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-cjk-text);
  font-size: 13px;
  color: var(--text-secondary);
  text-decoration: none;
  transition: color 150ms ease;
}

.site-footer__email:hover { color: var(--yellow-500); }

/* Contact row — socials + email side by side on a single row. */
.site-footer__contact {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  margin-top: var(--space-1);
}

.site-footer__social {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.site-footer__social-link {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-subtle);
  color: var(--text-secondary);
  transition: color 150ms ease, border-color 150ms ease, background 150ms ease;
}

.site-footer__social-link:hover {
  color: var(--yellow-500);
  border-color: rgba(255, 163, 63, 0.35);
  background: rgba(255, 163, 63, 0.06);
}

.site-footer__social-icon {
  width: 22px;
  height: 22px;
  display: inline-block;
  background-color: currentColor;
}

.site-footer__social-icon--instagram { -webkit-mask: url('assets/icons/ig.svg')     center / contain no-repeat;
                                                mask: url('assets/icons/ig.svg')     center / contain no-repeat; }
.site-footer__social-icon--threads   { -webkit-mask: url('assets/icons/thread.svg') center / contain no-repeat;
                                                mask: url('assets/icons/thread.svg') center / contain no-repeat; }

/* Nav columns (cols 2–5). */
.site-footer__nav {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.site-footer__nav-title {
  font-family: var(--font-cjk-text);
  font-size: 13px;
  font-weight: var(--fw-semibold);
  letter-spacing: 0.04em;
  color: var(--yellow-500);
  margin: 0 0 6px;
}

.site-footer__nav-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.site-footer__nav-link {
  font-family: var(--font-cjk-text);
  font-size: 14px;
  color: var(--text-secondary);
  text-decoration: none;
  transition: color 150ms ease;
}

.site-footer__nav-link:hover { color: var(--text-primary); }

.site-footer__nav-link.is-disabled {
  color: var(--text-disabled);
  cursor: not-allowed;
  pointer-events: none;
}

/* Bottom blocks — divider then 條款 chain → © row → legal notice. */
.site-footer__divider {
  height: 1px;
  background: var(--border-subtle);
  margin: var(--space-14) 0 var(--space-6);
}

.site-footer__legal-chain {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) 18px;
  font-family: var(--font-cjk-text);
  font-size: 12px;
  color: var(--text-tertiary);
}

.site-footer__legal-chain a {
  color: inherit;
  transition: color 150ms ease;
}

.site-footer__legal-chain a:hover { color: var(--text-secondary); }

.site-footer__bottom-row {
  margin-top: var(--space-5);
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  font-family: var(--font-cjk-text);
  font-size: 12px;
  color: var(--text-tertiary);
}

.site-footer__copy {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.site-footer__locale {
  display: inline-flex;
  align-items: center;
  gap: 14px;
}

.site-footer__locale a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: inherit;
  text-decoration: none;
  transition: color 150ms ease;
}

.site-footer__locale a:hover { color: var(--text-secondary); }

.site-footer__locale-icon {
  width: 14px;
  height: 14px;
  display: inline-block;
  background-color: currentColor;
  -webkit-mask: url('data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%202C17.5078%202%2022%206.49219%2022%2012C22%2017.5469%2017.5078%2022%2012%2022C6.45312%2022%202%2017.5469%202%2012C2%206.49219%206.45312%202%2012%202ZM12%2020.125C12.2734%2020.125%2013.0156%2019.8516%2013.8359%2018.25C14.1875%2017.5469%2014.4609%2016.7266%2014.6953%2015.75H9.26562C9.5%2016.7266%209.77344%2017.5469%2010.125%2018.25C10.9453%2019.8516%2011.6875%2020.125%2012%2020.125ZM8.95312%2013.875H15.0078C15.0859%2013.2891%2015.125%2012.6641%2015.125%2012C15.125%2011.375%2015.0859%2010.75%2015.0078%2010.125H8.95312C8.875%2010.75%208.875%2011.375%208.875%2012C8.875%2012.6641%208.875%2013.2891%208.95312%2013.875ZM14.6953%208.25C14.4609%207.3125%2014.1875%206.49219%2013.8359%205.78906C13.0156%204.1875%2012.2734%203.875%2012%203.875C11.6875%203.875%2010.9453%204.1875%2010.125%205.78906C9.77344%206.49219%209.5%207.3125%209.26562%208.25H14.6953ZM16.8828%2010.125C16.9609%2010.75%2017%2011.375%2017%2012C17%2012.6641%2016.9609%2013.2891%2016.8828%2013.875H19.8906C20.0469%2013.2891%2020.125%2012.6641%2020.125%2012C20.125%2011.375%2020.0469%2010.75%2019.8906%2010.125H16.8828ZM15.3594%204.61719C15.9062%205.63281%2016.3359%206.88281%2016.6094%208.25H19.1875C18.3672%206.64844%2017%205.35938%2015.3594%204.61719ZM8.60156%204.61719C6.96094%205.35938%205.59375%206.64844%204.77344%208.25H7.35156C7.625%206.88281%208.05469%205.63281%208.60156%204.61719ZM3.875%2012C3.875%2012.6641%203.91406%2013.2891%204.07031%2013.875H7.07812C7%2013.2891%207%2012.6641%207%2012C7%2011.375%207%2010.75%207.07812%2010.125H4.07031C3.91406%2010.75%203.875%2011.375%203.875%2012ZM19.1875%2015.75H16.6094C16.3359%2017.1562%2015.9062%2018.4062%2015.3594%2019.4219C17%2018.6797%2018.3672%2017.3906%2019.1875%2015.75ZM7.35156%2015.75H4.77344C5.59375%2017.3906%206.96094%2018.6797%208.60156%2019.4219C8.05469%2018.4062%207.625%2017.1562%207.35156%2015.75Z%22%20fill%3D%22white%22%2F%3E%3C%2Fsvg%3E') center / contain no-repeat;
          mask: url('data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%202C17.5078%202%2022%206.49219%2022%2012C22%2017.5469%2017.5078%2022%2012%2022C6.45312%2022%202%2017.5469%202%2012C2%206.49219%206.45312%202%2012%202ZM12%2020.125C12.2734%2020.125%2013.0156%2019.8516%2013.8359%2018.25C14.1875%2017.5469%2014.4609%2016.7266%2014.6953%2015.75H9.26562C9.5%2016.7266%209.77344%2017.5469%2010.125%2018.25C10.9453%2019.8516%2011.6875%2020.125%2012%2020.125ZM8.95312%2013.875H15.0078C15.0859%2013.2891%2015.125%2012.6641%2015.125%2012C15.125%2011.375%2015.0859%2010.75%2015.0078%2010.125H8.95312C8.875%2010.75%208.875%2011.375%208.875%2012C8.875%2012.6641%208.875%2013.2891%208.95312%2013.875ZM14.6953%208.25C14.4609%207.3125%2014.1875%206.49219%2013.8359%205.78906C13.0156%204.1875%2012.2734%203.875%2012%203.875C11.6875%203.875%2010.9453%204.1875%2010.125%205.78906C9.77344%206.49219%209.5%207.3125%209.26562%208.25H14.6953ZM16.8828%2010.125C16.9609%2010.75%2017%2011.375%2017%2012C17%2012.6641%2016.9609%2013.2891%2016.8828%2013.875H19.8906C20.0469%2013.2891%2020.125%2012.6641%2020.125%2012C20.125%2011.375%2020.0469%2010.75%2019.8906%2010.125H16.8828ZM15.3594%204.61719C15.9062%205.63281%2016.3359%206.88281%2016.6094%208.25H19.1875C18.3672%206.64844%2017%205.35938%2015.3594%204.61719ZM8.60156%204.61719C6.96094%205.35938%205.59375%206.64844%204.77344%208.25H7.35156C7.625%206.88281%208.05469%205.63281%208.60156%204.61719ZM3.875%2012C3.875%2012.6641%203.91406%2013.2891%204.07031%2013.875H7.07812C7%2013.2891%207%2012.6641%207%2012C7%2011.375%207%2010.75%207.07812%2010.125H4.07031C3.91406%2010.75%203.875%2011.375%203.875%2012ZM19.1875%2015.75H16.6094C16.3359%2017.1562%2015.9062%2018.4062%2015.3594%2019.4219C17%2018.6797%2018.3672%2017.3906%2019.1875%2015.75ZM7.35156%2015.75H4.77344C5.59375%2017.3906%206.96094%2018.6797%208.60156%2019.4219C8.05469%2018.4062%207.625%2017.1562%207.35156%2015.75Z%22%20fill%3D%22white%22%2F%3E%3C%2Fsvg%3E') center / contain no-repeat;
}

/* Legal notice — sits on the right of the © row. Matches the copy's
   12px to keep both sides of the row visually balanced. max-width
   keeps it a clean block instead of stretching across the row. */
.site-footer__notice {
  margin: 0;
  font-family: var(--font-cjk-text);
  font-size: 12px;
  line-height: 1.65;
  color: var(--text-tertiary);
  max-width: 640px;
}

/* ─────────────────────────────────────────
   SITE FOOTER — RESPONSIVE
   ───────────────────────────────────────── */

/* Tablet (768–1023): collapse to 3-col grid — brand full-width on first row,
   nav columns fill remaining two rows (2+3 split). */
@media (min-width: 768px) and (max-width: 1023.98px) {
  .site-footer__top {
    /* Brand spans full width; nav cols form a 2+3 sub-grid.
       Use a 3-col grid: brand takes full row, nav fills 3 equal cols. */
    grid-template-columns: 1fr 1fr 1fr;
    gap: var(--space-8);
  }

  /* Brand block occupies the full first row. */
  .site-footer__brand {
    grid-column: 1 / -1;
    max-width: 100%;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--space-8);
  }

  .site-footer__divider {
    margin: var(--space-10) 0 var(--space-6);
  }
}

/* Phone ≤767.98px: luxury-fashion footer — a clean vertical stack with
   COLLAPSIBLE link sections (Mobbin ref: SSENSE / Net-a-Porter / Best Buy /
   Blue Bottle — every premium mobile footer is an accordion). This kills the
   old "cramped left half, empty right half" 2-col grid. mobile-nav.js adds
   .site-footer--accordion + toggles .is-open per section. Mobile only. */
@media (max-width: 767.98px) {
  /* MOBILE: hide the footer on every page EXCEPT the home page (body.rf-home =
     index.html). Per L: "remove all the footer of the mobile site except index.html."
     Keyed off the body page-type class so it covers all pages via the shared bundle —
     no per-page HTML edits. Desktop is untouched (this lives in the phone query only). */
  body:not(.rf-home) .site-footer { display: none; }

  .site-footer {
    padding-bottom: var(--space-8);
  }

  /* Top region is now a single full-width column: brand block, then each
     nav group as a full-bleed collapsible row. */
  .site-footer__top {
    display: flex;
    flex-direction: column;
    gap: 0;
  }

  /* Brand block: full width, sits above the link sections with a clear gap. */
  .site-footer__brand {
    max-width: 100%;
    gap: var(--space-4);
    padding-bottom: var(--space-6);
  }

  /* ── Collapsible link sections ─────────────────────────────────────────
     Each .site-footer__nav becomes a full-width row separated by hairlines.
     Title = the tap target (chevron on the right); list collapses to 0. */
  .site-footer--accordion .site-footer__nav {
    gap: 0;
    border-top: 1px solid var(--border-subtle);
  }
  .site-footer--accordion .site-footer__nav:last-of-type {
    border-bottom: 1px solid var(--border-subtle);
  }

  .site-footer--accordion .site-footer__nav-title {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: 0;
    padding: var(--space-4) 0;
    min-height: 52px;                 /* generous tap target */
    cursor: pointer;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    /* Quieter than the desktop yellow — fashion houses keep footer titles
       neutral. Uppercase + wide tracking is the luxury convention. */
    color: var(--text-primary);
    font-size: 13px;
    font-weight: var(--fw-semibold);
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }

  /* Chevron (CSS-only) — points down at rest, flips up when open. */
  .site-footer--accordion .site-footer__nav-title::after {
    content: "";
    width: 9px;
    height: 9px;
    margin-right: 3px;
    border-right: 1.5px solid var(--text-tertiary);
    border-bottom: 1.5px solid var(--text-tertiary);
    transform: translateY(-2px) rotate(45deg);
    transition: transform 260ms cubic-bezier(0.23, 1, 0.32, 1);
  }
  .site-footer--accordion .site-footer__nav.is-open .site-footer__nav-title::after {
    transform: translateY(2px) rotate(225deg);
  }

  /* Collapsible list — animated max-height + fade. */
  .site-footer--accordion .site-footer__nav-list {
    gap: 0;
    overflow: hidden;
    max-height: 0;
    opacity: 0;
    transition: max-height 300ms cubic-bezier(0.23, 1, 0.32, 1),
                opacity 200ms ease,
                padding 300ms cubic-bezier(0.23, 1, 0.32, 1);
  }
  .site-footer--accordion .site-footer__nav.is-open .site-footer__nav-list {
    max-height: 420px;                /* > tallest section (條款: 6 links) */
    opacity: 1;
    padding-bottom: var(--space-4);
  }

  .site-footer__nav-link {
    display: flex;
    align-items: center;
    min-height: 40px;
    font-size: 14px;
    color: var(--text-secondary);
  }

  /* Contact row: let socials and email stack if narrow. */
  .site-footer__contact {
    flex-wrap: wrap;
    gap: var(--space-3);
  }

  /* The last accordion section already closes with a hairline, so the legal
     divider would be a redundant 2nd line — hide it on mobile. */
  .site-footer__divider { display: none; }
  .site-footer__bottom-row { margin-top: var(--space-6); }

  /* Legal chain: already flex-wrap, just tighten gap. */
  .site-footer__legal-chain {
    gap: var(--space-2) var(--space-4);
  }

  /* Bottom row: stack copyright and locale switcher. */
  .site-footer__bottom-row {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-3);
    margin-top: var(--space-4);
  }

  .site-footer__notice {
    max-width: 100%;
    margin-top: var(--space-4);
  }

  /* Premium closing moment — an oversized brand wordmark as a faint watermark
     at the foot of the page (Mobbin ref: Ditto / Kalstore / Fiasco). Mobile only. */
  .site-footer { position: relative; overflow: hidden; }
  .site-footer__bottom-row { position: relative; z-index: 1; }
  .site-footer::after {
    content: "";
    display: block;
    height: clamp(60px, 21vw, 116px);
    margin: var(--space-8) 0 var(--space-2);
    background: url('assets/icons/ztor_logo.svg') center bottom / auto 100% no-repeat;
    opacity: 0.16;
    pointer-events: none;
  }

  @media (prefers-reduced-motion: reduce) {
    .site-footer--accordion .site-footer__nav-list,
    .site-footer--accordion .site-footer__nav-title::after { transition: none; }
  }
}

/* Small phone ≤480px: just nudge the tagline down a size. */
@media (max-width: 480px) {
  .site-footer__tagline {
    font-size: var(--fs-h5);
  }
}

/* ─────────────────────────────────────────
   AUTH-GATING — show / hide blocks per auth state
   Pairs with body[data-auth] toggle. For now driven by dev-toggle;
   real auth would render server-side and remove these rules.
   ───────────────────────────────────────── */

body[data-auth="logged-out"] [data-auth-required="logged-in"]  { display: none !important; }
body[data-auth="logged-in"]  [data-auth-required="logged-out"] { display: none !important; }

/* ─────────────────────────────────────────
   POPCORN PRICING PAGE
   /popcorn.html — points-of-entry: header popcorn-entry pill (guest)
   and after-login popcorn balance/+ buttons. Two sections stacked:
   1) "我的爆米花" — balance + history + redeem (logged-in only)
   2) "購買爆米花" — 3 package cards (always)
   3) "使用指南" bullet block (always)
   ───────────────────────────────────────── */

.popcorn-page {
  padding-block: 72px var(--space-24);
  display: flex;
  flex-direction: column;
  gap: var(--space-20);
}

.popcorn-page__heading {
  text-align: center;
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: var(--fs-h3);
  line-height: 1.2;
  color: var(--text-primary);
  margin: 0 0 var(--space-8);
}

/* ── 1. My popcorn balance card ── */
.popcorn-balance-card {
  max-width: 720px;
  margin: 0 auto;
  width: 100%;
  background: var(--bg-secondary);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.popcorn-balance-card__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-6) 28px;
  text-decoration: none;
  color: var(--text-primary);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  border-bottom: 1px solid var(--white-alpha-8);
}

.popcorn-balance-card__row:last-child { border-bottom: none; }

/* Balance row uses a faint yellow tint and brand-coloured value */
.popcorn-balance-card__row--balance {
  background: rgba(255, 163, 63, 0.04);
  color: var(--yellow-500);
}

.popcorn-balance-card__label-icon {
  display: inline-block;
  width: 18px;
  height: 18px;
  vertical-align: -3px;
  background-color: var(--yellow-500);
  -webkit-mask: var(--icon) center / contain no-repeat;
  mask: var(--icon) center / contain no-repeat;
}

/* History / redeem rows are clickable, hover-lit */
.popcorn-balance-card__row--link {
  cursor: pointer;
  transition: background-color 150ms ease, color 150ms ease;
}

.popcorn-balance-card__row--link:hover {
  background: var(--white-alpha-4);
  color: var(--yellow-500);
}

.popcorn-balance-card__row--link:hover .popcorn-balance-card__chevron {
  background-color: var(--yellow-500);
  transform: translateX(2px);
}

.popcorn-balance-card__label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.popcorn-balance-card__value {
  font-family: var(--font-latin);
  font-size: var(--fs-h4);
  font-weight: var(--fw-bold);
}

/* Chevron rendered via mask so it can recolor on hover */
.popcorn-balance-card__chevron {
  width: 18px;
  height: 18px;
  background-color: var(--text-tertiary);
  -webkit-mask: url('assets/icons/chevron_right.svg') center / contain no-repeat;
          mask: url('assets/icons/chevron_right.svg') center / contain no-repeat;
  transition: background-color 150ms ease, transform 150ms ease;
}

/* ── 2. Purchase package cards ── */
.popcorn-packages {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-5);
  max-width: 720px;
  width: 100%;
  margin: 0 auto;
}

.popcorn-package {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: 36px var(--space-6) 28px;
  background: var(--bg-secondary);
  border-radius: var(--radius-lg);
  text-align: center;
  transition: background-color 200ms ease, transform 200ms ease;
}

.popcorn-package:hover {
  background: var(--bg-tertiary);
  transform: translateY(-2px);
}

.popcorn-package__icon {
  width: 56px;
  height: 56px;
  margin-bottom: var(--space-1);
}

.popcorn-package__name {
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: var(--fs-h5);
  line-height: var(--lh-h5);
  color: var(--text-primary);
  margin: 0;
}

.popcorn-package__count {
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body-sm);
  color: var(--yellow-500);
  margin: 0;
}

.popcorn-package__buy {
  margin-top: var(--space-3);
  min-width: 152px;
}

/* ── 3. Usage guide ── */
.popcorn-guide {
  max-width: 720px;
  width: 100%;
  margin: 0 auto;
}

.popcorn-guide__title {
  font-family: var(--font-cjk-text);
  font-weight: var(--fw-medium);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--text-primary);
  margin: 0 0 var(--space-3);
}

.popcorn-guide__list {
  margin: 0;
  padding-left: 1.4em;
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  line-height: 1.75;
  color: var(--text-secondary);
}

.popcorn-guide__list li { list-style: disc; }

/* Responsive — 3 → 1 col on narrow (768 = site-wide mobile breakpoint) */
@media (max-width: 767.98px) {
  .popcorn-page { padding-block: var(--space-12) var(--space-16); gap: var(--space-14); }
  .popcorn-page__heading { font-size: var(--fs-h3); margin-bottom: var(--space-5); }
  .popcorn-packages { grid-template-columns: 1fr; gap: 14px; }
  .popcorn-balance-card__row { padding: 18px var(--space-5); }
}

/* Flush the top padding for index/main pages — their title sits close
   under the site header (~32px per Figma), unlike homepage chapters which
   use the generous .section breathing room. Bottom padding unchanged. */
.section--flush-top { padding-top: clamp(var(--space-4), 4vw, var(--space-8)); }   /* 16 on phone → 32 desktop */

/* ─────────────────────────────────────────
   RESPONSIVE — popcorn page
   (supplements the existing max-width:768px block above)
   ───────────────────────────────────────── */

/* ── Tablet: 768–1023px ── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  .popcorn-page {
    padding-block: var(--space-14) 72px;
    gap: 60px;
  }

  .popcorn-packages {
    /* 3 columns still fit on tablet but give a bit more breathing room */
    gap: var(--space-4);
  }

  .popcorn-package {
    padding: var(--space-8) var(--space-5) var(--space-6);
  }
}

/* ── Phone: ≤767px — extend the existing block ── */
@media (max-width: 767.98px) {
  .popcorn-page {
    padding-block: var(--space-10) var(--space-16);
    gap: var(--space-14);
  }

  /* Heading: step down to h4 scale */
  .popcorn-page__heading {
    font-size: var(--fs-h4);
    margin-bottom: var(--space-5);
  }

  /* Balance card: full-width, tighter rows */
  .popcorn-balance-card {
    max-width: 100%;
  }

  .popcorn-balance-card__row {
    padding: var(--space-4) var(--space-5);
    gap: var(--space-3);
    flex-wrap: nowrap;
  }

  /* Value: slightly smaller so label + value don't collide in one row */
  .popcorn-balance-card__value {
    font-size: var(--fs-h5);
    white-space: nowrap;
  }

  /* Packages: single column (already set by 768px block above;
     restate at 767.98px boundary with tighter gap) */
  .popcorn-packages {
    max-width: 100%;
    gap: var(--space-3);
  }

  .popcorn-package {
    padding: var(--space-6) var(--space-5) var(--space-5);
    gap: var(--space-2);
  }

  .popcorn-package__icon {
    width: 44px;
    height: 44px;
    margin-bottom: var(--space-1);
  }

  /* Buy button: full width on phone so it's easy to tap */
  .popcorn-package__buy {
    width: 100%;
    min-width: unset;
  }

  /* Guide: full width */
  .popcorn-guide {
    max-width: 100%;
  }
}

/* ── Small phone: ≤480px ── */
@media (max-width: 480px) {
  /* Balance card rows: allow label to wrap if needed */
  .popcorn-balance-card__row {
    flex-wrap: wrap;
    padding: var(--space-3) var(--space-4);
  }

  /* Ensure the label + icon and the value each take a full line if cramped */
  .popcorn-balance-card__label {
    flex: 1 1 100%;
  }

  .popcorn-balance-card__value {
    flex: 1 1 auto;
    font-size: var(--fs-body);
  }

  .popcorn-balance-card__chevron {
    flex-shrink: 0;
  }

  .popcorn-package {
    padding: var(--space-5) var(--space-4);
  }
}

/* ─────────────────────────────────────────
   MAIN-PAGE HEADER ROW  (.page-head)
   Compact title row used by content index pages (cocreate / news /
   activity): H3 title on the left + optional info link on the right.
   Mirrors Figma "Title Combo" (97:517). Reusable — see [[cross-page
   consistency]] memory.
   ───────────────────────────────────────── */
.page-head {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) 0;
}

/* Left block — title + optional description, stacked. */
.page-head__copy {
  flex: 1 1 auto;
  min-width: 0;
}

.page-head__title {
  margin: 0;
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: var(--fs-page-title);   /* canonical page-header size (was --fs-h3) */
  line-height: var(--lh-h4);
  letter-spacing: 0.02em;            /* match the shop-head title */
  color: var(--text-primary);
}

.page-head__desc {
  margin: 6px 0 0;
  font-family: var(--font-cjk-text);
  font-size: var(--fs-page-desc);    /* canonical (was --fs-body) */
  line-height: var(--lh-body-sm);
  color: var(--text-secondary);
}

.page-head__info {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  flex-shrink: 0;
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
  text-decoration: none;
  transition: color 160ms ease;
}

.page-head__info:hover { color: var(--text-primary); }

.page-head__info-icon {
  display: inline-block;
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  background-color: currentColor;
  -webkit-mask: url('assets/icons/info.svg') center / contain no-repeat;
  mask: url('assets/icons/info.svg') center / contain no-repeat;
}

/* ─────────────────────────────────────────
   PAGE-HEAD — RESPONSIVE
   ───────────────────────────────────────── */

/* Tablet: tighten vertical padding slightly. */
@media (min-width: 768px) and (max-width: 1023.98px) {
  .page-head {
    padding: var(--space-3) 0;
  }
}

/* Phone: stack vertically; reduce padding; clamp title to comfortable size. */
@media (max-width: 767.98px) {
  .page-head {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
    padding: var(--space-3) 0;
  }

  .page-head__title {
    font-size: var(--fs-page-title);   /* flat canonical size, matches shop-head on phone */
    line-height: 1.35;
  }

  .page-head__desc {
    font-size: var(--fs-page-desc);
    line-height: var(--lh-body-sm);
    margin-top: var(--space-1);
  }

  /* Info link moves inline below the copy; no longer floats right. */
  .page-head__info {
    margin-top: var(--space-1);
  }
}

/* (≤480 title override removed — the canonical --fs-page-title is already a flat
   20px on every breakpoint, matching the shop header.) */

/* ─────────────────────────────────────────
   GLASS TAB NAV  (.glass-tabs)
   Pill-shaped segmented filter. Active item = yellow tint + yellow bold
   text. Mirrors Figma "Nav_Glass" (315:9438).
   ───────────────────────────────────────── */
.glass-tabs {
  display: inline-flex;
  align-items: center;
  max-width: 100%;
  /* Vertical rhythm: the section's padding-top owns the space ABOVE the tabs;
     the tabs own the gap BELOW so content never sticks to the bar on any page.
     32px below, universally. The one place a sibling adds its own top margin
     (.creator-sec) is de-stacked just below so it doesn't double up. */
  margin: 0 0 var(--space-8);
  padding: 4px;
  background: var(--white-alpha-4);
  border: 1px solid var(--neutral-700);
  border-radius: var(--radius-pill);
  overflow-x: auto;
  scrollbar-width: none;
}

/* De-stack: .creator-sec sets its own margin-top for non-tab contexts; when it
   directly follows the tabs, the tabs already own the gap — zero its top so the
   two don't add up (keeps the creator/leaderboard tabs→title gap at 32px). */
.glass-tabs + .creator-sec { margin-top: 0; }
.glass-tabs::-webkit-scrollbar { display: none; }

.glass-tabs__item {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Never shrink — without this, a width:100% scroll container makes flex
     compress the items into each other instead of overflowing + scrolling
     (the cause of the overlapping platform chips on mobile). */
  flex: 0 0 auto;
  height: 40px;
  padding: 0 26px;
  border: none;
  background: transparent;
  border-radius: var(--radius-pill);
  font-family: var(--font-cjk-display);
  font-size: 16px;
  font-weight: var(--fw-regular);
  line-height: 24px;
  color: var(--text-secondary);
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 160ms ease, color 160ms ease;
}

.glass-tabs__item:hover { color: var(--text-primary); }

/* Leading icon — mask uses currentColor so it tracks the tab text colour
   (灰 inactive / 白 hover / 黃 active). Only rendered where the span exists
   (片庫 放映廳/資料庫 toggle). */
.glass-tabs__icon {
  width: 18px;
  height: 18px;
  margin-right: 8px;
  flex: 0 0 auto;
  background-color: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
          mask: var(--icon) center / contain no-repeat;
}

/* Active tab borrows the .btn--yellow-ghost look: glass gradient + glowing
   yellow inset border + yellow bold text. */
.glass-tabs__item--active,
.glass-tabs__item--active:hover {
  color: var(--yellow-500);
  font-weight: var(--fw-bold);
  background: linear-gradient(0deg,
    rgba(255, 163, 63, 0) 0%,
    rgba(255, 163, 63, 0.18) 2.45%,
    rgba(255, 163, 63, 0) 126.14%);
  box-shadow:
    rgba(255, 163, 63, 0.9) 1.1px 2.2px 0.5px -1.8px inset,
    rgba(255, 163, 63, 0.9) -1px -2.2px 0.5px -1.8px inset,
    rgba(0, 0, 0, 0.10) 2px 3px 2px 0 inset;
  /* NO per-item backdrop — the pill itself carries the one glass layer (exactly like
     我的投票). A second blur here was the redundant frost layer that read as muddy. */
}

/* Ztor platform chip (first chip in the platform filter): render the brand
   mark in the light wordmark grey, not its orange source PNG, to match the
   main logo. brightness(0)+invert flattens the PNG to one flat grey regardless
   of its original colours. Other platforms keep their brand colours. */
@media (max-width: 767.98px) {
  /* Ztor platform chip: greyed to match the wordmark — MOBILE ONLY. */
  .library-platforms .glass-tabs__item:first-child .glass-tabs__logo,
  .ranking-platforms .glass-tabs__item:first-child .glass-tabs__logo {
    filter: grayscale(1) brightness(1.15);   /* desaturate but keep the Z visible */
  }
}

/* Coming-soon cocreation card: no progress bar, just an anticipatory
   start-date line (yellow), pinned to the card bottom. */
.cocreation-card__upcoming {
  margin-top: auto;
  font-family: var(--font-cjk-text);
  font-size: 13px;
  line-height: 1.6;
  font-weight: var(--fw-medium);
  color: var(--yellow-500);
}

/* ─────────────────────────────────────────
   GLASS-TABS — RESPONSIVE
   Tabs become a full-width horizontal scroll strip on phone.
   Never wraps; never overflows the viewport.
   ───────────────────────────────────────── */

/* Tablet: allow tab row to fill the available column width. */
@media (min-width: 768px) and (max-width: 1023.98px) {
  .glass-tabs {
    max-width: 100%;
  }
}

/* Phone ≤767.98px: full-width, scrollable, scroll-snap, hidden scrollbar.
   The .glass-tabs base already sets overflow-x:auto and scrollbar-width:none
   — we only need to extend it for phone layout specifics. */
@media (max-width: 767.98px) {
  .glass-tabs {
    /* Fill the container rather than hug content width. */
    width: 100%;
    /* Keep the bottom gap on phone (24px, a touch tighter) so content doesn't
       stick to the bar; horizontal margin stays 0 (full-width tabs). */
    margin: 0 0 var(--space-6);
    /* Horizontal scroll — mandatory so each active tab snaps into view. */
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    /* Prevent rubber-band bleed on iOS. */
    overscroll-behavior-x: contain;
  }

  .glass-tabs__item {
    /* Snap each item when the strip is scrolled. */
    scroll-snap-align: start;
    /* Ensure ≥44px touch target height. */
    height: 44px;
    /* Slightly tighter padding so more tabs fit before scrolling. */
    padding: 0 var(--space-5);
    font-size: var(--fs-body-sm);
  }
}

/* Small phone ≤480px: compress tab padding further; item font matches caption. */
@media (max-width: 480px) {
  .glass-tabs__item {
    padding: 0 var(--space-4);
    font-size: var(--fs-caption);
    height: 44px; /* maintain touch target */
  }

  .glass-tabs__icon {
    width: 15px;
    height: 15px;
    margin-right: var(--space-1);
  }
}

/* ─────────────────────────────────────────
   SCROLL-REVEAL FOUC GUARD  (content index pages: cocreate / news / activity)
   Pre-hide reveal targets so they don't flash before the reveal script runs.
   Active ONLY when <html> has .js-reveal-ready — added by the page's reveal
   IIFE and ONLY when GSAP is live + motion is allowed. If GSAP fails to load
   or the user prefers reduced motion, the class is never set and everything
   shows immediately. Distinct from index.html's .js-motion-ready so the two
   reveal systems never interfere.
   ───────────────────────────────────────── */
@media (prefers-reduced-motion: no-preference) {
  .js-reveal-ready [data-reveal-section] .page-head__title,
  .js-reveal-ready [data-reveal-section] .page-head__desc,
  .js-reveal-ready [data-reveal-section] .page-head__info,
  .js-reveal-ready [data-reveal-section] .glass-tabs,
  .js-reveal-ready [data-reveal-section] .section-eyebrow,
  .js-reveal-ready [data-reveal-section] .section-header__title,
  .js-reveal-ready [data-reveal-section] .section-header__sub,
  .js-reveal-ready [data-reveal-section] .section-link,
  .js-reveal-ready [data-reveal-section] .popcorn-page__heading,
  .js-reveal-ready [data-reveal-section] .popcorn-balance-card,
  .js-reveal-ready [data-reveal-section] .popcorn-guide,
  .js-reveal-ready [data-reveal-stack] > * {
    opacity: 0;
  }
}

/* ─────────────────────────────────────────
   CO-CREATION CARD — lifecycle status states
   Mirrors Figma "Crowdfund-Card" states (node 165:33664). The status badge
   is a glass "ActivityTag": colour tint over a dark underlay so it reads on
   any surface. Footer swaps between metrics / date-line / CTA per state.
   ───────────────────────────────────────── */
.status-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  align-self: flex-start;
  padding: 6px var(--space-3);
  border: 0.8px solid transparent;
  border-radius: var(--radius-pill);
  font-family: var(--font-cjk-text);
  font-size: 12px;
  line-height: 18px;
  font-weight: var(--fw-medium);
  white-space: nowrap;
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

.status-tag__icon {
  display: inline-flex;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

/* Movie icon (assets/icons/movie.svg) — mask so it follows the tag colour. */
.status-tag__icon--movie {
  background-color: currentColor;
  -webkit-mask: url('assets/icons/movie.svg') center / contain no-repeat;
  mask: url('assets/icons/movie.svg') center / contain no-repeat;
}

/* 計畫進行中 / 即將投入製作 / 製作中 */
.status-tag--yellow {
  background: linear-gradient(rgba(255, 163, 63, 0.12), rgba(255, 163, 63, 0.12)), rgba(0, 0, 0, 0.6);
  border-color: rgba(255, 163, 63, 0.3);
  color: var(--yellow-500);
}
/* 目標達成 / 已上架 */
.status-tag--green {
  background: linear-gradient(rgba(34, 197, 94, 0.12), rgba(34, 197, 94, 0.12)), rgba(0, 0, 0, 0.6);
  border-color: rgba(34, 197, 94, 0.3);
  color: var(--success-500);
}
/* 即將開放（亮灰）*/
.status-tag--soon {
  background: linear-gradient(rgba(163, 163, 163, 0.12), rgba(163, 163, 163, 0.12)), rgba(0, 0, 0, 0.6);
  border-color: rgba(163, 163, 163, 0.3);
  color: var(--text-secondary);
}
/* 已結束 / 已取消（暗灰）*/
.status-tag--ended {
  background: linear-gradient(rgba(115, 115, 115, 0.12), rgba(115, 115, 115, 0.12)), rgba(0, 0, 0, 0.6);
  border-color: rgba(115, 115, 115, 0.3);
  color: var(--text-tertiary);
}

/* Grey progress fill for ended / cancelled projects (no yellow glow). */
.cocreation-card__bar-fill--muted {
  background: var(--neutral-500);
  box-shadow: none;
}

/* Footer status line (yellow) — start date / release date. Optional leading icon. */
.cocreation-card__note {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-cjk-text);
  font-weight: var(--fw-medium);
  font-size: 16px;
  line-height: 27px;
  color: var(--yellow-500);
}
.cocreation-card__note-icon {
  display: inline-block;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  background-color: currentColor;
  -webkit-mask: url('assets/icons/media-play.svg') center / contain no-repeat;
  mask: url('assets/icons/media-play.svg') center / contain no-repeat;
}

/* CTA pinned bottom-right (e.g. 已上架 → 立即收看). */
.cocreation-card__cta-row {
  margin-top: auto;
  display: flex;
  justify-content: flex-end;
}

/* News index — render the list as a 2-column grid so a long article list
   doesn't become one very tall single column, and rows aren't stretched to
   the full wide container. Collapses to 1 column on mobile. */
.news-list--grid {
  display: grid;
  /* minmax(0,…): plain 1fr can't shrink below the card's intrinsic
     width (image flex-basis 40% of a min-content row), which forced
     horizontal page overflow on narrow windows. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  column-gap: var(--space-10);
}
@media (max-width: 1080px) {
  .news-list--grid { column-gap: var(--space-6); }
}
/* Uniform row hairline + even top padding across both columns. */
.news-list--grid > .news-card--list {
  border-bottom: 1px solid var(--white-alpha-8);
}
.news-list--grid > .news-card--list:first-child { padding-top: var(--space-5); }

/* ─────────────────────────────────────────
   RESPONSIVE — LIFECYCLE / PROGRESS + NEWS-LIST-GRID
   NOTE: Original @media (max-width: 768px) block below replaced + expanded.
   ───────────────────────────────────────── */

/* ── Tablet (768–1023) ── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  .news-list--grid {
    column-gap: var(--space-5);
  }
}

/* ── Phone (<768) ── */
@media (max-width: 767.98px) {
  /* News grid: single column (was already in the 768 block — kept here
     at the correct breakpoint 767.98 to close the 768px gap) */
  .news-list--grid {
    grid-template-columns: 1fr;
    column-gap: 0;
  }

  .news-list--grid > .news-card--list:first-child {
    padding-top: 0;
  }

  /* Status tag: stay compact on narrow screens */
  .status-tag {
    padding: var(--space-1) var(--space-3);
    font-size: 11px;
  }

  /* Note footer: tighter on phone */
  .cocreation-card__note {
    font-size: var(--fs-body-sm);
    line-height: var(--lh-body-sm);
    gap: var(--space-2);
  }

  /* CTA row: full-width button feel on phones */
  .cocreation-card__cta-row {
    justify-content: flex-start;
  }
}

/* ── Small phone (<480) ── */
@media (max-width: 480px) {
  .status-tag {
    padding: 3px var(--space-2);
    font-size: 10px;
  }

  /* Note icon: slightly smaller */
  .cocreation-card__note-icon {
    width: 14px;
    height: 14px;
  }
}

/* ─────────────────────────────────────────
   Z-ORIGIN (AI 原力創作計畫) — AIC Lobby (Figma 101:5666)
   Two card families on the shared page-head + glass-tabs layout:
   · .aic-card  — landscape 16:9 competition tile (image + bottom-gradient
                  overlay + status tag + title). Figma AIC-Unit (111:3157).
   · .work-card — portrait 2:3 featured work (poster + status badge + title +
                  creator row + optional vote button). Figma Voting-Desktop.
   ───────────────────────────────────────── */

/* Block spacing between the 徵集 section and the 精選作品 section. */
.zorigin-section + .zorigin-section { margin-top: var(--space-16); }

/* Tabs row: glass-tabs on the left, an action (我的投票) pinned right.
   This row IS the filter bar above the .aic-grid, so it owns its own trailing
   gap (the .tabstick inside it can't — its --filter margin would land between
   the nav and the button, not below the row). Tokenized to match the bare
   .tabstick--filter elsewhere: --space-8 desktop, --space-6 phone (see below). */
.tabs-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-8);
}

/* ── 徵集卡 grid (3-up landscape) ── */
.aic-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-6);
}

.aic-card {
  position: relative;
  display: block;
  aspect-ratio: 343 / 193;
  border: 0.5px solid var(--white-alpha-8);
  border-radius: var(--radius-md);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: transform 250ms ease;
}
/* 4th 徵集卡: shown only at ≥1680 (see wide-screen block) to fill the 4-col
   row; hidden by default so the 3-col layout stays a clean single row. */
.aic-card--xl-only { display: none; }
.aic-card:hover { transform: translateY(-4px); }
.aic-card:hover .aic-card__img { transform: scale(1.04); }

.aic-card__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 400ms ease;
}

/* Bottom gradient holding the tag + title (transparent → black 60%). */
.aic-card__overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-3) var(--space-4);
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 50%);
}

.aic-card__title {
  margin: 0;
  font-family: var(--font-cjk-display);
  font-size: 16px;
  line-height: 24px;
  font-weight: var(--fw-bold);
  color: #fff;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Load-more — centred quiet text button + chevron. */
.load-more {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  margin-top: var(--space-6);
  padding: var(--space-3);
  border: none;
  background: transparent;
  font-family: var(--font-cjk-text);
  font-size: 14px;
  font-weight: var(--fw-medium);
  color: var(--text-secondary);
  cursor: pointer;
  transition: color 160ms ease;
}
.load-more:hover { color: var(--text-primary); }
.load-more__chev { width: 16px; height: 16px; flex-shrink: 0; }

/* ── 精選作品 grid (5-up portrait poster) ── */
.works-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));   /* minmax(0,…) keeps columns
    truly equal — plain 1fr (= minmax(auto,1fr)) lets a column grow to its content's
    min-width, which was inflating column 1 (submit tile + the landscape work-06). */
  gap: var(--space-6);
}

.work-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  text-decoration: none;
  color: inherit;
}

.work-card__poster {
  position: relative;
  display: block;
  aspect-ratio: 256 / 384;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.work-card__poster img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 300ms ease;
}
.work-card:hover .work-card__poster img { transform: scale(1.03); }

/* Status badge bottom-right of the poster (reuses .status-tag glass styles). */
.work-card__badge {
  position: absolute;
  right: 11px;
  bottom: 11px;
}

/* 優勝 — solid yellow winner badge with ink text. */
.status-tag--win {
  background: var(--yellow-500);
  border-color: transparent;
  color: var(--neutral-900);   /* was #0a0a0a (identical). NOTE: semantically this is
                                  text-on-accent → --yellow-ink (#271302) would be more
                                  correct, but that changes the shade — flagged, not applied. */
}

/* Info block below the poster. */
.work-card__title {
  margin: 0;
  font-family: var(--font-cjk-display);
  font-size: 16px;
  line-height: 24px;
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.work-card__meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.work-card__creator {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  flex: 1;
}
.work-card__avatar {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  flex-shrink: 0;
  object-fit: cover;
}
.work-card__creator-name {
  font-family: var(--font-latin);
  font-size: 14px;
  line-height: 23px;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Vote button is a real .btn (btn--yellow-ghost for 投票, btn--ghost for
   改投・收回); this only stops it shrinking in the meta row. */
.work-card__vote { flex-shrink: 0; }

/* 即將截止 — Error glass tag (DS ActivityTag/Error 640:1776): rose tint over
   black + error-red text. Distinct from the highlight-rose ramp. */
.status-tag--error {
  background: linear-gradient(rgba(243, 53, 85, 0.12), rgba(243, 53, 85, 0.12)), rgba(0, 0, 0, 0.6);
  border-color: rgba(243, 53, 85, 0.3);
  color: var(--error-500);
}

/* Small mask icon inside a button — follows the button text colour. */
.btn__icon {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  background-color: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
  mask: var(--icon) center / contain no-repeat;
}

/* ── Z-ORIGIN responsive ── */
@media (max-width: 1100px) {
  .works-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  .aic-grid { grid-template-columns: repeat(2, 1fr); }
  .works-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .aic-grid { grid-template-columns: 1fr; }
  .works-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* .tabs-row stays one row (nav left / 我的投票 right) — no wrap; see ≤767.98 block. */
}

/* ── Z-ORIGIN — extended phone / small-phone responsive ── */

/* Phone ≤767.98px: tabs-row stacks; section spacing tightens. */
@media (max-width: 767.98px) {
  .zorigin-section + .zorigin-section {
    margin-top: var(--space-12);
  }

  /* tabs-row (phone): 我的投票 shares the filter row — nav LEFT, vote RIGHT, one
     row (was wrap → button dropped to its own row, wasting vertical space). The
     base rule's justify-content:space-between does the split. zorigin-only class,
     so no other page's nav layout is touched. */
  .tabs-row {
    flex-wrap: nowrap;
    gap: var(--space-3);
    margin-bottom: var(--space-6);   /* phone: match .tabstick--filter's tighter gap */
  }

  /* The generic sticky-tabs mobile rule stretches the pill to flex:1 1 100% and
     CENTRES it (45-sticky-tabs.css) — that's what forced 我的投票 to wrap. Opt this
     zorigin host out to a left/right split: the pill hugs LEFT and, on overflow,
     SHRINKS + scrolls internally (min-width:0 → filter pills keep priority), while
     我的投票 stays pinned RIGHT and never shrinks (flex:0 0 auto). Keyed to
     .tabs-row (zorigin-only) so every other snapped nav stays centred. Holds at
     rest AND when snapped — both states carry .tabstick-host. */
  .tabs-row.tabstick-host > .tabstick:has(> .glass-tabs) {
    flex: 0 1 auto;
    min-width: 0;
    justify-content: flex-start;
  }
  .tabs-row > .btn {
    flex: 0 0 auto;
  }

  /* aic-grid: single column (extends the 600px rule upward to 767.98px). */
  .aic-grid {
    grid-template-columns: 1fr;
    gap: var(--space-4);
  }

  /* works-grid: 2-up portrait on phone. */
  .works-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-4);
  }

  /* work-card: tighten internal gap. */
  .work-card {
    gap: var(--space-2);
  }

  .work-card__title {
    font-size: var(--fs-body-sm);
    line-height: 1.4;
  }

  .work-card__creator-name {
    font-size: var(--fs-caption);
  }

  /* aic-card title: smaller on phone. */
  .aic-card__title {
    font-size: var(--fs-body-sm);
    line-height: 1.4;
  }
}

/* Small phone ≤480px: works-grid stays 2-up but with tighter gap;
   aic-grid single-col with aspect-ratio preserved. */
@media (max-width: 480px) {
  .works-grid {
    gap: var(--space-3);
  }

  .aic-grid {
    gap: var(--space-3);
  }

  /* work-card meta: hide creator name if very tight — keep avatar + vote. */
  .work-card__creator-name {
    font-size: 11px;
  }

  /* load-more: full-width tap target. */
  .load-more {
    width: 100%;
    padding: var(--space-4);
  }
}

/* ─────────────────────────────────────────
   MOVIE LIBRARY (片庫) — 放映廳 / 資料庫
   Reuses .films-rail + .film-card from the homepage 片庫 section. Cards are
   poster-only here (Figma Movie Library 316:13155 / 316:10158): 放映廳 shows a
   popcorn rental price + optional 最新 ribbon; 資料庫 is plain poster (info only).
   Top bar = view toggle + search; a genre chip row sits above the rails.
   ───────────────────────────────────────── */

/* Library pages lead with a small toggle (no page-head title), so the default
   32px flush-top gap reads as wasted space — pull the whole block up closer
   to the header. */
.section--library { padding-top: var(--space-6); }

/* Top bar: 放映廳/資料庫 toggle, sitting left. */
.library-topbar {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}
/* The toggle borrows base .glass-tabs (12/24 vertical margin); zero it here so
   it doesn't stack on top of the section + topbar spacing. */
.library-topbar .glass-tabs { margin: 0; }

/* Search pill (visual only for now). Mirrors Figma "Search Bar" (343 wide). */
.library-search {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  width: 343px;
  max-width: 100%;
  height: 44px;
  padding: 0 var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--white-alpha-4);
  border: 1px solid var(--neutral-700);
}
.library-search__icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  background-color: var(--text-tertiary);
  -webkit-mask: url('data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M11.44%204.6C7.66238%204.6%204.60001%207.66237%204.60001%2011.44C4.60001%2015.2176%207.66238%2018.28%2011.44%2018.28C13.3026%2018.28%2014.9913%2017.5355%2016.2248%2016.3279C16.2264%2016.3263%2016.2281%2016.3246%2016.2298%2016.323C17.4951%2015.0817%2018.28%2013.3525%2018.28%2011.44C18.28%207.66237%2015.2176%204.6%2011.44%204.6ZM18.3177%2016.9941C19.545%2015.4763%2020.28%2013.544%2020.28%2011.44C20.28%206.5578%2016.3222%202.6%2011.44%202.6C6.55781%202.6%202.60001%206.5578%202.60001%2011.44C2.60001%2016.3222%206.55781%2020.28%2011.44%2020.28C13.4974%2020.28%2015.3907%2019.5771%2016.8928%2018.3984L19.7047%2021.1187C20.1016%2021.5027%2020.7347%2021.4922%2021.1187%2021.0953C21.5027%2020.6984%2021.4922%2020.0653%2021.0953%2019.6813L18.3177%2016.9941Z%22%20fill%3D%22white%22%2F%3E%3C%2Fsvg%3E') center / contain no-repeat;
  mask: url('data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M11.44%204.6C7.66238%204.6%204.60001%207.66237%204.60001%2011.44C4.60001%2015.2176%207.66238%2018.28%2011.44%2018.28C13.3026%2018.28%2014.9913%2017.5355%2016.2248%2016.3279C16.2264%2016.3263%2016.2281%2016.3246%2016.2298%2016.323C17.4951%2015.0817%2018.28%2013.3525%2018.28%2011.44C18.28%207.66237%2015.2176%204.6%2011.44%204.6ZM18.3177%2016.9941C19.545%2015.4763%2020.28%2013.544%2020.28%2011.44C20.28%206.5578%2016.3222%202.6%2011.44%202.6C6.55781%202.6%202.60001%206.5578%202.60001%2011.44C2.60001%2016.3222%206.55781%2020.28%2011.44%2020.28C13.4974%2020.28%2015.3907%2019.5771%2016.8928%2018.3984L19.7047%2021.1187C20.1016%2021.5027%2020.7347%2021.4922%2021.1187%2021.0953C21.5027%2020.6984%2021.4922%2020.0653%2021.0953%2019.6813L18.3177%2016.9941Z%22%20fill%3D%22white%22%2F%3E%3C%2Fsvg%3E') center / contain no-repeat;
}
.library-search__input {
  flex: 1;
  min-width: 0;
  border: none;
  background: none;
  color: var(--text-primary);
  font-family: var(--font-cjk-text);
  font-size: 14px;
}
.library-search__input::placeholder { color: var(--text-tertiary); }
.library-search__input:focus { outline: none; }

/* Content-category row — a tier BELOW the 放映廳/資料庫 toggle, so it reads as
   secondary filtering: smaller text, no glass-pill container, and an active
   state that's a flat neutral chip (no yellow, no glow, no blur). Scoped to
   .library-genres so the toggle's glass look is untouched. */
.library-genres {
  margin: 0 0 var(--space-4);
  padding: 0;
  background: none;
  border: none;
  border-radius: 0;
  gap: var(--space-1);
}
.library-genres .glass-tabs__item {
  height: 32px;
  padding: 0 14px;
  font-size: 14px;
  line-height: 20px;
}
.library-genres .glass-tabs__item--active,
.library-genres .glass-tabs__item--active:hover {
  color: var(--text-primary);
  font-weight: var(--fw-medium);
  background: var(--white-alpha-16);
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
/* Categories not yet open (綜藝 / 動畫) read dimmer, matching V1. */
.library-genres .glass-tabs__item--muted {
  color: var(--text-tertiary);
  cursor: default;
}
.library-genres .glass-tabs__item--muted:hover { color: var(--text-tertiary); }

/* Platform-filter variant (資料庫): each item carries a brand logo + label.
   Logos are pre-rounded app icons, shown small so the row stays a quiet
   secondary filter. */
.library-platforms .glass-tabs__item {
  gap: var(--space-2);
  padding-left: var(--space-2);
  padding-right: 14px;
}
.glass-tabs__logo {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  border-radius: 5px;
  object-fit: cover;
}
/* 臺灣排行 section overrides (scoped — leaves cocreate filter tabs / database
   untouched):
   1. tighten the title → platform-tabs gap
   2. active platform tab uses the neutral GHOST glass instead of yellow-ghost,
      so it never clashes with an OTT's brand color when selected. The Ztor
      tab keeps its yellow Z. chip (that's its logo, not the active treatment). */
[data-screen-label="ranking"] .section-header { margin-bottom: var(--space-5); }
/* 現正熱播 cards have a title row below the poster, so raise the digit by the
   body height (~66px) to land it on the poster bottom — matching the
   poster-only ranked rails which ground at the card bottom (global bottom:0). */
#films-panel-trending .film-card__rank { bottom: 50px; }
/* Platform pill spans the full content width; the options stay left-packed
   (natural order, widened gap) so the right side is just empty pill bg. */
.ranking-platforms {
  margin-top: 0;
  display: flex;
  width: 100%;
  justify-content: flex-start;
  gap: var(--space-4);
}
.ranking-platforms .glass-tabs__item--active,
.ranking-platforms .glass-tabs__item--active:hover {
  color: var(--text-primary);
  background: linear-gradient(0deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.18) 2.45%,
    rgba(255, 255, 255, 0) 126.14%);
  box-shadow:
    rgba(255, 255, 255, 0.9) 1.1px 2.2px 0.5px -1.8px inset,
    rgba(255, 255, 255, 0.9) -1px -2.2px 0.5px -1.8px inset,
    rgba(0, 0, 0, 0.10) 2px 3px 2px 0 inset;
}

.library-row + .library-row { margin-top: var(--space-10); }
/* Category title sits tight above its rail. */
.library-row .page-head { margin-bottom: var(--space-2); }

/* 繼續觀看 rail — 只在登入後顯示;標題列右側放「觀看紀錄 →」link. */
body[data-auth="logged-out"] .library-row--continue { display: none; }
.library-row--continue .page-head { justify-content: space-between; }

/* Corner ribbon on a poster (最新 / 推薦) — dark glass pill + brand-yellow label.
   Glass (not a yellow fill) so it reads on any poster art and keeps yellow as the
   "act here" CTA signal. White-on-blur fallback where backdrop-filter is unsupported. */
.film-card__ribbon {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  padding: var(--space-1) var(--space-2);
  border-bottom-right-radius: var(--radius-md);
  background: rgba(10, 10, 10, 0.6);
  backdrop-filter: blur(10px) saturate(1.1);
  -webkit-backdrop-filter: blur(10px) saturate(1.1);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);  /* hairline so it defines on bright posters */
  color: var(--yellow-500);                              /* brand yellow #ffa33f — the label is the brand tie */
  font-family: var(--font-cjk-text);
  font-size: 12px;
  line-height: 18px;
  font-weight: var(--fw-medium);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .film-card__ribbon { background: rgba(10, 10, 10, 0.86); }  /* solid ink so yellow stays legible */
}

@media (max-width: 767.98px) {
  .library-topbar { flex-wrap: wrap; }
  .library-search { width: 100%; }
}

/* ─────────────────────────────────────────
   MOVIE LIBRARY — RESPONSIVE
   Phone  : max-width 767.98px
   Tablet : 768px – 1023.98px
   ───────────────────────────────────────── */

/* ── Tablet (768px – 1023.98px) ─────────────────────────────────────────── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  /* Give the library section more breathing room after the header */
  .section--library { padding-top: var(--space-5); }

  /* Search: allow it to grow but cap at 280px so it doesn't eat the toggle */
  .library-search { width: clamp(220px, 40%, 280px); }

  /* Genre chips: horizontal scroll if they overflow — avoids wrapping chaos */
  .library-genres {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    margin-bottom: var(--space-3);
  }
  .library-genres::-webkit-scrollbar { display: none; }

  /* Library rows: tighten vertical rhythm slightly */
  .library-row + .library-row { margin-top: var(--space-8); }
}

/* ── Phone (≤767.98px) ──────────────────────────────────────────────────── */
@media (max-width: 767.98px) {
  /* Less top padding on phone — the header is shorter and closer */
  .section--library { padding-top: var(--space-4); }

  /* Topbar: toggle on first row, search on second row */
  .library-topbar {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
  }

  /* Search: full-width on phone, touch target already 44px tall */
  .library-search { width: 100%; }

  /* Genre chip row: horizontal scroll, no wrap */
  .library-genres {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    margin-bottom: var(--space-3);
    /* Negative horizontal margin so chips bleed to the section edge */
    margin-left: calc(var(--container-padding-x) * -1);
    margin-right: calc(var(--container-padding-x) * -1);
    padding-left: var(--space-4);
    padding-right: var(--space-4);
  }
  .library-genres::-webkit-scrollbar { display: none; }

  /* Platform filter row: same horizontal scroll treatment */
  .library-platforms {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    margin-left: calc(var(--container-padding-x) * -1);
    margin-right: calc(var(--container-padding-x) * -1);
    padding-left: var(--space-4);
    padding-right: var(--space-4);
  }
  .library-platforms::-webkit-scrollbar { display: none; }

  /* Ranking platform row (Taiwan's Top 10): scrollable, but the first chip
     ALIGNS with the section content (not flush to the screen edge). Overrides
     the full-bleed negative margins inherited from .library-platforms. */
  .ranking-platforms {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    gap: var(--space-2);
    margin-left: 0;
    margin-right: 0;
    padding-left: var(--container-padding-x, var(--space-5));
    padding-right: var(--container-padding-x, var(--space-5));
  }
  .ranking-platforms::-webkit-scrollbar { display: none; }

  /* Library rows: tighter vertical spacing */
  .library-row + .library-row { margin-top: var(--space-6); }

  /* Continue-watching row stays hidden when logged out (unchanged), but
     when shown, its page-head title needs to wrap on phone */
  .library-row--continue .page-head { flex-wrap: wrap; gap: var(--space-2); }

  /* Ribbon: touch target safe — it's decorative only, no tap action */
  .film-card__ribbon { font-size: 11px; padding: 3px 6px; }
}

/* ── Small phone (≤479.98px) ────────────────────────────────────────────── */
@media (max-width: 479.98px) {
  .library-topbar { gap: var(--space-2); }
  .library-genres {
    /* Tighter start padding on very small screens */
    padding-left: var(--space-3);
    padding-right: var(--space-3);
  }
}

/* ── Platform filter on phone: ICON-ONLY chips, with the active chip expanding
   to show its name. All 8 platforms now fit with NO horizontal scroll — the
   chips share the row width instead of overflowing. Appended last so it wins
   the cascade over the scroll treatment above. (.library-platforms = 資料庫 /
   database; .ranking-platforms = home Top-10.) ── */
@media (max-width: 767.98px) {
  .library-platforms,
  .ranking-platforms {
    flex-wrap: nowrap;
    overflow-x: hidden;                 /* no scroll — chips flex to fit */
    margin-left: 0; margin-right: 0;
    padding-left: 0; padding-right: 0;
    gap: var(--space-1);
    width: 100%;
  }

  /* Icon-only: equal flex columns, logo centred, text node hidden via font-size:0
     (the <img> logo keeps its fixed size). */
  .library-platforms .glass-tabs__item,
  .ranking-platforms .glass-tabs__item {
    flex: 1 1 0;
    min-width: 0;
    gap: 0;
    padding: 0;
    font-size: 0;
    justify-content: center;
  }

  /* Active chip: reveal icon + name. */
  .library-platforms .glass-tabs__item--active,
  .ranking-platforms .glass-tabs__item--active {
    flex: 0 0 auto;
    gap: var(--space-2);
    padding: 0 14px 0 10px;
    font-size: var(--fs-body-sm);
  }
}

/* ─────────────────────────────────────────
   WIDE-SCREEN TWEAKS  ─  single breakpoint @ 1680px
   Above 1680px the default 1440-capped layout leaves big side gutters, so
   per-page we either grow the container (共創 / zorigin — more columns) or go
   full-bleed (片庫 — Netflix/YouTube style). News / events keep the 1440 cap
   (container--wide untouched there) per request. Placed at file end so these
   overrides win the cascade over the base grid definitions above.
   ───────────────────────────────────────── */
@media (min-width: 1680px) {

  /* 共創 + zorigin: grow the container well past 1440 so the extra column keeps
     the cards near their 1440 size (≈432px at 3-col → ≈418px at 4-col here),
     not shrunken. Contained, not full-bleed — caps at 1840 then centres. */
  .container--wide-grow { max-width: 1840px; }

  /* Scoped to .container--wide-grow so ONLY the cocreate / zorigin pages widen.
     The 焦點 home previews reuse .cocreation-grid in a normal container and must
     stay 3-up (otherwise 3 preview cards leave an empty 4th column). */
  /* 影視共創頁: 共創卡 3 → 4 */
  .container--wide-grow .cocreation-grid { grid-template-columns: repeat(4, 1fr); }

  /* AI 原力創作計畫: 活動卡 3 → 4，精選作品 5 → 6 */
  .container--wide-grow .aic-grid   { grid-template-columns: repeat(4, 1fr); }
  .container--wide-grow .works-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  /* The 4th 徵集卡 only exists to fill the 4-col row here — hidden below 1680
     so the 3-col layout doesn't leave an orphan on a second row. */
  .container--wide-grow .aic-card--xl-only { display: block; }

  /* 片庫: full-bleed Netflix/YouTube feel — drop the 1440 cap, keep the 48px
     side padding (from .container). At this width 6 posters are too big, so the
     rails go to 8 across (ranked Top-N shelf to 6). Scoped to .section--library
     so the 焦點 home rail (also .films-rail, but 1440-capped) stays untouched. */
  .section--library .container--wide { max-width: none; }
  .section--library .films-rail__track { --rail-cols: 8; }
  .section--library .films-rail__track--ranked { --rail-cols: 6; }
}

/* ─────────────────────────────────────────
   MATERIAL FALLBACKS — liquid glass degrades honestly
   ───────────────────────────────────────── */

/* No frost without contrast: solid fill replaces transparency */
@media (prefers-reduced-transparency: reduce) {
  .btn--ghost,
  .btn--yellow-ghost,
  .btn--destructive,
  .btn--quiet,
  .icon-btn {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: rgba(20, 20, 20, 0.95);
  }
  /* Lens-based glass (ghost button + 計畫與價格 pill) carries its frost on a
     ::before; neutralise that too, or the transparency survives the fallback. */
  .btn--ghost::before,
  .popcorn-section::before {
    background: rgba(20, 20, 20, 0.95);
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .btn--ghost::after,
  .popcorn-section::after { opacity: 0; }
  .popcorn-section { background: transparent; }
}

/* Motion collapses to instant state changes */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* ─────────────────────────────────────────
   TOP-10 RANKING — two fixed rows of five
   Scoped to the homepage ranking section only; other ranked
   rails (library 現正熱播) keep their horizontal scroll.
   ───────────────────────────────────────── */
section[data-screen-label="ranking"] .films-rail__track--ranked {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  row-gap: var(--space-10);                 /* room for the oversized rank digits */
  overflow: visible;             /* rank "1" overhang no longer clipped */
  scroll-snap-type: none;
  margin-left: 0;
  padding-left: var(--space-6);            /* keep the #1 digit clear of the edge */
}

section[data-screen-label="ranking"] .film-card {
  width: auto;                   /* grid cell sizes the card, not --card-w */
  flex: none;
}

/* Rail arrows are pointless on a non-scrolling grid */
section[data-screen-label="ranking"] .films-rail__nav { display: none !important; }

@media (max-width: 767.98px) {
  section[data-screen-label="ranking"] .films-rail__track--ranked {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* ─────────────────────────────────────────
   TOP-10 RANKING — RESPONSIVE
   Phone  : max-width 767.98px
   Tablet : 768px – 1023.98px
   ───────────────────────────────────────── */

/* ── Tablet (768px – 1023.98px) ─────────────────────────────────────────── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  section[data-screen-label="ranking"] .films-rail__track--ranked {
    /* 3 columns on tablet: 5 is too tight, 2 is too sparse */
    grid-template-columns: repeat(3, minmax(0, 1fr));
    row-gap: var(--space-8);
    padding-left: var(--space-4);
  }
}

/* ── Phone (≤767.98px) ──────────────────────────────────────────────────── */
@media (max-width: 767.98px) {
  section[data-screen-label="ranking"] .films-rail__track--ranked {
    /* 2 columns on phone: cards share the viewport evenly */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    row-gap: var(--space-8);
    /* Reduce left padding: rank digit is smaller at this size */
    padding-left: var(--space-5);
    /* Rank digits are cqi-based and shrink with the card; overhang stays in-grid */
    overflow: visible;
  }

  /* Rank-section header: tighten margin for phone */
  section[data-screen-label="ranking"] .section-header {
    margin-bottom: var(--space-4);
  }
}

/* ── Small phone (≤479.98px) ────────────────────────────────────────────── */
@media (max-width: 479.98px) {
  section[data-screen-label="ranking"] .films-rail__track--ranked {
    row-gap: var(--space-6);
    padding-left: var(--space-4);
  }
}

/* ─────────────────────────────────────────
   MOBILE TWEAKS — single breakpoint @ 768px
   IMPORTANT: this block MUST stay at the END of the file. It overrides
   component rules of equal specificity, so it only wins by source order.
   (It previously sat mid-file and every rule below its components was
   silently dead — do not move it back up.)
   Quick adaptation to make the desktop layout legible on phones for
   demo. Not a full mobile design — future sprint will redesign per
   section (proper nav, hero crop, card hierarchy, etc.).
   ───────────────────────────────────────── */

@media (max-width: 767.98px) {

  /* Tighter horizontal padding for all .container / sections */
  :root { --container-padding-x: 20px; }

  /* Header — hide nav links, keep logo + actions; tighter padding */
  .header__nav-links { display: none; }
  .header { padding-inline: var(--space-4); }
  .header__actions { gap: var(--space-3); }

  /* Section rhythm now comes from the responsive --space-section token
     (tokens.css), so .section is NOT re-declared here — re-declaring it broke
     .section--flush-top on mobile (re-added top padding after heroes). */

  /* Section header — stack title row, scale down heading */
  .section-header { margin-bottom: 36px; }
  .section-header__title { font-size: clamp(28px, 7vw, 36px); line-height: 1.3; }
  .section-header__row {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-4);
  }

  /* Hero carousel — shorter, scaled text + drag-follow touch setup */
  .hero-carousel {
    height: clamp(420px, 70vw, 560px);
    /* Allow vertical pan to pass through to the page scroll naturally.
       JS only calls preventDefault() once a horizontal gesture is locked —
       this tells the browser the default vertical scroll is safe to start
       without waiting for the JS passive-listener handshake. */
    touch-action: pan-y;
  }
  .hero-carousel__title { font-size: clamp(28px, 7.5vw, 40px); }
  .hero-carousel__sub { font-size: 14px; }
  .hero-carousel__ctas { gap: 10px; }
  .hero-carousel__nav--prev { left: 12px; }
  .hero-carousel__nav--next { right: 12px; }
  /* Mobile copy-vs-edge safety: the copy is left-aligned starting at the
     container padding (~20px), so a wide left blur eats the headline's first
     characters. Narrow the LEFT edge to 16px (clears the copy; still softens
     the frame edge as banners slide through). Extra specificity (.hero-carousel
     prefix) so this wins over the base clamp(44px…) regardless of bundle order.
     Right edge keeps its default width — no copy ever sits there. */
  .hero-carousel .hero-carousel__edge--l { width: 16px; }

  /* 影視共創 — 3 col → 1 col */
  .cocreation-grid { grid-template-columns: 1fr; gap: var(--space-5); }

  /* AI 原力 — 2 col → 1 col (mosaic 內保持 2 col) */
  .ai-mosaic { grid-template-columns: 1fr; gap: 28px; }

  /* 電影庫 — rail shows ~2.4 cards (peek next); ranked ~1.5.
     Rank digit size/offsets need no mobile override anymore — they're
     in cqi units and scale with the card automatically. */
  .films-rail__track { --rail-cols: 2.4; --rail-gap: 12px; }
  .films-rail__track--ranked { --rail-cols: 1.5; }
  .film-card { --card-w: 144px; }   /* smaller on phones (responsive, not drift) — ~2.4 cards peek */
  .film-card--ranked { --card-w: calc(144px + 48px); }   /* poster stays 144px; 48px rank gutter */
  .films-rail__tabs { overflow-x: auto; scrollbar-width: none; }
  .films-rail__tabs::-webkit-scrollbar { display: none; }
  .film-card--ranked { padding-left: var(--space-12); }

  /* 社群 — 3 col → 1 col */
  .community-grid { grid-template-columns: 1fr; gap: var(--space-5); }

  /* 新聞 — 2 col → stack; list card 圖左 38% */
  .news-grid { grid-template-columns: 1fr; gap: 28px; }
  .news-card--hero .news-card__title { font-size: 20px; }
  .news-card--hero .news-card__body { padding: var(--space-5); }
  .news-card--list { gap: 14px; padding: var(--space-4) 0; }
  .news-card--list .news-card__cover { flex: 0 0 38%; }
  .news-card--list .news-card__title { font-size: 14px; line-height: 1.45; }

  /* 同場加映 — 3 col → 1 col, card 自然 hug */
  .activity-grid { grid-template-columns: 1fr; gap: var(--space-4); }
  .activity-card { min-height: auto; padding: var(--space-6); }

  /* Footer — 5 col → 1 col stack */
  .site-footer { padding: var(--space-14) 0 var(--space-6); }
  .site-footer__top { grid-template-columns: 1fr; gap: 28px; }
  .site-footer__brand { max-width: 100%; }
  .site-footer__divider { margin: var(--space-8) 0 var(--space-5); }
  .site-footer__bottom-row {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-4);
  }
  .site-footer__notice { max-width: 100%; }
}

/* ─────────────────────────────────────────
   SIDE RAIL — 主導覽 (board variant, 2026-06-11)
   Replaces the horizontal header nav. A fixed near-black rail on the
   left: wordmark up top, seven destinations as icon + 2-character
   label, nothing else. The header stays as a slim utility bar
   (search / language / login) with its nav links hidden.
   Motion per Animation Director spec: zero entrance animation (a rail
   seen 100+ times/day renders in place), opacity-only hover, static
   active state, instant focus. Icons are the existing house set via
   mask — no foreign icon family.
   ───────────────────────────────────────── */

:root { --rail-w: 78px; }

body.has-rail { padding-left: var(--rail-w); }

/* Header is the full-width top deck: identity (left), persistent
   search (center), utilities (right). Nav links stay hidden — the rail
   below owns navigation. The negative margin cancels the body's rail
   offset so the bar spans edge to edge. */
body.has-rail .header__nav-links { display: none; }
body.has-rail .header { margin-left: calc(-1 * var(--rail-w)); }
body.has-rail .header__logo,
body.has-rail .header__logo-img { height: 32px; }  /* identity reads at deck width */
/* The wordmark's z sits flush over the rail icons' left edge.
   Icon edge = (rail 84 − item 64)/2 + (item 64 − icon 22)/2 = 31px from
   the viewport; subtract the container padding the header applies. */
body.has-rail .header__logo {
  /* Icon edge = (rail-w − item-w)/2 + (item-w − icon-w)/2 from viewport left.
     With rail 78, item 62, icon 22: (78−62)/2 + (62−22)/2 = 8 + 20 = 28px.
     Original calc((rail-w − 72)/2 + 23) stays correct — at 78px: (6/2)+23=26px.
     Keeping the formula as-is; it auto-derives from --rail-w. */
  margin-left: calc((var(--rail-w) - 72px) / 2 + 23px - var(--container-padding-x));
}
/* The icon-button search is absorbed by the persistent field. Target it by
   DOM position, not aria-label — the label is localised ("搜尋" / "Search"),
   so an attribute selector silently stopped matching in English and the
   duplicate magnifier reappeared on top of the field. The search button is the
   only direct .header__icon-btn child of .header__actions (lang sits inside
   .header__popover-anchor; history/notif inside .header__buttons). */
body.has-rail .header__actions > .header__icon-btn { display: none; }

/* Persistent search — an important feature deserves a field, not an
   icon. Long on purpose: it owns the header's center. */
.header-search {
  /* The field scales with the window: grows to 820px on wide decks,
     shrinks before anything else hides. min-width is the floor where
     a search field stops being usable. */
  flex: 1 1 160px;
  min-width: 130px;
  max-width: 720px;   /* 配合社群頁下滑時搜尋欄的置中感(Dolly sidebar 版) */
  height: 40px;
  margin: 0 clamp(12px, 3vw, 56px);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 18px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid var(--white-alpha-16);
  transition: border-color 150ms cubic-bezier(0.23, 1, 0.32, 1),
              background-color 150ms cubic-bezier(0.23, 1, 0.32, 1);
}
.header-search:focus-within {
  border-color: var(--yellow-500);
  background: rgba(255, 255, 255, 0.12);
}
.header-search__icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  background: var(--neutral-400);
  -webkit-mask: url('data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M11.44%204.6C7.66238%204.6%204.60001%207.66237%204.60001%2011.44C4.60001%2015.2176%207.66238%2018.28%2011.44%2018.28C13.3026%2018.28%2014.9913%2017.5355%2016.2248%2016.3279C16.2264%2016.3263%2016.2281%2016.3246%2016.2298%2016.323C17.4951%2015.0817%2018.28%2013.3525%2018.28%2011.44C18.28%207.66237%2015.2176%204.6%2011.44%204.6ZM18.3177%2016.9941C19.545%2015.4763%2020.28%2013.544%2020.28%2011.44C20.28%206.5578%2016.3222%202.6%2011.44%202.6C6.55781%202.6%202.60001%206.5578%202.60001%2011.44C2.60001%2016.3222%206.55781%2020.28%2011.44%2020.28C13.4974%2020.28%2015.3907%2019.5771%2016.8928%2018.3984L19.7047%2021.1187C20.1016%2021.5027%2020.7347%2021.4922%2021.1187%2021.0953C21.5027%2020.6984%2021.4922%2020.0653%2021.0953%2019.6813L18.3177%2016.9941Z%22%20fill%3D%22white%22%2F%3E%3C%2Fsvg%3E') center / contain no-repeat;
          mask: url('data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M11.44%204.6C7.66238%204.6%204.60001%207.66237%204.60001%2011.44C4.60001%2015.2176%207.66238%2018.28%2011.44%2018.28C13.3026%2018.28%2014.9913%2017.5355%2016.2248%2016.3279C16.2264%2016.3263%2016.2281%2016.3246%2016.2298%2016.323C17.4951%2015.0817%2018.28%2013.3525%2018.28%2011.44C18.28%207.66237%2015.2176%204.6%2011.44%204.6ZM18.3177%2016.9941C19.545%2015.4763%2020.28%2013.544%2020.28%2011.44C20.28%206.5578%2016.3222%202.6%2011.44%202.6C6.55781%202.6%202.60001%206.5578%202.60001%2011.44C2.60001%2016.3222%206.55781%2020.28%2011.44%2020.28C13.4974%2020.28%2015.3907%2019.5771%2016.8928%2018.3984L19.7047%2021.1187C20.1016%2021.5027%2020.7347%2021.4922%2021.1187%2021.0953C21.5027%2020.6984%2021.4922%2020.0653%2021.0953%2019.6813L18.3177%2016.9941Z%22%20fill%3D%22white%22%2F%3E%3C%2Fsvg%3E') center / contain no-repeat;
}
.header-search:focus-within .header-search__icon { background: var(--yellow-500); }
.header-search__input {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: none;
  outline: none;
  color: var(--neutral-white);
  /* Display tier — search bar is header chrome (Nav/Item family), so CJK
     renders in LINE Seed like the web/sidebar build (was --font-cjk-text=Noto).
     Token, not a hardcoded 'LINE Seed TW' string: Latin/numbers stay Proxima. */
  font-family: var(--font-cjk-display);
  font-size: 16px;
}
.header-search__input::placeholder { color: var(--neutral-400); }
.header-search__input::-webkit-search-cancel-button { display: none; }

/* Search field sits IN FLOW between the nav cluster (left) and the utility
   cluster (right), flexing to fill whatever space is left. Auto-width by
   construction: it grows to the 720 cap on wide decks and shrinks first on
   laptops, and because flex items can't overlap their siblings it can NEVER
   collide with the right-hand actions.

   (Was: absolute-centred on the content axis with a hard-coded width budget,
   width = min(720, 100vw − 910). The 910 assumed a ~322px utility cluster,
   but the logged-in / English cluster is ~409px wide, so the centred field
   ran under the action icons at laptop widths. Auto-flex removes the magic
   numbers entirely.) */
body.has-rail .header-search {
  flex: 1 1 auto;
  min-width: 130px;
  max-width: 720px;
  margin: 0 clamp(16px, 4vw, 56px);
}

.side-rail {
  position: fixed;
  inset: var(--header-height) auto 0 0;
  width: var(--rail-w);
  z-index: var(--z-siderail);  /* beneath the full-width header (100) */
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 16px 0;   /* --space-4; rail items start level with the 放映廳 toggle */
  background: var(--neutral-900);
  border-right: 1px solid var(--border-subtle);  /* same token as the header hairline — one line, not two */
}

/* Logo lives in the header now — the rail is navigation only. */
.side-rail__logo { display: none; }

.side-rail__nav {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;  /* --space-2 */
  width: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
}
.side-rail__nav::-webkit-scrollbar { display: none; }

.side-rail__item {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  width: 62px;
  padding: 8px 0;
  border-radius: var(--radius-md);  /* 8px */
  color: var(--neutral-white);
  opacity: 0.64;
  text-decoration: none;
  transition: opacity 100ms cubic-bezier(0.23, 1, 0.32, 1),
              transform 120ms cubic-bezier(0.23, 1, 0.32, 1);
}

@media (hover: hover) and (pointer: fine) {
  .side-rail__item:hover {
    opacity: 1;
    transition-duration: 150ms, 120ms; /* enter 150ms, exit (above) 100ms */
  }
}

.side-rail__item:active { transform: scale(0.96); }

.side-rail__item:focus-visible {
  outline: 2px solid var(--yellow-500);
  outline-offset: -2px;
  border-radius: 8px;
}

/* Active destination: brand ink on a soft pill behind the glyph (per
   L's mock, 2026-06-11 — replaces the earlier edge bar). Static on
   purpose — each page load is fresh; animating it would be an entrance
   animation by the back door. */
.side-rail__item[aria-current="page"] {
  color: var(--yellow-500);
  opacity: 1;
}
.side-rail__item[aria-current="page"]::before {
  content: '';
  position: absolute;
  left: -8px;              /* item is inset (78−62)/2 = 8px; bar hugs the rail edge */
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 36px;
  border-radius: 0 2px 2px 0;
  background: var(--yellow-500);
}

.side-rail__icon {
  /* --icon-size: optical correction hook. The filled play glyph (焦點)
     carries more ink than its stroke siblings; it renders at 19px so
     the seven icons share one visual weight. */
  width: var(--icon-size, 22px);
  height: var(--icon-size, 22px);
  background: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
          mask: var(--icon) center / contain no-repeat;
}

.side-rail__label {
  font-family: var(--font-cjk-display);
  font-size: 12px;   /* Chinese (base) — CJK reads smaller per glyph, so it's larger than the English label, which is pinned to 11px below (decoupled from the global Afacad +2 per L). */
  line-height: 1;
  letter-spacing: 0.04em;
  white-space: nowrap;  /* prevent wrap on English labels like "Co-Create" */
}

/* Full program names return on hover — the two flagship programs
   (影視共創計畫 / AI 原力創作計畫) compress to 共創 / 創作 in the rail,
   so the flyout restores the differentiator. Only items with a
   data-tip carry one. */
.side-rail__item[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  left: calc(100% + 14px);
  top: 50%;
  transform: translate(-4px, -50%);
  padding: 7px 12px;
  border-radius: var(--radius-pill);
  background: rgba(10, 10, 10, 0.92);
  border: 1px solid rgba(255, 255, 255, 0.12);
  color: var(--neutral-white);
  font-family: var(--font-cjk-display);
  font-size: 12px;
  line-height: 1;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 150ms cubic-bezier(0.23, 1, 0.32, 1),
              transform 150ms cubic-bezier(0.23, 1, 0.32, 1);
}
@media (hover: hover) and (pointer: fine) {
  .side-rail__item[data-tip]:hover::after {
    opacity: 1;
    transform: translate(0, -50%);
  }
}

/* Rail foot — divider + 發表 button after the nav items (ported from
   Dolly's sidebar build). The + reuses the glass FAB language at rail scale. */
.side-rail__foot {
  margin-top: 12px;
  width: calc(100% - 20px);
  display: flex;
  justify-content: center;
  padding-top: 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.10);
}
.side-rail__post {
  width: 36px;
  height: 36px;
  padding: 0;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.side-rail__post .fab__icon { width: 16px; height: 16px; }

@media (prefers-reduced-motion: reduce) {
  .side-rail__item { transition-duration: 100ms; }
  .side-rail__item:active { transform: none; }
  .side-rail__item[data-tip]::after { transition: opacity 100ms ease; transform: translate(0, -50%); }
}

/* ── Phone (<768): the side-rail steps aside for the floating bottom nav
   (43-bottom-nav.css). Placed at end of file so it overrides the base
   .side-rail display above (equal specificity → later wins). Content
   reclaims full width; the header drops its rail offset. ── */
@media (max-width: 767.98px) {
  .side-rail { display: none; }
  body.has-rail { padding-left: 0; }
  body.has-rail .header { margin-left: 0; }
}

/* ── Tablet (768–1023): collapse to an icon-only rail (labels hidden,
   narrower track). Desktop-class nav, not phone chrome. ── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  :root { --rail-w: 62px; }
  .side-rail { width: var(--rail-w); padding-left: 0; padding-right: 0; }
  .side-rail__label { display: none; }
  .side-rail__item { padding-left: 0; padding-right: 0; }
  .side-rail__item[data-tip]::after { display: none; }   /* tooltips off; labels return ≥1024 */
}

/* ── Afacad size compensation (English locale)
   Afacad reads optically smaller than Proxima Nova / Satoshi at the same
   font-size. Rule: wherever Afacad is the rendered family in html[lang="en"],
   add 2px. For the rail label this means 11px base → 13px in English.
   Scoped to html[lang="en"] so CJK (LINE Seed / Proxima) is untouched. ── */
html[lang="en"] .side-rail__label {
  font-size: 11px;   /* English pinned at 11px (stays put); Chinese base is larger at 12px */
}

/* ─────────────────────────────────────────
   HEADER CONTENT SWITCHER — ALL / Music / Films
   Reference: Spotify Home's content-type chips (All/Music/Podcasts),
   re-spoken in the house language: one pill container, a single yellow
   thumb that slides between three equal segments (Animation Director
   spec — transform-only, 200ms house ease, retargets cleanly).
   Presentational in this static preview; selection is local state.
   ───────────────────────────────────────── */
.header-switch {
  --seg: 0;
  position: relative;
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  margin-left: 20px;
  padding: 3px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid var(--white-alpha-8);
}
.header-switch__thumb {
  position: absolute;
  left: 3px;
  top: 3px;
  bottom: 3px;
  width: 64px;
  border-radius: var(--radius-pill);
  background: var(--yellow-500);
  transform: translateX(calc(var(--seg) * 100%));
  transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1);
}
.header-switch__btn {
  position: relative;
  z-index: 1;
  width: 64px;
  height: 30px;
  border: none;
  background: transparent;
  border-radius: var(--radius-pill);
  font-family: var(--font-latin);
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: rgba(255, 255, 255, 0.55);
  cursor: pointer;
  transition: color 200ms cubic-bezier(0.23, 1, 0.32, 1),
              transform 120ms cubic-bezier(0.23, 1, 0.32, 1);
}
@media (hover: hover) and (pointer: fine) {
  .header-switch__btn:not(.is-active):hover { color: rgba(255, 255, 255, 0.92); }
}
.header-switch__btn:active { transform: scale(0.97); }
.header-switch__btn.is-active { color: var(--yellow-ink); }
.header-switch__btn:focus-visible {
  outline: 2px solid var(--yellow-500);
  outline-offset: 2px;
  transition: none;
}
/* Active-segment colour follows the content vertical (same --cat tokens as the
   category pills): 全部 = brand yellow (default) · 音樂 = red · 影視 = orange.
   Driven PURELY by which tab carries .is-active (the switch behaviour already
   toggles it on every page), so the colour lives entirely in this shared
   stylesheet — no per-page JS, no data-* hook. Buttons order: 全部 / 音樂 / 影視. */
.header-switch:has(.header-switch__btn:nth-of-type(2).is-active) .header-switch__thumb { background: var(--cat-music); }  /* 音樂 */
.header-switch:has(.header-switch__btn:nth-of-type(3).is-active) .header-switch__thumb { background: var(--cat-film); }   /* 影視 */
/* On a coloured thumb the active label flips to white (dark ink only reads on yellow). */
.header-switch:has(.header-switch__btn:nth-of-type(2).is-active) .header-switch__btn.is-active,
.header-switch:has(.header-switch__btn:nth-of-type(3).is-active) .header-switch__btn.is-active { color: #fff; }

@media (prefers-reduced-motion: reduce) {
  .header-switch__thumb { transition-duration: 0.01ms; }
  .header-switch__btn { transition: color 100ms ease; }
  .header-switch__btn:active { transform: none; }
}
/* Narrowing ladder: the search compresses first (flex above), then the
   switcher tightens its segments, then the plans pill steps aside.
   The switcher itself never disappears — it's navigation-level state. */
@media (max-width: 1240px) {
  .header-switch { margin-left: 14px; }
  .header-switch__btn { width: 54px; font-size: 12px; }
  .header-switch__thumb { width: 54px; }
}
@media (max-width: 1080px) {
  body.has-rail .header__buttons .popcorn-section { display: none; }
}
@media (max-width: 880px) {
  body.has-rail .header__inner { gap: 12px; }
  body.has-rail .header__logo,
  body.has-rail .header__logo-img { height: 30px; }
}


/* ── EndGame feature sections (shop, creators, leaderboard) ── */
/* ─────────────────────────────────────────
   SHOP v2 — 商店 (hub / 爆米花商店 / 創作者商店 / 活動)
   Rebuilt 2026-06-11 on luxury-PLP research (SSENSE / Net-a-Porter /
   Mr Porter / Farfetch / END live-render audit):
   · cards are chromeless — no border, no panel, no radius, no shadow,
     no hover lift. The image IS the card, locked 2:3 on one matte field.
   · text block: stacked left-aligned lines at ONE size; hierarchy from
     case + color only.
   · grid: 4 columns, tight column gap / wide row gap (editorial bands).
   · filters: plain text links + quiet result count over a hairline.
   Creator storefront re-ordered per Weverse/Fourthwall patterns;
   leaderboard per podium→table→my-rank canon.
   ───────────────────────────────────────── */

/* .glass-tabs--push margin removed 2026-06-23: it added ~36px above the tab bar
   on top of the section's own padding-top, opening the void above the tabs on the
   creator/leaderboard pages. The section padding-top is the single owner of that
   gap now. Class kept on the markup (inert) so ~13 pages need no edit. */
.glass-tabs--push { margin-top: 0; }

/* PLP head — quiet, never a hero */
.shop-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  margin-top: var(--space-2);
}
.shop-head__title {
  font-family: var(--font-cjk-display);
  font-size: var(--fs-page-title);   /* was 20px literal — now the canonical token */
  font-weight: var(--fw-bold);
  letter-spacing: 0.02em;
}
.shop-head__desc {
  font-size: var(--fs-page-desc);    /* was 13px literal */
  color: var(--text-tertiary);
}

/* Toolbar — text-link filters left, count right, one hairline below */
.shop-toolbar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-6);
  margin-top: 30px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--border-subtle);
}
.shop-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 22px;
}
.shop-filter__item {
  background: none;
  border: none;
  padding: 0;
  font-family: var(--font-cjk-text);
  font-size: 13px;
  color: var(--text-tertiary);
  cursor: pointer;
  transition: color 150ms var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .shop-filter__item:hover { color: var(--neutral-white); }
}
.shop-filter__item--active {
  color: var(--neutral-white);
  text-decoration: underline;
  text-decoration-color: var(--yellow-500);
  text-decoration-thickness: 1px;
  text-underline-offset: 8px;
}
.shop-count {
  font-family: var(--font-latin);
  font-size: 12px;
  color: var(--text-tertiary);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* The grid — editorial bands */
.shop-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: var(--space-4);
  row-gap: 44px;
  margin-top: 36px;
}
@media (max-width: 1180px) { .shop-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* Chromeless product card */
.shop-card {
  display: block;
  text-decoration: none;
  color: inherit;
}
.shop-card__media {
  position: relative;
  aspect-ratio: 2 / 3;
  background: var(--bg-secondary);   /* one matte field for every image */
  overflow: hidden;
}
.shop-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.shop-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding-top: var(--space-3);
}
.shop-card__flag {
  font-family: var(--font-latin);
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--yellow-500);
}
.shop-card__cat {
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--text-tertiary);
}
.shop-card__title {
  font-family: var(--font-cjk-text);
  font-size: 13px;
  font-weight: var(--fw-regular);
  line-height: 1.5;
  color: var(--neutral-white);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.shop-card__price {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: 2px;
  font-family: var(--font-latin);
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--neutral-white);
  font-variant-numeric: tabular-nums;
}
.shop-card__price-sub {
  font-weight: var(--fw-regular);
  color: var(--text-tertiary);
}
.shop-card__price-pop-icon {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  background: var(--yellow-500);
  -webkit-mask: url('assets/icons/popcorn_single.svg') center / contain no-repeat;
          mask: url('assets/icons/popcorn_single.svg') center / contain no-repeat;
}
.shop-card__note {
  font-size: 12px;
  color: var(--text-tertiary);
  line-height: 1.5;
}
.shop-card__redeem {
  margin-left: auto;
  font-size: 12px;
  color: var(--yellow-500);
}
/* Sold out: honest dimming, a word instead of a price */
.shop-card--soldout .shop-card__media img,
.shop-card--soldout .brand-tile { filter: grayscale(1) brightness(0.5); }
.shop-card--soldout .shop-card__title { color: var(--text-tertiary); }

/* Brand voucher tiles — typographic gradient tiles, never hotlinked logos */
.brand-tile {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 18px;
  text-align: center;
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: 18px;
  line-height: 1.4;
  color: var(--neutral-white);
}
.brand-tile--1 { background: linear-gradient(135deg, var(--neutral-700), var(--neutral-900)); }
.brand-tile--2 { background: linear-gradient(135deg, var(--yellow-800), var(--neutral-900)); }
.brand-tile--3 { background: linear-gradient(135deg, var(--neutral-600), var(--neutral-800)); }
.brand-tile--4 { background: linear-gradient(160deg, var(--yellow-900), var(--neutral-800)); }
.brand-tile--5 { background: linear-gradient(135deg, var(--neutral-800), var(--yellow-900)); }
.brand-tile--6 { background: linear-gradient(160deg, var(--neutral-700), var(--yellow-800)); }

/* ─────────────────────────────────────────
   RESPONSIVE — shop hub
   ───────────────────────────────────────── */

/* ── Tablet: 768–1023px ── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  .shop-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    column-gap: var(--space-3);
    row-gap: var(--space-10);
  }

  .shop-toolbar {
    gap: var(--space-4);
  }

  .shop-filter {
    gap: var(--space-4);
  }
}

/* ── Phone: ≤767px ── */
@media (max-width: 767.98px) {
  /* Head */
  .shop-head {
    flex-direction: column;
    gap: var(--space-1);
    align-items: flex-start;
  }

  /* Toolbar: filters wrap below count */
  .shop-toolbar {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-3);
    margin-top: var(--space-5);
    padding-bottom: var(--space-3);
  }

  .shop-filter {
    gap: var(--space-4);
    flex-wrap: wrap;
  }

  /* Touch targets: ensure filter buttons are at least 44px tall */
  .shop-filter__item {
    padding-block: 10px;
    font-size: var(--fs-body-sm);
  }

  /* Grid: 2-up on phone */
  .shop-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-3);
    row-gap: var(--space-8);
    margin-top: var(--space-6);
  }

  /* Card body: tighter but legible */
  .shop-card__body {
    gap: var(--space-1);
    padding-top: var(--space-2);
  }

  .shop-card__title {
    font-size: var(--fs-caption);
  }

  .shop-card__price {
    font-size: var(--fs-caption);
    flex-wrap: wrap;
    gap: var(--space-1);
    margin-top: var(--space-1);
  }

  .shop-card__price-sub {
    white-space: nowrap;
  }

  .shop-card__flag,
  .shop-card__cat {
    font-size: 10px;
  }

  .shop-card__note {
    font-size: 11px;
  }

  /* Brand tiles: reduce internal padding so text fits narrow cards */
  .brand-tile {
    font-size: var(--fs-body-sm);
    padding: var(--space-3);
  }
}

/* ── Small phone: ≤480px — 1-up for content-rich cards ── */
@media (max-width: 480px) {
  .shop-grid {
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--space-8);
  }

  /* Single-column: image can be shorter; preserve 2:3 or switch to 3:4 */
  .shop-card__media {
    aspect-ratio: 3 / 4;
  }
}

/* relocated, non-clipped PLP add-to-cart (overrides 36-shop-hub card defaults) */
/* ════════════════════════════════════════════════════════════════
   shop-quickadd.css — PLP add-to-cart, relocated out of the media frame.

   The old quick-add lived INSIDE .shop-card__media (aspect-ratio:2/3;
   overflow:hidden). On touch, refine-shop.css flipped it to position:static
   but it stayed inside that clip box → clipped to zero → invisible AND
   untappable on phones. It now lives in the card FOOTER beside the price:
   quiet + hover-revealed on desktop (keeps the grid editorial), and always
   visible + a real tap target on touch. Add behaviour stays cart.js's
   delegated [data-add]; this file is presentation only. Tokens only.
   ════════════════════════════════════════════════════════════════ */

.shop-card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-2);
}
.shop-card__foot .shop-card__price { min-width: 0; }   /* price takes the row's left */

.shop-card__qa {
  flex: none;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--white-alpha-4);
  color: var(--yellow-500);
  cursor: pointer;
  transition: opacity 200ms var(--ease-out), transform 160ms var(--ease-out),
              background 160ms ease, border-color 160ms ease, color 160ms ease;
}
.shop-card__qa-icon { width: 18px; height: 18px; display: grid; place-items: center; }
.shop-card__qa-icon svg { width: 100%; height: 100%; display: block; }

.shop-card__qa:hover { background: var(--yellow-500); color: var(--yellow-ink); border-color: var(--yellow-500); }
.shop-card__qa:active { transform: scale(0.92); }
.shop-card__qa:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }

/* added confirmation — brief success swap (cart.js + shop-render drive the timing) */
.shop-card__qa.is-added {
  background: var(--success-500);
  border-color: var(--success-500);
  color: var(--neutral-white);
}

/* Desktop (hover-capable, WIDE): quiet — revealed on card hover / keyboard
   focus, so the resting grid stays editorial. NEVER display:none (keeps it
   focusable); opacity + transform only. */
@media (hover: hover) and (pointer: fine) {
  .shop-card__qa { opacity: 0; transform: translateY(4px); }
  .shop-card:hover .shop-card__qa,
  .shop-card:focus-within .shop-card__qa { opacity: 1; transform: translateY(0); }
}

/* Touch OR narrow viewport: always visible — THIS is the mobile fix. Declared
   AFTER the desktop rule so it wins wherever both could match (a real phone is
   pointer:coarse; a narrow window is ≤767.98px even with a fine pointer). The
   site's mobile breakpoint is 767.98px. */
@media (hover: none), (pointer: coarse), (max-width: 767.98px) {
  .shop-card__qa { opacity: 1; transform: none; }
}

/* Reduced motion on a wide desktop: never trap the control behind a reveal
   transition. (Narrow/touch already force it visible above.) */
@media (prefers-reduced-motion: reduce) and (hover: hover) and (pointer: fine) and (min-width: 768px) {
  .shop-card__qa {
    opacity: 1;
    transform: none;
    transition: background 160ms ease, color 160ms ease, border-color 160ms ease;
  }
}

/* ─────────────────────────────────────────
   CREATOR DIRECTORY — chromeless portrait cards
   ───────────────────────────────────────── */
.creator-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: var(--space-4);
  row-gap: 44px;
  margin-top: 36px;
}
@media (max-width: 1180px) { .creator-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.creator-card {
  display: block;
  text-decoration: none;
  color: inherit;
}
.creator-card__media {
  position: relative;
  aspect-ratio: 2 / 3;
  background: var(--bg-secondary);
  overflow: hidden;
}
.creator-card__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.creator-card__cta { display: none; }
.creator-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding-top: var(--space-3);
}
.creator-card__name {
  font-family: var(--font-cjk-display);
  font-size: 15px;
  font-weight: var(--fw-bold);
}
.creator-card__meta {
  display: flex;
  gap: 10px;
  font-size: 12px;
  color: var(--text-tertiary);
}
.creator-card__meta .badge { font-size: 11px; }

/* ─────────────────────────────────────────
   RESPONSIVE — creator directory
   ───────────────────────────────────────── */

/* ── Tablet: 768–1023px ── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  .creator-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    column-gap: var(--space-3);
    row-gap: var(--space-10);
  }
}

/* ── Phone: ≤767px ── */
@media (max-width: 767.98px) {
  /* 2-up portrait grid */
  .creator-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-3);
    row-gap: var(--space-6);
    margin-top: var(--space-6);
  }

  .creator-card__body {
    padding-top: var(--space-2);
    gap: var(--space-1);
  }

  .creator-card__name {
    font-size: var(--fs-body-sm);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  .creator-card__meta {
    font-size: 11px;
    gap: var(--space-2);
    flex-wrap: wrap;
  }
}

/* Container query — card in a narrow sidebar/slot (e.g. creator storefront) */
@supports (container-type: inline-size) {
  .creator-grid {
    container-type: inline-size;
  }

  @container (max-width: 480px) {
    .creator-grid {
      grid-template-columns: repeat(2, minmax(0, 1fr));
      column-gap: var(--space-2);
      row-gap: var(--space-5);
    }
  }
}

/* ─────────────────────────────────────────
   CREATOR STOREFRONT — fan destination, one scroll
   identity header → pickup slot → product grid → events rows →
   top-fans strip → about. No anchor tabs.
   ───────────────────────────────────────── */
.creator-hero {
  position: relative;
  min-height: 300px;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
}
.creator-hero__bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center 25%;
}
.creator-hero__bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, var(--bg-primary) 2%, rgba(10, 10, 10, 0.45) 50%, rgba(10, 10, 10, 0.1));
}
.creator-hero__copy {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-bottom: 26px;
}
.creator-hero__name {
  font-family: var(--font-cjk-display);
  font-size: 40px;
  font-weight: var(--fw-extrabold);
  line-height: 1.15;
  letter-spacing: 0.02em;
}
.creator-hero__tagline {
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
}
.creator-hero__row {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: var(--space-1);
}
.creator-hero__social { display: inline-flex; gap: var(--space-2); }
.creator-hero__social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--glass-bg-default);
  border: 1px solid var(--glass-stroke-default);
  color: var(--neutral-white);
  font-family: var(--font-latin);
  font-size: 10px;
  font-weight: var(--fw-bold);
  text-decoration: none;
  transition: background 200ms var(--ease-out);
}
.creator-hero__social a:hover { background: var(--glass-bg-hover); }
/* Real brand marks, monochrome white (premium > original colours on a dark brand).
   fill:currentColor → the chip's white colour drives them. */
.creator-hero__social a svg { width: 18px; height: 18px; display: block; }
/* 小紅書 = the 红 glyph lifted from the real logo (recoloured white) — a single
   character, so it sits in a normal circle chip exactly like the other icons. */
.creator-hero__social a.creator-hero__social-word img { width: 18px; height: 18px; object-fit: contain; display: block; }

/* Quiet section heading inside the storefront */
.creator-sec {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: var(--space-8);   /* 32px — single owner of the tabs→title gap (was 64px, which stacked with the tabs' own 24px) */
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-subtle);
}
.creator-sec__title {
  font-family: var(--font-cjk-display);
  font-size: 16px;
  font-weight: var(--fw-bold);
  letter-spacing: 0.04em;
  margin: 0;   /* kill the UA <h2> margin (~13px each side) so rhythm comes only from the parent gap */
}
.creator-sec__aside {
  font-size: 12px;
  color: var(--text-tertiary);
}
.creator-sec__link {
  font-size: 13px;
  color: var(--yellow-500);
  text-decoration: none;
}
.creator-sec__link:hover { text-decoration: underline; text-underline-offset: 4px; }

/* Pickup slot — one featured drop, Weverse-style */
.creator-pickup {
  display: flex;
  align-items: center;
  gap: 28px;
  margin-top: 28px;
  padding: 22px 0;
  border-top: 1px solid var(--border-subtle);
  border-bottom: 1px solid var(--border-subtle);
}
.creator-pickup__media {
  width: 180px;
  aspect-ratio: 1 / 1;
  flex-shrink: 0;
  background: var(--bg-secondary);
  overflow: hidden;
}
.creator-pickup__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.creator-pickup__copy { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.creator-pickup__eyebrow {
  font-family: var(--font-latin);
  font-size: 11px;
  letter-spacing: 0.1em;
  color: var(--yellow-500);
}
.creator-pickup__name {
  font-family: var(--font-cjk-text);
  font-size: 17px;
  font-weight: var(--fw-bold);
  line-height: 1.4;
}
.creator-pickup__price {
  font-family: var(--font-latin);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}
.creator-pickup .btn { margin-top: 6px; align-self: flex-start; }

/* Events as quiet rows, not boxes */
.creator-events { margin-top: var(--space-3); }
.creator-event {
  display: flex;
  align-items: baseline;
  gap: var(--space-6);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--border-subtle);
}
.creator-event__date {
  font-family: var(--font-latin);
  font-size: 13px;
  color: var(--yellow-500);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
  width: 150px;
}
.creator-event__title {
  font-family: var(--font-cjk-text);
  font-size: 14px;
  font-weight: var(--fw-medium);
  flex: 1;
  min-width: 0;
}
.creator-event__venue {
  font-size: 12px;
  color: var(--text-tertiary);
}

/* About */
.creator-about {
  max-width: 680px;
  margin-top: var(--space-5);
  color: var(--text-secondary);
  line-height: 1.9;
  font-size: var(--fs-body-sm);
}

/* ─────────────────────────────────────────
   RESPONSIVE — creator storefront
   ───────────────────────────────────────── */

/* ── Tablet: 768–1023px ── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  .creator-hero {
    min-height: 240px;
  }

  .creator-hero__name {
    font-size: clamp(28px, 4vw, 36px);
  }

  .creator-pickup {
    gap: var(--space-5);
  }

  .creator-pickup__media {
    width: 140px;
  }
}

/* ── Phone: ≤767px ── */
@media (max-width: 767.98px) {
  /* Hero: shrink, let bg breathe less */
  .creator-hero {
    min-height: 200px;
  }

  .creator-hero__name {
    font-size: clamp(24px, 7vw, 32px);
  }

  .creator-hero__copy {
    padding-bottom: var(--space-5);
    gap: var(--space-2);
  }

  .creator-hero__row {
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-2);
  }

  /* Social icons: 44px touch targets */
  .creator-hero__social a {
    width: 40px;
    height: 40px;
  }

  /* Section heading */
  .creator-sec {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
    margin-top: var(--space-6);   /* 32 → 24 on phone: tighten the heading gap */
  }

  /* Pickup slot: stack image above copy */
  .creator-pickup {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-4);
    padding: var(--space-4) 0;
  }

  .creator-pickup__media {
    width: 100%;
    max-width: 260px;
    aspect-ratio: 1 / 1;
  }

  .creator-pickup .btn {
    width: 100%;
    justify-content: center;
  }

  /* Events rows: date and title stack */
  .creator-event {
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-4) 0;
    align-items: flex-start;
  }

  .creator-event__date {
    width: auto;
    font-size: var(--fs-caption);
  }

  .creator-event__title {
    font-size: var(--fs-body-sm);
  }
}

/* ── Small phone: ≤480px ── */
@media (max-width: 480px) {
  .creator-pickup__media {
    max-width: 100%;
  }

  .creator-hero__name {
    font-size: clamp(22px, 7vw, 28px);
  }
}

/* ─────────────────────────────────────────
   TOP FANS strip + tier system
   Tiers: INNER CIRCLE ≥80k · SUPER FANS ≥30k · SUPPORTER ≥8k · FANS
   One signifier per avatar: a 2px ring in the tier color. The top tier
   alone earns the filled treatment.
   ───────────────────────────────────────── */
.top-fans {
  display: flex;
  gap: var(--space-6);
  overflow-x: auto;
  scrollbar-width: none;
  padding: var(--space-6) 2px var(--space-2);
}
.top-fans::-webkit-scrollbar { display: none; }
.top-fan {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
  width: 86px;
  text-align: center;
}
.top-fan__avatar {
  position: relative;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  padding: 2px;
  background: var(--border-default);
}
.top-fan--inner .top-fan__avatar     { background: var(--yellow-500); }
.top-fan--super .top-fan__avatar     { background: var(--success-500); }
.top-fan--supporter .top-fan__avatar { background: var(--info-500); }
.top-fan__avatar img {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
  display: block;
  border: 2px solid var(--bg-primary);
}
.top-fan__rank {
  position: absolute;
  top: -5px;
  right: -5px;
  min-width: 20px;
  height: 20px;
  border-radius: var(--radius-pill);
  background: var(--bg-primary);
  border: 1px solid var(--border-default);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-latin);
  font-size: 10px;
  font-weight: var(--fw-bold);
}
.top-fan--inner .top-fan__rank { background: var(--yellow-500); color: var(--yellow-ink); border-color: transparent; }
.top-fan__nick {
  font-size: 12px;
  color: var(--text-secondary);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.top-fan__pts {
  font-family: var(--font-latin);
  font-size: 11px;
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}

/* ─────────────────────────────────────────
   RESPONSIVE — top fans strip
   ───────────────────────────────────────── */

/* ── Tablet: 768–1023px ── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  .top-fans {
    gap: var(--space-5);
  }
}

/* ── Phone: ≤767px ── */
@media (max-width: 767.98px) {
  .top-fans {
    gap: var(--space-4);
    padding: var(--space-5) var(--space-1) var(--space-2);
    /* Snap to each fan tile for easy scrolling */
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
  }

  .top-fan {
    width: 72px;
    scroll-snap-align: start;
  }

  /* Avatar: slightly smaller on phone */
  .top-fan__avatar {
    width: 52px;
    height: 52px;
  }

  .top-fan__nick {
    font-size: 11px;
  }
}

/* ── Small phone: ≤480px ── */
@media (max-width: 480px) {
  .top-fans {
    gap: var(--space-3);
  }

  .top-fan {
    width: 64px;
  }

  .top-fan__avatar {
    width: 48px;
    height: 48px;
  }

  /* Rank badge: scale to avatar */
  .top-fan__rank {
    min-width: 16px;
    height: 16px;
    font-size: 9px;
    top: -4px;
    right: -4px;
  }
}

/* ─────────────────────────────────────────
   LEADERBOARD — podium → table → sticky my-rank
   ───────────────────────────────────────── */
.lb-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: var(--space-2);
}
.lb-head__title {
  font-family: var(--font-cjk-display);
  font-size: 20px;
  font-weight: var(--fw-bold);
}
.lb-head__season {
  font-size: 12px;
  color: var(--text-tertiary);
}
.lb-back {
  font-size: 13px;
  color: var(--yellow-500);
  text-decoration: none;
}
.lb-back:hover { text-decoration: underline; text-underline-offset: 4px; }

/* Tier legend — four quiet columns over a hairline */
.lb-legend {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-8);   /* 32px — on scale (was off-scale 28px) */
  padding-top: 18px;
  border-top: 1px solid var(--border-subtle);
}
.lb-legend__tier { display: flex; flex-direction: column; gap: 5px; }
.lb-legend__note { font-size: 12px; color: var(--text-tertiary); }

/* Tier chip: top tier filled, the rest ringed */
.lb-chip {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  height: 20px;
  padding: 0 9px;
  border-radius: var(--radius-pill);
  font-family: var(--font-latin);
  font-size: 10px;
  font-weight: var(--fw-bold);
  letter-spacing: 0.08em;
  border: 1px solid var(--border-default);
  color: var(--text-secondary);
  white-space: nowrap;
}
.lb-chip--inner     { background: var(--yellow-500); color: var(--yellow-ink); border-color: transparent; }
.lb-chip--super     { border-color: var(--success-500); color: var(--success-500); }
.lb-chip--supporter { border-color: var(--info-500); color: var(--info-500); }

/* Podium — #1 centred and raised */
.lb-podium {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: var(--space-14);
  margin: var(--space-12) 0 var(--space-4);   /* 48 / 16 — on scale (was 56/16) */
}
.lb-podium .top-fan { width: 110px; }
.lb-podium .top-fan__avatar { width: 84px; height: 84px; }
.lb-podium .top-fan--first .top-fan__avatar { width: 108px; height: 108px; }
.lb-podium .top-fan__nick { font-size: 13px; color: var(--neutral-white); }
.lb-podium .top-fan__pts { font-size: 12px; }

/* Table */
.lb-table { margin-top: var(--space-6); }   /* 24px — on scale (was 28px) */
.lb-row {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 13px 0;
  border-top: 1px solid var(--border-subtle);
}
.lb-row__rank {
  font-family: var(--font-latin);
  font-size: 13px;
  color: var(--text-tertiary);
  width: 36px;
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}
.lb-row__avatar {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  padding: 2px;
  background: var(--border-default);
  flex-shrink: 0;
}
.lb-row--inner .lb-row__avatar     { background: var(--yellow-500); }
.lb-row--super .lb-row__avatar     { background: var(--success-500); }
.lb-row--supporter .lb-row__avatar { background: var(--info-500); }
.lb-row__avatar img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block; border: 2px solid var(--bg-primary); }
.lb-row__nick {
  flex: 1;
  min-width: 0;
  font-family: var(--font-cjk-text);
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lb-row__pts {
  font-family: var(--font-latin);
  font-size: 14px;
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
}

/* My rank — pinned, auth-gated */
/* MY RANK — floating snackbar. Fixed near the bottom so the user always sees
   their rank AND the gap to the next tier (the climb incentive). Auth-gated:
   a visitor has no "my rank". */
.lb-me {
  position: fixed;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  z-index: 60;
  width: min(560px, calc(100vw - 120px));   /* narrower — content-sized, no dead space */
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) 18px;
  border-radius: var(--radius-lg);
  background: rgba(18, 18, 18, 0.88);
  border: 1px solid var(--border-default);
  backdrop-filter: blur(18px) saturate(1.2);
  -webkit-backdrop-filter: blur(18px) saturate(1.2);
}
/* recentre into the content column, past the left side-rail */
body.has-rail .lb-me { left: calc(50% + 36px); }

.lb-me__rank {
  font-family: var(--font-latin);
  font-weight: var(--fw-bold);
  font-size: 16px;
  color: var(--yellow-500);
  min-width: 30px;
  text-align: center;
  flex-shrink: 0;
}
.lb-me .lb-row__avatar,
.lb-me .lb-row__avatar img { width: 38px; height: 38px; border-radius: 50%; flex-shrink: 0; }
.lb-me .lb-row__avatar img { object-fit: cover; display: block; }

/* Two compact rows: identity + score, then progress + next-tier target. */
.lb-me__main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 7px; }
.lb-me__line { display: flex; align-items: center; gap: var(--space-2); }
.lb-me__nick { flex: 1; min-width: 0; font-family: var(--font-cjk-text); font-size: 14px; font-weight: var(--fw-medium); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lb-me .lb-chip { flex-shrink: 0; }
.lb-me__score { flex-shrink: 0; margin-left: auto; font-family: var(--font-cjk-text); font-size: 12px; color: var(--text-tertiary); white-space: nowrap; }
.lb-me__score b { font-family: var(--font-latin); font-size: 15px; color: var(--text-primary); font-variant-numeric: tabular-nums; }

.lb-me__line--climb { gap: 10px; }
.lb-me__bar { flex: 1; min-width: 0; height: 6px; border-radius: 999px; background: rgba(255, 255, 255, 0.1); overflow: hidden; }
.lb-me__bar-fill { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--yellow-600), var(--yellow-400)); box-shadow: 0 0 10px rgba(255, 163, 63, 0.55); }
.lb-me__next { flex-shrink: 0; max-width: 58%; font-family: var(--font-cjk-text); font-size: 11.5px; color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lb-me__next b { color: var(--yellow-500); font-family: var(--font-latin); }

/* clearance so the floating card never covers the last rows (logged-in only) */
body[data-auth="logged-in"].rf-leaderboard main { padding-bottom: 130px; }

@media (max-width: 767.98px) {
  /* Float ABOVE the bottom-nav pill (pill ≈ 12px + ~53px tall) so the two
     never collide — this was the overlap in the screenshot. */
  .lb-me, body.has-rail .lb-me {
    left: 50%; width: calc(100vw - 24px);
    bottom: calc(84px + env(safe-area-inset-bottom, 0px));
    gap: 10px; padding: 11px var(--space-4);
  }
  /* Extra clearance so the last rows are never hidden behind the floating card. */
  body[data-auth="logged-in"].rf-leaderboard main { padding-bottom: calc(188px + env(safe-area-inset-bottom, 0px)); }
}

/* Ways to earn — verb rows with point chips */
.lb-earn { margin-top: var(--space-4); max-width: 560px; }
.lb-earn__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--border-subtle);
}
.lb-earn__verb { font-size: 14px; }
.lb-earn__pts {
  font-family: var(--font-latin);
  font-size: 12px;
  font-weight: var(--fw-bold);
  color: var(--yellow-500);
}

/* Shop v2 mobile collapse — kept below the global MOBILE TWEAKS block
   on purpose (source-order rule from the README). */
@media (max-width: 767.98px) {
  .shop-grid, .creator-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 10px; row-gap: 30px; }
  .shop-toolbar { flex-direction: column; gap: var(--space-3); }
  .creator-hero__name { font-size: 30px; }
  .creator-pickup { flex-direction: column; align-items: flex-start; }
  .lb-legend { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lb-podium { gap: var(--space-6); }
  .creator-event { flex-wrap: wrap; }
  .creator-event__date { width: auto; }
}

/* ─────────────────────────────────────────
   LEADERBOARD — RESPONSIVE
   ───────────────────────────────────────── */

/* Tablet (768–1023): tighten podium gap and table row padding. */
@media (min-width: 768px) and (max-width: 1023.98px) {
  .lb-podium {
    gap: var(--space-8);
    margin: var(--space-10) 0 var(--space-4);
  }

  .lb-legend {
    gap: var(--space-4);
  }
}

/* Phone ≤767.98px */
@media (max-width: 767.98px) {
  /* Head row: stack title and season on one side, keep back link. */
  .lb-head {
    flex-wrap: wrap;
    gap: var(--space-2);
  }

  .lb-head__title {
    font-size: var(--fs-h4);
  }

  /* Podium: scale avatars down, reduce gap and top margin. */
  .lb-podium {
    gap: var(--space-6);
    margin: var(--space-8) 0 var(--space-4);
  }

  .lb-podium .top-fan {
    width: 80px;
  }

  .lb-podium .top-fan__avatar {
    width: 62px;
    height: 62px;
  }

  .lb-podium .top-fan--first .top-fan__avatar {
    width: 80px;
    height: 80px;
  }

  .lb-podium .top-fan__nick {
    font-size: var(--fs-caption);
  }

  /* Legend: two columns at 768px already set; keep it consistent. */
  .lb-legend {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-4);
    margin-top: var(--space-6);
  }

  /* Table rows: reduce gap and avatar so rank/name/points stay on one line. */
  .lb-row {
    gap: var(--space-3);
    padding: var(--space-3) 0;
  }

  .lb-row__rank {
    width: 28px;
    font-size: var(--fs-caption);
  }

  .lb-row__avatar {
    width: 32px;
    height: 32px;
  }

  .lb-row__nick {
    font-size: var(--fs-caption);
  }

  .lb-row__pts {
    font-size: var(--fs-caption);
  }

  /* Ways-to-earn rows: let the label wrap if needed. */
  .lb-earn__row {
    gap: var(--space-3);
  }

  .lb-earn__verb {
    font-size: var(--fs-caption);
  }
}

/* Small phone ≤480px: compress further; legend goes single-col. */
@media (max-width: 480px) {
  .lb-legend {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3);
  }

  .lb-podium {
    gap: var(--space-5);
  }

  .lb-podium .top-fan {
    width: 68px;
  }

  .lb-podium .top-fan__avatar {
    width: 52px;
    height: 52px;
  }

  .lb-podium .top-fan--first .top-fan__avatar {
    width: 68px;
    height: 68px;
  }

  /* Stack climb label and points vertically when space is very tight. */
  .lb-me__climb-top {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-1);
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   GAMIFICATION LAYER — make the leaderboard a climb-the-ranks dopamine hub.
   Real medal podium (pedestals + crown + gold/silver/bronze badges + tier
   glow) and a promotion-framed "my rank" bar. Desktop + mobile. Appended last
   so it wins the cascade. Mobbin refs: Kahoot / Duolingo / Stadium Live.
   The renderer (shop-render.js) already emits rank badges + tier classes, so
   this is pure CSS — DOM order in .lb-podium is [#2, #1(.--first), #3].
   ═══════════════════════════════════════════════════════════════════════════ */

/* Raised pedestals — #1 (centre) tallest, then #2, then #3. align-items:flex-end
   (set on .lb-podium) makes the blocks rise from one baseline = a podium. */
.lb-podium .top-fan { position: relative; }
.lb-podium .top-fan::after {
  content: "";
  display: block;
  width: 86%;
  margin-top: 14px;
  border-radius: 12px 12px 0 0;
  border: 1px solid var(--border-subtle);
  border-bottom: 0;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.10), rgba(255, 255, 255, 0.02));
}
.lb-podium .top-fan:nth-child(1)::after { height: 46px; background: linear-gradient(180deg, rgba(232, 234, 240, 0.22), rgba(232, 234, 240, 0.03)); }  /* silver (#2) */
.lb-podium .top-fan--first::after        { height: 68px; background: linear-gradient(180deg, rgba(255, 200, 90, 0.28),  rgba(255, 163, 63, 0.04)); }   /* gold (#1)   */
.lb-podium .top-fan:nth-child(3)::after  { height: 32px; background: linear-gradient(180deg, rgba(214, 150, 95, 0.24),  rgba(214, 150, 95, 0.03)); }   /* bronze (#3) */

/* Medal rank badges */
.lb-podium .top-fan__rank { width: 22px; height: 22px; font-size: 11px; border-color: transparent; }
.lb-podium .top-fan:nth-child(1) .top-fan__rank { background: linear-gradient(180deg, #eef0f5, #b9bcc6); color: #33363d; }
.lb-podium .top-fan--first .top-fan__rank        { background: linear-gradient(180deg, #ffe48f, #f4a623); color: #5a3a00; box-shadow: 0 2px 10px rgba(244, 166, 35, 0.5); }
.lb-podium .top-fan:nth-child(3) .top-fan__rank  { background: linear-gradient(180deg, #edc09a, #c8854c); color: #4a2b10; }

/* Crown over #1 */
.lb-podium .top-fan--first .top-fan__avatar::after {
  content: "";
  position: absolute; left: 50%; top: -24px; transform: translateX(-50%);
  width: 34px; height: 26px;
  background: url('assets/icons/crown.svg') center / contain no-repeat;
  filter: drop-shadow(0 2px 6px rgba(244, 166, 35, 0.5));
}

/* Tier-coloured avatar glow on the podium (subtle dopamine) */
.lb-podium .top-fan--inner .top-fan__avatar     { box-shadow: 0 8px 26px rgba(255, 163, 63, 0.38); }
.lb-podium .top-fan--super .top-fan__avatar     { box-shadow: 0 8px 24px rgba(54, 211, 153, 0.30); }
.lb-podium .top-fan--supporter .top-fan__avatar { box-shadow: 0 8px 24px rgba(90, 200, 250, 0.28); }

/* The score is the dopamine number — make it read */
.lb-podium .top-fan__pts { font-size: 13px; color: var(--text-secondary); font-weight: var(--fw-medium); }
.lb-podium .top-fan--first .top-fan__pts { color: var(--yellow-500); font-weight: var(--fw-bold); }

/* My-rank bar — frame it as a PROMOTION climb (brand glow + glowing fill) */
.lb-me { border-color: rgba(255, 163, 63, 0.32); box-shadow: 0 14px 44px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 163, 63, 0.14), inset 0 1px 0 rgba(255, 255, 255, 0.06); }
.lb-me__bar-fill { box-shadow: 0 0 10px rgba(255, 163, 63, 0.55); }
.lb-me__climb-label::before {
  content: "▲"; display: inline-block; margin-right: 5px;
  font-size: 9px; color: var(--yellow-500); transform: translateY(-1px);
}

/* Mobile: scale the pedestals + crown down with the smaller podium */
@media (max-width: 767.98px) {
  .lb-podium .top-fan:nth-child(1)::after { height: 34px; }
  .lb-podium .top-fan--first::after        { height: 50px; }
  .lb-podium .top-fan:nth-child(3)::after  { height: 24px; }
  .lb-podium .top-fan--first .top-fan__avatar::after { width: 28px; height: 22px; top: -20px; }
  .lb-podium .top-fan__pts { font-size: 12px; }
}

@media (prefers-reduced-motion: reduce) {
  .lb-me__bar-fill { box-shadow: none; }
}

/* ─────────────────────────────────────────
   CREATOR STOREFRONT — tabs, posts, projects (additions)
   Tab row: in-page anchors + one real link (排行榜). Sections carry
   scroll-margin so anchors land below the sticky header.
   ───────────────────────────────────────── */
.creator-sec[id], .top-fans[id] { scroll-margin-top: 84px; }

/* 貼文 — quiet feed rows */
.creator-posts { margin-top: 4px; }
.creator-post {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 18px 0;
  border-bottom: 1px solid var(--border-subtle);
  max-width: 720px;
}
.creator-post__date {
  font-family: var(--font-latin);
  font-size: 12px;
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}
.creator-post__text {
  font-size: var(--fs-body-sm);
  line-height: 1.8;
  color: var(--text-secondary);
}
.creator-post__likes {
  font-family: var(--font-latin);
  font-size: 12px;
  color: var(--text-tertiary);
}

/* 項目 — co-creation rows with a quiet progress line */
.creator-projects { margin-top: 4px; }
.creator-project {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 20px 0;
  border-bottom: 1px solid var(--border-subtle);
  max-width: 720px;
}
.creator-project__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}
.creator-project__title {
  font-family: var(--font-cjk-text);
  font-size: 15px;
  font-weight: var(--fw-bold);
}
.creator-project__status {
  font-size: 12px;
  color: var(--text-tertiary);
  white-space: nowrap;
}
.creator-project__status--live { color: var(--yellow-500); }
.creator-project__status--done { color: var(--success-500); }
.creator-project__bar {
  height: 3px;
  background: var(--white-alpha-8);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.creator-project__bar span {
  display: block;
  height: 100%;
  background: var(--yellow-500);
  border-radius: var(--radius-pill);
}
.creator-project__meta {
  font-family: var(--font-latin);
  font-size: 12px;
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}

/* ─────────────────────────────────────────
   CREATOR TABS (posts + projects) — RESPONSIVE
   ───────────────────────────────────────── */

/* Tablet: reduce max-width so prose doesn't stretch too wide. */
@media (min-width: 768px) and (max-width: 1023.98px) {
  .creator-post,
  .creator-project {
    max-width: 100%;
  }
}

/* Phone ≤767.98px */
@media (max-width: 767.98px) {
  /* Posts: tighten row padding. */
  .creator-post {
    padding: var(--space-4) 0;
    max-width: 100%;
    gap: var(--space-2);
  }

  .creator-post__text {
    font-size: var(--fs-caption);
    line-height: 1.7;
  }

  /* Projects: project head can wrap if title is long. */
  .creator-project {
    padding: var(--space-4) 0;
    max-width: 100%;
    gap: var(--space-2);
  }

  .creator-project__head {
    /* Let title wrap; status stays on its own line below if needed. */
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--space-2);
  }

  .creator-project__title {
    font-size: var(--fs-body-sm);
  }

  .creator-project__status {
    font-size: var(--fs-caption);
  }

  /* Scroll-margin accounts for sticky header on anchor navigation. */
  .creator-sec[id],
  .top-fans[id] {
    scroll-margin-top: 70px;
  }
}

/* Small phone ≤480px: minimal further tweaks. */
@media (max-width: 480px) {
  .creator-post__date,
  .creator-post__likes,
  .creator-project__meta {
    font-size: 11px;
  }
}

/* ─────────────────────────────────────────
   CREATOR SUB-PAGES — 活動 timeline + 貼文 IG grid
   Each storefront section is its own page; the tab row navigates
   between real pages. Timeline: one hairline spine, upcoming events
   lit, past events dimmed below. Posts: profile head + 3-column
   square grid, likes on hover.
   ───────────────────────────────────────── */

/* 活動 — vertical roadmap */
.timeline {
  position: relative;
  margin-top: var(--space-8);
  padding-left: 28px;
  max-width: 860px;
}
.timeline::before {
  content: '';
  position: absolute;
  left: 5px;
  top: 8px;
  bottom: 8px;
  width: 1px;
  background: var(--border-default);
}
.timeline-item {
  position: relative;
  display: flex;
  gap: var(--space-6);
  padding: var(--space-6) 0;
}
.timeline-item::before {
  content: '';
  position: absolute;
  left: -28px;
  top: 34px;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--bg-primary);
  border: 2px solid var(--text-tertiary);
}
.timeline-item--live::before {
  background: var(--yellow-500);
  border-color: var(--yellow-500);
}
.timeline-item--past { opacity: 0.5; }
.timeline-item__poster {
  width: 110px;
  aspect-ratio: 2 / 3;
  flex-shrink: 0;
  background: var(--bg-secondary);
  overflow: hidden;
}
.timeline-item__poster img { width: 100%; height: 100%; object-fit: cover; display: block; }
.timeline-item__copy {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  padding-top: 6px;
}
.timeline-item__date {
  font-family: var(--font-latin);
  font-size: 13px;
  color: var(--yellow-500);
  font-variant-numeric: tabular-nums;
}
.timeline-item--past .timeline-item__date { color: var(--text-tertiary); }
.timeline-item__title {
  font-family: var(--font-cjk-text);
  font-size: 16px;
  font-weight: var(--fw-bold);
  line-height: 1.45;
}
.timeline-item__meta {
  font-size: 12px;
  color: var(--text-tertiary);
}
.timeline-item__status {
  font-size: 12px;
  color: var(--text-secondary);
}
.timeline-item__status--onsale { color: var(--yellow-500); }
.timeline-item .btn { margin-top: var(--space-2); align-self: flex-start; }

/* 貼文 — profile head + square grid */
.ig-head {
  display: flex;
  align-items: center;
  gap: 36px;
  margin-top: 36px;
  padding-bottom: 30px;
  border-bottom: 1px solid var(--border-subtle);
}
.ig-head__avatar {
  width: 92px;
  height: 92px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--bg-secondary);
  flex-shrink: 0;
}
.ig-head__avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ig-head__info { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.ig-head__name {
  font-family: var(--font-cjk-display);
  font-size: 19px;
  font-weight: var(--fw-bold);
}
.ig-stats { display: flex; gap: 28px; }
.ig-stat { font-size: 13px; color: var(--text-tertiary); }
.ig-stat b {
  font-family: var(--font-latin);
  font-weight: var(--fw-bold);
  color: var(--neutral-white);
  margin-right: 5px;
  font-variant-numeric: tabular-nums;
}
.ig-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-1);
  margin-top: 28px;
}
.ig-cell {
  position: relative;
  aspect-ratio: 1 / 1;
  background: var(--bg-secondary);
  overflow: hidden;
}
.ig-cell img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ig-cell__overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: var(--space-4);
  text-align: center;
  background: rgba(10, 10, 10, 0.6);
  opacity: 0;
  transition: opacity 150ms var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .ig-cell:hover .ig-cell__overlay { opacity: 1; }
}
.ig-cell__likes {
  font-family: var(--font-latin);
  font-size: 14px;
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
}
.ig-cell__caption {
  font-size: 12px;
  color: var(--text-secondary);
  line-height: 1.6;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ─────────────────────────────────────────
   CREATOR SUB-PAGES — RESPONSIVE
   ───────────────────────────────────────── */

/* Tablet (768–1023): minor timeline + ig-head refinements. */
@media (min-width: 768px) and (max-width: 1023.98px) {
  .timeline {
    max-width: 100%;
  }

  .ig-head {
    gap: var(--space-6);
  }

  .list-banner {
    gap: var(--space-10);
  }

  .list-banner__title {
    font-size: clamp(36px, 4vw, var(--fs-display));
  }
}

/* Phone ≤767.98px */
@media (max-width: 767.98px) {
  /* Timeline: keep row layout but shrink poster; reduce spine gap. */
  .timeline {
    padding-left: var(--space-6);
    margin-top: var(--space-6);
  }

  .timeline-item {
    flex-direction: row;
    gap: var(--space-4);
    padding: var(--space-5) 0;
  }

  .timeline-item__poster {
    width: 80px;
    /* Maintain 2:3 aspect-ratio — flex-shrink:0 already set on base. */
  }

  .timeline-item__copy {
    padding-top: var(--space-1);
    gap: var(--space-1);
  }

  .timeline-item__title {
    font-size: var(--fs-body-sm);
  }

  /* IG profile head: compress gap. */
  .ig-head {
    gap: var(--space-5);
    margin-top: var(--space-6);
    padding-bottom: var(--space-5);
  }

  .ig-head__avatar {
    width: 68px;
    height: 68px;
  }

  .ig-head__name {
    font-size: var(--fs-h5);
  }

  .ig-stats {
    gap: var(--space-5);
    flex-wrap: wrap;
  }

  /* IG grid: tighter gap — already 3-col, keep it. */
  .ig-grid {
    gap: 2px;
    margin-top: var(--space-5);
  }

  /* List banner: single-col, title clamp pulled in from existing 768 rule. */
  .list-banner {
    grid-template-columns: 1fr;
    gap: var(--space-8);
  }

  .list-banner__title {
    font-size: clamp(28px, 7vw, 36px);
    line-height: 1.3;
  }

  .list-banner__sub {
    margin-top: var(--space-3);
  }

  .list-banner__cta {
    margin-top: var(--space-6);
  }
}

/* Small phone ≤480px: further compress timeline poster + ig-head. */
@media (max-width: 480px) {
  .timeline {
    padding-left: var(--space-5);
  }

  .timeline-item__poster {
    width: 64px;
  }

  /* IG head: stack avatar above stats on very narrow screens. */
  .ig-head {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-4);
  }

  .ig-stats {
    gap: var(--space-5);
  }

  /* IG grid → 2-col on smallest phones. */
  .ig-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* 項目 — the creator's working register (IMDb-style groups) */
.reg-group { margin-top: 36px; }
.reg-group__title {
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--text-tertiary);
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border-subtle);
}

/* ── 片單推薦 · 探索愛情 list-banner（ported from dolly 2.0）──────── */
/* Double-class beats index.html's inline `.section-bg-surface
   { background: var(--yellow-500) }`, whose shorthand would otherwise
   reset this background-image (same specificity, later source order). */
.section.section--list-banner {
  background-image: url('assets/images/gradient/gradient-2.webp');
  background-size: cover;
  background-position: center;
}

.list-banner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 600px);
  align-items: center;
  gap: var(--space-16);
}

.list-banner__copy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* Stretch to the media column's height; space-between pins the title
     to the image's top edge, the CTA to its bottom edge, and floats the
     description midway between them. */
  align-self: stretch;
  justify-content: space-between;
}

.list-banner__title {
  margin: 0;  /* kill the h2 UA margin (0.83em ≈ 63px per side at 76px) */
  padding-top: var(--space-2);  /* 視覺微調:字面比圖頂多壓 8px */
  font-family: var(--font-cjk-display);
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}

.list-banner__sub {
  margin: 0;  /* clear p UA margins; spacing comes from space-between */
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-lg);
  line-height: var(--lh-body-lg);
  color: var(--neutral-100);
}

/* Ink-filled CTA — yellow primary has no contrast on the warm gradient. */
.list-banner__cta {
  background: var(--yellow-ink);
  color: var(--text-primary);
  border-color: transparent;
}

.list-banner__cta:hover:not(:disabled) {
  box-shadow: 0 8px 24px rgba(26, 23, 0, 0.35);
}

.list-banner__media {
  display: block;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-xl);
  overflow: hidden;
  box-shadow: 0 24px 48px rgba(26, 23, 0, 0.18);
}

.list-banner__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 300ms ease;
}

.list-banner__media:hover img { transform: scale(1.03); }

@media (max-width: 767.98px) {
  .list-banner {
    grid-template-columns: 1fr;
    gap: var(--space-8);
  }
  .list-banner__title { font-size: clamp(28px, 7vw, 36px); line-height: 1.3; }
  /* Single column: copy height is natural, so space-between collapses —
     restore fixed gaps instead. */
  .list-banner__sub { margin-top: var(--space-3); }
  .list-banner__cta { margin-top: var(--space-6); }
}

/* ── 43-bottom-nav.css — mobile floating pill navigation ──────────────────
   A detached, centered, safe-area-aware pill shown only on phones (<768).
   At rest: icon + label. On scroll-down it condenses to icons-only
   (mobile-nav.js toggles .is-condensed); re-expands on scroll-up / at top.

   Five primary destinations. The Community cell becomes "Create" on the
   Community page (JS adds .botnav__item--create + swaps icon/label/action).
   Tablet (icon-rail) and desktop (side-rail) hide this entirely.
   Owns its own responsive behaviour — no page-level overrides. */

.botnav { display: none; }

/* Hide the floating bottom-nav while a take-over overlay is open — otherwise it
   overlaps the overlay's own primary CTA (e.g. the cart's 前往結帳) and invites a
   tap that abandons the task. Covers cart, ds-drawers (checkout/auth/completions),
   the mobile menu, and the zoom lightbox. Higher specificity wins over the
   mobile @media show rule below. */
body.cart-open .botnav,
body.ds-drawer-open .botnav,
body.mdrawer-open .botnav,
body.ds-zoom-open .botnav { display: none; }

/* Reserve space so page content / footers never sit under the pill */
@media (max-width: 767.98px) {
  body { padding-bottom: calc(104px + env(safe-area-inset-bottom, 0px)); }
}

@media (max-width: 767.98px) {
  .botnav {
    display: block;
    position: fixed;
    /* THE FROST LIVES HERE, on the FIXED element (2026-06-29). A fixed nav's
       backdrop-filter only composites on mobile when transform + backdrop-filter
       are on the SAME (fixed) element — that gives it its own GPU layer (exactly
       what the working f2e .m-bottomnav does). The old "no transform" note was a
       MISDIAGNOSIS: the transform failed only because it was split onto this
       wrapper while the backdrop sat on the CHILD .botnav__list. Together here,
       they work. The child keeps only the visible tint/border/shadow. */
    left: 0;
    right: 0;
    margin-inline: auto;
    bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    z-index: var(--z-bottomnav);
    width: min(92vw, 390px);         /* equal columns → middle cell (the +) is dead-centre */
    pointer-events: none;            /* outer wrapper is click-through; backdrop still renders */
    isolation: isolate;
    border-radius: var(--radius-pill, 999px);   /* clip the backdrop blur to the pill shape */
    transform: translateZ(0);                   /* own compositing layer → the fixed-nav frost actually composites */
    -webkit-transform: translateZ(0);
    backdrop-filter: blur(var(--lg-blur));     /* 8px — EXACTLY 我的投票's frost; the per-element lens stops a wide bar going see-through (kit) */
    -webkit-backdrop-filter: blur(var(--lg-blur));
    transition: width 260ms var(--ease-settle, ease-out);
  }
  .botnav.is-condensed { width: min(74vw, 330px); }   /* narrows on scroll-down */

  .botnav__list {
    position: relative;                 /* for the specular sheen ::before */
    pointer-events: auto;
    display: flex;
    align-items: stretch;
    gap: 0;
    margin: 0;
    width: 100%;
    padding: 4px;
    list-style: none;
    /* The VISIBLE glass styling only — translucent tint + border + the 10-layer
       specular box-shadow. The actual blur (backdrop-filter) + transform live on
       the FIXED .botnav parent above (one element = own GPU layer → it composites);
       this child sits on top of that blurred backdrop and tints it. NO sheen, NO
       spectral rim, NO url() refraction. */
    isolation: isolate;
    background: var(--lg-fill);                /* bright liquid glass (white-5%) — same material as 我的投票; the parent's blur shows through */
    border: var(--glass-border);
    border-radius: var(--radius-pill, 999px);
    box-shadow: var(--glass-shadow);
    transition: padding 220ms var(--ease-settle, ease-out);
  }
  /* The frost is kicked ONE FRAME AFTER LOAD (mobile-nav.js adds .lg-ready) — a
     post-paint property change forces the fixed element to (re)composite its
     backdrop-filter. Applied to .botnav (where the blur now lives). */
  .botnav.lg-ready {
    -webkit-backdrop-filter: var(--lg-frost);   /* 8px — EXACTLY 我的投票; Chromium adds the bend in 52-… */
            backdrop-filter: var(--lg-frost);
  }

  .botnav__cell { display: flex; flex: 1 1 0; min-width: 0; }   /* 5 equal columns → + dead-centre */

  .botnav__item {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-width: 0;
    min-height: 45px;
    padding: 5px 2px;
    border-radius: 16px;
    color: var(--text-secondary, rgba(255, 255, 255, 0.58));
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
    transition: color 160ms ease, padding 200ms var(--ease-settle, ease), gap 200ms var(--ease-settle, ease), min-height 200ms var(--ease-settle, ease);   /* condense morph parts share the bar's settle curve */
  }
  .botnav__item:active { transform: scale(0.94); }

  .botnav__icon {
    width: 20px; height: 20px;
    flex: none;
    background: currentColor;
    -webkit-mask: var(--i) center / contain no-repeat;
            mask: var(--i) center / contain no-repeat;
    transition: width 200ms var(--ease-settle, ease), height 200ms var(--ease-settle, ease);
  }

  /* Label shown at rest; collapses smoothly when the bar condenses on scroll. */
  .botnav__label {
    font-family: var(--font-cjk-text);
    font-size: 9px;
    line-height: 1.35;               /* room for descenders (g/p/y) */
    font-weight: var(--fw-medium, 500);
    letter-spacing: 0.01em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
    opacity: 1;
    max-height: 16px;
    transition: opacity 150ms ease, max-height 200ms var(--ease-settle, ease);
  }

  /* Active destination — accent colour only (no highlight background). */
  .botnav__item.is-active,
  .botnav__item[aria-current="page"] { color: var(--accent-500, var(--yellow-500, #ffa33f)); }
  .botnav__item--create { color: var(--accent-500, var(--yellow-500, #ffa33f)); }

  /* Condensed (scroll-down): drop the labels → icon-only + shorter. */
  .botnav.is-condensed .botnav__list { padding: 3px; }
  .botnav.is-condensed .botnav__item { gap: 0; min-height: 35px; padding: 6px 2px; }
  .botnav.is-condensed .botnav__icon { width: 19px; height: 19px; }
  .botnav.is-condensed .botnav__label { opacity: 0; max-height: 0; pointer-events: none; }

  @media (max-width: 360px) {
    .botnav__label { font-size: 8.5px; }
  }

  @media (prefers-reduced-motion: reduce) {
    .botnav__list, .botnav__item, .botnav__icon, .botnav__label { transition: none; }
    .botnav__item:active { transform: none; }
  }
}

/* Short landscape phones: hug the bottom a little tighter */
@media (max-width: 926px) and (orientation: landscape) and (max-height: 460px) {
  .botnav { bottom: calc(8px + env(safe-area-inset-bottom, 0px)); }
  .botnav.is-condensed .botnav__item { padding: 7px 12px; }
}


/* ── 44-mobile-drawer.css — hamburger menu + mobile/tablet header chrome ───
   The drawer holds every global destination NOT in the 5-item bottom nav
   (Library, News, Events) plus account/utility links and language. Opens
   from a burger in the compact mobile header. Focus-trapped, Esc/scrim/close
   dismissible, background scroll-locked (mobile-nav.js drives behaviour).
   Phone (<768) only — tablet/desktop use the rail + full header. */

/* Burger button — only on phone */
.header__burger {
  display: none;
  width: 40px; height: 40px;
  align-items: center; justify-content: center;
  background: transparent; border: 0; cursor: pointer;
  color: var(--text-primary, #fff);
  border-radius: var(--radius-md, 12px);
  -webkit-tap-highlight-color: transparent;
}
.header__burger-icon { position: relative; }
.header__burger-icon, .header__burger-icon::before, .header__burger-icon::after {
  content: ''; display: block;
  width: 20px; height: 2px; border-radius: 2px;
  background: currentColor;
  /* Settle curve (2026-07-03: retired the springy back-out overshoot —
     bouncy chrome reads playful, not luxury). Vertical move uses translateY
     (not top) so one transform transition handles move + rotate together. */
  transition: transform 300ms var(--ease-settle, ease-out),
              background-color 160ms ease;
}
.header__burger-icon::before { position: absolute; left: 0; top: 0; transform: translateY(-6px); }
.header__burger-icon::after  { position: absolute; left: 0; top: 0; transform: translateY(6px); }

/* Burger → X: the bars spin 180° as they collapse into the X (parent rotates,
   the two bars cross). Springy overshoot makes it feel alive. */
.header__burger:active { transform: scale(0.88); transition: transform 120ms ease-out; }
.header__burger.is-open .header__burger-icon { background: transparent; transform: rotate(180deg); }
.header__burger.is-open .header__burger-icon::before { transform: translateY(0) rotate(45deg); }
.header__burger.is-open .header__burger-icon::after  { transform: translateY(0) rotate(-45deg); }

@media (prefers-reduced-motion: reduce) {
  .header__burger-icon, .header__burger-icon::before, .header__burger-icon::after {
    transition: transform 140ms ease, background-color 120ms ease;
  }
  .header__burger.is-open .header__burger-icon { transform: none; }   /* X without the spin */
  .header__burger:active { transform: none; }
}

/* ── Drawer ── */
.mdrawer { position: fixed; inset: 0; z-index: var(--z-modal); display: none; }
.mdrawer.is-open { display: block; }
.mdrawer__scrim {
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, 0.55);
  opacity: 0; transition: opacity 200ms ease;   /* scrim leads the panel — world dims, then the sheet lands */
}
.mdrawer.is-open .mdrawer__scrim { opacity: 1; }
.mdrawer__panel {
  position: absolute; top: 0; left: 0; bottom: 0;     /* opens from the LEFT — same side as the burger */
  width: min(76vw, 360px);
  display: flex; flex-direction: column;
  background: var(--bg-elevated, #141416);
  border-right: 1px solid var(--border-subtle, rgba(255,255,255,0.1));
  box-shadow: 16px 0 48px rgba(0,0,0,0.5);
  padding: calc(env(safe-area-inset-top, 0px) + 12px) 20px calc(env(safe-area-inset-bottom, 0px) + 20px);
  transform: translateX(-100%);
  transition: transform var(--dur-panel-exit, 280ms) var(--ease-emphasized, ease-in);   /* close: quicker exit (user decided to leave) */
  overflow-y: auto; overscroll-behavior: contain;
}
.mdrawer.is-open .mdrawer__panel {
  transform: translateX(0);
  transition: transform var(--dur-sheet-m, 550ms) var(--ease-panel);   /* open: a 76vw panel has mass — rides the mobile sheet tier (was a snappy 300ms) */
}

.mdrawer__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.mdrawer__logo img { height: 26px; display: block; }
.mdrawer__close {
  width: 40px; height: 40px; display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 0; cursor: pointer; color: var(--text-secondary, #aaa);
  font-size: 22px; line-height: 1; border-radius: var(--radius-md, 12px);
}
.mdrawer__close:hover { color: var(--text-primary, #fff); }

.mdrawer__group { padding: 14px 0; }
.mdrawer__group + .mdrawer__group { border-top: 1px solid var(--border-subtle, rgba(255,255,255,0.08)); }
.mdrawer__label {
  font-family: var(--font-cjk-text); font-size: 11px; font-weight: var(--fw-semibold, 600);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-tertiary, rgba(255,255,255,0.45)); margin: 0 0 6px;
}
.mdrawer__link {
  display: flex; align-items: center; gap: 10px;
  width: 100%; text-align: left;
  min-height: 48px; padding: 6px 8px; margin: 0 -8px;
  background: transparent; border: 0; cursor: pointer; text-decoration: none;
  font-family: var(--font-cjk-display); font-size: 16px; font-weight: var(--fw-medium, 500);
  color: var(--text-primary, #fff); border-radius: var(--radius-md, 12px);
  transition: background-color 120ms ease, transform 120ms cubic-bezier(0.23, 1, 0.32, 1);
}
.mdrawer__link:hover, .mdrawer__link:focus-visible { background: rgba(255,255,255,0.06); }
.mdrawer__link:active { transform: scale(0.985); background: rgba(255,255,255,0.06); }
.mdrawer__link[aria-current="page"], .mdrawer__link.is-active { color: var(--accent-500, var(--yellow-500, #ffa33f)); }
.mdrawer__foot { margin-top: auto; padding-top: 16px; }
.mdrawer__login { width: 100%; }

@media (prefers-reduced-motion: reduce) {
  .mdrawer__panel, .mdrawer__scrim, .mdrawer__link { transition: none; }
  .mdrawer__link:active { transform: none; }
}

/* ── Phone header: compact 3-column grid so the logo stays optically centered
   regardless of the burger width. Burger left, logo center, balancing cell
   right. Desktop clutter (search/actions/switch) moves into the drawer. ── */
@media (max-width: 767.98px) {
  .header,
  body.has-rail .header { margin-left: 0; }
  .header__inner {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: var(--space-3);
    min-height: 56px;
    padding-inline: var(--space-4);
  }
  .header__burger {
    display: inline-flex; grid-column: 1; justify-self: start;
    justify-content: flex-start;     /* icon hugs the button's left edge */
    width: 40px; height: 40px; padding: 0;
    margin: 0 0 0 -16px;             /* pull the bars out to the 16px content gutter (were ~42px in) */
  }
  .header__nav    { grid-column: 2; justify-self: center; }
  .header__logo-img { height: 28px; }
  .header-search,
  .header__actions,
  .header-switch { display: none; }            /* relocated into the drawer */
  body.mdrawer-open { overflow: hidden; }
}

/* ── Profile shortcut button (injected by mobile-nav.js into the header).
   Phone only — desktop has its own profile/account affordances. Sits in the
   header grid's right cell, mirroring the burger's 16px gutter on the left. ── */
.header__profile { display: none; }
@media (max-width: 767.98px) {
  .header__profile {
    display: inline-flex; grid-column: 3; justify-self: end;
    align-items: center; justify-content: flex-end;
    width: 40px; height: 40px; padding: 0; margin: 0 -16px 0 0;   /* icon hugs the 16px right gutter */
    color: var(--text-primary, #fff);
    border-radius: var(--radius-md, 12px);
    -webkit-tap-highlight-color: transparent;
    transition: transform 120ms ease-out, opacity 150ms ease;
  }
  .header__profile:active { transform: scale(0.9); }
  .header__profile-icon {
    width: 24px; height: 24px; flex: none;
    background: currentColor;
    -webkit-mask: url('assets/icons/nav/user.svg') center / contain no-repeat;
            mask: url('assets/icons/nav/user.svg') center / contain no-repeat;
  }
}

/* ── Mobile header right cluster (cart + profile), injected by mobile-nav.js.
   Phone only. The cart opens the same drawer as the desktop .header__cart and
   carries a live count badge; it sits to the LEFT of the profile icon. ── */
.header__m-cluster { display: none; }
@media (max-width: 767.98px) {
  .header__m-cluster {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);             /* breathing room between cart + profile */
    grid-column: 3;
    justify-self: end;
    margin-right: -16px;             /* hug the 16px content gutter (was on profile) */
  }
  /* profile now lives inside the cluster — drop its own grid placement + gutter */
  .header__m-cluster .header__profile { grid-column: auto; justify-self: auto; margin: 0; }

  .header__cart-m {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    border: none;
    background: none;
    cursor: pointer;
    color: var(--text-primary);
    border-radius: var(--radius-md);
    -webkit-tap-highlight-color: transparent;
    transition: transform 120ms ease-out, color 150ms ease;
  }
  .header__cart-m:active { transform: scale(0.9); }
  .header__cart-m:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }
  .header__cart-m-glyph {
    position: relative;              /* badge anchor */
    width: 24px;
    height: 24px;
    display: grid;
    place-items: center;
  }
  .header__cart-m-glyph svg { width: 100%; height: 100%; display: block; }

  /* count badge — orange pill ringed in canvas so it lifts off the glyph */
  .header__cart-m-badge {
    position: absolute;
    top: -6px;
    right: -7px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    display: none;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-pill);
    background: var(--yellow-500);
    color: var(--yellow-ink);
    font-family: var(--font-latin);
    font-size: 10px;
    font-weight: var(--fw-bold);
    line-height: 1;
    box-shadow: 0 0 0 2px var(--neutral-900);
  }
  .header__cart-m.has-items .header__cart-m-badge { display: inline-flex; }
  .header__cart-m-badge.is-bumping { animation: header-cart-m-bump 360ms var(--ease-out); }
  @keyframes header-cart-m-bump {
    0%   { transform: scale(1); }
    40%  { transform: scale(1.3); }
    100% { transform: scale(1); }
  }
}
@media (max-width: 767.98px) and (prefers-reduced-motion: reduce) {
  .header__cart-m { transition: none; }
  .header__cart-m:active { transform: none; }
  .header__cart-m-badge.is-bumping { animation: none; }
}

/* ── Drawer search field (injected by mobile-nav.js at the top of the panel).
   Mirrors the desktop persistent search; submits to the Library. ── */
.mdrawer__search {
  display: flex; align-items: center; gap: 10px;
  height: 44px; margin: 6px 0 10px; padding: 0 14px;
  border-radius: var(--radius-pill, 999px);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.12));
  transition: border-color 150ms ease, background-color 150ms ease;
}
.mdrawer__search:focus-within {
  border-color: var(--yellow-500, #ffa33f);
  background: rgba(255, 255, 255, 0.09);
}
.mdrawer__search-icon {
  width: 18px; height: 18px; flex: none;
  background: var(--text-tertiary, rgba(255, 255, 255, 0.5));
  -webkit-mask: url('data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M11.44%204.6C7.66238%204.6%204.60001%207.66237%204.60001%2011.44C4.60001%2015.2176%207.66238%2018.28%2011.44%2018.28C13.3026%2018.28%2014.9913%2017.5355%2016.2248%2016.3279C16.2264%2016.3263%2016.2281%2016.3246%2016.2298%2016.323C17.4951%2015.0817%2018.28%2013.3525%2018.28%2011.44C18.28%207.66237%2015.2176%204.6%2011.44%204.6ZM18.3177%2016.9941C19.545%2015.4763%2020.28%2013.544%2020.28%2011.44C20.28%206.5578%2016.3222%202.6%2011.44%202.6C6.55781%202.6%202.60001%206.5578%202.60001%2011.44C2.60001%2016.3222%206.55781%2020.28%2011.44%2020.28C13.4974%2020.28%2015.3907%2019.5771%2016.8928%2018.3984L19.7047%2021.1187C20.1016%2021.5027%2020.7347%2021.4922%2021.1187%2021.0953C21.5027%2020.6984%2021.4922%2020.0653%2021.0953%2019.6813L18.3177%2016.9941Z%22%20fill%3D%22white%22%2F%3E%3C%2Fsvg%3E') center / contain no-repeat;
          mask: url('data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M11.44%204.6C7.66238%204.6%204.60001%207.66237%204.60001%2011.44C4.60001%2015.2176%207.66238%2018.28%2011.44%2018.28C13.3026%2018.28%2014.9913%2017.5355%2016.2248%2016.3279C16.2264%2016.3263%2016.2281%2016.3246%2016.2298%2016.323C17.4951%2015.0817%2018.28%2013.3525%2018.28%2011.44C18.28%207.66237%2015.2176%204.6%2011.44%204.6ZM18.3177%2016.9941C19.545%2015.4763%2020.28%2013.544%2020.28%2011.44C20.28%206.5578%2016.3222%202.6%2011.44%202.6C6.55781%202.6%202.60001%206.5578%202.60001%2011.44C2.60001%2016.3222%206.55781%2020.28%2011.44%2020.28C13.4974%2020.28%2015.3907%2019.5771%2016.8928%2018.3984L19.7047%2021.1187C20.1016%2021.5027%2020.7347%2021.4922%2021.1187%2021.0953C21.5027%2020.6984%2021.4922%2020.0653%2021.0953%2019.6813L18.3177%2016.9941Z%22%20fill%3D%22white%22%2F%3E%3C%2Fsvg%3E') center / contain no-repeat;
}
.mdrawer__search:focus-within .mdrawer__search-icon { background: var(--yellow-500, #ffa33f); }
.mdrawer__search-input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  color: var(--text-primary, #fff);
  font-family: var(--font-cjk-display); font-size: 15px;
}
.mdrawer__search-input::placeholder { color: var(--text-tertiary, rgba(255, 255, 255, 0.5)); }
.mdrawer__search-input::-webkit-search-cancel-button { display: none; }


/* ── hand-extracted single-source components ── */
/* CROWDFUNDING / PRESALE — .cocreation-card (+states/--overlay). [COMPONENT]
   NOTE: .cocreation-grid layout (base + responsive) is owned by
   07-cocreation-grid.css so the base + media rules cascade in one file. */

.cocreation-card {
  display: flex;
  flex-direction: column;
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: transform 250ms ease, border-color 250ms ease;
  text-decoration: none;
  color: inherit;
}

.cocreation-card:hover {
  transform: translateY(-4px);
  border-color: var(--border-default);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.35);
}

.cocreation-card__poster {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--neutral-900);
}

.cocreation-card__poster img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.cocreation-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: 22px;
  flex: 1;
}

.cocreation-card__title {
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: 22px;
  line-height: 1.3;
  color: var(--text-primary);
  margin: 0;
}

.cocreation-card__desc {
  font-family: var(--font-cjk-text);
  /* Figma spec: Body/Small/zh · Regular · 14/23 · Text/Tertiary */
  font-weight: var(--fw-regular);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body-sm);
  color: var(--text-tertiary);
  margin: 0;
}

.cocreation-card__metrics {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.cocreation-card__amount {
  font-family: var(--font-cjk-text);
  font-size: 18px;
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}

/* Funded-percent header — the hero figure above the bar (Kickstarter /
   Buy Me a Coffee convention). Promoted from refine-engagement (.rf-cocreate)
   to the component so it renders identically on every page the card appears
   (home 影視/音樂共創 sections + cocreate.html), keyed to the class = single source. */
.rf-fund {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}
.rf-fund__pct {
  font-family: var(--font-latin);
  font-size: 30px;
  line-height: 1;
  font-weight: var(--fw-bold);
  color: var(--yellow-500);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.5px;
}
.rf-fund__pct small {
  font-size: 17px;
  font-weight: var(--fw-bold);
  margin-left: 1px;
}
.rf-fund--done .rf-fund__pct { color: var(--success-500); }
.rf-fund__label {
  font-family: var(--font-cjk-text);
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--text-secondary);
}

.cocreation-card__bar {
  height: 3px;
  background: var(--white-alpha-8);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

.cocreation-card__bar-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--yellow-600), var(--yellow-400));
  box-shadow: 0 0 12px rgba(255, 163, 63, 0.35);
  border-radius: var(--radius-pill);
  transition: width 1s ease;
}

/* Goal reached → the fill goes green (matches the 目標達成 badge--success).
   Keyed off the existing data-bar-fill="100" state, authored once. The
   :not(--muted) keeps deliberately-greyed 已結束/未達標 bars grey even at 100%. */
.cocreation-card__bar-fill[data-bar-fill="100"]:not(.cocreation-card__bar-fill--muted) {
  background: var(--success-500);
  box-shadow: 0 0 8px rgba(34, 197, 94, 0.40);
}

.cocreation-card__bar-fill--muted {
  background: var(--white-alpha-24);
  box-shadow: none;
}

.cocreation-card__meta-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  font-family: var(--font-cjk-text);
  font-size: 12.5px;
  font-weight: var(--fw-regular);
  color: var(--text-tertiary);
}
.cocreation-card__meta-row span:first-child {
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}

/* ── Crowdfunding card · OVERLAY variant ───────────────────────────────────
   Opt-in via .cocreation-grid--overlay. The poster shows at its NATURAL ratio
   (no crop) and the funding block (badge → title → desc → amount → bar → meta)
   sits in a .media-glass panel that rides up onto the poster's lower edge and
   flows onto the card surface below — same text-on-photo treatment as the
   community posts, and it never clips regardless of poster shape. The body
   carries .media-glass (added in markup). ──────────────────────────────────── */
.cocreation-grid--overlay .cocreation-card {
  position: relative;                 /* natural height: poster + funding panel below */
}
.cocreation-grid--overlay .cocreation-card__poster { position: relative; aspect-ratio: auto; line-height: 0; }
.cocreation-grid--overlay .cocreation-card__poster img { position: relative; width: 100%; height: auto; }
/* Funding block FLOWS (not absolute → never clips). Negative margin lifts it
   onto the poster's lower edge for the glass dissolve; the dense data simply
   continues onto the card surface below when the poster is short/landscape. */
.cocreation-grid--overlay .cocreation-card__body {
  position: relative;
  z-index: 1;
  margin-top: -110px;
  padding: var(--space-16) var(--space-5) 18px;            /* top ~64px = the blur dissolve zone over the poster */
  flex: 0 0 auto;
  gap: 7px;
}
.cocreation-grid--overlay .cocreation-card__desc {
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Text → light on the glass. */
.cocreation-grid--overlay .cocreation-card__title { color: #fff; font-size: 18px; }
.cocreation-grid--overlay .cocreation-card__amount { color: #fff; }
.cocreation-grid--overlay .cocreation-card__desc,
.cocreation-grid--overlay .cocreation-card__meta-row { color: rgba(255, 255, 255, 0.85); }
/* Progress track needs more presence over a photo. */
.cocreation-grid--overlay .cocreation-card__bar { background: rgba(255, 255, 255, 0.28); }

/* Generic badge — yellow tinted pill (used inside cards / labels)
   Modifier .badge--success → green tinted + ✓ icon (for 目標達成 / 已完成 狀態) */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  align-self: flex-start;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  background: rgba(255, 163, 63, 0.12);
  border: 1px solid rgba(255, 163, 63, 0.25);
  color: var(--yellow-500);
  font-family: var(--font-cjk-text);
  font-size: 11px;
  font-weight: var(--fw-semibold);
  letter-spacing: 0.05em;
}

/* 目標達成 / 已完成. --success-500 (#22c55e) is documented (see tokens.css
   score-ink note) to read too dark for small text on near-black, which made
   this badge invisible on the dark card. Use the brighter system green and
   a stronger tint/border so the pill reads clearly. */
.badge--success {
  background: rgba(48, 209, 88, 0.16);
  border-color: rgba(48, 209, 88, 0.42);
  color: #30d158;
}

.badge--urgent {
  background: rgba(239, 68, 68, 0.14);
  border-color: rgba(239, 68, 68, 0.32);
  color: var(--error-500);
}

.badge--ended {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.16);
  color: var(--text-tertiary);
}

/* ─────────────────────────────────────────
   HERO CAROUSEL
   Full-bleed image-led banner. Each slide stacks:
     <bg image>  +  <darken gradients>  +  <foreground content bottom-left>
   Auto-advance, pause-on-hover, dots, prev/next.

   ─── BANNER UPLOAD SPEC (for backend / content team) ───
     Aspect ratio:  3 : 1  (wide cinematic, balances height + minimal cropping)
     Master size:   2400 × 800  (retina-ready up to 1200px CSS at 2× DPR)
     Format:        JPG (≤ 700 KB).  WebP (≤ 500 KB) optional later for ~30% size win.
     Subject safe-zone:
       central 1080 × 800 area must contain the hero subject so the
       browser's center-crop survives portrait-phone viewports without
       cutting off faces / titles.
     If a separate mobile asset is supported later:
       1080 × 1350  (4:5 portrait)  — same content, recropped.
   ───────────────────────────────────────────────────────── */

.hero-carousel {
  position: relative;
  width: 100%;
  /* Aspect-aware height: tracks viewport width to match image's 3:1 ratio.
     - On 1680–2400px viewports: hero height ≈ viewport_width / 3 → image fits with no vertical crop.
     - Min 560px on narrow viewports (≤ 1680 wide) — slight horizontal crop, subject in safe zone survives.
     - Max 800px on ultra-wide (≥ 2400 wide) — image native height, no upscale.
     - Beyond 2400 CSS px (true 4K+ at 1× DPR) image still crops vertically;
       fix would be a larger source asset (e.g. 4800×1600). */
  height: clamp(560px, 33.33vw, 800px);
  overflow: hidden;
  background: var(--bg-primary);
  /* Explicit stacking context so edge vignette children composite independently
     of the swiper-wrapper GPU transform layer beneath them. */
  isolation: isolate;
}

/* ── Swiper wrapper — Swiper owns transform/transition on .swiper-wrapper.
   We only assert the height so slides fill the carousel frame. */
.hero-carousel .swiper-wrapper {
  height: 100%;
}

/* Each slide fills the carousel frame. overflow:hidden contains
   the darken-bottom/darken-left overlays within the slide's bounds.
   Swiper sets width automatically (slidesPerView:1 → 100%). */
.hero-carousel__slide {
  position: relative;
  height: 100%;
  overflow: hidden;
}

.hero-carousel__image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 30%;
  transform: scale(1.04);
}

/* Ken Burns (restored 2026-07-03 — an earlier cleanup deleted the animation
   and left every banner frozen at its start scale). The ACTIVE slide's image
   breathes: slow linear grow through the 5s dwell; `both` holds the end
   frame until the slide leaves. Inactive slides drop the class and snap
   back to base scale off-screen, so re-arrival restarts the zoom invisibly.
   Runs on mobile too — transform-only, one compositor layer. */
@media (prefers-reduced-motion: no-preference) {
  .swiper-slide-active .hero-carousel__image {
    animation: hero-kb 9s linear both;
  }
}
@keyframes hero-kb {
  from { transform: scale(1.04); }
  to   { transform: scale(1.12); }
}

/* Slide 1 — constellation canvas background (replaces static image)
   Many small dots with thin connecting lines, slowly drifting; some
   dots are brand yellow with a soft halo. Drawn at devicePixelRatio
   for crisp lines. Pauses when slide is not active to save CPU.
   Layered background: warm yellow ambient near top + dark purple-blue
   radial centre + black edges → gives depth before particles draw. */
.hero-carousel__particles {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  background:
    radial-gradient(ellipse 70% 50% at 50% 25%, rgba(255, 163, 63, 0.06), transparent 70%),
    radial-gradient(ellipse 60% 60% at 30% 80%, rgba(80, 40, 120, 0.08), transparent 70%),
    radial-gradient(ellipse at 50% 50%, #15141c 0%, #0a0a0a 75%);
}

/* Slide 1 only — soften darken-left so the constellation shows through.
   darken-left is now a PERSISTENT frame layer, so the slide-0 softening is
   keyed to the frame's data-active (set by hero-carousel.js), not the slide. */
.hero-carousel[data-active="0"] .hero-carousel__darken-left {
  background: linear-gradient(90deg,
    rgba(0, 0, 0, 0.45) 0%,
    rgba(0, 0, 0, 0.15) 40%,
    transparent         70%);
}

/* Persistent darken overlays — direct children of .hero-carousel (frame level),
   above the sliding track (z auto) and below the captions (z2). They no longer
   live inside a slide, so they never repaint as the track transforms. */
.hero-carousel__darken-bottom,
.hero-carousel__darken-left {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  transition: background 700ms var(--ease-settle, ease);   /* 700ms has mass — the feedback curve was off-doctrine here */
}

/* Bottom-up darken — fades into page bg for smooth section join */
.hero-carousel__darken-bottom {
  background: linear-gradient(180deg,
    rgba(0, 0, 0, 0.15) 0%,
    rgba(0, 0, 0, 0)    30%,
    rgba(0, 0, 0, 0.55) 70%,
    var(--bg-primary)   100%);
}

/* Left-side darken — gives copy on the bottom-left enough contrast */
.hero-carousel__darken-left {
  background: linear-gradient(90deg,
    rgba(0, 0, 0, 0.7)  0%,
    rgba(0, 0, 0, 0.35) 35%,
    transparent         60%);
}

/* Premium edge vignette — fixed to the carousel FRAME, not inside slides.
   Sits as a direct child of .hero-carousel (above the scrolling track) so
   banners ease THROUGH the edges during drag.
   z-index:2 = above track (z-index:auto); nav/dots at z-index:3 sit above this.

   TECHNIQUE: pure gradient vignette — NO backdrop-filter.
   backdrop-filter flickered on every Swiper slide transition because Chrome
   drops BF compositing when a sibling element (swiper-wrapper) runs a GPU
   transform. A multi-stop feathered gradient achieves the premium soft-edge
   look with zero compositing dependency — visually constant before/during/after
   every swipe.

   Layer promotion (transform: translateZ(0)) keeps these elements on their own
   compositor layer so they never need to repaint during the track animation. */
.hero-carousel__edge {
  position: absolute;
  top: 0;
  bottom: 0;
  width: clamp(44px, 9%, 168px);
  z-index: 2;
  pointer-events: none;
  /* iOS WebKit compositing stability — the real cause of the residual
     "vignette flickers on swipe" after backdrop-filter was already removed.
     The edge rides its own GPU layer above the Swiper track. On iOS Safari a
     PERMANENT `will-change: transform` reserves a backing store that WebKit
     evicts / re-rasterises while the sibling .swiper-wrapper runs its slide
     transform — so the gradient blanks for a frame, then repaints. Promoting
     harder is not the fix; STABILISING the layer is:
       • translate3d(0,0,0)        — keep it on its own layer (battle-tested)
       • backface-visibility:hidden — stop iOS dropping the layer mid-transform
       • NO will-change             — the permanent reservation is the thrash
                                       trigger on iOS; removing it stops the flash
     Solid on iOS Safari and desktop Chrome alike. */
  transform: translate3d(0, 0, 0);
  -webkit-transform: translate3d(0, 0, 0);
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}
.hero-carousel__edge--l {
  left: 0;
  /* Multi-stop feathered gradient: opaque dark core fades to nothing.
     Mimics the blurred-edge look without any compositing dependency. */
  background: linear-gradient(
    90deg,
    rgba(0, 0, 0, 0.72) 0%,
    rgba(0, 0, 0, 0.52) 25%,
    rgba(0, 0, 0, 0.28) 55%,
    rgba(0, 0, 0, 0.08) 78%,
    rgba(0, 0, 0, 0)    100%
  );
}
.hero-carousel__edge--r {
  right: 0;
  background: linear-gradient(
    270deg,
    rgba(0, 0, 0, 0.72) 0%,
    rgba(0, 0, 0, 0.52) 25%,
    rgba(0, 0, 0, 0.28) 55%,
    rgba(0, 0, 0, 0.08) 78%,
    rgba(0, 0, 0, 0)    100%
  );
}

/* Captions layer — persistent frame-level stack. pointer-events:none so a
   horizontal drag anywhere over the hero still reaches the Swiper track; the
   active slide's buttons re-enable pointer events explicitly (below). */
.hero-carousel__captions {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

/* All captions are stacked and absolutely filling the frame; only the
   .is-active one is visible. They CROSS-FADE in place on slide change —
   decoupled from the slide transform, so text no longer blanks/re-mounts.
   (Also more robust than the old GSAP approach: text is visible by default,
   not opacity:0-until-JS — survives a GSAP/script failure.) */
.hero-carousel__content {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  /* Lift the content block up off the section join — bottom-aligned, so a
     larger bottom padding rides the whole block (eyebrow→title→sub→CTA)
     higher and keeps the CTA clear of the next zebra stripe. */
  padding-bottom: clamp(120px, 15vh, 190px);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 700ms var(--ease-out, ease), visibility 0s linear 700ms;
}
.hero-carousel__content.is-active {
  opacity: 1;
  visibility: visible;
  transition: opacity 700ms var(--ease-out, ease);
}
/* Only the visible caption's buttons capture clicks; the rest of the caption
   area stays pointer-transparent so the carousel remains swipeable over it. */
.hero-carousel__content.is-active .btn { pointer-events: auto; }

@media (prefers-reduced-motion: reduce) {
  .hero-carousel__content { transition: opacity 1ms linear; }
}

.hero-carousel__copy {
  display: flex;
  flex-direction: column;
  gap: 22px;
  max-width: 880px;
}

.hero-carousel__eyebrow {
  font-family: var(--font-cjk-text);
  font-size: 12px;
  font-weight: var(--fw-medium);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.85);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
}

.hero-carousel__title {
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: clamp(44px, 5.4vw, 76px);
  line-height: 1.18;
  letter-spacing: -0.02em;
  color: var(--text-primary);
  margin: 0;
  text-shadow: 0 2px 24px rgba(0, 0, 0, 0.4);
  /* Default to single line (cinematic 1-line feel).
     Slides that want a 2-line title use explicit <br> — <br> overrides nowrap. */
  white-space: nowrap;
}

.hero-carousel__sub {
  font-family: var(--font-cjk-text);
  font-weight: var(--fw-light);
  font-size: 18px;
  line-height: 1.7;
  color: rgba(255, 255, 255, 0.85);
  margin: 0;
  max-width: 520px;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.4);
}

/* Meta line — used for countdown / status meta on event-style slides
   (e.g. AI 競賽：「參賽截止還有 1 天」) */
.hero-carousel__meta {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-1);
  font-family: var(--font-cjk-text);
  font-size: 16px;
  line-height: 1.7;
  color: rgba(255, 255, 255, 0.85);
  margin: 0;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.4);
}

.hero-carousel__countdown {
  font-family: var(--font-latin);
  font-size: 24px;
  font-weight: var(--fw-bold);
  color: var(--neutral-white);
  margin: 0 6px;
  font-style: normal;
}

.hero-carousel__ctas {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-2);
  flex-wrap: wrap;
}

/* Prev / next nav buttons — Artlist-style glass (matches popcorn / notification)
   Hidden by default, fade in when carousel is hovered or nav is focused.
   z-index:3 — above the fixed edge vignettes (z-index:2). */
.hero-carousel__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  border-radius: var(--radius-pill);
  border: none;
  padding: 0;
  cursor: pointer;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  background: linear-gradient(0deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.18) 2.45%,
    rgba(255, 255, 255, 0) 126.14%);
  box-shadow:
    rgba(255, 255, 255, 0.9) 1.1px 2.2px 0.5px -1.8px inset,
    rgba(255, 255, 255, 0.9) -1px -2.2px 0.5px -1.8px inset,
    rgba(0, 0, 0, 0.10) 2px 3px 2px 0 inset;
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  transition: opacity 250ms ease, background 200ms ease;
}

.hero-carousel:hover .hero-carousel__nav,
.hero-carousel__nav:focus-visible {
  opacity: 1;
  pointer-events: auto;
}

.hero-carousel__nav:hover {
  background: linear-gradient(0deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.30) 2.45%,
    rgba(255, 255, 255, 0) 126.14%);
}

.hero-carousel__nav--prev { left: 24px; }
.hero-carousel__nav--next { right: 24px; }

.hero-carousel__nav img {
  width: 22px;
  height: 22px;
  display: block;
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.5));
}

/* Dots — bottom center; active = wider yellow + glow.
   z-index:3 — above the fixed edge vignettes (z-index:2). */
.hero-carousel__dots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 32px;
  display: flex;
  justify-content: center;
  gap: 14px;
  z-index: 3;
}

.hero-carousel__dot {
  height: 4px;
  width: 16px;
  padding: 0;
  border: none;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.3);
  cursor: pointer;
  transition: width 350ms ease, background 250ms ease, box-shadow 250ms ease;
}

.hero-carousel__dot.is-active {
  width: 36px;
  background: var(--yellow-500);
  box-shadow: 0 0 12px rgba(255, 163, 63, 0.55);
}

/* ─────────────────────────────────────────
   RESPONSIVE — COCREATION CARD INTERNALS
   Grid layout handled in 07-cocreation-grid.css.
   ───────────────────────────────────────── */

/* ── Container query — card adjusts when placed in a narrow container ──
   Works in Chromium 105+, Safari 16+, Firefox 110+. Graceful fallback:
   the @media rules below handle older engines. */
.cocreation-card {
  container-type: inline-size;
  container-name: cocreation-card;
}

@container cocreation-card (max-width: 320px) {
  .cocreation-card__body {
    padding: var(--space-3);
    gap: var(--space-2);
  }

  .cocreation-card__title {
    font-size: var(--fs-h5);
  }

  .rf-fund__pct {
    font-size: 22px;
  }
}

/* ── Tablet (768–1023) — card internals ── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  .cocreation-card__body {
    padding: var(--space-4);
    gap: var(--space-3);
  }

  .cocreation-card__title {
    font-size: var(--fs-h4);
  }

  .rf-fund__pct {
    font-size: 26px;
  }
}

/* ── Phone (<768) — card internals ── */
@media (max-width: 767.98px) {
  .cocreation-card__body {
    padding: var(--space-4);
    gap: var(--space-3);
  }

  .cocreation-card__title {
    font-size: var(--fs-h4);
    line-height: 1.35;
  }

  /* Funded % figure: slightly smaller but still prominent */
  .rf-fund__pct {
    font-size: 24px;
  }

  .rf-fund__pct small {
    font-size: 14px;
  }

  .rf-fund__label {
    font-size: var(--fs-caption);
  }

  /* Clamp description so cards keep a consistent height in the mobile rail
     (prevents the tall-narrow-column problem). */
  .cocreation-card__desc {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  /* Meta row: allow wrap if amounts are long */
  .cocreation-card__meta-row {
    flex-wrap: wrap;
    gap: var(--space-1);
    font-size: var(--fs-caption);
  }

  /* Overlay variant: shallower ride-up on narrow */
  .cocreation-grid--overlay .cocreation-card__body {
    margin-top: -80px;
    padding: 50px var(--space-4) var(--space-4);
  }

  /* Note footer: smaller font */
  .cocreation-card__note {
    font-size: var(--fs-body-sm);
    line-height: var(--lh-body-sm);
  }

  /* Hover lift: disable on touch */
  .cocreation-card:hover {
    transform: none;
  }
}

/* ── Small phone (<480) ── */
@media (max-width: 480px) {
  .cocreation-card__body {
    padding: var(--space-3);
  }

  .cocreation-card__title {
    font-size: var(--fs-h5);
  }

  .rf-fund__pct {
    font-size: 20px;
  }
}

/* ── Reduced motion ── */
@media (prefers-reduced-motion: reduce) {
  .cocreation-card,
  .cocreation-card__bar-fill {
    transition: none;
  }
}

/* ── Hero carousel — phone. Full-bleed cover image (restored), wrapping
   title, fitted height, touch-friendly (arrows hidden, swipe + dots). ── */
@media (max-width: 767.98px) {
  .hero-carousel { height: clamp(440px, 72vh, 560px); }
  .hero-carousel__image { object-position: center 28%; }
  .hero-carousel__content { padding-bottom: clamp(76px, 12vh, 120px); }
  .hero-carousel__copy { gap: 14px; max-width: 100%; }
  .hero-carousel__title {
    white-space: normal;                 /* allow wrap — was nowrap (clipped the title) */
    font-size: clamp(28px, 7.6vw, 40px);
    line-height: 1.2;
  }
  .hero-carousel__sub { font-size: 15px; line-height: 1.6; }
  .hero-carousel__ctas { gap: 10px; }
  .hero-carousel__nav { display: none; }   /* swipe + dots on touch */
  .hero-carousel__dots { bottom: 18px; }
}
@media (max-width: 480px) {
  .hero-carousel__title { font-size: clamp(26px, 8vw, 34px); }
  .hero-carousel__ctas .btn { flex: 1 1 auto; }   /* CTAs share the row evenly */
}

/* MEDIA-GLASS — .media-glass shared text-on-photo primitive. [COMPONENT] */
/* ════════════════════════════════════════════════════════════════════════
   .media-glass — THE canonical "text on a photo" treatment (shared primitive)
   ════════════════════════════════════════════════════════════════════════
   One frosted-glass band for any text overlaid on an image: a PROGRESSIVE
   backdrop-blur (strong at the foot, dissolving upward via a mask) + a quiet
   ink tint + a text-shadow safety net — so copy reads on ANY photo (bright,
   dark, or busy). Lifted from the news hero (the original implementation).

   USAGE: put .media-glass on the TEXT PANEL that overlays the photo. The panel
   MUST come AFTER the <img>/cover in the DOM (backdrop-filter blurs whatever is
   painted behind it). Give the panel a tall TOP padding — that empty zone is
   where the blur dissolves into the photo. Children ride above the glass
   automatically. Adopters: community --overlay, ai-mosaic tiles. (The news
   hero keeps its inline copy as the origin, pending migration to this class.)
   ──────────────────────────────────────────────────────────────────────── */
.media-glass { position: relative; isolation: isolate; }
.media-glass > * { position: relative; z-index: 1; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); }
.media-glass::before {              /* progressive blur, masked to fade in upward */
  content: "";
  position: absolute;
  inset: 0;
  z-index: -2;
  -webkit-backdrop-filter: var(--lg-frost-media);   /* 22px media-card frost — one knob */
          backdrop-filter: var(--lg-frost-media);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, rgba(0, 0, 0, 0.5) 30%, #000 62%);
          mask-image: linear-gradient(to bottom, transparent 0%, rgba(0, 0, 0, 0.5) 30%, #000 62%);
  pointer-events: none;
}
.media-glass::after {               /* quiet ink tint ramp */
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(to bottom, transparent 0%, rgba(10, 10, 10, 0.30) 34%, rgba(10, 10, 10, 0.62) 100%);
  pointer-events: none;
}
/* No backdrop-filter (old engines): the tint alone carries legibility. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .media-glass::after {
    background: linear-gradient(to bottom, transparent 0%, rgba(10, 10, 10, 0.62) 30%, rgba(10, 10, 10, 0.88) 100%);
  }
}

/* DS-ACTIONBAR — bottom-pinned action region for overlays. [COMPONENT]
   ════════════════════════════════════════════════════════════════════════
   A bar that pins an overlay's primary CTA to the bottom so it is ALWAYS
   reachable: it sits as a flex-shrink:0 sibling BELOW a scrollable body — the
   body takes the remaining height and scrolls, this bar never does. Values are
   the proven .ztor-cart__foot recipe (assets/cart.css), tokenised. Adopted by
   .ds-drawer__foot (ds-drawer.css) and the non-drawer modals. The cart + shop
   filter sheet keep their own equivalent feet — do NOT refactor those.

   Host anatomy (the panel/dialog is a flex column):
     [ head ]                         ← fixed
     [ body  flex:1; overflow:auto ]  ← scrolls
     [ foot  .ds-actionbar ]          ← pinned, this file

   Honours the variant convention (data-size → --ds-scale). On phones the bar is
   compact and capped at 20svh so it can never eat more than ~20% of the (small)
   viewport — ≥80% stays for content — and it clears the iOS home indicator via
   env(safe-area-inset-bottom). Search-before-create: no shared action-bar
   existed (only per-component .ztor-cart__foot / .shop-sheet__apply /
   .cc-stepview__foot / .mdrawer__foot); this consolidates them.
   ──────────────────────────────────────────────────────────────────────── */
.ds-actionbar {
  flex-shrink: 0;                       /* never shrinks — the body scrolls, not this */
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding:
    calc(var(--space-5) * var(--ds-scale, 1))
    calc(var(--space-6) * var(--ds-scale, 1))
    max(calc(var(--space-6) * var(--ds-scale, 1)), env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--border-default);
  background: var(--bg-secondary);      /* matches the panel; the top border delineates it */
}

/* The relocated CTA row (.cc-stepview__foot / .completion-foot) carries its own
   top margin for in-flow layout; inside the bar the bar owns the spacing, so
   cancel it to avoid a double gap. */
.ds-actionbar > .cc-stepview__foot,
.ds-actionbar > .completion-foot { margin-top: 0; }

/* A hidden bar must truly hide — .ds-actionbar{display:flex} would otherwise win
   over the [hidden] UA rule and leave an empty bar visible. */
.ds-actionbar[hidden] { display: none; }

/* ── Phone — tight + hard-capped so the bar can't crowd the content ── */
@media (max-width: 767.98px) {
  .ds-actionbar {
    gap: var(--space-2);
    padding:
      calc(var(--space-3) * var(--ds-scale, 1))
      calc(var(--space-4) * var(--ds-scale, 1))
      max(calc(var(--space-4) * var(--ds-scale, 1)), env(safe-area-inset-bottom, 0px));
    max-block-size: 20svh;              /* ≤20% of the small viewport height (the ceiling) */
    overflow-y: auto;                   /* enforce the cap WITHOUT ever clipping the CTA */
  }
}

/* DS-DRAWER — canonical reusable slide-over drawer primitive. [COMPONENT]
   ════════════════════════════════════════════════════════════════════════
   The COMPONENTS.md "drawer consolidation target", realised. Right slide-over
   on desktop, bottom-sheet on phone. Scrim + panel; behaviour (open/close/
   focus-trap/scroll-lock) in assets/ds-drawer.js. Depth via border + glass
   per the brand "no drop shadows" rule (uses the sanctioned --glass-shadow).
   Stacks at --z-drawer (above sticky chrome, below modal).
   ──────────────────────────────────────────────────────────────────────── */
.ds-drawer {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  visibility: hidden;
  pointer-events: none;
}
.ds-drawer.is-open { visibility: visible; pointer-events: auto; }

.ds-drawer__scrim {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  -webkit-backdrop-filter: blur(2px);
          backdrop-filter: blur(2px);
  opacity: 0;
  /* scrim trails the panel out (light returns after the sheet clears) */
  transition: opacity calc(var(--dur-panel-exit, 280ms) + 60ms) ease;
}
.ds-drawer.is-open .ds-drawer__scrim {
  opacity: 1;
  /* and leads it in — the room finishes dimming while the sheet is still landing */
  transition: opacity calc(var(--dur-panel, 480ms) * 0.8) ease;
}

.ds-drawer__panel {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: min(440px, 92vw);
  display: flex;
  flex-direction: column;
  background: var(--bg-secondary);
  border-left: 1px solid var(--border-strong);
  box-shadow: var(--glass-shadow);          /* sanctioned glass depth, not a drop */
  transform: translateX(100%);
  /* asymmetric: base = EXIT (quick — users want gone); .is-open = ENTER (slow, mass) */
  transition: transform var(--dur-panel-exit, 280ms) var(--ease-emphasized);
}
.ds-drawer.is-open .ds-drawer__panel {
  transform: none;
  transition: transform var(--dur-panel, 480ms) var(--ease-panel);
}

.ds-drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-subtle);
}

.ds-drawer__close {
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  border: none;
  border-radius: var(--radius-pill);
  background: var(--white-alpha-8);
  color: var(--text-secondary);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 150ms var(--ease-out), color 150ms var(--ease-out);
}
.ds-drawer__close:hover { background: var(--white-alpha-16); color: var(--text-primary); }
.ds-drawer__close:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }

.ds-drawer__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: var(--space-5);
}

/* ── Pinned foot (the CTA region) ──
   A flex-shrink:0 sibling of __body that composes .ds-actionbar
   (css/components/ds-actionbar.css). The primitive (assets/ds-drawer.js)
   relocates each step's .cc-stepview__foot / .completion-foot out of the
   scrolling __body and into here, so the primary CTA is pinned to the panel
   bottom on desktop AND phone while the body scrolls under it. Created +
   populated by the registry; hidden until it holds a CTA. */
.ds-drawer__foot { flex: 0 0 auto; }   /* layout/skin comes from .ds-actionbar */

/* ── Step indicator (numbered) — reuses brand accent only ── */
.cc-step {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}
.cc-step__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
}
.cc-step__dot {
  width: 22px;
  height: 22px;
  border-radius: var(--radius-pill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-latin);
  font-size: 12px;
  font-weight: var(--fw-bold);
  background: var(--white-alpha-8);
  color: var(--text-tertiary);
  border: 1px solid var(--border-default);
}
.cc-step__item--active { color: var(--text-primary); }
.cc-step__item--active .cc-step__dot {
  background: var(--yellow-500);
  color: var(--yellow-ink);
  border-color: var(--yellow-500);
}
.cc-step__item--done { color: var(--text-secondary); }
.cc-step__item--done .cc-step__dot {
  background: rgba(255, 163, 63, 0.16);
  color: var(--yellow-500);
  border-color: rgba(255, 163, 63, 0.36);
}
.cc-step__label { white-space: nowrap; }

/* ── Step views inside the drawer ── */
.cc-stepview { display: flex; flex-direction: column; gap: var(--space-4); }
.cc-stepview__title {
  margin: 0;
  font-family: var(--font-cjk-display);
  font-size: var(--fs-h4);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}
.cc-stepview__lead {
  margin: 0;
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body-sm);
  color: var(--text-secondary);
}
.cc-stepview__foot {
  margin-top: var(--space-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.cc-stepview__foot .btn { width: 100%; }

/* reassurance / info callout — reward-only + no-charge messaging */
.cc-callout {
  position: relative;
  padding: var(--space-3) var(--space-4);
  padding-left: calc(var(--space-4) + 24px);   /* gutter for the absolute icon */
  border-radius: var(--radius-md);
  background: rgba(255, 163, 63, 0.10);
  border: 1px solid rgba(255, 163, 63, 0.28);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-caption);
  line-height: var(--lh-body-sm);
  color: var(--text-secondary);
}
.cc-callout--info {
  background: rgba(59, 130, 246, 0.10);
  border-color: rgba(59, 130, 246, 0.28);
}
.cc-callout strong { color: var(--text-primary); font-weight: var(--fw-bold); }
.cc-callout__icon {
  position: absolute;
  left: var(--space-4);
  top: calc(var(--space-3) + 2px);
  width: 16px;
  height: 16px;
  background-color: var(--yellow-500);
  -webkit-mask: url('assets/icons/info.svg') center / contain no-repeat;
          mask: url('assets/icons/info.svg') center / contain no-repeat;
}
.cc-callout--info .cc-callout__icon { background-color: var(--info-500); }
.cc-callout--err { background: rgba(239, 68, 68, 0.10); border-color: rgba(239, 68, 68, 0.30); }
.cc-callout--err .cc-callout__icon { background-color: var(--error-500); }
.cc-callout--err strong { color: var(--error-500); font-variant-numeric: tabular-nums; }
.cc-sca { background: var(--bg-card); border: 1px solid var(--border-default); border-radius: var(--radius-md); padding: var(--space-4); display: flex; flex-direction: column; gap: 4px; }
.cc-sca__bank { font-family: var(--font-cjk-display); font-weight: var(--fw-bold); color: var(--text-primary); }
.cc-sca__row { font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-secondary); }

/* tier picker (radio list) */
.cc-pick { margin: 0; padding: 0; border: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.cc-pick__legend { padding: 0; font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); }
.cc-pick__opt {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: var(--space-3);
  align-items: start;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color 150ms var(--ease-out), background 150ms var(--ease-out);
}
.cc-pick__opt:hover { border-color: var(--border-strong); }
.cc-pick__radio { margin-top: 3px; accent-color: var(--yellow-500); }
.cc-pick__radio:checked ~ .cc-pick__row .cc-pick__name { color: var(--text-primary); }
.cc-pick__opt:has(.cc-pick__radio:checked) {
  border-color: var(--yellow-500);
  background: rgba(255, 163, 63, 0.07);
}
.cc-pick__row { grid-column: 2; display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.cc-pick__name { font-family: var(--font-cjk-display); font-weight: var(--fw-bold); font-size: var(--fs-body); color: var(--text-secondary); }
.cc-pick__price { font-family: var(--font-latin); font-weight: var(--fw-bold); color: var(--yellow-500); font-variant-numeric: tabular-nums; }
.cc-pick__perks { grid-column: 2; margin-top: 4px; font-family: var(--font-cjk-text); font-size: var(--fs-caption); line-height: var(--lh-body-sm); color: var(--text-tertiary); }
/* perks render as separate nodes (so each localises); CSS supplies the separator */
.cc-pick__perk:not(:first-child)::before { content: " · "; }
.cc-pick__meta { grid-column: 2; margin-top: var(--space-1); }
.cc-pick__slots { font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); font-variant-numeric: tabular-nums; }
.cc-pick__slots--full { color: var(--warning-500); font-weight: var(--fw-medium); }
/* sold-out tiers (or in-stock tiers in waitlist mode) are shown but not selectable */
.cc-pick__opt--disabled { opacity: 0.55; }
.cc-pick__opt--disabled,
.cc-pick__opt--disabled .cc-pick__radio { cursor: not-allowed; }
.cc-pick__opt--disabled:hover { border-color: var(--border-default); background: transparent; }

/* ── Phone — bottom sheet ── */
@media (max-width: 767.98px) {
  .ds-drawer__panel {
    top: auto;
    bottom: 0;
    right: 0;
    left: 0;
    width: 100%;
    height: auto;
    max-height: 90vh;
    border-left: none;
    border-top: 1px solid var(--border-strong);
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    transform: translateY(100%);
  }
  .ds-drawer.is-open .ds-drawer__panel {
    transform: none;
    transition: transform var(--dur-sheet-m, 550ms) var(--ease-panel);   /* an 85vh panel needs the settle */
  }
  .ds-drawer__panel { transition: transform 300ms var(--ease-emphasized); }  /* mobile exit */
  /* grab pill — and unlike a decoration, it drags (ds.js sheet-drag) */
  .ds-drawer__head { position: relative; padding-top: calc(var(--space-4) + 10px); touch-action: none; }
  .ds-drawer__head::before {
    content: "";
    position: absolute; top: 8px; left: 50%; transform: translateX(-50%);
    width: 40px; height: 4px; border-radius: 4px;
    background: rgba(255, 255, 255, 0.22);
  }
}

/* while a finger owns the panel, the transition lets go (1:1, interruptible) */
.ds-drawer__panel.is-dragging { transition: none; }
/* the body's scroll never chains into the page (kills the iOS arrival rubber-band) */
.ds-drawer__body { overscroll-behavior: contain; }

/* While a drawer is open, suppress the floating bottom-nav pill so a phone
   bottom-sheet's footer CTA is never overlapped by it. */
body.ds-drawer-open .botnav { display: none; }

@media (prefers-reduced-motion: reduce) {
  .ds-drawer__scrim,
  .ds-drawer__panel { transition: none; }
}

/* ════════════════════════════════════════════════════════════════
   ds-toast.css — single global toast (window.DSToast). Bottom-centre,
   glass material, slides up + fades. Reused by the PDP and checkout.
   ════════════════════════════════════════════════════════════════ */
.ds-toast {
  position: fixed;
  left: 50%;
  bottom: calc(28px + env(safe-area-inset-bottom, 0px));
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  max-width: min(92vw, 440px);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-lg);
  background: var(--glass-fill);
  -webkit-backdrop-filter: var(--glass-backdrop);
          backdrop-filter: var(--glass-backdrop);
  border: var(--glass-border);
  box-shadow: var(--glass-shadow);
  color: var(--text-primary);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  opacity: 0;
  transform: translate(-50%, 14px);
  pointer-events: none;
  transition: opacity 220ms var(--ease-out), transform 220ms var(--ease-out);
}
.ds-toast.is-shown { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }
.ds-toast__dot {
  flex: none;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-pill);
  background: var(--yellow-500);
  color: var(--yellow-ink);
}
.ds-toast__dot svg { width: 13px; height: 13px; }
.ds-toast__msg { min-width: 0; }
.ds-toast__link {
  flex: none;
  margin-left: var(--space-1);
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  color: var(--yellow-500);
  font-family: var(--font-cjk-display);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-bold);
  white-space: nowrap;
}
.ds-toast__link:hover { color: var(--yellow-400); }
.ds-toast__link:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; border-radius: var(--radius-xs); }

@media (prefers-reduced-motion: reduce) {
  .ds-toast { transition: opacity 180ms ease; transform: translate(-50%, 0); }
}

/* Phones: lift above the floating bottom-nav pill (43-bottom-nav.css) so the
   toast never sits under it. Desktop/tablet hide the pill → keep the 28px rest. */
@media (max-width: 767.98px) {
  .ds-toast { bottom: var(--botnav-clearance); }
}

/* ════════════════════════════════════════════════════════════════
   ds-success-tick.css — ONE reusable animated success tick.

   Drop-in for any SUCCESS confirmation (not selection / status).
   Plays on mount: the glyph pops in with a slight overshoot, then the
   checkmark strokes itself on in one confident pass. No loop, no
   confetti — a small celebration, then stillness.

   ── Markup contract ──────────────────────────────────────────────
   Host owns the disc (background + colour, from the success-green
   token). Drop this SVG INSIDE the host element:

     <span class="cc-success__check ds-success-tick" aria-hidden="true">
       <svg class="ds-success-tick__svg" viewBox="0 0 36 36">
         <path class="ds-success-tick__check" fill="none"
               d="M10 18.5 l5 5 l11 -12"/>
       </svg>
     </span>

   The check stroke inherits the host's `color` (currentColor) so it
   matches whatever disc it sits on — green ✓ on the cc-success tinted
   disc, dark ✓ on the toast's solid dot. Override with --ds-tick-check
   if a specific contrast colour is needed.

   GPU-safe: animates transform / opacity / stroke-dashoffset only —
   never layout. Reduced-motion → fully visible, no pop, no draw.
   ════════════════════════════════════════════════════════════════ */

.ds-success-tick {
  /* glyph pop: scale in 0→1 with a slight overshoot, on mount */
  transform: scale(0);
  transform-origin: center;
  animation: ds-tick-pop 260ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
  /* opacity rides in fast so nothing flashes at scale 0 */
  opacity: 0;
}

.ds-success-tick__svg {
  width: 62%;
  height: 62%;
  display: block;
  overflow: visible;
}

/* The checkmark — strokes on via dashoffset after the pop settles.
   pathLength is normalised to 1 in markup so the dash math is
   resolution-independent. */
.ds-success-tick__check {
  stroke: var(--ds-tick-check, currentColor);
  stroke-width: 4;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: ds-tick-draw 300ms var(--ease-out, cubic-bezier(0.23, 1, 0.32, 1)) 240ms forwards;
}

@keyframes ds-tick-pop {
  0%   { transform: scale(0);   opacity: 0; }
  60%  { opacity: 1; }
  100% { transform: scale(1);   opacity: 1; }
}

@keyframes ds-tick-draw {
  from { stroke-dashoffset: 1; }
  to   { stroke-dashoffset: 0; }
}

/* Reduced motion — appear statically, fully visible, no pop / no draw. */
@media (prefers-reduced-motion: reduce) {
  .ds-success-tick {
    transform: none;
    opacity: 1;
    animation: none;
  }
  .ds-success-tick__check {
    stroke-dashoffset: 0;
    animation: none;
  }
}

/* ════════════════════════════════════════════════════════════════
   ds-zoom.css — fullscreen product-image viewer (window.DSZoom).
   Farfetch-parity INTERACTION on our DARK chrome (a white field would
   seam against #0a0a0a). Pinch / drag-pan zoom, swipe carousel, progress
   scrubber. The gesture surface is .ds-zoom__viewport (touch-action:none
   — JS owns every touch). Scroll-lock is handled globally by ds.js via the
   body.ds-zoom-open class (iOS-safe position:fixed) — not here.
   ════════════════════════════════════════════════════════════════ */
.ds-zoom {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  visibility: hidden;
  pointer-events: none;
  background: rgba(10, 10, 10, 0.94);          /* dark field — no seam vs the product */
  opacity: 0;
  transition: opacity 220ms var(--ease-emphasized);              /* exit — quick */
}
.ds-zoom[aria-hidden="false"] { visibility: visible; pointer-events: auto; opacity: 1; transition: opacity 320ms var(--ease-settle); }

/* ── gesture surface + horizontal slide track ── */
.ds-zoom__viewport {
  position: absolute;
  inset: 0;
  overflow: hidden;
  touch-action: none;                          /* JS owns pan / pinch / swipe */
  cursor: zoom-in;
}
.ds-zoom__track {
  display: flex;
  height: 100%;
  will-change: transform;
}
.ds-zoom__slide {
  flex: 0 0 100%;
  height: 100%;
  display: grid;
  place-items: center;
  padding: clamp(16px, 4vw, 56px);
  box-sizing: border-box;
}
.ds-zoom__img {
  /* mock parity: cap so a portrait/square product image fits the viewport height
     AND leaves ~80px each side for the hugging glass arrows (see .lb__img). */
  max-width: calc(100vw - 160px);
  max-height: 88vh;
  object-fit: contain;
  user-select: none;
  -webkit-user-drag: none;
  transform: translate3d(0, 0, 0) scale(1);
  outline: 1px solid rgba(255, 255, 255, 0.08); /* a defined edge vs the dark field */
}
/* touch pages by swipe (arrows hidden) → let the image use more width */
@media (hover: none) { .ds-zoom__img { max-width: 94vw; } }
/* promote to its own compositor layer ONLY while the viewer is open */
.ds-zoom[aria-hidden="false"] .ds-zoom__img { will-change: transform; }

/* ── chrome (glass, on our tokens) ── */
.ds-zoom__close,
.ds-zoom__nav {
  position: absolute;
  display: grid;
  place-items: center;
  padding: 0;
  border: var(--glass-border);
  border-radius: var(--radius-pill);
  background: var(--glass-fill);
  -webkit-backdrop-filter: var(--glass-backdrop);
          backdrop-filter: var(--glass-backdrop);
  color: var(--text-primary);
  cursor: pointer;
  z-index: 2;
}
.ds-zoom__close { top: clamp(12px, 3vw, 28px); right: clamp(12px, 3vw, 28px); width: 44px; height: 44px; }
.ds-zoom__close svg { width: 20px; height: 20px; }

.ds-zoom__nav {
  top: 50%;
  transform: translateY(-50%);
  width: 48px;
  height: 48px;
  /* the glass is the .liquid-glass ::before — the button itself must be clear
     (an opaque button bg would paint over the lens). JS positions left/right so
     the arrows HUG the image, not the screen edge. */
  border: 0;
  background: transparent;
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
}
.ds-zoom__nav svg { width: 22px; height: 22px; position: relative; z-index: 1; }
.ds-zoom__nav--prev { left: clamp(8px, 2vw, 24px); }   /* fallback until JS hugs the image */
.ds-zoom__nav--next { right: clamp(8px, 2vw, 24px); }
.ds-zoom__nav:disabled { opacity: 0; pointer-events: none; }   /* fade out at the ends */
.ds-zoom--single .ds-zoom__nav { display: none; }
.ds-zoom.is-zoomed .ds-zoom__nav { opacity: 0; pointer-events: none; }   /* mock: hide arrows while zoomed-in */
/* touch devices page by swipe — hide the arrows there (Farfetch parity) */
@media (hover: none) { .ds-zoom__nav { display: none; } }

/* ── progress scrubber (Farfetch signature) — a position thumb on a thin track ── */
.ds-zoom__scrub {
  position: absolute;
  left: 50%;
  bottom: clamp(16px, 4vw, 32px);
  transform: translateX(-50%);
  width: min(60vw, 360px);
  height: 3px;
  border-radius: 2px;
  background: var(--white-alpha-16, rgba(255, 255, 255, 0.16));
  overflow: hidden;
  z-index: 2;
}
.ds-zoom--single .ds-zoom__scrub { display: none; }
.ds-zoom__scrub-thumb {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 25%;
  border-radius: 2px;
  background: var(--text-primary, #fff);
  transition: left 280ms var(--ease-out), width 280ms var(--ease-out);
}

.ds-zoom__close:focus-visible,
.ds-zoom__nav:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  .ds-zoom { transition: none; }
  .ds-zoom__scrub-thumb { transition: none; }
}

/* ════════════════════════════════════════════════════════════════
   pdp-sheet.css — bundle configurator, slide-up sheet (mock parity).
   Every bundle component is a row (thumb + name + option radios);
   configurable rows carry the same data-cidx/data-vval controls as the
   inline picker, so shop-detail-render.js's wire() handlers drive both.
   Recovered 2026-07-02 from hand-edits made directly in the generated
   components.css (the rebuild had silently dropped them) — this file is
   now the single source. Rendered by bundleSheetHtml().
   ════════════════════════════════════════════════════════════════ */
/* ════════ Bundle configurator — slide-up sheet (mock parity) ════════ */
.pdp-sheet-scrim {
  position: fixed; inset: 0; z-index: 1400;
  background: rgba(0, 0, 0, 0.62);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  opacity: 0; pointer-events: none;
  transition: opacity 360ms ease;                                /* exit: light returns after the sheet clears */
}
.pdp-sheet-scrim.is-open { opacity: 1; pointer-events: auto; transition: opacity 450ms ease; }
.pdp-sheet {
  position: fixed; left: 50%; bottom: 0; z-index: 1410;
  width: min(780px, 100%); height: min(80vh, 720px);
  display: flex; flex-direction: column;
  background: var(--bg-secondary, #141414);
  border: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.08)); border-bottom: 0;
  border-radius: 26px 26px 0 0;
  box-shadow: 0 -24px 80px rgba(0, 0, 0, 0.6);
  transform: translate(-50%, 100%);
  transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1);   /* exit — users want gone */
  will-change: transform;
}
.pdp-sheet.is-open {
  transform: translate(-50%, 0);
  transition: transform 560ms cubic-bezier(0.32, 0.72, 0, 1);   /* enter — mass, settle */
}
.pdp-sheet__grab { width: 40px; height: 4px; border-radius: 4px; background: rgba(255, 255, 255, 0.22); margin: 12px auto 4px; flex: none; }
/* the grab + head are the drag surface (ds.js sheet-drag) — a promise kept */
.pdp-sheet__grab, .pdp-sheet__head { touch-action: none; }
.pdp-sheet.is-dragging { transition: none; }
.pdp-sheet__body { overscroll-behavior: contain; }
.pdp-sheet__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 6px 24px 16px; border-bottom: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.07)); flex: none; }
.pdp-sheet__title { margin: 0; font-family: var(--font-cjk-display, inherit); font-weight: 700; font-size: 20px; color: var(--text-primary); }
.pdp-sheet__sub { margin: 4px 0 0; font-size: 13px; color: var(--text-tertiary, #888); }
.pdp-sheet__close { flex: none; width: 38px; height: 38px; display: grid; place-items: center; border: 0; border-radius: var(--radius-pill, 999px); background: rgba(255, 255, 255, 0.06); color: var(--text-secondary, #bbb); cursor: pointer; transition: background 0.2s; }
.pdp-sheet__close:hover { background: rgba(255, 255, 255, 0.12); }
.pdp-sheet__close svg { width: 18px; height: 18px; }
.pdp-sheet__body { flex: 1 1 auto; overflow-y: auto; padding: 20px 24px 24px; display: flex; flex-direction: column; gap: 14px; -webkit-overflow-scrolling: touch; }

.pdp-sheet-row { position: relative; display: grid; grid-template-columns: 84px 1fr; gap: 16px; padding: 14px; border: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.08)); border-radius: var(--radius-xl, 20px); background: var(--bg-primary, #101010); transition: border-color 0.25s cubic-bezier(0.32, 0.72, 0, 1); }
.pdp-sheet-row::before { content: ""; position: absolute; left: 0; top: 14px; bottom: 14px; width: 3px; border-radius: 3px; background: var(--yellow-500, #FFA33F); opacity: 0; transform: scaleY(0.4); transition: opacity 0.25s, transform 0.25s cubic-bezier(0.32, 0.72, 0, 1); }
.pdp-sheet-row.is-done::before { opacity: 1; transform: scaleY(1); }
.pdp-sheet-row.is-flagged { border-color: var(--error-500, #e5484d); animation: pdp-shake 320ms var(--ease-out, ease); }
.pdp-sheet-row.is-included { opacity: 0.82; }
.pdp-sheet-row__thumb { width: 84px; height: 105px; border-radius: var(--radius-md, 12px); overflow: hidden; background: radial-gradient(120% 90% at 50% 15%, #262626, #0f0f0f); align-self: start; }
.pdp-sheet-row__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pdp-sheet-row__body { min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.pdp-sheet-row__name-line { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.pdp-sheet-row__name { font-weight: 600; font-size: 15px; color: var(--text-primary); }
.pdp-sheet-row__field { display: flex; flex-direction: column; gap: 8px; }
.pdp-sheet-row__flabel { font-size: 12px; color: var(--text-secondary, #9a9a9a); }
.pdp-sheet-row__flabel b { color: var(--text-tertiary, #888); font-weight: 400; }
.pdp-sheet-row__opts { display: flex; flex-wrap: wrap; gap: 8px; }
.pdp-sheet-row__status { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-tertiary, #888); }
.pdp-sheet-row__status.is-done { color: var(--yellow-500, #FFA33F); }
.pdp-sheet-row__status.is-included { color: var(--text-tertiary, #888); }

.pdp-sheet__foot { flex: none; display: flex; align-items: center; gap: 16px; padding: 16px 24px; padding-bottom: max(16px, env(safe-area-inset-bottom)); border-top: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.08)); background: rgba(14, 14, 14, 0.92); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); }
.pdp-sheet__info { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.pdp-sheet__dots { display: flex; gap: 5px; }
.pdp-sheet-dot { width: 22px; height: 4px; border-radius: 2px; background: rgba(255, 255, 255, 0.16); transition: background 0.25s var(--ease-out); }
.pdp-sheet-dot.is-on { background: var(--yellow-500, #FFA33F); }
.pdp-sheet__count { font-size: 13px; color: var(--text-secondary, #a8a8a8); font-variant-numeric: tabular-nums; }
.pdp-sheet__count b { color: var(--text-primary, #ececec); }
.pdp-sheet__cta { flex: none; min-height: 50px; padding: 0 26px; border: 0; border-radius: var(--radius-pill, 999px); font-family: var(--font-cjk-text, inherit); font-weight: 700; font-size: 15px; cursor: pointer; background: rgba(255, 255, 255, 0.14); color: var(--text-secondary, #9a9a9a); transition: background 0.25s cubic-bezier(0.32, 0.72, 0, 1), color 0.25s, transform 0.12s ease; }
.pdp-sheet__cta:active { transform: scale(0.98); }
.pdp-sheet__cta.is-ready { background: #fff; color: #0c0c0c; box-shadow: 0 8px 28px rgba(0, 0, 0, 0.4); }
.pdp-buy__add-chev { transition: transform 0.25s cubic-bezier(0.32, 0.72, 0, 1); }
.pdp-buy__add:hover .pdp-buy__add-chev { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) {
  .pdp-sheet, .pdp-sheet-scrim, .pdp-sheet-row, .pdp-sheet-row::before, .pdp-sheet-dot, .pdp-sheet__cta { transition: none; }
  .pdp-sheet-row.is-flagged { animation: none; }
}

/* ════════════════════════════════════════════════════════════════
   pdp-ref.css — the REFERENCE LAYER (尺寸指南 / 座位圖 content).
   One concept, two mounts:
     · PDP → a [data-ds-drawer="pdp-ref"] instance of the ds-drawer
       primitive (right slide-over desktop / bottom sheet phone — the
       primitive already handles both postures + focus trap + Esc).
     · Configurator sheet → .pdp-sheet__ref, an overlay panel that
       slides OVER the sheet body (never a second stacked sheet: the
       drawer's z-300 would render UNDER the sheet's z-1410).
   The gate (COMPONENTS.md): a line stays inline; a table or a map
   earns the layer; nothing gets a new page.
   Signature: the layer answers back — the buyer's selected size is the
   highlighted row when the table opens.
   ════════════════════════════════════════════════════════════════ */

/* ── controls row: standard selector (國際/US/EU/JP) + unit toggle ── */
.ref-controls {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.ref-controls .ref-units { margin-bottom: 0; }

/* ── unit toggle (公分 / 英吋) + standard selector share one look ── */
.ref-units {
  display: inline-flex;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-pill);
  overflow: hidden;
  margin-bottom: var(--space-4);
}
.ref-units button {
  border: 0; background: none; cursor: pointer;
  padding: 6px 16px;
  font-family: var(--font-cjk-text); font-size: 12px;
  color: var(--text-secondary);
  transition: background 0.2s, color 0.2s;
}
.ref-units button.is-on { background: var(--text-primary); color: #0c0c0c; font-weight: 700; }
.ref-units button:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: -2px; }

/* ── measurement table — flat-lay convention, tabular numerals ── */
.ref-table { width: 100%; border-collapse: collapse; font-size: 13px; font-variant-numeric: tabular-nums; }
.ref-table th {
  text-align: left; padding: 8px 10px;
  font-size: 12px; font-weight: 600; color: var(--text-tertiary);
}
.ref-table td { padding: 10px; border-top: 1px solid var(--border-subtle); color: var(--text-primary); }
.ref-table td:first-child { font-weight: 600; }
/* the answering-back row — the buyer's current pick */
.ref-table tr.is-you td { background: rgba(255, 163, 63, 0.08); }
.ref-table tr.is-you td:first-child { box-shadow: inset 3px 0 0 var(--yellow-500); }
.ref-table tr.is-you .ref-you {
  margin-left: 8px; font-size: 11px; font-weight: 400; color: var(--yellow-500);
}

/* ── section heading (身高體重參考 / 如何測量) ── */
.ref-sect {
  margin: var(--space-6) 0 var(--space-3);
  font-family: var(--font-cjk-display, inherit);
  font-size: 14px; font-weight: 700; color: var(--text-primary);
}

/* ── how to measure — diagram + steps ── */
.ref-measure {
  display: grid; grid-template-columns: 132px 1fr; gap: var(--space-4);
  align-items: start;
  padding: 14px;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-tertiary);
}
.ref-measure__fig { color: var(--text-secondary); }
.ref-measure__fig svg { display: block; width: 100%; height: auto; }
.ref-measure__steps { margin: 0; font-size: 12.5px; line-height: 1.6; }
.ref-measure__steps dt { font-weight: 700; color: var(--yellow-500); margin-top: 8px; }
.ref-measure__steps dt:first-child { margin-top: 0; }
.ref-measure__steps dd { margin: 1px 0 0; color: var(--text-secondary); }
@media (max-width: 420px) {
  .ref-measure { grid-template-columns: 1fr; }
  .ref-measure__fig { max-width: 150px; margin: 0 auto; }
}

/* ── fit note + disclaimer ── */
.ref-fit {
  margin-top: var(--space-4);
  padding: 14px 16px;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-tertiary);
  font-size: 13px; line-height: 1.65; color: var(--text-secondary);
}
.ref-fit b { color: var(--text-primary); }
.ref-note { margin-top: var(--space-3); font-size: 12px; color: var(--text-tertiary); line-height: 1.6; }

/* ── in-sheet push panel — slides over the sheet body ── */
.pdp-sheet__ref {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex; flex-direction: column;
  background: var(--bg-secondary, #141414);
  border-radius: inherit;
  transform: translateX(100%);
  transition: transform 420ms cubic-bezier(0.32, 0.72, 0, 1);
  visibility: hidden;
}
.pdp-sheet { overflow: hidden; }                 /* clip the ref panel to the sheet's radius */
.pdp-sheet__ref.is-pushed { transform: none; visibility: visible; }
/* the body slips aside a touch — the detour reads as depth, not replacement */
.pdp-sheet__body, .pdp-sheet__head { transition: transform 420ms cubic-bezier(0.32, 0.72, 0, 1); }
.pdp-sheet.is-ref-pushed .pdp-sheet__body,
.pdp-sheet.is-ref-pushed .pdp-sheet__head { transform: translateX(-24px); }

.pdp-sheet__ref-head {
  display: flex; align-items: center; gap: 12px;
  padding: 18px 24px 14px;
  border-bottom: 1px solid var(--border-subtle);
  flex: none;
}
.pdp-sheet__ref-back {
  flex: none; width: 38px; height: 38px;
  display: grid; place-items: center;
  border: 0; border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.06); color: var(--text-secondary);
  cursor: pointer; transition: background 0.2s;
}
.pdp-sheet__ref-back:hover { background: rgba(255, 255, 255, 0.12); }
.pdp-sheet__ref-back svg { width: 18px; height: 18px; }
.pdp-sheet__ref-title { margin: 0; font-size: 17px; font-weight: 700; color: var(--text-primary); }
.pdp-sheet__ref-sub { margin: 2px 0 0; font-size: 12.5px; color: var(--text-tertiary); }
.pdp-sheet__ref-body { flex: 1; overflow-y: auto; padding: 18px 24px 24px; -webkit-overflow-scrolling: touch; }

/* ── row-level trigger inside the sheet ── */
.pdp-sheet-row__guide {
  appearance: none; border: 0; background: none; padding: 0;
  font-family: var(--font-cjk-text); font-size: 12px;
  color: var(--text-tertiary); cursor: pointer;
  display: inline-flex; align-items: center; gap: 5px;
  transition: color 0.2s;
}
.pdp-sheet-row__guide:hover { color: var(--yellow-500); }
.pdp-sheet-row__guide:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 3px; }
.pdp-sheet-row__guide svg { width: 13px; height: 13px; }

@media (prefers-reduced-motion: reduce) {
  .pdp-sheet__ref, .pdp-sheet__body, .pdp-sheet__head { transition: none; }
}

/* PACKAGE-TIER — co-create reward-tier card (.pkg). [COMPONENT]
   ════════════════════════════════════════════════════════════════════════
   A reward/pledge tier on the co-create inner page: name + price + perks
   checklist + slots-left + CTA (支持方案 / 額滿→加入候補). Reward-only — these
   are co-creation rewards, never investments. Composes from existing tokens
   (accent, success/state colours, spacing, radius); no new token needed.
   data-size aware via the --ds-scale convention.
   ──────────────────────────────────────────────────────────────────────── */
.pkg-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gap-card, var(--space-5));
}
@media (max-width: 767.98px) {
  .pkg-grid { grid-template-columns: 1fr; }
}

.pkg {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: calc(var(--space-5) * var(--ds-scale, 1));
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  cursor: pointer;
  text-align: left;
  transition: border-color 200ms var(--ease-out), transform 160ms var(--ease-out);
}
.pkg:hover { border-color: var(--border-default); transform: translateY(-3px); }
.pkg:active { transform: translateY(-1px); }
.pkg:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }
/* hovering the card brings the quiet cue forward — the card is the button */
.pkg:hover .pkg__select { color: var(--yellow-400); }
.pkg:hover .pkg__select-arrow { transform: translateX(3px); }

.pkg--popular { border-color: rgba(255, 163, 63, 0.4); }
.pkg__flag {
  position: absolute;
  top: 0;
  right: var(--space-5);
  transform: translateY(-50%);
  padding: 4px var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--yellow-500);
  color: var(--yellow-ink);
  font-family: var(--font-cjk-text);
  font-size: 11px;
  font-weight: var(--fw-bold);
  letter-spacing: 0.04em;
}

.pkg__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-subtle);
}
.pkg__name {
  margin: 0;
  font-family: var(--font-cjk-display);
  font-size: var(--fs-h5);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}
.pkg__price {
  font-family: var(--font-latin);
  font-size: 26px;
  line-height: 1.1;
  font-weight: var(--fw-bold);
  color: var(--yellow-500);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.5px;
}

.pkg__perks {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  flex: 1;
}
.pkg__perk {
  position: relative;
  padding-left: var(--space-6);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body-sm);
  color: var(--text-secondary);
}
.pkg__perk::before {
  content: "";
  position: absolute;
  left: 0;
  top: 3px;
  width: 16px;
  height: 16px;
  background-color: var(--yellow-500);
  -webkit-mask: url('assets/icons/movie.svg') center / contain no-repeat;
          mask: url('assets/icons/movie.svg') center / contain no-repeat;
}

.pkg__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: auto;
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
}
.pkg__slots {
  font-family: var(--font-cjk-text);
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}
.pkg__slots--full { color: var(--warning-500); font-weight: var(--fw-medium); }

/* Quiet selectable cue — NOT a primary button. Four of these read as a list,
   not as competing CTAs; the single orange action lives in the funding band. */
.pkg__select {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-bold);
  color: var(--yellow-500);
}
.pkg__select-arrow { transition: transform 160ms var(--ease-out); }
.pkg__select--wait { color: var(--text-secondary); }

/* Full tier — quiet the card so the live tiers lead the eye. */
.pkg--full { opacity: 0.92; }
.pkg--full .pkg__price { color: var(--text-secondary); }
.pkg--full .pkg__perk::before { background-color: var(--text-tertiary); }

@media (prefers-reduced-motion: reduce) {
  .pkg { transition: none; }
  .pkg:hover { transform: none; }
}

/* COCREATE-DETAIL — co-create project inner page. [COMPONENT]
   ════════════════════════════════════════════════════════════════════════
   Layout follows the live reference anatomy (ztor.com/.../crowdfunding/*):
   a SINGLE centered column — 返回 → poster-left/title-right hero → full-width
   funding band (raised · backers · progress + % badge · status · CTA) →
   福利 tiers → about → plan → budget (ONE segmented bar + legend) → team
   credit list → stills. Structure only; skinned in Ztor tokens. Reuses
   status-tag (+ __note for 額滿/收款中, no 5th colour), .cocreation-card__bar
   (-fill) (fundbar DS), page chrome. Component-keyed; composes existing tokens.
   ──────────────────────────────────────────────────────────────────────── */
.cocreate-detail { display: block; }
.cc-wrap {
  max-width: 1040px;
  margin: 0 auto;
  padding: var(--space-6) var(--space-6) var(--space-section, var(--space-20));
  display: flex;
  flex-direction: column;
  gap: var(--space-10);
}

/* back link */
.cc-back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  align-self: flex-start;
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
  text-decoration: none;
  transition: color 150ms var(--ease-out);
}
.cc-back:hover { color: var(--text-primary); }
.cc-back__icon {
  width: 16px; height: 16px;
  background-color: currentColor;
  -webkit-mask: url('assets/icons/chevron_right.svg') center / contain no-repeat;
          mask: url('assets/icons/chevron_right.svg') center / contain no-repeat;
  transform: rotate(180deg);
}

/* status-tag note (額滿 / 收款中) — reuses the tag colour, NO 5th colour */
.status-tag__note { font-weight: var(--fw-regular); opacity: 0.9; }
.status-tag__note::before { content: "·"; margin: 0 var(--space-1) 0 2px; opacity: 0.7; }

/* ── HERO — poster left; right column = copy + funding band (fills the row) ── */
.cc-hero {
  display: grid;
  grid-template-columns: 320px minmax(0, 1fr);
  gap: var(--space-8);
  /* the right column is compacted (smaller display sizes + tighter spacing), and the
     poster matches that compacted height — bottoms align on every project and the
     poster never grows tall (object-fit cover crops). No per-page pixel heights. */
  align-items: stretch;
}
.cc-hero__poster {
  /* matches the compacted right column height; intrinsic ratio applies only when the
     hero is stacked (see the responsive blocks below). */
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--neutral-900);
  border: 1px solid var(--border-subtle);
}
.cc-hero__poster img { width: 100%; height: 100%; object-fit: cover; }
.cc-hero__main { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.cc-hero__copy { display: flex; flex-direction: column; gap: var(--space-2); }
.cc-hero__title {
  margin: 0;
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: clamp(24px, 2.6vw, 32px);
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}
.cc-hero__orig { margin: 0; font-family: var(--font-latin); font-size: var(--fs-body); color: var(--text-tertiary); }
.cc-hero__tags { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-top: var(--space-1); }
.cc-genre {
  padding: 3px var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-caption);
  color: var(--text-secondary);
}
.cc-hero__meta { margin: 0; font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-secondary); }

/* ── FUNDING BAND (full-width panel under hero) ── */
.cc-fund {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
}
.cc-fund__top { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-4); }
.cc-fund__figures { display: flex; flex-direction: column; gap: 2px; }
.cc-fund__raised {
  font-family: var(--font-latin);
  font-size: clamp(22px, 2.2vw, 28px);
  line-height: 1.1;
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.5px;
}
.cc-fund__raised.is-done { color: var(--success-500); }
.cc-fund__goal { font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-tertiary); }
.cc-fund__goal-num { font-family: var(--font-latin); font-variant-numeric: tabular-nums; }
.cc-fund__backers { display: inline-flex; align-items: center; gap: var(--space-1); font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-secondary); white-space: nowrap; }
.cc-fund__backers-icon {
  width: 16px; height: 16px;
  background-color: currentColor;
  -webkit-mask: url('assets/icons/nav/user.svg') center / contain no-repeat;
          mask: url('assets/icons/nav/user.svg') center / contain no-repeat;
}

/* progress + % badge at the fill tip.
   --cc-pct (0–100, set inline by cocreate-render.js) is the SINGLE source for
   both the bar fill width (via data-bar-fill) and the pill's position — they
   can never drift. --cc-badge-edge ≈ half the widest pill ("100%"), the inset
   that keeps the pill inside the track at 0% / 100% instead of clipping. */
.cc-fund__progress { position: relative; margin-top: var(--space-2); --cc-badge-edge: 24px; }
.cc-fund .cocreation-card__bar { height: 6px; }
.cc-fund__badge {
  position: absolute;
  /* the bar line runs through the pill's DEAD CENTRE — centred on the fill edge
     horizontally (var(--cc-pct)%) AND on the bar vertically; clamped so it never clips */
  top: 50%;
  left: clamp(var(--cc-badge-edge), calc(var(--cc-pct, 0) * 1%), calc(100% - var(--cc-badge-edge)));
  transform: translate(-50%, -50%);
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--yellow-500);
  color: var(--yellow-ink);
  font-family: var(--font-latin);
  font-size: 11px;
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.cc-fund__badge.is-done { background: var(--success-500); }

.cc-fund__status { margin: 0; font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-tertiary); }
.cc-fund__status--warn { color: var(--warning-500); }
.cc-fund__count { font-family: var(--font-latin); font-weight: var(--fw-bold); color: var(--yellow-500); font-variant-numeric: tabular-nums; }
.cc-fund__status--warn .cc-fund__count { color: var(--warning-500); }

.cc-fund__cta-row { display: flex; gap: var(--space-3); align-items: center; flex-wrap: wrap; }
.cc-fund__cta { min-width: 180px; flex: 1 1 auto; }
.cc-preview-btn { flex: 0 0 auto; display: inline-flex; align-items: center; gap: var(--space-1); }
/* 提醒我 → countdown state: reads as a status, not a press target */
.cc-fund__cta--counting { font-variant-numeric: tabular-nums; cursor: default; }
.cc-preview-btn svg { width: 18px; height: 18px; }

/* ── top action row: 返回 + 分享 ── */
.cc-backrow { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.cc-share-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 6px var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
  background: transparent;
  color: var(--text-secondary);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  cursor: pointer;
  transition: border-color 150ms var(--ease-out), color 150ms var(--ease-out);
}
.cc-share-btn:hover { border-color: var(--border-strong); color: var(--text-primary); }
.cc-share-btn:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }
.cc-share-btn svg { width: 16px; height: 16px; }

/* ── OVERLAY (centered modal) — shared by preview + share ── */
.cc-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(16px, 5vh, 48px);
  visibility: hidden;
  pointer-events: none;
}
.cc-overlay.is-open { visibility: visible; pointer-events: auto; }
.cc-overlay__scrim {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.7);
  -webkit-backdrop-filter: blur(4px);
          backdrop-filter: blur(4px);
  opacity: 0;
  transition: opacity 200ms var(--ease-out);
}
.cc-overlay.is-open .cc-overlay__scrim { opacity: 1; }
.cc-overlay__dialog {
  position: relative;
  width: min(720px, 100%);
  max-height: 90vh;
  overflow-y: auto;
  background: var(--bg-secondary);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-xl);
  box-shadow: var(--glass-shadow);
  transform: scale(0.96);
  opacity: 0;
  transition: transform 220ms var(--ease-out), opacity 220ms var(--ease-out);
}
.cc-overlay.is-open .cc-overlay__dialog { transform: none; opacity: 1; }
.cc-overlay__close {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  z-index: 2;
  width: 36px;
  height: 36px;
  border: none;
  border-radius: var(--radius-pill);
  background: var(--white-alpha-16);
  color: var(--text-primary);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 150ms var(--ease-out);
}
.cc-overlay__close:hover { background: var(--white-alpha-24); }
.cc-overlay__close:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }
.cc-overlay__body { padding: var(--space-6); }

/* preview content */
.cc-pv { display: flex; flex-direction: column; gap: var(--space-4); }
.cc-pv__title { margin: 0; font-family: var(--font-cjk-display); font-size: var(--fs-h4); font-weight: var(--fw-bold); color: var(--text-primary); }
.cc-pv__note, .cc-pv__info { margin: 0; font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); line-height: var(--lh-body-sm); color: var(--text-tertiary); }
.cc-pv__info { color: var(--text-secondary); line-height: 1.8; }
.cc-pv__video { width: 100%; aspect-ratio: 16 / 9; border-radius: var(--radius-md); background: var(--neutral-900); display: block; }
.cc-pv__frame { position: relative; aspect-ratio: 16 / 9; border-radius: var(--radius-md); overflow: hidden; background: var(--neutral-900); }
.cc-pv__frame img { width: 100%; height: 100%; object-fit: cover; opacity: 0.85; }
.cc-pv__play {
  position: absolute; inset: 0; margin: auto;
  width: 64px; height: 64px; border-radius: var(--radius-pill);
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.45);
  border: 1px solid var(--white-alpha-24);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  color: var(--neutral-white);
  pointer-events: none;
}
.cc-pv__play svg { width: 26px; height: 26px; }
.cc-pv__cap { position: absolute; left: var(--space-3); bottom: var(--space-3); padding: 3px var(--space-3); border-radius: var(--radius-pill); background: rgba(0, 0, 0, 0.55); color: var(--neutral-white); font-family: var(--font-cjk-text); font-size: var(--fs-caption); }
/* song preview */
.cc-pv__song { display: flex; gap: var(--space-4); align-items: center; }
.cc-pv__cover { width: 96px; height: 96px; border-radius: var(--radius-md); object-fit: cover; flex-shrink: 0; }
.cc-pv__songbody { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; flex: 1; }
.cc-pv__track { font-family: var(--font-cjk-display); font-weight: var(--fw-bold); color: var(--text-primary); }
.cc-pv__song audio { width: 100%; }
.cc-pv__placeholder { font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-tertiary); }
/* concert preview gallery */
.cc-pv__gallery { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-3); }
.cc-pv__gallery img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--radius-md); background: var(--neutral-900); }

/* user-action ribbon on a list card poster (per signed-in user × project) */
.cc-ribbon {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 4px var(--space-3);
  border-radius: var(--radius-pill);
  font-family: var(--font-cjk-text);
  font-size: 12px;
  font-weight: var(--fw-bold);
  background: rgba(10, 10, 10, 0.62);
  -webkit-backdrop-filter: blur(6px);
          backdrop-filter: blur(6px);
  border: 1px solid var(--white-alpha-16);
}
.cc-ribbon__dot { width: 7px; height: 7px; border-radius: var(--radius-pill); background: currentColor; flex-shrink: 0; }
.cc-ribbon--urgent  { color: var(--error-500);      border-color: rgba(239, 68, 68, 0.45); }
.cc-ribbon--warn    { color: var(--yellow-500);     border-color: rgba(255, 163, 63, 0.45); }
.cc-ribbon--success { color: var(--score-ink-high); border-color: rgba(48, 209, 88, 0.45); }
.cc-ribbon--neutral { color: var(--neutral-white);  border-color: var(--white-alpha-24); }
.cc-ribbon--urgent .cc-ribbon__dot { animation: cc-ribbon-pulse 1.4s ease-in-out infinite; }
@keyframes cc-ribbon-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
@media (prefers-reduced-motion: reduce) { .cc-ribbon--urgent .cc-ribbon__dot { animation: none; } }

/* roadmap timeline (製作進度 — platform / creator updates) */
.cc-timeline { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.cc-timeline__item { position: relative; padding: 0 0 var(--space-5) var(--space-6); }
.cc-timeline__item:last-child { padding-bottom: 0; }
.cc-timeline__item::before {
  content: ""; position: absolute; left: 6px; top: 8px; bottom: -2px;
  width: 2px; background: var(--border-default);
}
.cc-timeline__item:last-child::before { display: none; }
.cc-timeline__dot {
  position: absolute; left: 0; top: 5px;
  width: 14px; height: 14px; border-radius: var(--radius-pill);
  background: var(--bg-secondary); border: 2px solid var(--border-strong);
}
.cc-timeline__item--done .cc-timeline__dot { background: var(--success-500); border-color: var(--success-500); }
.cc-timeline__item--current .cc-timeline__dot { background: var(--yellow-500); border-color: var(--yellow-500); box-shadow: 0 0 0 4px rgba(255, 163, 63, 0.18); }
.cc-timeline__head { display: flex; align-items: center; gap: var(--space-2); margin-bottom: 2px; }
.cc-timeline__date { font-family: var(--font-latin); font-size: var(--fs-caption); font-weight: var(--fw-bold); color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.cc-timeline__by { font-family: var(--font-cjk-text); font-size: 11px; padding: 1px var(--space-2); border-radius: var(--radius-pill); background: var(--white-alpha-8); color: var(--text-tertiary); }
.cc-timeline__title { margin: 0; font-family: var(--font-cjk-display); font-size: var(--fs-body); font-weight: var(--fw-bold); color: var(--text-primary); }
.cc-timeline__item--upcoming .cc-timeline__title { color: var(--text-secondary); }
.cc-timeline__text { margin: 2px 0 0; font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); line-height: var(--lh-body-sm); color: var(--text-tertiary); }

/* share content */
.cc-sh { display: flex; flex-direction: column; gap: var(--space-4); }
.cc-sh__sub { margin: 0; font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-secondary); }
.cc-sh__copy { display: flex; gap: var(--space-2); }
.cc-sh__url { flex: 1; min-width: 0; padding: var(--space-2) var(--space-3); border-radius: var(--radius-md); border: 1px solid var(--border-default); background: var(--bg-tertiary); color: var(--text-secondary); font-family: var(--font-latin); font-size: var(--fs-body-sm); }
.cc-sh__copybtn { flex-shrink: 0; padding: 0 var(--space-4); border-radius: var(--radius-md); border: none; background: var(--yellow-500); color: var(--yellow-ink); font-family: var(--font-cjk-text); font-weight: var(--fw-bold); cursor: pointer; }
.cc-sh__targets { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.cc-sh__target { flex: 1; min-width: 120px; text-align: center; padding: var(--space-3); border-radius: var(--radius-md); border: 1px solid var(--border-default); color: var(--text-primary); text-decoration: none; font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); transition: border-color 150ms var(--ease-out); }
.cc-sh__target:hover { border-color: var(--border-strong); }

/* ── backing payment form (GoFundMe-style) + login gate + success ── */
.cc-pay, .cc-login { display: flex; flex-direction: column; gap: var(--space-4); }
.cc-pay__summary { background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-2); }
.cc-pay__sumrow { display: flex; justify-content: space-between; gap: var(--space-3); font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-secondary); }
.cc-pay__sumrow--total { padding-top: var(--space-2); border-top: 1px solid var(--border-subtle); color: var(--text-primary); font-weight: var(--fw-bold); }
.cc-pay__sumrow--total span:last-child { font-family: var(--font-latin); color: var(--yellow-500); font-variant-numeric: tabular-nums; }
.cc-pay__express { display: flex; gap: var(--space-2); }
.cc-pay__express-btn { flex: 1; height: 44px; border-radius: var(--radius-md); border: 1px solid var(--border-default); background: var(--white-alpha-8); color: var(--text-primary); font-family: var(--font-latin); font-weight: var(--fw-bold); cursor: pointer; transition: border-color 150ms var(--ease-out); }
.cc-pay__express-btn:hover { border-color: var(--border-strong); }
.cc-pay__or { display: flex; align-items: center; gap: var(--space-3); color: var(--text-tertiary); font-family: var(--font-cjk-text); font-size: var(--fs-caption); }
.cc-pay__or::before, .cc-pay__or::after { content: ""; flex: 1; height: 1px; background: var(--border-subtle); }
.cc-pay__form { display: flex; flex-direction: column; gap: var(--space-3); }
.cc-pay__grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
.cc-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.cc-field__label { font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); }
.cc-field__input { width: 100%; height: 44px; padding: 0 var(--space-3); border-radius: var(--radius-md); border: 1px solid var(--border-default); background: var(--bg-tertiary); color: var(--text-primary); font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); }
.cc-field__input:focus { outline: none; border-color: var(--yellow-500); box-shadow: 0 0 0 3px rgba(255, 163, 63, 0.18); }
/* iOS Safari auto-zooms (and breaks the layout) when a focused input is < 16px.
   Bump to 16px on touch pointers only — desktop keeps the tighter rhythm. */
@media (pointer: coarse) { .cc-field__input { font-size: 16px; } }
.cc-pay__save { display: flex; align-items: center; gap: var(--space-2); font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-secondary); }
.cc-pay__save input { accent-color: var(--yellow-500); }
/* saved-card fast path (dev-state hasCard) — used by ZtorPay + co-create */
.cc-pay__saved { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: var(--space-4); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); background: var(--bg-secondary); }
.cc-pay__saved-card { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cc-pay__saved-brand { font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); }
.cc-pay__saved-num { font-family: var(--font-latin, inherit); font-size: var(--fs-body); color: var(--text-primary); font-variant-numeric: tabular-nums; letter-spacing: 0.04em; }
.cc-pay__saved-new { border: 0; background: none; padding: 0; color: var(--yellow-500); font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); font-weight: var(--fw-medium); cursor: pointer; white-space: nowrap; }
.cc-pay__saved-new:hover { color: var(--yellow-400, #ffb866); }
.cc-pay__secure { display: flex; align-items: center; gap: var(--space-2); font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); line-height: var(--lh-body-sm); }
.cc-pay__lock { width: 8px; height: 8px; border-radius: var(--radius-pill); background: var(--score-ink-high); flex-shrink: 0; }
.cc-pay__err { margin: 0; color: var(--error-500); font-family: var(--font-cjk-text); font-size: var(--fs-caption); }
.cc-pay__err[hidden] { display: none; }
.cc-login__social { width: 100%; }

/* login gate (matches the 登入或創建帳號 interface) */
.cc-login2 { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-2) 0; }
.cc-login2__title { margin: 0 0 var(--space-2); text-align: center; font-family: var(--font-cjk-display); font-size: var(--fs-h4); font-weight: var(--fw-bold); color: var(--text-primary); }
/* Subtitle — the one line that tells a logged-out visitor WHY to sign in
   (Ogilvy). Sits under the title, above the identifier field. */
.cc-login2__sub { margin: 0 0 var(--space-4); text-align: center; font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); line-height: var(--lh-body-sm); color: var(--text-secondary); }
.cc-login2__method {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  height: 56px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-default);
  background: var(--bg-card);
  color: var(--text-primary);
  font-family: var(--font-cjk-text); font-size: var(--fs-body); font-weight: var(--fw-medium);
  cursor: pointer;
  transition: border-color 150ms var(--ease-out), background 150ms var(--ease-out);
}
.cc-login2__method:hover { border-color: var(--border-strong); background: var(--bg-tertiary); }
.cc-login2__method:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }
.cc-login2__method svg { position: absolute; left: var(--space-5); width: 20px; height: 20px; }
.cc-login2__socials { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-2); }
.cc-login2__social {
  height: 56px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-default);
  background: var(--bg-card);
  color: var(--text-primary);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: border-color 150ms var(--ease-out), background 150ms var(--ease-out);
}
.cc-login2__social:hover { border-color: var(--border-strong); background: var(--bg-tertiary); }
.cc-login2__social:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }
.cc-login2__social svg { width: 22px; height: 22px; }
.cc-login2__glyph { font-family: var(--font-latin); font-weight: var(--fw-bold); font-size: 20px; line-height: 1; }
.cc-login2__glyph--line { font-size: 12px; letter-spacing: 0.02em; }
.cc-login2__signup { width: 100%; margin-top: var(--space-1); }
.cc-login2__redeem { margin: var(--space-1) 0 0; font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-secondary); }
.cc-login2__redeem a { color: var(--yellow-500); text-decoration: underline; }
.cc-login2__legal { margin: var(--space-2) 0 0; text-align: center; font-family: var(--font-cjk-text); font-size: var(--fs-caption); line-height: var(--lh-body-sm); color: var(--text-tertiary); }
/* identifier-first form + full-width SSO rows */
.cc-login2__form { display: flex; flex-direction: column; gap: var(--space-3); }
.cc-login2__continue { width: 100%; }
.cc-login2__sso {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  height: 52px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-default);
  background: var(--bg-card);
  color: var(--text-primary);
  font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); font-weight: var(--fw-medium);
  cursor: pointer;
  transition: border-color 150ms var(--ease-out), background 150ms var(--ease-out);
}
.cc-login2__sso:hover { border-color: var(--border-strong); background: var(--bg-tertiary); }
.cc-login2__sso:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }
.cc-login2__sso svg { position: absolute; left: var(--space-5); width: 20px; height: 20px; }
.cc-login2__sso .cc-login2__glyph { position: absolute; left: var(--space-5); width: 20px; text-align: center; font-family: var(--font-latin); font-weight: var(--fw-bold); font-size: 18px; line-height: 1; }
.cc-login2__sso .cc-login2__glyph--line { font-size: 11px; }
/* identifier-first sub-steps: back link, identifier chip, forgot link, consent */
.cc-login2__back { align-self: flex-start; padding: 0; background: none; border: none; cursor: pointer; font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-secondary); }
.cc-login2__back::before { content: "‹ "; }
.cc-login2__back:hover { color: var(--text-primary); }
.cc-login2__ident { margin: 0; padding: var(--space-2) var(--space-3); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-tertiary); font-family: var(--font-latin); font-size: var(--fs-body-sm); color: var(--text-secondary); word-break: break-all; }
.cc-login2__link { align-self: center; padding: 0; background: none; border: none; cursor: pointer; font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--yellow-500); text-decoration: underline; }
.cc-check { display: flex; align-items: flex-start; gap: var(--space-2); cursor: pointer; font-family: var(--font-cjk-text); font-size: var(--fs-caption); line-height: var(--lh-body-sm); color: var(--text-secondary); }
.cc-check input { margin-top: 2px; flex-shrink: 0; accent-color: var(--yellow-500); }

/* ── auth overlay: desktop = spacious box sliding in from top-right;
      mobile = full-width sheet sliding down from the top ── */
.cc-auth { position: fixed; inset: 0; z-index: var(--z-modal); visibility: hidden; pointer-events: none; }
.cc-auth.is-open { visibility: visible; pointer-events: auto; }
.cc-auth__scrim {
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, 0.5);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  opacity: 0; transition: opacity var(--dur-page, 300ms) var(--ease-out);  /* was --dur-nav-hide; repointed when the nav-hide token slowed to 450ms (2026-07-03) */
}
.cc-auth.is-open .cc-auth__scrim { opacity: 1; }
.cc-auth__panel {
  position: absolute;
  top: calc(var(--header-height, 64px) + var(--space-3));
  right: var(--space-5);
  width: 408px;
  max-width: calc(100vw - 2 * var(--space-5));
  max-height: calc(100dvh - var(--header-height, 64px) - var(--space-6));
  overflow-y: auto;
  padding: var(--space-6);
  background: var(--bg-secondary);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-xl);
  box-shadow: var(--glass-shadow);
  transform-origin: top right;
  transform: translateY(-10px) scale(0.97);
  opacity: 0;
  transition: transform var(--dur-nav-show, 320ms) var(--ease-out), opacity var(--dur-nav-show, 320ms) var(--ease-out);
}
.cc-auth.is-open .cc-auth__panel { transform: none; opacity: 1; }
.cc-auth__close {
  position: absolute; top: var(--space-3); right: var(--space-3);
  width: 36px; height: 36px; border: none; border-radius: var(--radius-pill);
  background: var(--white-alpha-8); color: var(--text-secondary);
  font-size: 22px; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 150ms var(--ease-out), color 150ms var(--ease-out);
}
.cc-auth__close:hover { background: var(--white-alpha-16); color: var(--text-primary); }
.cc-auth__close:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }

@media (max-width: 767.98px) {
  .cc-auth__panel {
    top: 0; left: 0; right: 0; width: 100%; max-width: none;
    max-height: 92dvh;
    padding: calc(var(--header-height, 64px) + var(--space-4)) var(--space-4) var(--space-6);
    border-left: none; border-right: none; border-top: none;
    border-radius: 0 0 var(--radius-xl) var(--radius-xl);
    transform-origin: top center;
    transform: translateY(-100%);
    /* full-screen sheet on phone — mass tier, not the desktop popover's
       200ms feedback tier (2026-07-03). Base = exit; .is-open = enter. */
    transition: transform var(--dur-panel-exit, 280ms) var(--ease-emphasized, ease-in), opacity var(--dur-panel-exit, 280ms) var(--ease-emphasized, ease-in);
  }
  .cc-auth.is-open .cc-auth__panel {
    transform: none;
    transition: transform var(--dur-sheet-m, 550ms) var(--ease-panel), opacity var(--dur-sheet-m, 550ms) var(--ease-panel);
  }
}
@media (prefers-reduced-motion: reduce) {
  .cc-auth__scrim, .cc-auth__panel { transition: none; }
}

.cc-success { display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--space-3); padding: var(--space-6) 0 var(--space-2); }
.cc-success__check { width: 56px; height: 56px; border-radius: var(--radius-pill); display: inline-flex; align-items: center; justify-content: center; background: rgba(48, 209, 88, 0.16); color: var(--score-ink-high); font-size: 28px; font-weight: var(--fw-bold); }
.cc-success__title { margin: 0; font-family: var(--font-cjk-display); font-size: var(--fs-h3); font-weight: var(--fw-bold); color: var(--text-primary); }
.cc-success__msg { margin: 0; font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); line-height: 1.7; color: var(--text-secondary); max-width: 34em; }
.cc-success .cc-stepview__foot { width: 100%; margin-top: var(--space-2); }

@media (max-width: 767.98px) {
  .cc-overlay__body { padding: var(--space-5); }
  .cc-pv__gallery { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
  .cc-overlay__scrim, .cc-overlay__dialog { transition: none; }
}
.cc-fund__note {
  display: flex; gap: var(--space-2); margin: 0;
  font-family: var(--font-cjk-text); font-size: var(--fs-caption);
  line-height: var(--lh-body-sm); color: var(--text-tertiary);
}
.cc-fund__note-icon {
  flex-shrink: 0; width: 15px; height: 15px; margin-top: 2px;
  background-color: var(--success-500);
  -webkit-mask: url('assets/icons/info.svg') center / contain no-repeat;
          mask: url('assets/icons/info.svg') center / contain no-repeat;
}

/* ── SECTIONS ── */
.cc-section { display: flex; flex-direction: column; gap: var(--space-4); }
.cc-section__title {
  margin: 0;
  font-family: var(--font-cjk-display);
  font-size: var(--fs-h4);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}
.cc-section__lead { margin: 0; font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); line-height: var(--lh-body-sm); color: var(--text-secondary); }
.cc-prose { margin: 0; font-family: var(--font-cjk-text); font-size: var(--fs-body); line-height: 1.8; color: var(--text-secondary); white-space: pre-line; }

/* ── BUDGET — one segmented bar + legend (reference anatomy) ── */
.cc-budget { display: flex; flex-direction: column; gap: var(--space-5); }
.cc-budget__bar { display: flex; height: 12px; border-radius: var(--radius-pill); overflow: hidden; background: var(--white-alpha-8); }
.cc-budget__seg { height: 100%; }
.cc-budget__seg--1 { background: var(--yellow-500); }
.cc-budget__seg--2 { background: var(--info-500); }
.cc-budget__seg--3 { background: var(--success-500); }
.cc-budget__legend { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-5); }
.cc-budget__item { display: grid; grid-template-columns: auto 1fr; column-gap: var(--space-2); row-gap: 2px; align-items: center; }
.cc-budget__dot { width: 10px; height: 10px; border-radius: var(--radius-pill); }
.cc-budget__dot--1 { background: var(--yellow-500); }
.cc-budget__dot--2 { background: var(--info-500); }
.cc-budget__dot--3 { background: var(--success-500); }
.cc-budget__pct { font-family: var(--font-latin); font-weight: var(--fw-bold); font-size: var(--fs-h5); color: var(--text-primary); font-variant-numeric: tabular-nums; }
.cc-budget__label { grid-column: 1 / -1; font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-secondary); }
.cc-budget__amt { grid-column: 1 / -1; font-family: var(--font-latin); font-size: var(--fs-caption); color: var(--text-tertiary); font-variant-numeric: tabular-nums; }

/* ── TEAM — credit list (role + name) ── */
.cc-team { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: var(--space-4); }
.cc-team__link { display: flex; flex-direction: column; gap: 2px; text-decoration: none; }
.cc-team__role { font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); }
.cc-team__name { font-family: var(--font-cjk-display); font-weight: var(--fw-bold); font-size: var(--fs-body); color: var(--text-primary); }
.cc-team__link:hover .cc-team__name { color: var(--yellow-500); }

/* ── STILLS — 4-up gallery ── */
.cc-stills { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-3); }
.cc-stills__item { margin: 0; aspect-ratio: 16 / 9; overflow: hidden; border-radius: var(--radius-md); background: var(--neutral-900); }
.cc-stills__item img { width: 100%; height: 100%; object-fit: cover; }

/* ── PHONE STICKY CTA — floats above the bottom-nav pill (mobile-only add) ── */
.cc-stickybar {
  position: fixed;
  left: var(--space-3); right: var(--space-3); bottom: var(--space-20);
  z-index: var(--z-sticky);
  display: none;
  align-items: center; justify-content: space-between; gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--bg-card);
  border: 1px solid var(--border-strong);
  box-shadow: var(--glass-shadow);
}
.cc-stickybar__pct { font-family: var(--font-latin); font-weight: var(--fw-bold); font-size: var(--fs-h5); color: var(--yellow-500); font-variant-numeric: tabular-nums; }
.cc-stickybar__pct small { font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); margin-left: var(--space-1); font-weight: var(--fw-regular); }
.cc-stickybar .btn { flex-shrink: 0; }

/* list filter empty-state (cocreate.html grid) */
.cc-list-empty {
  grid-column: 1 / -1;
  padding: var(--space-16) var(--space-5);
  text-align: center;
  font-family: var(--font-cjk-text);
  color: var(--text-secondary);
}

/* ── EMPTY / SKELETON ── */
.cc-empty { display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--space-3); padding: var(--space-20) var(--space-5); }
.cc-empty__title { margin: 0; font-family: var(--font-cjk-display); font-size: var(--fs-h3); font-weight: var(--fw-bold); color: var(--text-primary); }
.cc-empty__body { margin: 0 0 var(--space-3); font-family: var(--font-cjk-text); color: var(--text-secondary); }

/* ── RESPONSIVE ── */
/* Tablet (768–1151px): the wide 2-col hero can only bottom-align when the wrap is
   at its full width (right column ≈ poster height). Below that the column outgrows
   the fixed-width poster and the poster must not grow — so stack with a centred,
   height-capped poster. The bottom-alignment rule only applies to the ≥1152px 2-col. */
@media (min-width: 768px) and (max-width: 1151.98px) {
  .cc-hero { grid-template-columns: 1fr; gap: var(--space-6); justify-items: center; }
  .cc-hero__poster { width: min(320px, 48%); aspect-ratio: 4 / 5; align-self: start; }
  .cc-hero__main { width: 100%; }
  .cc-fund { margin-top: 0; }
}
@media (max-width: 767.98px) {
  .cc-wrap { padding: var(--space-5) var(--space-4) var(--space-28); gap: var(--space-8); }
  .cc-hero { grid-template-columns: 1fr; gap: var(--space-4); }
  .cc-hero__poster { aspect-ratio: 16 / 9; }
  .cc-budget__legend { grid-template-columns: 1fr; gap: var(--space-3); }
  .cc-stills { grid-template-columns: repeat(2, 1fr); }
  .cc-fund__cta, .cc-fund__cta-row { width: 100%; }
  .cc-fund__cta { min-width: 0; }
  .cc-stickybar { display: flex; }
}

@media (prefers-reduced-motion: reduce) {
  .cocreation-card__bar-fill { transition: none; }
}

/* FEED TABS (.feed-tabs / .feed-tab) — primary feed filter. [COMPONENT] */
/* Primary feed filter. ONE yellow underline SLIDES between equal-width
   segments (not a per-tab border that teleports) — the slide itself tells the
   eye where the filter went. --feed-active (0-based index) + --feed-count are
   set by ds.js on tab click; the CSS below derives the bar from them. */
.feed-tabs {
  position: relative;
  display: flex;
}
/* The single sliding underline. width = one segment; the transform moves it by
   whole segments (translateX is relative to the bar's OWN width = one segment).
   bottom:-1px seats it on the tabbar hairline, like the old per-tab border. */
.feed-tabs::after {
  content: "";
  position: absolute;
  bottom: -1px;
  left: 0;
  width: calc(100% / var(--feed-count, 4));
  height: 2px;
  background: var(--yellow-500);
  transform: translateX(calc(var(--feed-active, 0) * 100%));
  transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1);
}
/* Each tab is an equal segment, label centred within it. */
.feed-tab {
  position: relative;
  flex: 1;
  text-align: center;
  padding: 14px 6px;
  background: none;
  border: none;
  color: var(--text-tertiary);
  font-family: var(--font-cjk-text);
  font-size: 15px;
  font-weight: var(--fw-medium);
  cursor: pointer;
  /* colour crossfade (150ms) resolves just before the slide lands, so the slide
     leads and colour confirms; press dims the label to 0.6 for 100ms on tap
     (opacity, not scale — scale would jiggle a full-width segment). */
  transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1), opacity 100ms ease-out;
}
.feed-tab:hover { color: var(--text-secondary); }
.feed-tab:active { opacity: 0.6; }
.feed-tab--active,
.feed-tab--active:hover {
  color: var(--yellow-500);
}
/* Reduced motion: the bar snaps to the active segment; colour + press stay
   (comprehension aids, no vestibular cost). */
@media (prefers-reduced-motion: reduce) {
  .feed-tabs::after { transition: none; }
}

/* ─────────────────────────────────────────
   STICKY FILTER PILL  (.tabstick)  +  MOBILE HEADER HIDE/SHOW
   Behaviour: this file + assets/sticky-tabs.js. Each primary tab row is wrapped in
   <div class="tabstick">. Spotlight (index) is excluded.

   POSITION: NATIVE position:sticky. The nav moves WITH the scroll and holds at the pin
   line — continuous, no teleport. (A relative→fixed swap is an INSTANT position change = a
   hard JUMP that no animation can hide; that teleport survived many attempts. Sticky is
   continuous AND behaves identically in Safari and Chrome → no "works in dev-tools, jumps on
   iPhone" bug. It self-reserves its flow box → zero CLS, no spacer.)

   THE STICKY HOST: a sticky element only sticks WITHIN its containing block (its parent). Some
   rows sit in a too-short flex row (e.g. a "nav + button" .tabs-row only as tall as the nav) →
   sticky has no room. So the controller picks a HOST per row at measure time — the .tabstick
   itself when its parent is tall, else the parent (whose own containing block is the tall
   section) — and marks it `.tabstick-host`. Everything positional keys off the host; the glass
   pill is always the descendant .glass-tabs.

   The controller only toggles a STYLE class (.is-pinned) as the host sticks; the glass material
   MORPHS in via a CSS transition. The header-follow transform (--tabs-shift) rides the header
   hide and lives ONLY on the stuck states.
   ───────────────────────────────────────── */

/* ── The sticky host (set by the controller). Natively sticky → no teleport, no spacer. */
.tabstick-host {
  position: -webkit-sticky;
  position: sticky;
  top: var(--header-height);
  z-index: var(--z-sticky);
}
/* DESKTOP (≥768): the snapped nav stays LEFT-aligned with the content grid (centring below is
   mobile-only — no horizontal shift on snap), and pins BELOW the full desktop header. That header
   carries the search row, so its visible height exceeds --header-height; offset by the header height
   + a small gap so it never covers the nav. Mobile keeps its top:var(--header-height) full-width pill. */
@media (min-width: 768px) {
  .tabstick-host { top: calc(var(--header-height) + var(--space-8)); }   /* header height + a small gap */
}
/* MOBILE ONLY — when the host IS the tabstick, centre the floating pill. */
@media (max-width: 767.98px) {
  .tabstick.tabstick-host { display: flex; justify-content: center; }
}

/* CONSISTENT CENTERING: when the host is a "nav + button" row (tabs-row / library-topbar /
   popcorn-navrow) the pill-nav `.tabstick` would otherwise sit LEFT. Give it a full line and centre
   the pill so EVERY snapped nav is centred (matches the centred bottom nav); the sibling button
   wraps below (these rows are flex-wrap on mobile). Scoped to `:has(> .glass-tabs)` so it targets the
   pill navs only — the shop category bar (.shop-filter) keeps its own stuck-centring + count-hide.
   Same alignment at rest AND stuck → no horizontal jump on snap. */
@media (max-width: 767.98px) {
  .tabstick-host > .tabstick:has(> .glass-tabs) {
    flex: 1 1 100%;
    min-width: 0;
    display: flex;
    justify-content: center;
  }
}

.tabstick > .glass-tabs { margin-bottom: 0; }
.tabstick--filter { margin-bottom: var(--space-8); }
@media (max-width: 767.98px) { .tabstick--filter { margin-bottom: var(--space-6); } }

/* ── The PILL — same box in flow and stuck (no width/position jump). As the host sticks, ONLY
   the glass MATERIAL resolves in, eased: backdrop-blur, depth shadow, border transition; the
   fill+sheen+rim ride pseudo OPACITY so the gradients fade rather than pop. Descendant
   selectors so it works whether the host is the .tabstick or the parent row. */
.tabstick-host .glass-tabs {
  position: relative;
  isolation: isolate;
  /* Own persistent compositing layer so the frost actually composites on iOS Safari while the
     pill rides as a sticky/hidden-header host. WITHOUT it the glass pill drops out on scroll-down
     and stays gone until a scroll-up repaint (real-device only; invisible in dev-tools). Same fix
     as the bottom nav (43-bottom-nav.css) and the will-change on .community-tabbar below. */
  -webkit-transform: translateZ(0);
          transform: translateZ(0);
  width: auto;                       /* hug → transparent sides (overrides phone width:100%) */
  max-width: 100%;
  margin-bottom: 0;
  padding-inline: 10px;              /* clear the pill's rounded ends — first/last tab not jammed */
  scroll-padding-inline: 10px;       /* same breathing room as a wide nav scrolls */
  border-color: rgba(255, 255, 255, 0.10);   /* always-glass: matches 我的投票 at rest too, not only when stuck */
  box-shadow: var(--glass-shadow);
  -webkit-backdrop-filter: var(--lg-frost);
          backdrop-filter: var(--lg-frost);
  transition: box-shadow      var(--dur-nav-morph) var(--ease-out),
              border-color    var(--dur-nav-morph) var(--ease-out),
              border-radius   var(--dur-nav-morph) var(--ease-out),
              -webkit-backdrop-filter var(--dur-nav-morph) var(--ease-out),
                      backdrop-filter var(--dur-nav-morph) var(--ease-out);
}
/* SHARED glass FILL (::before) for BOTH the .glass-tabs pill AND the .shop-filter
   pill — the white-5% lens that fades in on stick (the morph). NO sheen, NO spectral
   rim — exactly 21st.dev's recipe (the ::after rim was removed). */
.tabstick-host .glass-tabs::before,
.tabstick-host .shop-filter::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: var(--lg-fill); /* bright liquid glass (white-5%) — same material as 我的投票; shine + frost keep it visible */
  opacity: 1;    /* always-glass: the fill shows at rest too (not only on stick) */
  transition: opacity var(--dur-nav-morph) var(--ease-out);
}

/* ── STUCK: the shared glass MATERIAL resolves in on BOTH pill types (.is-pinned = currently stuck;
   .is-buried = replaced earlier row). Layout-specific bits (hug/scroll) stay per-component below. */
.tabstick-host.is-pinned .glass-tabs,
.tabstick-host.is-buried .glass-tabs,
.tabstick-host.is-pinned .shop-filter,
.tabstick-host.is-buried .shop-filter {
  border-color: rgba(255, 255, 255, 0.10);   /* = --glass-border colour */
  border-radius: var(--radius-pill);          /* always a PILL when snapped — some navs (e.g.
                                                 .library-genres 探索/AI/…) have a square base radius */
  box-shadow: var(--glass-shadow);
  -webkit-backdrop-filter: var(--lg-frost);
          backdrop-filter: var(--lg-frost);
}
.tabstick-host.is-pinned .glass-tabs::before,
.tabstick-host.is-buried .glass-tabs::before,
.tabstick-host.is-pinned .shop-filter::before,
.tabstick-host.is-buried .shop-filter::before { opacity: 1; }
.tabstick-host.is-pinned .glass-tabs::after,
.tabstick-host.is-buried .glass-tabs::after,
.tabstick-host.is-pinned .shop-filter::after,
.tabstick-host.is-buried .shop-filter::after  { opacity: 0.30; }

/* header-follow transform — ONLY when stuck (an in-flow host must not ride the header). */
.tabstick-host.is-pinned,
.tabstick-host.is-buried {
  transform: translateY(var(--tabs-shift, 0px));
}

/* ── BURIED: the earlier row fades out as the later one sticks (sequential replace). */
.tabstick-host.is-buried {
  opacity: 0;
  pointer-events: none;
  transition: opacity 360ms var(--ease-out);
}

/* ── 2nd-type nav (.shop-filter — the category bar). In flow it's the normal WRAPPING text filter;
   when its host sticks it morphs (eased — same transition + SHARED glass material/pseudos as the
   type-nav above) into a glass pill — single line, scrolls. Only the LAYOUT is per-component here;
   the material is shared, so the two navs read as one component on the sequential handoff. */
.tabstick-host .shop-filter {
  position: relative;
  isolation: isolate;
  /* Own compositing layer — see the .glass-tabs note above. Keeps the category pill's frost
     alive on iOS while the header-hidden host rides it, instead of dropping out on scroll. */
  -webkit-transform: translateZ(0);
          transform: translateZ(0);
  border: var(--glass-border);       /* 0.8px solid, colour transparent until stuck (mirrors .glass-tabs) */
  border-color: rgba(255, 255, 255, 0.10);   /* always-glass: matches 我的投票 at rest too, not only when stuck */
  box-shadow: var(--glass-shadow);
  -webkit-backdrop-filter: var(--lg-frost);
          backdrop-filter: var(--lg-frost);
  transition: box-shadow      var(--dur-nav-morph) var(--ease-out),
              border-color    var(--dur-nav-morph) var(--ease-out),
              border-radius   var(--dur-nav-morph) var(--ease-out),
              -webkit-backdrop-filter var(--dur-nav-morph) var(--ease-out),
                      backdrop-filter var(--dur-nav-morph) var(--ease-out);
}
.tabstick-host.is-pinned .shop-filter,
.tabstick-host.is-buried .shop-filter {
  flex-wrap: nowrap;                 /* was wrap → single scrolling line when stuck */
  width: auto;                       /* hug → floating pill */
  max-width: 100%;
  gap: 2px;
  padding: 5px 8px;
  overflow-x: auto;
  border-radius: var(--radius-pill);
}
.tabstick-host.is-pinned .shop-filter .shop-filter__item,
.tabstick-host.is-buried .shop-filter .shop-filter__item {
  padding: 6px 12px;
  white-space: nowrap;               /* don't let a label wrap inside the pill */
  text-decoration: none;             /* drop the in-flow underline; active = pill highlight below */
  border-radius: var(--radius-pill);
}
.tabstick-host.is-pinned .shop-filter .shop-filter__item--active,
.tabstick-host.is-buried .shop-filter .shop-filter__item--active {
  background: var(--white-alpha-10, rgba(255,255,255,0.10));
  color: var(--neutral-white);
}
/* The host row is the whole .shop-toolbar (it carries the filter + a count, so a sticky element
   can only be this row). MOBILE: when stuck, centre the pill, constrain it to the viewport (it
   scrolls inside), drop the in-flow divider/spacing, and hide the count so only the floating pill
   shows. DESKTOP keeps the toolbar's natural left-aligned layout (the pill stays at the grid's left
   edge in both states — no horizontal shift on snap). */
@media (max-width: 767.98px) {
  .shop-toolbar.tabstick-host.is-pinned,
  .shop-toolbar.tabstick-host.is-buried {
    justify-content: center;
    border-bottom-color: transparent;
    margin-top: 0;
    padding-bottom: 0;
  }
  .shop-toolbar.tabstick-host.is-pinned  > .tabstick,
  .shop-toolbar.tabstick-host.is-buried  > .tabstick { min-width: 0; max-width: 100%; }
  .shop-toolbar.tabstick-host.is-pinned  .shop-count,
  .shop-toolbar.tabstick-host.is-buried  .shop-count { display: none; }
}

/* ════════════════════════════════════════════════════════════════════
   COLLAPSE-TO-DROPDOWN  (nav-collapse.js) — a SNAPPED nav wider than the screen becomes a compact
   "[active] ⌄" trigger; tap drops the full list down as a glass panel. Only active when the module
   adds .is-collapsed (snapped + overflowing), so this never affects fit navs or the rest state.
   ════════════════════════════════════════════════════════════════════ */
.nav-trigger { display: none; }                 /* only shown when collapsed */
.tabstick.is-collapsed { position: relative; }  /* anchor the dropdown */
.tabstick.is-collapsed > .glass-tabs,
.tabstick.is-collapsed > .shop-filter { display: none; }   /* hide the chip row; trigger stands in */
.tabstick.is-collapsed > .nav-trigger {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  max-width: 100%;
  padding: 7px 14px;
  border-radius: var(--radius-pill);
  background: var(--lg-fill);             /* bright liquid glass (white-5%) — same material as 我的投票; shine + frost keep it visible */
  border: var(--glass-border);
  box-shadow: var(--glass-shadow);
  -webkit-backdrop-filter: var(--lg-frost);
          backdrop-filter: var(--lg-frost);
  color: var(--neutral-white);
  font-family: var(--font-cjk-text);
  font-size: 14px;
  line-height: 1.3;
  cursor: pointer;
}
.nav-trigger__label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nav-trigger__chev {
  width: 9px; height: 9px; flex: none; opacity: 0.7;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);     /* ⌄ */
  transition: transform var(--dur-nav-show) var(--ease-out);
}
.tabstick.is-open > .nav-trigger .nav-trigger__chev { transform: translateY(1px) rotate(-135deg); }  /* ⌃ */

/* OPEN: the real item list drops down below the trigger as a vertical glass panel (handlers intact). */
.tabstick.is-collapsed.is-open > .glass-tabs,
.tabstick.is-collapsed.is-open > .shop-filter {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  max-width: 88vw;
  max-height: 60vh;
  overflow-y: auto;
  z-index: var(--z-nav-pop);
  padding: 6px;
  gap: 2px;
  border-radius: 16px;
}
.tabstick.is-collapsed.is-open .glass-tabs__item,
.tabstick.is-collapsed.is-open .shop-filter__item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 11px 16px;
  border-radius: 10px;
  white-space: nowrap;
}

/* ════════════════════════════════════════════════════════════════════
   MOBILE HEADER HIDE/SHOW  (<768)
   ════════════════════════════════════════════════════════════════════ */
@media (max-width: 767.98px) {
  /* Header hide/show — TRANSFORM-ONLY, both directions (retune 2026-07-03).
     The old exit faded opacity alongside the slide; on a blur-dominant header
     (community: background none, frost on the element) the fade erased the bar
     ~100ms in and the 250ms slide read as a BLINK. translateY(-100%) fully
     clears the viewport, so opacity carried zero information — deleted. Never
     fade a backdrop-filter surface; if a fade is ever wanted, fade a child.
     EXIT rides --ease-panel (the glass slab has MASS — starts with the scroll
     gesture, glides the last edge out); ENTER stays fast-confident --ease-out.
     CRITICAL: transform/will-change apply ONLY in the hidden state — a
     permanent transform on the header promotes a layer that breaks its
     backdrop-filter. */
  .header {
    transition: transform var(--dur-nav-show) var(--ease-out);
  }
  body.header-hidden .header {
    transform: translateY(calc(-100% - 1px));   /* -1px buries the hairline rim */
    transition: transform var(--dur-nav-hide) var(--ease-panel);
  }

  /* The stuck host + community tab bar ride the header via --tabs-shift —
     SAME tokens, SAME curves, zero offset: one welded motion. */
  body.header-hidden .tabstick-host.is-pinned,
  body.header-hidden .tabstick-host.is-buried,
  body.header-hidden .community-tabbar {
    transition: transform var(--dur-nav-hide) var(--ease-panel);
  }
  .community-tabbar {
    transform: translateY(var(--tabs-shift, 0px));
    will-change: transform;
    transition: transform var(--dur-nav-show) var(--ease-out);
  }
}

/* ── Reduced motion / snap: the material resolves instantly, no eased morph, no drift. */
@media (prefers-reduced-motion: reduce) {
  .header,
  .community-tabbar,
  .tabstick-host .glass-tabs,
  .tabstick-host .glass-tabs::before,
  .tabstick-host .glass-tabs::after,
  .tabstick-host.is-pinned,
  .tabstick-host.is-buried { transition: none !important; }
}
body.motion-snap .header,
body.motion-snap .community-tabbar,
body.motion-snap .tabstick-host .glass-tabs,
body.motion-snap .tabstick-host .glass-tabs::before,
body.motion-snap .tabstick-host .glass-tabs::after,
body.motion-snap .tabstick-host.is-pinned,
body.motion-snap .tabstick-host.is-buried { transition: none !important; }

/* BADGE SHELF (.badge-shelf / .badge-medal). [COMPONENT] */
/* ───────────────────────────────────────────
   BADGE SHELF  (.badge-shelf / .badge-medal)
   A trophy row for 我的片庫. Each medal = a circular disc (brand-orange when
   earned, muted when locked) + label + optional unlock hint. Scrolls
   horizontally like the film rails; no own motion (reveal handled by the
   page's [data-reveal-stack]). Reuses the masked-icon (--icon) convention.
   ─────────────────────────────────────────── */
.badge-shelf {
  display: flex;
  gap: 28px;
  overflow-x: auto;
  scrollbar-width: none;
  padding: var(--space-1) 2px var(--space-2);
  scroll-snap-type: x proximity;
}
.badge-shelf::-webkit-scrollbar { display: none; }

.badge-medal {
  flex: 0 0 auto;
  width: 104px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  scroll-snap-align: start;
}
.badge-medal__disc {
  width: 84px;
  height: 84px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: radial-gradient(circle at 50% 28%,
    rgba(255, 207, 92, 0.98), var(--yellow-500) 58%, #c98f00 100%);
  border: 1px solid rgba(255, 255, 255, 0.16);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.45),
    inset 0 -3px 8px rgba(0, 0, 0, 0.28),
    0 6px 18px rgba(255, 163, 63, 0.18);
}
.badge-medal__icon {
  width: 38px;
  height: 38px;
  background-color: var(--yellow-ink);
  -webkit-mask: var(--icon) center / contain no-repeat;
          mask: var(--icon) center / contain no-repeat;
}
.badge-medal__label {
  margin-top: var(--space-3);
  font-family: var(--font-cjk-text);
  font-size: 13px;
  font-weight: var(--fw-medium);
  color: var(--text-primary);
}
.badge-medal__hint {
  margin-top: 3px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--text-tertiary);
}
/* Locked: greyed disc, no glow, dimmed glyph + label. */
.badge-medal--locked .badge-medal__disc {
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.08);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.badge-medal--locked .badge-medal__icon { background-color: rgba(255, 255, 255, 0.26); }
.badge-medal--locked .badge-medal__label { color: var(--text-tertiary); }

/* SECTION-EYEBROW TAG (.section-eyebrow--tag) — category pill. [COMPONENT] */
/* ───────────────────────────────────────────
   SECTION-EYEBROW TAG  (.section-eyebrow--tag)
   Turns a section eyebrow into a colour-coded category PILL. The COLOUR
   encodes the content vertical (影視 / 音樂 / 原力), so future labels like
   「影視 - 共創項目」 vs 「音樂 - 共創項目」 read as different families at a
   glance. Solid saturated fill + white text (every variant WCAG AA) stays
   legible on BOTH zebra stripes (yellow + black); a hairline keeps the pill
   edge on dark. Add ONE vertical modifier; default (none) = neutral pill.
   To add a vertical: one line in the colour-code list below.
   ─────────────────────────────────────────── */
.section-eyebrow--tag {
  /* A colour pill is a signal, never a faded label — beat the home-page
     eyebrow dim in refine-home.css. */
  opacity: 1 !important;
  /* Hug the text and CENTRE the label both axes. inline-flex + line-height:1
     fixes the mixed CJK/Latin baseline drift that made the text sit high. */
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: fit-content;
  max-width: 100%;
  padding: 6px var(--space-3);               /* symmetric → text optically centred */
  line-height: 1;
  border-radius: var(--radius-pill);
  letter-spacing: 0.06em;          /* it's a chip now, not a wide-tracked label */
  font-weight: var(--fw-bold);
  color: #fff;
  /* Plain flat fill — NO liquid-glass sheen (dropped the inset highlight +
     white border). A hair of transparency + a soft frost keeps it modern
     without the glossy 3D look. */
  background: color-mix(in srgb, var(--cat, var(--neutral-700)) 92%, transparent);
  backdrop-filter: blur(8px) saturate(1.05);
  -webkit-backdrop-filter: blur(8px) saturate(1.05);
}
@supports not (background: color-mix(in srgb, red, blue)) {
  .section-eyebrow--tag { background: var(--cat, var(--neutral-700)); }  /* solid fallback */
}
/* ── Vertical colour codes — set --cat. Extend by adding one line. ──
   Hues pulled from yellow's split-complementary + triadic arc (blue / violet /
   green) so they complement the yellow+black brand without reading "off-brand".
   Tuned to the mid-luminance balance point: NO solid colour can clear 3:1
   surface contrast against bright yellow AND near-black at the same time (the
   luminance bands don't overlap — it's mathematically impossible), so each is
   balanced (~2.7–4:1 on each stripe) and the WHITE label (AA 5–7:1 on every
   fill) is what carries legibility on both. Functional/cross = neutral. */
.section-eyebrow--film    { --cat: var(--cat-film); }    /* 影視    — blue   · PRIMARY */
.section-eyebrow--music   { --cat: var(--cat-music); }   /* 音樂    — red    · PRIMARY */
.section-eyebrow--origin  { --cat: var(--cat-origin); }  /* 原力/AI — orange · secondary */
.section-eyebrow--events  { --cat: var(--cat-events); }  /* 活動    — green  · secondary */
.section-eyebrow--neutral { --cat: var(--cat-neutral); } /* 功能/跨類 — charcoal */

/* AUCTION CARD (.shop-card--auction + .rf-auc-* + .rf-bid). [COMPONENT] */
/* ════════════════════════════════════════════════════════════════════════
   AUCTION CARD  ·  .shop-card--auction + .rf-auc-* + .rf-bid   [SHARED]
   ════════════════════════════════════════════════════════════════════════
   Rarible/Foundation timed-auction card: live countdown pill, current bid as
   the price line, bid count, leading/outbid/ending-soon state, hover 出價 CTA.
   Built on the shared .shop-card base.

   MOVED here from refine-shop.css (2026-06-16). It was keyed `body.rf-shop`,
   so it ONLY styled the shop pages — the creator 商店›拍賣 reused the markup but
   got none of the CSS (bid row collapsed). Keyed to the COMPONENT CLASS now, so
   it renders on EVERY page with .shop-card--auction: shop-auction.html, all 12
   creator pages, and anything built later. See COMPONENTS.md usage map.
   ════════════════════════════════════════════════════════════════════════ */
.shop-card--auction .shop-card__media::after {
  /* soft bottom scrim so the live pill always reads on busy art */
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 46%;
  background: linear-gradient(to top, rgba(10,10,10,0.66), transparent);
  pointer-events: none;
  border-radius: 0 0 14px 14px;
}
/* live / status pill — bottom-left over the media */
.rf-auc-pill {
  position: absolute;
  left: 10px;
  bottom: 10px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 24px;
  padding: 0 10px;
  border-radius: 999px;
  font-family: var(--font-latin);
  font-size: 11px;
  font-weight: var(--fw-bold);
  letter-spacing: 0.04em;
  background: rgba(10,10,10,0.66);
  border: 1px solid var(--white-alpha-16);
  color: var(--text-primary);
  backdrop-filter: blur(4px);
  font-variant-numeric: tabular-nums;
  transition: opacity 180ms ease;
}
.rf-auc-pill__tick {
  width: 6px; height: 6px; border-radius: 999px;
  background: var(--success-500);
  box-shadow: 0 0 0 0 rgba(34,197,94,0.55);
  animation: rf-ping 1.6s ease-out infinite;
}
@keyframes rf-ping {
  0% { box-shadow: 0 0 0 0 rgba(34,197,94,0.5); }
  70%,100% { box-shadow: 0 0 0 7px rgba(34,197,94,0); }
}
.rf-auc-pill--soon { color: var(--yellow-500); }
.rf-auc-pill--soon .rf-auc-pill__tick { background: var(--yellow-500); animation: none; box-shadow: none; }
.rf-auc-pill--ended { color: var(--text-tertiary); }
.rf-auc-pill--ended .rf-auc-pill__tick { background: var(--text-tertiary); animation: none; box-shadow: none; }

/* status row under the title: 目前出價 left, bid-count + state right */
.rf-auc-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-top: 2px;
}
.rf-auc-bid { display: flex; flex-direction: column; gap: 1px; }
.rf-auc-bid__label { font-size: 11px; letter-spacing: 0.04em; color: var(--text-tertiary); }
.rf-auc-bid__val {
  font-family: var(--font-latin);
  font-size: 16px;
  font-weight: var(--fw-bold);
  color: var(--neutral-white);
  font-variant-numeric: tabular-nums;
}
.rf-auc-meta { text-align: right; display: flex; flex-direction: column; gap: 2px; }
.rf-auc-meta__bids {
  font-family: var(--font-latin);
  font-size: 12px;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}
.rf-auc-state { font-family: var(--font-cjk-text); font-size: 11px; font-weight: var(--fw-bold); }
.rf-auc-state--leading { color: var(--success-500); }
.rf-auc-state--outbid { color: var(--error-500); }

/* bid CTA — appears on hover like the storefront quick-add */
.rf-bid {
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: 8px;
  z-index: 2;
  height: 38px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  border: none;
  border-radius: 10px;
  font-family: var(--font-cjk-text);
  font-size: 13px;
  font-weight: var(--fw-bold);
  color: var(--yellow-ink);
  background: var(--yellow-500);
  cursor: pointer;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 200ms var(--ease-out, ease), transform 200ms var(--ease-out, ease), background 140ms ease;
}
@media (hover: hover) and (pointer: fine) {
  .shop-card--auction:hover .rf-bid { opacity: 1; transform: translateY(0); }
  .shop-card--auction:hover .rf-auc-pill { opacity: 0; }
}
/* The gavel glyph had NO sizing rule anywhere (only .rf-wish/.rf-quickadd svgs
   were sized), so an unconstrained <svg> ballooned to ~200px on every auction
   card — latent on shop-auction (hover-only), visible once reused. Size it here. */
.rf-bid svg { width: 16px; height: 16px; flex: 0 0 auto; display: block; }
.rf-bid:hover { background: var(--yellow-400); }
.rf-bid:focus-visible { opacity: 1; transform: translateY(0); outline: 2px solid var(--neutral-white); outline-offset: 2px; }
.rf-bid--ended { background: var(--neutral-700); color: var(--text-tertiary); cursor: default; }
@media (hover: none), (pointer: coarse) {
  .rf-bid { position: static; opacity: 1; transform: none; margin-top: 10px; width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .rf-bid { transition: none !important; opacity: 1; transform: none; }
  .rf-auc-pill__tick { animation: none !important; }
}

/* ─────────────────────────────────────────
   RESPONSIVE — auction card
   ───────────────────────────────────────── */

/* ── Tablet: 768–1023px ── */
@media (min-width: 768px) and (max-width: 1023.98px) {
  /* Bid value can be slightly smaller so label doesn't crowd */
  .rf-auc-bid__val {
    font-size: var(--fs-body);
  }
}

/* ── Phone: ≤767px ── */
@media (max-width: 767.98px) {
  /* Status row: allow wrapping so bid + meta don't collide */
  .rf-auc-row {
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: flex-start;
  }

  /* Bid val: step down one notch on narrow */
  .rf-auc-bid__val {
    font-size: var(--fs-body-sm);
  }

  .rf-auc-meta {
    text-align: left;
  }

  /* Pill: ensure it never overflows the card image */
  .rf-auc-pill {
    max-width: calc(100% - 20px);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* Bid CTA: already handled by (hover: none) rule above,
     but enforce full width and adequate height on phone */
  .rf-bid {
    height: 44px;
    font-size: var(--fs-body-sm);
  }
}

/* ── Small phone: ≤480px ── */
@media (max-width: 480px) {
  /* Pill text: abbreviate by letting ellipsis kick in sooner */
  .rf-auc-pill {
    max-width: calc(100% - 12px);
    font-size: 10px;
  }

  .rf-auc-bid__label {
    font-size: 10px;
  }

  .rf-auc-meta__bids,
  .rf-auc-state {
    font-size: 10px;
  }
}

/* ════════════════════════════════════════════════════════════════
   shop-detail.css — Product Detail Page (PDP) layout.

   Desktop (≥768px): 3-pane gallery (vertical thumb strip + main image) in
   left column; sticky buy column in right. Details + recommendations are
   full-width sections below the grid.

   Mobile (≤767px): single column. Main image above a horizontal thumb
   carousel. Buy column stacks. Sticky bottom buy-bar (shop-buybar.css).

   Tokens only: --space-*, --radius-*, --yellow-500, --bg-secondary/tertiary,
   --border-*, --text-*, --font-cjk-display/cjk-text/latin.
   ════════════════════════════════════════════════════════════════ */

/* ── screen-reader only (a11y utility) ── */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

.pdp { padding-block: var(--space-10) 0; }

/* page wrapper — contains the full-width details + recs outside the container */
.pdp__page { display: flex; flex-direction: column; }

/* ── breadcrumb ── */
.page-head__crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-6);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
}
.page-head__crumbs a { color: var(--text-tertiary); transition: color 160ms var(--ease-out); }
.page-head__crumbs a:hover { color: var(--text-secondary); }
.page-head__crumb-sep { color: var(--text-disabled); }
.page-head__crumb-cur {
  color: var(--text-secondary);
  max-width: 46ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── two-column page grid ── */
.pdp__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-8);
  align-items: start;
  margin-bottom: var(--space-section);
}
@media (min-width: 768px) {
  .pdp__grid {
    /* left = gallery (3-pane); right = buy column */
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
    gap: var(--space-12);
  }
}

/* ══════════════════════════════════════════════════════════════
   GALLERY
   Desktop: [strip | main-image]   ← 3-pane, strip is left column
   Mobile:  [main-image]           ← thumbs below as horizontal scroll
   ══════════════════════════════════════════════════════════════ */
.pdp-gallery {
  display: grid;
  /* Mobile: single column — strip hidden, thumbs-mobile shown */
  grid-template-columns: 1fr;
  grid-template-areas:
    "stage";
  gap: var(--space-3);
  min-width: 0;
}

@media (min-width: 768px) {
  .pdp-gallery {
    /* Desktop: [strip] [stage] */
    grid-template-columns: 68px minmax(0, 1fr);
    grid-template-areas:
      "strip stage";
    gap: var(--space-3);
    align-items: start;
  }
}

/* ── Vertical thumb strip (desktop left) ── */
.pdp-gallery__strip {
  display: none; /* hidden on mobile */
  grid-area: strip;
  flex-direction: column;
  gap: var(--space-2);
  /* scrollable if many thumbs */
  overflow-y: auto;
  scrollbar-width: none;
  max-height: min(560px, calc(100vh - 160px));
}
.pdp-gallery__strip::-webkit-scrollbar { display: none; }

@media (min-width: 768px) {
  .pdp-gallery__strip {
    display: flex;
  }
}

/* ── Stage (main image + mobile carousel) ── */
.pdp-gallery__stage {
  grid-area: stage;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

/* ── Main image frame ── */
.pdp-gallery__frame {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  max-height: min(560px, calc(100vh - 160px));
  margin-inline: auto;
  padding: 0;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-2xl);
  background: var(--bg-secondary);
  overflow: hidden;
  cursor: zoom-in;
}
.pdp-gallery__img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.pdp-gallery__zoom-hint {
  position: absolute;
  right: var(--space-3);
  bottom: var(--space-3);
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-pill);
  color: var(--text-primary);
  background: var(--glass-fill);
  -webkit-backdrop-filter: var(--glass-backdrop);
          backdrop-filter: var(--glass-backdrop);
  border: var(--glass-border);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 200ms var(--ease-out), transform 200ms var(--ease-out);
}
.pdp-gallery__zoom-hint svg { width: 18px; height: 18px; }
@media (hover: hover) and (pointer: fine) {
  .pdp-gallery__frame:hover .pdp-gallery__zoom-hint,
  .pdp-gallery__frame:focus-visible .pdp-gallery__zoom-hint {
    opacity: 1;
    transform: translateY(0);
  }
}
@media (hover: none), (pointer: coarse) {
  .pdp-gallery__zoom-hint { opacity: 1; transform: none; }
}
.pdp-gallery__frame:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 3px; }

/* ── angle/view arrows over the image — liquid glass, hover-reveal on desktop, hidden on mobile (swipe instead) ── */
.pdp-gallery__arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: transparent;            /* glass comes from .liquid-glass ::before — the button's own bg must be clear */
  border-radius: var(--radius-pill);
  color: #fff;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms var(--ease-out), transform 160ms var(--ease-out);
}
.pdp-gallery__arrow svg { position: relative; z-index: 1; width: 18px; height: 18px; }
.pdp-gallery__arrow--prev { left: var(--space-3); }
.pdp-gallery__arrow--next { right: var(--space-3); }
.pdp-gallery__arrow:hover { transform: translateY(-50%) scale(1.06); }
.pdp-gallery__arrow:active { transform: translateY(-50%) scale(0.95); }
@media (hover: hover) and (pointer: fine) {
  .pdp-gallery__stage:hover .pdp-gallery__arrow { opacity: 1; pointer-events: auto; }
}
@media (hover: none), (pointer: coarse) {
  .pdp-gallery__arrow { display: none; }
}

/* ── Shared thumb appearance (strip + mobile carousel) ── */
.pdp-gallery__thumb {
  position: relative;
  flex: 0 0 auto;
  width: 64px;
  aspect-ratio: 1 / 1;
  padding: 0;
  border: 1.5px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-secondary);
  overflow: hidden;
  cursor: pointer;
  transition: border-color 160ms var(--ease-out), transform 160ms var(--ease-out);
}
.pdp-gallery__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pdp-gallery__thumb.is-active {
  border-color: var(--yellow-500);
  border-width: 2px;
}
.pdp-gallery__thumb:hover { transform: scale(1.04); border-color: var(--border-strong); }
.pdp-gallery__thumb.is-active:hover { border-color: var(--yellow-500); }
.pdp-gallery__thumb:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }

/* Video thumb overlay */
.pdp-gallery__thumb--video .pdp-gallery__thumb-play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--text-primary);
  background: rgba(10, 10, 10, 0.48);
}
.pdp-gallery__thumb--video .pdp-gallery__thumb-play svg { width: 18px; height: 18px; }

/* In the strip: thumbs are 68px and fill the strip column */
.pdp-gallery__strip .pdp-gallery__thumb {
  width: 100%;
  aspect-ratio: 1 / 1;
}

/* ── Mobile horizontal thumbnail carousel ── */
.pdp-gallery__thumbs-mobile {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  scrollbar-width: none;
  scroll-snap-type: x proximity;
  padding-bottom: var(--space-1);
}
.pdp-gallery__thumbs-mobile::-webkit-scrollbar { display: none; }
.pdp-gallery__thumbs-mobile .pdp-gallery__thumb {
  width: 60px;
  scroll-snap-align: start;
}

/* On desktop: hide the mobile carousel */
@media (min-width: 768px) {
  .pdp-gallery__thumbs-mobile { display: none; }
}

/* ══════════════════════════════════════════════════════════════
   BUY COLUMN
   ══════════════════════════════════════════════════════════════ */
.pdp-buy {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
@media (min-width: 768px) {
  .pdp-buy { position: sticky; top: var(--space-10); }
}
.pdp-buy__badge { align-self: flex-start; margin-bottom: var(--space-3); }
.pdp-buy__eyebrow {
  display: block;
  margin-bottom: var(--space-2);
  color: var(--text-tertiary);
}
.pdp-buy__title {
  margin: 0 0 var(--space-4);
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  font-size: var(--fs-h3);
  line-height: var(--lh-tight, 1.25);
  letter-spacing: -0.01em;
  color: var(--text-primary);
}
@media (min-width: 768px) { .pdp-buy__title { font-size: var(--fs-h2); } }

/* ── price ── */
.pdp-price {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}
/* short lead under the price (buy column) — full description, clamped */
.pdp-buy__lead {
  margin: 0 0 var(--space-6);
  color: var(--text-secondary);
  font-size: var(--fs-body-sm, 14px);
  line-height: 1.65;
  max-width: 48ch;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.pdp-price__now {
  font-family: var(--font-latin);
  font-size: var(--fs-h3);
  font-weight: var(--fw-bold);
  color: var(--yellow-500);
  font-variant-numeric: tabular-nums;
}
.pdp-price__sub {
  font-family: var(--font-latin);
  font-size: var(--fs-body-sm);
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}
.pdp-price__was {
  font-family: var(--font-latin);
  font-size: var(--fs-body-sm);
  color: var(--text-tertiary);
  text-decoration: line-through;
}
.pdp-price__save {
  font-family: var(--font-cjk-text);
  font-size: var(--fs-caption);
  font-weight: var(--fw-bold);
  color: var(--yellow-500);
  padding: 2px var(--space-2);
  border: 1px solid var(--yellow-500);
  border-radius: var(--radius-pill);
}

/* ── variant header row (label + guide link) ── */
.pdp-variants {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  margin-bottom: var(--space-6);
}
.pdp-variant__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-2);
}
.pdp-variant__label {
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-medium);
  color: var(--text-secondary);
}
.pdp-variant__colour-name {
  font-weight: var(--fw-regular);
  color: var(--text-tertiary);
}
.pdp-variant__guide {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0;
  border: none;
  background: none;
  font-family: var(--font-cjk-text);
  font-size: var(--fs-caption);
  color: var(--yellow-500);
  cursor: pointer;
  transition: color 160ms var(--ease-out);
}
.pdp-variant__guide:hover { color: var(--yellow-400); }
.pdp-variant__guide:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; border-radius: var(--radius-sm); }
.pdp-variant__guide-icon { display: inline-grid; place-items: center; }
.pdp-variant__guide-icon svg { width: 14px; height: 14px; }

/* ── Colour swatches ── */
.pdp-variant__opts--swatches {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.pdp-swatch {
  position: relative;
  width: 32px;
  height: 32px;
  padding: 0;
  border-radius: var(--radius-pill);
  border: 2px solid transparent;
  background: transparent;
  cursor: pointer;
  transition: border-color 160ms var(--ease-out), transform 160ms var(--ease-out), box-shadow 160ms var(--ease-out);
  /* ring gap via outline-offset trick */
}
.pdp-swatch__fill {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--swatch-bg, var(--border-strong));
  /* slight inner shadow for depth */
  box-shadow: inset 0 1px 2px rgba(0,0,0,0.3);
}
.pdp-swatch:hover { transform: scale(1.1); }
.pdp-swatch:focus-visible {
  outline: 2px solid var(--yellow-500);
  outline-offset: 3px;
}
.pdp-swatch.is-selected {
  border-color: var(--yellow-500);
  box-shadow: 0 0 0 1px var(--bg-primary), 0 0 0 3px var(--yellow-500);
}
.pdp-swatch.is-oos {
  opacity: 0.4;
  cursor: not-allowed;
}
.pdp-swatch.is-oos::after {
  content: '';
  position: absolute;
  inset: 2px;
  border-radius: var(--radius-pill);
  background: linear-gradient(135deg, transparent 44%, var(--text-disabled) 44%, var(--text-disabled) 56%, transparent 56%);
}

/* ── Size grid ── */
.pdp-variant__opts--sizes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.pdp-size-chip {
  position: relative;
  min-height: 44px;
  min-width: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-2) var(--space-4);
  white-space: nowrap;            /* long size labels (A2（420 × 594 mm）) stay on one line */
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  background: var(--white-alpha-4);
  color: var(--text-primary);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: border-color 160ms var(--ease-out), background 160ms var(--ease-out);
}
.pdp-size-chip:hover { border-color: var(--border-strong); background: var(--white-alpha-8); }
.pdp-size-chip.is-selected {
  border-color: var(--yellow-500);
  border-width: 2px;
  background: rgba(255, 163, 63, 0.08);
  color: var(--text-primary);
}
.pdp-size-chip:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }
.pdp-size-chip.is-oos {
  color: var(--text-disabled);
  cursor: not-allowed;
  text-decoration: line-through;
  opacity: 0.6;
}
/* struck line for sold-out */
.pdp-size-chip__oos {
  position: absolute;
  inset: 0;
  border-radius: var(--radius-md);
  background: linear-gradient(to bottom right, transparent calc(50% - 0.5px), var(--border-default) calc(50% - 0.5px), var(--border-default) calc(50% + 0.5px), transparent calc(50% + 0.5px));
  pointer-events: none;
}

/* ── Generic chip (fallback for other variant types) ── */
.pdp-chip {
  position: relative;
  min-width: 48px;
  min-height: 44px;
  padding: 0 var(--space-4);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  background: var(--white-alpha-4);
  color: var(--text-primary);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: border-color 160ms var(--ease-out), background 160ms var(--ease-out), color 160ms var(--ease-out);
}
.pdp-chip:hover { border-color: var(--border-strong); }
.pdp-chip.is-selected {
  border-color: var(--yellow-500);
  background: rgba(255, 163, 63, 0.08);
  color: var(--text-primary);
}
.pdp-chip:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }
.pdp-chip.is-oos {
  color: var(--text-disabled);
  cursor: not-allowed;
  text-decoration: line-through;
  opacity: 0.7;
}
.pdp-chip__oos { font-size: var(--fs-caption); text-decoration: none; color: var(--text-disabled); }
.pdp-variant.is-flagged .pdp-variant__label { color: var(--error-500); }
.pdp-variant.is-flagged .pdp-variant__opts,
.pdp-variant.is-flagged .pdp-variant__opts--swatches,
.pdp-variant.is-flagged .pdp-variant__opts--sizes {
  animation: pdp-shake 320ms var(--ease-out);
}
@keyframes pdp-shake {
  0%,100% { transform: translateX(0); }
  25%      { transform: translateX(-4px); }
  75%      { transform: translateX(4px); }
}

/* guard note */
.pdp-buy__guard { margin: 0 0 var(--space-4); }

/* ── Actions row: Add-to-Cart + Heart icon ── */
.pdp-buy__actions {
  display: flex;
  gap: var(--space-2);
  align-items: stretch;
  margin-bottom: var(--space-4);
}
.pdp-qty {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-pill);
  background: var(--white-alpha-4);
}
.pdp-qty__btn {
  width: 44px;
  height: 52px;
  display: grid;
  place-items: center;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  color: var(--text-secondary);
  border-radius: var(--radius-pill);
  transition: color 160ms var(--ease-out), background 160ms var(--ease-out);
}
.pdp-qty__btn:hover { color: var(--text-primary); background: var(--white-alpha-8); }
.pdp-qty__btn:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: -2px; }
.pdp-qty__btn svg { width: 16px; height: 16px; }
.pdp-qty__val {
  min-width: 36px;
  text-align: center;
  font-family: var(--font-latin);
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

/* Add-to-Cart: fills remaining width */
.pdp-buy__add {
  flex: 1 1 auto;
  gap: var(--space-2);
}
.pdp-buy__add-glyph { display: inline-grid; place-items: center; }
.pdp-buy__add-glyph svg { width: 18px; height: 18px; }

/* Heart icon button: square bordered, never full-width */
.pdp-buy__heart {
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  background: var(--white-alpha-4);
  color: var(--text-secondary);
  cursor: pointer;
  transition: border-color 160ms var(--ease-out), color 160ms var(--ease-out), background 160ms var(--ease-out);
}
.pdp-buy__heart svg { width: 20px; height: 20px; display: block; }
.pdp-buy__heart:hover {
  border-color: var(--yellow-500);
  color: var(--yellow-500);
  background: rgba(255, 163, 63, 0.08);
}
.pdp-buy__heart:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }
.pdp-buy__heart[aria-pressed="true"] {
  border-color: var(--yellow-500);
  color: var(--yellow-500);
  background: rgba(255, 163, 63, 0.10);
}
.pdp-buy__heart[aria-pressed="true"] svg { fill: var(--yellow-500); }

/* ── Delivery micro-line ── */
.pdp-delivery {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--white-alpha-4);
  margin-bottom: var(--space-4);
}
.pdp-delivery__icon { display: inline-grid; place-items: center; color: var(--text-tertiary); flex: none; }
.pdp-delivery__icon svg { width: 16px; height: 16px; }
.pdp-delivery__text {
  font-family: var(--font-cjk-text);
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
  line-height: 1.4;
}
.pdp-delivery__sep { color: var(--border-default); font-size: var(--fs-caption); }

/* ── popcorn balance note ── */
.pdp-buy__note { margin-top: var(--space-3); }

/* ══════════════════════════════════════════════════════════════
   DETAILS SECTION — full-width below the 2-col grid
   ══════════════════════════════════════════════════════════════ */
.pdp-details {
  border-top: 1px solid var(--border-subtle);
  background: var(--bg-secondary);
  padding-block: var(--space-section);
}
.pdp-details__inner {
  /* container padding applied via container utility class */
}
.pdp-details__heading {
  margin: 0 0 var(--space-8);
  font-family: var(--font-cjk-display);
  font-size: var(--fs-h4);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  letter-spacing: var(--ls-h4);
}
.pdp-details__cols {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-10);
}
@media (min-width: 768px) {
  .pdp-details__cols {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-12);
  }
}

/* Left column: description + highlights */
.pdp-details__desc {
  margin: 0 0 var(--space-6);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body);
  line-height: 1.75;
  color: var(--text-secondary);
}
.pdp-details__highlights {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.pdp-details__highlight {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
  line-height: 1.6;
}
.pdp-details__highlight::before {
  content: '';
  display: inline-block;
  flex: none;
  width: 4px;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--yellow-500);
  margin-top: 0.5em;
  vertical-align: middle;
}

/* Right column: specs + shipping */
.pdp-details__sub-heading {
  margin: 0 0 var(--space-4);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-bold);
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.pdp-details__specs-block { margin-bottom: var(--space-8); }
.pdp-details__shipping-block { }
.pdp-details__shipping-text {
  margin: 0 0 var(--space-3);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  line-height: 1.7;
  color: var(--text-secondary);
}

/* Specs definition list (used in details section) */
.pdp-specs { display: flex; flex-direction: column; gap: var(--space-2); margin: 0; }
.pdp-specs__row {
  display: grid;
  grid-template-columns: 88px 1fr;
  gap: var(--space-3);
  padding-block: var(--space-2);
  border-bottom: 1px solid var(--border-subtle);
}
.pdp-specs__row:last-child { border-bottom: none; }
.pdp-specs__row dt {
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  color: var(--text-tertiary);
}
.pdp-specs__row dd {
  margin: 0;
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
}

/* ══════════════════════════════════════════════════════════════
   RECOMMENDATIONS RAIL
   ══════════════════════════════════════════════════════════════ */
.pdp-recs {
  padding-block: var(--space-section);
}
.pdp-recs__inner { }
.pdp-recs__heading {
  margin: 0 0 var(--space-6);
  font-family: var(--font-cjk-display);
  font-size: var(--fs-h4);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}
/* Desktop: 4-up grid */
.pdp-recs__rail {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-5);
}
@media (min-width: 768px) {
  .pdp-recs__rail {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
/* Mobile: horizontal scroll instead of grid */
@media (max-width: 767.98px) {
  .pdp-recs__rail {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    gap: var(--space-4);
    padding-bottom: var(--space-2);
  }
  .pdp-recs__rail::-webkit-scrollbar { display: none; }
  .pdp-rec-card { flex: 0 0 200px; scroll-snap-align: start; }
}

/* Rec card */
.pdp-rec-card {
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-xl);
  background: var(--bg-secondary);
  border: 1px solid var(--border-subtle);
  overflow: hidden;
  transition: transform 180ms var(--ease-out), border-color 180ms var(--ease-out);
  text-decoration: none;
}
.pdp-rec-card:hover {
  transform: translateY(-3px);
  border-color: var(--border-default);
}
.pdp-rec-card:focus-visible {
  outline: 2px solid var(--yellow-500);
  outline-offset: 3px;
}
.pdp-rec-card__media {
  aspect-ratio: 1 / 1;
  overflow: hidden;
  background: var(--bg-tertiary);
}
.pdp-rec-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 300ms var(--ease-out);
}
.pdp-rec-card:hover .pdp-rec-card__media img { transform: scale(1.03); }
.pdp-rec-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-4) var(--space-4);
}
.pdp-rec-card__cat {
  font-family: var(--font-cjk-text);
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.pdp-rec-card__title {
  margin: 0;
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-medium);
  color: var(--text-primary);
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.pdp-rec-card__price {
  margin-top: var(--space-2);
  font-family: var(--font-latin);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-bold);
  color: var(--yellow-500);
  font-variant-numeric: tabular-nums;
}

/* ══════════════════════════════════════════════════════════════
   BUNDLE 套組 — "what's inside"
   ══════════════════════════════════════════════════════════════ */
.pdp-bundle { margin-top: var(--space-section); }
.pdp-bundle__title {
  margin: 0 0 var(--space-6);
  font-family: var(--font-cjk-display);
  font-size: var(--fs-h4);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}
.pdp-bundle__grid { display: grid; grid-template-columns: 1fr; gap: var(--space-5); }
@media (min-width: 768px) {
  .pdp-bundle__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-6); }
}
.pdp-bundle-item {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-xl);
  background: var(--bg-secondary);
}
.pdp-bundle-item.is-flagged { border-color: var(--error-500); animation: pdp-shake 320ms var(--ease-out); }
.pdp-bundle-item__media {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--bg-tertiary);
}
.pdp-bundle-item__img { width: 100%; height: 100%; object-fit: cover; display: block; transition: opacity 200ms var(--ease-out); }
.pdp-bundle-item__img[data-comp-zoom] { cursor: zoom-in; }
/* zoom affordance (mock bcard__zoom) */
.pdp-bundle-item__zoom {
  position: absolute; right: 8px; bottom: 8px; width: 28px; height: 28px;
  display: grid; place-items: center; border-radius: var(--radius-pill);
  color: #fff; background: rgba(0, 0, 0, 0.5); pointer-events: none;
  opacity: 0; transition: opacity 180ms var(--ease-out);
}
.pdp-bundle-item__zoom svg { width: 15px; height: 15px; }
@media (hover: hover) { .pdp-bundle-item__media:hover .pdp-bundle-item__zoom { opacity: 1; } }
@media (hover: none) { .pdp-bundle-item__zoom { opacity: 1; } }
/* per-item colour swatches */
.pdp-bundle-item__sws { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.pdp-bundle-item__sw { width: 22px; height: 22px; }
.pdp-bundle-item__thumbs { display: flex; gap: var(--space-2); }
.pdp-bundle-item__thumb {
  width: 44px;
  aspect-ratio: 1 / 1;
  padding: 0;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--bg-tertiary);
  cursor: pointer;
  transition: border-color 160ms var(--ease-out);
}
.pdp-bundle-item__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pdp-bundle-item__thumb.is-active { border-color: var(--yellow-500); }
.pdp-bundle-item__thumb:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 1px; }
.pdp-bundle-item__body { display: flex; flex-direction: column; gap: var(--space-2); }
.pdp-bundle-item__name {
  margin: 0;
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-medium);
  color: var(--text-primary);
  line-height: 1.4;
}
.pdp-bundle-item__variant { margin-top: var(--space-1); }
.pdp-bundle-item__variant .pdp-chip { min-height: 36px; min-width: 40px; }
.pdp-bundle-item__link {
  margin-top: var(--space-1);
  align-self: flex-start;
  font-family: var(--font-cjk-text);
  font-size: var(--fs-caption);
  color: var(--yellow-500);
}
.pdp-bundle-item__link:hover { color: var(--yellow-400); }

/* ══════════════════════════════════════════════════════════════
   SKELETON / EMPTY
   ══════════════════════════════════════════════════════════════ */
.pdp-skel__hero { aspect-ratio: 1 / 1; border-radius: var(--radius-2xl); }
.pdp-skel__buy { display: flex; flex-direction: column; gap: var(--space-3); }
.pdp-empty { padding-block: var(--space-20); gap: var(--space-4); }

/* ══════════════════════════════════════════════════════════════
   POPCORN
   ══════════════════════════════════════════════════════════════ */
.pdp-pop { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-2); }
.pdp-pop__icon {
  width: 22px; height: 22px; flex: none;
  -webkit-mask: url('assets/icons/popcorn-md.svg') center / contain no-repeat;
          mask: url('assets/icons/popcorn-md.svg') center / contain no-repeat;
  background: var(--yellow-500);
}
.pdp-pop__val { font-family: var(--font-latin); font-size: var(--fs-h3); font-weight: var(--fw-bold); color: var(--yellow-500); font-variant-numeric: tabular-nums; }
.pdp-pop__unit { font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-secondary); }
.pdp-pop__balance { margin-bottom: var(--space-6); font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-tertiary); }
.pdp-pop__balance b { color: var(--text-primary); font-family: var(--font-latin); font-variant-numeric: tabular-nums; }

/* ══════════════════════════════════════════════════════════════
   EVENT
   ══════════════════════════════════════════════════════════════ */
.pdp-event-meta {
  display: flex; flex-direction: column; gap: var(--space-2);
  margin-bottom: var(--space-6); padding: var(--space-4);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); background: var(--bg-secondary);
}
.pdp-event-meta__row { display: flex; gap: var(--space-3); font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-secondary); }
.pdp-event-meta__k { flex: 0 0 48px; color: var(--text-tertiary); }
.pdp-tiers { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-5); }
.pdp-tier {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  min-height: 56px; padding: var(--space-3) var(--space-4); text-align: left;
  border: 1px solid var(--border-default); border-radius: var(--radius-md);
  background: var(--white-alpha-4); color: var(--text-primary); cursor: pointer;
  transition: border-color 160ms var(--ease-out), background 160ms var(--ease-out);
}
.pdp-tier:hover { border-color: var(--border-strong); }
.pdp-tier.is-selected { border-color: var(--yellow-500); background: rgba(255, 163, 63, 0.08); }
.pdp-tier:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }
.pdp-tier.is-oos { color: var(--text-disabled); cursor: not-allowed; opacity: 0.7; }
.pdp-tier__label { font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); font-weight: var(--fw-medium); }
.pdp-tier__oos { color: var(--text-disabled); font-size: var(--fs-caption); }
.pdp-tier__price { font-family: var(--font-latin); font-size: var(--fs-body-sm); font-weight: var(--fw-semibold); white-space: nowrap; }
.pdp-tier__sub { color: var(--text-tertiary); font-weight: var(--fw-regular); }
.pdp-lineup { margin-top: var(--space-section); }
.pdp-lineup__title { margin: 0 0 var(--space-5); font-family: var(--font-cjk-display); font-size: var(--fs-h4); font-weight: var(--fw-bold); color: var(--text-primary); }
.pdp-lineup__list { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.pdp-lineup__chip {
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--border-default); border-radius: var(--radius-pill);
  background: var(--white-alpha-4);
  font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-secondary);
}

/* ══════════════════════════════════════════════════════════════
   AUCTION
   ══════════════════════════════════════════════════════════════ */
.pdp-auction {
  margin-bottom: var(--space-5); padding: var(--space-4);
  border: 1px solid var(--border-default); border-radius: var(--radius-lg); background: var(--bg-secondary);
}
.pdp-auction__bidrow { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: var(--space-3); }
.pdp-auction__bidlabel { font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-tertiary); }
.pdp-auction__bid { font-family: var(--font-latin); font-size: var(--fs-h3); font-weight: var(--fw-bold); color: var(--yellow-500); font-variant-numeric: tabular-nums; }
.pdp-auction__meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.pdp-auction__bidders, .pdp-auction__time { font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-secondary); }
.pdp-auction__time { font-variant-numeric: tabular-nums; }
.pdp-provenance { margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--border-subtle); }
.pdp-provenance__k { display: block; margin-bottom: var(--space-2); font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); }
.pdp-provenance__text { margin: 0; font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); line-height: 1.7; color: var(--text-secondary); }

/* ══════════════════════════════════════════════════════════════
   REDUCED MOTION
   ══════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  .pdp-gallery__zoom-hint,
  .pdp-gallery__thumb,
  .pdp-chip,
  .pdp-swatch,
  .pdp-size-chip,
  .pdp-qty__btn,
  .pdp-buy__heart,
  .pdp-rec-card,
  .pdp-rec-card__media img { transition: none; }
  .pdp-variant.is-flagged .pdp-variant__opts,
  .pdp-variant.is-flagged .pdp-variant__opts--swatches,
  .pdp-variant.is-flagged .pdp-variant__opts--sizes { animation: none; }
  .pdp-gallery__zoom-hint { opacity: 1; transform: none; }
  .pdp-swatch:hover { transform: none; }
  .pdp-rec-card:hover { transform: none; }
  .pdp-gallery__thumb:hover { transform: none; }
}

/* ══════════════════════════════════════════════════════════════
   EDITORIAL RESKIN — HODINKEE/Aesop tier
   Scoped to .pdp where possible so nothing outside PDP is touched.
   LEVER 1: Display headings via the brand --font-cjk-display token (LINE Seed TW)
   LEVER 2: Monochrome CTA + quiet price (accent kept only on detail elements)
   LEVER 3: Airier gallery + spotlight background
   LEVER 4: Editorial lead paragraph (styled via .pdp-details__lead)
   ══════════════════════════════════════════════════════════════ */

/* ── LEVER 1: Headings use the brand display token (LINE Seed TW) — NOT a serif.
   Premium comes from weight/scale/spacing + the other levers, not a foreign face. ── */
.pdp-buy__title,
.pdp-details__heading,
.pdp-details__sub-heading,
.pdp-recs__heading,
.pdp-related__title,
.pdp-bundle__title,
.pdp-lineup__title {
  font-family: var(--font-cjk-display);
  font-weight: var(--fw-bold);
  letter-spacing: -0.01em;
}

/* ── LEVER 2: Monochrome primary CTA ── */
/* Scoped to .pdp so no other btn--primary on the site is changed */
.pdp .pdp-buy__add.btn--primary,
.pdp .shop-buybar__add.btn--primary {
  background: var(--text-primary);
  color: var(--bg-primary);
  border: none;
  transition: opacity 220ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
              transform 220ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.pdp .pdp-buy__add.btn--primary:hover,
.pdp .shop-buybar__add.btn--primary:hover {
  opacity: 0.85;
  transform: translateY(-1px);
}
.pdp .pdp-buy__add.btn--primary:active,
.pdp .shop-buybar__add.btn--primary:active {
  opacity: 0.95;
  transform: translateY(0);
}
.pdp .pdp-buy__add.btn--primary[disabled] {
  background: var(--text-disabled);
  color: var(--bg-primary);
  opacity: 0.5;
  transform: none;
  cursor: not-allowed;
}

/* Price headline — near-white instead of brand orange */
.pdp .pdp-price__now {
  color: var(--text-primary);
}

/* Accent retained on: save badge, size-chip selected ring, swatch selected ring,
   heart-active state, size guide link, delivery link — these rules are already
   token-correct in the base CSS and are NOT overridden here. */

/* ── LEVER 3: Airier gallery + spotlight frame ── */
.pdp-gallery {
  max-width: 600px;
}

.pdp-gallery__frame {
  max-height: min(620px, calc(100vh - 140px));
  background: radial-gradient(120% 120% at 50% 38%, var(--bg-tertiary), var(--bg-secondary));
}

/* More breathing room on the page level */
.pdp {
  padding-block: var(--space-12) 0;
}

@media (min-width: 768px) {
  .pdp__grid {
    gap: var(--space-12);
  }
}

/* Details section — relax the card (more padding, hairline dividers) */
.pdp-details {
  padding-block: var(--space-section);
  background: transparent; /* let the page bg breathe instead of the heavier bg-secondary */
  border-top: 1px solid var(--border-subtle);
}
.pdp-details__inner {
  padding-block: 0;
}

/* ── LEVER 4: Editorial lead paragraph (brand text font, enlarged — no serif) ── */
.pdp-details__lead {
  font-family: var(--font-cjk-text);
  font-size: var(--fs-h4);
  font-weight: 400;
  line-height: 1.7;
  color: var(--text-secondary);
  max-width: 62ch;
  margin-bottom: var(--space-8);
}

/* ── Reduced-motion overrides for monochrome CTA ── */
@media (prefers-reduced-motion: reduce) {
  .pdp .pdp-buy__add.btn--primary,
  .pdp .shop-buybar__add.btn--primary {
    transition: opacity 150ms ease;
  }
  .pdp .pdp-buy__add.btn--primary:hover,
  .pdp .shop-buybar__add.btn--primary:hover {
    transform: none;
  }
}

/* ════════════════════════════════════════════════════════════════
   shop-buybar.css — mobile sticky buy-bar for the PDP. Phone only.
   Revealed (IntersectionObserver) once the in-page add button scrolls
   out of view; sits ABOVE the floating bottom-nav. Glass material.
   ════════════════════════════════════════════════════════════════ */
.shop-buybar { display: none; }

@media (max-width: 767.98px) {
  .shop-buybar {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    position: fixed;
    left: 0;
    right: 0;
    /* sit above the floating bottom-nav pill (≈68px tall + 12px gap) */
    bottom: calc(78px + env(safe-area-inset-bottom, 0px));
    z-index: var(--z-fab);
    padding: var(--space-3) var(--space-4);
    background: var(--glass-fill);
    -webkit-backdrop-filter: var(--glass-backdrop);
            backdrop-filter: var(--glass-backdrop);
    border-top: var(--glass-border);
    transform: translateY(120%);
    opacity: 0;
    pointer-events: none;
    transition: transform 280ms var(--ease-out), opacity 200ms var(--ease-out);
  }
  .shop-buybar.is-shown { transform: translateY(0); opacity: 1; pointer-events: auto; }

  /* a modal surface owns the screen — the bar yields. The size-guide
     drawer is z-300, the bar z-400: without this the bar paints OVER
     the open drawer. Hiding on the body lock classes beats reshuffling
     the z-scale (the bar must still sit above the bottom-nav normally). */
  body.ds-drawer-open .shop-buybar,
  body.cart-open .shop-buybar,
  body.mdrawer-open .shop-buybar,
  body.ds-zoom-open .shop-buybar,
  body.sheet-open .shop-buybar {
    transform: translateY(120%);
    opacity: 0;
    pointer-events: none;
  }

  .shop-buybar__info { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
  .shop-buybar__name {
    font-family: var(--font-cjk-text);
    font-size: var(--fs-caption);
    color: var(--text-tertiary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .shop-buybar__price {
    font-family: var(--font-latin);
    font-size: var(--fs-body);
    font-weight: var(--fw-bold);
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
  }
  .shop-buybar__add { flex: 0 0 auto; min-height: 44px; }

  /* give the page room so the related rail never hides behind the bar */
  .pdp { padding-bottom: calc(var(--space-section) + 64px); }
}

@media (prefers-reduced-motion: reduce) {
  .shop-buybar { transition: opacity 180ms ease; transform: translateY(0); }
  .shop-buybar:not(.is-shown) { transform: translateY(120%); }
}

/* F14 wishlist — entry link on shop heads + list-page states (after refine-shop's .rf-wish it overrides) */
/* ─────────────────────────────────────────
   WISHLIST (F14) — head-row entry link + list-page states
   Entry: ♥ 願望清單 · N on every shop-family head row. The count is the
   unclosed-loop nudge; it hides at 0 (a zero count advertises emptiness).
   List page: reversible removal (dim + 復原 — the signature), designed
   empty state, auth gate. Cards are the shared shop-card — no new card CSS.
   ───────────────────────────────────────── */

/* ── entry link on .shop-head (all 5 shop-family hub pages) ── */
.shop-head { position: relative; }
.shop-head__wish {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--font-cjk-text);
  font-size: 14px;
  line-height: 1;
  color: var(--text-secondary);
  text-decoration: none;
  white-space: nowrap;
  transition: color 160ms ease;
}
.shop-head__wish:hover { color: var(--text-primary); }
.shop-head__wish-icon { width: 16px; height: 16px; display: inline-grid; place-items: center; }
.shop-head__wish-icon svg { width: 100%; height: 100%; display: block; }
.shop-head__wish-count {
  font-variant-numeric: tabular-nums;   /* the count changes live — no width jitter */
  color: var(--yellow-500);
  font-weight: var(--fw-bold);
}
.shop-head__wish-count::before {
  content: '· ';
  color: var(--text-tertiary);
  font-weight: var(--fw-regular);
}
/* Phone: .shop-head becomes a column (36-shop-hub.css) — pin the link to the
   TITLE row, top-right (L's sketch: same row as the 商店 H1). */
@media (max-width: 767.98px) {
  .shop-head__wish { position: absolute; right: 0; top: 4px; margin-left: 0; }
}

/* ── wishlist page (body[data-feature="wishlist"]) ── */
/* Hearts on the list ARE the remove control — always visible. The shop's
   desktop hover-reveal (refine-shop.css) would hide the primary action here. */
body[data-feature="wishlist"] .rf-wish { opacity: 1 !important; transform: none !important; }
body[data-feature="wishlist"] .shop-card { position: relative; }

/* toolbar carries only the count — no filters on a self-curated list */
.shop-toolbar--wish { justify-content: flex-end; margin-top: var(--space-5); }

/* SIGNATURE — removed-in-place: the card dims and goes inert; the 復原 chip
   stays live. Eviction happens on the next render, never on the tap. */
.shop-card--wish-removed { pointer-events: none; }
.shop-card--wish-removed .shop-card__media,
.shop-card--wish-removed .shop-card__body {
  opacity: 0.35;
  filter: grayscale(0.4);
  transition: opacity 300ms var(--ease-settle), filter 300ms var(--ease-settle);
}
.wish-undo {
  position: absolute;
  inset: auto 10px 10px 10px;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 12px 9px 14px;
  border-radius: var(--radius-pill);
  background: rgba(10, 10, 10, 0.78);
  border: var(--glass-border);
  border-color: rgba(255, 255, 255, 0.10);
  box-shadow: var(--glass-shadow);
  -webkit-backdrop-filter: var(--lg-frost);
          backdrop-filter: var(--lg-frost);
  font-family: var(--font-cjk-text);
  font-size: 13px;
  color: var(--text-secondary);
  pointer-events: auto;                 /* the card is inert; the chip is not */
}
.wish-undo__btn {
  border: none;
  background: none;
  cursor: pointer;
  padding: 6px 8px;
  margin: -6px -8px;                    /* 復原 keeps a real touch target */
  font-family: var(--font-cjk-text);
  font-size: 13px;
  font-weight: var(--fw-bold);
  color: var(--yellow-500);
}
.wish-undo__btn:hover { text-decoration: underline; }

/* ── empty state — the first thing most people see; designed, never a void ── */
.wish-empty,
.wish-gate {
  text-align: center;
  padding: 72px 24px 84px;
}
.wish-empty__icon,
.wish-gate__icon {
  width: 44px;
  height: 44px;
  margin: 0 auto 18px;
  color: var(--text-tertiary);
  opacity: 0.85;
}
.wish-empty__icon svg,
.wish-gate__icon svg { width: 100%; height: 100%; display: block; }
.wish-empty__title,
.wish-gate__title {
  font-family: var(--font-cjk-display);
  font-size: 19px;
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}
.wish-empty__desc,
.wish-gate__desc {
  margin-top: 10px;
  font-family: var(--font-cjk-text);
  font-size: 14px;
  line-height: 1.7;
  color: var(--text-tertiary);
}
.wish-empty__cta,
.wish-gate__cta { margin-top: 26px; }

/* ════════════════════════════════════════════════════════════════════════
   SKELETON LOADER  ·  .ds-skeleton (+ shape/width modifiers)   [COMPONENT]
   ════════════════════════════════════════════════════════════════════════
   The ONE skeleton primitive. Replaces 4 fragmented, page-keyed copies
   (.rf-skel / .m-skeleton / refine-engagement / refine-library shimmers).
   Pure-CSS shimmer (animated background-position, off the main thread — NOT
   GSAP: a loading state is exactly when the main thread is busy, so JS-driven
   shimmer drops frames). background-attachment:fixed → every skeleton samples
   one shared light field and shimmers as a single surface.

   USE: give any placeholder `.ds-skeleton` + a shape + (optional) width.
   The skeleton DOM must MIRROR the real component's box → zero layout shift
   on swap. Only data-driven components get one; static (header/footer/nav/
   buttons/hero) never do — see COMPONENTS.md "skeleton cut".

   First component extracted into the per-component structure (2026-06-16).
   ════════════════════════════════════════════════════════════════════════ */
.ds-skeleton {
  background-color: var(--skeleton-surface);       /* sits back on the dark canvas */
  background-image: linear-gradient(100deg,
    transparent 40%, rgba(255, 255, 255, 0.05) 50%, transparent 60%);  /* white sheen, never brand yellow */
  background-repeat: no-repeat;
  background-size: 200% 100%;
  background-attachment: fixed;                    /* shared field → all shimmer as one */
  animation: ds-shimmer 1.4s linear infinite;      /* linear = constant motion; slow = calm */
  border-radius: 6px;
}
@keyframes ds-shimmer {                             /* background-position only — no layout/paint */
  from { background-position: 150% 0; }
  to   { background-position: -150% 0; }
}
/* shapes — match the real component's box */
.ds-skeleton--media   { width: 100%; aspect-ratio: 16 / 10; border-radius: var(--radius-md, 10px); }
.ds-skeleton--poster  { width: 100%; aspect-ratio: 2 / 3;  border-radius: var(--radius-md, 10px); }
.ds-skeleton--line    { height: 0.82em; border-radius: 4px; }
.ds-skeleton--circle  { aspect-ratio: 1; width: 40px; border-radius: 50%; }
/* line widths */
.ds-skeleton--title   { width: 84%; }
.ds-skeleton--cat     { width: 38%; }
.ds-skeleton--price   { width: 30%; }
.ds-skeleton--short   { width: 60%; }
.ds-skeleton--full    { width: 100%; }
/* card-shaped wrappers (mirror a card column / a row) */
.ds-skeleton-card { display: flex; flex-direction: column; gap: 10px; }
.ds-skeleton-row  { display: flex; align-items: center; gap: var(--space-3); }
.ds-skeleton-row > .ds-skeleton--line { flex: 1; }
/* reduced motion → gentle pulse (a frozen grey block reads as broken) */
@media (prefers-reduced-motion: reduce) {
  .ds-skeleton { animation: ds-skeleton-pulse 2s ease-in-out infinite; background-image: none; }
  @keyframes ds-skeleton-pulse { 0%, 100% { opacity: 0.6; } 50% { opacity: 1; } }
}

/* ── image-load shimmer ──────────────────────────────────────────────
   A media box wears .ds-imgwait while its <img> is still decoding —
   ds.js's `imgskel` behaviour adds it at attach time and fades it out
   on the img's load/error (`--done` → 240ms opacity → classes removed).
   Overlay via ::after: the img keeps its box (zero layout shift), the
   shimmer samples the SAME fixed light field as .ds-skeleton, so page
   skeletons and image mattes move as one surface. */
.ds-imgwait { position: relative; }
.ds-imgwait::after {
  content: "";
  position: absolute; inset: 0; z-index: 1;
  pointer-events: none;
  border-radius: inherit;                            /* clip to the media shape */
  background-color: var(--skeleton-surface);
  background-image: linear-gradient(100deg,
    transparent 40%, rgba(255, 255, 255, 0.05) 50%, transparent 60%);
  background-repeat: no-repeat;
  background-size: 200% 100%;
  background-attachment: fixed;
  animation: ds-shimmer 1.4s linear infinite;
  opacity: 1;
  transition: opacity 450ms var(--ease-settle, cubic-bezier(0.22, 1, 0.36, 1));
}
.ds-imgwait--done::after { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .ds-imgwait::after { animation: none; background-image: none; }
}

/* ── grid card entrance — the skeleton-to-content moment ─────────────
   The renderer marks the grid [data-entrance] on its FIRST fill only
   (filter/sort re-renders stay instant — a repeated action must not
   replay theatre). Keyframes are safe here because the gate makes the
   animation one-shot. First six cards cascade; the rest (below the
   fold at phone widths) share one delay so nothing queues offscreen. */
/* Arrival travel — component-scoped alias so the -m token swaps in one place
   (same pattern as 51-page-transition.css's --page-travel) */
html { --arrive-travel: var(--arrive-y, 24px); }
@media (max-width: 767.98px) {
  html { --arrive-travel: var(--arrive-y-m, 20px); }
}

@media (prefers-reduced-motion: no-preference) {
  /* deferred (below-first-screen) cards are excluded — the keyframe's
     fill:both would pin them visible and kill their scroll arrival.
     `.was-defer` (left behind by ds.js cardreveal when it strips
     is-defer post-arrival) keeps them excluded FOREVER: data-entrance
     stays on the grid, so without it the entrance would restart the
     moment is-defer drops — the card blinks out and replays (the
     2026-07-03 shop-grid flashing glitch).
     The stagger rides a custom property because these :not()s
     out-specify the nth-child rules — a bare animation-delay there
     loses to this shorthand's implicit delay:0 and the cascade dies.
     Fade and rise are SPLIT (retune 2026-07-03): presence reads from
     opacity, luxury reads from the long transform tail — one animation
     can't carry two durations, hence two comma-joined keyframes. */
  [data-entrance] > .shop-card:not(.is-defer):not(.was-defer),
  [data-entrance] > .creator-card:not(.is-defer):not(.was-defer) {
    animation:
      ds-card-fade var(--dur-arrive-fade, 450ms) var(--ease-settle) both,
      ds-card-rise var(--dur-arrive, 800ms) var(--ease-settle) both;
    animation-delay: var(--card-delay, 0ms);
  }
  [data-entrance] > :nth-child(2) { --card-delay: 55ms; }
  [data-entrance] > :nth-child(3) { --card-delay: 110ms; }
  [data-entrance] > :nth-child(4) { --card-delay: 165ms; }
  [data-entrance] > :nth-child(5) { --card-delay: 220ms; }
  [data-entrance] > :nth-child(6) { --card-delay: 275ms; }
  [data-entrance] > :nth-child(n+7) { --card-delay: 330ms; }
}
@keyframes ds-card-fade {
  from { opacity: 0; }
}
@keyframes ds-card-rise {
  from { transform: translateY(var(--arrive-travel, 24px)); }
}

/* ── scroll arrival — below-first-screen cards (ds.js `cardreveal`) ──
   The renderer marks them .is-defer on the first fill; each card rises
   as it first crosses the 85% viewport line (ds.js ARRIVE_INSET), in
   step with its lazyimg image swap. Fade (450ms) is faster than travel
   (800ms): the card is legible while the mass still settles — same
   grammar as the inline GSAP engines' 0.8s card tier. The transition
   lives on .is-defer (not .is-in) and both classes drop ~850ms after
   arrival (token-bound in ds.js), so a settled card's hover lift is
   never slowed by a transition it no longer needs. */
@media (prefers-reduced-motion: no-preference) {
  .is-defer {
    opacity: 0;
    transform: translateY(var(--arrive-travel, 24px));
    transition:
      opacity   var(--dur-arrive-fade, 450ms) var(--ease-settle),
      transform var(--dur-arrive, 800ms) var(--ease-settle);
  }
  .is-defer.is-in { opacity: 1; transform: translateY(0); }
}

/* ════════════════════════════════════════════════════════════════
   ds-empty.css — THE consolidated empty-state primitive.

   COMPONENTS.md listed .ds-empty as a consolidation target (replaces
   .rf-empty / .rf-feed-empty / .m-empty); shipped 2026-07-15 with the
   exchange build (search-before-create: no .ds-empty rule existed in
   any component CSS). Existing .rf-* empties keep working — migrate
   them here opportunistically, don't mass-rewrite.

   Markup contract:
     <div class="ds-empty" [data-size]>
       <span class="ds-empty__icon" aria-hidden="true">🍿</span>
       <p class="ds-empty__title">…one line…</p>
       <p class="ds-empty__desc">…one short line…</p>
       <a class="btn btn--yellow ds-empty__cta" href="…">…</a>
     </div>
   ════════════════════════════════════════════════════════════════ */

.ds-empty[hidden] { display: none; }

.ds-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--space-2);
  padding: calc(var(--space-12) * var(--ds-scale, 1)) var(--space-6);
  color: var(--text-secondary);
}

.ds-empty__icon {
  font-size: calc(40px * var(--ds-scale, 1));
  line-height: 1;
  opacity: 0.55;
  margin-bottom: var(--space-2);
}

.ds-empty__title {
  margin: 0;
  font-size: var(--fs-h5);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}

.ds-empty__desc {
  margin: 0;
  font-size: var(--fs-body-sm);
  max-width: 36ch;
}

.ds-empty__cta { margin-top: var(--space-3); }

/* ════════════════════════════════════════════════════════════════════════
   SAVED GRID · "My List" 收藏  (.rf-saved-grid)                  [COMPONENT]
   ════════════════════════════════════════════════════════════════════════
   Poster grid with remove-on-hover. Reuses .film-card; container-scoped to
   .rf-saved-grid so it travels to ANY page that drops the markup.
   Consolidated 2026-06-16 out of refine-library.css (was body.rf-library-keyed
   split-brain — verified zero-diff: .rf-saved-grid renders only inside the grid,
   so dropping the body prefix selects the identical elements).
   ════════════════════════════════════════════════════════════════════════ */
.rf-saved-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: clamp(var(--space-4), 2vw, var(--space-6));
  margin-top: var(--space-2);
}
.rf-saved-grid .film-card { width: auto; flex: initial; }

.rf-saved__remove {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 3;
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border: none;
  border-radius: var(--radius-pill);
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(var(--lg-blur));               /* 8px chip frost — off the master knob */
  -webkit-backdrop-filter: blur(var(--lg-blur));
  color: var(--text-primary);
  cursor: pointer;
  opacity: 0;
  transform: scale(0.85);
  transition: opacity 160ms var(--ease-out), transform 160ms var(--ease-out), background-color 160ms var(--ease-out);
}
.film-card:hover .rf-saved__remove,
.rf-saved__remove:focus-visible {
  opacity: 1;
  transform: scale(1);
}
.rf-saved__remove:hover { background: var(--error-500); }
.rf-saved__remove:focus-visible {
  outline: 2px solid var(--yellow-500);
  outline-offset: 2px;
}
.rf-saved__remove svg { width: 15px; height: 15px; display: block; }

/* A removed card collapses out with a quiet fade (JS toggles .is-removing) */
.rf-saved-grid .film-card.is-removing {
  opacity: 0;
  transform: scale(0.94);
  transition: opacity 200ms var(--ease-out), transform 200ms var(--ease-out);
  pointer-events: none;
}

/* ─────────────────────────────────────────
   LIST BANNER — homepage "list recommendation" hero (e.g. 愛有千百種樣子)
   Left copy column + right 16:9 media, on a pure-CSS warm gradient.
   Ported from main's 42-creator-subpages bundle (the standalone homepage
   banner, separated here). Background is a multi-layer radial-gradient —
   0 KB, retina-sharp — NOT the 41 MB JPEG the older build shipped.
   ───────────────────────────────────────── */

/* Layers paint top→bottom; the linear base sits last (behind).
   Double-class beats index.html's inline `.section-bg-surface
   { background: var(--yellow-500) }` (same specificity, later source order). */
.section.section--list-banner {
  background-color: #ef6f3d;
  background-image:
    /* magenta mass, upper-left — large so its rounded BOTTOM-RIGHT edge
       sweeps across and forms the TOP of the gold swoosh */
    radial-gradient(46% 60% at 26% 26%, #e2255a 0%, #e2255a 14%, rgba(226, 37, 90, 0) 54%),
    /* deep maroon mass rising from the bottom-centre — its rounded TOP edge
       forms the BOTTOM of the swoosh; sits right of the magenta so the gap
       between them opens up-and-to-the-right (the comma curl) */
    radial-gradient(66% 54% at 62% 98%, #c01f29 0%, rgba(192, 31, 41, 0) 58%),
    /* gold hook TAIL — small, low-left: pulls the bright band down into a
       curl at the comma's point */
    radial-gradient(20% 30% at 45% 55%, #f9b042 0%, rgba(249, 176, 66, 0) 62%),
    /* gold swoosh CORE — luminous, sweeping up to the right */
    radial-gradient(50% 30% at 68% 38%, #ffc551 0%, #f7a83e 38%, rgba(247, 168, 62, 0) 66%),
    /* gold continuation into the top-right */
    radial-gradient(48% 40% at 90% 20%, #f8b948 0%, rgba(248, 185, 72, 0) 54%),
    /* pink hint, top-right */
    radial-gradient(42% 48% at 78% 1%, #e63f63 0%, rgba(230, 63, 99, 0) 50%),
    /* red-orange, bottom-left */
    radial-gradient(58% 72% at 6% 94%, #ef5535 0%, rgba(239, 85, 53, 0) 60%),
    linear-gradient(120deg, #f6a23f 0%, #ef6f3d 56%, #e54b2d 100%);
}

.list-banner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 600px);
  align-items: center;
  gap: var(--space-16);
}

.list-banner__copy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* Stretch to the media column's height; space-between pins the title
     to the image's top edge, the CTA to its bottom edge, and floats the
     description midway between them. */
  align-self: stretch;
  justify-content: space-between;
}

.list-banner__title {
  margin: 0;  /* kill the h2 UA margin (0.83em ≈ 63px per side at 76px) */
  padding-top: var(--space-2);  /* optical nudge: title cap sits ~8px below image top */
  font-family: var(--font-cjk-display);
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}

.list-banner__sub {
  margin: 0;  /* clear p UA margins; spacing comes from space-between */
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-lg);
  line-height: var(--lh-body-lg);
  color: var(--neutral-100);
}

/* Ink-filled CTA — yellow primary has no contrast on the warm gradient. */
.list-banner__cta {
  background: var(--yellow-ink);
  color: var(--text-primary);
  border-color: transparent;
}

.list-banner__cta:hover:not(:disabled) {
  box-shadow: 0 8px 24px rgba(26, 23, 0, 0.35);
}

.list-banner__media {
  display: block;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-xl);
  overflow: hidden;
  box-shadow: 0 24px 48px rgba(26, 23, 0, 0.18);
}

.list-banner__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 300ms ease;
}

.list-banner__media:hover img { transform: scale(1.03); }

@media (max-width: 767.98px) {
  .list-banner {
    grid-template-columns: 1fr;
    gap: var(--space-8);
  }
  .list-banner__title { font-size: clamp(28px, 7vw, 36px); line-height: 1.3; }
  /* Single column: copy height is natural, so space-between collapses —
     restore fixed gaps instead. */
  .list-banner__sub { margin-top: var(--space-3); }
  .list-banner__cta { margin-top: var(--space-6); }
}

/* ─────────────────────────────────────────
   AUTH-GATE — gated-control active states
   ------------------------------------------------------------
   Driven by the global auth gate (assets/auth.js), which sets
   aria-pressed="true" (+ .is-active) on a follow / save / like
   control after a successful (mock) sign-in, and mock-persists it.
   Keyed to those universal signals so ANY gated icon button lights
   up with no per-component class — mirroring the existing
   .is-bookmarked convention (bookmark_regular → bookmark_active +
   brand yellow). Follow buttons get their own active look from
   [data-following="true"] in refine-creator.css.
   ───────────────────────────────────────── */

/* Save bookmark — filled + brand yellow when saved. */
.post-card__bookmark[aria-pressed="true"] .post-card__bookmark-icon,
.community-card__action[aria-pressed="true"] .community-card__action-icon--bookmark {
  -webkit-mask: url('assets/icons/bookmark_active.svg') center / contain no-repeat;
          mask: url('assets/icons/bookmark_active.svg') center / contain no-repeat;
  background-color: var(--yellow-500);
}

/* Like (heart) — brand yellow when liked, on any gated heart icon. */
.community-card__action[aria-pressed="true"] .community-card__action-icon--heart,
.community-card__stat[aria-pressed="true"] .community-card__stat-icon--heart,
.post-card__stat[aria-pressed="true"] .post-card__stat-icon--heart {
  background-color: var(--yellow-500);
}

/* Press feedback on bare-icon gated actions — calm, quick, never bouncy
   (these fire constantly). No transition declared so it can't clobber the
   component's own color transition; the press snap reads as crisp, not stiff. */
.post-card__bookmark:active,
.post-card__stat:active,
.community-card__action:active { transform: scale(0.92); }
@media (prefers-reduced-motion: reduce) {
  .post-card__bookmark:active,
  .post-card__stat:active,
  .community-card__action:active { transform: none; }
}


/* ── shop mobile nav restructure — MUST load last (overrides 45-sticky-tabs) ── */
/* ─────────────────────────────────────────
   SHOP FILTER SHEET — mobile-only nav restructure for the shop umbrella.

   On phones (≤767.98px): the shop-TYPE nav (商店/爆米花商店/…) shares one
   PINNED row with a filter icon (nav left, scrolls; icon pinned right). The
   per-page category filter (the old 2nd nav, .shop-toolbar) is hidden inline
   and its options move into a glass BOTTOM SHEET. On 爆米花商店, 兌換紀錄 is
   lifted above the pinned row (non-sticky). Active filters show as chips.

   DESKTOP IS UNTOUCHED: every layout rule is inside @media (max-width: 767.98px),
   and the always-on pieces (trigger icon, sheet, scrim, chips, prerow) are
   display:none above the phone breakpoint. The JS-added .shop-typenav-row class
   has no desktop rules. Loaded LAST in main.css so it wins over 45-sticky-tabs.
   Reuses glass + motion tokens — no new magic numbers.
   ───────────────────────────────────────── */

/* Injected by shop-filter-sheet.js — hidden until the phone breakpoint. */
.shop-filter-trigger { display: none; }
.shop-active-filters { display: none; }

/* The glass bottom sheet + scrim are a phone-only control. */
@media (min-width: 768px) {
  .shop-sheet, .shop-sheet-scrim { display: none !important; }
}

@media (max-width: 767.98px) {

  /* ── Pinned row: shop-type nav (left, scrolls) + filter icon (right) ── */
  /* Beats 45-sticky-tabs' `.tabstick.tabstick-host{justify-content:center}`
     by equal specificity + later source order (this file imports last). */
  .tabstick.shop-typenav-row {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    gap: var(--space-2);
    justify-content: flex-start;
  }
  .tabstick.shop-typenav-row > .glass-tabs--push {
    flex: 1 1 auto;                  /* FILL the row up to the filter icon (no dead gap) */
    min-width: 0;
    margin-bottom: 0;
    justify-content: space-between;  /* spread the 5 items across the full pill width */
  }

  .shop-filter-trigger {
    flex: 0 0 auto;
    margin-left: auto;       /* pinned hard right */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: var(--radius-pill);
    border: var(--glass-border);
    background: var(--glass-fill);
    -webkit-backdrop-filter: var(--glass-backdrop);
            backdrop-filter: var(--glass-backdrop);
    box-shadow: var(--glass-shadow);
    color: var(--text-primary);
    position: relative;
  }
  .shop-filter-trigger svg { width: 20px; height: 20px; }
  .shop-filter-trigger__count {
    position: absolute;
    top: -3px;
    right: -3px;
    min-width: 17px;
    height: 17px;
    padding: 0 4px;
    border-radius: var(--radius-pill);
    background: var(--yellow-500);
    color: var(--yellow-ink);
    font-size: 10px;
    line-height: 17px;
    font-weight: var(--fw-bold);
    text-align: center;
    display: none;
  }
  .shop-filter-trigger.has-active .shop-filter-trigger__count { display: block; }

  /* ── Main nav fit: condense the shop-type items so all 5 fit IN-PILL without
     horizontal scroll. Scoped to .shop-typenav-row so other glass-tab navs
     (lifecycle / platform / creator tabs) keep their roomier spacing. The pill
     keeps overflow-x:auto (from .glass-tabs) as the LAST-RESORT fallback: it
     only scrolls if a device is too narrow to fit even condensed. ── */
  .shop-typenav-row .glass-tabs__item {
    padding-inline: var(--space-2);   /* comfortable interior; space-between adds the breathing room */
    font-size: var(--fs-caption);
  }

  /* ── 2nd nav (category toolbar) — moved into the sheet, hidden inline ── */
  .shop-toolbar { display: none; }

  /* ── 兌換紀錄 (popcorn) — on the TITLE row: title left, link right-aligned.
     JS moves the link into .shop-head and adds .shop-head--with-action; the
     desc drops to its own line below. (Supersedes the prerow placement.) ── */
  .shop-head--with-action {
    display: flex;
    flex-direction: row;          /* base .shop-head is column — title+link must be a row */
    flex-wrap: wrap;
    align-items: center;
    column-gap: var(--space-3);
  }
  .shop-head--with-action .popcorn-history-link { margin-left: auto; }
  .shop-head--with-action .shop-head__desc { flex: 1 1 100%; }

  /* ── Active-filter chips (below the pinned row, above the grid) ── */
  .shop-active-filters {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: var(--space-4) 0 var(--space-2);
  }
  .shop-active-filters:empty { display: none; }
  .shop-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 8px 6px 12px;
    border-radius: var(--radius-pill);
    background: var(--white-alpha-8);
    border: 0.8px solid var(--border-default);
    color: var(--text-primary);
    font-size: var(--fs-body-sm);
    line-height: 1;
  }
  .shop-chip__x {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border: none;
    border-radius: 50%;
    background: var(--white-alpha-8);
    color: var(--text-secondary);
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
  }
  .shop-chip__x:hover { color: var(--text-primary); background: var(--white-alpha-16); }
}

/* ── The TOP sheet + scrim (rendered into <body> by JS). Full-width, slides
   DOWN from under the header; capped so it never reaches the fixed bottom nav.
   z-index sits BELOW the header (which is 100) so the panel emerges from under
   the header instead of sliding over it; the header stays crisp above the dim. */
.shop-sheet-scrim {
  position: fixed;
  inset: var(--header-height) 0 0 0;   /* dim the content BELOW the header */
  z-index: var(--z-sheet-scrim);
  background: rgba(0, 0, 0, 0.5);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-nav-show) var(--ease-out);
}
.shop-sheet-scrim.is-open { opacity: 1; pointer-events: auto; }

.shop-sheet {
  position: fixed;
  left: 0;
  right: 0;
  top: var(--header-height);           /* clears the header */
  z-index: var(--z-sheet);             /* below the header (100) → slides from under it */
  /* never reach the bottom-nav zone */
  max-height: calc(100dvh - var(--header-height) - var(--space-24));
  display: flex;
  flex-direction: column;
  border-radius: 0 0 var(--radius-2xl) var(--radius-2xl);
  background: var(--glass-fill);
  -webkit-backdrop-filter: var(--glass-backdrop);
          backdrop-filter: var(--glass-backdrop);
  border-bottom: var(--glass-border);
  box-shadow: var(--glass-shadow);
  transform: translateY(-110%);        /* parked above the header, slides down to 0 */
  /* visibility:hidden guarantees the sheet is FULLY gone when closed — translate
     alone leaves ~0.1×height peeking below the viewport top (only masked while
     the header is shown; it reappears once the header hides on scroll). The 0s
     visibility delay = the slide duration on close (stays visible through the
     slide-out), instant on open. */
  visibility: hidden;
  transition: transform var(--dur-panel-exit, 280ms) var(--ease-emphasized),
              visibility 0s linear var(--dur-panel-exit, 280ms);
  padding: var(--space-4) var(--space-5);
}
.shop-sheet.is-open {
  transform: translateY(0);
  visibility: visible;
  transition: transform var(--dur-nav-morph) var(--ease-out),
              visibility 0s;
}

.shop-sheet__grip {
  width: 36px;
  height: 4px;
  border-radius: 2px;
  background: var(--white-alpha-16);
  margin: var(--space-3) auto 0;       /* handle at the BOTTOM edge — swipe up to close */
  flex: 0 0 auto;
}
.shop-sheet__title {
  font-size: var(--fs-h5);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  margin: 0 0 var(--space-4);
  flex: 0 0 auto;
}
.shop-sheet__options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  overflow-y: auto;
  margin: 0;
  padding: 2px;
}
.shop-sheet__opt {
  padding: 9px 15px;
  border-radius: var(--radius-pill);
  border: 0.8px solid var(--border-default);
  background: transparent;
  color: var(--text-secondary);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body-sm);
  cursor: pointer;
  transition: background var(--dur-nav-show) var(--ease-out),
              border-color var(--dur-nav-show) var(--ease-out),
              color var(--dur-nav-show) var(--ease-out);
}
.shop-sheet__opt[aria-pressed="true"] {
  background: var(--white-alpha-16);
  border-color: var(--border-strong);
  color: var(--text-primary);
}
.shop-sheet__apply {
  flex: 0 0 auto;
  margin-top: var(--space-4);
  width: 100%;
  padding: 14px;
  border: none;
  border-radius: var(--radius-pill);
  background: var(--yellow-500);
  color: var(--yellow-ink);
  font-family: var(--font-cjk-display);
  font-size: var(--fs-h5);
  font-weight: var(--fw-bold);
  cursor: pointer;
}

/* Reduced motion: the sheet appears without the slide. */
@media (prefers-reduced-motion: reduce) {
  .shop-sheet,
  .shop-sheet-scrim { transition: none; }
}

/* ════════════════════════════════════════════════════════════════
   47-checkout.css — multi-step checkout (rendered by checkout.js into the
   cart drawer's checkout view). Reuses cc-field / cc-pay / btn / status-tag;
   this file adds the stepper progress, delivery rows, review, promo, and the
   order tracker. Tokens only.
   ════════════════════════════════════════════════════════════════ */

/* progress steps */
.checkout-steps { display: flex; align-items: center; gap: var(--space-1); margin: 0 0 var(--space-5); padding: 0; list-style: none; }
.checkout-steps__item { display: flex; align-items: center; gap: var(--space-2); flex: 1; min-width: 0; }
.checkout-steps__item:not(:last-child)::after { content: ''; flex: 1; height: 1px; background: var(--border-default); }
.checkout-steps__dot {
  width: 24px; height: 24px; flex: none; display: grid; place-items: center;
  border-radius: var(--radius-pill); border: 1px solid var(--border-default);
  font-family: var(--font-latin); font-size: var(--fs-caption); font-weight: var(--fw-bold);
  color: var(--text-tertiary); background: var(--bg-secondary);
}
.checkout-steps__item.is-active .checkout-steps__dot { border-color: var(--yellow-500); color: var(--yellow-ink); background: var(--yellow-500); }
.checkout-steps__item.is-done .checkout-steps__dot { border-color: var(--yellow-500); color: var(--yellow-500); background: transparent; }
.checkout-steps__label { font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); white-space: nowrap; }
.checkout-steps__item.is-active .checkout-steps__label { color: var(--text-primary); }

/* form */
.checkout-form { display: flex; flex-direction: column; gap: var(--space-3); margin-bottom: var(--space-5); }
.checkout-form__row { display: flex; gap: var(--space-3); }
.checkout-form__row .cc-field { flex: 1; min-width: 0; }

/* delivery methods */
.checkout-methods { display: flex; flex-direction: column; gap: var(--space-3); margin-bottom: var(--space-5); }
.checkout-method {
  display: flex; align-items: center; gap: var(--space-3); text-align: left;
  padding: var(--space-4); border: 1px solid var(--border-default); border-radius: var(--radius-md);
  background: var(--white-alpha-4); color: var(--text-primary); cursor: pointer;
  transition: border-color 160ms var(--ease-out), background 160ms var(--ease-out);
}
.checkout-method:hover { border-color: var(--border-strong); }
.checkout-method.is-selected { border-color: var(--yellow-500); background: rgba(255, 163, 63, 0.06); }
.checkout-method:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; }
.checkout-method__radio { width: 18px; height: 18px; flex: none; border-radius: var(--radius-pill); border: 2px solid var(--border-strong); display: grid; place-items: center; }
.checkout-method.is-selected .checkout-method__radio { border-color: var(--yellow-500); }
.checkout-method.is-selected .checkout-method__radio::after { content: ''; width: 9px; height: 9px; border-radius: var(--radius-pill); background: var(--yellow-500); }
.checkout-method__main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.checkout-method__label { font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); font-weight: var(--fw-medium); }
.checkout-method__eta { font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); }
.checkout-method__fee { font-family: var(--font-latin); font-size: var(--fs-body-sm); font-weight: var(--fw-semibold); white-space: nowrap; }

/* summary */
.checkout-sum { margin-top: var(--space-4); }
.checkout-sum__lines { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-4); padding-bottom: var(--space-4); border-bottom: 1px solid var(--border-subtle); }
.checkout-sum__line { display: flex; justify-content: space-between; gap: var(--space-3); font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-secondary); }
.checkout-sum__line-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.checkout-sum__discount span:last-child { color: var(--success-500); }

/* review */
.checkout-review { display: flex; flex-direction: column; gap: 0; margin-bottom: var(--space-5); }
.checkout-review__block { display: grid; grid-template-columns: 48px 1fr auto; gap: var(--space-3); align-items: start; padding: var(--space-3) 0; border-bottom: 1px solid var(--border-subtle); }
.checkout-review__k { font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); }
.checkout-review__v { font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-primary); line-height: 1.5; }
.checkout-review__edit { padding: 0; border: none; background: none; cursor: pointer; color: var(--yellow-500); font-family: var(--font-cjk-text); font-size: var(--fs-caption); }

/* promo */
.checkout-promo { margin-bottom: var(--space-5); }
.checkout-promo__row { display: flex; gap: var(--space-2); }
.checkout-promo__input { flex: 1; min-width: 0; }
.checkout-promo__msg { margin: var(--space-2) 0 0; font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); }
.checkout-promo__msg.is-error { color: var(--error-500); }

.checkout-cta { width: 100%; }
.checkout-foot__hint { margin: 0 0 var(--space-3); text-align: center; font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); }

/* ── review hub (single-screen checkout) ── */
.checkout-hub { display: flex; flex-direction: column; gap: var(--space-5); }
.checkout-hub__group { display: flex; flex-direction: column; gap: var(--space-2); }
.checkout-hub__label { margin: 0; font-family: var(--font-cjk-text); font-size: var(--fs-caption); font-weight: var(--fw-medium); letter-spacing: 0.02em; color: var(--text-tertiary); }
.checkout-hub__row {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-4); border: 1px solid var(--border-default); border-radius: var(--radius-md);
  background: var(--white-alpha-4);
}
.checkout-hub__row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.checkout-hub__row-k { font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); }
.checkout-hub__row-v { font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-primary); line-height: 1.5; }
.checkout-hub__edit { flex: none; padding: 0; border: none; background: none; cursor: pointer; color: var(--yellow-500); font-family: var(--font-cjk-text); font-size: var(--fs-caption); font-weight: var(--fw-medium); }
.checkout-hub__edit:hover { text-decoration: underline; }
.checkout-hub__add {
  display: flex; align-items: center; gap: var(--space-3); width: 100%; text-align: left;
  padding: var(--space-4); border: 1px dashed rgba(255, 163, 63, 0.5); border-radius: var(--radius-md);
  background: rgba(255, 163, 63, 0.06); color: var(--yellow-500);
  font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); font-weight: var(--fw-medium); cursor: pointer;
  transition: background 160ms var(--ease-out), border-color 160ms var(--ease-out);
}
.checkout-hub__add:hover { background: rgba(255, 163, 63, 0.1); border-color: var(--yellow-500); }
.checkout-hub__add-icon { width: 22px; height: 22px; flex: none; display: grid; place-items: center; border-radius: var(--radius-pill); background: rgba(255, 163, 63, 0.18); font-size: 15px; line-height: 1; }
.checkout-hub__group .checkout-methods,
.checkout-hub__group .checkout-sum { margin: 0; }
.checkout-hub__group .checkout-promo { margin: 0; }
.checkout-hub > .checkout-sum { margin-top: 0; }

/* confirmation */
.checkout-confirm__order { margin-top: var(--space-3); padding: var(--space-3) var(--space-5); border: 1px dashed var(--border-default); border-radius: var(--radius-md); font-family: var(--font-latin); font-size: var(--fs-body-sm); color: var(--text-primary); letter-spacing: 0.04em; }
.checkout-confirm__order b { color: var(--yellow-500); font-weight: var(--fw-bold); }

/* order tracker (mini, horizontal) */
.order-tracker { display: flex; align-items: flex-start; gap: var(--space-1); margin-top: var(--space-5); width: 100%; }
.order-tracker__node { flex: 1; display: flex; flex-direction: column; align-items: center; gap: var(--space-2); position: relative; }
.order-tracker__node:not(:last-child)::after { content: ''; position: absolute; top: 6px; left: 50%; width: 100%; height: 2px; background: var(--border-default); }
.order-tracker__dot { width: 14px; height: 14px; border-radius: var(--radius-pill); background: var(--border-default); position: relative; z-index: 1; }
.order-tracker__node.is-current .order-tracker__dot { background: var(--yellow-500); }
.order-tracker__node.is-done .order-tracker__dot { background: var(--yellow-500); }
.order-tracker__label { font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); }
.order-tracker__node.is-current .order-tracker__label { color: var(--text-primary); }

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

/* ════════════════════════════════════════════════════════════════
   48-order-tracker.css — the "我的訂單" surface (orders / tickets / bids /
   rentals) in my-library.html, rendered by my-orders.js. Cards + ticket-QR
   placeholder. The mini order-tracker lives in 47-checkout.css and is reused.
   Tokens only.
   ════════════════════════════════════════════════════════════════ */

.orders-tabs { margin-bottom: var(--space-6); }
.orders-list { display: grid; grid-template-columns: 1fr; gap: var(--space-4); }
@media (min-width: 768px) { .orders-list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-5); } }

.orders-card {
  display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-5);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-xl);
  background: var(--bg-secondary);
}
.orders-card__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.orders-card__id { font-family: var(--font-latin); font-size: var(--fs-body-sm); font-weight: var(--fw-bold); color: var(--text-primary); letter-spacing: 0.03em; }
.orders-card__title { font-family: var(--font-cjk-display); font-size: var(--fs-body); font-weight: var(--fw-bold); color: var(--text-primary); min-width: 0; }
.orders-card__date { font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); white-space: nowrap; flex: none; }
.orders-card__meta { font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-secondary); }

.orders-card__thumbs { display: flex; align-items: center; gap: var(--space-2); }
.orders-card__thumb { width: 44px; height: 44px; flex: none; border-radius: var(--radius-sm); overflow: hidden; background: var(--bg-tertiary); }
.orders-card__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.orders-card__count { font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); margin-left: var(--space-1); }

.orders-card__foot { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin-top: var(--space-1); }
.orders-card__total { font-family: var(--font-latin); font-size: var(--fs-body); font-weight: var(--fw-bold); color: var(--text-primary); font-variant-numeric: tabular-nums; }
.orders-card__sim { display: flex; gap: var(--space-2); flex-wrap: wrap; }

/* ticket card — QR left, details right */
.orders-card--ticket { flex-direction: row; align-items: stretch; gap: var(--space-4); }
.orders-card__qr { flex: none; }
.orders-card__main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-2); justify-content: space-between; }
.ticket-qr { display: flex; flex-direction: column; align-items: center; gap: var(--space-1); padding: var(--space-2); background: var(--neutral-white); border-radius: var(--radius-md); }
.ticket-qr__svg { width: 84px; height: 84px; display: block; image-rendering: pixelated; }
.ticket-qr__svg rect { fill: var(--neutral-900); }
.ticket-qr__code { font-family: var(--font-latin); font-size: 9px; color: var(--neutral-900); letter-spacing: 0.03em; }

.orders-empty { grid-column: 1 / -1; padding-block: var(--space-16); gap: var(--space-4); }

/* ════════════════════════════════════════════════════════════════
   49-completions.css — per-type completion sheets (ticket / auction /
   popcorn), rendered by completions.js into ds-drawer sheets. Reuses
   cc-pay / cc-field / cc-success / checkout / ds-drawer / ticket-qr; this
   file adds only the drawer title + sheet-specific spacing. Tokens only.
   ════════════════════════════════════════════════════════════════ */

/* ds-drawer head title (ds-drawer.css ships no title rule) */
.ds-drawer__title { margin: 0; font-family: var(--font-cjk-display); font-size: var(--fs-h4); font-weight: var(--fw-bold); color: var(--text-primary); }

.completion-form { margin-top: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); }
.completion-foot { margin-top: var(--space-5); display: flex; flex-direction: column; gap: var(--space-2); }
.completion-success { padding-block: var(--space-6); text-align: center; }
.completion-qr { width: -moz-fit-content; width: fit-content; margin: var(--space-5) auto; }

/* auction bid sheet */
.bid-sheet__status { margin-bottom: var(--space-5); padding: var(--space-4); border: 1px solid var(--border-default); border-radius: var(--radius-lg); background: var(--bg-secondary); }
.bid-sheet__row { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: var(--space-2); }
.bid-sheet__row:last-child { margin-bottom: 0; }
.bid-sheet__k { font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-tertiary); }
.bid-sheet__cur { font-family: var(--font-latin); font-size: var(--fs-h4); font-weight: var(--fw-bold); color: var(--yellow-500); font-variant-numeric: tabular-nums; }
.bid-sheet__chips { display: flex; gap: var(--space-2); margin-bottom: var(--space-4); }
.bid-sheet__chip { flex: 1; min-height: 44px; border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--white-alpha-4); color: var(--text-primary); font-family: var(--font-latin); font-size: var(--fs-body-sm); font-weight: var(--fw-semibold); cursor: pointer; transition: border-color 160ms var(--ease-out), background 160ms var(--ease-out); }
.bid-sheet__chip:hover { border-color: var(--border-strong); }
.bid-sheet__chip.is-selected { border-color: var(--yellow-500); background: rgba(255, 163, 63, 0.08); }
.bid-sheet__note { margin: var(--space-3) 0 0; font-family: var(--font-cjk-text); font-size: var(--fs-caption); color: var(--text-tertiary); }

/* popcorn spend / top-up sheet */
.pop-sheet__balance { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: var(--space-4); padding: var(--space-4); border: 1px solid var(--border-default); border-radius: var(--radius-lg); background: var(--bg-secondary); }
.pop-sheet__balance-v { font-family: var(--font-latin); font-size: var(--fs-h4); font-weight: var(--fw-bold); color: var(--yellow-500); font-variant-numeric: tabular-nums; }
.pop-sheet__row { display: flex; align-items: baseline; justify-content: space-between; font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--text-secondary); padding: var(--space-2) 0; }
.pop-sheet__row--total { border-top: 1px solid var(--border-subtle); margin-top: var(--space-2); padding-top: var(--space-3); color: var(--text-primary); font-weight: var(--fw-bold); }
.pop-sheet__insufficient { margin: var(--space-3) 0; padding: var(--space-3) var(--space-4); border: 1px solid var(--error-500); border-radius: var(--radius-md); font-family: var(--font-cjk-text); font-size: var(--fs-body-sm); color: var(--error-500); }
.pop-sheet__packages { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-4); }
.pop-sheet__package { display: flex; align-items: center; justify-content: space-between; min-height: 52px; padding: var(--space-3) var(--space-4); border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--white-alpha-4); color: var(--text-primary); cursor: pointer; transition: border-color 160ms var(--ease-out), background 160ms var(--ease-out); }
.pop-sheet__package.is-selected { border-color: var(--yellow-500); background: rgba(255, 163, 63, 0.08); }
.pop-sheet__package-pop { font-family: var(--font-latin); font-weight: var(--fw-bold); }
.pop-sheet__package-price { font-family: var(--font-latin); font-size: var(--fs-body-sm); color: var(--text-secondary); }

@media (prefers-reduced-motion: reduce) {
  .bid-sheet__chip, .pop-sheet__package { transition: none; }
}


/* ── cross-document page transition (shared, all pages) ── */
/* ════════════════════════════════════════════════════════════════
   51-page-transition.css — PREMIUM PAGE TRANSITION (2026-07-02)
   dissolve + slide-in on every same-origin navigation.

   History, because both rulings came from L and only ONE was reversed:
     • 2026-06-26 — "no animation on navigation" → root old/new forced to
       1ms; AND "chrome dead-still, icons never blink" → chrome named +
       snapped. The 300ms aimless crossfade of that era was killed.
     • 2026-07-02 — L reverses the FIRST half by build-prompt: navigation
       now gets a subtle premium dissolve + slide-in (Animation Director
       spec: 300ms in / 150ms out — Material fade-through envelope, iOS
       push ceiling, Linear never-gated). The SECOND half — chrome
       stillness — remains law and is what makes a root slide safe at all.

   Two layers, one grammar (same tokens, same travel, same curves):
     1. Cross-document View Transitions (Chromium): old root dissolves
        (150ms), new root fades in + rises 14px (300ms settle). Composes
        with nav-speed.js prerender (plays on activation).
     2. Fallback (Safari/FF): nav-speed.js adds html.pt-enter when
        CSSViewTransitionRule is ABSENT → <main> alone plays the same
        fade + rise. Chrome sits outside <main>, so it never moves.
        VT-capable browsers never get .pt-enter → no double-animation.

   Rulings encoded below (Animation Director, 2026-07-02):
     • html[data-page-choreo] (index + the 5 shop grid pages, set in the
       page markup) → slide zeroed, fade kept: those pages' own entrance
       choreography owns the rise. One rise per screen.
     • html[data-nav-traverse] (set by nav-speed.js from
       navigation.activation / performance nav type) → back/forward is a
       RESTORATION, not a descent: 150ms crossfade, no slide. Also avoids
       sliding a scroll-restored mid-page snapshot past still chrome.
     • Reduced motion → opacity-only 150ms, BOTH layers. Never an instant
       hard cut: between two dark pages that's a luminance flash.
     • ::view-transition gets pointer-events:none — the VT pseudo-layer
       must never create a click-dead window on a dozens-per-session path.
   ════════════════════════════════════════════════════════════════ */

@view-transition { navigation: auto; }

/* the transition layer never gates input */
::view-transition { pointer-events: none; }

/* page travel: smallest token for the heaviest plane (below the cards' 18px
   → parent-calm, children-detail); component-scoped alias for the -m swap */
html {
  --page-travel: var(--reveal-y-sm);
}
@media (max-width: 767.98px) {
  html { --page-travel: var(--reveal-y-sm-m); }
}

/* Name the persistent chrome so the VT carries each across the swap as a
   single held element (no progressive repaint → icons never blink). Each name
   must be unique per document; only the rendered header variant is named. */
.side-rail { view-transition-name: ztor-rail; }
.botnav    { view-transition-name: ztor-botnav; }
body[data-auth="logged-in"]  .header[data-state="after-login"],
body[data-auth="logged-out"] .header[data-state="before-login"] { view-transition-name: ztor-header; }

/* ── Layer 1: the dissolve + slide (Chromium cross-document VT) ──
   Old page: quick dissolve — leaving is an acknowledgment, not theatre.
   New page: fade + short rise on the settle curve. The old snapshot does
   NOT drift (one moving plane per screen; drifting it would also move a
   mid-scroll footer past the still chrome). */
::view-transition-old(root) {
  animation: ztor-page-out var(--dur-page-out) var(--ease-emphasized) both;
}
::view-transition-new(root) {
  animation: ztor-page-in var(--dur-page) var(--ease-settle) both;
}
@keyframes ztor-page-out {
  to { opacity: 0; }
}
@keyframes ztor-page-in {
  from { opacity: 0; transform: translateY(var(--page-travel)); }
  to   { opacity: 1; transform: none; }
}
@keyframes ztor-page-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* choreographed pages (index + shop grids): their own entrance owns the
   rise — the incoming root only dissolves. Destination document's attr
   drives the cross-document transition, so this needs no JS. */
html[data-page-choreo]::view-transition-new(root) { animation-name: ztor-page-fade; }

/* back/forward: restoration, not descent — 150ms crossfade, no slide */
html[data-nav-traverse]::view-transition-new(root) {
  animation: ztor-page-fade var(--dur-page-out) var(--ease-emphasized) both;
}

/* ── Layer 2: universal fallback — <main> entry (Safari/FF only) ──
   nav-speed.js adds html.pt-enter ONLY where CSSViewTransitionRule is
   absent, so VT browsers never run this. One-shot `both` fill → bfcache
   restores resume in the finished state (no replay, no reversed slide). */
html.pt-enter main {
  animation: ztor-page-in var(--dur-page) var(--ease-settle) both;
}
html.pt-enter[data-page-choreo] main { animation-name: ztor-page-fade; }
html.pt-enter[data-nav-traverse] main {
  animation: ztor-page-fade var(--dur-page-out) var(--ease-emphasized) both;
}

/* reduced motion: opacity-only, quick, both layers — a fade still says
   "the page changed" without vestibular motion or a hard luminance cut */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root) { animation-duration: var(--dur-page-out); }
  ::view-transition-new(root),
  html[data-page-choreo]::view-transition-new(root) {
    animation: ztor-page-fade var(--dur-page-out) linear both;
  }
  html.pt-enter main,
  html.pt-enter[data-page-choreo] main {
    animation: ztor-page-fade var(--dur-page-out) linear both;
  }
}

/* Persistent CHROME: SNAP dead-still — do NOT cross-fade. Cross-fading the old
   and new header/rail snapshots ghosts the two images and flickers the masked
   icons inside them (the search icon, etc.). Instead hide the old snapshot and
   show the new at full opacity instantly, with the group morph zeroed — so at
   t=0 the destination chrome is simply there, held perfectly still. This is the
   "Zara snap" and it's what keeps every nav/header icon from blinking. */
::view-transition-group(ztor-rail),
::view-transition-group(ztor-header),
::view-transition-group(ztor-botnav) { animation-duration: 0s; }
::view-transition-old(ztor-rail),
::view-transition-old(ztor-header),
::view-transition-old(ztor-botnav) { animation: none; opacity: 0; }
::view-transition-new(ztor-rail),
::view-transition-new(ztor-header),
::view-transition-new(ztor-botnav) { animation: none; opacity: 1; }

/* ── Scroll-reveal on navigation — handled in JS, not here (2026-07-02) ──
   This file previously force-finished EVERY reveal target site-wide
   (`opacity:1/transform:none !important` on all [data-reveal-section] /
   [data-reveal-stack] content) to stop the page-head "sliding up" on
   navigation. That blanket rule ALSO killed every legitimate scroll reveal
   below the fold — the whole scroll-motion system silently became a no-op
   while the GSAP engines kept setting inline opacity:0 and creating
   triggers that could never win against !important.

   The nav-stillness requirement is now met surgically by the
   `reveal-instant-boot` block in ds.js: reveal targets already inside the
   viewport at load are snapped to their final state (no tween → nothing
   slides on navigation, and no above-fold element ever sticks at
   opacity:0 on a slow load), while below-fold targets keep their
   scroll-triggered entrances. Do NOT reintroduce a CSS !important
   neutraliser here — it is invisible to the JS engines and kills motion
   site-wide. */

/* ── .ds-enter — CSS-only entrance for JS-INJECTED CONTENT ──
   Reclassified 2026-07-02: this is a CONTENT-ARRIVAL utility (async
   injections that land after page entry — cocreate-project's render),
   NOT a page-entry mechanism. Its former page-entry uses
   (profile-assets / profile-earnings) are superseded by the shared
   page transition above and were removed. Curve fixed --ease-out →
   --ease-settle (--ease-out is feedback-only ≤200ms per tokens.css). */
.ds-enter {
  animation: ds-enter var(--dur-md, 520ms) var(--ease-settle) both;
}
@keyframes ds-enter {
  from { opacity: 0; transform: translateY(var(--reveal-y-md, 16px)); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .ds-enter { animation-duration: 1ms; }
}


/* ── progressive liquid-glass refraction (Chromium-only, frost fallback) ── */
/* ════════════════════════════════════════════════════════════════
   52-liquid-glass-refraction.css — the BEND layer (Chromium-only, frost fallback)

   Adds real REFRACTION (the background bends through the glass) on top of the
   frost + tint + shine material. The lens is PER-ELEMENT: assets/liquid-glass.js
   (buildLenses → genLensMap) draws each surface its OWN displacement map at its
   EXACT px size, exposes it as the inline custom property --lg-refract on the
   element, and this file composes it into backdrop-filter as
   `blur(…) var(--lg-refract) saturate(…)`. The engine also flips
   html.glass-refract (the gate); non-Chromium / iOS never get it and keep the
   plain frost (no url() to break — see the engine's UA gate).

   ── FALLBACK IS EMPTY, ON PURPOSE ───────────────────────────────────────────
   `var(--lg-refract, )` — the fallback is EMPTY, so before the engine sets the
   per-element lens the surface renders as plain frost (blur+saturate, no bend),
   then the bend snaps in one frame later. We do NOT fall back to the old shared
   url(#liquid-glass): that filter is objectBoundingBox + scale 0.5, so on a wide
   bar its throw is ~0.5×width → it samples outside the box → Chromium drops the
   whole backdrop-filter → see-through nav. The empty fallback can never do that.

   ── WHY per-element fixes wide bars ─────────────────────────────────────────
   The per-element lens is primitiveUnits="userSpaceOnUse" with a FIXED 40px throw
   sized to the element, so it never over-throws regardless of width — which is the
   whole reason the signature bottom-nav pill can now safely bend.

   ── SCOPE: FOCAL surfaces only (Carmack budget ~6/viewport) ─────────────────
   The bend is a focal material, not a texture. It opts in here for the primary CTAs
   (ghost / yellow-ghost / 計畫與價格 pill), the signature bottom-nav pill, the focal
   drawers, and the language / profile popovers. Everything small or repeated (44
   price chips, status badges, score pills, glass-tab items) or sitting over MOVING
   media (hero-carousel + film-rail arrows) deliberately stays on the plain frost —
   refraction there is invisible noise and a per-frame backdrop re-snapshot. The
   matching engine list is REFRACT_SELECTORS in assets/liquid-glass.js — keep the
   two in sync.
   ════════════════════════════════════════════════════════════════ */

/* ── IN-FLOW BUTTONS — frost lives on the ::before lens. The bend rides the SAME
   ::before, sized per element by the engine (--lg-refract inherits onto the pseudo). */
.glass-refract .btn--ghost::before,
.glass-refract .btn--yellow-ghost::before,
.glass-refract .popcorn-section::before {
  -webkit-backdrop-filter: blur(var(--lg-blur)) var(--lg-refract, ) saturate(var(--lg-sat));
          backdrop-filter: blur(var(--lg-blur)) var(--lg-refract, ) saturate(var(--lg-sat));
}

/* ── BOTTOM NAV — the signature pill. Frost lives on the FIXED .botnav element (+ its
   own translateZ GPU layer); the specular shine stays on the non-transformed
   .botnav__list, so the bend never flattens the gloss (kit rule 3). Heavier wide-bar
   frost (--lg-frost-nav-wide = 38px) + the size-matched lens. On .lg-ready (the
   post-load recomposite frame mobile-nav.js adds) so the bend lands with the frost. */
.glass-refract .botnav.lg-ready {
  -webkit-backdrop-filter: blur(var(--lg-blur)) var(--lg-refract, ) saturate(var(--lg-sat));
          backdrop-filter: blur(var(--lg-blur)) var(--lg-refract, ) saturate(var(--lg-sat));
}

/* ── FOCAL DRAWERS — frost on the panel element. Single instance, opens over a
   mostly-static page → safe to bend. (--lg-frost-panel = 12px.) */
.glass-refract .watch-drawer__panel,
.glass-refract .notif-drawer__panel {
  -webkit-backdrop-filter: blur(calc(var(--lg-blur) * 1.5)) var(--lg-refract, ) saturate(var(--lg-sat));
          backdrop-filter: blur(calc(var(--lg-blur) * 1.5)) var(--lg-refract, ) saturate(var(--lg-sat));
}

/* ── HEADER POPOVERS — language (24px) + profile (28px). Frost on the panel element;
   each keeps its own depth, the lens is sized per element by the engine. */
.glass-refract .header-dropdown--lang {
  -webkit-backdrop-filter: blur(calc(var(--lg-blur) * 3)) var(--lg-refract, ) saturate(1.6);
          backdrop-filter: blur(calc(var(--lg-blur) * 3)) var(--lg-refract, ) saturate(1.6);
}
.glass-refract .header-dropdown--profile {
  -webkit-backdrop-filter: blur(calc(var(--lg-blur) * 3.5)) var(--lg-refract, ) saturate(1.4);
          backdrop-filter: blur(calc(var(--lg-blur) * 3.5)) var(--lg-refract, ) saturate(1.4);
}

/* ── SEGMENTED TAB PILLS (.glass-tabs / .shop-filter) — when stuck, the SAME 我的投票
   material: white-5% + 8px frost + shine + the per-element bend. The engine sizes the
   lens to the pill (selectors are in REFRACT_SELECTORS); the non-Chromium fallback is
   the plain 8px frost in 45-sticky-tabs. Empty fallback → never a see-through pill. */
.glass-refract .tabstick-host .glass-tabs,
.glass-refract .tabstick-host .shop-filter {
  -webkit-backdrop-filter: blur(var(--lg-blur)) var(--lg-refract, ) saturate(var(--lg-sat));
          backdrop-filter: blur(var(--lg-blur)) var(--lg-refract, ) saturate(var(--lg-sat));
}

/* ── COMMUNITY FEED PILL — mobile only: the tab bar wears the standard pill dress
   (18-community-page.css) and gets the same per-element bend. Desktop keeps the
   full-bleed strip and must NOT consume the lens, hence the media scope (the engine
   still sets --lg-refract on the element; unconsumed there, harmless). */
@media (max-width: 767.98px) {
  .glass-refract .community-tabbar .feed-tabs {
    -webkit-backdrop-filter: blur(var(--lg-blur)) var(--lg-refract, ) saturate(var(--lg-sat));
            backdrop-filter: blur(var(--lg-blur)) var(--lg-refract, ) saturate(var(--lg-sat));
  }
}


/* ── Ztor Spot Exchange (exchange/ app — shared bundle, .x- prefix) ── */
/* ════════════════════════════════════════════════════════════════
   60-x-market.css — Ztor Spot Exchange · market home (exchange/index.html)
   + shared exchange atoms reused on every exchange page (.x-delta,
   .x-price, mode visibility, header bell).

   All values are tokens; dimensions ride --ds-scale. Mode switching is
   CSS-reacted off body[data-x-mode="spot|futures"] (set by the registered
   x-market behaviour + restored pre-paint) — never per-page JS styling.
   ════════════════════════════════════════════════════════════════ */

/* ── exchange page rhythm (content lives in the REAL .container anatomy;
      .x-page only owns vertical spacing + botnav clearance) ── */
.x-page {
  display: block;
  padding-top: calc(var(--space-6) * var(--ds-scale, 1));
  padding-bottom: calc(var(--space-24) + env(safe-area-inset-bottom, 0px));
}
/* app pages: .section carries the MARKETING rhythm token (--space-section,
   64-100px) — inside .x-page that reads as a dead band above the page head,
   so .x-page alone owns vertical spacing (each exchange page has exactly
   one .section; zeroing it collapses no inter-section gaps). */
.x-page .section { padding-block: 0; }
.x-page .page-head { margin-bottom: var(--space-6); }
@media (min-width: 768px) {
  .x-page { padding-top: var(--space-8); padding-bottom: var(--space-16); }
}

/* ── exchange header variant (mobile): the my-value strip carries the wallet
      on the market page, so the header stays a single quiet bar — logo left,
      ONLY the bell right. Everything else in .header__actions (language,
      popcorn pill, login) is hidden so the header can never wrap to two lines.
      The bell is pinned absolute-right at a 44px target. Desktop untouched. ── */
@media (max-width: 767.98px) {
  .header--x .header__inner { position: relative; }
  .header--x .header__nav-links { display: none; }
  .header--x .header__actions > * { display: none; }
  .header--x .header__actions .x-bell {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 50%;
    right: var(--container-padding-x, var(--space-4));
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
  }
}

/* mode visibility RETIRED (L 2026-07-17): the unified trade slip owns the
   spot/futures choice — every page shows everything. body[data-x-mode] and
   the ledger mode key remain as harmless legacy state. */

/* ── brand glyphs (NO unicode emoji in exchange UI — house rule) ──
   .x-pop → popcorn currency mark rendered after a number (brand SVG;
            JS patches plain numbers, the icon is CSS-owned)
   .x-ico → maskable house icon colored by currentColor; the concrete
            glyph comes from --icon (set by a variant class, not inline) ── */
.x-pop::after {
  content: '';
  display: inline-block;
  width: 0.92em;
  height: 0.92em;
  margin-left: 0.28em;
  vertical-align: -0.1em;
  background: url('assets/icons/popcorn-sm.svg') center / contain no-repeat;
}
.x-ico {
  display: inline-block;
  width: 0.9em;
  height: 0.9em;
  vertical-align: -0.08em;
  background: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
          mask: var(--icon) center / contain no-repeat;
}
.x-ico--up      { --icon: url('assets/icons/arrow_up.svg');   background: var(--success-500); }
.x-ico--down    { --icon: url('assets/icons/arrow_down.svg'); background: var(--error-500); }
.x-ico--flat    { --icon: url('assets/icons/report.svg'); }
.x-ico--search  { --icon: url('assets/icons/search.svg'); }
/* popcorn currency mark — use the same SVG as .x-pop ::after for inline contexts */
.x-ico--popcorn { --icon: url('assets/icons/popcorn-sm.svg'); }
/* film / movie icon (no-film emoji replacement) */
.x-ico--film    { --icon: url('assets/icons/movie.svg'); }
/* crown / award icon */
.x-ico--crown   { --icon: url('assets/icons/crown.svg'); }
/* report / news icon */
.x-ico--report  { --icon: url('assets/icons/report.svg'); }
.x-row__watch .x-ico       { --icon: url('assets/icons/bookmark_regular.svg'); }
.x-row__watch.is-on .x-ico { --icon: url('assets/icons/bookmark_active.svg'); }
.x-ico--watch                            { --icon: url('assets/icons/bookmark_regular.svg'); }
[aria-pressed="true"] > .x-ico--watch,
.is-on > .x-ico--watch                   { --icon: url('assets/icons/bookmark_active.svg'); }

/* whole card navigates to the asset detail (delegated click in x-market.js) */
.x-row, .x-round-card { cursor: pointer; }

/* ── shared: price + delta atoms ── */
.x-price {
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}
.x-price--big {
  font-size: calc(var(--fs-display) * 0.82);
  line-height: 1.05;
  letter-spacing: -0.02em;
}
.x-delta {
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
}
.x-delta--up   { color: var(--success-500); }
.x-delta--down { color: var(--error-500); }
.x-delta--flat { color: var(--text-tertiary); }

/* tick pulse — transform/opacity only, reduced-motion safe */
@media (prefers-reduced-motion: no-preference) {
  .x-tick-pulse { animation: x-tick 480ms var(--ease-out); }
  @keyframes x-tick {
    0%   { transform: scale(1); }
    35%  { transform: scale(1.045); }
    100% { transform: scale(1); }
  }
}

/* ════════════════════════════════════════════════════════════════
   LIVE AMBIENT BREATH — the second layer of the liveness language.

   Layer 1 (above) is the tick PULSE: a one-shot scale on the changed
   element, fired by the engine's 'xtick' via .x-tick-pulse.
   Layer 2 (here) is the ambient BREATH: a slow, sub-perceptual, always-
   on opacity loop that says "this surface is live" even between ticks.
   Renderers add .x-live to the small live glyphs (sparklines, delta
   chips, gauge %, LIVE dots, countdowns) — NEVER to the large readable
   numbers, which hold still and only tick-pulse.

   ONE source: this class + these tokens are the whole system. No
   per-surface keyframes. Tune globally via the two tokens below.

   Opacity (not transform) on purpose: a scale on a 1.5px SVG stroke or
   a tiny chip shimmers at the sub-pixel level under a straight-on gaze;
   opacity stays clean and is equally compositor-cheap.

   Test: felt, not watched. If you can SEE it looking straight at it,
   drop --x-live-min closer to 1 (raise the trough).
   ──────────────────────────────────────────────────────────────── */
:root {
  --x-live-dur: 5200ms;   /* one breath; offset from TICK_MS(5000) so the two layers never phase-lock into a visible beat */
  --x-live-min: 0.82;     /* opacity trough — depth of the breath (1 → 0.82 → 1, an 18% swing). Demo tuning per L 2026-07-17: clearly visible liveness for the client room. Director's shipped-product setting is 0.88 (felt, not watched) — nudge UP toward 1 to calm it after the demo, DOWN to deepen it */
}
@media (prefers-reduced-motion: no-preference) {
  .x-live {
    /* negative delay → every element boots MID-breath, so nothing does a
       synchronized one-time fade at load. Per-element phase via inline
       --x-live-phase (set by renderers on sibling lists); the nth-child
       fallbacks below cover static/auto-rendered rows. */
    animation: x-live-breathe var(--x-live-dur) ease-in-out infinite;
    animation-delay: var(--x-live-phase, 0ms);
    will-change: opacity;
  }
  @keyframes x-live-breathe {
    0%,
    100% { opacity: 1; }
    50%  { opacity: var(--x-live-min); }
  }
  /* phase-offset: stagger by ROW so a surface never breathes in lockstep.
     --x-live-phase is set ONLY on the row CONTAINER and INHERITS down to the
     nested .x-live glyphs (custom props inherit; animation-delay reads it).
     Deliberately NOT set on .x-live itself — a per-element nth-child rule
     would shadow the inherited row phase (every sparkline is the same child
     index, so all rows would collapse to one phase). Gauge pairs (direct
     .x-live siblings with no row container) carry an inline --x-live-phase.
     Negative delay = element boots mid-cycle → no synchronized load fade. */
  .x-row:nth-child(5n + 1), .x-round-card:nth-child(5n + 1), .x-pos-card:nth-child(5n + 1) { --x-live-phase: 0ms; }
  .x-row:nth-child(5n + 2), .x-round-card:nth-child(5n + 2), .x-pos-card:nth-child(5n + 2) { --x-live-phase: -1040ms; }
  .x-row:nth-child(5n + 3), .x-round-card:nth-child(5n + 3), .x-pos-card:nth-child(5n + 3) { --x-live-phase: -2080ms; }
  .x-row:nth-child(5n + 4), .x-round-card:nth-child(5n + 4), .x-pos-card:nth-child(5n + 4) { --x-live-phase: -3120ms; }
  .x-row:nth-child(5n + 5), .x-round-card:nth-child(5n + 5), .x-pos-card:nth-child(5n + 5) { --x-live-phase: -4160ms; }
}
/* prefers-reduced-motion: the ambient breath is KILLED entirely (no
   opacity loop), and the tick pulse degrades to a plain opacity swap
   (no scale motion) — comprehension-safe, motion-free. */
@media (prefers-reduced-motion: reduce) {
  .x-live { animation: none; opacity: 1; }
  .x-tick-pulse { animation: x-tick-fade 240ms var(--ease-out); }
  @keyframes x-tick-fade {
    0%   { opacity: 0.5; }
    100% { opacity: 1; }
  }
}

/* ── header bell (notification trigger, exchange chrome) ── */
.x-bell { position: relative; }
.x-bell__dot {
  position: absolute;
  top: 6px; right: 6px;
  min-width: 16px; height: 16px;
  padding: 0 4px;
  border-radius: var(--radius-pill);
  background: var(--error-500);
  color: var(--text-primary);
  font-size: 10px;
  font-weight: var(--fw-bold);
  line-height: 16px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.x-bell__dot[hidden] { display: none; }

/* ── market page column rhythm ── */
.x-market-main {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  min-width: 0;
}

/* ── my-value card / wallet strip ──
   Acorns value-hero idiom: label small, number big + tabular, delta right.
   On mobile it also shows the popcorn balance (the header hides its balance),
   so this strip carries the wallet where the header can't. */
/* compact strip (L 2026-07-17 — the h1-number card read as a dead hero):
   one slim row, whole card links to portfolio */
.x-value-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: calc(var(--space-3) * var(--ds-scale, 1)) calc(var(--space-4) * var(--ds-scale, 1));
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  text-decoration: none;
  color: inherit;
  transition: border-color var(--dur-xs) var(--ease-out);
}
.x-value-card:hover { border-color: var(--border-strong); }
.x-value-card__main { min-width: 0; }
.x-value-card__label {
  font-size: var(--fs-caption);
  color: var(--text-secondary);
  margin: 0 0 2px;
}
.x-value-card__num {
  margin: 0;
  font-size: var(--fs-h4);
  line-height: 1.1;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
.x-value-card__side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-1);
  text-align: right;
}
/* popcorn balance chip — only surfaces on mobile (header balance is hidden there) */
.x-value-card__wallet {
  display: none;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
}
.x-value-card__wallet-icon { width: 16px; height: 16px; display: block; }
@media (max-width: 1023.98px) {
  .x-value-card__wallet { display: inline-flex; }
}

/* ── market table (trending rows) — V2 兩行堆疊 stacked anatomy ──
   L-approved 2026-07-17: the seven atoms no longer fight on one ~69px line.
   TWO columns: poster | body. The body stacks two lines:
     line1: title (own line, full width) + 「N 份」pill · bookmark RIGHT
     line2: price 🍿 + 今日Δ (.x-row__num cluster) · sparkline RIGHT
   ONE right rail: the bookmark glyph and the sparkline stroke end at the same
   x (the row content-box right edge). Shared by market.html (x-market rowHtml)
   and index.html 市場脈動 (x-home pulseRowHtml) — one component, one body.
   .x-row stays the DIRECT list child so the nth-child breath stagger keys off it;
   .x-row__num still wraps price+delta (tour step 2 spotlights it). */
.x-table { display: flex; flex-direction: column; }
.x-row {
  display: grid;
  grid-template-columns:
    calc(40px * var(--ds-scale, 1))   /* poster */
    minmax(0, 1fr);                   /* stacked body */
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: calc(64px * var(--ds-scale, 1));
  padding: calc(var(--space-3) * var(--ds-scale, 1)) var(--space-2);
  border: 0;
  border-bottom: 1px solid var(--border-subtle);
  background: none;
  color: inherit;
  text-align: left;
  cursor: pointer;
  text-decoration: none;
  transition: background var(--dur-xs) var(--ease-out), transform var(--dur-xs) var(--ease-out);
}
.x-row:last-child { border-bottom: 0; }
.x-row:hover { background: var(--border-subtle); }
.x-row:active { transform: scale(0.995); }
/* skeleton variant: same 2-col stacked box, no interaction affordances */
.x-row--skel { cursor: default; }
.x-row--skel:hover { background: none; }
.x-row--skel:active { transform: none; }
.x-row__thumb {
  width: calc(40px * var(--ds-scale, 1));
  height: calc(52px * var(--ds-scale, 1));
  object-fit: cover;
  border-radius: var(--radius-sm);
  background: var(--bg-secondary);
}
/* stacked body: two lines, min-width:0 so the title can ellipsis as last resort */
.x-row__body { min-width: 0; display: flex; flex-direction: column; gap: var(--space-1); }
/* line1: title takes its own line (shows full where it fits), pill, bookmark right */
.x-row__line1 { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
/* line2: price+delta cluster left, sparkline right — SHARE the right rail with the bookmark */
.x-row__line2 { display: flex; align-items: center; gap: var(--space-3); }
/* name-block: title + holdings pill on ONE line, ellipsis only as last resort */
.x-row__name {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  margin: 0;
  flex: 1 1 auto;
}
.x-row__name-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.x-row__hold {
  flex: 0 0 auto;
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
  padding: 1px var(--space-2);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
}
/* name-block variant: title over a cost/delta sub-line (reused by portfolio
   holdings — same .x-row anatomy, two-line name-block) */
.x-row__block { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.x-row__block .x-row__name { font-size: var(--fs-body); }
.x-row__sub {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}

/* sparkline: ~80×20 on line2, right-anchored (margin-left:auto) so its stroke
   ends at the content-box right edge — the SAME rail as the line1 bookmark */
.x-row__spark { width: 80px; height: 20px; display: block; flex: 0 0 auto; margin-left: auto; }
.x-row__spark path { fill: none; stroke-width: 1.5; }
.x-row__spark--up path   { stroke: var(--success-500); }
.x-row__spark--down path { stroke: var(--error-500); }
/* frame-label: tiny muted tag that names the reference frame of the adjacent number
   (e.g. 總損益 vs 今日) — sits inline in .x-row__sub or .x-row__num */
.x-row__frame-label {
  font-size: 9px;
  line-height: 1;
  color: var(--text-tertiary);
  opacity: 0.7;
  letter-spacing: 0.04em;
}
/* price-block: price + delta INLINE on line2 (row form, not stacked) — the big
   price holds still, the delta breathes. Tour step 2 spotlights this cluster. */
.x-row__num { display: flex; flex-direction: row; align-items: baseline; gap: var(--space-2); min-width: 0; }
.x-row__num .x-price { font-size: var(--fs-body-lg); display: inline-block; }
/* bookmark: right-anchored on line1, glyph flush to the content-box right edge
   (justify-content:flex-end + no right pad) so it shares the spark's rail */
.x-row__watch {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  margin-left: auto;
  flex: 0 0 auto;
  width: 28px; height: 28px;
  padding: 0;
  border: 0; background: none; cursor: pointer;
  color: var(--text-disabled);
  font-size: var(--fs-body-lg);
  border-radius: var(--radius-pill);
  transition: transform var(--dur-xs) var(--ease-out), color var(--dur-xs) var(--ease-out);
}
.x-row__watch .x-ico { width: 18px; height: 18px; }
.x-row__watch.is-on { color: var(--yellow-500); }
.x-row__watch:active { transform: scale(0.9); }
/* 買/賣 quick-action pair in the star slot (portfolio holdings — the complete
   own→trade loop, L 2026-07-17): each opens the unified slip with intent */
.x-row__acts {
  display: inline-flex;
  gap: var(--space-1);
  flex: 0 0 auto;
}
.x-row__act {
  min-height: 32px;
  min-width: 40px;
  padding: 2px var(--space-3);
  border: 1px solid var(--border-default);
  background: transparent;
  border-radius: var(--radius-pill);
  font-family: inherit;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
  cursor: pointer;
  transition: color var(--dur-xs) var(--ease-out), border-color var(--dur-xs) var(--ease-out);
}
.x-row__act:hover { color: var(--text-primary); border-color: var(--border-strong); }
.x-row__act--buy { color: var(--yellow-500); border-color: rgba(255, 163, 63, 0.45); }
.x-row__act--buy:hover { color: var(--yellow-400); border-color: var(--yellow-500); }
/* ── portfolio holding row — P2 三欄對齊 (L 2026-07-17) ──
   Overrides the base 2-col market row: a flex-column of two lines.
     .x-row__hold-top  = poster + title + 「N 份」pill (line1)
     .x-row__hold-grid = 成本 / 總損益 / 今日 labeled columns + 買/賣 (line2)
   Each column is a label ABOVE its value; values baseline-align across columns.
   Colour tracks ONLY its own frame (07 §7) — 總損益 vs cost, 今日 vs today's open. */
.x-row--holding {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  align-items: stretch;
}
.x-row__hold-top {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}
.x-row__hold-top .x-row__thumb { flex: 0 0 auto; }
/* name link eats the slack so the pill is pushed to the FAR RIGHT of line1
   (flush with the 買/賣 right rail below); title ellipsis only as last resort */
.x-row__hold-top .x-row__name { flex: 1 1 auto; min-width: 0; }
.x-row__hold-top .x-row__hold { flex: 0 0 auto; margin-left: auto; }
/* three labeled data columns + trailing action pair. Top-aligned so all three
   labels sit on ONE line and the primary value line aligns across columns; the
   今日 column's secondary price tucks quietly beneath its delta. */
.x-row__hold-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr)) auto;
  gap: var(--space-2) var(--space-3);
  align-items: start;
}
.x-row__frame {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.x-row__frame-lab {
  font-size: 10px;
  line-height: 1;
  color: var(--text-tertiary);
  letter-spacing: 0.04em;
}
.x-row__frame-val {
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* current price is intentionally NOT shown in the holdings row (L 2026-07-17):
   the live price lives on the asset page behind the row tap. The 今日 column
   carries the delta% only. */
.x-row--holding .x-row__acts {
  display: inline-flex;
  gap: var(--space-1);
  align-self: center;
}

/* ── futures round cards (market home, futures mode) ── */
.x-round-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}
@media (min-width: 768px) {
  .x-round-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
  .x-round-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
/* ── futures round card — V6 混搭 anatomy (L confirmed 2026-07-17):
   blurred poster backdrop + scrim · 2:3 poster left · centered question ·
   dot-matrix marquee countdown · full-width 民意 gauge. Mobile: gauge
   breaks out full card width below the poster zone (L's sketch). ── */
.x-round-card {
  position: relative;
  border-radius: var(--radius-xl);
  overflow: hidden;
  min-height: 300px;
  display: flex;
  flex-direction: column;
}
.x-round-skel {
  border-radius: var(--radius-xl);
  padding: var(--space-5);
  min-height: 200px;
}
.x-round-card__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: blur(18px) saturate(1.2);
  transform: scale(1.08);
}
.x-round-card__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0.72) 50%, rgba(0,0,0,0.88) 100%);
}
.x-round-card__content {
  position: relative;
  z-index: 1;
  flex: 1;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-6);
  padding: var(--space-6);
}
/* standard theatrical 2:3 — never stretched off-format */
.x-round-card__poster {
  width: 148px;
  height: auto;
  aspect-ratio: 2 / 3;
  align-self: start;
  object-fit: cover;
  border-radius: var(--radius-md);
  border: 1px solid rgba(255,255,255,0.25);
  box-shadow: 0 8px 24px rgba(0,0,0,0.5);
}
.x-round-card__main {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--space-3);
  min-width: 0;
}
.x-round-card__title {
  margin: 0;
  font-family: var(--font-cjk-display);
  font-size: var(--fs-h4);
  font-weight: var(--fw-bold);
  color: var(--neutral-white);
  text-align: center;
  line-height: var(--lh-h4);
  text-shadow: 0 2px 12px rgba(0,0,0,0.6);
}
.x-round-card__mine {
  display: flex;
  justify-content: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* ── dot-matrix marquee countdown ── */
.x-marquee {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  background: rgba(0,0,0,0.50);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
}
.x-marquee__label {
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  letter-spacing: 0.14em;
  color: rgba(255,255,255,0.5);
  white-space: nowrap;
}
.x-marquee__digits { display: flex; gap: 5px; align-items: center; }
.x-dotchar {
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
  border-radius: var(--radius-xs);
  padding: 2px 5px;
  font-family: var(--font-latin);
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--neutral-white);
  min-width: 22px;
  text-align: center;
  line-height: 1.3;
}
.x-dotchar--sep {
  background: transparent;
  border: none;
  color: rgba(255,255,255,0.35);
  padding: 0 1px;
  min-width: auto;
}

/* ── 民意 gauge — the crowd split IS the layout (flex weights = live q).
   CLICKABILITY FLOOR: each side keeps a tappable minimum however lopsided
   the split — even 0:100 (L 2026-07-16). ── */
.x-gauge {
  display: flex;
  height: 56px;
  border-radius: var(--radius-md);
  position: relative;
}
.x-gauge__up,
.x-gauge__down {
  min-width: 96px;
  display: flex;
  align-items: center;
  text-decoration: none;
  transition: background 200ms var(--ease-out);
}
.x-gauge__up {
  flex: var(--qup, 50);
  background: linear-gradient(to right, rgba(34,197,94,0.25), rgba(34,197,94,0.18));
  border: 1px solid rgba(34,197,94,0.35);
  border-right: none;
  border-radius: var(--radius-md) 0 0 var(--radius-md);
  padding-left: var(--space-4);
}
.x-gauge__up:hover { background: linear-gradient(to right, rgba(34,197,94,0.35), rgba(34,197,94,0.25)); }
.x-gauge__down {
  flex: var(--qdown, 50);
  background: linear-gradient(to left, rgba(239,68,68,0.25), rgba(239,68,68,0.18));
  border: 1px solid rgba(239,68,68,0.35);
  border-left: none;
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  justify-content: flex-end;
  padding-right: var(--space-4);
}
.x-gauge__down:hover { background: linear-gradient(to left, rgba(239,68,68,0.35), rgba(239,68,68,0.25)); }
.x-gauge__inner { display: flex; flex-direction: column; gap: 1px; align-items: flex-start; }
.x-gauge__down .x-gauge__inner { align-items: flex-end; }
.x-gauge__label { font-size: var(--fs-caption); font-weight: var(--fw-bold); }
.x-gauge__up .x-gauge__label { color: rgba(34,197,94,0.85); }
.x-gauge__down .x-gauge__label { color: rgba(239,68,68,0.85); }
.x-gauge__pct {
  font-family: var(--font-latin);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
}
.x-gauge__up .x-gauge__pct { color: var(--success-500); }
.x-gauge__down .x-gauge__pct { color: var(--error-500); }

/* mobile zoning (L sketch): poster+question+marquee top; gauge full width */
@media (max-width: 767.98px) {
  .x-round-card { min-height: 0; }
  .x-round-card__content {
    grid-template-columns: 96px minmax(0, 1fr);
    grid-template-rows: auto 1fr auto auto;
    grid-template-areas:
      "poster title"
      "poster marquee"
      "gauge  gauge"
      "mine   mine";
    gap: var(--space-4);
    padding: var(--space-5);
  }
  .x-round-card__poster { width: 96px; grid-area: poster; }
  .x-round-card__main { display: contents; }
  .x-round-card__title { grid-area: title; align-self: center; font-size: var(--fs-h5); line-height: var(--lh-h5); }
  .x-marquee { grid-area: marquee; align-self: end; padding: var(--space-2); gap: var(--space-2); }
  .x-marquee__label { display: none; }
  .x-dotchar { min-width: 16px; padding: 2px 4px; font-size: var(--fs-body-sm); }
  .x-gauge { grid-area: gauge; }
  .x-gauge__up, .x-gauge__down { min-width: 72px; }
  .x-round-card__mine { grid-area: mine; }
}

/* ── 更多玩法 cards (T1 多選項 · T2 大於/小於 — Kalshi anatomy) ── */
.x-plays-grid { display: grid; gap: var(--space-4); }
@media (min-width: 900px) { .x-plays-grid { grid-template-columns: 1fr 1fr; align-items: start; } }
.x-tcard {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-xl);
  padding: var(--space-5) var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.x-tcard__title {
  margin: 0;
  font-family: var(--font-cjk-display);
  font-size: var(--fs-h5);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}
.x-tcard__meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
}
.x-tcard__live {
  color: var(--error-500);
  font-weight: var(--fw-bold);
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.x-tcard__live::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--error-500); }
.x-tcard__row { border-top: 1px solid var(--border-subtle); padding-top: var(--space-3); display: flex; flex-direction: column; gap: var(--space-2); }
.x-tcard__row--ou { flex-direction: row; align-items: center; gap: var(--space-3); padding-bottom: var(--space-1); }
.x-tcard__line { display: flex; align-items: center; gap: var(--space-3); }
.x-tcard__poster { width: 36px; height: 50px; object-fit: cover; border-radius: var(--radius-sm); border: 1px solid var(--border-subtle); flex: 0 0 auto; }
.x-tcard__name { flex: 1; min-width: 0; font-size: var(--fs-body); font-weight: var(--fw-semibold); color: var(--text-primary); }
.x-tcard__pct { font-family: var(--font-latin); font-size: var(--fs-h4); font-weight: var(--fw-bold); color: var(--text-primary); font-variant-numeric: tabular-nums; }
.x-tcard__btns { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); }
.x-tcard__th {
  flex: 1;
  min-width: 0;
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  padding-bottom: 4px;
  border-bottom: 2px solid transparent;
}
.x-tcard__th--lead { border-bottom-color: var(--success-500); }
.x-tcard__mult { font-size: var(--fs-caption); color: var(--text-tertiary); font-variant-numeric: tabular-nums; }
.x-tcard__chance { min-width: 72px; }
.x-tpill {
  min-height: 40px;
  min-width: 96px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
  background: transparent;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  transition: background 150ms var(--ease-out);
}
.x-tpill--yes { color: var(--success-500); border-color: rgba(34,197,94,0.45); }
.x-tpill--yes:hover { background: rgba(34,197,94,0.12); }
.x-tpill--no { color: var(--error-500); border-color: rgba(239,68,68,0.45); }
.x-tpill--no:hover { background: rgba(239,68,68,0.12); }
/* ── 即將推出 badge chip (T1/T2 preview state) ──
   Matches the .x-pos-tag / .x-bell__dot chip anatomy already in this file.
   Sits inline in .x-tcard__meta alongside the existing meta spans. */
.x-coming-badge {
  display: inline-flex;
  align-items: center;
  padding: 1px var(--space-2);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
  background: var(--bg-secondary);
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  color: var(--text-tertiary);
  letter-spacing: 0.01em;
  margin-left: auto;   /* floats to the right of the flex meta row */
  white-space: nowrap;
}

/* ── 即將推出 pill disabled state (T1 會/不會 · T2 chance %) ──
   Removes pointer, hover lift, and saturates color. Applied by .x-tpill--preview.
   .x-gauge__up / .x-gauge__down (漲/跌) are NOT .x-tpill so this never hits them. */
.x-tpill--preview {
  opacity: 0.45;
  cursor: default;
  pointer-events: none;   /* belt-and-suspenders with the JS interceptor */
}
.x-tpill--preview:hover { background: transparent; }

.x-countdown {
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
}
.x-pos-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
  color: var(--text-secondary);
}
.x-pos-tag--up   { color: var(--success-500); border-color: currentColor; }
.x-pos-tag--down { color: var(--error-500);   border-color: currentColor; }

/* odds buttons: 看漲 / 看跌 — semantic colour is the meaning, not decoration */
.x-odds-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: calc(var(--space-3) * var(--ds-scale, 1));
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-default);
  background: var(--bg-secondary);
  color: var(--text-primary);
  cursor: pointer;
  transition: transform var(--dur-xs) var(--ease-out), border-color var(--dur-xs) var(--ease-out);
  min-height: 44px;
}
.x-odds-btn:active { transform: scale(0.98); }
.x-odds-btn__label { font-size: var(--fs-body-sm); font-weight: var(--fw-bold); }
.x-odds-btn__q     { font-size: var(--fs-h4); font-weight: var(--fw-bold); font-variant-numeric: tabular-nums; }
.x-odds-btn--up:hover,  .x-odds-btn--up.is-active   { border-color: var(--success-500); }
.x-odds-btn--down:hover,.x-odds-btn--down.is-active { border-color: var(--error-500); }
.x-odds-btn--up .x-odds-btn__q   { color: var(--success-500); }
.x-odds-btn--down .x-odds-btn__q { color: var(--error-500); }

/* ── 新上市 section: quiet heading (§8) — h4, tight rhythm, no eyebrow ── */
.x-newlist .page-head { padding: 0; margin-bottom: var(--space-4); }
.x-newlist .page-head__title { font-size: var(--fs-h4); letter-spacing: 0; }

/* ── 新上市 rail (drops pattern) ── */
.x-rail {
  display: flex;
  gap: var(--space-4);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: var(--space-2);
  -webkit-overflow-scrolling: touch;
}
.x-rail::-webkit-scrollbar { display: none; }
.x-rail-card {
  scroll-snap-align: start;
  flex: 0 0 calc(132px * var(--ds-scale, 1));
  text-decoration: none;
}
.x-rail-card__poster {
  width: 100%;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  border-radius: var(--radius-md);
  background: var(--bg-secondary);
}
.x-rail-card__name {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.x-rail-card__meta {
  margin-top: 2px;
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
}

/* ── market toolbar: search + category chips row ── */
.x-toolbar {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.x-search {
  width: 100%;
  padding: calc(var(--space-3) * var(--ds-scale, 1)) var(--space-4);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
  background: var(--bg-secondary);
  color: var(--text-primary);
  font-size: var(--fs-body);
}
.x-search::placeholder { color: var(--text-tertiary); }
.x-search:focus-visible {
  outline: 2px solid var(--interactive-primary);
  outline-offset: 2px;
}
@media (min-width: 768px) {
  .x-toolbar { flex-direction: row; align-items: center; justify-content: space-between; }
  .x-search { max-width: 320px; }
}

/* ── notification drawer items (shared exchange chrome) ── */
.x-notif {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--border-subtle);
  text-decoration: none;
  color: inherit;
  border-radius: var(--radius-sm);
  transition: background var(--dur-xs) var(--ease-out);
}
.x-notif:hover { background: var(--border-subtle); }
.x-notif__icon { font-size: var(--fs-body-lg); line-height: 1.4; }
.x-notif__body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.x-notif__title { font-size: var(--fs-body-sm); color: var(--text-secondary); font-weight: var(--fw-semibold); }
.x-notif.is-unread .x-notif__title { color: var(--text-primary); }
.x-notif.is-unread .x-notif__icon { position: relative; }
.x-notif.is-unread .x-notif__icon::after {
  content: '';
  position: absolute;
  margin-left: 2px;
  width: 6px; height: 6px;
  border-radius: var(--radius-pill);
  background: var(--interactive-primary);
}
.x-notif__desc { font-size: var(--fs-caption); color: var(--text-tertiary); }
.x-notif__time { font-size: var(--fs-caption); color: var(--text-disabled); }

/* ── page-head 怎麼玩 quiet link (right-aligned on the page-head line) ──
   Replaces the floating ？ circle: a plain text link, no chrome. */
.x-help {
  flex: 0 0 auto;
  align-self: flex-start;   /* rides the TITLE line, top-right — never its own row */
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 44px;
  padding: var(--space-1) 0;
  /* it's a LINK — underline + leading info glyph so it reads clickable (L 2026-07-17) */
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
  transition: color var(--dur-xs) var(--ease-out);
}
.x-help:hover { color: var(--text-primary); }
/* leading info glyph (mask → currentColor, same machinery as .x-ico) */
.x-help::before {
  content: '';
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  background: currentColor;
  -webkit-mask: url('assets/icons/info.svg') center / contain no-repeat;
          mask: url('assets/icons/info.svg') center / contain no-repeat;
}

/* phone: the shared .page-head stacks to a column (24-page-head.css), which
   drops the help link to its own row — on exchange pages keep the ROW so
   怎麼玩 stays top-right beside the title (L, 2026-07-16) */
@media (max-width: 767.98px) {
  .x-page .page-head { flex-direction: row; align-items: flex-start; }
  .x-page .x-help { min-height: 0; padding-top: 2px; }
}

.x-row__main { text-decoration: none; color: inherit; min-width: 0; }

/* ── market layout ──
   Mobile / tablet (<1024): single column, but the value strip LEADS —
   flex-column + order:-1 on the aside floats it above the toolbar/table
   without moving the DOM (desktop keeps source order). */
@media (max-width: 1023.98px) {
  .x-market-grid { display: flex; flex-direction: column; gap: var(--space-6); }
  .x-market-grid > .x-market-side { order: -1; }
}
/* Desktop two-column: value card sticky in the right rail (source order). */
@media (min-width: 1024px) {
  .x-market-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: var(--space-8);
    align-items: start;
  }
  .x-market-grid > .x-market-side { position: sticky; top: var(--space-20); }
}

/* ════════════════════════════════════════════════════════════════
   61-x-asset.css — Ztor Spot Exchange · asset page (exchange/asset.html)

   Hero · big price block · interactive chart · paired Buy/Sell CTAs in a
   §9-compliant sticky glass action bar · holding chip · supply bar ·
   earnings accordion · trades ticker · holders strip · watch/alert toggles.
   Futures mode swaps the price block for today's round card.

   Every value is a token; dimensions ride --ds-scale. Mode switching reacts
   to body[data-x-mode] (shared rule lives in 60-x-market.css) — never JS.
   Shared atoms (.x-price/.x-delta/.x-odds-btn/.x-pos-tag/.x-countdown) are
   REUSED from 60-x-market.css, not redefined here.
   ════════════════════════════════════════════════════════════════ */

/* ── page frame: two-column on desktop (chart+content left, trade right) ── */
.x-asset {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
}
@media (min-width: 1024px) {
  .x-asset {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 380px;
    gap: var(--space-10);
    align-items: start;
  }
  /* boxed as a deliberate quick-trade card — an unboxed sticky strip reads
     as dead space beside a long chart column */
  .x-asset__side {
    position: sticky;
    top: var(--space-20);
    background: var(--bg-card);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    padding: var(--space-5);
  }
}
.x-asset__main { display: flex; flex-direction: column; gap: var(--space-6); min-width: 0; }
.x-asset__side { display: flex; flex-direction: column; gap: var(--space-5); }

/* ── back link ── */
.x-back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  align-self: flex-start;
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
  text-decoration: none;
  padding: var(--space-1) 0;
}
.x-back:hover { color: var(--text-primary); }

/* ── hero: poster + titles + status ── */
.x-hero {
  display: flex;
  gap: var(--space-4);
  align-items: flex-start;
}
.x-hero__poster {
  flex: 0 0 auto;
  width: calc(88px * var(--ds-scale, 1));
  aspect-ratio: 2 / 3;
  object-fit: cover;
  border-radius: var(--radius-lg);
  background: var(--bg-secondary);
}
.x-hero__meta { min-width: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.x-hero__title {
  margin: 0;
  font-size: var(--fs-h2);
  line-height: 1.1;
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  letter-spacing: -0.01em;
}
.x-hero__orig {
  margin: 0;
  font-size: var(--fs-body-sm);
  color: var(--text-tertiary);
  font-family: var(--font-latin);
}
.x-hero__tags { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }

/* ── SPOT price block ──
   Big price (--fs-display, tabular); delta + why-chip share the meta line
   directly under it (Acorns/Stake value-hero idiom). */
.x-priceblock {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.x-priceblock__row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.x-priceblock__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}
.x-priceblock__unit {
  font-size: var(--fs-h3);
  font-weight: var(--fw-bold);
  color: var(--text-tertiary);
}
/* asset hero price: full display size (the market row uses the smaller --big) */
.x-priceblock .x-price--big {
  font-size: var(--fs-display);
  line-height: 1.0;
}
/* "why did it move?" chip */
.x-why {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  align-self: flex-start;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
  background: var(--bg-secondary);
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
  cursor: default;
}
.x-why__icon { font-size: var(--fs-body); line-height: 1; }

/* ── my-holding chip ── */
.x-hold-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  align-self: flex-start;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  border: 1px solid var(--yellow-500);
  background: rgba(255, 163, 63, 0.08);
  color: var(--text-primary);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
}
.x-hold-chip__val { color: var(--yellow-500); font-variant-numeric: tabular-nums; }
.x-hold-chip[hidden] { display: none; }

/* ── FUTURES round card (replaces price block) ── */
.x-round {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: calc(var(--space-5) * var(--ds-scale, 1));
  border: 1px solid var(--border-default);
  border-radius: var(--radius-lg);
  background: var(--bg-card);
}
.x-round__q { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.x-round__ask { margin: 0; font-size: var(--fs-h4); font-weight: var(--fw-bold); color: var(--text-primary); }
.x-round__timer {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
}
.x-round__odds { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
.x-round__mine { display: flex; flex-wrap: wrap; gap: var(--space-2); min-height: 22px; }
.x-round__pitch {
  margin: 0;
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}
.x-round__pitch strong { color: var(--yellow-500); font-weight: var(--fw-bold); }
.x-round__disclosure {
  margin: 0;
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
}
.x-round.is-locked { opacity: 0.7; }

/* ── chart ── */
.x-chart {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: calc(var(--space-4) * var(--ds-scale, 1));
  background: var(--bg-card);
}
/* timeframe pills: centered under the chart (glass-tabs owns its own bottom
   margin — zero it here so the pills hug the chart base) */
.x-chart__tools { display: flex; align-items: center; justify-content: center; }
.x-chart__tools .glass-tabs { margin-bottom: 0; }
.x-chart__canvas {
  position: relative;
  width: 100%;
  height: calc(240px * var(--ds-scale, 1));
  min-height: 200px;
}
.x-chart__canvas > .lightweight-charts { border-radius: var(--radius-md); overflow: hidden; }
/* SVG sparkline fallback */
.x-chart__fallback { width: 100%; height: 100%; display: block; }
.x-chart__fallback path { fill: none; stroke: var(--yellow-500); stroke-width: 1.5; }
.x-chart__fallback-area { fill: rgba(255, 163, 63, 0.10); stroke: none; }
.x-chart__note {
  margin: 0;
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
  text-align: center;
}
.x-chart__note[hidden] { display: none; }
/* crosshair tooltip (hover + touch-drag) */
.x-chart__tip {
  position: absolute;
  top: var(--space-2);
  left: var(--space-2);
  display: inline-flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--bg-secondary);
  border: 1px solid var(--border-default);
  pointer-events: none;
  z-index: 2;
}
.x-chart__tip[hidden] { display: none; }
.x-chart__tip-date { font-size: var(--fs-caption); color: var(--text-tertiary); }
.x-chart__tip-price { font-size: var(--fs-body-sm); font-weight: var(--fw-bold); color: var(--text-primary); font-variant-numeric: tabular-nums; }
/* chart header: latest price + change as real DOM text (a11y mirror) */
.x-chart__head { display: flex; align-items: baseline; gap: var(--space-3); }
.x-chart__price { font-size: var(--fs-h4); font-weight: var(--fw-bold); color: var(--text-primary); font-variant-numeric: tabular-nums; }

/* ── supply bar (reuses the募資卡 bar idiom, tokenised) ── */
.x-supply { display: flex; flex-direction: column; gap: var(--space-2); }
.x-supply__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
}
.x-supply__val { color: var(--text-primary); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; }
.x-supply__track {
  width: 100%;
  height: 8px;
  border-radius: var(--radius-pill);
  background: rgba(0, 0, 0, 0.4);
  overflow: hidden;
}
.x-supply__fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--yellow-600), var(--yellow-400));
  box-shadow: 0 0 10px rgba(255, 163, 63, 0.55);
  transition: width var(--dur-md, 480ms) var(--ease-out);
}
@media (prefers-reduced-motion: reduce) { .x-supply__fill { transition: none; } }

/* ── section blocks (accordion / ticker / holders) ── */
.x-block {
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--bg-card);
  overflow: hidden;
}
.x-block__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  padding: calc(var(--space-4) * var(--ds-scale, 1)) var(--space-5);
  border: 0;
  background: none;
  color: var(--text-primary);
  cursor: pointer;
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  text-align: left;
}
.x-block__head[aria-expanded] .x-block__chevron { transition: transform var(--dur-xs) var(--ease-out); }
.x-block__head[aria-expanded='true'] .x-block__chevron { transform: rotate(180deg); }
.x-block__chevron { color: var(--text-tertiary); font-size: var(--fs-body-sm); }
.x-block__body { padding: 0 var(--space-5) var(--space-4); }
.x-block__body[hidden] { display: none; }

/* earnings list */
.x-earn { display: flex; flex-direction: column; }
.x-earn__row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--border-subtle);
}
.x-earn__row:last-child { border-bottom: 0; }
.x-earn__icon { font-size: var(--fs-body-lg); }
.x-earn__label { font-size: var(--fs-body-sm); color: var(--text-primary); font-weight: var(--fw-semibold); }
.x-earn__date { font-size: var(--fs-caption); color: var(--text-tertiary); }
.x-earn__amt { font-size: var(--fs-body-sm); font-weight: var(--fw-bold); color: var(--success-500); font-variant-numeric: tabular-nums; }
.x-earn__link {
  display: inline-flex;
  margin-top: var(--space-3);
  font-size: var(--fs-body-sm);
  color: var(--yellow-500);
  text-decoration: none;
}
.x-earn__link:hover { text-decoration: underline; }

/* recent-trades ticker */
.x-ticker { display: flex; flex-direction: column; gap: var(--space-2); }
.x-ticker__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  font-size: var(--fs-body-sm);
  border-bottom: 1px solid var(--border-subtle);
}
.x-ticker__row:last-child { border-bottom: 0; }
.x-ticker__who { color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.x-ticker__side { font-weight: var(--fw-bold); }
.x-ticker__side--buy { color: var(--success-500); }
.x-ticker__side--sell { color: var(--error-500); }
.x-ticker__amt { color: var(--text-tertiary); font-variant-numeric: tabular-nums; white-space: nowrap; }
@media (prefers-reduced-motion: no-preference) {
  .x-ticker__row.x-ticker__row--new { animation: x-ticker-in 420ms var(--ease-out); }
  @keyframes x-ticker-in {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
  }
}

/* top-holders strip */
.x-holders { display: flex; gap: var(--space-3); align-items: center; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.x-holders::-webkit-scrollbar { display: none; }
.x-holder {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  text-decoration: none;
  min-width: 56px;
}
.x-holder__avatar {
  width: 40px; height: 40px;
  border-radius: var(--radius-pill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 163, 63, 0.14);
  color: var(--yellow-500);
  font-weight: var(--fw-bold);
  font-size: var(--fs-body);
}
.x-holder__name { font-size: var(--fs-caption); color: var(--text-secondary); max-width: 60px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.x-holder__qty  { font-size: var(--fs-caption); color: var(--text-tertiary); font-variant-numeric: tabular-nums; }

/* ── watch + alert toggles row ── */
.x-toggles { display: flex; gap: var(--space-3); flex-wrap: wrap; }
.x-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  min-height: 44px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
  background: var(--bg-secondary);
  color: var(--text-secondary);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: border-color var(--dur-xs) var(--ease-out), color var(--dur-xs) var(--ease-out), transform var(--dur-xs) var(--ease-out);
}
.x-toggle:active { transform: scale(0.97); }
.x-toggle.is-on { border-color: var(--yellow-500); color: var(--yellow-500); }

/* ── paired CTAs: sticky §9-compliant glass action bar on mobile ── */
.x-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
  align-items: stretch;
}
.x-actions .btn { width: 100%; }
/* the sell CTA disabled affordance (0 owned) */
.x-actions .btn:disabled { opacity: 0.5; }

/* Mobile: pin the actions to the bottom as a fixed glass bar. §9 — transform +
   backdrop-filter live on THIS SAME fixed element (mirrors .botnav), so the
   frost composites on a real GPU. No new glass knobs; --lg-* only. */
@media (max-width: 767.98px) {
  .x-actionbar {
    position: fixed;
    left: var(--space-4);
    right: var(--space-4);
    /* float ABOVE the botnav pill (66px tall + breathing room) — at 8px the
       botnav intercepted every tap on the buy/sell CTAs (z 20 vs 1000) */
    bottom: calc(76px + env(safe-area-inset-bottom, 0px));
    z-index: var(--z-sticky);
    padding: var(--space-2);
    border-radius: var(--radius-lg);
    transform: translateZ(0);                       /* §9: same element as the blur */
    -webkit-transform: translateZ(0);
    backdrop-filter: blur(var(--lg-blur));
    -webkit-backdrop-filter: blur(var(--lg-blur));
    border: var(--glass-border);
    box-shadow: var(--glass-shadow);
    background: var(--lg-fill, rgba(20, 20, 22, 0.55));
  }
  .x-actionbar.lg-ready {
    -webkit-backdrop-filter: var(--lg-frost);
            backdrop-filter: var(--lg-frost);
  }
  /* docked-bar COMPACT variant (L 2026-07-16: "way smaller") — the canonical
     btn--md is for in-page CTAs; inside the floating dock the pair rides a
     tighter profile. Scoped here so the canon stays untouched elsewhere. */
  .x-actionbar .btn {
    min-height: 36px;
    padding: var(--space-2) var(--space-4);
    font-size: var(--fs-body-sm);
  }
  /* the page reserves space so the fixed bar never covers the last block */
  .x-asset { padding-bottom: calc(160px + env(safe-area-inset-bottom, 0px)); }
  /* while the trade sheet is open the fixed bar hides (like .botnav) */
  body.ds-drawer-open .x-actionbar { display: none; }
}
@media (min-width: 768px) {
  .x-actionbar { position: static; }
}

/* ── 404 empty ── */
.x-asset-404 { max-width: 420px; margin-inline: auto; }

/* ════════════════════════════════════════════════════════════════
   .x-signals — 熱度指標 section (asset detail, appended 2026-07-16)

   Coinbase-style expandable stat rows: label · value · trend chip ·
   native <details> white-space definition on open.
   All values token-driven; buy/sell bar uses --success-500/--error-500.
   ════════════════════════════════════════════════════════════════ */

/* IG icon variant (mask idiom, same as .x-ico--up etc.) */
.x-ico--ig { --icon: url('assets/icons/ig.svg'); }

/* "現在發生什麼" one-liner */
.x-happening {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--bg-secondary);
  border-left: 3px solid var(--yellow-500);
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
  line-height: 1.55;
}
.x-happening[hidden] { display: none; }

/* signals list container */
.x-signals { display: flex; flex-direction: column; }
.x-signals[aria-busy='true'] { min-height: 120px; }

/* each expandable row: native <details> */
.x-signals__row {
  border-bottom: 1px solid var(--border-subtle);
}
.x-signals__row:last-child { border-bottom: 0; }

/* summary = the always-visible row (Fix #4: no cursor:pointer — rows expand
   inline def text only; detail sheets are not built yet, so pointer/tap
   affordances would falsely imply navigability) */
.x-signals__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: calc(var(--space-3) * var(--ds-scale, 1)) 0;
  cursor: default;
  list-style: none;
  font-size: var(--fs-body-sm);
  color: var(--text-primary);
}
/* remove native disclosure triangle across browsers */
.x-signals__summary::-webkit-details-marker { display: none; }
.x-signals__summary::marker { content: ''; }
.x-signals__summary:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; border-radius: var(--radius-sm); }

.x-signals__label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
  flex: 0 0 auto;
}
.x-signals__row-icon { display: inline-flex; align-items: center; }

.x-signals__value-wrap {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  justify-content: flex-end;
  min-width: 0;
}
.x-signals__value {
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  white-space: nowrap;
}
.x-signals__trend {
  /* reuses .x-delta colour variants; keeps tabular-nums */
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  white-space: nowrap;
}
.x-signals__chevron {
  color: var(--text-tertiary);
  font-size: var(--fs-body-sm);
  transition: transform var(--dur-xs) var(--ease-out);
  flex: 0 0 auto;
}
.x-signals__row[open] .x-signals__chevron { transform: rotate(90deg); }

/* definition paragraph (revealed when <details> is open) */
.x-signals__def {
  margin: 0 0 var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--bg-secondary);
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
  line-height: 1.6;
}

/* reduced-motion: no chevron spin */
@media (prefers-reduced-motion: reduce) {
  .x-signals__chevron { transition: none; }
}

/* ── buy/sell proportion bar ── */
.x-signals__bsbar {
  position: relative;
  width: 52px;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--error-500);
  overflow: hidden;
  flex: 0 0 auto;
}
.x-signals__bsbar-buy {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  border-radius: var(--radius-pill);
  background: var(--success-500);
}
.x-signals__bstext {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-caption);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.x-signals__bs-buy  { color: var(--success-500); font-weight: var(--fw-semibold); }
.x-signals__bs-sell { color: var(--error-500);   font-weight: var(--fw-semibold); }

/* ════════════════════════════════════════════════════════════════
   62-x-trade.css — Ztor Spot Exchange · trade sheet (the .ds-drawer
   bottom-sheet / right-panel that spot買賣 + futures看漲看跌 run in).

   The drawer chrome comes from ds-drawer.css / ds-actionbar.css — this
   file styles ONLY the trade-specific interior: tab pair, qty stepper,
   quick chips, quote rows, total, disclosure, and the success state.

   Every value is a token; dimensions ride --ds-scale. Action rows are
   view-local .x-trade__foot elements (NOT .cc-stepview__foot — ds-drawer's
   one-foot composition would relocate them and the two switching views
   would wipe each other's CTA; amended 2026-07-15).
   ════════════════════════════════════════════════════════════════ */

/* KNOWN TRAP: a display:flex/grid rule on a class defeats the [hidden] attribute.
   Class-level guard: EVERYTHING inside the trade sheet obeys [hidden] — a
   per-selector list here kept missing elements (the futures tabset rendered
   inside the spot sheet because .x-trade__tabs{display:grid} beat [hidden]). */
.x-trade [hidden],
[data-x-view][hidden],
[data-x-spot-only][hidden],
[data-x-trade-err][hidden] { display: none !important; }

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

/* ── T3 unified slip (Kalshi anatomy — L 2026-07-17) ── */
.x-trade__typerow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
.x-trade__typerow .x-trade__tabs { flex: 1; }
.x-trade__limit-tag {
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}
.x-type-btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  border: 1px solid var(--border-default);
  background: var(--bg-secondary);
  border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-3);
  min-height: 36px;
  font-family: inherit;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  cursor: pointer;
}
.x-type-btn::after {
  content: '';
  width: 7px;
  height: 7px;
  border-left: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg) translate(1px, -2px);
}
.x-type-menu {
  background: var(--bg-secondary);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  padding: var(--space-2);
  display: flex;
  flex-direction: column;
}
.x-type-menu__item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  border: 0;
  background: none;
  cursor: pointer;
  font-family: inherit;
}
.x-type-menu__item.is-active { background: var(--bg-card); }
.x-type-menu__name {
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
/* CSS-drawn check — no unicode glyph (house rule) */
.x-type-check {
  width: 11px;
  height: 6px;
  border-left: 2px solid var(--yellow-500);
  border-bottom: 2px solid var(--yellow-500);
  transform: rotate(-45deg);
  display: inline-block;
}
.x-type-menu__hint { font-size: var(--fs-caption); color: var(--text-tertiary); }

/* numeric field rows (期貨 金額/倍數 · 限價 目標價) */
.x-numrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  background: var(--bg-secondary);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-4);
}
.x-numrow__label { font-size: var(--fs-body-sm); color: var(--text-secondary); }
.x-numrow__wrap { display: inline-flex; align-items: baseline; gap: var(--space-1); }
.x-numrow__x { font-size: var(--fs-body-sm); font-weight: var(--fw-semibold); color: var(--text-tertiary); }
.x-numfield {
  width: 88px;
  background: transparent;
  border: 0;
  text-align: right;
  font-family: inherit;
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  appearance: textfield;
  -moz-appearance: textfield;
}
.x-numfield::-webkit-outer-spin-button,
.x-numfield::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.x-numfield:focus-visible { outline: 2px solid var(--yellow-500); outline-offset: 2px; border-radius: var(--radius-xs); }

/* stepper + chips wrapper keeps the sheet's vertical rhythm */
[data-x-qty-block] { display: flex; flex-direction: column; gap: var(--space-5); }

/* 限價 open orders */
.x-orders { display: flex; flex-direction: column; gap: var(--space-2); }
.x-orders__title { margin: 0; font-size: var(--fs-body-sm); font-weight: var(--fw-semibold); color: var(--text-secondary); }
.x-orders__empty { margin: 0; font-size: var(--fs-caption); color: var(--text-tertiary); }
.x-orders__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-secondary);
}
.x-orders__what { font-size: var(--fs-body-sm); color: var(--text-primary); font-variant-numeric: tabular-nums; }
.x-orders__cancel {
  border: 1px solid var(--border-default);
  background: transparent;
  color: var(--text-secondary);
  border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-3);
  min-height: 32px;
  font-family: inherit;
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  cursor: pointer;
}
.x-orders__cancel:hover { color: var(--error-500); border-color: var(--error-500); }

/* view-local action row — pinned feel without the relocation machinery */
.x-trade__foot {
  display: flex;
  gap: var(--space-3);
  padding-top: var(--space-4);
  margin-top: var(--space-2);
  border-top: 1px solid var(--border-subtle);
}
.x-trade__foot .btn { flex: 1; min-height: 44px; }

/* ── tab pair (買/賣 · 看漲/看跌) ── */
.x-trade__tabs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2);
  padding: 4px;
  border-radius: var(--radius-pill);
  background: var(--bg-secondary);
  border: 1px solid var(--border-subtle);
}
.x-trade__tab {
  border: 0;
  background: none;
  cursor: pointer;
  padding: var(--space-3);
  min-height: 44px;
  border-radius: var(--radius-pill);
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  color: var(--text-tertiary);
  transition: background var(--dur-xs) var(--ease-out), color var(--dur-xs) var(--ease-out);
}
.x-trade__tab.is-active { color: var(--text-primary); background: var(--bg-card); }
/* semantic colour when the active side carries meaning */
.x-trade__tab[data-x-tab='sell'].is-active,
.x-trade__tab[data-x-tab='down'].is-active { color: var(--error-500); }
.x-trade__tab[data-x-tab='up'].is-active   { color: var(--success-500); }

/* ── holding context (spot-only: 持有 N 份 · 現值 X) ── */
.x-trade__holding {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-lg);
  background: var(--bg-secondary);
  border: 1px solid var(--border-subtle);
  font-size: var(--fs-body-sm);
}
.x-trade__holding-label {
  color: var(--text-secondary);
  font-weight: var(--fw-semibold);
  flex: 0 0 auto;
}
.x-trade__holding-val {
  color: var(--text-primary);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

/* ── unit quote row ── */
.x-trade__quote {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
}
.x-trade__quote-val { color: var(--text-primary); font-weight: var(--fw-bold); font-variant-numeric: tabular-nums; }

/* ── qty stepper ── */
.x-stepper {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr) 56px;
  align-items: center;
  gap: var(--space-3);
}
.x-stepper__btn {
  width: 56px; height: 56px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-default);
  background: var(--bg-secondary);
  color: var(--text-primary);
  font-size: var(--fs-h3);
  font-weight: var(--fw-bold);
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: transform var(--dur-xs) var(--ease-out), border-color var(--dur-xs) var(--ease-out);
  touch-action: manipulation;
}
.x-stepper__btn:active:not(:disabled) { transform: scale(0.94); }
.x-stepper__btn:disabled { opacity: 0.4; cursor: not-allowed; }
.x-stepper__val {
  text-align: center;
  font-size: var(--fs-display);
  line-height: 1;
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
/* number + unit ride ONE centered baseline row: 「1 張」 — the unit on its
   own line under a left-drifting number read as a broken slip (L 2026-07-16) */
.x-stepper > div {
  display: inline-flex;
  align-items: baseline;
  justify-content: center;
  gap: var(--space-2);
  min-width: 0;
}
.x-stepper__unit { font-size: var(--fs-body-sm); font-weight: var(--fw-semibold); color: var(--text-tertiary); }

/* ── quick chips (1·5·10·最多) ── */
.x-chips { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-2); }
.x-chip {
  border: 1px solid var(--border-default);
  background: var(--bg-secondary);
  color: var(--text-secondary);
  border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-3);
  min-height: 40px;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: border-color var(--dur-xs) var(--ease-out), color var(--dur-xs) var(--ease-out);
}
.x-chip:active { transform: scale(0.96); }
.x-chip.is-active { border-color: var(--yellow-500); color: var(--yellow-500); }

/* ── total + balance-after readout ── */
.x-total {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4);
  border-radius: var(--radius-lg);
  background: var(--bg-secondary);
  border: 1px solid var(--border-subtle);
}
.x-total__row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.x-total__label { font-size: var(--fs-body-sm); color: var(--text-secondary); }
.x-total__big {
  font-size: var(--fs-h2);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.x-total__after { font-size: var(--fs-caption); color: var(--text-tertiary); font-variant-numeric: tabular-nums; }
.x-total__after--low { color: var(--error-500); }

/* ── futures payout line ── */
.x-payout {
  margin: 0;
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}
.x-payout strong { color: var(--yellow-500); font-weight: var(--fw-bold); }

/* ── inline error (insufficient / cap) ── */
.x-trade__err[hidden] { display: none; }

/* ── success state ── */
.x-success { display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--space-4); padding: var(--space-4) 0 var(--space-2); }
/* disc hosts the ds-success-tick (replaces medal emoji) */
.x-success__disc {
  width: calc(88px * var(--ds-scale, 1));
  height: calc(88px * var(--ds-scale, 1));
  border-radius: var(--radius-pill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(circle at 50% 40%, rgba(255, 163, 63, 0.28), rgba(255, 163, 63, 0.08));
  border: 1px solid var(--yellow-500);
  color: var(--yellow-500);
}
/* tick inherits the disc's color (currentColor → gold stroke) */
.x-success__disc .ds-success-tick { color: var(--yellow-500); }
.x-success__disc .ds-success-tick__svg { width: 52%; height: 52%; }
.x-success__title { margin: 0; font-size: var(--fs-h3); font-weight: var(--fw-bold); color: var(--text-primary); }
.x-success__title strong { color: var(--yellow-500); }
.x-success__sub { margin: 0; font-size: var(--fs-body-sm); color: var(--text-secondary); font-variant-numeric: tabular-nums; }

/* toast (share-link copied) — aria-live region */
.x-toast {
  position: fixed;
  left: 50%;
  bottom: calc(88px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%) translateY(10px);
  z-index: var(--z-modal);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-pill);
  background: var(--bg-card);
  border: 1px solid var(--border-strong);
  color: var(--text-primary);
  font-size: var(--fs-body-sm);
  box-shadow: var(--glass-shadow);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-xs) var(--ease-out), transform var(--dur-xs) var(--ease-out);
}
.x-toast.is-shown { opacity: 1; transform: translateX(-50%) translateY(0); }
@media (prefers-reduced-motion: reduce) { .x-toast { transition: opacity var(--dur-xs) linear; } }

/* disc pop — reduced-motion: ds-success-tick.css already handles it */
@media (prefers-reduced-motion: reduce) {
  .x-success__disc { transform: none !important; }
}

/* ════════════════════════════════════════════════════════════════
   63-x-home.css — Ztor Exchange · 總覽 lobby (exchange/index.html)

   Coinbase-home anatomy. Dashboard surface: DENSITY 4, MOTION 3.
   All values are tokens; dimensions ride --ds-scale.
   Mode visibility atoms from 60-x-market.css (never redefined here).
   Mobile-first 390px → ≥768px → ≥1024px (two-column).
   ════════════════════════════════════════════════════════════════ */

/* ── lobby layout ── */
.x-home-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

/* ONE centered column, all widths — the desktop right rail was removed:
   every section appears exactly once (L 2026-07-17) */
.x-home-grid { display: block; }
@media (min-width: 1024px) {
  .x-home-main { max-width: 760px; margin-inline: auto; }
}

/* ── balance hero ── */
.x-hero {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-6) 0 var(--space-4);
}
.x-hero__label {
  font-size: var(--fs-body-sm);
  color: var(--text-tertiary);
  font-weight: var(--fw-medium);
  letter-spacing: 0.01em;
}
.x-hero__num-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
}
/* The hero number: signature "錢包數字會呼吸" */
.x-hero__num {
  font-size: clamp(2.5rem, 7vw, 4rem);
  font-weight: var(--fw-bold);
  line-height: 1.0;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  transition: color 200ms var(--ease-out);
}
/* micro-pulse on xtick — transform+opacity only, reduced-motion safe */
@media (prefers-reduced-motion: no-preference) {
  .x-hero__num.x-tick-pulse {
    animation: x-hero-breathe 520ms var(--ease-out);
  }
  @keyframes x-hero-breathe {
    0%   { transform: scale(1); }
    30%  { transform: scale(1.025); }
    100% { transform: scale(1); }
  }
}
.x-hero__delta {
  font-size: var(--fs-body-sm);
  font-variant-numeric: tabular-nums;
}
.x-hero__actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-top: var(--space-1);
}
/* sizing/colors come from the canonical .btn .btn--primary .btn--md
   (02-button.css) — a parallel button implementation here caused the
   size drift L flagged 2026-07-16; only the link reset stays local */
.x-hero__deposit { text-decoration: none; }

/* tap-to-expand sparkline (collapsed by default; JS toggles .is-expanded) */
.x-hero__chart-wrap {
  overflow: hidden;
  max-height: 0;
  transition: max-height 380ms var(--ease-out);
}
.x-hero__chart-wrap.is-expanded { max-height: 180px; }
.x-hero__chart-toggle {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-tertiary);
  font-size: var(--fs-body-sm);
  padding: 0;
  display: flex;
  align-items: center;
  gap: var(--space-1);
  transition: color 150ms;
}
.x-hero__chart-toggle:hover { color: var(--text-primary); }
.x-hero__chart-svg {
  width: 100%;
  height: 80px;
  margin-top: var(--space-3);
}

/* ── asset rows (爆米花 + 份額) ── */
.x-asset-rows {
  display: flex;
  flex-direction: column;
  gap: 0;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--bg-surface);
}
.x-asset-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-4);
  text-decoration: none;
  color: inherit;
  transition: background 150ms;
  border-bottom: 1px solid var(--border-subtle);
  cursor: pointer;
}
.x-asset-row:last-child { border-bottom: none; }
.x-asset-row:hover { background: var(--bg-hover, var(--bg-surface)); }
.x-asset-row__icon {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-full);
  background: var(--bg-page);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.x-asset-row__icon img {
  width: 22px;
  height: 22px;
}
.x-asset-row__label {
  flex: 1;
  font-weight: var(--fw-medium);
  font-size: var(--fs-body);
  color: var(--text-primary);
  white-space: nowrap;
}
.x-asset-row__value {
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-semibold);
  font-size: var(--fs-body);
  color: var(--text-primary);
  margin-right: var(--space-2);
  white-space: nowrap;
}
.x-asset-row__delta {
  font-size: var(--fs-caption, 12px);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  margin-right: var(--space-2);
}
.x-asset-row__chev {
  width: 16px;
  height: 16px;
  opacity: 0.4;
  flex-shrink: 0;
}

/* ── section headers (lobby internal) ── */
.x-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-4);
}
/* The column's flex gap owns ALL inter-section rhythm on home. Portfolio's
   generic .x-section-head (63-x-portfolio.css, margin-top: space-8) loads
   later in the bundle and bleeds in here — stacked on the gap it made 64px
   voids (L 2026-07-17). Scoped kill; de-duplicating the shared class name
   is queued post-demo. */
.x-home-body .x-section-head { margin-top: 0; }
.x-section-head__title {
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  margin: 0;
}
.x-section-head__link {
  font-size: var(--fs-body-sm);
  color: var(--text-tertiary);
  text-decoration: none;
  transition: color 150ms;
}
.x-section-head__link:hover { color: var(--text-primary); }

/* ── market pulse module ── */
.x-pulse-section { display: flex; flex-direction: column; }
.x-pulse-toggle {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.x-pulse-tab {
  background: none;
  border: none;
  cursor: pointer;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-full);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-medium);
  color: var(--text-tertiary);
  transition: background 150ms, color 150ms;
}
.x-pulse-tab.is-active {
  background: var(--bg-surface);
  color: var(--text-primary);
}
/* market pulse rows reuse .x-row anatomy from 60-x-market.css */
.x-pulse-rows {
  display: flex;
  flex-direction: column;
  gap: 0;
}
.x-pulse-empty {
  font-size: var(--fs-body-sm);
  color: var(--text-tertiary);
  padding: var(--space-2) 0;
}

/* ── 新上市 rail section (inside lobby) ── */
.x-home-new {
  display: flex;
  flex-direction: column;
}

/* ── 最近動態 rows ── */
.x-recent-rows {
  display: flex;
  flex-direction: column;
  gap: 0;
}
/* lobby activity rows reuse .x-feed-row from 63-x-portfolio (already in bundle) */

/* ── right-rail quick actions card (desktop) ── */
.x-rail-card-panel {
  background: var(--bg-surface);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.x-rail-card-panel__title {
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
  margin: 0 0 var(--space-1);
}
.x-rail-card-panel .btn { width: 100%; justify-content: center; }

/* panel variant: compact horizontal rows — a 280px side column can't wear
   the rail's vertical poster card (the poster inflates to full width) */
.x-rail-card-panel .x-rail-card {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  grid-template-rows: auto auto;
  column-gap: var(--space-3);
  row-gap: 2px;
  align-items: center;
}
.x-rail-card-panel .x-rail-card__poster {
  grid-row: 1 / 3;
  width: 44px;
  height: auto;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  border-radius: var(--radius-sm);
}
.x-rail-card-panel .x-rail-card__name {
  margin: 0;
  font-size: var(--fs-body-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.x-rail-card-panel .x-rail-card__meta {
  margin: 0;
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
}

/* ── zero state: hero 0 (already handled by hero num + deposit cta visible) ── */
/* "去逛市場" link on zero state */
.x-hero__market-link {
  font-size: var(--fs-body-sm);
  color: var(--text-tertiary);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  transition: color 150ms;
}
.x-hero__market-link:hover { color: var(--text-primary); }

/* ── sparkline path colours ── */
.x-spark-up   { stroke: var(--success-500); fill: none; }
.x-spark-down { stroke: var(--error-500);   fill: none; }
.x-spark-flat { stroke: var(--text-tertiary); fill: none; }

/* ════════════════════════════════════════════════════════════════
   63-x-portfolio.css — Ztor Exchange · portfolio.html + activity.html

   Shared atoms (.x-price, .x-delta, .x-pos-tag, .x-countdown) live
   in 60-x-market.css — NEVER redefined here.  All values are tokens;
   dimensions ride --ds-scale.  Mobile-first 390px → ≥768 → ≥1024.
   ════════════════════════════════════════════════════════════════ */

/* ── portfolio: page sections ── */
.x-portfolio-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
}

/* all-empty state: hero + one inline empty line show; section shells hide.
   (JS sets .is-all-empty on the root; the look is authored once here.) */
.x-portfolio-body.is-all-empty > section { display: none; }

/* inline all-empty line (Stake pattern) — quiet, left-aligned, no big icon */
.x-portfolio-empty {
  align-items: flex-start;
  text-align: left;
  padding: var(--space-2) 0 0;
}

/* total value hero RETIRED (L 2026-07-17): the portfolio total wears the same
   .x-value-card compact strip as the 市場 page (defined in 60-x-market.css) */

/* ── section head (shared title + count) ──
   Quiet section rhythm (§8): --fs-h4 title, space-8 above / space-4 below,
   no eyebrows, no decorations. The first section drops its top space (the
   flex gap from the hero already supplies it). */
.x-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin: var(--space-8) 0 var(--space-4);
}
section:first-of-type > .x-section-head { margin-top: 0; }
.x-section-head__title {
  margin: 0;
  font-size: var(--fs-h4);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}
.x-section-count {
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}

/* ── holdings grid ── */
.x-holdings-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}
@media (min-width: 768px) {
  .x-holdings-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
  .x-holdings-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ── holding card ── */
.x-holding-card {
  display: flex;
  flex-direction: column;
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  transition: border-color var(--dur-xs) var(--ease-out),
              transform var(--dur-xs) var(--ease-out);
}
.x-holding-card:hover { border-color: var(--border-default); }
.x-holding-card:active { transform: scale(0.99); }

.x-holding-card__poster-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 2 / 3;
  overflow: hidden;
}
.x-holding-card__poster {
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--bg-secondary);
  display: block;
}

/* on-chain badge stub */
.x-onchain-badge {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
  cursor: pointer;
  transition: border-color var(--dur-xs) var(--ease-out);
}
.x-onchain-badge:hover { border-color: var(--interactive-primary); }

.x-holding-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: calc(var(--space-4) * var(--ds-scale, 1));
}
.x-holding-card__title {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.x-holding-card__qty {
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
}

/* paid-vs-now mini delta row */
.x-holding-card__delta-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}
.x-holding-card__cost {
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}

/* inline sparkline */
.x-holding-spark {
  width: 100%;
  height: 28px;
  display: block;
}
.x-holding-spark path { fill: none; stroke-width: 1.5; }
.x-holding-spark--up   path { stroke: var(--success-500); }
.x-holding-spark--down path { stroke: var(--error-500); }
.x-holding-spark--flat path { stroke: var(--text-tertiary); }

/* quick sell button (secondary action inside card) */
.x-holding-card__sell-btn {
  display: block;
  width: 100%;
  padding: calc(var(--space-2) * var(--ds-scale, 1)) var(--space-3);
  border-top: 1px solid var(--border-subtle);
  background: none;
  border-left: 0; border-right: 0; border-bottom: 0;
  color: var(--text-secondary);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  text-align: center;
  min-height: 44px;
  transition: background var(--dur-xs) var(--ease-out), color var(--dur-xs) var(--ease-out);
}
.x-holding-card__sell-btn:hover { background: var(--bg-secondary); color: var(--text-primary); }
.x-holding-card__sell-btn:focus-visible {
  outline: 2px solid var(--interactive-primary);
  outline-offset: -2px;
}

/* ── futures positions section ── */
.x-positions-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.x-pos-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-4);
  padding: calc(var(--space-4) * var(--ds-scale, 1)) calc(var(--space-4) * var(--ds-scale, 1));
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  transition: border-color var(--dur-xs) var(--ease-out),
              transform var(--dur-xs) var(--ease-out);
}
.x-pos-card:hover  { border-color: var(--border-default); }
.x-pos-card:active { transform: scale(0.99); }
/* ── 期貨倉位 → same P2 anatomy as 份額收藏 (L 2026-07-17) ──
   Explicit stacked modifier: the base .x-pos-card grid above would win the
   cascade over 60-x-market's .x-row--holding, so the layout is restated here.
   Line1 reuses .x-row__hold-top (60); columns reuse .x-row__frame family. */
.x-pos-card--stacked {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  align-items: stretch;
}
.x-pos-card--stacked .x-pos-tag { margin-left: auto; flex: 0 0 auto; }
.x-pos-card--stacked .x-row__name { font-size: var(--fs-body-sm); }
.x-pos-card__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  gap: var(--space-2) var(--space-3);
  align-items: start;
}

.x-pos-card__thumb {
  width: calc(40px * var(--ds-scale, 1));
  height: calc(52px * var(--ds-scale, 1));
  object-fit: cover;
  border-radius: var(--radius-sm);
  background: var(--bg-secondary);
  flex-shrink: 0;
}
.x-pos-card__main { min-width: 0; }
.x-pos-card__title {
  margin: 0 0 var(--space-1);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.x-pos-card__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
}
.x-pos-card__right {
  text-align: right;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-1);
}
.x-pos-card__unrealized {
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}

/* result flash on settlement */
.x-pos-card--settling {
  animation: x-pos-settle 1.2s var(--ease-out) both;
}
@media (prefers-reduced-motion: no-preference) {
  @keyframes x-pos-settle {
    0%   { background: var(--bg-card); }
    25%  { background: color-mix(in srgb, var(--success-500) 15%, var(--bg-card)); }
    100% { background: var(--bg-card); }
  }
}

/* ── settled history accordion ── */
.x-settled-toggle {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  background: none;
  border: 0;
  color: var(--text-tertiary);
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: color var(--dur-xs) var(--ease-out);
}
.x-settled-toggle:hover { color: var(--text-secondary); }
.x-settled-toggle__arrow {
  display: inline-block;
  transition: transform var(--dur-xs) var(--ease-out);
}
.x-settled-toggle[aria-expanded='true'] .x-settled-toggle__arrow { transform: rotate(90deg); }

.x-settled-body[hidden] { display: none; }
.x-settled-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-top: var(--space-2);
}

/* settled history rows */
.x-settled-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border-subtle);
  text-decoration: none;
  color: inherit;
}
.x-settled-row:last-child { border-bottom: 0; }
.x-settled-row__label {
  flex: 1;
  min-width: 0;
  font-size: var(--fs-caption);
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.x-settled-row__result {
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  white-space: nowrap;
}
.x-settled-row__result--win  { color: var(--success-500); }
.x-settled-row__result--lose { color: var(--error-500); }
.x-settled-row__result--tie  { color: var(--text-tertiary); }

/* ── activity page ── */
.x-feed-layout {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

/* tab panel visibility */
.x-feed-panel[hidden] { display: none; }
.x-feed-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* trade / feed rows */
.x-feed-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-3);
  min-height: calc(64px * var(--ds-scale, 1));
  padding: calc(var(--space-2) * var(--ds-scale, 1)) var(--space-2);
  border-bottom: 1px solid var(--border-subtle);
  text-decoration: none;
  color: inherit;
  border-radius: var(--radius-sm);
  transition: background var(--dur-xs) var(--ease-out);
}
.x-feed-row:last-child { border-bottom: 0; }
.x-feed-row:hover { background: var(--bg-secondary); }
.x-feed-row:active { transform: scale(0.99); }

.x-feed-row__icon {
  font-size: var(--fs-body-lg);
  line-height: 1;
  width: calc(36px * var(--ds-scale, 1));
  height: calc(36px * var(--ds-scale, 1));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--bg-secondary);
  flex-shrink: 0;
}

/* buy/sell side tags */
.x-side-tag {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  font-size: var(--fs-caption);
  font-weight: var(--fw-bold);
  border: 1px solid transparent;
}
.x-side-tag--buy  {
  color: var(--success-500);
  border-color: var(--success-500);
  background: color-mix(in srgb, var(--success-500) 12%, transparent);
}
.x-side-tag--sell {
  color: var(--error-500);
  border-color: var(--error-500);
  background: color-mix(in srgb, var(--error-500) 12%, transparent);
}

.x-feed-row__main { min-width: 0; }
.x-feed-row__title {
  margin: 0 0 2px;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.x-feed-row__meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
  flex-wrap: wrap;
}
/* time reads as the quietest datum, pushed to the trailing edge of the meta line */
.x-feed-row__meta time {
  margin-left: auto;
  color: var(--text-disabled);
}
.x-feed-row__right {
  text-align: right;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
}
.x-feed-row__amount {
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  white-space: nowrap;
}
.x-feed-row__status {
  font-size: var(--fs-caption);
  color: var(--success-500);
}

/* load-more button */
.x-load-more {
  display: block;
  width: 100%;
  padding: var(--space-3) var(--space-4);
  margin-top: var(--space-4);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
  background: none;
  color: var(--text-secondary);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  text-align: center;
  min-height: 44px;
  transition: background var(--dur-xs) var(--ease-out), color var(--dur-xs) var(--ease-out);
}
.x-load-more:hover { background: var(--bg-secondary); color: var(--text-primary); }
.x-load-more[hidden] { display: none; }

/* exchange tab switcher — .x-tabs, OWN class. (Was shipped as an unscoped
   .feed-tabs redefinition with different child classes than the real
   feed-tabs component — renamed 2026-07-15 to kill the cross-app bleed.) */
.x-tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--border-subtle);
  margin-bottom: var(--space-4);
}
.x-tabs__item {
  flex: 1;
  padding: var(--space-3) var(--space-2);
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  color: var(--text-tertiary);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  text-align: center;
  transition: color var(--dur-xs) var(--ease-out),
              border-color var(--dur-xs) var(--ease-out);
}
.x-tabs__item.is-active,
.x-tabs__item[aria-selected='true'] {
  color: var(--text-primary);
  border-bottom-color: var(--interactive-primary);
}

/* ── aria-live toast for stubs ── */
.x-stub-toast {
  position: fixed;
  bottom: calc(var(--space-10) + var(--space-16));
  left: 50%;
  transform: translateX(-50%) translateY(8px);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-pill);
  background: var(--bg-card);
  border: 1px solid var(--border-default);
  color: var(--text-primary);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  pointer-events: none;
  opacity: 0;
  z-index: var(--z-toast, 900);
  transition: opacity var(--dur-sm) var(--ease-out),
              transform var(--dur-sm) var(--ease-out);
}
.x-stub-toast.is-visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* ════════════════════════════════════════════════════════════════
   64-x-onboard.css — Ztor Exchange · onboarding surfaces

   Covers:
     - welcome.html   (.x-welcome-*)   3-step intro cards
     - how-to-play.html (.x-howto-*)   6-section doc + animated demo chart
     - x-tour overlay  (.x-tour-*)    spotlight tour component

   All values are tokens; dimensions ride --ds-scale.
   Zero hex literals — all colour via tokens.
   ════════════════════════════════════════════════════════════════ */

/* ──────────────────────────────────────────────────────────────
   WELCOME PAGE — full-height, airy, stepped card intro
   ────────────────────────────────────────────────────────────── */

.x-welcome-page {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--bg-page);
}

.x-welcome-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4) var(--space-6);
  flex-shrink: 0;
}

.x-welcome-logo {
  height: calc(28px * var(--ds-scale, 1));
  display: block;
}

.x-welcome-skip {
  background: none;
  border: none;
  color: var(--text-tertiary);
  font-size: var(--fs-body-sm);
  cursor: pointer;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  transition: color var(--dur-xs) var(--ease-out), background var(--dur-xs) var(--ease-out);
  min-height: 44px;
}
.x-welcome-skip:hover { color: var(--text-primary); background: var(--border-subtle); }

/* card stage — clipping box for slide transitions */
.x-welcome-stage {
  flex: 1;
  overflow: hidden;
  position: relative;
}

/* single card */
.x-welcome-card {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-6);
  padding: var(--space-6) var(--space-6) var(--space-4);
  text-align: center;
  /* initial hidden state — GSAP drives opacity + x */
  opacity: 0;
  pointer-events: none;
}
.x-welcome-card.is-active {
  opacity: 1;
  pointer-events: auto;
}

.x-welcome-card__poster {
  max-height: 40vh;
  width: auto;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  flex-shrink: 0;
  margin-inline: auto;
}

.x-welcome-card__icon {
  font-size: calc(72px * var(--ds-scale, 1));
  line-height: 1;
  flex-shrink: 0;
  /* visual balance against the poster height */
  display: flex;
  align-items: center;
  justify-content: center;
  height: calc(160px * var(--ds-scale, 1) * 1.5);
}

.x-welcome-card__body { max-width: 320px; }

.x-welcome-card__title {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-h2);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  line-height: 1.2;
}

.x-welcome-card__desc {
  margin: 0;
  font-size: var(--fs-body);
  color: var(--text-secondary);
  line-height: 1.55;
}

/* step dots */
.x-step-dots {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  justify-content: center;
  padding: var(--space-2) 0;
  flex-shrink: 0;
}

.x-step-dot {
  width: calc(8px * var(--ds-scale, 1));
  height: calc(8px * var(--ds-scale, 1));
  border-radius: var(--radius-pill);
  background: var(--border-default);
  transition: background var(--dur-sm) var(--ease-out),
              width var(--dur-sm) var(--ease-out);
  flex-shrink: 0;
}
.x-step-dot.is-active {
  background: var(--interactive-primary);
  width: calc(20px * var(--ds-scale, 1));
}

/* footer CTA area */
.x-welcome-foot {
  padding: var(--space-4) var(--space-6) calc(var(--space-6) + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  flex-shrink: 0;
}

.x-welcome-cta {
  width: 100%;
  padding: calc(var(--space-4) * var(--ds-scale, 1)) var(--space-6);
  border-radius: var(--radius-pill);
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  cursor: pointer;
  transition: transform var(--dur-xs) var(--ease-out), opacity var(--dur-xs) var(--ease-out);
  border: none;
  min-height: 52px;
}
.x-welcome-cta:active { transform: scale(0.98); }

/* primary = filled yellow */
.x-welcome-cta--primary {
  background: var(--interactive-primary);
  color: var(--bg-page);
}
.x-welcome-cta--primary:hover { opacity: 0.9; }

/* ghost = outline */
.x-welcome-cta--ghost {
  background: none;
  border: 1px solid var(--border-default);
  color: var(--text-secondary);
}
.x-welcome-cta--ghost:hover { background: var(--border-subtle); }

/* desktop: centre the welcome at comfortable width */
@media (min-width: 768px) {
  .x-welcome-page {
    align-items: center;
  }
  .x-welcome-header,
  .x-welcome-foot {
    width: 100%;
    max-width: 480px;
  }
  .x-welcome-stage {
    width: 100%;
    max-width: 480px;
    flex: 0 0 520px;
  }
}


/* ──────────────────────────────────────────────────────────────
   HOW-TO-PLAY PAGE — two-column doc at ≥1024px; scroll reveal
   ────────────────────────────────────────────────────────────── */

.x-howto-page {
  max-width: 860px;
  margin: 0 auto;
  padding: 0 var(--space-4) var(--space-16);
}

/* section reveal: .x-reveal starts invisible; .x-reveal.is-visible animates in */
.x-reveal {
  opacity: 0;
  transform: translateY(var(--space-5));
  transition: opacity var(--dur-md) var(--ease-out),
              transform var(--dur-md) var(--ease-out);
}
.x-reveal.is-visible {
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  .x-reveal, .x-reveal.is-visible {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* main section block */
.x-howto-section {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
  padding: var(--space-8) 0;
  border-bottom: 1px solid var(--border-subtle);
}
.x-howto-section:last-of-type { border-bottom: 0; }

@media (min-width: 1024px) {
  .x-howto-section {
    grid-template-columns: minmax(0, 1fr) calc(280px * var(--ds-scale, 1));
    align-items: center;
    gap: var(--space-10);
  }
  .x-howto-section--reverse {
    grid-template-columns: calc(280px * var(--ds-scale, 1)) minmax(0, 1fr);
  }
  .x-howto-section--reverse .x-howto-visual { order: -1; }
}

/* section number eyebrow */
.x-howto-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin: 0 0 var(--space-2);
}

.x-howto-title {
  margin: 0 0 var(--space-3);
  font-size: var(--fs-h3);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  line-height: 1.25;
}

.x-howto-body {
  margin: 0;
  font-size: var(--fs-body);
  color: var(--text-secondary);
  line-height: 1.65;
}
.x-howto-body + .x-howto-body {
  margin-top: var(--space-3);
}

/* force-arrows labels inside the demo chart */
.x-howto-force {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-4);
  flex-wrap: wrap;
}
.x-howto-force-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
  color: var(--text-secondary);
}
.x-howto-force-tag--anchor { color: var(--interactive-primary); border-color: currentColor; }
.x-howto-force-tag--pressure { color: var(--success-500); border-color: currentColor; }

/* visual panel */
.x-howto-visual {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* demo chart SVG container */
.x-demo-chart {
  width: 100%;
  max-width: 280px;
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  box-sizing: border-box;
}

.x-demo-chart svg {
  width: 100%;
  height: auto;
  display: block;
  overflow: visible;
}

/* trade step illustration */
.x-howto-steps {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  width: 100%;
  max-width: 280px;
}
.x-howto-step {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
}
.x-howto-step__num {
  flex-shrink: 0;
  width: calc(28px * var(--ds-scale, 1));
  height: calc(28px * var(--ds-scale, 1));
  border-radius: var(--radius-pill);
  background: var(--interactive-primary);
  color: var(--bg-page);
  font-size: var(--fs-caption);
  font-weight: var(--fw-bold);
  display: flex;
  align-items: center;
  justify-content: center;
}

/* futures illustration pill */
.x-howto-fut-card {
  width: 100%;
  max-width: 280px;
  padding: var(--space-5);
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.x-howto-fut-card__q {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}
.x-howto-fut-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-3);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-default);
  background: var(--bg-secondary);
  font-size: var(--fs-caption);
  font-weight: var(--fw-bold);
}
.x-howto-fut-btn__label { font-size: var(--fs-body-sm); }
.x-howto-fut-btn__price {
  font-size: var(--fs-h4);
  font-variant-numeric: tabular-nums;
}
.x-howto-fut-btn--up .x-howto-fut-btn__price { color: var(--success-500); }
.x-howto-fut-btn--down .x-howto-fut-btn__price { color: var(--error-500); }

.x-howto-disclosure {
  font-size: var(--fs-caption);
  color: var(--text-tertiary);
  background: var(--bg-secondary);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
  margin-top: var(--space-2);
}

/* popcorn link visual */
.x-howto-popcorn-visual {
  font-size: calc(64px * var(--ds-scale, 1));
  line-height: 1;
  text-align: center;
}

/* FAQ accordion — native <details> styled */
.x-faq { display: flex; flex-direction: column; gap: var(--space-2); width: 100%; }
.x-faq-item {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.x-faq-item summary {
  list-style: none;
  padding: var(--space-4) var(--space-5);
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  min-height: 44px;
  user-select: none;
  transition: background var(--dur-xs) var(--ease-out);
}
.x-faq-item summary:hover { background: var(--border-subtle); }
.x-faq-item summary::-webkit-details-marker { display: none; }
.x-faq-item summary::after {
  content: '+';
  font-size: var(--fs-h4);
  color: var(--text-tertiary);
  flex-shrink: 0;
  transition: transform var(--dur-xs) var(--ease-out);
}
.x-faq-item[open] summary::after {
  transform: rotate(45deg);
}
.x-faq-item__body {
  padding: 0 var(--space-5) var(--space-4);
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
  line-height: 1.65;
}

/* howto page CTA footer */
.x-howto-cta-row {
  display: flex;
  justify-content: center;
  padding: var(--space-10) 0 var(--space-4);
}


/* ──────────────────────────────────────────────────────────────
   SPOTLIGHT TOUR — fixed overlay + SVG mask
   ────────────────────────────────────────────────────────────── */

/* outer fixed overlay — contains SVG scrim + card + arrow */
.x-tour {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  pointer-events: none;           /* children opt-in */
  display: none;
}
.x-tour.is-active { display: block; }

/* SVG scrim: full-viewport, carries the cut-out mask.
   backdrop-filter is intentionally OMITTED here — applying it at the
   wrapper level would blur the spotlit target element too (blur does not
   respect SVG clip/mask boundaries). The SVG tint rect at 0.82 opacity
   provides sufficient dimming without blurring the featured element.
   transform: translateZ(0) is kept for GPU compositing. */
.x-tour__svg-wrap {
  position: absolute;
  inset: 0;
  pointer-events: all;            /* blocks clicks through scrim */
  transform: translateZ(0);
}
/* the actual tint is rendered via the SVG rect fill so we can cut the mask */
.x-tour__svg-wrap svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* the spotlight TARGET PASS-THROUGH — steps ③ and ④ let the
   spotlit element receive pointer events. We elevate the target
   element to z-index above the scrim via a class toggled by x-tour.js */
.x-tour-target-live {
  position: relative;
  z-index: calc(var(--z-modal) + 1);
  pointer-events: auto !important;
}

/* card + arrow cluster: absolutely positioned relative to .x-tour */
.x-tour__card-wrap {
  position: absolute;
  pointer-events: all;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: calc(220px * var(--ds-scale, 1));
  max-width: calc(320px * var(--ds-scale, 1));
}

/* floating arrow (GSAP animates y for the float loop) */
.x-tour__arrow {
  font-size: calc(28px * var(--ds-scale, 1));
  line-height: 1;
  text-align: center;
  color: var(--interactive-primary);
  filter: drop-shadow(0 2px 6px var(--shadow-color, rgba(0,0,0,.4)));
  flex-shrink: 0;
  /* default points down; JS rotates via data-arrow-dir */
}
.x-tour__arrow[data-dir="up"]    { transform: rotate(180deg); }
.x-tour__arrow[data-dir="left"]  { transform: rotate(90deg); }
.x-tour__arrow[data-dir="right"] { transform: rotate(-90deg); }

/* info card */
.x-tour__card {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.x-tour__card-text {
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
  line-height: 1.6;
  margin: 0;
}

.x-tour__card-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.x-tour__dots {
  display: flex;
  gap: var(--space-1);
  align-items: center;
}
.x-tour__dot {
  width: 6px; height: 6px;
  border-radius: var(--radius-pill);
  background: var(--border-default);
  transition: background var(--dur-xs) var(--ease-out);
  flex-shrink: 0;
}
.x-tour__dot.is-active { background: var(--interactive-primary); }

.x-tour__actions {
  display: flex;
  gap: var(--space-2);
}

.x-tour__btn {
  border: none;
  border-radius: var(--radius-pill);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  padding: var(--space-2) var(--space-4);
  min-height: 36px;
  transition: opacity var(--dur-xs) var(--ease-out);
}
.x-tour__btn:hover { opacity: 0.8; }

.x-tour__btn--next {
  background: var(--interactive-primary);
  color: var(--bg-page);
}
.x-tour__btn--skip {
  background: none;
  color: var(--text-tertiary);
  border: 1px solid var(--border-default);
}

@media (prefers-reduced-motion: reduce) {
  .x-tour__arrow { animation: none !important; }
  .x-welcome-card { transition: none !important; }
  .x-reveal { transition: none !important; }
}


/* ── temporary feature flags — load LAST so they win ── */
/* ════════════════════════════════════════════════════════════════
   50-feature-flags.css — temporary, visual-only feature toggles.
   Loaded LAST so these win the cascade. Delete a block to re-enable a
   feature; the underlying pages/code are always retained.
   ════════════════════════════════════════════════════════════════ */

/* NEWS — paused 2026-06-26 (L). Hide every entry point site-wide (side rail,
   header nav, mobile drawer, footer, cards). news.html + assets/news-* are
   kept intact. Re-enable: delete this block and rebuild the CSS. */
a[href="news.html"],
a[href$="/news.html"] { display: none !important; }

