/* GraphOfLife — local viewer styling. Dark by default; the canvas is the star. */

:root {
  --bg: #0b0e13;
  --panel: #141a22;
  --panel-2: #1b232e;
  --border: #26313e;
  --text: #e6edf3;
  --muted: #8fa3b5;
  --accent: #4fb3ff;
  --accent-dim: #2b6f9e;
  --danger: #e5534b;
  --ok: #3fb950;
  --radius: 8px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 14px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
}

h1, h2, h3 { margin: 0; font-weight: 600; }
h1 { font-size: 17px; letter-spacing: 0.3px; }
h2 { font-size: 15px; }
h3 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.8px; color: var(--muted); }

.hint { color: var(--muted); font-weight: 400; font-size: 11px; }

/* ---- top bar ---- */
.topbar {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 10px 18px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 20;
}

.tabs { display: flex; gap: 4px; }

.tab {
  background: transparent;
  border: 1px solid transparent;
  color: var(--muted);
  padding: 5px 14px;
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 13px;
}
.tab:hover { color: var(--text); }
.tab.active { background: var(--panel-2); color: var(--text); border-color: var(--border); }

.run-label { margin-left: auto; color: var(--muted); font-size: 12px; }

/* ---- views ---- */
.view { display: none; padding: 18px; }
.view.active { display: block; }

#view-viewer { padding: 12px; }

.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 16px;
  max-width: 1400px;
}

.panel-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }

/* ---- forms ---- */
.config-form { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 12px; }

fieldset {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 14px 14px;
  margin: 0;
  flex: 1 1 260px;
  min-width: 240px;
}

legend { color: var(--accent); font-size: 11px; text-transform: uppercase; letter-spacing: 0.8px; padding: 0 6px; }

.field { margin-bottom: 10px; }
.field.wide { flex: 1 1 100%; }
.field label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 3px; }
.field small { display: block; color: #6b7c8d; font-size: 10.5px; margin-top: 2px; }
.field.checkbox label { display: flex; align-items: center; gap: 6px; color: var(--text); }

input[type="text"], input[type="number"], select {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 6px 8px;
  border-radius: 6px;
  font-size: 13px;
  font-family: inherit;
}
input:focus, select:focus { outline: none; border-color: var(--accent-dim); }

input[type="color"] { width: 100%; height: 28px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 2px; }
input[type="range"] { width: 100%; accent-color: var(--accent); }

.derived { color: var(--muted); font-size: 11px; margin: 8px 0 0; line-height: 1.45; }

.form-actions { flex: 1 1 100%; display: flex; align-items: center; gap: 10px; }
.form-error { color: var(--danger); font-size: 12px; }
.form-error.ok { color: var(--ok); }

button {
  background: var(--panel-2);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 6px 14px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 13px;
  font-family: inherit;
}
button:hover:not(:disabled) { border-color: var(--accent-dim); }
button:disabled { opacity: 0.4; cursor: not-allowed; }
button.primary { background: var(--accent-dim); border-color: var(--accent-dim); }
button.primary:hover { background: var(--accent); }
button.danger:hover { border-color: var(--danger); color: var(--danger); }
button.ghost { background: transparent; }
button.small { padding: 4px 9px; font-size: 12px; }

/* ---- run list ---- */
.run-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 12px; }
.empty { color: var(--muted); }

.run-card {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 14px;
}

