:root {
  --app-surface: #ffffff;
  --app-border: #e2e8f0;
  --app-muted: #64748b;
  /* Translucent, so the same chip reads correctly on the neutral, success and
     error surfaces without needing one tint per state. */
  --app-chip-bg: rgba(15, 23, 42, 0.06);
}

body {
  background: #f8fafc;
  color: #0f172a;
}

/* Wider than Bootstrap's 1320px xxl container so the four panes fit. */
.container {
  max-width: 1800px;
  /* 20px gutter = 10px padding per side; row negative margins follow it. */
  --bs-gutter-x: 1.25rem;
}

.dashboard-grid {
  display: grid;
  gap: 1.5rem;
  align-items: start;
}

.dashboard-controls {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

@media (min-width: 992px) {
  .dashboard-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .dashboard-graph {
    grid-column: span 2;
  }

  /* Controls stack sits beside the metrics pane, draw pad spans the rest. */
  .dashboard-draw {
    grid-column: span 2;
  }
}

@media (min-width: 1200px) {
  .dashboard-grid {
    grid-template-columns: minmax(0, 225px) minmax(0, 1fr) minmax(0, 350px);
  }

  .dashboard-controls {
    grid-column: 1;
    grid-row: 1 / span 2;
  }

  .dashboard-graph {
    grid-column: 2 / span 2;
    grid-row: 1;
  }

  .dashboard-metrics {
    grid-column: 2;
    grid-row: 2;
  }

  .dashboard-draw {
    grid-column: 3;
    grid-row: 2;
  }
}

@media (min-width: 1400px) {
  .dashboard-grid {
    grid-template-columns:
      minmax(0, 225px) minmax(0, 1.5fr) minmax(0, 1fr)
      minmax(0, 350px);
    align-items: stretch;
  }

  .dashboard-controls,
  .dashboard-graph,
  .dashboard-metrics,
  .dashboard-draw {
    grid-column: auto;
    grid-row: auto;
  }

  /* Panes share the tallest card's height instead of floating at different sizes. */
  .dashboard-metrics,
  .dashboard-draw {
    display: flex;
    flex-direction: column;
  }

  .network-canvas {
    flex: 1 1 auto;
    height: auto;
    min-height: 460px;
  }

  .dashboard-metrics .card-body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
  }

  .dashboard-metrics .chart-container {
    flex: 1 1 auto;
  }
}

.app-header {
  background: var(--app-surface);
}

.card {
  border-color: var(--app-border);
}

.card-header {
  background: #f1f5f9;
  border-bottom-color: var(--app-border);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #334155;
}

/* Locked cards keep their controls in place and overlay the reason instead of
   leaving inputs that look usable but silently ignore input. */
.config-body {
  position: relative;
}

.card-overlay {
  position: absolute;
  /* Inset rather than flush: the card body padding would otherwise push the
     surface against the border and clip the controls' focus rings. */
  inset: 10px;
  display: grid;
  place-items: center;
  padding: 0.5rem;
  border: 1px solid var(--app-border);
  border-radius: 0.375rem;
  /* Translucent, so the locked values stay readable underneath the message. */
  background: rgba(255, 255, 255, 0.8);
  /* Decorative only: the inputs underneath are disabled, so it must not eat clicks. */
  pointer-events: none;
}

.card-overlay-message {
  margin: 0;
  color: #1e293b;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.3;
  text-align: center;
  /* The controls below bleed through the translucent surface, so the text needs
     its own halo to stay readable where it crosses a label or a select. */
  text-shadow:
    0 0 3px #ffffff,
    0 0 6px #ffffff,
    0 0 9px #ffffff;
}

/* `.card-overlay` sets `display`, which would otherwise beat the UA rule. */
.card-overlay[hidden] {
  display: none;
}

.status-bar {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  border: 1px solid var(--app-border);
  border-radius: 0.375rem;
  background: var(--app-surface);
  padding: 0.5rem 0.75rem;
  font-size: 0.875rem;
}

