/* DeepDive · Storyline Banners ────────────────────────────────────────────
 * The *secondary* axis of the index. Where the six category banners are
 * abstract instruments (recursion, load, graticule, emergence, constellation,
 * interference), every storyline banner draws that storyline's own metaphor:
 * a chessboard, a felt table of chips, a flywheel with an unexploded shell
 * chained to it, a colonnade emptying into startup blocks, a hiring arrow
 * pointing the wrong way, a bar counter spilling into a national map, a
 * lecture theatre under a semester rule, a peripheral emitting packets, a
 * radar sector holding one contact. You should know what the storyline is
 * about before you read the title.
 *
 * Geometry is generated per card by scripts/deepdive/storyline_banner.py —
 * there is no sprite here, because no two banners are the same drawing.
 *
 * Contract
 *   <a class="sl-card" data-sl-motif="power-game" href="…">
 *     <svg class="sl-bnr" viewBox="0 0 480 120">…</svg>
 *     <div class="sl-card-body">
 *       <span class="sl-card-id">SL·01</span>
 *       <h3 class="sl-card-title">…</h3>
 *       <span class="sl-card-n">串联 10 篇</span>
 *     </div>
 *   </a>
 *   …all wrapped in <div class="sl-grid">.
 *
 * Rules this file obeys
 *   · Zero JavaScript. All motion is @keyframes; no SMIL, no script hooks.
 *   · prefers-reduced-motion: reduce → animation off, and every motif still
 *     reads as a finished static plate (the generator composes them that way).
 *   · Colour only ever comes from --sl-accent (per motif) + the surface
 *     tokens in colors_and_type.css. Light mode is [data-mode="light"]
 *     overrides, never a second set of markup.
 *   · Motion budget: transform / opacity / stroke-dashoffset only. Nine of
 *     these render at once, below six heavier category cards — no filter
 *     animation, no box-shadow tweening, nothing that repaints large areas.
 *   · Visual weight sits deliberately BELOW .cat-card: flatter banner (4:1 vs
 *     16:9), lower resting opacity, smaller title, hairline-only hover.
 *
 * No shadow-tree caveat here — the SVG is inlined, not <use>d — so descendant
 * selectors work. They are kept shallow anyway.
 * -------------------------------------------------------------------- */

/* ============ 1 · MOTIF ACCENTS ============
 * One hue per storyline, keyed to its subject rather than to a rotation:
 * oxide red for the power board, amber for the gambling table, terracotta
 * for the flywheel, olive for the campus exodus, moss for the labour market,
 * warm mauve for the bar, brass for the lecture hall, and the only two cool
 * tones on the page for the two hardware/defence lines — instrument teal and
 * scope slate. All inside the warm-archive range: no tech blue-violet, no
 * candy gradients, nothing that outshouts the six category hues above.
 *
 * --sl-bg is the banner surface, re-used *inside* the SVG as the casing that
 * lets a shell body occlude the protocol rail or a chip occlude the stack
 * beneath it. It must track the real background.
 */
