/* ╔══════════════════════════════════════════════════════════════════╗
   ║  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)
   ============================================================ */

/* ─────────────────────────────────────────
   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); }

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

/* ── Sizes ── */
.btn--lg {
  padding: 16px 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: 12px 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: 8px 16px;
  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. LG adds soft top highlight (4px inner shadow).
   Hover: + outer yellow glow 24px. Pressed: glow 40px (no highlight).
*/
.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 (Artlist-style glass) ──
   background = bottom-edge bright stripe gradient
   box-shadow = top + bottom edge highlights + inner soft shadow
   text-shadow = subtle text glow
*/
.btn--ghost {
  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%);
  border-color: transparent;
  color: var(--text-primary);
  text-shadow: none;
  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);
}

.btn--ghost:hover:not(:disabled) {
  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%);
  text-shadow: rgba(255, 255, 255, 0.45) 0 0 10px;
}

.btn--ghost:active:not(:disabled) {
  background: linear-gradient(0deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.42) 2.45%,
    rgba(255, 255, 255, 0) 126.14%);
  box-shadow:
    rgba(255, 255, 255, 0.6) 1.1px 2.2px 0.5px -1.8px inset,
    rgba(255, 255, 255, 0.6) -1px -2.2px 0.5px -1.8px inset,
    rgba(0, 0, 0, 0.20) 2px 3px 4px 0 inset;
}

.btn--ghost:disabled {
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-disabled);
  text-shadow: none;
  box-shadow:
    rgba(255, 255, 255, 0.25) 1.1px 2.2px 0.5px -1.8px inset,
    rgba(255, 255, 255, 0.25) -1px -2.2px 0.5px -1.8px inset;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* ── YELLOW-GHOST ── */
.btn--yellow-ghost {
  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%);
  border-color: transparent;
  color: var(--yellow-500);
  text-shadow: none;
  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;
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}

.btn--yellow-ghost:hover:not(:disabled) {
  background: linear-gradient(0deg,
    rgba(255, 163, 63, 0) 0%,
    rgba(255, 163, 63, 0.30) 2.45%,
    rgba(255, 163, 63, 0) 126.14%);
  text-shadow: rgba(255, 163, 63, 0.4) 0 0 10px;
}

.btn--yellow-ghost:active:not(:disabled) {
  background: linear-gradient(0deg,
    rgba(255, 163, 63, 0) 0%,
    rgba(255, 163, 63, 0.42) 2.45%,
    rgba(255, 163, 63, 0) 126.14%);
  box-shadow:
    rgba(255, 163, 63, 0.6) 1.1px 2.2px 0.5px -1.8px inset,
    rgba(255, 163, 63, 0.6) -1px -2.2px 0.5px -1.8px inset,
    rgba(0, 0, 0, 0.20) 2px 3px 4px 0 inset;
}

.btn--yellow-ghost:disabled {
  background: rgba(255, 163, 63, 0.04);
  color: rgba(255, 163, 63, 0.4);
  text-shadow: none;
  box-shadow:
    rgba(255, 163, 63, 0.3) 1.1px 2.2px 0.5px -1.8px inset,
    rgba(255, 163, 63, 0.3) -1px -2.2px 0.5px -1.8px inset;
  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;
}

/* ─────────────────────────────────────────
   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: 8px;
  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: 400;
  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: 80px;
  align-items: center;
}

.ai-mosaic__copy {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.ai-mosaic__ctas {
  display: flex;
  gap: 12px;
  margin-top: 12px;
  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: blur(22px) saturate(1.35);
          backdrop-filter: blur(22px) saturate(1.35);
  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: 8px;
}

.ai-mosaic__tile-status {
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.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: 24px;
}
/* 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; } }

/* ─────────────────────────────────────────
   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: 24px;
}
.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; } }

/* ─────────────────────────────────────────
   CO-CREATION CARD / GRID
   3-up cards for 影視共創計畫 section.
   Card structure: 16:9 hero image → badge → title → desc → metrics block
   ───────────────────────────────────────── */

