/* Module 1 — platform digital sign train times (grid of signs) */

/* Module 1 — one sign at a time in a swipeable pager. A header above
   the sign shows the current station (name + line bullets + direction)
   and page dots. The module fills the available screen height (flex chain
   from the dashboard shell); the sign keeps its configured aspect ratio
   when there is room and stretches to fill when the screen is narrower
   than that ratio. The module is a container query context: every size —
   header and sign text alike — scales with the module's actual width. */
.module-train-times {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
  width: 100%;
  container-type: inline-size;
}

/* Header: current station, its line bullets, direction, and page dots */
.board-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2cqw;
  padding: 1.2cqw 2cqw;
  margin-bottom: 1.2cqw;
  background-color: #080a0f;
  border: 2px solid #222;
  border-radius: 10px;
}

.board-title {
  display: flex;
  align-items: center;
  gap: 1.2cqw;
  min-width: 0;
}

.board-station {
  color: #ffffff;
  font-weight: 700;
  text-transform: capitalize;
  letter-spacing: 0.06em;
  font-size: clamp(13px, 2.8cqw, 38px);
}

.board-direction {
  color: rgba(255, 255, 255, 0.65);
  font-weight: 700;
  text-transform: capitalize;
  font-size: clamp(11px, 2.5cqw, 33px);
}

/* Compact line bullets in the header */
.line-badge.mini {
  width: clamp(15px, 2.6cqw, 36px);
  font-size: clamp(10px, 1.7cqw, 22px);
}

.board-dots {
  display: flex;
  align-items: center;
  gap: 1cqw;
  flex: 0 0 auto;
}

.pager-dot {
  width: clamp(7px, 1cqw, 14px);
  aspect-ratio: 1;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.25);
  cursor: pointer;
  flex: 0 0 auto;
}

.pager-dot.active {
  background: #ffc72c;
}

/* Swipe pager: one sign visible, the track slides horizontally. The
   pager fills all module height below the header. */
.sign-pager {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  overflow: hidden;
  background-color: #080a0f;
  border: 2px solid #222;
  border-radius: 10px;
  touch-action: pan-y;
}

.sign-track {
  flex: 1 1 auto;
  display: flex;
  transition: transform 0.3s ease;
  will-change: transform;
}

.sign-cell {
  flex: 0 0 100%;
  min-width: 0;
  display: flex;
  justify-content: center;
}