/* The title is a chip, not prose: bare uppercase text at this size still reads
   as part of the sentence that follows, and the message is the thing that
   changes. The fill is translucent so one chip works on the neutral, success
   and error surfaces; the border keeps it readable on the pale tints, where a
   6% wash alone is nearly invisible. */
.status-bar-title {
  flex: none;
  padding: 0.1rem 0.4rem;
  border: 1px solid var(--app-border);
  border-radius: 0.25rem;
  background: var(--app-chip-bg);
  color: #334155;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.status-bar[data-kind='success'] {
  border-color: #a7f3d0;
  background: #ecfdf5;
  color: #065f46;
}

.status-bar[data-kind='error'] {
  border-color: #fecdd3;
  background: #fff1f2;
  color: #9f1239;
}

.dashboard-graph {
  display: flex;
  flex-direction: column;
}

.network-canvas {
  height: 460px;
}

.network-canvas svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* Palette switch plus its colormaps. Sharing `.metric-row` keeps the two control
   strips visually identical, so the graph toolbar does not look like a second,
   differently styled widget language. */
.graph-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  padding: 0.4rem 0.75rem;
  border-bottom: 1px solid var(--app-border);
}

/* Two rows of equal structure: caption, low endpoint, ramp, high endpoint. The
   shared grid is what aligns the two ramps and their numbers with each other. */
.colormap-legend {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 0.15rem;
  font-size: 0.6875rem;
  color: var(--app-muted);
  font-variant-numeric: tabular-nums;
}

.colormap-row {
  display: grid;
  /* The endpoint column has to fit "inhibits" as well as "100%", so it is wide
     enough for the longest label; all three rows share the grid, which is what
     keeps their ramps aligned with each other. */
  grid-template-columns: 3.4rem 3.4rem minmax(0, 1fr) 3.4rem;
  align-items: center;
  gap: 0.4rem;
}

/* Captions and endpoints keep their width so both ramps line up and the ramps
   themselves do not resize as the weight scale drifts during training. */
.colormap-label,
.colormap-low,
.colormap-high,
.edge-legend-low,
.edge-legend-high,
.focus-legend-low,
.focus-legend-high {
  white-space: nowrap;
}

.colormap-label {
  font-weight: 600;
}

.colormap-low,
.colormap-high,
.edge-legend-low,
.edge-legend-high,
.focus-legend-low,
.focus-legend-high {
  text-align: center;
}

/* Height rather than an aspect ratio: the gradient is painted through
   `background-image`, so the box only needs to define the extent. */
.colormap-ramp {
  height: 0.6rem;
  border: 1px solid var(--app-border);
  border-radius: 0.15rem;
  /* Neutral placeholder until the graph paints the active palettes. */
  background: linear-gradient(to right, #e2e8f0, #e2e8f0);
}

.chart-container {
  position: relative;
  height: 240px;
}

/* Chart switch rows. Radio inputs + labels give one-active highlighting for free
   via Bootstrap's btn-check, so no JS state can drift from the visual state. */
.metrics-controls {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-block: 0.875rem 0.75rem;
  padding-block: 0.875rem;
  border-block: 1px solid var(--app-border);
}

.metrics-controls .metric-segments {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 0;
}

.metrics-controls .metric-segments .btn {
  display: grid;
  align-items: center;
  min-height: 1.875rem;
  border-radius: 0;
  font-size: 0.75rem;
}

.metrics-controls .metric-segments .btn:not(:first-of-type) {
  border-left-width: 0;
}

.metrics-controls .metric-segments .btn:first-of-type {
  border-radius: 0.25rem 0 0 0.25rem;
}

.metrics-controls .metric-segments .btn:last-of-type {
  border-radius: 0 0.25rem 0.25rem 0;
}

.metric-view-settings {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.625rem 0.75rem;
  margin-top: 0.25rem;
}

.metric-setting {
  display: grid;
  min-width: 0;
  gap: 0.25rem;
}

.metric-setting-label {
  font-size: 0.6875rem;
  font-weight: 500;
  color: var(--app-muted);
}

.metric-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.15rem;
}