.sl-card {
  --sl-accent: #9a8a72;                  /* fallback: neutral archive grey */
  --sl-bg: var(--bg-2, #161310);
}

[data-sl-motif="power-game"]          { --sl-accent: #b4503a; }  /* oxide      · 棋盘 */
[data-sl-motif="neolab-bets"]         { --sl-accent: #c08a2e; }  /* amber      · 赌局 */
[data-sl-motif="anthropic-flywheel"]  { --sl-accent: #b96a41; }  /* terracotta · 飞轮 */
[data-sl-motif="research-migration"]  { --sl-accent: #98a05e; }  /* olive      · 迁徙 */
[data-sl-motif="ai-hiring"]           { --sl-accent: #7f9c7a; }  /* moss       · 雇佣 */
[data-sl-motif="third-place"]         { --sl-accent: #b0757a; }  /* mauve      · 吧台 */
[data-sl-motif="university-frontier"] { --sl-accent: #b09a52; }  /* brass      · 讲堂 */
[data-sl-motif="agent-peripheral"]    { --sl-accent: #6f9490; }  /* teal       · 外设 */
[data-sl-motif="defense-tech"]        { --sl-accent: #7590a0; }  /* slate      · 雷达 */
[data-sl-motif="thread"]              { --sl-accent: #9a8a72; }  /* grey       · 通用 */

/* Light mode — deepened so hairlines survive on parchment (#f3ede0).
 * Double selectors match the specificity pattern colors_and_type.css uses. */
[data-mode="light"] [data-sl-motif="power-game"],
.mode-light [data-sl-motif="power-game"]          { --sl-accent: #8f3a26; }
[data-mode="light"] [data-sl-motif="neolab-bets"],
.mode-light [data-sl-motif="neolab-bets"]         { --sl-accent: #8a5f12; }
[data-mode="light"] [data-sl-motif="anthropic-flywheel"],
.mode-light [data-sl-motif="anthropic-flywheel"]  { --sl-accent: #8e4520; }
[data-mode="light"] [data-sl-motif="research-migration"],
.mode-light [data-sl-motif="research-migration"]  { --sl-accent: #5f6a2c; }
[data-mode="light"] [data-sl-motif="ai-hiring"],
.mode-light [data-sl-motif="ai-hiring"]           { --sl-accent: #4b6b4b; }
[data-mode="light"] [data-sl-motif="third-place"],
.mode-light [data-sl-motif="third-place"]         { --sl-accent: #8a4048; }
[data-mode="light"] [data-sl-motif="university-frontier"],
.mode-light [data-sl-motif="university-frontier"] { --sl-accent: #7a6524; }
[data-mode="light"] [data-sl-motif="agent-peripheral"],
.mode-light [data-sl-motif="agent-peripheral"]    { --sl-accent: #35625e; }
[data-mode="light"] [data-sl-motif="defense-tech"],
.mode-light [data-sl-motif="defense-tech"]        { --sl-accent: #3d5a69; }
[data-mode="light"] [data-sl-motif="thread"],
.mode-light [data-sl-motif="thread"]              { --sl-accent: #6d6250; }

[data-mode="light"] .sl-card,
.mode-light .sl-card                       { --sl-bg: var(--bg-2, #ebe3d2); }


/* ============ 2 · GRID ============
 * Collapsed hairline grid — the 1px gaps ARE the rules, same idiom as
 * .cat-grid. Explicit breakpoints: 1 → 2 → 3 columns.
 */
.sl-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
}

@media (min-width: 620px) {
  .sl-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 980px) {
  .sl-grid { grid-template-columns: repeat(3, 1fr); }
}


/* ============ 3 · CARD ============ */
.sl-card {
  display: flex;
  flex-direction: column;
  position: relative;
  background: var(--bg);
  color: inherit;
  text-decoration: none;
  border: 0;
  border-radius: 0;                      /* this brand never rounds */
  transition: background var(--d-base, 300ms) var(--ease-out, ease);
}

.sl-card:hover,
.sl-card:focus-visible {
  background: var(--bg-2);
  outline: 0;
}

/* Hairline highlight instead of a shadow. Dimmer than .cat-card's 0.55 —
 * storylines must not out-shout the six category cards above them. */
.sl-card::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  box-shadow: inset 0 0 0 1px var(--sl-accent);
  opacity: 0;
  transition: opacity var(--d-base, 300ms) var(--ease-out, ease);
}
.sl-card:hover::after,
.sl-card:focus-visible::after { opacity: 0.4; }


/* ============ 4 · BANNER CANVAS ============
 * 4:1 strip — flatter than the 16:9 category banner on purpose. The SVG's own
 * preserveAspectRatio="xMidYMid slice" crops it at any card width; all
 * load-bearing geometry sits in the y 14–106 safe band.
 */
.sl-bnr {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 1;
  background: var(--bg-2);
  overflow: hidden;
  contain: paint;
  opacity: 0.66;                         /* resting: quieter than .cat-bnr */
  transition: opacity var(--d-base, 300ms) var(--ease-out, ease);
}

.sl-card:hover .sl-bnr,
.sl-card:focus-visible .sl-bnr { opacity: 0.98; }

/* Parchment absorbs hairlines: dark ink at 0.15 alpha on #ebe3d2 reads much
 * fainter than bone ink at 0.15 on #161310, so light mode gets a higher
 * resting opacity. Still under .cat-bnr's 0.94 — the pecking order holds. */
[data-mode="light"] .sl-bnr,
.mode-light .sl-bnr { opacity: 0.9; }
[data-mode="light"] .sl-card:hover .sl-bnr,
.mode-light .sl-card:hover .sl-bnr { opacity: 1; }

@media (max-width: 619px) {
  .sl-bnr { aspect-ratio: 5 / 1; }       /* full-width row: flatter still */
}


/* ============ 5 · CARD BODY ============ */
.sl-card-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 13px 16px 15px;
  border-top: 1px solid var(--line);
}

.sl-card-id {
  font-family: var(--mono);
  font-size: var(--micro, 10px);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--sl-accent);
  font-variant-numeric: tabular-nums;
}

.sl-card-title {
  font-family: var(--display);
  font-weight: 400;
  font-size: 17px;                       /* vs 22px on .cat-card-title */
  line-height: 1.4;
  letter-spacing: 0;                     /* never negative-track CJK */
  color: var(--fg);
  margin: 0;
  transition: color var(--d-fast, 150ms) var(--ease-out, ease);
}
.sl-card:hover .sl-card-title { color: var(--sl-accent); }

.sl-card-n {
  margin-top: auto;
  padding-top: 9px;
  border-top: 1px dashed var(--line);
  font-family: var(--mono);
  font-size: var(--micro, 10px);
  letter-spacing: 0.14em;
  color: var(--fg-mute);
  font-variant-numeric: tabular-nums;
}


/* ============ 6 · MOTION ============
 * Six keyframes, shared by all ten motifs. Per-instance duration / delay /
 * step-count / rotation origin are inlined by the generator, so nine banners
 * in one viewport drift out of phase instead of pulsing together.
 * Everything here is ambient instrumentation, not motion graphics: long
 * periods, low amplitude, transform + opacity + stroke-dashoffset only.
 */

/* Travelling pulse — migrating researchers, hiring offers, packet traffic,
 * the odds ticker, the timeline read-head. Every animated path carries
 * pathLength="100", so one keyframe gives the same apparent speed no matter
 * how long the generated path actually is. */
.sl-run {
  fill: none;
  stroke: var(--sl-accent, #9a8a72);
  animation: sl-run 18s linear infinite;
  stroke-dashoffset: 0;
}
@keyframes sl-run {
  from { stroke-dashoffset: 0; }
  to   { stroke-dashoffset: -100; }
}

/* Markers — chips, outposts, seats, blips, the fuse. Slow asymmetric breath. */
.sl-mark {
  animation: sl-mark 7s ease-in-out infinite alternate;
}
@keyframes sl-mark {
  from { opacity: 0.32; }
  to   { opacity: 0.95; }
}

/* Discrete advance — a chess piece moving one square, a semester moving one
 * week. The generator inlines --sl-step (total travel in user units) and
 * animation-timing-function: steps(k, jump-none), so the dwell is exact for
 * any k without generating per-k keyframes. */
.sl-step {
  transform-box: view-box;
  animation: sl-step 20s steps(6, jump-none) infinite;
}
@keyframes sl-step {
  from { transform: translateX(0); }
  to   { transform: translateX(var(--sl-step, 0px)); }
}

/* Continuous rotation — the flywheel. transform-origin is inlined per
 * instance in view-box pixels; direction is flipped inline for the inner
 * ring so the assembly reads as geared rather than as one spinning disc. */
.sl-spin {
  transform-box: view-box;
  animation: sl-spin 54s linear infinite;
}
@keyframes sl-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* Sector scan — the radar sweep. A full turn would park the wedge off-plate
 * for half its cycle, so it swings ±--sl-a instead, the way a real sector
 * scan does. Eased at the turnarounds. */
.sl-swing {
  transform-box: view-box;
  animation: sl-swing 12s ease-in-out infinite alternate;
}
@keyframes sl-swing {
  from { transform: rotate(calc(-1 * var(--sl-a, 50deg))); }
  to   { transform: rotate(var(--sl-a, 50deg)); }
}

/* Expanding ring — one bar becoming a chain, one contact being acquired. */
.sl-pulse {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: sl-pulse 9s var(--ease-out, cubic-bezier(0.2, 0.7, 0.2, 1)) infinite;
}
@keyframes sl-pulse {
  0%   { transform: scale(0.45); opacity: 0; }
  18%  { opacity: 0.5; }
  100% { transform: scale(1.7);  opacity: 0; }
}


/* ============ 7 · REDUCED MOTION ============
 * Static states are complete compositions: .sl-run rests at dashoffset 0
 * (reads as a measured graduation on its path), .sl-mark and .sl-pulse keep
 * a mid opacity, the flywheel and the sweep park at their authored angle,
 * the chess piece parks on its starting square and the term on week 01.
 */
@media (prefers-reduced-motion: reduce) {
  .sl-run, .sl-mark, .sl-step, .sl-spin, .sl-swing, .sl-pulse {
    animation: none !important;
  }
  .sl-mark  { opacity: 0.78; }
  .sl-pulse { opacity: 0.3; }
  .sl-card, .sl-card::after, .sl-bnr, .sl-card-title {
    transition: none !important;
  }
}

/* Print: drop the decorative layer entirely. */
@media print {
  .sl-bnr { display: none; }
}
