/* ==========================================================================
   Eki Design System — NYC Subway Timetable
   ========================================================================== */

:root {
  /* Palette — Light */
  --eki-bg: #fafafa;
  --eki-surface: #ffffff;
  --eki-text: #1a1a1a;
  --eki-text-muted: #6b6b6b;
  --eki-text-dim: #9b9b9b;
  --eki-border: #e5e5e5;
  --eki-alert-accent: var(--eki-state-error);
  --eki-active-bg: #111111;
  --eki-active-text: #ffffff;

  /* Spacing */
  --eki-space-xs: 4px;
  --eki-space-sm: 8px;
  --eki-space-md: 16px;
  --eki-space-lg: 24px;
  --eki-space-xl: 40px;
  --eki-space-2xl: 64px;

  /* Typography */
  --eki-font: 'Sora', 'Zen Kaku Gothic New', system-ui, -apple-system, sans-serif;
  --eki-font-num: var(--eki-font);
  --eki-font-size-xs: 10px;
  --eki-font-size-sm: 12px;
  --eki-font-size-md: 14px;
  --eki-font-size-lg: 18px;
  --eki-font-size-xl: 24px;
  --eki-font-size-2xl: 32px;

  /* Touch targets */
  --eki-min-touch: 44px;

  /* MTA Line Colors */
  --mta-red: #ee352e; /* 1, 2, 3 */
  --mta-green: #00933c; /* 4, 5, 6 */
  --mta-purple: #b933ad; /* 7 */
  --mta-blue: #2850ad; /* A, C, E */
  --mta-orange: #ff6319; /* B, D, F, M */
  --mta-yellow: #fccc0a; /* N, Q, R, W */
  --mta-lime: #6cbe45; /* G */
  --mta-brown: #996633; /* J, Z */
  --mta-gray-light: #a7a9ac; /* L */
  --mta-gray-dark: #808183; /* S */
  --mta-sir: #253070; /* SIR */

  /* Semantic State Colors */
  --eki-state-error: #ee352e;
  --eki-state-warning: #ff6319;
  --eki-state-success: #00933c;
  --eki-state-info: #2850ad;
  /* Brighter teal info — used for "running early" minute pills so they
     read as a positive deviation against the orange/red late palette. */
  --eki-state-info-bright: #0891b2;

  /* Motion */
  --eki-duration-fast: 0.18s;
  --eki-duration-normal: 0.28s;
  --eki-duration-slow: 0.36s;
  --eki-duration-slower: 0.44s;
  --eki-duration-entrance: 0.34s;
  --eki-duration-exit: 0.24s;
  --eki-ease-soft: cubic-bezier(0.32, 0, 0.18, 1);
  --eki-ease-gentle-out: cubic-bezier(0.2, 0.7, 0.2, 1);

  /* Dock add/remove animation tokens — used by station rows and group
     chips. Outer collapse and inner crumble run concurrently with a
     60ms exit-side handoff so the contents fade first then the
     container closes. */
  --eki-dock-collapse-duration: 220ms;
  --eki-dock-collapse-ease: cubic-bezier(0.2, 0, 0, 1);
  --eki-dock-fade-duration: 200ms;
  --eki-dock-scale-duration: 280ms;
  --eki-dock-handoff-delay: 60ms;

  /* Elevation scale — every cross-component z-index goes through one of
     these tokens. Layers are widely spaced so future insertions don't
     require shifting neighbours.

       floating  10   content-anchored floating UI (delay tooltip, etc.)
       sticky    20   in-content sticky headers (date sections)
       header   100   fixed page chrome
       dropdown 200   in-flow dropdowns (search results, etc.)
       modal    500   modals, dock pill/sheet, toasts
       popover 1000   menus/popovers ABOVE modal-level UI (chip context menu)

     Stacking-context-local cases (raising one element above its
     siblings within an isolated parent — e.g. `.jk-toggle__opt`,
     `.jk-group-chip--dragging`) keep using a bare `z-index: 1`; they
     don't participate in this global scale. */
  --eki-z-floating: 10;
  --eki-z-sticky: 20;
  --eki-z-header: 100;
  --eki-z-dropdown: 200;
  --eki-z-modal: 500;
  --eki-z-popover: 1000;

  /* Glass overlays — always pair these two on backdrops/dimmers; never use a
     flat rgba(0, 0, 0, 0.4+) fill. See docs/DESIGN_SYSTEM.md § Glass Overlays. */
  --eki-overlay-blur: blur(4px) saturate(120%);
  --eki-overlay-tint: linear-gradient(180deg, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.1));

  /* Border Radius */
  --eki-radius-sm: 2px;
  --eki-radius-md: 4px;
  --eki-radius-full: 50%;
  /* Capped pill — relies on the browser clamping to min(w,h)/2 so a single
     token covers any "stadium" shape (small badges, full-height pills). */
  --eki-radius-pill: 999px;

  /* Elevation — used by the dock pill, the jump-to-now pill, and other
     hovering surfaces. Keep the rgba values inline (no shadow color tokens
     today) so the shape stays a single drop-in var. */
  --eki-shadow-floating: 0 4px 12px rgb(0 0 0 / 16%), 0 1px 3px rgb(0 0 0 / 8%);

  /* Toggle controls */
  --eki-toggle-border-width: 1px;
  --eki-toggle-border-color: var(--eki-border);
  --eki-toggle-border-color-hover: var(--eki-text-muted);
  --eki-toggle-border-color-active: var(--eki-active-text);

  /* Font Weights */
  --eki-weight-400: 400;
  --eki-weight-500: 500;
  --eki-weight-600: 600;
  --eki-weight-700: 700;
  --eki-weight-800: 800;
  --eki-weight-900: 900;

  /* Layout */
  --eki-sidebar-width: 240px;
  --eki-main-padding: 56px;
  --eki-main-padding-tablet: 32px;
  --eki-main-padding-mobile: 20px;
  --eki-modal-max-width: 500px;
  --eki-picker-max-width: 600px;
  --eki-dropdown-max-height: 280px;
  --eki-sticky-offset: 0px;
}

/* ---- Dark mode ---- */
[data-theme='dark'] {
  --eki-bg: #111111;
  --eki-surface: #1a1a1a;
  --eki-text: #f0f0f0;
  --eki-text-muted: #999999;
  --eki-text-dim: #666666;
  --eki-border: #333333;
  --eki-active-bg: #f0f0f0;
  --eki-active-text: #111111;
}

/* Reset */
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html,
body {
  font-family: var(--eki-font);
  background: var(--eki-bg);
  color: var(--eki-text);
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
}

body {
  display: flex;
  flex-direction: column;
  /* lvh = large viewport: page extends behind iOS Safari's collapsing URL bar
     so the background reaches the bottom edge. Last-item padding below adds
     safe-area room so content isn't hidden by the bar. */
  min-height: 100lvh;
}

html:not([data-no-transition]) body,
html:not([data-no-transition]) .jk-header,
html:not([data-no-transition]) .jk-alerts,
html:not([data-no-transition]) .jk-sidebar,
html:not([data-no-transition]) .jk-main,
html:not([data-no-transition]) .jk-modal,
html:not([data-no-transition]) .jk-toggle,
html:not([data-no-transition]) .jk-date-section,
html:not([data-no-transition]) .jk-date-section__sticky {
  transition:
    background-color var(--eki-duration-slower) var(--eki-ease-soft),
    color var(--eki-duration-slower) var(--eki-ease-soft),
    border-color var(--eki-duration-slower) var(--eki-ease-soft);
}

/* ==========================================================================
   MTA Badge System — Three sizes, hover glow
   ========================================================================== */
.mta-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--eki-radius-full);
  font-weight: var(--eki-weight-700);
  font-family: var(--eki-font);
  line-height: 1;
  flex-shrink: 0;
}

.mta-badge--sm {
  width: 20px;
  height: 20px;
  font-size: 11px;
}

.mta-badge--md {
  width: 32px;
  height: 32px;
  font-size: 16px;
}

.mta-badge--lg {
  width: 48px;
  height: 48px;
  font-size: 24px;
}

.mta-badge--diamond {
  position: relative;
  border-radius: 0;
  border: 0;
  outline: 0;
  background: transparent;
  overflow: visible;
}

.mta-badge--diamond::before {
  content: '';
  position: absolute;
  inset: 2.5px;
  background: var(--mta-badge-fill, currentColor);
  border-radius: 0;
  transform: rotate(45deg);
}