.run-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; }
.run-id { color: #62717f; font-size: 11px; font-family: ui-monospace, monospace; }

.status {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  padding: 2px 8px;
  border-radius: 20px;
  border: 1px solid var(--border);
  color: var(--muted);
  white-space: nowrap;
}
.status-running { color: var(--ok); border-color: var(--ok); }
.status-error { color: var(--danger); border-color: var(--danger); }
.status-extinct { color: #d29922; border-color: #d29922; }
.status-interrupted { color: #d29922; border-color: #6e5494; }
.status-stopping { color: #d29922; border-color: #d29922; }

/* A worker only notices a stop request between iterations, so the button and
   the status chip both spin until it actually stops. Without this the click
   looks as though it did nothing. */
.spin {
  display: inline-block;
  width: 9px; height: 9px;
  margin-right: 6px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: notice-spin 0.8s linear infinite;
  vertical-align: -1px;
}
.run-actions button[disabled] { opacity: 0.7; cursor: default; }

.run-meta { display: flex; flex-wrap: wrap; gap: 10px; margin: 9px 0; font-size: 11.5px; color: var(--muted); }
.run-meta b { color: var(--text); font-weight: 600; }

.run-error {
  background: #2a1416;
  border: 1px solid #5c2a2a;
  color: #ffb4ab;
  font-size: 10.5px;
  padding: 6px 8px;
  border-radius: 6px;
  max-height: 90px;
  overflow: auto;
  white-space: pre-wrap;
}

.run-actions { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; align-items: center; }

/* ---- viewer layout ---- */
.viewer-layout { display: grid; grid-template-columns: 1fr 268px; gap: 12px; align-items: start; }

.canvas-column { min-width: 0; }

.canvas-wrap {
  position: relative;
  background: #0d1117;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  height: min(64vh, 720px);
}

#graphCanvas { width: 100%; height: 100%; display: block; cursor: grab; }
#graphCanvas:active { cursor: grabbing; }

.canvas-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  pointer-events: none;
  text-align: center;
  padding: 20px;
}

.hover-card {
  position: absolute;
  background: rgba(12, 16, 22, 0.94);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 7px 9px;
  font-size: 11.5px;
  pointer-events: none;
  line-height: 1.5;
  white-space: nowrap;
  z-index: 5;
}
.hover-card.hidden { display: none; }

/* ---- playbar ---- */
.playbar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 9px 10px;
  margin-top: 10px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.playbar button { padding: 4px 10px; min-width: 34px; }
#frameSlider { flex: 1; min-width: 80px; }
.frame-label { color: var(--muted); font-size: 11.5px; white-space: nowrap; }
.speed { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 11.5px; white-space: nowrap; }
.speed input { width: 76px; }

/* ---- stats ---- */
.stats-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}
.stat {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 10px;
  min-width: 88px;
}
.stat-key { display: block; font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
.stat-val { display: block; font-size: 15px; font-variant-numeric: tabular-nums; }

.charts { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-top: 12px; }
.charts figure { margin: 0; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 10px; }
.charts figcaption { font-size: 11.5px; color: var(--muted); margin-bottom: 6px; }
/* Height pinned in CSS rather than left to the element's aspect ratio.
   Sizing the backing store rewrites the width and height attributes, and those
   attributes are what a width:100% canvas derives its height from — so each
   redraw fed the next one and the box drifted to whatever the ratio implied,
   which on a wide column was three times the height asked for. */
.charts canvas { width: 100%; height: 190px; display: block; }

/* ---- controls sidebar ---- */
.controls {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px;
  max-height: calc(100vh - 90px);
  overflow-y: auto;
  position: sticky;
  top: 68px;
}

.control-group { margin-bottom: 16px; }
.control-group h3 { margin-bottom: 8px; }
.control-group label { display: block; font-size: 11.5px; color: var(--muted); margin-bottom: 7px; }
.control-group label.check { display: flex; align-items: center; gap: 6px; color: var(--text); }
.control-group label.check input { width: auto; }
.control-group select { margin-top: 2px; }
.control-row { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 4px; }

@media (max-width: 1000px) {
  .viewer-layout { grid-template-columns: 1fr; }
  .controls { position: static; max-height: none; }
  .charts { grid-template-columns: 1fr; }
}

/* ==========================================================================
   Two-pane layouts with a drag handle
   ========================================================================== */

.runs-layout,
.viewer-layout {
  display: grid;
  grid-template-columns: 1fr 6px 420px;
  gap: 0;
  align-items: start;
}

.resizer {
  align-self: stretch;
  min-height: 200px;
  cursor: col-resize;
  background: transparent;
  position: relative;
}
.resizer::after {
  content: '';
  position: absolute;
  inset: 0 2px;
  background: var(--border);
  border-radius: 2px;
  opacity: 0.5;
  transition: opacity 0.15s, background 0.15s;
}
.resizer:hover::after { opacity: 1; background: var(--accent-dim); }

/* While dragging, stop the pointer selecting text across the page. */
body.resizing { user-select: none; cursor: col-resize; }
body.resizing * { cursor: col-resize !important; }

/* ---- runs view ---- */
.runs-layout .panel { margin-bottom: 0; max-width: none; }
.new-run-panel { min-width: 0; }
.runs-panel { min-width: 0; position: sticky; top: 68px; max-height: calc(100vh - 90px); display: flex; flex-direction: column; }
.runs-panel .run-list { overflow-y: auto; }

/* Everything about a new run visible at once: four columns of settings. */
.config-columns {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  margin-top: 10px;
}
.config-columns fieldset { flex: none; min-width: 0; }
.name-field { max-width: 520px; }
legend.sub {
  display: block;
  color: var(--muted);
  margin: 10px 0 4px;
  padding: 0;
  font-size: 10px;
  border-top: 1px solid var(--border);
  padding-top: 8px;
  width: 100%;
}

.run-list { grid-template-columns: 1fr; }

/* The full configuration of a run, small enough to scan but always present. */
.run-settings {
  display: flex;
  flex-wrap: wrap;
  gap: 3px 9px;
  margin: 7px 0;
  padding: 7px 8px;
  background: rgba(0, 0, 0, 0.22);
  border-radius: 5px;
  font-size: 10px;
  line-height: 1.5;
  color: #b7c6d4;
}
.run-settings i { color: #6b7c8d; font-style: normal; }

button.warn { background: #7a4a12; border-color: #9a5d17; }
button.warn:hover { background: #96591a; }

/* ==========================================================================
   Viewer: collapsible groups, toggles, playbar, stat detail
   ========================================================================== */

details.control-group { border-bottom: 1px solid var(--border); padding-bottom: 8px; }
details.control-group:last-of-type { border-bottom: none; }
details.control-group > summary {
  cursor: pointer;
  list-style: none;
  padding: 7px 2px;
  display: flex;
  align-items: center;
  gap: 6px;
}
details.control-group > summary::-webkit-details-marker { display: none; }
details.control-group > summary::before {
  content: '▸';
  color: var(--muted);
  font-size: 10px;
  transition: transform 0.15s;
}
details.control-group[open] > summary::before { transform: rotate(90deg); }
details.control-group > summary h3 { margin: 0; }
details.control-group > summary:hover h3 { color: var(--text); }
.group-body { padding: 4px 2px 2px; }

.saved-label { margin-top: 10px; }
#savedPresets { width: 100%; margin-bottom: 6px; }
.preset-name { width: 100%; }

/* Segmented toggles */
.view-toggles { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 10px; }
.seg { display: inline-flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
.seg-btn {
  background: var(--panel);
  border: none;
  border-radius: 0;
  padding: 5px 11px;
  font-size: 12px;
  color: var(--muted);
}
.seg-btn + .seg-btn { border-left: 1px solid var(--border); }
.seg-btn:hover { color: var(--text); }
.seg-btn.active { background: var(--accent-dim); color: #fff; }

/* Fixed-width slots so the bar cannot reflow while playing. */
.frame-label {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  font-size: 11.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.fl-iter { min-width: 96px; }
.fl-phase { min-width: 82px; color: #7d8fa0; }
.fl-pos { min-width: 62px; text-align: right; }

/* Statistics are buttons now, since each opens its own explanation. */
.stat {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 10px;
  min-width: 88px;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
}
.stat:hover { border-color: var(--accent-dim); background: var(--panel-2); }

/* Statistic detail popover */
.stat-detail {
  position: fixed;
  right: 24px;
  bottom: 24px;
  width: min(460px, calc(100vw - 48px));
  background: var(--panel);
  border: 1px solid var(--accent-dim);
  border-radius: var(--radius);
  padding: 14px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.55);
  z-index: 40;
}
.stat-detail.hidden { display: none; }
.stat-detail-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.stat-detail-text { font-size: 12px; color: #c2d0dc; line-height: 1.55; margin: 8px 0 10px; }
#statDetailChart { width: 100%; display: block; }

@media (max-width: 1400px) {
  .config-columns { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 1000px) {
  .runs-layout, .viewer-layout { grid-template-columns: 1fr !important; }
  .resizer { display: none; }
  .runs-panel { position: static; max-height: none; }
  .config-columns { grid-template-columns: 1fr; }
}

/* Inline share next to a node-count statistic. */
.stat-val i {
  font-style: normal;
  font-size: 11px;
  color: var(--muted);
  margin-left: 3px;
}
.stat-val { white-space: nowrap; }

/* The Fit view button is sticky, so it needs an on state. */
#btnFit.active { background: var(--accent-dim); color: #fff; }

/* Hover card sections and verdicts. */
.hover-card hr {
  border: none;
  border-top: 1px solid var(--border);
  margin: 6px 0;
}
.hover-card .good { color: var(--ok); }
.hover-card .bad { color: #ff8f7a; }
.hover-card .warnText { color: #d29922; }

/* Many more statistics now, so give the strip a ceiling and let it scroll. */
.stats-strip {
  max-height: 190px;
  overflow-y: auto;
  align-content: flex-start;
}

/* Run picker sits with the view controls rather than in the settings list. */
.run-inline { display: inline-flex; align-items: stretch; gap: 6px; }
.run-inline select {
  width: auto;
  min-width: 190px;
  max-width: 300px;
  font-size: 12px;
  padding: 4px 8px;
}
.seg-btn.standalone {
  border: 1px solid var(--border);
  border-radius: 6px;
}

/* The toggle row carries more now, so let it wrap tidily. */
.view-toggles { row-gap: 8px; }

/* Chart headers: the metric pickers sit in the caption, scales on the right. */
.charts figcaption {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}
.chart-pair { display: inline-flex; gap: 4px; align-items: center; min-width: 0; }
.chart-metric {
  width: auto;
  min-width: 0;
  max-width: 190px;
  flex: 0 1 auto;
  font-size: 11px;
  padding: 2px 4px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
}
.chart-pair .chart-metric { max-width: 130px; }
.axis-toggles { display: inline-flex; gap: 10px; align-items: center; }
.axis-group { display: inline-flex; align-items: center; gap: 3px; color: #6b7c8d; font-size: 10px; }
.axis-btn {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--muted);
  font-size: 10px;
  padding: 1px 5px;
  border-radius: 4px;
  line-height: 1.4;
}
.axis-btn.active { background: var(--accent-dim); color: #fff; border-color: var(--accent-dim); }

/* Statistics, grouped into collapsible categories */
.stats-groups { margin-top: 10px; display: flex; flex-direction: column; gap: 6px; }
.stat-group {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.stat-group > summary {
  cursor: pointer;
  list-style: none;
  padding: 7px 10px;
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.7px;
  color: var(--muted);
}
.stat-group > summary::-webkit-details-marker { display: none; }
.stat-group > summary::before { content: '▸'; font-size: 9px; transition: transform 0.15s; }
.stat-group[open] > summary::before { transform: rotate(90deg); }
.stat-group > summary:hover { color: var(--text); }
.stat-group-count { margin-left: auto; color: #5b6b7c; font-size: 10px; letter-spacing: 0; }
.stat-group-body { display: flex; flex-wrap: wrap; gap: 8px; padding: 0 10px 10px; }
.stat-group .stat { background: var(--panel-2); }

/* Progress bar for a statistics rebuild */
.progress { margin: 4px 0 10px; }
.progress.hidden { display: none; }
.progress-track {
  height: 6px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  overflow: hidden;
}
.progress-fill {
  height: 100%;
  width: 0;
  background: var(--accent);
  border-radius: 3px;
  transition: width 0.2s ease-out;
}
/* Before the server reports a total there is nothing honest to show as a
   fraction, so the bar sweeps instead of claiming a position. */
.progress-fill.indeterminate {
  width: 35%;
  animation: progress-sweep 1.1s ease-in-out infinite;
}
@keyframes progress-sweep {
  0%   { margin-left: -35%; }
  100% { margin-left: 100%; }
}
.progress-label {
  display: block;
  margin-top: 5px;
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* ==========================================================================
   Fullscreen
   ==========================================================================
   The canvas is normally a fixed slab with the statistics and charts beneath
   it. Filling the screen only helps if the canvas is what grows, so here the
   column becomes a flex stack and the canvas takes whatever the playbar and
   the toggles leave. The statistics and charts step aside: they are the two
   things that would otherwise eat the height that was just gained, and they
   are a click away again on the way out.

   Both the :fullscreen selector and a class are used. The class is what makes
   the layout react on browsers that report fullscreen differently, and it is
   also what lets this be styled at all when the element entering fullscreen
   is a descendant of the one being matched.
*/
.viewer-layout.is-fullscreen {
  height: 100vh;
  max-height: 100vh;
  padding: 10px;
  background: var(--bg);
  box-sizing: border-box;
  align-items: stretch;
  /* Narrow screens drop this to a single column, which puts the settings
     below the canvas and past the bottom of the screen. Scrolling beats
     clipping them out of reach. */
  overflow: auto;
}

.viewer-layout.is-fullscreen .canvas-column {
  display: flex;
  flex-direction: column;
  min-height: 0;          /* or the canvas refuses to shrink inside the grid */
}

.viewer-layout.is-fullscreen .canvas-wrap {
  height: auto;
  flex: 1 1 auto;
  min-height: 0;
}

.viewer-layout.is-fullscreen .stats-groups,
.viewer-layout.is-fullscreen .charts {
  display: none;
}

.viewer-layout.is-fullscreen .controls {
  max-height: 100%;
  overflow-y: auto;
}

/* Backdrop, for the gap around the padded layout. */
.viewer-layout:fullscreen { background: var(--bg); }
.viewer-layout:-webkit-full-screen { background: var(--bg); }

#btnFullscreen[aria-pressed="true"] { background: var(--accent-dim); color: #fff; }

/* Focus: the clicked node's neighbourhood, and how far out it reaches. */
.seg-field {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 0 8px;
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
}
/* The unit sits against its field rather than being read as a separate
   control, which is what the gap would otherwise make of it. */
.seg-field .unit { margin-left: -2px; opacity: 0.75; }

.seg-field input {
  width: 46px;
  padding: 2px 4px;
  font-size: 11px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
}
.focus-note { font-size: 11px; color: var(--accent); align-self: center; }
.focus-note:empty { display: none; }

/* Three abreast normally. Below that the trajectory takes the full width of a
   two-column row rather than being squeezed, and gets the height back. */
@media (max-width: 1200px) {
  .charts { grid-template-columns: 1fr 1fr; }
  .charts .chart-wide { grid-column: 1 / -1; }
  .charts .chart-wide canvas { height: 240px; }
}
.chart-wide .chart-metric { max-width: 240px; }

/* Says where the simulation is running, and how far along it is getting
   there. On a static host the first visit spends several seconds fetching a
   Python runtime, and silence for that long reads as a broken page. */
.backend-notice {
  margin: 0 0 12px;
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  background: var(--panel);
  font-size: 12px;
  color: var(--muted);
  line-height: 1.55;
}
.backend-notice.hidden { display: none; }
.backend-notice b { color: var(--text); font-weight: 600; }
.backend-notice .spin {
  display: inline-block;
  width: 9px; height: 9px;
  margin-right: 6px;
  border: 2px solid var(--accent);
  border-right-color: transparent;
  border-radius: 50%;
  animation: notice-spin 0.8s linear infinite;
  vertical-align: -1px;
}
@keyframes notice-spin { to { transform: rotate(360deg); } }

/* ==========================================================================
   Brand mark
   ========================================================================== */

/* The same mark as the front page's, at the size the header has room for:
   white agents, and the iris colour written into the symbol rather than
   inherited, so it is blue here too. */
.brand-mark {
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  color: #fff;
  margin-right: -2px;
}

/* ==========================================================================
   Page headings
   ==========================================================================
   A title and a paragraph above a view. */

.explain-intro {
  max-width: 1080px;
  margin: 0 auto 18px;
  padding: 0 4px;
}
.explain-intro h2 { margin: 0 0 6px; font-size: 20px; }
.explain-intro p { margin: 0; color: var(--muted); line-height: 1.6; }
.analysis-empty { max-width: 620px; }

/* ==========================================================================
   Home
   ========================================================================== */

/* The stage fills what is left under the top bar, edge to edge: the view's
   usual padding would put a frame around something meant to be a backdrop. */
#view-home { padding: 0; }

.home-stage {
  position: relative;
  height: calc(100vh - 47px);
  min-height: 420px;
  overflow: hidden;
  background: #0d1117;
}

/* Not interactive, and it says so: no pointer events at all, so a click or a
   drag falls through to the page rather than half-working. */
#homeCanvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}

/* Darkened towards the middle and the bottom left, which is where the words
   are. White on a bright cluster of agents is otherwise unreadable. Mixed in
   the background's own colour, so an empty patch of canvas stays exactly
   #0d1117 no matter how heavily the veil sits on it. */
.home-veil {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(120% 90% at 50% 45%, rgba(13, 17, 23, 0.52) 0%, rgba(13, 17, 23, 0.16) 45%, rgba(13, 17, 23, 0) 75%),
    linear-gradient(to top, rgba(13, 17, 23, 0.68) 0%, rgba(13, 17, 23, 0) 42%);
}

.home-hero {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  text-align: center;
  padding: 0 24px;
}

/* The wordmark and the mark stand together on one line, with the subtitle
   under both — so the subtitle centres on the pair rather than on the words. */
.home-headline {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(14px, 2.4vw, 34px);
}

/* Sized against the same viewport width as the title, so the two keep their
   proportions at every width instead of the mark swelling on a small screen. */
.home-mark {
  width: clamp(46px, 9vw, 118px);
  height: clamp(46px, 9vw, 118px);
  flex: 0 0 auto;
  color: #fff;
  filter: drop-shadow(0 2px 30px rgba(0, 0, 0, 0.7));
}

/* Named faces before system-ui, and a weight that real fonts actually ship.
   system-ui resolves to whatever the desktop happens to set, which on Linux is
   often a face with no light weight at all — so the browser either fell back to
   regular or faked a thin one by shaving the strokes, and a faked weight at a
   hundred pixels looks exactly as bad as it sounds. font-synthesis stops it
   inventing one; if a light weight is not there, a real regular is better than
   a counterfeit light. */
.home-title {
  margin: 0;
  font-family: "Helvetica Neue", "Segoe UI", Roboto, Ubuntu, Cantarell,
               system-ui, -apple-system, Arial, sans-serif;
  font-size: clamp(38px, 8.2vw, 112px);
  font-weight: 300;
  font-synthesis: none;
  line-height: 1.04;
  color: #fff;
  letter-spacing: 0.055em;
  text-shadow: 0 2px 40px rgba(0, 0, 0, 0.75);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.home-subtitle {
  margin: 18px 0 0;
  font-family: "Helvetica Neue", "Segoe UI", Roboto, Ubuntu, Cantarell,
               system-ui, -apple-system, Arial, sans-serif;
  font-size: clamp(13px, 1.7vw, 20px);
  font-weight: 400;
  font-synthesis: none;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.72);
  text-shadow: 0 2px 24px rgba(0, 0, 0, 0.8);
}

/* Resolving out of a blur, with the letters drawing together as they land.
   Restarted by re-adding the class whenever the tab is opened, so it plays
   every time rather than only on the first load. */
.home-stage.play .home-title {
  animation: home-resolve 2600ms cubic-bezier(0.16, 0.84, 0.24, 1) both,
             home-tighten-title 2600ms cubic-bezier(0.16, 0.84, 0.24, 1) both;
}
.home-stage.play .home-subtitle {
  animation: home-resolve 2000ms cubic-bezier(0.16, 0.84, 0.24, 1) 900ms both,
             home-tighten-sub 2000ms cubic-bezier(0.16, 0.84, 0.24, 1) 900ms both;
}
.home-stage.play .home-note {
  animation: home-fade 1600ms ease-out 1900ms both;
}

/* The mark arrives after the words have resolved: it turns into place and
   settles, rather than fading in alongside them where it would read as one
   more thing appearing at once. */
.home-stage.play .home-mark {
  animation: home-mark-in 2200ms cubic-bezier(0.16, 0.84, 0.24, 1) 700ms both;
}

@keyframes home-mark-in {
  from { opacity: 0; filter: blur(12px); transform: scale(0.55) rotate(-32deg); }
  55%  { opacity: 1; }
  to   { opacity: 1; filter: blur(0);    transform: none; }
}

/* Letter-spacing is animated separately, because the title and the subtitle
   settle on different values and a shared keyframe would end both on the
   title's — quietly overriding the subtitle's own rule, since the animation
   holds its final state. */
@keyframes home-resolve {
  from { opacity: 0; filter: blur(18px); transform: translateY(14px); }
  60%  { opacity: 1; }
  to   { opacity: 1; filter: blur(0);    transform: none; }
}

@keyframes home-tighten-title { from { letter-spacing: 0.3em; }  to { letter-spacing: 0.055em; } }
@keyframes home-tighten-sub   { from { letter-spacing: 0.42em; } to { letter-spacing: 0.22em; } }

@keyframes home-fade {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

.home-note {
  position: absolute;
  left: 22px;
  bottom: 22px;
  max-width: 380px;
  padding: 13px 15px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-left: 2px solid var(--accent);
  border-radius: var(--radius);
  background: rgba(13, 17, 23, 0.62);
  backdrop-filter: blur(6px);
}
.home-note p {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: rgba(230, 237, 243, 0.82);
}
.home-note b { color: #fff; font-weight: 600; }

/* The wordmark is the way back here, so it has to look pressable. */
.brand {
  background: none;
  border: none;
  padding: 0 2px;
  margin: 0;
  cursor: pointer;
  color: var(--text);
  border-radius: 6px;
}
.brand:hover:not(:disabled) { border-color: transparent; color: #fff; }
.brand.active h1 { color: #fff; }
.brand.active { box-shadow: inset 0 -2px 0 var(--accent); }

@media (max-width: 640px) {
  .home-note { left: 12px; right: 12px; bottom: 12px; max-width: none; }
  .home-headline { flex-direction: column-reverse; gap: 14px; }
  .home-mark { width: 68px; height: 68px; }
}

/* ==========================================================================
   Explanation 2 — the run, the words and the script, side by side
   ========================================================================== */

#view-explain { padding: 0; }

.ex {
  height: calc(100vh - 47px);
  display: flex;
  flex-direction: column;
  padding: 14px 18px 10px;
  gap: 10px;
}

.ex-where { font-size: 11.5px; color: var(--muted); letter-spacing: 0.02em; }

/* The step's name and number head the explanation, because that is what they
   are naming — the words, not the row of three panes. */
.ex-title {
  margin: 0 0 2px;
  font-size: 17px;
  font-weight: 600;
  line-height: 1.25;
  color: var(--text);
  text-transform: none;
  letter-spacing: 0;
  text-align: left;
}
.ex-note p.ex-step {
  margin: 0 0 10px;
  font-size: 12px;
  color: var(--accent);
  letter-spacing: 0.04em;
  text-align: left;
}
.ex-nav { display: flex; gap: 8px; }
.ex-nav button {
  width: 46px;
  height: 34px;
  display: grid;
  place-items: center;
  padding: 0;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
}
.ex-nav button:hover { border-color: var(--accent); background: #1f2a36; }
.ex-nav button:active { transform: translateY(1px); }
.ex-nav svg { width: 13px; height: 13px; fill: var(--text); }
.ex-nav button:hover svg { fill: #fff; }

/* Nothing but the two arrows. A row of thirteen dots was a second thing to
   read before the reader had read the first. */

/* The three panes. The picture gets the most room, the script enough to read
   without wrapping, and the words the space between them. */
.ex-body {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  /* The picture gets the wider share; the words and the script share the
     other column, the explanation sitting directly above the place in the
     script it is talking about. */
  grid-template-columns: minmax(0, 1.6fr) minmax(340px, 1fr);
  gap: 12px;
}

.ex-stage, .ex-code {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  min-height: 0;
}

.ex-stage { position: relative; overflow: hidden; background: #0d1117; }
#explainCanvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

.ex-side {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-height: 0;
}

/* The step's words, in the card the front page uses for its note. The step's
   emblem is the card's background — one light grey on nothing, so it reads as
   a watermark and not as an illustration. */
.ex-note {
  position: relative;
  /* Its own height, up to a share of the column — not whatever is left after
     the script has taken what it wants. As a grid row against the script's
     1fr it was squeezed to half the words and scrolled on every step. */
  flex: 0 0 auto;
  max-height: 60%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 15px 17px;
  padding-right: 94px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-left: 2px solid var(--accent);
  border-radius: var(--radius);
  background-color: var(--panel);
  background-repeat: no-repeat;
  background-position: right 14px top 15px;
  background-size: auto 62px;
}

/* Where you are, and the way through: at the foot of the words they belong
   to rather than in a bar across the top of all three panes. */
.ex-foot-row {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 14px;
  padding-top: 11px;
  border-top: 1px solid var(--border);
  margin-right: -77px;
}
/* Only the words scroll. The way through is pinned under them, or the one
   step whose explanation runs long is the one step you cannot page off. */
#explainText {
  min-height: 0;
  overflow-y: auto;
}

.ex-note p {
  margin: 0 0 10px;
  color: var(--text);
  line-height: 1.62;
  font-size: 13.5px;
  /* Justified, with hyphenation on so the ragged gaps justification usually
     opens up in a narrow column do not appear. */
  text-align: justify;
  hyphens: auto;
}
.ex-note p:last-child { margin-bottom: 0; }
.ex-note b { color: #fff; }
.ex-note em { color: var(--accent); font-style: normal; }

/* The script, whole, with the lines under discussion lit and the rest left
   dim — so the part being explained is read in the place it actually lives
   rather than as a detached snippet. */
.ex-code {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 12px 0;
  font: 11.5px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace;
  scroll-behavior: smooth;
}
.explain-line { display: flex; gap: 10px; padding: 0 12px; white-space: pre; }
.explain-no {
  flex: 0 0 auto;
  width: 26px;
  text-align: right;
  color: #3d4a58;
  user-select: none;
}
.explain-src { color: #8b98a8; }

/* Lines away from the step are faded rather than recoloured, so the script
   still reads as a syntax-highlighted file all the way down instead of turning
   grey outside the part being discussed. */
.explain-line { transition: opacity 140ms ease; opacity: 0.42; }
.explain-line.lit { opacity: 1; background: rgba(79, 179, 255, 0.07); }
.explain-line.lit .explain-no { color: var(--accent); }

/* Token colours. The same family GitHub uses on this background, which is
   where most people will have read Python last. */
.tok-com  { color: #8b949e; font-style: italic; }
.tok-str  { color: #a5d6ff; }
.tok-kw   { color: #ff7b72; }
.tok-num  { color: #79c0ff; }
.tok-def  { color: #d2a8ff; }
.tok-dec  { color: #d2a8ff; }
.tok-bi   { color: #79c0ff; }
.tok-self { color: #ffa657; }

.ex-foot { margin: 0; font-size: 11.5px; color: var(--muted); }
.ex-foot code {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 5px;
}

@media (max-width: 1100px) {
  .ex { height: auto; }
  .ex-body { grid-template-columns: 1fr; }
  .ex-stage { min-height: 340px; }
  .ex-note { max-height: none; }
  .ex-code { max-height: 420px; }
}