.sign-frame {
  position: relative;
  height: 100%;
  width: auto;
  max-width: 100%;
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* --- Arrival board (top ~80%): two big slots --- */

.arrival-board {
  flex: 1 1 80%;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.slot {
  flex: 1 1 50%;
  min-height: 0;
  display: flex;
  align-items: center;
  gap: 2.5%;
  padding: 0 4%;
}

.slot + .slot {
  border-top: 1px solid rgba(255, 255, 255, 0.14);
}

.slot.is-empty {
  visibility: hidden;
}

/* Train info (badge, destination, minutes): crossfades between trains */
.slot-info {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 2.5%;
  transition: opacity 0.6s ease;
}

.slot-info.is-swapping {
  opacity: 0;
}

/* Big ordinal number (left): which train this is (1st, 2nd, ...).
   Masked one-digit window; the strip inside scrolls when it changes. */
.minutes {
  flex: 0 0 auto;
  min-width: 1.1em;
  height: 1em;
  overflow: hidden;
  text-align: center;
  color: #ffffff;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-size: clamp(26px, 9.5cqw, 150px);
  line-height: 1;
}

.minutes-strip {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}

.minutes-digit {
  display: block;
  line-height: 1;
  text-align: center;
}

/* Flip-counter roll: up when the ordinal increments, back down through
   the numbers when it wraps (e.g. 5 -> 2). */
.minutes-strip.rolling {
  animation: minutes-roll 0.6s ease both;
}

@keyframes minutes-roll {
  from {
    transform: translateY(var(--roll-from, 0em));
  }
  to {
    transform: translateY(var(--roll-to, 0em));
  }
}

/* Route badge */
.line-badge {
  flex: 0 0 auto;
  width: clamp(24px, 7.5cqw, 115px);
  aspect-ratio: 1;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: clamp(12px, 3.7cqw, 56px);
}

/* Destination (middle): direction label stacked over terminus */
.destination {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-transform: capitalize;
}

.direction {
  color: rgba(255, 255, 255, 0.65);
  font-weight: 700;
  font-size: clamp(14px, 5cqw, 70px);
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dest-text {
  color: #ffffff;
  font-weight: 700;
  font-size: clamp(12px, 4.4cqw, 62px);
  line-height: 1.1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ETA (right) */
.eta {
  flex: 0 0 auto;
  color: #ffffff;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: right;
  font-size: clamp(12px, 4.2cqw, 58px);
}

/* Off-pager signs: animations paused (their rendering is skipped too,
   via the pager's is-paused class) */
.sign-cell.is-paused .ticker-inner,
.sign-cell.is-paused .eta.is-arriving,
.sign-cell.is-paused .minutes-strip.rolling {
  animation-play-state: paused;
}

.eta.is-arriving {
  animation: flash 1s linear infinite;
}

@keyframes flash {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.2;
  }
}

/* --- Official line badge palette --- */

.line-123  { background-color: #ee352e; color: #fff; } /* Red */
.line-456  { background-color: #00933c; color: #fff; } /* Green */
.line-7    { background-color: #b933ad; color: #fff; } /* Purple */
.line-ACE  { background-color: #0039a6; color: #fff; } /* Blue */
.line-BDFM { background-color: #ff6319; color: #fff; } /* Orange */
.line-NQRW { background-color: #fccc0a; color: #000; } /* Yellow (black text) */
.line-G    { background-color: #6cbe45; color: #fff; } /* Light Green */
.line-JZ   { background-color: #996633; color: #fff; } /* Brown */
.line-L    { background-color: #a7a9ac; color: #000; } /* Gray (L bullet: black text) */
.line-L-S  { background-color: #a7a9ac; color: #fff; } /* Gray (shuttles) */

/* --- Footer bar (bottom, short strip) --- */

.footer-bar {
  flex: 0 0 min(16%, 10cqw);
  min-height: 0;
  display: flex;
  align-items: center;
  border-top: 1px solid rgba(255, 255, 255, 0.14);
}

/* Left block: clock + update freshness */
.footer-status {
  flex: 0 0 auto;
  display: flex;
  align-items: baseline;
  gap: 1.2em;
  padding: 0 1.5%;
  font-family: 'SF Mono', Menlo, Consolas, 'Courier New', monospace;
  font-variant-numeric: tabular-nums;
  font-size: clamp(10px, 2.3cqw, 32px);
}

.clock {
  color: #ffc72c;
  /* Fixed box (monospace ch units): the hour flips between 1 and 2
     digits ("9:59" -> "10:00") twice a day — a resizing clock shifts
     the ticker box's left edge and the marquee jumps a few px. */
  min-width: 8ch;
}

/* Last update time + countdown to next poll */
.updated {
  color: rgba(255, 255, 255, 0.55);
  font-size: 0.85em;
  /* Fixed box: the countdown's digit count changes every poll
     ("Next 9s" -> "Next 10s"); reserving the 2-digit width keeps the
     footer — and with it the ticker box — from shifting. Covers poll
     intervals up to 99s. */
  min-width: 8ch;
}

/* Very narrow signs: drop the countdown so the footer doesn't crowd */
@container (max-width: 380px) {
  .updated {
    display: none;
  }
}

/* Right block: marquee ticker */
.ticker {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 4%, #000 96%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 4%, #000 96%, transparent);
}

.ticker-inner {
  display: inline-block;
  white-space: nowrap;
  text-transform: capitalize;
  color: #fccc0a;
  font-size: clamp(11px, 2.1cqw, 28px);
  animation: marquee var(--marquee-duration, 30s) linear infinite;
  /* Compositor layer: the marquee is pure transform, so promote it and
     the scroll stays smooth even while arrivals re-render above. */
  will-change: transform;
  backface-visibility: hidden;
}

/* Line badges inside the ticker (alert headers like "[E] trains..."):
   sized to the ticker's own text instead of the board-slot clamp, so
   they read as inline bullets in the marquee line. */
.ticker-inner .line-badge.ticker-badge {
  display: inline-flex;
  width: 1.35em;
  aspect-ratio: 1;
  font-size: 0.8em;
  vertical-align: middle;
  margin: 0 0.12em;
}

/* Seamless loop: the JS renders the text twice; shifting by exactly one
   copy (-50%) lands the second copy where the first started, so the
   stream never breaks and the ticker is never blank. */
@keyframes marquee {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-50%);
  }
}

/* Loading dots: shown from first paint until every live sign has its
   first feed data (train-times.js fades the element out, then removes
   it). Sits over the whole module, centered. */
.sign-loading {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.2cqw;
  z-index: 5;
  pointer-events: none;
  transition: opacity 600ms ease;
}

.sign-loading.is-done {
  opacity: 0;
}

.sign-loading-dot {
  width: 1.4cqw;
  height: 1.4cqw;
  border-radius: 50%;
  background-color: #fccc0a;
  animation: sign-loading-pulse 1.2s ease-in-out infinite;
}

.sign-loading-dot:nth-child(2) {
  animation-delay: 0.2s;
}

.sign-loading-dot:nth-child(3) {
  animation-delay: 0.4s;
}

@keyframes sign-loading-pulse {
  0%,
  100% {
    opacity: 0.25;
    transform: scale(0.85);
  }
  50% {
    opacity: 1;
    transform: scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .sign-loading-dot {
    animation: none;
  }
}

/* First-load fade-in: only the dynamic content — the arrival slots —
   fades in when the first feed data lands. The static chrome (station
   header, sign frame, clock, ticker) is visible from first paint with
   the loading dots over it; empty slots are visibility:hidden, so the
   frame reads as a clean empty sign while loading. */
.arrival-board {
  transition: opacity 1200ms ease-out;
}

.module-train-times.is-loading .arrival-board {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .arrival-board {
    transition: none;
  }
}

/* The loader element persists after the first load (hidden) — sign
   switches re-show it for the same dots-and-fade treatment. */
.sign-loading.is-hidden {
  display: none;
}

/* Empty state: the sign HAS data but no trains pass the filters
   (off-hours, lead time, nothing within the 90-min horizon window).
   A muted line fades in over the arrival area; the footer (clock,
   ticker) keeps running so the sign still reads as live. */
.arrival-board {
  position: relative;
}

.no-trains {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0 2cqw;
  color: rgba(255, 255, 255, 0.5);
  font-weight: 700;
  font-size: clamp(12px, 2.2cqw, 30px);
  letter-spacing: 0.04em;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 600ms ease;
}

.no-trains.is-shown {
  opacity: 1;
  visibility: visible;
}

@media (prefers-reduced-motion: reduce) {
  .no-trains {
    transition: none;
  }
}