.mta-badge--diamond > span {
  display: block;
  position: relative;
  z-index: 1;
  line-height: 1;
  transform: translateY(-0.25px);
}

.mta-badge--diamond.mta-badge--md::before {
  inset: 4px;
}

.mta-badge--diamond.mta-badge--lg::before {
  inset: 6px;
}

.mta-badge-cluster {
  display: inline-flex;
  gap: 6px;
  align-items: center;
}

/* ==========================================================================
   Header — Single-row flex layout
   ========================================================================== */
.jk-header {
  position: sticky;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--eki-space-lg);
  /* Top padding adds env(safe-area-inset-top) so the header clears the iOS
     status bar / Dynamic Island in standalone PWA mode (status-bar-style is
     black-translucent, so content renders behind the bar). */
  padding: calc(env(safe-area-inset-top, 0px) + 16px) 40px 12px;
  background: var(--eki-bg);
  color: var(--eki-text);
  border-bottom: 1px solid var(--eki-border);
  flex-shrink: 0;
  z-index: var(--eki-z-header);
}

.jk-header__left {
  display: flex;
  align-items: baseline;
  gap: var(--eki-space-md);
}

.jk-header__title {
  display: inline-flex;
  align-items: baseline;
  gap: 0.25em;
  line-height: 1;
  cursor: pointer;
}

/* EN: hover dims only the smaller 駅 */
[lang='en'] .jk-header__title:hover .eki-secondary {
  opacity: 0.7;
}

/* JA: hover dims only the smaller Eki */
[lang='ja'] .jk-header__title:hover .eki-primary {
  opacity: 0.7;
}

/* Eki branding — shared transition */
.eki-primary,
.eki-secondary {
  transition:
    font-size var(--eki-duration-slower) var(--eki-ease-soft),
    font-weight var(--eki-duration-slower) var(--eki-ease-soft),
    color var(--eki-duration-slower) var(--eki-ease-soft),
    letter-spacing var(--eki-duration-slower) var(--eki-ease-soft),
    opacity var(--eki-duration-fast) var(--eki-ease-soft);
}

.eki-primary {
  font-family: 'Sora', system-ui, sans-serif;
  text-transform: uppercase;
}

.eki-secondary {
  font-family: 'Zen Kaku Gothic New', system-ui, sans-serif;
}

/* EN mode: EKI leads, 駅 follows */
[lang='en'] .eki-primary {
  font-size: clamp(24px, 3.5vw, 34px);
  font-weight: var(--eki-weight-800);
  letter-spacing: 0.08em;
  color: var(--eki-text);
  order: 0;
}

[lang='en'] .eki-secondary {
  font-size: clamp(16px, 2.2vw, 22px);
  font-weight: var(--eki-weight-500);
  color: var(--eki-text-muted);
  order: 1;
}

/* JA mode: 駅 leads, Eki follows */
[lang='ja'] .eki-secondary {
  font-size: clamp(24px, 3.5vw, 34px);
  font-weight: var(--eki-weight-700);
  color: var(--eki-text);
  order: 0;
}

[lang='ja'] .eki-primary {
  font-size: clamp(14px, 1.8vw, 18px);
  font-weight: var(--eki-weight-400);
  letter-spacing: 0.12em;
  color: var(--eki-text-muted);
  order: 1;
}

.jk-header__right {
  display: flex;
  align-items: center;
  gap: var(--eki-space-md);
}

.jk-header__home {
  font-size: var(--eki-font-size-xs);
  color: var(--eki-text-dim);
  text-decoration: none;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.jk-header__home:hover {
  color: var(--eki-text);
}

/* ==========================================================================
   Toggle controls
   ========================================================================== */
.jk-toggle-btn {
  border: var(--eki-toggle-border-width) solid var(--eki-toggle-border-color);
  border-radius: var(--eki-radius-sm);
  background: transparent;
  transition:
    background var(--eki-duration-normal),
    color var(--eki-duration-normal),
    border-color var(--eki-duration-normal);
}

.jk-toggle-btn:hover {
  border-color: var(--eki-toggle-border-color-hover);
}

.jk-toggle-btn.is-active {
  border-color: var(--eki-toggle-border-color-active);
}

/* ==========================================================================
   Sliding-pill toggle — reusable N-option radio group
   ========================================================================== */
.jk-toggle {
  position: relative;
  display: inline-flex;
  border: var(--eki-toggle-border-width) solid var(--eki-toggle-border-color);
  border-radius: var(--eki-radius-sm);
  background: var(--eki-surface);
  padding: 0;
}

.jk-toggle__pill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: var(--pill-w, 0);
  transform: translateX(var(--pill-x, 0));
  background: var(--eki-active-bg);
  border-radius: var(--eki-radius-sm);
  transition:
    transform var(--eki-duration-normal) var(--eki-ease-soft),
    width var(--eki-duration-normal) var(--eki-ease-soft);
  pointer-events: none;
}

.jk-toggle.no-transition .jk-toggle__pill {
  transition: none;
}

.jk-toggle__opt {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-family: var(--eki-font);
  font-size: var(--eki-font-size-sm);
  font-weight: var(--eki-weight-400);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: var(--eki-space-sm) var(--eki-space-md);
  background: transparent;
  border: none;
  cursor: pointer;
  color: var(--eki-text-dim);
  min-height: 32px;
  white-space: nowrap;
  transition: color var(--eki-duration-normal) var(--eki-ease-soft);
}

.jk-toggle__opt:hover {
  color: var(--eki-text);
}

.jk-toggle__opt.is-active {
  color: var(--eki-active-text);
  font-weight: var(--eki-weight-600);
}

.jk-toggle__opt .jk-toggle__icon {
  width: 16px;
  height: 16px;
  font-size: 16px;
  flex-shrink: 0;
}

.jk-toggle__label.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Icon-only modifier — tighter padding */
.jk-toggle--icon-only .jk-toggle__opt {
  padding: var(--eki-space-xs) var(--eki-space-sm);
}

/* Direction toggle — inside sidebar */
.jk-sidebar__dir {
  display: flex;
  flex: 1;
}

.jk-sidebar__dir .jk-toggle {
  flex: 1;
}

.jk-sidebar__dir .jk-toggle__opt {
  flex: 1;
  text-align: center;
  font-size: 10px;
  padding: var(--eki-space-xs) var(--eki-space-sm);
}

.jk-sidebar__dir .jk-toggle__icon {
  width: 14px;
  height: 14px;
  font-size: 14px;
}

/* Share button — icon-only, mirrors the theme toggle's chip footprint. */
.jk-share-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--eki-space-xs) var(--eki-space-sm);
  cursor: pointer;
  color: var(--eki-text-muted);
  min-width: var(--eki-min-touch);
  min-height: 32px;
}

.jk-share-btn__icon {
  font-size: 20px;
}

.jk-share-btn:hover {
  color: var(--eki-text);
}

.jk-share-btn.sp-share--copied {
  animation: copied-flash var(--eki-duration-normal) var(--eki-ease-soft);
}

/* ==========================================================================
   Alert bar — Black band
   ========================================================================== */
.jk-alerts {
  background: var(--eki-active-bg);
  color: var(--eki-active-text);
  padding: var(--eki-space-md) var(--eki-space-xl);
  font-size: var(--eki-font-size-sm);
  display: flex;
  align-items: center;
  gap: var(--eki-space-md);
  font-weight: var(--eki-weight-500);
}

.jk-alert-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  background: var(--eki-alert-accent);
  color: #fff;
  font-weight: var(--eki-weight-900);
  font-size: 13px;
  flex-shrink: 0;
  line-height: 1;
}

.jk-alert-routes {
  display: inline-flex;
  gap: 3px;
  margin-right: var(--eki-space-sm);
}

/* ==========================================================================
   Two-Column Layout
   ========================================================================== */
.jk-layout {
  display: grid;
  grid-template-columns: var(--eki-sidebar-width) 1fr;
  flex: 1;
}

