/* midi-graph styles — extracted verbatim from the old upload.html. */

:root {
  --bg: #0a0a0f;
  --panel: #131419;
  --border: #252a31;
  --text: #e9edf2;
  --muted: #8a93a0;
  --accent: #00e5ff;
}
* { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  min-height: 100vh;
}
.wrap { max-width: 1280px; margin: 0 auto; padding: 20px; position: relative; }
h1 {
  margin: 0 0 4px 0;
  font-size: 36px;
  font-weight: 600;
  letter-spacing: -0.3px;
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}
/* Centered variant for the main page title. Subtitle stays left-aligned. */
h1.title-centered {
  justify-content: center;
  text-align: center;
}
.title-tag-centered {
  text-align: center;
  font-size: 13px;
  font-weight: 400;
  color: var(--muted);
  letter-spacing: 0.2px;
  margin: -6px 0 10px;
}
.source-label {
  font-size: 12px;
  font-weight: 400;
  color: var(--muted);
  margin-left: 8px;
}
.track-picker-row {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  background: rgba(255, 255, 255, 0.04);
  border-radius: 4px;
  border: 1px solid var(--border);
}
.track-picker-row select {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 3px 6px;
  font-size: 12px;
  cursor: pointer;
}
.track-picker-hint {
  font-size: 11px;
  color: var(--muted);
}
.subtitle { color: var(--muted); font-size: 13px; margin-bottom: 20px; }

.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 14px;
  margin-bottom: 14px;
}
.panel.hidden { display: none; }
.hidden { display: none; }
.panel h2 {
  margin: 0 0 10px 0;
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  font-weight: 500;
}

/* ----- Upload ----- */
.upload-row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
input[type="file"] { color: var(--text); }
.filename-display {
  color: var(--accent);
  font-family: ui-monospace, "SF Mono", Consolas, monospace;
  font-size: 13px;
  margin-left: 8px;
}
.error-display {
  margin-top: 10px;
  padding: 8px 12px;
  background: #5a1a1a;
  color: #ffd2d2;
  border: 1px solid #802020;
  border-radius: 4px;
  font-size: 13px;
  font-family: ui-monospace, "SF Mono", Consolas, monospace;
}
button {
  background: var(--accent);
  color: #001218;
  border: none;
  padding: 8px 16px;
  border-radius: 4px;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
}
button.secondary { background: #2a2f37; color: var(--text); }
button.danger { background: #5a1a1a; color: #fff; }
button:disabled { opacity: 0.4; cursor: not-allowed; }

/* ----- Stats ----- */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px;
}
.stat { background: #0d0e12; padding: 10px 12px; border-radius: 4px; border: 1px solid var(--border); }
.stat .label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); }
.stat .value { font-size: 18px; font-weight: 500; margin-top: 4px; }
.stat .sub { font-size: 12px; color: var(--muted); margin-top: 2px; }

.top-transitions { font-size: 13px; line-height: 1.7; }
.top-transitions code {
  background: #1a1d23;
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 12px;
}
.top-transitions .pct { color: var(--accent); margin-left: 6px; }

/* ALL-transitions list — vertical, scrollable, dense. Used when the graph
   has more than ~5 transitions (e.g. 40+ in twinkle_twinkle.mid). The list
   shows every transition in probability-descending order so the user can
   see the full distribution, not just the top 5. */
.transition-list {
  list-style: none;
  padding: 0;
  margin: 4px 0 0 0;
  max-height: 180px;
  overflow-y: auto;
  font-size: 12px;
  line-height: 1.55;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg-deep);
}
.transition-list li {
  display: flex;
  justify-content: space-between;
  padding: 2px 8px;
  border-bottom: 1px solid rgba(255,255,255,0.04);
}
.transition-list li:last-child { border-bottom: none; }
.transition-list code {
  font-family: 'JetBrains Mono', 'SF Mono', Menlo, Consolas, monospace;
  color: var(--text);
  font-size: 11.5px;
}
.transition-list .pct {
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  margin-left: 8px;
}

/* ----- Sheet music ----- */
/* Paper-like background for the OSMD-rendered sheet. OSMD defaults to a
   dark staff which clashes with the dark page; giving the container a
   light background makes the sheet music look like a sheet of paper on
   the dark dashboard. The OSMD svg fills the container, so this
   background color shows through wherever OSMD doesn't paint (margins,
   system gaps). */