/* ─────────────────────────────────────────
   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: 100;
  height: var(--header-height);
  background: rgba(10, 10, 10, 0.72);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  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;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  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%);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  -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 only appears once scrolled — at page top everything is crisp.
   Scroll-driven, no JS; browsers without scroll-timeline keep it on. */
@supports (animation-timeline: scroll()) {
  body.has-rail .header::before {
    animation: header-veil-in linear both;
    animation-timeline: scroll(root);
    animation-range: 0 120px;
  }
}
@keyframes header-veil-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
body.has-rail:has(.community-tabbar) .header::before {
  inset: 0;
  background: rgba(10, 10, 10, 0.82);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  -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('assets/icons/search.svg');" */
.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;
}

/* ── 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, Artlist-style glass).
   Renders as <a> on real pages so the whole pill navigates to /popcorn. */
.popcorn-section {
  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: 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: background 150ms ease;
}

.popcorn-section: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%);
}

.popcorn-section:hover .popcorn-section__label {
  text-shadow: rgba(255, 255, 255, 1) 0 0 10px;
}

.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: 24px;
  height: 24px;
  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 16px;
}

.popcorn-section__amount {
  font-family: var(--font-latin);
  font-size: 16px;
  line-height: 27px;
  font-weight: var(--fw-medium);
  color: #eae9f7;
  text-align: right;
  min-width: 40px;
}

.popcorn-section__deposit {
  width: 32px;
  height: 32px;
  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(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--radius-md);
  z-index: 200;
  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 (260 wide) ─── */
.header-dropdown--lang {
  width: 260px;
}

.lang-dropdown__item {
  display: flex;
  align-items: center;
  gap: 4px;
  width: 100%;
  padding: 16px 8px 16px 16px;
  background: transparent;
  border: none;
  font-family: var(--font-cjk-display);
  font-size: 16px;
  line-height: 24px;
  color: var(--text-secondary);
  text-align: left;
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

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

.lang-dropdown__item--active,
.lang-dropdown__item--active:hover {
  background: rgba(255, 163, 63, 0.24);
  color: var(--yellow-500);
}

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

.lang-dropdown__check {
  width: 24px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--yellow-500);
  flex-shrink: 0;
}

/* ─── 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: blur(28px) saturate(1.4);
  -webkit-backdrop-filter: blur(28px) saturate(1.4);
}

/* 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: 300;
  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(12px);
  -webkit-backdrop-filter: blur(12px);
  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 320ms cubic-bezier(0.4, 0, 0.2, 1),
              box-shadow 320ms cubic-bezier(0.4, 0, 0.2, 1);
}
.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: 300;
  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(12px);
  -webkit-backdrop-filter: blur(12px);
  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: 32px;
  align-items: flex-start;
  padding: 8px 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: 12px; }
.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: 24px;
  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: 4px;
}
.profile-head__online {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 12px 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: 16px 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: 4px;
  border-bottom: 1px solid var(--border-subtle);
}
.profile-tab {
  position: relative;
  padding: 14px 20px;
  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: 8px;
  margin-bottom: 12px;
}
.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 16px;
  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: 20px;
}
.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: 9999;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 16px;
  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); }
/* ─── /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: 12px 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: 4px 2px 16px;
  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 24px 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: 24px;
  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));
}

/* ─────────────────────────────────────────
   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: 4px;
  padding: 4px 8px 4px 6px;
  border-radius: var(--radius-pill);
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  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: 4px 10px;
  border-radius: var(--radius-pill);
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  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: 12px 4px 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: 8px;
  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: 56px;   /* 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);
}

/* ─────────────────────────────────────────
   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: 24px;
  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: 12px;
  padding: 16px 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: 8px;
  padding: 12px 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: 12px;
}

.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: 4px;
  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: 4px;
  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: 24px;
}
@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: 24px;
  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: 80px 18px 18px;            /* top ~80px = the blur dissolve zone over the photo */
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.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);
}