.jk-sidebar {
  position: sticky;
  top: var(--eki-header-height, 0px);
  height: calc(100dvh - var(--eki-header-height, 0px));
  overflow: hidden;
  border-right: 1px solid var(--eki-border);
  background: var(--eki-bg);
  padding: var(--eki-space-md);
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* Dock panel — flex column on every viewport. Pinned chrome (groups bar
   + direction toggle on top, add-actions on bottom) flanks a scrolling
   stations list in the middle. The mobile @media block layers in the
   morph fade and padding; this base rule defines the layout. */
.jk-dock__panel {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow: hidden;
  gap: var(--eki-space-md);
}

.jk-groups-bar-host,
.jk-direction-toggle-host,
.jk-view-mode-toggle-host,
.jk-add-actions {
  flex-shrink: 0;
}

.jk-sidebar__add-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-family: var(--eki-font);
  font-size: var(--eki-font-size-sm);
  font-weight: var(--eki-weight-600);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: var(--eki-space-sm) var(--eki-space-md);
  background: var(--eki-active-bg);
  color: var(--eki-active-text);
  border: none;
  cursor: pointer;
  min-height: var(--eki-min-touch);
  width: 100%;
  transition: opacity var(--eki-duration-normal);
}

.jk-sidebar__add-btn .material-symbols-rounded {
  font-size: 18px;
}

.jk-sidebar__add-btn:hover {
  opacity: 0.85;
}

.jk-sidebar__add-btn.is-pulsed {
  animation: add-pulse var(--eki-duration-normal) var(--eki-ease-soft);
}

/* Add-action row: holds either one button or two side-by-side. */
.jk-add-actions {
  display: flex;
  flex-direction: column;
  gap: var(--eki-space-sm);
}

/* Direction toggle host — permanently mounted; the toggle gets a
   disabled state when no stations are selected. */
.jk-direction-toggle-host {
  display: flex;
}

.jk-direction-toggle-host:empty {
  display: none;
}

/* View-mode toggle host — single icon button, sits beside the
   direction toggle in the dock. Hidden when empty so the gap above
   the stations list collapses. */
.jk-view-mode-toggle-host {
  display: flex;
  justify-content: flex-end;
}

.jk-view-mode-toggle-host:empty {
  display: none;
}

.jk-view-mode-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--eki-space-xs) var(--eki-space-sm);
  min-height: var(--eki-min-touch);
  min-width: var(--eki-min-touch);
  font-family: var(--eki-font);
  color: var(--eki-text-dim);
  cursor: pointer;
}

.jk-view-mode-btn:hover {
  color: var(--eki-text);
}

.jk-view-mode-btn[aria-pressed='true'] {
  color: var(--eki-active-text);
  background: var(--eki-active-bg);
}

.jk-view-mode-btn .material-symbols-rounded {
  font-size: 18px;
}

.jk-sidebar__dir.is-disabled {
  opacity: 0.4;
  pointer-events: none;
}

/* Long-press / right-click menu on a group chip. */
.jk-group-menu {
  display: flex;
  flex-direction: column;
  background: var(--eki-bg);
  border: 1px solid var(--eki-border);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
  z-index: var(--eki-z-popover);
  min-width: 160px;
  font-family: var(--eki-font);
}

.jk-group-menu__item {
  text-align: left;
  font-family: inherit;
  font-size: var(--eki-font-size-sm);
  padding: var(--eki-space-sm) var(--eki-space-md);
  background: transparent;
  color: var(--eki-text);
  border: none;
  cursor: pointer;
  white-space: nowrap;
}

.jk-group-menu__item:hover {
  background: var(--eki-surface);
}

.jk-group-menu__item--danger {
  color: var(--eki-alert-accent, #c00);
}

.jk-sidebar__stations {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* Scope per-frame row collapse/expand to this scroller — the parent
     dock-panel is overflow:hidden with a fixed flex slot, so ancestors
     can't be affected either way. */
  contain: layout paint;
}

/* Single-row list item: [badges] [name] [×]. Generous vertical padding
   keeps each row's tap target comfortable; the gap separates the
   badge cluster from the name and from the remove button.

   Add/remove uses the dock-collapse tokens above. Inner stays on
   opacity + transform — no filter — so the per-frame budget goes to
   the layout-heavy max-height transition. */
.jk-sidebar-station {
  display: flex;
  align-items: center;
  gap: var(--eki-space-md);
  padding: var(--eki-space-md) var(--eki-space-xs);
  border-bottom: 1px solid var(--eki-border);
  min-width: 0;
  overflow: hidden;
  max-height: 120px;
  transition:
    max-height var(--eki-dock-collapse-duration) var(--eki-dock-collapse-ease),
    padding-top var(--eki-dock-collapse-duration) var(--eki-dock-collapse-ease),
    padding-bottom var(--eki-dock-collapse-duration) var(--eki-dock-collapse-ease),
    border-bottom-color var(--eki-dock-collapse-duration) var(--eki-dock-collapse-ease);
}

.jk-sidebar-station > * {
  transition:
    opacity var(--eki-dock-fade-duration) ease-out var(--eki-dock-handoff-delay),
    transform var(--eki-dock-scale-duration) ease-out var(--eki-dock-handoff-delay);
}

.jk-sidebar-station.is-removing {
  pointer-events: none;
  transition:
    max-height var(--eki-dock-collapse-duration) var(--eki-dock-collapse-ease)
      var(--eki-dock-handoff-delay),
    padding-top var(--eki-dock-collapse-duration) var(--eki-dock-collapse-ease)
      var(--eki-dock-handoff-delay),
    padding-bottom var(--eki-dock-collapse-duration) var(--eki-dock-collapse-ease)
      var(--eki-dock-handoff-delay),
    border-bottom-color var(--eki-dock-collapse-duration) var(--eki-dock-collapse-ease)
      var(--eki-dock-handoff-delay);
}

.jk-sidebar-station.is-removing > * {
  transition:
    opacity var(--eki-dock-fade-duration) ease-in,
    transform var(--eki-dock-scale-duration) ease-in;
}

.jk-sidebar-station.is-entering,
.jk-sidebar-station.is-removing {
  max-height: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-bottom-color: transparent;
  will-change: max-height;
}

.jk-sidebar-station.is-entering > *,
.jk-sidebar-station.is-removing > * {
  opacity: 0;
  transform: scale(0.92);
  will-change: opacity, transform;
}

@media (prefers-reduced-motion: reduce) {
  .jk-sidebar-station,
  .jk-sidebar-station > *,
  .jk-sidebar-station.is-removing,
  .jk-sidebar-station.is-removing > * {
    transition: none;
  }
}

.jk-sidebar-station__lines {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--eki-space-xs);
  flex-shrink: 0;
}