/* Compact enough that the longest row (x-axis + span) fits the narrow metrics
   pane on one line; wrapping it would defeat the point of the control strip. */
.metric-row .btn {
  --bs-btn-padding-y: 0.125rem;
  --bs-btn-padding-x: 0.35rem;
  --bs-btn-font-size: 0.6875rem;
  --bs-btn-color: var(--app-muted);
  --bs-btn-border-color: var(--app-border);
  --bs-btn-hover-color: #0f172a;
  --bs-btn-hover-bg: #f1f5f9;
  --bs-btn-hover-border-color: #cbd5e1;
  white-space: nowrap;
}

.metric-row .btn-check:checked + .btn {
  --bs-btn-active-color: #ffffff;
  --bs-btn-active-bg: #2563eb;
  --bs-btn-active-border-color: #2563eb;
}

.metric-row .btn:focus-visible {
  box-shadow: 0 0 0 0.15rem rgba(37, 99, 235, 0.25);
}

/* Reserved height so switching metrics does not shift the chart. */
.metric-hint {
  min-height: 2.2rem;
  margin: 0;
  font-size: 0.75rem;
  line-height: 1.1rem;
}

/* Epoch progress. Read-only by construction: no hover, no focus ring, no cursor
   change, so it cannot be mistaken for one of the chart's control rows. */
.epoch-progress {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.epoch-progress-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  font-size: 0.75rem;
  line-height: 1.1rem;
}

.epoch-progress-label {
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: #334155;
}

.epoch-progress-counts {
  color: var(--app-muted);
  font-variant-numeric: tabular-nums;
}

.epoch-progress .progress {
  --bs-progress-height: 0.375rem;
  --bs-progress-bg: #e2e8f0;
  border: 1px solid var(--app-border);
}

/* Same blue as the metric series, so the bar reads as part of the chart block. */
.epoch-progress .progress-bar {
  --bs-progress-bar-bg: #2563eb;
  /* Batches arrive every few milliseconds; Bootstrap's 0.6 s width transition
     would lag the real position and make the bar look stalled. */
  transition: none;
}

.metric-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0;
  margin: 0.875rem 0 0;
  border-top: 1px solid var(--app-border);
  font-size: 0.875rem;
}

.metric-grid > div {
  padding: 0.625rem 0;
}

.metric-grid > div:nth-child(even) {
  padding-left: 1rem;
  border-left: 1px solid var(--app-border);
}

.metric-grid > div:nth-child(n + 3) {
  border-top: 1px solid var(--app-border);
}

.metric-grid dt {
  font-weight: 500;
  color: var(--app-muted);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.metric-grid dd {
  margin-bottom: 0;
  font-size: 1rem;
  font-variant-numeric: tabular-nums;
}

.draw-pad {
  /* Square at the canvas resolution; CSS scales it to whatever the pane allows. */
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  height: auto;
  border: 1px solid var(--app-border);
  border-radius: 0.375rem;
  cursor: crosshair;
  touch-action: none;
}

/* The overlay is locked to the pad, not the card, so its message sits in the
   middle of the drawing surface rather than drifting towards the buttons. */
.draw-stage {
  position: relative;
  width: 100%;
  max-width: 320px;
  margin-inline: auto;
}

.draw-stage .card-overlay {
  inset: 0;
}

/* The overlay is decorative, so the pad itself must refuse input while locked. */
.draw-pad.is-locked {
  cursor: default;
  pointer-events: none;
}

/* Icon-only Play/Pause, labelled Reset: equal halves, content centered, height
   independent of label text. */
.training-controls .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  flex: 1 1 0;
  min-width: 0;
  min-height: 2.25rem;
  white-space: nowrap;
}

.training-controls .btn svg {
  width: 1.125rem;
  height: 1.125rem;
  flex: none;
}

/* The labelled Reset button keeps its natural width; the icon-only buttons
   share whatever remains. */
.training-controls .training-reset {
  flex: 0 0 auto;
}