/* ─────────────────────────────────────────
   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: 20px;
}

/* 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: 20px;
  background: rgba(10, 10, 10, 0.82);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border-subtle);
}
/* 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: 32px;
  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 24px;
}
.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: 20px;
  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: 24px;
  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: 12px;
  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: 16px 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: 4px;
  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: 20px;
}
.post-card__stat,
.post-card__bookmark {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px;
  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; }

@media (max-width: 768px) {
  .post-card__title { font-size: 18px; }
  .feed-tab { padding: 10px 12px; }
}

/* ─────────────────────────────────────────
   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: 24px;
}

/* 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: 64px 26px 26px;  /* tall top padding IS the dissolve zone */
  gap: 12px;
}
/* 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: blur(22px) saturate(1.35);
  -webkit-backdrop-filter: blur(22px) saturate(1.35);
  -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: 20px 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: 4px 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: 8px;
  padding: 6px 12px;
  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: 12px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  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: 16px;
}
.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 16px 14px 0;
}

/* ─────────────────────────────────────────
   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: 24px;
}

.activity-card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 32px;
  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: 24px;
}

/* 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 12px;
}

.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: 24px;
  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: 24px;
}
.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: 16px;
}
.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;
}

/* ─────────────────────────────────────────
   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(64px, 8vw, 96px) 0 32px;
  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: 48px;
}

/* 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: 20px;
  margin-top: 4px;
}

.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: 12px;
}

.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: 56px 0 24px;
}

.site-footer__legal-chain {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 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: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  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('assets/icons/language.svg') center / contain no-repeat;
          mask: url('assets/icons/language.svg') 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;
}

/* ─────────────────────────────────────────
   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 96px;
  display: flex;
  flex-direction: column;
  gap: 80px;
}

.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 32px;
}

/* ── 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: 16px;
  padding: 24px 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: 8px;
}

.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: 20px;
  max-width: 720px;
  width: 100%;
  margin: 0 auto;
}

.popcorn-package {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 36px 24px 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: 4px;
}

.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: 12px;
  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 12px;
}

.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: 768px) {
  .popcorn-page { padding-block: 48px 64px; gap: 56px; }
  .popcorn-page__heading { font-size: var(--fs-h3); margin-bottom: 20px; }
  .popcorn-packages { grid-template-columns: 1fr; gap: 14px; }
  .popcorn-balance-card__row { padding: 18px 20px; }
}

/* 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: 32px; }

/* ─────────────────────────────────────────
   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: 16px;
  padding: 16px 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-h3);
  line-height: var(--lh-h3);
  letter-spacing: -0.5px;
  color: var(--text-primary);
}

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

.page-head__info {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  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;
}

/* ─────────────────────────────────────────
   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%;
  /* Figma spacing: 12px below the title row, 24px above the card grid. */
  margin: 12px 0 24px;
  padding: 4px;
  background: var(--white-alpha-4);
  border: 1px solid var(--neutral-700);
  border-radius: var(--radius-pill);
  overflow-x: auto;
  scrollbar-width: none;
}
.glass-tabs::-webkit-scrollbar { display: none; }

.glass-tabs__item {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  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;
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}

/* 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);
}

@media (max-width: 768px) {
  .page-head { flex-wrap: wrap; gap: 8px; padding: 8px 0; }
  .glass-tabs { width: 100%; }
}

/* ─────────────────────────────────────────
   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: 4px;
  align-self: flex-start;
  padding: 6px 12px;
  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: 8px;
  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: 40px;
}
@media (max-width: 1080px) {
  .news-list--grid { column-gap: 24px; }
}
/* 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: 20px; }

@media (max-width: 768px) {
  .news-list--grid { grid-template-columns: 1fr; column-gap: 0; }
  .news-list--grid > .news-card--list:first-child { padding-top: 0; }
}

/* ─────────────────────────────────────────
   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: 64px; }

/* Tabs row: glass-tabs on the left, an action (我的投票) pinned right. */
.tabs-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

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

.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: 8px;
  padding: 12px 12px 16px;
  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: 24px;
  padding: 12px;
  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: 24px;
}

.work-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  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: #0a0a0a;
}