.jk-sidebar-station__name {
  flex: 1;
  font-size: var(--eki-font-size-sm);
  font-weight: var(--eki-weight-700);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.jk-sidebar-station__remove {
  background: none;
  border: none;
  cursor: pointer;
  font-size: var(--eki-font-size-lg);
  color: var(--eki-text-dim);
  padding: 0 var(--eki-space-xs);
  line-height: 1;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.jk-sidebar-station__remove .material-symbols-rounded {
  font-size: 20px;
}

.jk-sidebar-station__remove:hover {
  color: var(--eki-alert-accent);
}

/* Toggleable line badge in sidebar */
.jk-sidebar-station__lines .mta-badge {
  cursor: pointer;
  transition:
    opacity var(--eki-duration-normal),
    filter var(--eki-duration-normal);
}

.jk-sidebar-station__lines .mta-badge--disabled {
  opacity: 0.3;
  filter: grayscale(100%);
}

.mta-badge--not-today {
  opacity: 0.35;
}

/* ==========================================================================
   Station group chips — preset switcher above the stations list
   ========================================================================== */
.jk-groups-bar-host {
  display: flex;
  flex-direction: column;
}

.jk-groups-bar {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--eki-space-sm);
  overflow-x: auto;
  scrollbar-width: none;
  /* Vertical padding gives the dragged chip's drop-shadow room before
     `overflow-x: auto` clips it (per CSS spec, any non-visible overflow
     on one axis clips the other too). Negative margin offsets the
     padding so the bar's visual position stays put. */
  padding: 10px 0;
  margin: -8px 0;
  /* Scope chip add/remove layout cost — max-width + margin-inline
     transitions can't reflow ancestors. */
  contain: layout paint;
}

.jk-groups-bar::-webkit-scrollbar {
  display: none;
}

/* Wrapper holds the visual chrome (border, radius, bg). Real <button>
   children inside handle interaction — keeps the DOM free of nested
   focusable elements. */
.jk-group-chip {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  font-family: var(--eki-font);
  /* 16px so the inline edit input (which inherits) doesn't trip iOS
     Safari's font-size-16 auto-zoom on focus. Padding below is sized
     to keep chip height comparable to other dock controls. */
  font-size: 16px;
  font-weight: var(--eki-weight-600);
  color: var(--eki-text);
  background: var(--eki-bg);
  border: 1px solid var(--eki-border);
  border-radius: 999px;
  white-space: nowrap;
  /* Chips are interactive controls; suppress iOS text-selection halo
     (visible during drag) and the long-press callout menu. The edit
     <input> below re-enables selection inside its own scope. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  /* Default cap so max-width can transition during is-entering. The
     chip's intrinsic content width still wins as the upper bound. */
  max-width: 640px;
  transition:
    background var(--eki-duration-fast) var(--eki-ease-soft),
    color var(--eki-duration-fast) var(--eki-ease-soft),
    border-color var(--eki-duration-fast) var(--eki-ease-soft),
    opacity var(--eki-duration-fast) var(--eki-ease-soft),
    max-width var(--eki-dock-collapse-duration) var(--eki-dock-collapse-ease),
    border-inline-width var(--eki-dock-collapse-duration) var(--eki-dock-collapse-ease),
    margin-inline var(--eki-dock-collapse-duration) var(--eki-dock-collapse-ease);
}

/* Mirrors the station add/remove pattern, with margin-inline pulled
   negative to absorb the parent's flex `gap` so adjacent chips close
   all the way in once this one is gone. */
.jk-group-chip > * {
  transition:
    opacity var(--eki-dock-fade-duration) ease-out var(--eki-dock-handoff-delay),
    transform var(--eki-dock-scale-duration) ease-out var(--eki-dock-handoff-delay);
}

.jk-group-chip.is-removing {
  pointer-events: none;
  transition:
    max-width var(--eki-dock-collapse-duration) var(--eki-dock-collapse-ease)
      var(--eki-dock-handoff-delay),
    border-inline-width var(--eki-dock-collapse-duration) var(--eki-dock-collapse-ease)
      var(--eki-dock-handoff-delay),
    margin-inline var(--eki-dock-collapse-duration) var(--eki-dock-collapse-ease)
      var(--eki-dock-handoff-delay);
}

.jk-group-chip.is-removing > * {
  transition:
    opacity var(--eki-dock-fade-duration) ease-in,
    transform var(--eki-dock-scale-duration) ease-in;
}

.jk-group-chip.is-entering,
.jk-group-chip.is-removing {
  max-width: 0;
  border-inline-width: 0;
  margin-inline: calc(-0.5 * var(--eki-space-sm));
  overflow: hidden;
  will-change: max-width;
}

.jk-group-chip.is-entering > *,
.jk-group-chip.is-removing > * {
  opacity: 0;
  transform: scale(0.92);
  will-change: opacity, transform;
}

@media (prefers-reduced-motion: reduce) {
  .jk-group-chip,
  .jk-group-chip > *,
  .jk-group-chip.is-removing,
  .jk-group-chip.is-removing > * {
    transition: none;
  }
}

.jk-group-chip__input {
  user-select: text;
  -webkit-user-select: text;
}

@media (hover: hover) {
  .jk-group-chip:hover {
    border-color: var(--eki-text-muted);
  }
}

.jk-group-chip--active {
  background: var(--eki-active-bg);
  color: var(--eki-active-text);
  border-color: var(--eki-active-bg);
}

.jk-group-chip--add {
  border-style: dashed;
  color: var(--eki-text-muted);
}

.jk-group-chip--empty:not(.jk-group-chip--active) {
  color: var(--eki-text-muted);
  border-style: dashed;
}

.jk-group-chip.is-loading {
  opacity: 0.6;
  cursor: progress;
}

.jk-group-chip.is-disabled {
  opacity: 0.45;
}

.jk-group-chip__select,
.jk-group-chip__remove {
  font: inherit;
  color: inherit;
  background: transparent;
  border: 0;
  cursor: pointer;
}

.jk-group-chip__select {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border-radius: 999px;
}

/* When a delete button follows, drop the select's right padding so the
   chip's overall width matches the no-remove case and the gap between
   label and × stays consistent. */
.jk-group-chip:has(.jk-group-chip__remove) .jk-group-chip__select {
  padding-right: 4px;
}

.jk-group-chip__select:focus-visible,
.jk-group-chip__remove:focus-visible {
  outline: 2px solid var(--eki-active-bg);
  outline-offset: 2px;
}

.jk-group-chip__icon {
  font-size: 16px;
  line-height: 1;
  display: inline-block;
}

.jk-group-chip__select:disabled {
  cursor: not-allowed;
}

.jk-group-chip__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 4px 10px 4px 4px;
  font-size: 16px;
  line-height: 1;
  opacity: 0.7;
  border-radius: 999px;
}

.jk-group-chip__remove .material-symbols-rounded {
  font-size: 18px;
}

.jk-group-chip__remove:hover {
  opacity: 1;
}

/* Reorder handle (item 26a). Lives on the LEFT of saved-group chips
   and is the only surface that initiates a drag. Pointer events on the
   handle don't reach the select button, so rename and reorder don't
   compete. Sized to match the right-side × so chips stay symmetric. */
.jk-group-chip__handle {
  font: inherit;
  color: inherit;
  background: transparent;
  border: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 4px 4px 4px 10px;
  font-size: 14px;
  line-height: 1;
  opacity: 0.5;
  cursor: grab;
  touch-action: none;
  border-radius: 999px;
}

.jk-group-chip__handle .material-symbols-rounded {
  font-size: 18px;
}

.jk-group-chip__handle:hover {
  opacity: 0.85;
}

.jk-group-chip__handle:focus-visible {
  outline: 2px solid var(--eki-active-bg);
  outline-offset: 2px;
}

.jk-group-chip--dragging {
  cursor: grabbing;
  z-index: 1;
  opacity: 0.85;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
}

.jk-group-chip--dragging .jk-group-chip__handle {
  cursor: grabbing;
}

/* Duplicate-name soft-warn (item 26c). Border picks up the same alert
   tint as the armed-delete state — visual continuity with the bar's
   other "armed" affordance. */
.jk-group-chip--warn-duplicate {
  border-color: var(--eki-state-error);
}

/* Inline edit (rename / new-group naming). The wrapper stays a single
   visual pill — input on the left, ✓ / × buttons on the right. The
   dashed --add border is overridden to solid here so a chip in edit
   mode reads as "active interaction", not "empty placeholder". */
.jk-group-chip--editing,
.jk-group-chip--add.jk-group-chip--editing {
  border-style: solid;
  padding-right: 2px;
}

/* Input inherits the chip's typography so the text reads identically
   to a regular chip label. The chip's 16px font also satisfies iOS
   Safari's auto-zoom-on-focus threshold, so no viewport hack needed.
   `field-sizing: content` (Chrome 123+, Firefox 130+) lets the input
   auto-grow with the typed name; older browsers fall back to the
   `min-width` value as a static width. */
.jk-group-chip__input {
  font: inherit;
  color: inherit;
  background: transparent;
  border: 0;
  outline: 0;
  padding: 4px 4px 4px 12px;
  field-sizing: content;
  min-width: 6ch;
  max-width: 18ch;
}

.jk-group-chip__input:focus,
.jk-group-chip__input:focus-visible {
  outline: 0;
}

.jk-group-chip__confirm,
.jk-group-chip__cancel {
  font: inherit;
  color: inherit;
  background: transparent;
  border: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 4px 8px;
  font-size: var(--eki-font-size-md);
  line-height: 1;
  border-radius: 999px;
}

.jk-group-chip__confirm .material-symbols-rounded,
.jk-group-chip__cancel .material-symbols-rounded {
  font-size: 18px;
}

.jk-group-chip__confirm {
  font-weight: var(--eki-weight-700);
}

.jk-group-chip__cancel {
  opacity: 0.7;
}

.jk-group-chip__cancel:hover {
  opacity: 1;
}

.jk-group-chip__confirm:focus-visible,
.jk-group-chip__cancel:focus-visible {
  outline: 2px solid var(--eki-active-bg);
  outline-offset: 2px;
}

/* Armed × — first tap on the delete button puts the chip into a
   "confirming" state; second tap commits. The × is replaced with a
   "?" glyph and the chip border picks up an alert tint so the user
   sees the change of mode. */