#sheet-container {
  background: #f5f4ee;
  border-radius: 4px;
  padding: 12px;
  /* OSMD draws staff lines in dark gray by default — readable on light bg. */
  color: #1a1a1a;
  min-height: 120px;
}
#sheet-container svg {
  /* Center the score horizontally inside the container. */
  display: block;
  margin: 0 auto;
}

/* ----- Graph ----- */
.graph-wrap { position: relative; overflow: hidden; }
/* Both graphs are pinned to 560 px in the side-by-side layout so the
   two visualizations feel balanced regardless of which controls wrap
   to a second line. The flex column on each panel keeps the wrap
   aligned to the same row height; we just override the per-graph
   height here to match. */
#graph { width: 100%; height: 560px; background: #0a0b0e; border-radius: 4px; }
#harmonic-graph {
  width: 100%;
  height: 560px;
  background: #0a0b0e;
  border-radius: 4px;
}

/* Side-by-side graph layout. Two columns on wide viewports, stacks
   vertically when the screen is too narrow for both graphs to be
   legible. The gap matches the panel margin-bottom so the visual
   rhythm stays consistent. The row is forced to a single 1fr row
   and each panel is stretched to fill the cell so the two graph
   panels end up exactly the same height — important because the
   user sees them side by side and a height mismatch looks broken.
   The chord-sequence list inside the harmonic panel is hidden in
   the side-by-side case so the harmonic graph can stretch to match
   the melodic graph (the list shows up below in the stacked case). */
.graphs-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr;
  gap: 14px;
  margin-bottom: 14px;
}
.graphs-row .panel {
  margin-bottom: 0;
  /* Stretch each panel to the full height of the row so the two
     graph panels are exactly equal in height. The panel's
     internal flex column (h2 + controls + graph) keeps the
     graph SVG from overflowing the panel. */
  display: flex;
  flex-direction: column;
}
.graphs-row .panel .graph-wrap { flex: 1 1 auto; min-height: 0; }
/* In the side-by-side case the graphs use their own fixed height
   (560px) rather than stretching to the wrap — both panels have
   different control bar heights (the melodic bar is taller because
   of the 3 range sliders), so letting each graph stretch to its
   wrap would make them visually unequal. Equal fixed heights give
   the user two graphs of the same size. The stacked case below
   switches to height:auto so each graph sizes to its own viewport. */
/* Hide the chord-sequence list in the side-by-side layout so the
   harmonic graph can match the melodic graph's height. The list
   reappears in the stacked layout (below 1100px). */
.graphs-row .chord-sequence-panel { display: none; }
@media (max-width: 1100px) {
  /* Stack when the viewport can't comfortably fit two graphs side by
     side. Threshold chosen so each graph is at least ~520 px wide.
     When stacked, the panels naturally size to their own content
     (the flex-stretch only applies to the side-by-side case). */
  .graphs-row { grid-template-columns: 1fr; grid-template-rows: auto; }
  .graphs-row .panel { display: block; }
  .graphs-row .panel #graph,
  .graphs-row .panel #harmonic-graph { height: 460px; }
  .graphs-row .chord-sequence-panel { display: block; }
}

/* Summary panel: two columns side by side, mirroring the graphs
   row above. Each column is a vertical flex so the stats grid and
   the transition list scroll independently inside the column if
   they overflow. Stacks to a single column below 1100px (matches
   the graphs-row breakpoint). */