/* 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: 8px;
}
.work-card__creator {
  display: flex;
  align-items: center;
  gap: 8px;
  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 { flex-wrap: wrap; }
}

/* ─────────────────────────────────────────
   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: 24px; }

/* Top bar: 放映廳/資料庫 toggle, sitting left. */
.library-topbar {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 24px;
}
/* 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: 8px;
  width: 343px;
  max-width: 100%;
  height: 44px;
  padding: 0 16px;
  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('assets/icons/search.svg') center / contain no-repeat;
  mask: url('assets/icons/search.svg') 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 16px;
  padding: 0;
  background: none;
  border: none;
  border-radius: 0;
  gap: 4px;
}
.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: 8px;
  padding-left: 8px;
  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: 16px;
}
.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: 40px; }
/* Category title sits tight above its rail. */
.library-row .page-head { margin-bottom: 8px; }

/* 繼續觀看 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: 4px 8px;
  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: 768px) {
  .library-topbar { flex-wrap: wrap; }
  .library-search { width: 100%; }
}

/* ─────────────────────────────────────────
   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);
  }
}

/* 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: 40px;                 /* room for the oversized rank digits */
  overflow: visible;             /* rank "1" overhang no longer clipped */
  scroll-snap-type: none;
  margin-left: 0;
  padding-left: 24px;            /* 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: 768px) {
  section[data-screen-label="ranking"] .films-rail__track--ranked {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* ─────────────────────────────────────────
   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: 768px) {

  /* 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: 16px; }
  .header__actions { gap: 12px; }

  /* Section rhythm — smaller vertical padding */
  .section { padding-block: clamp(48px, 12vw, 72px); }

  /* 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: 16px;
  }

  /* Hero carousel — shorter, scaled text */
  .hero-carousel { height: clamp(420px, 70vw, 560px); }
  .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; }

  /* 影視共創 — 3 col → 1 col */
  .cocreation-grid { grid-template-columns: 1fr; gap: 20px; }

  /* 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: 48px; }

  /* 社群 — 3 col → 1 col */
  .community-grid { grid-template-columns: 1fr; gap: 20px; }

  /* 新聞 — 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: 20px; }
  .news-card--list { gap: 14px; padding: 16px 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: 16px; }
  .activity-card { min-height: auto; padding: 24px; }

  /* Footer — 5 col → 1 col stack */
  .site-footer { padding: 56px 0 24px; }
  .site-footer__top { grid-template-columns: 1fr; gap: 28px; }
  .site-footer__brand { max-width: 100%; }
  .site-footer__divider { margin: 32px 0 20px; }
  .site-footer__bottom-row {
    flex-direction: column;
    align-items: flex-start;
    gap: 16px;
  }
  .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: 100px; }

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 {
  margin-left: calc((var(--rail-w) - 72px) / 2 + 23px - var(--container-padding-x));
}
/* The icon-button search is absorbed by the persistent field */
body.has-rail .header__icon-btn[aria-label="搜尋"] { 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('assets/icons/search.svg') center / contain no-repeat;
          mask: url('assets/icons/search.svg') 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; }

/* Centre the search field's AXIS on the CONTENT column, not the viewport
   (design call 2026-06-18: centre-alignment chosen over keeping the field
   a full 720 wide). The header spans edge to edge while the body content
   is pushed right by the rail, so the centred content — e.g. the logged-in
   community feed, max-width 720 — sits at 50vw + rail/2 (= 770px @1440).
   Plain flex centring would land the field on 50vw, visibly left of that.

   We pin the field to the content axis with absolute positioning, then cap
   its width so it never collides with the right-hand utility cluster
   (~322px wide in both auth states). Width = viewport − 2·(header pad 48 +
   utilities 322 + clearance 35 + rail/2 50) = 100vw − 910px, capped at the
   original 720 on wide decks (≥~1630). The side clusters keep their natural
   flex — logo hugs left, utilities hug right. */
body.has-rail .header__inner { position: relative; }
body.has-rail .header-search {
  position: absolute;
  left: calc(50% + var(--rail-w) / 2);
  top: 50%;
  transform: translate(-50%, -50%);
  margin: 0;
  width: min(720px, 100vw - 910px);
}

