:root {
  --bg: #0f172a;
  --panel: #111827;
  --card: #ffffff;
  --ink: #0f172a;
  --muted: #64748b;
  --line: #7dd3fc;
  --accent: #0284c7;
  --chip: #e0f2fe;
  --border: #dbe4ee;
}

* { box-sizing: border-box; }
html, body {
  margin: 0;
  padding: 0;
  /* Inter is loaded via Google Fonts in index.html; the stack keeps a robust
     fallback while it downloads. Modular type scale: 12 / 14 / 16 / 20 / 28. */
  font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, Arial, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  background: #f8fafc;
  color: var(--ink);
  height: 100%;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
body.panel-open { overflow: hidden; }
body {
  display: flex;
  flex-direction: column;
}

.hero {
  background: linear-gradient(135deg, #082f49, #0f172a);
  color: white;
  padding: 16px 24px 14px;
  position: sticky;
  top: 0;
  z-index: 20;
}
.contribute-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  background: #38bdf8;
  color: #0f172a;
  font-weight: 600;
  font-size: 14px;
  text-decoration: none;
  border-radius: 999px;
  box-shadow: 0 6px 16px rgba(56, 189, 248, 0.35);
  transition: transform 0.15s ease, background 0.15s ease;
  white-space: nowrap;
}
.contribute-btn:hover { background: #7dd3fc; transform: translateY(-1px); }
.contribute-btn span { font-size: 16px; line-height: 1; }
.hero-inner {
  display: flex;
  gap: 24px;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
}
.hero-copy {
  min-width: 0;
  flex: 1 1 420px;
}
/* Right-side action group in the header: docked institutional logo above the
   contribute pill. Replaces the previous fixed-position floating white box in
   the bottom-left corner, which permanently overlapped the timeline canvas. */
.hero-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
  flex-shrink: 0;
}
.header-logo {
  display: block;
  width: min(260px, 32vw);
  height: auto;
  background: transparent;
  padding: 0;
  border-radius: 0;
  box-shadow: none;
}
.eyebrow {
  margin: 0 0 4px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 12px;
  font-weight: 600;
  color: #bae6fd;
}
.hero h1 { margin: 0 0 6px; font-size: clamp(20px, 2.6vw, 28px); font-weight: 700; letter-spacing: -0.01em; }
.intro { max-width: 900px; margin: 0; color: #dbeafe; font-size: 14px; line-height: 1.5; }

.controls {
  display: grid;
  gap: 16px;
  grid-template-columns: 1.2fr 2fr;
  padding: 18px 24px;
  background: white;
  border-bottom: 1px solid var(--border);
  /* No sticky here — the hero above already sticks at top:0. A second
     sticky element at the same offset causes the two to visually collide
     on scroll. .controls is part of the normal document flow. */
  position: relative;
  z-index: 19;
}

/* Visually hidden but announced by assistive tech. Used by #a11yLive. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
.search-wrap { display: grid; gap: 6px; font-weight: 600; font-size: 14px; }
/* Wraps the input + a leading magnifying-glass icon + a trailing clear button
   so the search field can be styled like a modern research-tool input. The
   `has-value` class is toggled from JS whenever the input is non-empty. */
.search-input-wrap {
  position: relative;
  display: block;
}
.search-icon {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  color: #94a3b8;
  pointer-events: none;
}
.search-wrap input {
  padding: 12px 40px 12px 40px;
  border-radius: 12px;
  border: 1px solid var(--border);
  font-size: 14px;
  font-family: inherit;
  width: 100%;
}
/* Hide the default "x" that some browsers add to <input type="search"> so it
   doesn't clash with our custom .search-clear button. */
.search-wrap input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.search-clear {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  width: 24px;
  height: 24px;
  border-radius: 999px;
  border: 0;
  background: #e2e8f0;
  color: #334155;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  line-height: 1;
  font-family: inherit;
}
.search-clear[hidden] { display: none; }
.search-clear:hover { background: #cbd5e1; }
.filter-label { display: block; margin-bottom: 8px; font-weight: 600; font-size: 14px; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
/* Category chips take an accent color from `--cat-color` (set in JS from a
   stable per-category palette). All chips get a matching tinted border and
   text; the active chip inverts to a solid pill. */
.chip {
  border: 1px solid var(--cat-color, #cbd5e1);
  background: white;
  color: var(--cat-color, #0f172a);
  padding: 8px 14px;
  border-radius: 999px;
  cursor: pointer;
  font-size: 14px;
  font-family: inherit;
  font-weight: 600;
  text-transform: capitalize;
  transition: background 0.15s ease, color 0.15s ease, transform 0.15s ease;
}
.chip:hover { background: color-mix(in srgb, var(--cat-color, #cbd5e1) 12%, white); }
.chip.active { background: var(--cat-color, var(--accent)); border-color: var(--cat-color, var(--accent)); color: white; }
/* Per-chip entry count badge. Sits inline after the label so users can gauge
   category density before clicking. Inherits color from the chip. */
.chip-count {
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: 999px;
  background: color-mix(in srgb, currentColor 15%, transparent);
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
}
.chip.active .chip-count { background: rgba(255, 255, 255, 0.24); }

.help-fab-wrap {
  /* Docked to the bottom-left of the viewport, opposite the zoom toolbar.
     The previous top-right anchor overlapped the sticky "Contribute a
     reference" button in the hero on most screen widths. Sits above the
     minimap strip (62 px) and footer (~40 px) with a 16 px gap. */
  position: fixed;
  bottom: 120px;
  left: 20px;
  z-index: 70;
}
body:has(.detail-panel.open) .help-fab-wrap { display: none; }
.help-fab {
  width: 42px;
  height: 42px;
  border-radius: 999px;
  border: 1px solid #cbd5e1;
  background: white;
  color: #0f172a;
  cursor: pointer;
  font-weight: 800;
  font-size: 18px;
  box-shadow: 0 8px 20px rgba(15, 23, 42, 0.14);
}
.help-popup {
  /* Opens upward from the FAB now that the wrap sits at the bottom-left. */
  position: absolute;
  bottom: 50px;
  left: 0;
  width: min(360px, 82vw);
  background: white;
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 14px 16px;
  box-shadow: 0 14px 30px rgba(15, 23, 42, 0.16);
}
.help-popup h3 {
  margin: 0 24px 10px 0;
  font-size: 16px;
  font-weight: 700;
}
.help-popup p {
  margin: 0 0 10px;
  font-size: 14px;
  line-height: 1.5;
  color: #334155;
}
.help-popup p:last-child {
  margin-bottom: 0;
}
/* Keyboard shortcut hint pill inside the help popup. Replaces the standalone
   `.timeline-help` line that used to sit above the canvas and cluttered it. */
.keyboard-hint {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  font-size: 13px;
  color: #475569;
  line-height: 1.9;
}
.kbd {
  display: inline-block;
  padding: 1px 6px;
  border: 1px solid #cbd5e1;
  border-bottom-width: 2px;
  border-radius: 5px;
  background: #f8fafc;
  color: #0f172a;
  font-family: 'Inter', 'Segoe UI', system-ui, monospace;
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  vertical-align: baseline;
}
.kbd-sep { margin: 0 6px; color: #cbd5e1; }
.help-close {
  position: absolute;
  top: 8px;
  right: 8px;
  border: 0;
  background: #e2e8f0;
  width: 28px;
  height: 28px;
  border-radius: 999px;
  cursor: pointer;
}

main {
  padding: 12px 0 20px;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
.timeline-shell {
  position: relative;
  overflow: hidden;
  height: 100%;
}
.timeline {
  position: relative;
  overflow: auto;
  padding: 10px 24px 24px;
  cursor: grab;
  /* Reserve room for the sticky hero (~110px), the controls row (~90px),
     the minimap strip (~62px), and the site footer strip (~46px) beneath. */
  height: calc(100vh - 350px);
  max-height: none;
  /* When zoomed out, the stage no longer fills the scroll area. Paint the
     scroll container in the same outer tone as the stage's radial gradient so
     the empty margin blends into the canvas instead of showing the page bg. */
  background: #eef2f6;
  /* Themed scrollbars — slim, subtle, brand-neutral. Firefox uses the two
     scrollbar-* properties; WebKit / Blink use the ::-webkit-* pseudos. */
  scrollbar-width: thin;
  scrollbar-color: #cbd5e1 transparent;
}
.timeline::-webkit-scrollbar { width: 10px; height: 10px; }
.timeline::-webkit-scrollbar-track { background: transparent; }
.timeline::-webkit-scrollbar-thumb {
  background: #cbd5e1;
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
.timeline::-webkit-scrollbar-thumb:hover {
  background: #94a3b8;
  background-clip: padding-box;
}
.timeline.dragging,
.timeline:active { cursor: grabbing; }
/* Wrapper keeps the scrollable area sized correctly when the inner stage is
   visually shrunk with CSS transform. The wrap's width/height are the true
   post-scale pixel dimensions; the stage inside is at natural size and gets
   scaled by --timeline-zoom. This replaces the non-standard `zoom` property
   (which Firefox only supports partially and which blurs subpixel content). */
.timeline-stage-wrap {
  position: relative;
  width: calc(var(--stage-w, 0px) * var(--timeline-zoom, 1));
  height: calc(var(--stage-h, 0px) * var(--timeline-zoom, 1));
}
.timeline-stage {
  position: absolute;
  top: 0;
  left: 0;
  min-height: 900px;
  border-radius: 18px;
  /* Subtle radial vignette gives the canvas a paper / studio-light feel
     without competing with the cards. */
  background:
    radial-gradient(ellipse 90% 60% at 50% 0%, #ffffff 0%, #f6f9fc 55%, #eef2f6 100%);
  transform: scale(var(--timeline-zoom, 0.55));
  transform-origin: top left;
}
/* Loading skeleton — shown inside #timeline while the fetch is in flight.
   Opacity animates in after a 200 ms hold so a fast local fetch doesn't
   cause a flash. Cards use a subtle shimmer to signal activity. */
.loading-skeleton {
  position: relative;
  padding: 80px 40px;
  min-height: 320px;
  opacity: 0;
  animation: skeleton-appear 0s linear 200ms forwards;
}
@keyframes skeleton-appear { to { opacity: 1; } }
.skeleton-axis {
  position: absolute;
  left: 24px;
  right: 24px;
  top: 50%;
  height: 48px;
  background: #eef2f6;
  border-radius: 6px;
}
.skeleton-cards {
  position: relative;
  display: flex;
  gap: 40px;
  justify-content: center;
  flex-wrap: wrap;
}
.skeleton-card {
  width: 160px;
  height: 140px;
  border-radius: 10px;
  background: linear-gradient(90deg, #eef2f6 0%, #f8fafc 50%, #eef2f6 100%);
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.4s ease-in-out infinite;
  border: 1px solid #e2e8f0;
}
.skeleton-card:nth-child(2) { animation-delay: 0.2s; }
.skeleton-card:nth-child(3) { animation-delay: 0.4s; }
.skeleton-card:nth-child(4) { animation-delay: 0.6s; }
@keyframes skeleton-shimmer {
  0% { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}

/* -------- Bottom-right zoom toolbar (redesigned from the old top-left column) -------- */
.zoom-toolbar {
  position: absolute;
  bottom: 16px;
  right: 20px;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 2px;
  background: white;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 6px;
  box-shadow: 0 8px 20px rgba(15, 23, 42, 0.14);
}
.zoom-toolbar button {
  width: 32px;
  height: 32px;
  border-radius: 999px;
  border: 0;
  background: transparent;
  color: #0f172a;
  font-size: 16px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s ease;
}
.zoom-toolbar button:hover { background: #f1f5f9; }
.zoom-level {
  min-width: 44px;
  padding: 0 4px;
  text-align: center;
  font-size: 12px;
  font-weight: 600;
  color: #475569;
  font-variant-numeric: tabular-nums;
  border-left: 1px solid var(--border);
  border-right: 1px solid var(--border);
  margin: 0 2px;
  line-height: 1;
}
.timeline-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.connection-line {
  /* Ambient state: barely visible so the timeline doesn't feel like a web of
     noise. Hovering or selecting a card ramps the shared-author lines up to
     full opacity via .active. */
  opacity: 0.15;
  stroke: #94a3b8 !important;
  transition: opacity 0.18s ease, stroke-width 0.18s ease, stroke 0.18s ease, filter 0.18s ease;
}
.connection-line.dimmed {
  opacity: 0.06;
  stroke: #94a3b8 !important;
}
.connection-line.active {
  opacity: 1;
  stroke: var(--active-stroke, #0284c7) !important;
  filter: drop-shadow(0 0 4px rgba(2, 132, 199, 0.55));
}
.timeline-items {
  position: absolute;
  inset: 0;
}
.entry {
  position: absolute;
  width: 210px;
  background: var(--card);
  border: 1px solid #e2e8f0;
  /* Left accent bar tinted per category via --cat-color (set inline in JS).
     Falls back to the neutral border color if no mapping exists. */
  border-left: 3px solid var(--cat-color, #e2e8f0);
  border-radius: 10px;
  padding: 10px;
  box-shadow: 0 4px 14px rgba(15, 23, 42, 0.08);
  cursor: pointer;
  z-index: 2;
  opacity: 1;
  transition: transform 0.18s ease, box-shadow 0.18s ease, z-index 0s, opacity 0.18s ease, filter 0.18s ease;
}
.entry:hover {
  /* Editorial lift: no pixel jump. A slightly brighter surface plus a warmer,
     deeper shadow reads as depth without shifting the layout. Elevate above
     neighbours so partial overlaps don't hide the hovered card. */
  background: #fdfefe;
  border-color: #cbd5e1;
  box-shadow: 0 12px 22px rgba(2, 8, 23, 0.14);
  z-index: 15;
}
.entry.active {
  z-index: 30;
  transform: scale(1.12);
  box-shadow: 0 18px 36px rgba(2, 8, 23, 0.22);
  opacity: 1;
}
.entry.related {
  opacity: 1;
  box-shadow: 0 10px 20px rgba(2, 132, 199, 0.18);
}
.entry.dimmed {
  opacity: 0.12;
  filter: grayscale(0.5);
}
.entry-images {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  gap: 3px;
  margin-top: 6px;
}
.entry-images img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  background: #e2e8f0;
  display: block;
}
.entry-image-placeholder {
  width: 100%;
  aspect-ratio: 1 / 1;
  background: #e2e8f0;
}
.detail-image-placeholder {
  width: 100%;
  aspect-ratio: 1 / 1;
  background: #e2e8f0;
  border-radius: 12px;
}
.entry img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  background: #e2e8f0;
  margin-top: 6px;
}
/* Card typography: aligned to the 12 / 14 / 16 / 20 / 28 modular scale.
   Italic preview was dropped — italics at small sizes are hard to read. */
.entry-year { color: var(--cat-color, var(--accent)); font-weight: 800; margin: 0 0 4px; font-size: 12px; letter-spacing: 0.04em; }
.entry h3 { margin: 0 0 6px; font-size: 14px; line-height: 1.3; font-weight: 700; }
.entry p { margin: 0 0 5px; color: #334155; font-size: 12px; line-height: 1.4; }
.entry-preview { text-align: center; margin-top: 6px; font-style: normal; color: #475569; }
.entry button {
  border: 0;
  background: #0f172a;
  color: white;
  padding: 6px 10px;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 600;
  font-size: 12px;
  font-family: inherit;
}
.year-label {
  /* Year label sits inside the 48 px year band. Bumped from 16 to 20 px so
     it feels proportional to the thicker band. */
  font-size: 20px;
  font-weight: 700;
  text-anchor: middle;
  dominant-baseline: middle;
  font-family: 'Inter', 'Segoe UI', system-ui, sans-serif;
}
.empty-state {
  margin: 20px 24px;
  padding: 20px;
  background: white;
  border: 1px dashed var(--border);
  border-radius: 16px;
  color: var(--muted);
}

/* -------- Detail panel ---------------------------------------------------
   Off-canvas dialog that slides in from the right. Uses a fixed sticky
   header (year + category pill + close) and a scrollable body so the
   metadata stays anchored regardless of image count. */
.detail-panel {
  position: fixed;
  top: 0;
  right: -520px;
  width: min(520px, 94vw);
  height: 100vh;
  background: white;
  border-left: 1px solid var(--border);
  box-shadow: -12px 0 36px rgba(15, 23, 42, 0.18);
  transition: right 0.28s cubic-bezier(0.22, 0.61, 0.36, 1);
  z-index: 50;
  overflow: hidden;               /* scroll happens inside .detail-body */
  display: flex;
  flex-direction: column;
}
.detail-panel.open { right: 0; }
#detailContent { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.detail-card { display: flex; flex-direction: column; height: 100%; min-height: 0; }

/* Sticky header — year, category pill, title, close */
.detail-header {
  padding: 20px 24px 16px;
  border-bottom: 1px solid var(--border);
  background:
    linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
  flex-shrink: 0;
}
.detail-header-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}
.detail-category-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--cat-color, #334155);
  background: color-mix(in srgb, var(--cat-color, #cbd5e1) 14%, white);
  border: 1px solid color-mix(in srgb, var(--cat-color, #cbd5e1) 40%, transparent);
}
.detail-category-pill::before {
  content: '';
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--cat-color, #94a3b8);
}
.detail-close {
  width: 36px;
  height: 36px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: white;
  color: #334155;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
  flex-shrink: 0;
}
.detail-close:hover {
  background: #f1f5f9;
  border-color: #cbd5e1;
  color: #0f172a;
}
.detail-close:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.detail-header-year {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 6px;
}
.detail-title {
  margin: 0;
  font-size: 20px;
  font-weight: 700;
  line-height: 1.3;
  color: #0f172a;
  letter-spacing: -0.01em;
}
.detail-refimg {
  margin: 10px 0 0;
  color: #64748b;
  font-size: 12px;
  font-style: italic;
}

/* Scrollable body */
.detail-body {
  padding: 20px 24px 32px;
  overflow-y: auto;
  flex: 1;
  min-height: 0;
  scrollbar-width: thin;
  scrollbar-color: #cbd5e1 transparent;
}
.detail-body::-webkit-scrollbar { width: 8px; }
.detail-body::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 999px; }

/* Images — grid, capped height so metadata stays in view. */
.detail-images {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-bottom: 20px;
}
.detail-images-single { grid-template-columns: 1fr; }
.detail-image {
  width: 100%;
  max-height: 220px;
  object-fit: cover;
  border-radius: 10px;
  background: #eef2f6;
  border: 1px solid var(--border);
  transition: transform 0.2s ease;
}
.detail-images-single .detail-image { max-height: 320px; object-fit: contain; background: #f8fafc; }
.detail-image:hover { transform: scale(1.02); }

/* Section headings */
.detail-section {
  margin-bottom: 18px;
}
.detail-section-heading {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #64748b;
  margin: 0 0 8px;
}
.detail-description {
  margin: 0;
  color: #334155;
  font-size: 14px;
  line-height: 1.55;
}

/* Meta blocks — flat rows with subtle icon accent */
.detail-meta {
  display: grid;
  gap: 8px;
  margin-bottom: 20px;
}
.meta-block {
  padding: 12px 14px;
  border-radius: 10px;
  background: #f8fafc;
  border: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.meta-block strong {
  display: block;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #64748b;
}
.meta-block span {
  font-size: 13px;
  color: #0f172a;
  line-height: 1.45;
  word-break: break-word;
}

/* Action buttons */
.detail-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 4px;
}
.detail-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 16px;
  border-radius: 10px;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
  transition: background 0.15s ease, transform 0.1s ease, box-shadow 0.15s ease;
}
.detail-btn:active { transform: translateY(1px); }
.detail-btn-primary {
  background: var(--accent);
  color: white;
  box-shadow: 0 4px 12px rgba(2, 132, 199, 0.24);
}
.detail-btn-primary:hover {
  background: #0369a1;
  box-shadow: 0 6px 16px rgba(2, 132, 199, 0.32);
}
.detail-btn-secondary {
  background: white;
  color: #334155;
  border: 1px solid var(--border);
}
.detail-btn-secondary:hover {
  background: #f8fafc;
  border-color: #cbd5e1;
}

@media (max-width: 900px) {
  .controls { grid-template-columns: 1fr; }
  .hero-actions { align-items: flex-start; flex-direction: row-reverse; width: 100%; justify-content: space-between; }
  .header-logo { width: min(220px, 44vw); }
}

/* -------- Card entrance + drag/snap-back (additive, does not alter layout) -------- */
/* Initial hidden state applied in JS before the card is appended, so cards
   never flash at full opacity before the entrance transition starts. */
.entry.entry-enter-init {
  opacity: 0;
  transform: translateY(28px);
}
/* Longer, staggered transition just for the entrance. Removed once the
   entrance finishes so the base .entry transition (0.18s) governs hover /
   .active behavior again. */
.entry.entry-enter-active {
  transition:
    opacity 1560ms cubic-bezier(0.2, 0.7, 0.3, 1) var(--entry-enter-delay, 0ms),
    transform 1860ms cubic-bezier(0.34, 1.35, 0.55, 1) var(--entry-enter-delay, 0ms) !important;
}
.entry.snap-back {
  /* Springy return when the pointer is released after dragging.
     !important beats the base .entry transition (which lists transform first). */
  transition:
    transform 620ms cubic-bezier(0.34, 1.56, 0.64, 1),
    box-shadow 180ms ease,
    opacity 180ms ease,
    filter 180ms ease !important;
}
.entry.dragging {
  transition: none !important;
  cursor: grabbing;
  z-index: 40;
  box-shadow: 0 20px 36px rgba(2, 8, 23, 0.32);
  touch-action: none;
  user-select: none;
}
@media (prefers-reduced-motion: reduce) {
  .entry.entry-enter-active { transition-duration: 200ms !important; }
  .entry.snap-back { transition-duration: 200ms !important; }
}

/* --- Focus rings ---------------------------------------------------------
   A single visible focus treatment for every interactive control. Uses
   :focus-visible so mouse users don't see the ring, but keyboard users
   always do. Outline offset keeps the ring from touching the border.
   -------------------------------------------------------------------------*/
.chip:focus-visible,
.help-fab:focus-visible,
.zoom-toolbar button:focus-visible,
.close-btn:focus-visible,
.entry:focus-visible,
.search-clear:focus-visible,
.contribute-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* On the search input the accent ring sits nicer as a shadow than an outline
   because the input already has a solid border. */
.search-wrap input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* -------- Minimap / density brush ---------------------------------------
   Slim overview strip below the main canvas. One bar per year encodes
   entry count; the translucent brush mirrors the visible slice of the
   main canvas and is drag/click-scrubbable. */
.minimap {
  flex-shrink: 0;
  height: 62px;
  padding: 6px 24px 4px;
  background: white;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  position: relative;
}
.minimap-svg {
  display: block;
  width: 100%;
  height: 100%;
  cursor: pointer;
  touch-action: none;
  user-select: none;
}
.minimap-svg.minimap-dragging { cursor: grabbing; }
.minimap-brush {
  transition: fill 120ms ease;
  pointer-events: none;
}
.minimap-svg:hover .minimap-brush { fill: rgba(2, 132, 199, 0.24); }
/* HTML label overlay — kept out of the SVG so text doesn't stretch with the
   non-uniform preserveAspectRatio. Each span is positioned at the fractional
   x of its tick and centred with translateX(-50%). */
.minimap-labels {
  position: absolute;
  left: 24px;
  right: 24px;
  bottom: 2px;
  height: 12px;
  pointer-events: none;
}
.minimap-label {
  position: absolute;
  bottom: 0;
  transform: translateX(-50%);
  font-family: 'Inter', 'Segoe UI', system-ui, sans-serif;
  font-size: 9px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: #64748b;
  line-height: 1;
  white-space: nowrap;
}
.minimap-label-start { transform: translateX(-50%); }
.minimap-label-end   { transform: translateX(-50%); }

/* -------- Site footer strip ---------------------------------------------
   Slim, non-sticky footer that anchors credit + license + version + last
   updated date at the bottom of the viewport. Sits outside <main>, so it
   docks below the timeline without being scrolled with it. */
.site-footer {
  flex-shrink: 0;
  border-top: 1px solid var(--border);
  background: white;
  color: #64748b;
  font-size: 12px;
  line-height: 1.4;
}
.site-footer-inner {
  padding: 10px 24px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
  max-width: 1600px;
  margin: 0 auto;
}
.site-footer-sep {
  color: #cbd5e1;
  padding: 0 2px;
}
.site-footer-link {
  color: var(--accent);
  text-decoration: none;
  font-weight: 600;
}
.site-footer-link:hover { text-decoration: underline; }
.site-footer-version { font-variant-numeric: tabular-nums; }
.site-footer-updated time { font-variant-numeric: tabular-nums; }

/* -------- Toast notification --------------------------------------------
   Fixed bottom-right pill used to surface non-fatal errors (data load
   failures, etc.). Hidden by default via the [hidden] attribute; JS shows
   it, then triggers the .toast-visible transition. */
.toast {
  position: fixed;
  right: 20px;
  /* Above the minimap strip + footer so it never lands on the overview. */
  bottom: 120px;
  z-index: 90;
  max-width: min(360px, 84vw);
  padding: 12px 16px;
  background: #0f172a;
  color: white;
  border-radius: 12px;
  box-shadow: 0 12px 32px rgba(15, 23, 42, 0.28);
  font-size: 13px;
  line-height: 1.45;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 220ms ease, transform 220ms ease;
  pointer-events: none;
}
.toast[hidden] { display: none; }
.toast.toast-visible {
  opacity: 1;
  transform: translateY(0);
}

/* -------- Print stylesheet ----------------------------------------------
   Flattens the interactive canvas into a single-column, paginated list of
   entries so academics can hand a printout to reviewers. Every entry keeps
   its title, year, category, authors, and description; connections /
   details panel / help FAB / zoom UI are all suppressed. */
@media print {
  @page { margin: 18mm 16mm; size: A4; }

  html, body {
    height: auto;
    overflow: visible;
    background: white;
    color: #0f172a;
    font-size: 11pt;
    line-height: 1.4;
  }
  body { display: block; }

  /* Hide interactive chrome. */
  .hero-actions,
  .controls,
  .help-fab-wrap,
  .zoom-toolbar,
  .detail-panel,
  .toast,
  .minimap,
  .loading-skeleton,
  .timeline-svg,
  .entry-images,
  .entry-preview,
  #a11yLive {
    display: none !important;
  }

  .hero {
    position: static;
    background: white;
    color: #0f172a;
    padding: 0 0 8mm;
    border-bottom: 1pt solid #94a3b8;
    margin-bottom: 6mm;
  }
  .hero h1 { font-size: 18pt; color: #0f172a; }
  .eyebrow { color: #475569; }
  .intro { color: #334155; font-size: 10pt; max-width: none; }

  main {
    padding: 0;
    overflow: visible;
  }
  .timeline-shell { overflow: visible; height: auto; }
  .timeline {
    position: static;
    overflow: visible;
    height: auto;
    padding: 0;
  }

  /* Flatten the transformed / scaled stage back to normal document flow. */
  .timeline-stage-wrap {
    width: auto !important;
    height: auto !important;
  }
  .timeline-stage {
    position: static;
    transform: none !important;
    width: 100% !important;
    height: auto !important;
    background: none;
    border-radius: 0;
    min-height: 0;
  }
  .timeline-items {
    position: static;
    display: block;
  }

  /* Cards become a linear list — one per row, always fully expanded. */
  .entry,
  .entry.entry-enter-init,
  .entry.entry-enter-active {
    position: static !important;
    display: block;
    width: 100% !important;
    min-height: 0 !important;
    margin: 0 0 4mm;
    padding: 4mm 5mm;
    border: 1pt solid #cbd5e1;
    border-left: 3pt solid var(--cat-color, #64748b);
    border-radius: 4pt;
    box-shadow: none !important;
    transform: none !important;
    opacity: 1 !important;
    page-break-inside: avoid;
    break-inside: avoid;
    background: white;
  }
  .entry h3 {
    margin: 0 0 2mm;
    font-size: 12pt;
    color: #0f172a;
  }
  .entry-year {
    font-weight: 700;
    color: #0f172a;
    font-size: 10pt;
    margin-bottom: 1mm;
  }
  .entry p { margin: 1mm 0; font-size: 10pt; color: #1f2937; }

  .site-footer {
    border-top: 1pt solid #cbd5e1;
    color: #475569;
    padding: 3mm 0 0;
    margin-top: 6mm;
  }
  .site-footer-inner {
    padding: 0;
    max-width: none;
  }

  a, a:visited {
    color: #0f172a;
    text-decoration: underline;
  }
}