.jk-group-chip--confirm-delete {
  border-color: var(--eki-state-error);
}

.jk-group-chip__remove.is-armed {
  opacity: 1;
  color: var(--eki-state-error);
  font-weight: var(--eki-weight-700);
}

/* Toast — used for groups feedback (location denied, no nearby, etc.) */
.jk-toast {
  position: fixed;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 24px);
  transform: translateX(-50%) translateY(8px);
  z-index: var(--eki-z-modal);
  max-width: calc(100vw - 32px);
  padding: 10px 16px;
  font-family: var(--eki-font);
  font-size: var(--eki-font-size-sm);
  background: var(--eki-active-bg);
  color: var(--eki-active-text);
  border-radius: 999px;
  /* Force the message + optional action button onto a single line. The
     toast is a pill, not a multi-line snackbar. */
  display: inline-flex;
  align-items: center;
  gap: 12px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition:
    opacity var(--eki-duration-normal) var(--eki-ease-soft),
    transform var(--eki-duration-normal) var(--eki-ease-soft),
    visibility 0s linear var(--eki-duration-normal);
}

.jk-toast.is-visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateX(-50%) translateY(0);
  transition:
    opacity var(--eki-duration-normal) var(--eki-ease-soft),
    transform var(--eki-duration-normal) var(--eki-ease-soft),
    visibility 0s;
}

/* Inline action button on the toast (item 26b "Undo"). Sits to the right
   of the message text within the same pill; flex `gap` handles the
   separation. */
.jk-toast__message {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.jk-toast__action {
  font: inherit;
  color: inherit;
  background: transparent;
  border: 0;
  padding: 4px 8px;
  font-weight: var(--eki-weight-700);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  cursor: pointer;
  border-radius: 999px;
}

.jk-toast__action:hover {
  background: rgba(255, 255, 255, 0.12);
}

.jk-toast__action:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

/* ==========================================================================
   Mobile bottom sheet — pill, backdrop, drag handle
   Hidden on desktop; activated by mobile media query below.
   ========================================================================== */
.jk-sheet__handle {
  display: none;
}

.jk-dock__pill-label {
  display: none;
}

.jk-sheet-backdrop {
  display: none;
}

/* ==========================================================================
   Search Modal — Editorial "Find Station"
   ========================================================================== */
.jk-modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--eki-overlay-tint);
  -webkit-backdrop-filter: var(--eki-overlay-blur);
  backdrop-filter: var(--eki-overlay-blur);
  z-index: var(--eki-z-modal);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 15vh;
  animation: content-fade-in var(--eki-duration-slow) var(--eki-ease-soft);
}

.jk-modal {
  background: var(--eki-surface);
  width: 90%;
  max-width: var(--eki-modal-max-width);
  border: 1px solid var(--eki-border);
  position: relative;
  padding: var(--eki-space-xl) var(--eki-space-xl) var(--eki-space-lg);
}

.jk-modal.is-opening {
  animation: modal-arrive var(--eki-duration-entrance) var(--eki-ease-gentle-out);
}

.jk-modal-overlay.is-closing {
  animation: overlay-fade-out var(--eki-duration-exit) var(--eki-ease-soft) forwards;
}

.jk-modal-overlay.is-closing .jk-modal {
  animation: modal-depart var(--eki-duration-exit) var(--eki-ease-soft) forwards;
}

.jk-modal__title {
  font-size: clamp(var(--eki-font-size-xl), 5vw, var(--eki-font-size-2xl));
  font-weight: var(--eki-weight-900);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: 1.05;
  color: var(--eki-text);
  margin: 0 0 var(--eki-space-lg) 0;
}

.jk-modal__close {
  position: absolute;
  top: var(--eki-space-lg);
  right: var(--eki-space-lg);
  background: none;
  border: none;
  cursor: pointer;
  font-size: 24px;
  color: var(--eki-text-muted);
  line-height: 1;
  padding: 0;
  min-width: var(--eki-min-touch);
  min-height: var(--eki-min-touch);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.jk-modal__close .material-symbols-rounded {
  font-size: 24px;
}

.jk-modal__close:hover {
  color: var(--eki-text);
}

.jk-modal__body {
  padding: 0;
  min-height: clamp(240px, 38vh, 380px);
}

.jk-modal__body .sp-selected {
  display: none;
}

.jk-modal__hint {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: var(--eki-space-xs);
  padding-top: var(--eki-space-lg);
  font-size: var(--eki-font-size-xs);
  font-weight: var(--eki-weight-500);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--eki-text-dim);
  margin: 0;
}

.jk-modal__hint kbd {
  font-family: var(--eki-font);
  font-weight: var(--eki-weight-600);
  border: 1px solid var(--eki-border);
  padding: 1px 6px;
  border-radius: var(--eki-radius-sm);
  font-size: inherit;
}

@media (pointer: coarse) {
  .jk-modal__hint {
    display: none;
  }
}