.side-rail {
  position: fixed;
  inset: var(--header-height) auto 0 0;
  width: var(--rail-w);
  z-index: 90;  /* beneath the full-width header (100) */
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 24px 0 24px;   /* 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: 12px;
  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: 6px;
  width: 72px;
  padding: 11px 0 9px;
  border-radius: 10px;
  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: -14px;             /* item is inset (100−72)/2 = 14px; bar hugs the rail edge */
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 42px;
  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, 26px);
  height: var(--icon-size, 26px);
  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: 13px;
  line-height: 1;
  letter-spacing: 0.04em;
}

/* 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: 14px;
  width: calc(100% - 24px);
  display: flex;
  justify-content: center;
  padding-top: 16px;
  border-top: 1px solid rgba(255, 255, 255, 0.10);
}
.side-rail__post {
  width: 40px;
  height: 40px;
  padding: 0;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.side-rail__post .fab__icon { width: 18px; height: 18px; }

@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%); }
}

/* ─────────────────────────────────────────
   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.
   ───────────────────────────────────────── */

/* spacing util kept for the type-switcher rows */
.glass-tabs--push { margin-top: 36px; }

/* PLP head — quiet, never a hero */
.shop-head {
  display: flex;
  align-items: baseline;
  gap: 16px;
  margin-top: 8px;
}
.shop-head__title {
  font-family: var(--font-cjk-display);
  font-size: 20px;
  font-weight: var(--fw-bold);
  letter-spacing: 0.02em;
}
.shop-head__desc {
  font-size: 13px;
  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: 24px;
  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: 16px;
  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: 4px;
  padding-top: 12px;
}
.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: 8px;
  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)); }

/* ─────────────────────────────────────────
   CREATOR DIRECTORY — chromeless portrait cards
   ───────────────────────────────────────── */
.creator-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: 16px;
  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: 4px;
  padding-top: 12px;
}
.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; }

/* ─────────────────────────────────────────
   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: 8px;
  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: 4px;
}
.creator-hero__social { display: inline-flex; gap: 8px; }
.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: 16px;
  margin-top: 64px;
  padding-bottom: 12px;
  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;
}
.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: 8px; 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: 12px; }
.creator-event {
  display: flex;
  align-items: baseline;
  gap: 24px;
  padding: 16px 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: 20px;
  color: var(--text-secondary);
  line-height: 1.9;
  font-size: var(--fs-body-sm);
}

/* ─────────────────────────────────────────
   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: 24px;
  overflow-x: auto;
  scrollbar-width: none;
  padding: 24px 2px 8px;
}
.top-fans::-webkit-scrollbar { display: none; }
.top-fan {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  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;
}

/* ─────────────────────────────────────────
   LEADERBOARD — podium → table → sticky my-rank
   ───────────────────────────────────────── */
.lb-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-top: 8px;
}
.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: 16px;
  margin-top: 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: 56px;
  margin: 56px 0 16px;
}
.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: 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(760px, calc(100vw - 120px));
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 11px 18px;
  border-radius: var(--radius-lg);
  background: rgba(18, 18, 18, 0.86);
  border: 1px solid var(--border-default);
  box-shadow: 0 14px 44px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
  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: 15px;
  color: var(--yellow-500);
  min-width: 32px;
  text-align: center;
  flex-shrink: 0;
}
.lb-me .lb-row__avatar,
.lb-me .lb-row__avatar img { width: 36px; height: 36px; border-radius: 50%; flex-shrink: 0; }
.lb-me .lb-row__avatar img { object-fit: cover; display: block; }
.lb-me__id { display: inline-flex; align-items: center; gap: 8px; min-width: 0; flex-shrink: 0; }
.lb-me__id .lb-row__nick { font-weight: var(--fw-medium); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 9em; }
.lb-me__climb { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.lb-me__climb-top { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.lb-me__climb-label { font-family: var(--font-cjk-text); font-size: 12.5px; color: var(--text-secondary); white-space: nowrap; }
.lb-me__climb-label b { color: var(--yellow-500); font-family: var(--font-latin); }
.lb-me__pts { font-family: var(--font-cjk-text); font-size: 12px; color: var(--text-tertiary); white-space: nowrap; }
.lb-me__pts b { color: var(--text-primary); font-family: var(--font-latin); font-size: 14px; }
.lb-me__bar { height: 5px; 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)); }