.summary-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
.summary-col {
  min-width: 0;        /* let the column shrink below its content */
  display: flex;
  flex-direction: column;
}
.summary-col h3 {
  margin: 0 0 8px 0;
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.summary-col .summary-list-title { margin-top: 14px; }
.summary-col .summary-sub {
  font-size: 11px;
  color: var(--muted);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
}
.summary-col .top-transitions {
  max-height: 240px;
  overflow-y: auto;
}
@media (max-width: 1100px) {
  .summary-row { grid-template-columns: 1fr; }
}

/* The settings bar can sit above the graph (preferred for the new
   side-by-side layout) or below it (kept for backwards compat with
   any saved styles). The min-height matches the tallest bar
   (melodic has 3 range sliders that wrap to a second line) so both
   panels' graphs start at the same vertical position — otherwise
   the side-by-side layout looks misaligned because one controls
   bar is taller than the other and the graph below is offset. */
.graph-controls-top {
  margin-top: 0;
  margin-bottom: 10px;
  min-height: 140px;
}
.harmonic-notice {
  background: rgba(255, 230, 100, 0.08);
  border: 1px solid rgba(255, 230, 100, 0.3);
  border-radius: 4px;
  padding: 10px 14px;
  margin-bottom: 12px;
  font-size: 13px;
  color: rgba(255, 230, 100, 0.9);
}
.chord-sequence-panel {
  margin-top: 12px;
}
.chord-sequence-panel h3 {
  margin: 8px 0 4px 0;
  font-size: 13px;
  font-weight: 500;
  color: var(--muted);
}
.graph-controls {
  display: flex; gap: 8px; align-items: center; margin-top: 10px; flex-wrap: wrap;
}
.graph-controls label { font-size: 12px; color: var(--muted); margin-left: 12px; }
.graph-controls input[type="range"] { width: 120px; }
.graph-controls input[type="checkbox"] { margin-right: 4px; }

.node-label {
  /* Drawn INSIDE the node circle (36px diameter). Pitch name on top,
     frequency % on the bottom. The dark stroke around the text makes
     it readable on any circle color (yellow, cyan, white, etc.). */
  font-size: 8.5px;
  font-weight: 600;
  fill: #ffffff;
  /* Dark stroke gives contrast on light-colored nodes without making
     thin letters look heavy. 1.5px is enough at 8.5px font size. */
  stroke: rgba(0, 0, 0, 0.92);
  stroke-width: 1.5px;
  paint-order: stroke fill;
  pointer-events: none;
  user-select: none;
}
/* Frequency line: slightly dimmer so the pitch name is the primary
   identifier. */
.node-label .node-label-freq {
  font-size: 7.5px;
  fill: #f0f0f0;
  font-weight: 500;
}
.node-label.chord-label {
  /* Chord labels are longer ("Cm7b5", "D# half-sharp raised 2nd") so
     we use a smaller font. Now shows 2 lines like the pitch graph:
     chord name on top, frequency % on the second line. 7px is small
     enough to fit both lines in the 36px-diameter circle. */
  font-size: 7px;
  font-weight: 600;
  fill: #ffffff;
  stroke: rgba(0, 0, 0, 0.92);
  stroke-width: 1px;
  paint-order: stroke fill;
}
.link-label {
  font-size: 9.5px;
  font-weight: 500;
  fill: #c8cdd4;
  /* Dark stroke behind the text for legibility on any background — works
     whether an edge passes over a node, a self-loop arc, or empty space. */
  stroke: rgba(0, 0, 0, 0.95);
  stroke-width: 2.5px;
  paint-order: stroke fill;
  pointer-events: none;
  user-select: none;
  /* Always visible — the user wants to read probabilities without hovering.
     Was opacity 0 + .visible class set on edge hover; removed so labels
     show all the time. */
}
.link-label.dim {
  /* Slightly faded for very small transitions so dense graphs stay
     readable. Still fully visible — just less attention-grabbing. */
  fill: #8a93a0;
}
.self-loop-label {
  font-size: 10.5px;
  font-weight: 600;
  fill: #ffd166;                                  /* warm amber for contrast */
  stroke: rgba(0, 0, 0, 0.85);                    /* dark outline for legibility */
  stroke-width: 2.5px;
  paint-order: stroke fill;
  pointer-events: none;
  text-anchor: middle;
}

/* Active-node glow: drop-shadow that pulses while the pitch is sounding.
   The animation runs on every node but only nodes with .active get the
   filter applied (the rest keep the default 0 0 0 transparent shadow). */
/* Phase 3 fix: D3's setActive adds the 'active' class to the circle
   element, not to a parent g.node group, so the selector is
   `circle.active` (or `circle.node.active` for extra safety). The
   previous `.node.active` selector never matched. */
/* Phase 3 fix: the glow fades in over 150 ms and out over 250 ms
   so chord transitions during playback look smooth instead of
   snapping on/off. The pulse animation is layered on top — the
   base state is the weak pulse from-keyframe and the filter
   transition provides the cross-fade between inactive (no filter)
   and active (pulsing filter). Without the transition the glow
   pops on/off and fast chord changes look like a strobe. */
circle.node,
circle {
  transition: filter 150ms ease-out;
}
circle.node.active,
circle.active {
  filter: drop-shadow(0 0 6px #00e5ff) drop-shadow(0 0 14px #00e5ff);
  animation: pulse-glow 0.6s ease-in-out infinite alternate;
}
@keyframes pulse-glow {
  from { filter: drop-shadow(0 0 4px #00e5ff) drop-shadow(0 0 10px #00e5ff); }
  to   { filter: drop-shadow(0 0 10px #00e5ff) drop-shadow(0 0 22px #00e5ff); }
}

/* ----- Playback ----- */
.playback-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* ----- Info button + modal (concept & implementation by Zaher Alkaei) ----- */

/* Inline info button next to the page title. The ℹ glyph is the universal
   "more info" affordance; aria-label gives it a proper accessible name
   for screen readers since the visible character is decorative. */
.info-btn {
  position: absolute;
  top: 18px;
  right: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  /* Slightly down from the text baseline so it visually aligns with the
     lowercase x-height of "midi-graph" rather than the cap height. */
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  transition: color 120ms, border-color 120ms, background 120ms;
}
.info-btn:hover, .info-btn:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
  background: rgba(0, 229, 255, 0.08);
  outline: none;
}

/* Modal container — fixed full-viewport, hidden by default. The "open"
   class fades it in. pointer-events: none on the wrapper prevents the
   hidden modal from intercepting clicks on the page below; the wrapper
   re-enables pointer events only when .open is set. */
.info-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 180ms ease;
}
.info-modal.open {
  pointer-events: auto;
  opacity: 1;
}
/* Backdrop — semi-transparent dark wash. Clicking it dismisses (handled
   in app.js via the [data-info-dismiss] attribute). */
.info-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  cursor: pointer;
}
/* The card itself — bounded, scrollable, looks like a real modal. */
.info-card {
  position: relative;
  max-width: 720px;
  width: 100%;
  max-height: calc(100vh - 40px);
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 24px 28px 28px 28px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(0, 229, 255, 0.08);
  /* Subtle entry animation; the wrapper handles opacity so this just
     nudges the card into place. */
  transform: translateY(8px) scale(0.985);
  transition: transform 180ms ease;
}
.info-modal.open .info-card {
  transform: translateY(0) scale(1);
}
/* Modal typography — h2 is the modal title, h3 is section heading. */
.info-card h2 {
  margin: 0 0 14px 0;
  font-size: 19px;
  font-weight: 600;
  color: var(--text);
  padding-right: 32px;  /* room for the close button */
}
.info-card h3 {
  margin: 18px 0 8px 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.6px;
}
.info-card p, .info-card li {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text);
}
.info-card ul, .info-card ol {
  margin: 6px 0 8px 0;
  padding-left: 20px;
}
.info-card .info-credits {
  font-size: 12.5px;
  color: var(--muted);
}
.info-card .info-credits li {
  margin-bottom: 2px;
}
.info-card li {
  margin: 3px 0;
}
.info-card code, .info-card kbd {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 12px;
  padding: 1px 5px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--text);
}
.info-card kbd {
  background: rgba(0, 229, 255, 0.1);
  border-color: rgba(0, 229, 255, 0.3);
  color: var(--accent);
}
.info-card a {
  color: var(--accent);
  text-decoration: none;
}
.info-card a:hover { text-decoration: underline; }
.info-card strong { color: var(--text); font-weight: 600; }
.info-card em { color: var(--muted); font-style: normal; }

/* Close (×) button — top-right of the card. data-info-dismiss=1 makes
   app.js wire it up as a dismiss trigger so the JS stays declarative. */
.info-close {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 30px;
  height: 30px;
  border: none;
  background: transparent;
  color: var(--muted);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  border-radius: 4px;
  transition: color 120ms, background 120ms;
}
.info-close:hover, .info-close:focus-visible {
  color: var(--text);
  background: rgba(255, 255, 255, 0.06);
  outline: none;
}

/* When the modal is open, freeze background scrolling so the page under
   the modal doesn't shift when the user scrolls inside the card. */
body.info-open {
  overflow: hidden;
}
.playback-row .info { color: var(--muted); font-size: 12px; }