@keyframes modal-arrive {
  from {
    opacity: 0;
    transform: scale(0.997);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes modal-depart {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

@keyframes overlay-fade-out {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

@keyframes section-enter {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes content-fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes hour-block-enter {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes add-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.6;
  }
}

@keyframes copied-flash {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.6;
  }
}

/* ==========================================================================
   Station picker — Underline input, stacked results
   ========================================================================== */
.sp-picker {
  position: relative;
  max-width: var(--eki-picker-max-width);
}

.sp-input {
  appearance: none;
  -webkit-appearance: none;
  font-family: var(--eki-font);
  font-size: var(--eki-font-size-lg);
  font-weight: var(--eki-weight-500);
  padding: var(--eki-space-md) 0;
  border: 0;
  border-bottom: 2px solid var(--eki-active-bg);
  border-radius: 0;
  box-shadow: none;
  background: transparent;
  width: 100%;
  min-height: var(--eki-min-touch);
  outline: 0;
  color: var(--eki-text);
}

.sp-input:focus {
  outline: 0;
  box-shadow: none;
}

.sp-input::placeholder {
  color: var(--eki-text-dim);
  font-weight: var(--eki-weight-400);
}

.sp-results {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  background: var(--eki-surface);
  border: none;
  border-bottom: 2px solid var(--eki-active-bg);
  list-style: none;
  z-index: var(--eki-z-dropdown);
  max-height: var(--eki-dropdown-max-height);
  overflow-y: auto;
}

/* Inside modal: inline flow, edge-to-edge hover */
.jk-modal__body .sp-results {
  position: static;
  border: none;
  background: transparent;
  z-index: auto;
  margin: 0 calc(-1 * var(--eki-space-xl));
  max-height: 320px;
}

.sp-result {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto auto;
  column-gap: var(--eki-space-md);
  row-gap: 2px;
  padding: var(--eki-space-md) var(--eki-space-xl);
  cursor: pointer;
  min-height: var(--eki-min-touch);
  border-bottom: 1px solid var(--eki-border);
  transition:
    background var(--eki-duration-fast),
    color var(--eki-duration-fast);
}

.sp-result:last-child {
  border-bottom: none;
}

.sp-result:hover,
.sp-result--active {
  background: var(--eki-active-bg);
  color: var(--eki-active-text);
}

.sp-result:hover .sp-result-name,
.sp-result--active .sp-result-name,
.sp-result:hover .sp-result-borough,
.sp-result--active .sp-result-borough {
  color: inherit;
}

.sp-result-name {
  grid-column: 1;
  grid-row: 1;
  font-size: var(--eki-font-size-lg);
  font-weight: var(--eki-weight-700);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sp-result-borough {
  grid-column: 1;
  grid-row: 2;
  font-size: var(--eki-font-size-sm);
  font-weight: var(--eki-weight-500);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--eki-text-muted);
}

.sp-result-badges {
  grid-column: 2;
  grid-row: 1 / -1;
  align-self: center;
  display: flex;
  gap: 2px;
  flex-shrink: 0;
}

/* ==========================================================================
   Empty state — Bauhaus geometric composition
   ========================================================================== */
.jk-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 80px var(--eki-space-xl);
  min-height: 320px;
}

.jk-empty__fun {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}

/* Fun fact — label + rotating text + route progress bar */
.jk-empty__fact {
  display: flex;
  flex-direction: column;
  gap: var(--eki-space-sm);
}

.jk-empty__fact-label {
  display: block;
  font-size: var(--eki-font-size-md);
  font-weight: var(--eki-weight-600);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--eki-text-dim);
  margin-bottom: 0;
}

.jk-empty__fact-text {
  font-size: 18px;
  font-weight: var(--eki-weight-400);
  color: var(--eki-text-muted);
  line-height: 1.5;
  min-height: calc(2 * 18px * 1.5);
  max-width: 34ch;
  text-align: center;
  transition: opacity var(--eki-duration-slower) ease;
}

.jk-empty__fact-text.is-fading {
  opacity: 0;
}

/* Train scene — subway map route */
.jk-empty__scene {
  position: relative;
  max-width: none;
  margin-top: 0;
  width: 100%;
  min-width: 0;
}

.jk-empty__map {
  display: block;
  width: 100%;
  height: auto;
}

.jk-empty__river {
  stroke: color-mix(in srgb, #6eb6ee 70%, var(--eki-bg));
  stroke-width: 22;
  opacity: 0.95;
}

.jk-empty__river-core {
  stroke: color-mix(in srgb, #3f8fd3 78%, var(--eki-bg));
  stroke-width: 10;
  opacity: 0.95;
}

.jk-empty__route-bg {
  stroke: var(--eki-border);
}

.jk-empty__route-color {
  stroke-dasharray: 500;
  stroke-dashoffset: 500;
}

.jk-empty__scene.is-running .jk-empty__route-color {
  animation: routeDraw 10s linear forwards;
}

@keyframes routeDraw {
  to {
    stroke-dashoffset: 0;
  }
}

.jk-empty__stop {
  fill: var(--eki-bg);
  stroke: var(--eki-text);
}

.jk-empty__label {
  font-size: 9px;
  fill: var(--eki-text-muted);
  font-family: var(--eki-font);
}

/* ==========================================================================
   Main timetable area
   ========================================================================== */
.jk-main {
  padding: 0 var(--eki-main-padding) 48px;
  display: flex;
  flex-direction: column;
  overflow-x: clip;
}

/* Suppress paint while initial chunks render and scroll-to-now lands —
   without this the user sees today's hour 00 flash before the view jumps
   to the current hour. */
.jk-main--prescroll {
  visibility: hidden;
}

.jk-date-feed-wrap {
  margin: 0 calc(-1 * var(--eki-main-padding));
  animation: content-fade-in var(--eki-duration-slow) var(--eki-ease-soft);
}

.jk-date-feed {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.jk-date-feed__empty {
  padding: 20px var(--eki-main-padding);
  color: var(--eki-text-muted);
  font-size: var(--eki-font-size-sm);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.jk-date-feed__sentinel {
  padding: 18px var(--eki-main-padding) 8px;
  color: var(--eki-text-dim);
  font-size: var(--eki-font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  text-align: center;
}

.jk-date-feed__sentinel--done {
  color: var(--eki-text-muted);
}

.jk-date-section {
  position: relative;
  animation: section-enter var(--eki-duration-slow) var(--eki-ease-gentle-out) both;
}

.jk-date-section__sticky {
  position: sticky;
  top: var(--eki-sticky-offset);
  z-index: var(--eki-z-sticky);
  background: var(--eki-active-bg);
  color: var(--eki-active-text);
}

.jk-date-section__header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--eki-space-md);
  min-height: 42px;
  padding: 10px var(--eki-main-padding);
}

.jk-date-section__date {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
}

.jk-date-section__monthday {
  font-weight: var(--eki-weight-800);
  font-size: 24px;
  line-height: 1;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
}

.jk-date-section__weekday {
  font-size: var(--eki-font-size-xs);
  font-weight: var(--eki-weight-600);
  letter-spacing: 0.14em;
  opacity: 0.85;
  white-space: nowrap;
}

.jk-date-section__meta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 18px;
}

.jk-date-section__live-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--eki-state-error);
}

.jk-date-section__live-text {
  font-size: var(--eki-font-size-xs);
  font-weight: var(--eki-weight-600);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.jk-date-alert {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  min-width: 0;
}

.jk-date-alert .jk-alert-icon {
  width: 16px;
  height: 16px;
  font-size: 11px;
  margin-top: 1px;
}

.jk-date-alert .jk-alert-routes {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-right: 0;
  flex-shrink: 0;
}

.jk-date-alert .jk-alert-routes .mta-badge--sm {
  width: 16px;
  height: 16px;
  font-size: 9px;
}

.jk-date-alert__text {
  font-size: 11px;
  line-height: 1.25;
  color: color-mix(in srgb, var(--eki-active-text) 88%, transparent);
}

.jk-date-section__body {
  padding: 12px 0 0;
}

.jk-date-section__empty {
  padding: 18px var(--eki-main-padding) 28px;
  color: var(--eki-text-muted);
  font-size: var(--eki-font-size-sm);
}

/* ==========================================================================
   Timetable — Poster grid layout
   ========================================================================== */
.jk-table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.jk-poster {
  display: flex;
  flex-direction: column;
  padding-top: var(--eki-space-md);
}

.jk-poster--date-feed {
  padding-top: 0;
}

/* Hour block — grid: large number | departure rows */
.jk-hour-block {
  display: grid;
  grid-template-columns: 92px 1fr;
  gap: 0 32px;
  padding: 28px var(--eki-main-padding);
  border-bottom: 1px solid var(--eki-border);
  animation: hour-block-enter var(--eki-duration-slow) var(--eki-ease-gentle-out) both;
}

.jk-hour-block:last-of-type {
  border-bottom: none;
}

.jk-hour-block:first-of-type {
  padding-top: 16px;
}

/* Hour label — big number + small AM/PM */
.jk-hour-block__label {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  padding-top: 4px;
}

.jk-hour-block__number {
  font-family: var(--eki-font-num);
  font-size: clamp(48px, 6vw, 80px);
  font-weight: var(--eki-weight-800);
  line-height: 1;
  color: var(--eki-active-bg);
  font-variant-numeric: tabular-nums;
}

.jk-hour-block__period {
  font-size: var(--eki-font-size-xs);
  font-weight: var(--eki-weight-600);
  color: var(--eki-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-top: 6px;
}

/* Departure rows container */
.jk-hour-block__lines {
  display: grid;
  row-gap: 12px;
  padding-top: 10px;
}

/* Single departure row: badge + (station) + minutes */
.jk-departure-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  min-height: 22px;
}

.jk-departure-row__station {
  font-size: 9px;
  font-weight: var(--eki-weight-500);
  color: var(--eki-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 80px;
  flex-shrink: 0;
}

.jk-departure-row__mins {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-family: var(--eki-font-num);
  font-size: 14px;
  line-height: 1.4;
  font-variant-numeric: tabular-nums;
}

/* Minute spans (shared across layouts) */
.jk-min {
  display: inline-block;
  width: 24px;
  text-align: center;
  transition: color var(--eki-duration-normal) ease;
}

.jk-min--colored {
  font-weight: var(--eki-weight-700);
}

.jk-min--delayed {
  background-color: var(--eki-alert-accent);
  color: #fff;
  font-weight: var(--eki-weight-700);
  border-radius: var(--eki-radius-sm);
  transition: background-color var(--eki-duration-normal) ease;
}

.jk-min--delayed-minor {
  background-color: var(--eki-state-warning);
  color: #fff;
  font-weight: var(--eki-weight-700);
  border-radius: var(--eki-radius-sm);
  transition: background-color var(--eki-duration-normal) ease;
}

.jk-min--early {
  background-color: var(--eki-state-info-bright);
  color: #fff;
  font-weight: var(--eki-weight-700);
  border-radius: var(--eki-radius-sm);
  transition: background-color var(--eki-duration-normal) ease;
}

.jk-min--cancelled {
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
  opacity: 0.4;
}

.jk-min--express-diamond {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  font-weight: var(--eki-weight-700);
  border: 0;
  outline: 0;
  transform: rotate(45deg);
  border-radius: var(--eki-radius-sm);
  line-height: 1;
  margin: 0 2px;
  transition: background-color var(--eki-duration-normal) ease;
}

.jk-min--express-diamond.jk-min--delayed {
  background-color: var(--eki-alert-accent);
}

.jk-min--express-diamond.jk-min--delayed-minor {
  background-color: var(--eki-state-warning);
}

.jk-min--express-diamond.jk-min--early {
  background-color: var(--eki-state-info-bright);
}

/* Counter-rotated inner span carries the strike so it reads horizontally. */
.jk-min--express-diamond.jk-min--cancelled > span {
  text-decoration: line-through;
}

.jk-min--express-diamond > span {
  transform: rotate(-45deg);
  display: block;
  font-size: 10px;
}

/* Past minute dimming */
.jk-min--past {
  opacity: 0.4;
}

/* Added: trip exists in realtime but not in the static schedule
   (schedule_relationship: ADDED, or trip-id join failed). Visually
   distinct from baseline scheduled cells so users know it's not on the
   timetable. */
.jk-min--added {
  outline: 1px dashed currentColor;
  outline-offset: 1px;
}

.jk-min {
  touch-action: manipulation;
}

.jk-min-tooltip {
  /* Absolute (page-coord), not fixed — set top/left in page coordinates
     (rect + window.scrollY) and the tooltip scrolls naturally with the
     cell it annotates, no scroll listener needed. */
  position: absolute;
  top: 0;
  left: 0;
  z-index: var(--eki-z-floating);
  background: var(--eki-active-bg);
  color: var(--eki-active-text);
  padding: var(--eki-space-xs) var(--eki-space-sm);
  border-radius: var(--eki-radius-sm);
  font-size: 11px;
  font-weight: var(--eki-weight-700);
  pointer-events: none;
  box-shadow:
    0 4px 12px rgb(0 0 0 / 16%),
    0 1px 3px rgb(0 0 0 / 8%);
  white-space: nowrap;
  transition: opacity var(--eki-duration-normal) ease;
}

.jk-min-tooltip[hidden] {
  display: none;
}

/* Current-hour clock chip. Descendant selector wins over the base
   .jk-hour-block__period rule (which sets text-transform: uppercase). */
.jk-hour-block--current .jk-hour-block__period {
  align-self: flex-start;
  background: var(--eki-active-bg);
  color: var(--eki-active-text);
  border-radius: var(--eki-radius-pill);
  padding: var(--eki-space-xs) var(--eki-space-sm);
  margin-top: var(--eki-space-sm);
  font-weight: var(--eki-weight-700);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
  text-transform: none;
  white-space: nowrap;
  opacity: 1;
}

/* Poster footer — "UPDATED HH:MM AM" */
.jk-poster__footer {
  padding: 24px 0 8px;
  text-align: right;
}

.jk-poster__update {
  font-size: var(--eki-font-size-xs);
  font-weight: var(--eki-weight-600);
  color: var(--eki-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* ==========================================================================
   Alert toggle — Clickable meta area in date header
   ========================================================================== */
.jk-date-section__alert-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  color: inherit;
  font-family: var(--eki-font);
  font-size: var(--eki-font-size-xs);
  font-weight: var(--eki-weight-600);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: var(--eki-radius-sm);
  min-height: 18px;
  white-space: nowrap;
  transition: opacity var(--eki-duration-fast) var(--eki-ease-soft);
}

.jk-date-section__alert-toggle > :not(.jk-date-section__live-dot) {
  align-self: baseline;
}

.jk-date-section__alert-toggle:hover {
  opacity: 0.75;
}

.jk-date-section__alert-toggle[disabled] {
  cursor: default;
  opacity: 1;
}

.jk-date-section__alert-count {
  font-size: var(--eki-font-size-xs);
  font-weight: var(--eki-weight-600);
  letter-spacing: 0.06em;
}

.jk-date-section__alerts {
  display: grid;
  gap: 8px;
  padding: 0 var(--eki-main-padding) 12px;
  overflow: hidden;
  transition:
    max-height var(--eki-duration-normal) var(--eki-ease-soft),
    opacity var(--eki-duration-normal) var(--eki-ease-soft),
    padding var(--eki-duration-normal) var(--eki-ease-soft);
}

.jk-date-section__alerts.jk-date-section__alerts--collapsed {
  max-height: 0;
  min-height: 0;
  margin: 0;
  padding-top: 0;
  padding-bottom: 0;
  border: 0;
  opacity: 0;
}

/* ==========================================================================
   Focus styles
   ========================================================================== */
:focus-visible {
  outline: 2px solid var(--eki-active-bg);
  outline-offset: 2px;
}

/* ==========================================================================
   Accessibility
   ========================================================================== */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ==========================================================================
   Responsive
   ========================================================================== */

/* Tablet */
@media (max-width: 1023px) {
  .jk-header {
    padding: calc(env(safe-area-inset-top, 0px) + 12px) var(--eki-main-padding-tablet) 10px;
  }

  .jk-main {
    padding: 0 var(--eki-main-padding-tablet) var(--eki-space-lg);
  }

  .jk-empty {
    padding: 60px var(--eki-main-padding-tablet);
  }

  .jk-date-feed-wrap {
    margin: 0 calc(-1 * var(--eki-main-padding-tablet));
  }

  .jk-date-section__header {
    padding: 10px var(--eki-main-padding-tablet);
  }

  .jk-date-section__alerts {
    padding: 0 var(--eki-main-padding-tablet) 12px;
  }

  .jk-hour-block {
    padding-left: var(--eki-main-padding-tablet);
    padding-right: var(--eki-main-padding-tablet);
  }
}

/* Mobile */
@media (max-width: 768px) {
  .jk-layout {
    grid-template-columns: 1fr;
  }

  /* ====================================================================
     Morphing dock — single surface morphs between collapsed (pill) and
     expanded (panel). Two endpoint states are defined here; JS drives
     intermediate values inline during a drag, then clears them on
     release so the CSS transition handles the spring. Curve is Vaul's
     cubic-bezier(0.32, 0.72, 0, 1).

     The fade-with-visibility token pair (--dock-fade-show / -hide) is
     reused on the pill label, panel, handle, and backdrop: visibility
     flips instantly when entering, and after the morph duration when
     leaving, so the element keeps absorbing clicks for the full fade.
     ==================================================================== */
  .jk-sidebar {
    --dock-morph-duration: 450ms;
    --dock-morph-ease: cubic-bezier(0.32, 0.72, 0, 1);
    --dock-fade-show:
      opacity var(--dock-morph-duration) var(--dock-morph-ease), visibility 0s linear 0s;
    --dock-fade-hide:
      opacity var(--dock-morph-duration) var(--dock-morph-ease),
      visibility 0s linear var(--dock-morph-duration);

    /* Collapsed (pill) — default */
    position: fixed;
    /* Reset desktop sticky's `top: var(--eki-header-height)` — leaving it set
       alongside `bottom` would stretch the dock between the two anchors and
       pin it to the header. */
    top: auto;
    left: 50%;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 12px);
    transform: translateX(-50%);
    width: 220px;
    max-width: calc(100vw - 24px);
    height: var(--eki-min-touch);
    z-index: var(--eki-z-modal);

    background: var(--eki-active-bg);
    color: var(--eki-active-text);
    border: 0;
    /* Fixed pixel radius (≥ half the pill height) reads as a perfect pill at
       44px tall and as a rounded-rect at the expanded size. Animating
       border-radius: 999px would clamp to min(w,h)/2 every frame and make
       the shape look like a circle through the morph. */
    border-radius: 22px;
    overflow: hidden;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    box-shadow:
      0 4px 12px rgba(0, 0, 0, 0.16),
      0 1px 3px rgba(0, 0, 0, 0.08);

    display: flex;
    flex-direction: column;
    gap: 0;
    padding: 0;

    /* Capture vertical drags ourselves; flipped to pan-y once expanded so the
       panel can scroll internally. */
    touch-action: none;

    transition:
      bottom var(--dock-morph-duration) var(--dock-morph-ease),
      width var(--dock-morph-duration) var(--dock-morph-ease),
      height var(--dock-morph-duration) var(--dock-morph-ease),
      background-color var(--dock-morph-duration) var(--dock-morph-ease),
      color var(--dock-morph-duration) var(--dock-morph-ease),
      box-shadow var(--dock-morph-duration) var(--dock-morph-ease);
  }

  .jk-sidebar[data-state='expanded'] {
    bottom: calc(env(safe-area-inset-bottom, 0px) + 12px);
    width: min(calc(100vw - 24px), 600px);
    height: min(75dvh, 480px);
    background: var(--eki-surface);
    color: var(--eki-text);
    box-shadow:
      0 16px 48px rgba(0, 0, 0, 0.24),
      0 1px 3px rgba(0, 0, 0, 0.08);
    touch-action: pan-y;
  }

  .jk-sidebar[data-state='dragging'] {
    transition: none;
  }

  /* Pill label — centered text in the collapsed state. Uses visibility +
     opacity (not display:none) so tap toggles animate the fade instead of
     popping. Visibility transitions with 0s duration and a delay so the
     hidden state only takes effect after the opacity fade completes. */
  .jk-dock__pill-label {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 0 22px;
    font-size: var(--eki-font-size-sm);
    font-weight: var(--eki-weight-600);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: inherit;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;
    visibility: visible;
    opacity: 1;
    transition: var(--dock-fade-show);
  }

  .jk-dock__pill-icon {
    font-size: 18px;
  }

  .jk-dock__pill-label[data-mode='info'] .jk-dock__pill-icon {
    display: none;
  }

  .jk-sidebar[data-state='expanded'] .jk-dock__pill-label {
    opacity: 0;
    visibility: hidden;
    transition: var(--dock-fade-hide);
  }

  /* Panel content wrapper — base flex layout lives at module scope so
     desktop and mobile share it. Mobile-only: padding (with safe-area
     offsets), and the visibility/opacity fade for the morph. */
  .jk-dock__panel {
    padding: var(--eki-space-md) var(--eki-main-padding-mobile)
      calc(var(--eki-space-md) + env(safe-area-inset-bottom, 0px));
    visibility: hidden;
    opacity: 0;
    transition: var(--dock-fade-hide);
  }

  .jk-sidebar[data-state='expanded'] .jk-dock__panel,
  .jk-sidebar[data-state='dragging'] .jk-dock__panel {
    visibility: visible;
    transition: var(--dock-fade-show);
  }

  .jk-sidebar[data-state='expanded'] .jk-dock__panel {
    opacity: 1;
  }

  /* Drag handle — same visibility/opacity pattern as the panel. Inline opacity
     during drag (via JS) gates it on a sequential-fade timing so small drags
     don't reveal the handle. */
  .jk-sheet__handle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    min-height: 28px;
    padding: 10px 0 6px;
    margin: 0;
    background: transparent;
    border: 0;
    cursor: grab;
    touch-action: none;
    flex-shrink: 0;
    visibility: hidden;
    opacity: 0;
    transition: var(--dock-fade-hide);
  }

  .jk-sidebar[data-state='expanded'] .jk-sheet__handle,
  .jk-sidebar[data-state='dragging'] .jk-sheet__handle {
    visibility: visible;
    transition: var(--dock-fade-show);
  }

  .jk-sidebar[data-state='expanded'] .jk-sheet__handle {
    opacity: 1;
  }

  .jk-sheet__handle:active {
    cursor: grabbing;
  }

  .jk-sheet__handle-grip {
    display: block;
    width: 36px;
    height: 4px;
    border-radius: 2px;
    background: var(--eki-border);
    transition: background var(--eki-duration-fast) var(--eki-ease-soft);
  }

  /* :hover scoped to actual hover-capable devices — without the @media gate,
     iOS Safari triggers :hover on first tap and only fires click on second
     tap (the "hover-lock on touch" quirk). :focus-visible stays unscoped so
     keyboard users still get the affordance. */
  @media (hover: hover) {
    .jk-sheet__handle:hover .jk-sheet__handle-grip {
      background: var(--eki-text-muted);
    }
  }

  .jk-sheet__handle:focus-visible .jk-sheet__handle-grip {
    background: var(--eki-text-muted);
  }

  /* Backdrop dim — visibility transitions on a delay so the backdrop keeps
     receiving clicks throughout the close animation. iOS Safari needs an
     explicit cursor:pointer on non-button click targets to fire reliably. */
  .jk-sheet-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: calc(var(--eki-z-modal) - 1);
    background: var(--eki-overlay-tint);
    -webkit-backdrop-filter: var(--eki-overlay-blur);
    backdrop-filter: var(--eki-overlay-blur);
    cursor: pointer;
    visibility: hidden;
    opacity: 0;
    transition: var(--dock-fade-hide);
  }

  .jk-sheet-backdrop.is-open {
    visibility: visible;
    opacity: 1;
    transition: var(--dock-fade-show);
  }

  .jk-sidebar__add-btn {
    width: 100%;
  }

  .jk-header {
    padding: calc(env(safe-area-inset-top, 0px) + 10px) var(--eki-main-padding-mobile) 8px;
    gap: var(--eki-space-sm);
  }

  .jk-main {
    /* Bottom padding clears the floating pill (44px tall + 12px offset above
       safe-area) plus the iOS Safari URL bar (env(safe-area-inset-bottom)). */
    padding: 0 var(--eki-main-padding-mobile)
      calc(var(--eki-min-touch) + 12px + env(safe-area-inset-bottom, 0px));
  }

  .jk-empty {
    padding: var(--eki-space-xl) var(--eki-main-padding-mobile);
  }

  .jk-date-feed-wrap {
    margin: 0 calc(-1 * var(--eki-main-padding-mobile));
  }

  .jk-hour-block {
    grid-template-columns: 60px 1fr;
    padding: 18px var(--eki-main-padding-mobile);
    gap: 0 12px;
  }

  .jk-hour-block__number {
    font-size: clamp(36px, 8vw, 48px);
  }

  /* Tighter chip padding on mobile so HH:MM:SS fits inside the 60px label
     column without leaking into the inter-column gap. */
  .jk-hour-block--current .jk-hour-block__period {
    padding: var(--eki-space-xs);
    letter-spacing: 0.04em;
  }

  .jk-date-section__header {
    padding: 8px var(--eki-main-padding-mobile);
    min-height: 38px;
  }

  .jk-date-section__monthday {
    font-size: 20px;
  }

  .jk-date-section__weekday {
    letter-spacing: 0.1em;
  }

  .jk-date-section__alerts {
    padding: 0 var(--eki-main-padding-mobile) 10px;
    gap: 6px;
  }

  .jk-date-alert__text {
    font-size: 10px;
  }

  .jk-departure-row__mins {
    font-size: 12px;
  }

  .jk-alerts {
    padding: var(--eki-space-md) var(--eki-main-padding-mobile);
  }

  .mta-badge--lg {
    width: 36px;
    height: 36px;
    font-size: 18px;
  }

  .mta-badge--md {
    width: 24px;
    height: 24px;
    font-size: 12px;
  }
}

/* Small mobile */
@media (max-width: 480px) {
  .jk-toggle__opt {
    font-size: 10px;
    padding: var(--eki-space-xs) var(--eki-space-sm);
  }
}

/* ==========================================================================
   Jump-to-now pill — surfaces when the current hour leaves the viewport.
   Visual style mirrors the collapsed dock pill (.jk-sidebar collapsed state):
   filled --eki-active-bg, no border, 22px radius, same shadow. Keep the two
   pills' styles in sync.
   ========================================================================== */
.jk-jump-now {
  position: fixed;
  /* Sit just below the full sticky stack (header + date sticky bar). The
     stack height is computed in setStickyOffset() and updated on resize and
     after each timetable render. */
  top: calc(var(--eki-sticky-stack, 60px) + var(--eki-space-sm));
  left: 50%;
  transform: translate(-50%, calc(-1 * var(--eki-space-sm)));
  z-index: var(--eki-z-dropdown);

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--eki-space-xs);
  /* Compact secondary affordance — below --eki-min-touch (44px) but big
     enough to read at a glance. No system token for this height today. */
  height: 28px;
  padding: 0 var(--eki-space-md);
  border: 0;
  border-radius: var(--eki-radius-pill);
  background: var(--eki-active-bg);
  color: var(--eki-active-text);
  font-family: var(--eki-font);
  font-size: var(--eki-font-size-sm);
  font-weight: var(--eki-weight-600);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: var(--eki-shadow-floating);

  opacity: 0;
  pointer-events: none;
  transition:
    opacity var(--eki-duration-normal) var(--eki-ease-soft),
    transform var(--eki-duration-normal) var(--eki-ease-soft);
}

.jk-jump-now:not([hidden]) {
  opacity: 1;
  transform: translate(-50%, 0);
  pointer-events: auto;
}

.jk-jump-now:hover {
  filter: brightness(1.05);
}

.jk-jump-now:focus-visible {
  outline: 2px solid var(--eki-active-bg);
  outline-offset: 2px;
}

.jk-jump-now__arrow {
  font-size: 16px;
  line-height: 1;
}

/* ==========================================================================
   Reduced motion
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}