/* clearance so the floating bar never covers the last rows (logged-in only) */
body[data-auth="logged-in"].rf-leaderboard main { padding-bottom: 120px; }

@media (max-width: 768px) {
  .lb-me, body.has-rail .lb-me { left: 50%; width: calc(100vw - 24px); bottom: 12px; gap: 10px; padding: 10px 14px; }
  .lb-me__id .lb-row__nick { max-width: 6em; }
}

/* Ways to earn — verb rows with point chips */
.lb-earn { margin-top: 16px; max-width: 560px; }
.lb-earn__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 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: 768px) {
  .shop-grid, .creator-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 10px; row-gap: 30px; }
  .shop-toolbar { flex-direction: column; gap: 12px; }
  .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: 24px; }
  .creator-event { flex-wrap: wrap; }
  .creator-event__date { width: auto; }
}

/* ─────────────────────────────────────────
   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 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: 32px;
  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: 24px;
  padding: 24px 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: 8px; 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: 4px;
  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: 16px;
  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;
}

@media (max-width: 768px) {
  .timeline-item { flex-direction: row; }
  .timeline-item__poster { width: 84px; }
  .ig-head { gap: 20px; }
  .ig-grid { gap: 2px; }
}

/* 項目 — 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: 64px;
}

.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: 768px) {
  .list-banner {
    grid-template-columns: 1fr;
    gap: 32px;
  }
  .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: 12px; }
  .list-banner__cta { margin-top: 24px; }
}


/* ── hand-extracted single-source components ── */
/* CROWDFUNDING / PRESALE — .cocreation-grid + .cocreation-card (+states/--overlay). [COMPONENT] */
.cocreation-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}

.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: 12px;
  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: 8px;
}
.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: 12px;
  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: 64px 20px 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: 4px;
  align-self: flex-start;
  padding: 4px 12px;
  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);
}

.hero-carousel__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity 800ms ease;
}

.hero-carousel__slide.is-active {
  opacity: 1;
  pointer-events: auto;
}

.hero-carousel__image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 30%;
  transform: scale(1.04);
}

/* 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
   on the copy side. Darken-bottom keeps its full strength for section join. */
.hero-carousel__slide[data-slide="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%);
}

.hero-carousel__darken-bottom,
.hero-carousel__darken-left {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* 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%);
}

.hero-carousel__content {
  position: relative;
  z-index: 1;
  height: 100%;
  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);
}

.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: 4px;
  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: 12px;
  margin-top: 8px;
  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. */
.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: 2;
  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 */
.hero-carousel__dots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 32px;
  display: flex;
  justify-content: center;
  gap: 14px;
  z-index: 2;
}

.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);
}

/* 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: blur(22px) saturate(1.35);
          backdrop-filter: blur(22px) saturate(1.35);
  -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%);
  }
}

/* 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; }
}

/* 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: 4px 2px 8px;
  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: 12px;
  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 12px;               /* 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; }
}

/* ════════════════════════════════════════════════════════════════════════
   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: 12px; }
.ds-skeleton-row > .ds-skeleton--line { flex: 1; }
/* crossfade on swap (renderer sets [data-exiting] just before replacing) */
.ds-skeleton[data-exiting] { opacity: 0; transition: opacity 150ms cubic-bezier(0.23, 1, 0.32, 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; } }
}

/* ════════════════════════════════════════════════════════════════════════
   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(16px, 2vw, 24px);
  margin-top: 8px;
}
.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(8px);
  -webkit-backdrop-filter: blur(8px);
  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: 64px;
}

.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: 768px) {
  .list-banner {
    grid-template-columns: 1fr;
    gap: 32px;
  }
  .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: 12px; }
  .list-banner__cta { margin-top: 24px; }
}

