/* The episode-split strip and the track zone (§7.5, §3.7). Loads after hero.css. */
/* ---------- The strip ---------- */

/* --cell is the widest an episode grows; --cell-min the narrowest, below which the strip scrolls
   in its frame instead. --bulge is how much a cell swells under a slide. */
.strip {
  --h: 12px; --gap: 2px; --sgap: 5px; --r: 2px; --cell: 14px; --cell-min: 6px; --line: 1px;
  --bulge: 0.8; --fade: 28px;
  --swell: calc(var(--h) * var(--bulge) / 2 + 3px);
  position: relative; display: block; width: 100%; height: var(--h);
  max-width: calc(var(--total, 10) * var(--cell) + (var(--total, 10) - var(--seasons, 1)) * var(--gap) + (var(--seasons, 1) - 1) * var(--sgap));
  color: var(--status, var(--text-dim));
  /* Its own layer: a slide repaints only the strip, never the grained page behind it. */
  will-change: transform;
}
/* The frame clips sideways only, with room above and below for the swell. */
.strip-view { position: absolute; inset: calc(-1 * var(--swell)) 0; padding-block: var(--swell); overflow-x: clip; }
.strip-view[data-clip-start] { -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--fade)); mask-image: linear-gradient(to right, transparent, #000 var(--fade)); }
.strip-view[data-clip-end] { -webkit-mask-image: linear-gradient(to left, transparent, #000 var(--fade)); mask-image: linear-gradient(to left, transparent, #000 var(--fade)); }
.strip-view[data-clip-start][data-clip-end] {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--fade), #000 calc(100% - var(--fade)), transparent);
  mask-image: linear-gradient(to right, transparent, #000 var(--fade), #000 calc(100% - var(--fade)), transparent);
}
.strip-tape {
  position: relative; display: flex; gap: var(--sgap); height: 100%; min-width: 100%;
  width: calc(var(--total, 10) * var(--cell-min) + (var(--total, 10) - var(--seasons, 1)) * var(--gap) + (var(--seasons, 1) - 1) * var(--sgap));
  transition: transform 420ms var(--ease);
  /* Its own layer, so scrolling it moves pixels instead of repainting them. */
  will-change: transform;
}
.season { position: relative; display: flex; gap: var(--gap); flex: var(--eps) 1 0; min-width: 0; transition: opacity 140ms var(--ease); }
/* Behind a closed season, one solid bar fills the hairline seams where stretched cells meet. It
   fades in as the cells finish closing, and out as they start to open. */
.season::before {
  content: ''; position: absolute; inset: 0; border-radius: var(--r); background: currentColor;
  opacity: 0; transition: opacity 120ms linear;
}
.season.is-shut::before { opacity: 1; transition: opacity 200ms linear 340ms; }
/* A finished season closes up by stretching its cells over the gaps: cell j of n grows about the
   point j/(n−1) across it, by --close (strip.js), so they meet edge to edge and the season's ends
   never move. Opening is quick and eases out; closing takes its time. */
.ep {
  position: relative; flex: 1 1 0; min-width: 1px; overflow: hidden;
  border-radius: var(--r);
  box-shadow: inset 0 0 0 var(--line) currentColor;
  transform-origin: calc(var(--j, 0) / max(var(--eps) - 1, 1) * 100%) 50%;
  transition: scale 380ms cubic-bezier(0.22, 1, 0.36, 1), border-radius 380ms cubic-bezier(0.22, 1, 0.36, 1);
}
.season.is-shut .ep { scale: var(--close, 1) 1; border-radius: 0; box-shadow: none; transition-duration: 560ms; transition-timing-function: var(--ease-in-out); }
.season.is-shut .ep:first-child { border-radius: var(--r) 0 0 var(--r); }
.season.is-shut .ep:last-child { border-radius: 0 var(--r) var(--r) 0; }
.ep::after {
  content: ''; position: absolute; inset: 0; background: currentColor;
  transform: scaleX(0); transform-origin: left center;
  transition: transform 220ms var(--ease), opacity 140ms var(--ease);
}
.ep[data-s='w']::after { transform: none; }
/* Not out yet: a dotted edge and no fill, never a solid outline like an episode you can watch. */
.ep[data-s='f'], .season-tba { box-shadow: none; border: var(--line) dotted color-mix(in srgb, currentColor 75%, transparent); }
/* Announced without episodes: one dotted block, its size unknown. */
.season-tba { flex: var(--eps) 1 0; min-width: 8px; border-radius: var(--r); }
/* Hovering with a mouse shows what a click there would change: episodes it would mark or unmark,
   at a third. */
.ep[data-g='+']::after { transform: none; opacity: 0.38; }
.season:not(.is-shut) .ep[data-g='-']::after { opacity: 0.38; }
/* A closed season dims as one bar, so no seams show through. */
.season.is-shut:has(.ep[data-g='-']) { opacity: 0.38; }
.ep.is-next::before { content: ''; position: absolute; inset: 0; background: currentColor; opacity: 0; animation: next 1.8s var(--ease-in-out) infinite; }
@keyframes next { 50% { opacity: 0.38; } }
.season.is-settling { animation: settle 560ms var(--ease); }
@keyframes settle { 0% { filter: brightness(1.9); } }
.strip.is-buffering .ep, .strip.is-buffering .season-tba { animation: buffer 640ms var(--ease) both; animation-delay: calc(var(--i, 0) * 11ms); }
@keyframes buffer { from { opacity: 0; transform: scaleY(0.25); } }

/* While sliding, the fill follows the playhead (--f, set per frame) instead of the cell's state. */
.strip.is-scrubbing .ep[data-s]::after { transform: scaleX(var(--f, 0)); transition: none; }
.strip.is-scrubbing .ep.is-next::before { animation: none; }
.strip-head {
  position: absolute; z-index: 2; left: -1px; top: 50%; width: 2px; height: calc(var(--h) * (1 + var(--bulge)) + 8px);
  border-radius: 2px; background: var(--text); box-shadow: 0 0 0 1px rgb(13 12 11 / 0.5);
  opacity: 0; pointer-events: none; transition: opacity 160ms var(--ease);
}
.strip-head.is-on { opacity: 1; }
/* Over episodes not out yet, the playhead is a ghost of itself: nothing there can be marked. */
.strip-head.is-on.is-loose { opacity: 0.55; width: 1px; }
.strip-tag {
  position: absolute; z-index: 6; left: 0; bottom: calc(100% + var(--swell) + 6px);
  padding: 5px 8px; border-radius: 7px; background: var(--surface-high); color: var(--text);
  box-shadow: 0 0 0 1px var(--rule), 0 10px 24px -10px rgb(0 0 0 / 0.85);
  font: 600 12px/1 var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; word-spacing: -0.3em;
  opacity: 0; pointer-events: none; transition: opacity 160ms var(--ease);
}
.strip-tag.is-on { opacity: 1; }
.strip-tag.is-dim { color: var(--text-dim); }
.strip-tag.is-nudged { animation: tag-nudge 420ms var(--ease); }
@keyframes tag-nudge { 20% { translate: -5px 0; } 45% { translate: 4px 0; } 70% { translate: -2px 0; } }
/* Above the thumb, not under it. */
@media (pointer: coarse) { .strip-tag { bottom: calc(100% + var(--swell) + 30px); font-size: 13px; padding: 6px 9px; } }

.strip-hero { --h: clamp(20px, 2.3vw, 32px); --gap: clamp(3px, 0.35vw, 5px); --sgap: clamp(8px, 1vw, 14px); --r: 4px; --cell: 36px; --cell-min: 10px; --line: 1.5px; --bulge: 0.45; flex: 1 1 auto; }
.strip-row { --h: 12px; --cell: 13px; }
.strip-scene { --h: 16px; --gap: 3px; --sgap: 7px; --r: 3px; --cell: 22px; --cell-min: 8px; --line: 1.25px; --bulge: 0.6; }
@media (pointer: coarse) { .strip-row { --cell-min: 7px; } }

/* The track zone: tap to mark, hold and slide to move. The page still scrolls up and down through
   it, and pinches still zoom; a sideways swipe is left to track.js, which scrolls a long strip
   with it. A native range inside is the keyboard and screen-reader control. */
.track-zone {
  position: relative; cursor: pointer; touch-action: pan-y pinch-zoom;
  -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent;
}
.track-zone.is-sliding { cursor: grabbing; }
.track-zone:has(.track-input:focus-visible) { outline: 2px solid var(--text); outline-offset: 2px; }
.track-input { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.screen-meta.is-preview, .c-where.is-preview { color: var(--accent); }

