/*
 * style.css — Engineering-grade UI for STFT Spectrogram Viewer
 *
 * Design rationale:
 *   - Monospace font throughout: text reads like instrument readouts, not
 *     prose. Engineers scanning parameters expect uniform character widths.
 *   - No gradients, no shadows, no border-radius > 2px: visual noise that
 *     provides no information is removed. Every pixel should convey state.
 *   - Strict colour palette (#0f1115 bg / #1a1d23 panels / #e6e6e6 text):
 *     matches the contrast ratios of tools like MATLAB and Audacity. The
 *     eye adjusts to the dark background; bright UI chrome would cause
 *     visual fatigue during long analysis sessions.
 *   - Sidebar + main panel (Option A): fixed-width sidebar scrolls vertically
 *     when controls exceed the viewport height; spectrogram canvas gets the
 *     maximum horizontal run.
 */

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

/* ── Base ──────────────────────────────────────────────────────────────── */
html, body {
  height: 100%;       /* needed so the app div can fill the full viewport */
}

body {
  /* Monospace stack: Consolas (Windows), Menlo (macOS), monospace fallback.
     This gives the interface the feel of a terminal / instrument display. */
  font-family: Consolas, Menlo, "Liberation Mono", "Courier New", monospace;
  font-size: 13px;
  background: #0f1115;
  color: #e6e6e6;
  line-height: 1.4;
}

/* ── Two-pane application shell ────────────────────────────────────────── */
/*
 * #app fills the entire viewport using a simple CSS Grid:
 *   - left column  (220px fixed): sidebar with all controls
 *   - right column (1fr):         spectrogram main panel
 *
 * Using grid instead of flexbox here because the sidebar width needs to be
 * absolutely fixed — we don't want it to grow or shrink when the canvas
 * gets a lot of data.
 */
#app {
  display: grid;
  /* 340px sidebar gives room for window-function plots + equations without
     sacrificing the spectrogram area meaningfully on typical 1080p+ screens. */
  grid-template-columns: 340px 1fr;
  grid-template-rows: 100vh;
  height: 100vh;
  overflow: hidden;   /* prevent page-level scrollbars; each pane scrolls independently */
}

/* ── Sidebar ───────────────────────────────────────────────────────────── */
/*
 * The sidebar is a vertical strip. A thin 1px right border acts as a
 * physical separator — the same convention used by Audacity's toolbox.
 * No box-shadow: shadows imply depth/layering, which is a UI metaphor
 * for consumer products, not analysis tools.
 *
 * Scrolling rationale:
 *   overflow-y:auto is required so that when the total height of all
 *   sidebar sections exceeds 100vh the sidebar shows a scrollbar rather
 *   than compressing its children.  Without it, content would silently
 *   overflow the grid cell and be invisible.
 *
 *   min-height:0 is equally critical: grid and flex items default to
 *   min-height:auto, which lets them grow past their allocated track
 *   height.  Setting it to 0 allows the grid row (100vh) to actually
 *   constrain the sidebar, which is the prerequisite for overflow-y
 *   to have any effect.
 */
#sidebar {
  background: #1a1d23;
  border-right: 1px solid #2e3138;
  display: flex;
  flex-direction: column;
  gap: 0;             /* sections manage their own spacing with padding */
  /* overflow-y:auto — enables vertical scrolling when sections exceed 100vh */
  overflow-y: auto;
  /* overflow-x:hidden — clip any accidental horizontal overflow from content */
  overflow-x: hidden;
  min-width: 0;       /* allow the grid column to constrain this item */
  /* min-height:0 — allows the grid row to cap the sidebar at 100vh so
     overflow-y:auto can actually trigger scrolling */
  min-height: 0;
}

/* Application title — compact, left-aligned, no decoration */
.sidebar-title {
  padding: 8px 10px;
  border-bottom: 1px solid #2e3138;
  display: flex;
  align-items: center;
  gap: 8px;
  /* flex-shrink:0 — prevent flex from compressing the title bar.
     Without this, when sidebar content overflows the title row shrinks
     instead of the sidebar scrolling. */
  flex-shrink: 0;
}

.title-main {
  flex: 1;          /* fills space so button is always flush-right */
  min-width: 0;
  font-size: 13px;
  font-weight: bold;
  color: #e6e6e6;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.title-sub {
  font-size: 11px;
  color: #6a6f7a;
}

/* Each logical group of controls */
.sidebar-section {
  padding: 8px 10px;
  border-bottom: 1px solid #2e3138;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;   /* flex child: must opt out of min-width:auto to allow shrinking */
  /* flex-shrink:0 — CRITICAL: prevents flex from compressing sections when
     the total sidebar height exceeds the viewport.  Without this, flex
     distributes negative free space across all sections, squishing every
     input field.  Setting flex-shrink:0 forces sections to keep their
     natural height; the sidebar scrolls instead of compressing them. */
  flex-shrink: 0;
  overflow: hidden;
}

/*
 * Section heading labels (INPUT, PARAMETERS …).
 * All-caps, small, muted — matches the convention used in IDEs and
 * oscilloscope menu systems.
 */
.section-label {
  font-size: 10px;
  color: #6a6f7a;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding-bottom: 2px;
}

/* Individual parameter field: label sits directly above its control */
.field {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;   /* flex child must declare this or it won't shrink below content size */
  overflow: hidden;
}

/* Hidden fields collapse completely — toggled by handleComparisonMode() */
.field.hidden {
  display: none;
}

.field-label {
  font-size: 11px;
  color: #9aa0ad;
}

/* ── File input ─────────────────────────────────────────────────────────── */
/*
 * We use a visible button that triggers the hidden native file input.
 * This gives full CSS control without hiding accessibility — the native
 * input is still in the DOM and receives focus via keyboard.
 */
#audioInput {
  /* Hidden but accessible (not display:none which removes from tab order) */
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

.file-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;   /* allows the row itself to shrink inside its flex parent */
  overflow: hidden;
}

/* "Browse…" trigger button */
.btn-file {
  background: #252830;
  color: #c8cdd6;
  border: 1px solid #3a3f4a;
  border-radius: 2px;
  padding: 3px 8px;
  font-family: inherit;
  font-size: 12px;
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
}

.btn-file:hover {
  background: #2e333d;
  border-color: #5a8fff;
}

/* Filename display — truncates long paths with ellipsis */
.file-name {
  font-size: 11px;
  color: #6a6f7a;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;       /* allow flex child to shrink below content width */
}

.file-name.selected {
  color: #c8cdd6;
}

/* ── Select inputs ──────────────────────────────────────────────────────── */
/*
 * Selects use a flat appearance: no border-radius, minimal padding.
 * This matches the look of numeric spinboxes in MATLAB or LabVIEW — the
 * control is clearly interactive but doesn't dominate the panel visually.
 */
select {
  appearance: none;
  -webkit-appearance: none;
  background: #252830;
  color: #e6e6e6;
  border: 1px solid #3a3f4a;
  border-radius: 2px;
  padding: 3px 22px 3px 6px;
  font-family: inherit;
  font-size: 12px;
  cursor: pointer;
  outline: none;
  /* min-height prevents the element from collapsing below a usable size
     even if a parent flex container is misbehaving */
  min-height: 28px;
  box-sizing: border-box;
  /* Custom chevron drawn as a CSS background — avoids platform-native arrow
     which looks inconsistent across OSes */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%236a6f7a'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 6px center;
  width: 100%;
}

select:focus {
  border-color: #5a8fff;
  outline: 1px solid #5a8fff;
  outline-offset: 0;
}

select:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

/* ── Number inputs ──────────────────────────────────────────────────────── */
/*
 * Free-form numeric inputs share the same flat aesthetic as selects.
 * The native spinner arrows are hidden — the analyst types exact values.
 * No min/max is enforced at the CSS layer; validation is handled by the
 * computation layer (computeSTFT / prepareSegment) which throws descriptive
 * errors rather than silently clamping.
 */
input[type="number"] {
  appearance: textfield;
  -webkit-appearance: textfield;
  -moz-appearance: textfield;
  background: #252830;
  color: #e6e6e6;
  border: 1px solid #3a3f4a;
  border-radius: 2px;
  padding: 3px 6px;
  font-family: inherit;
  font-size: 12px;
  outline: none;
  width: 100%;
  /* min-height ensures the input never collapses below a usable tap/click
     target regardless of flex pressure from ancestor containers */
  min-height: 28px;
  box-sizing: border-box;
}

/* Remove spinner buttons in WebKit / Blink */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

input[type="number"]:focus {
  border-color: #5a8fff;
  outline: 1px solid #5a8fff;
  outline-offset: 0;
}

/* ── Range slider (time scale) ──────────────────────────────────────────── */
.slider-label-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 4px;
}

.slider-readout {
  font-size: 11px;
  color: #5a8fff;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  flex-shrink: 0;
  cursor: text;
  border-bottom: 1px dashed #3a3f4a;
}
.slider-readout:hover {
  border-bottom-color: #5a8fff;
}
/* Inline number input that replaces the readout on click */
.slider-readout-input {
  font-family: inherit;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: #5a8fff;
  background: #252830;
  border: 1px solid #5a8fff;
  border-radius: 2px;
  padding: 0 3px;
  width: 5ch;
  outline: none;
  text-align: right;
  flex-shrink: 0;
}

input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 4px;
  background: #3a3f4a;
  border-radius: 2px;
  outline: none;
  cursor: pointer;
  margin: 5px 0 3px;
}

input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #5a8fff;
  cursor: pointer;
  border: none;
}

input[type="range"]::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #5a8fff;
  cursor: pointer;
  border: none;
}

input[type="range"]:focus {
  outline: none;
}

input[type="range"]:focus::-webkit-slider-thumb {
  box-shadow: 0 0 0 2px rgba(90, 143, 255, 0.35);
}

/* ── Read-only metadata values (audio duration, sample rate) ───────────── */
/*
 * Displayed as plain text under a muted label.
 * Rendered in the accent colour to distinguish them from editable fields.
 */
.info-value {
  font-size: 12px;
  color: #5a8fff;
  letter-spacing: 0.03em;
}

/* ── Run button ─────────────────────────────────────────────────────────── */
/*
 * Full-width, flat, functional. No border-radius animation or colour
 * transition — state is communicated via disabled/enabled only.
 * The ▶ glyph immediately communicates "execute", like a transport control.
 */
#processBtn {
  width: 100%;
  background: #1e4a9a;
  color: #e6e6e6;
  border: 1px solid #2a5bbf;
  border-radius: 2px;
  padding: 6px 0;
  font-family: inherit;
  font-size: 12px;
  font-weight: bold;
  letter-spacing: 0.05em;
  cursor: pointer;
  text-align: center;
}

#processBtn:hover:not(:disabled) {
  background: #2456b8;
  border-color: #4a7fdf;
}

#processBtn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

/* ── Status bar ─────────────────────────────────────────────────────────── */
/*
 * Status sits at the bottom of the sidebar (margin-top:auto pushes it down).
 * It is a single-line readout — no card, no icon, no animation.
 * Similar to the status strip at the bottom of an IDE.
 */
.status-bar {
  margin-top: auto;   /* push to bottom of scrollable content */
  padding: 6px 10px;
  border-top: 1px solid #2e3138;
  font-size: 11px;
  color: #5a8fff;
  word-break: break-word;
  /* flex-shrink:0 — status bar must not be compressed by flex */
  flex-shrink: 0;
}

.status-bar.hidden {
  display: none;
}

.status-bar.error {
  color: #e05252;
  border-top-color: #6e2020;
}

/* ── Mobile collapse hint ───────────────────────────────────────────────── */
/*
 * Inline label in the sidebar-title row, right beside the collapse button.
 * display:none on desktop; shown on mobile via media query after a Run.
 */
.collapse-hint {
  display: none;
}

@media (max-width: 640px) {
  .collapse-hint {
    display: inline;
    font-size: 11px;
    color: #5a8fff;
    letter-spacing: 0.02em;
    /* title-main flex:1 absorbs remaining space, so hint+button are always at the right */
    margin-right: 4px;
    white-space: nowrap;
    cursor: pointer;
    animation: hint-fade-in 0.35s ease;
    /* Draw attention with a subtle pulsing opacity */
    animation: hint-fade-in 0.35s ease, hint-pulse 1.6s ease-in-out 0.35s infinite;
  }

  .collapse-hint.hidden {
    display: none;
  }

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

  @keyframes hint-pulse {
    0%, 100% { opacity: 1; }
    50%       { opacity: 0.45; }
  }
}

/* ── Sidebar collapse button ────────────────────────────────────────────── */
/*
 * The collapse button sits at the far-right of the sidebar title bar.
 * It has no border in rest state — just a muted glyph — so it doesn't
 * compete visually with the title text.
 */
.btn-collapse {
  flex-shrink: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 2px;
  color: #6a6f7a;
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
  padding: 1px 5px;
  line-height: 1;
  letter-spacing: -1px;
  flex-shrink: 0;
}

.btn-collapse:hover {
  color: #5a8fff;
  border-color: #3a3f4a;
  background: #252830;
}

/* Section icon buttons — hidden in expanded mode; one per sidebar section.
 * In collapsed mode they are the only visible element per section. */
.section-icon-btn {
  display: none;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 2px;
  color: #9aa0ad;
  font-size: 15px;
  font-family: inherit;
  cursor: pointer;
  padding: 7px 0;
  width: 100%;
  text-align: center;
  line-height: 1;
}

.section-icon-btn:hover {
  color: #5a8fff;
  border-color: #3a3f4a;
  background: #252830;
}

@media (max-width: 640px) {
  /* button is absolute — no margin adjustment needed */
}

/* ── Collapsed sidebar (desktop: narrow strip; mobile: thin top bar) ────── */
/*
 * A single class on #app drives the entire collapsed state.  On desktop the
 * grid column shrinks so the spectrogram gains the released space immediately.
 * The grid-template-columns override is intentionally kept inside a min-width
 * media query so mobile (which uses a row layout) is not affected.
 */
@media (min-width: 641px) {
  #app.sidebar-collapsed {
    grid-template-columns: 44px 1fr;
  }

  #app.sidebar-collapsed #sidebar {
    overflow: hidden;         /* clip the content that is now hidden */
    align-items: center;
  }
}

/* Hide all sidebar text content in collapsed mode (all viewport sizes) */
#app.sidebar-collapsed .title-main,
#app.sidebar-collapsed .title-sub,
#app.sidebar-collapsed .section-label,
#app.sidebar-collapsed .field,
#app.sidebar-collapsed #processBtn,
#app.sidebar-collapsed #playBtn,
#app.sidebar-collapsed .status-bar,
#app.sidebar-collapsed .collapse-hint,
#app.sidebar-collapsed #winVizCanvas,
#app.sidebar-collapsed #winEquation,
#app.sidebar-collapsed .win-freq-label,
#app.sidebar-collapsed #winFreqCanvas {
  display: none;
}

/* Show the icon buttons */
#app.sidebar-collapsed .section-icon-btn {
  display: block;
}

/* Hide buttons explicitly marked as not needed in collapsed mode */
#app.sidebar-collapsed .section-icon-btn[data-collapsed-hidden] {
  display: none;
}

/* Sidebar title when collapsed — desktop: center the lone button */
#app.sidebar-collapsed .sidebar-title {
  justify-content: center;
  padding: 8px 4px;
}

/* Each collapsed section is a centered icon cell */
#app.sidebar-collapsed .sidebar-section {
  padding: 2px 0;
  align-items: center;
  justify-content: center;
}

/*
 * Padding gives the spectrogram visual breathing room from the viewport edge.
 * min-height:0 is required so the grid item can shrink below auto-size in
 * a fixed-height grid (prevents the panel from overflowing 100vh).
 */
#mainPanel {
  background: #0f1115;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-height: 0;
  padding: 8px 10px;
}

/* Canvas wrapper: fixed-height spectrogram grid sits inside here */
#canvasWrapper {
  position: relative;
  flex: 1;
  height: 0;          /* flex:1 overrides this to fill #mainPanel; 0 prevents content blowout */
  display: flex;
  flex-direction: column;
  min-height: 200px;
  overflow: hidden;
}

/* Placeholder text shown before first render */
#placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  color: #3a3f4a;
  pointer-events: none;
  letter-spacing: 0.04em;
}

#placeholder.hidden {
  display: none;
}

/* The spectrogram canvas itself */
#spectrogramCanvas {
  display: block;
  width: 100%;          /* CSS width = container width; canvas.width = data columns */
  height: 100%;         /* CSS height fills the wrapper */
  object-fit: fill;
  image-rendering: pixelated;  /* no interpolation — preserve FFT bin boundaries */
}

/*
 * Cursor overlay canvas.
 * Absolutely positioned over the spectrogram so it doesn't affect layout.
 * pointer-events: none keeps all mouse/touch events on the zoom canvas below.
 * width/height attributes are set in JS on each draw to match clientWidth/Height.
 */
#cursorCanvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 3;
}

/* Zoom selection overlay: sits between spectrogram and cursor.
 * cursor:crosshair signals to the user that drag-to-zoom is active. */
#zoomCanvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  cursor: crosshair;
  z-index: 2;
}

/* Reset-zoom floating button — visible only when zoomed in */
.btn-reset-zoom {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 10;
  background: rgba(26, 29, 35, 0.88);
  color: #9aa0ad;
  border: 1px solid #3a3f4a;
  border-radius: 2px;
  padding: 3px 8px;
  font-family: inherit;
  font-size: 11px;
  cursor: pointer;
}

.btn-reset-zoom:hover {
  color: #5a8fff;
  border-color: #5a8fff;
}

.btn-reset-zoom.hidden {
  display: none;
}

/* Scroll/Zoom mode toggle — floats top-left of the spectrogram area */
.btn-scroll-mode {
  position: absolute;
  top: 6px;
  left: 6px;
  z-index: 10;
  background: rgba(26, 29, 35, 0.88);
  color: #9aa0ad;
  border: 1px solid #3a3f4a;
  border-radius: 2px;
  padding: 3px 8px;
  font-family: inherit;
  font-size: 11px;
  cursor: pointer;
}

.btn-scroll-mode:hover {
  color: #5a8fff;
  border-color: #5a8fff;
}

.btn-scroll-mode.active {
  color: #e6e6e6;
  background: rgba(30, 74, 154, 0.88);
  border-color: #5a8fff;
}

.btn-scroll-mode.hidden {
  display: none;
}

/* On mobile: always show the toggle so users can switch scroll / zoom */

/* ── spectroGrid — four-panel grid around the spectrogram ──────────────── */
/*
 * Columns: [y-axis 60px] [scroll area 1fr] [colorbar 58px]
 * Rows:    [data 1fr]    [x-axis 36px]
 *
 * The scroll area (col 2) spans BOTH rows so the x-axis scrolls with the
 * spectrogram. The y-axis and colorbar occupy only the data row.
 *
 * Fixed total height: the spectrogram always has the same vertical scale
 * regardless of screen size. Horizontal navigation is via scrolling.
 */
#spectroGrid {
  display: grid;
  grid-template-columns: 60px 1fr 58px;
  grid-template-rows: 1fr 36px 14px;
  flex: 1;
  min-width: 0;
  min-height: 0;
}

#spectroGrid.hidden {
  display: none;
}

/* Grid placement */
#yAxisCanvas           { grid-column: 1; grid-row: 1; }      /* data row only        */
#spectroScrollArea     { grid-column: 2; grid-row: 1 / 3; }  /* data + x-axis rows   */
#colorbarArea          { grid-column: 3; grid-row: 1; }      /* data row only        */
#spectroCustomScrollbar { grid-column: 2; grid-row: 3; }     /* custom scrollbar row */

/* Horizontal scroll container — wraps spectrogram + x-axis.
 * overflow-x: scroll keeps native momentum scrolling on desktop;
 * the native scrollbar is hidden via ::-webkit-scrollbar so only
 * our custom always-visible bar below is rendered. */
#spectroScrollArea {
  display: flex;
  flex-direction: column;
  overflow-x: scroll;
  overflow-y: hidden;
  min-width: 0;
}

/* Hide the native scrollbar on all platforms */
#spectroScrollArea::-webkit-scrollbar { display: none; }
#spectroScrollArea { scrollbar-width: none; }

/* ── Custom always-visible scrollbar ─────────────────────────────────── */
#spectroCustomScrollbar {
  position: relative;
  background: #1a1d23;
  border-top: 1px solid #2e3138;
  cursor: pointer;
  user-select: none;
}

#spectroCustomThumb {
  position: absolute;
  top: 2px;
  height: 10px;
  min-width: 24px;
  background: #4a5060;
  border-radius: 3px;
  cursor: grab;
  transition: background 0.15s;
}

#spectroCustomThumb:hover  { background: #5a8fff; }
#spectroCustomThumb.active { background: #5a8fff; cursor: grabbing; }

/* Spectrogram cell: width driven by JS (time-scale × view duration) */
#spectroArea {
  flex: 1;            /* fills scroll container height minus x-axis */
  position: relative;
  overflow: hidden;
  min-width: 0;
  min-height: 0;
}

/* Colorbar cell */
#colorbarArea {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-left: 1px solid #2e3138;
}

#colorbarCanvas {
  display: block;
  width: 100%;
  flex: 1 1 0;
  min-height: 0;
}

/* Y-axis: fills its grid cell, contains absolutely-positioned tick children */
#yAxisDiv {
  position: relative;
  width: 100%;
  height: 100%;
  border-right: 1px solid #2e3138;
  overflow: hidden;
}

/* X-axis: sits at the bottom of the scroll container */
#xAxisDiv {
  position: relative;
  flex-shrink: 0;
  height: 36px;
  border-top: 1px solid #2e3138;
  overflow: hidden;
}

/* ── Axis tick marks and labels (shared by all axis divs) ──────────────── */
/* X-axis ticks: centered on their data position, tick line above, label below */
.tick-x {
  position: absolute;
  top: 7px;
  transform: translateX(-50%);
  font: 10px Consolas, Menlo, monospace;
  color: #6a6f7a;
  white-space: nowrap;
  pointer-events: none;
  line-height: 1;
}
.tick-x::before {
  content: '';
  position: absolute;
  top: -7px;
  left: 50%;
  transform: translateX(-50%);
  width: 1px;
  height: 5px;
  background: #3a3f4a;
}
/* Y-axis ticks: centered vertically on their data position, label left of tick line */
.tick-y {
  position: absolute;
  right: 6px;
  transform: translateY(50%);
  font: 10px Consolas, Menlo, monospace;
  color: #6a6f7a;
  white-space: nowrap;
  pointer-events: none;
  line-height: 1;
  text-align: right;
}
.tick-y::after {
  content: '';
  position: absolute;
  right: -6px;
  top: 50%;
  transform: translateY(-50%);
  width: 4px;
  height: 1px;
  background: #3a3f4a;
}
/* Axis title labels */
.axis-title-x {
  position: absolute;
  right: 4px;
  top: 7px;
  font: 10px Consolas, Menlo, monospace;
  color: #9aa0ad;
  pointer-events: none;
  white-space: nowrap;
}
.axis-title-y {
  position: absolute;
  left: 10px;
  top: 50%;
  font: 9px Consolas, Menlo, monospace;
  color: #9aa0ad;
  pointer-events: none;
  white-space: nowrap;
  transform: translate(-50%, -50%) rotate(-90deg);
}

/* ── Side-by-side comparison container ─────────────────────────────────── */
/*
 * Mirrors the structure of #spectroGrid but holds 4 panels arranged in a
 * 2×2 grid.  The layout is:
 *   [y-axis 60px] [scroll area 1fr] [colorbar 58px]
 *   row 1: panel row (1fr)
 *   row 2: x-axis (36px)
 *   row 3: custom scrollbar (14px)
 */
#sbsContainer {
  display: grid;
  grid-template-columns: 60px 1fr 58px;
  grid-template-rows: 1fr 36px 14px;
  flex: 1;
  min-width: 0;
  min-height: 0;
}

#sbsContainer.hidden {
  display: none;
}

#sbsYAxisWrapper     { grid-column: 1; grid-row: 1; display: flex; flex-direction: column; border-right: 1px solid #2e3138; overflow: hidden; }
#sbsYAxisDiv         { position: relative; width: 100%; overflow: hidden; flex-shrink: 0; }
#sbsMain             { grid-column: 2; grid-row: 1 / 3; display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }
#sbsColorbarArea     { grid-column: 3; grid-row: 1; display: flex; flex-direction: column; overflow: hidden; border-left: 1px solid #2e3138; }
#sbsCustomScrollbar  { grid-column: 2; grid-row: 3; position: relative; background: #1a1d23; border-top: 1px solid #2e3138; cursor: pointer; user-select: none; }

#sbsColorbarCanvas {
  display: block;
  width: 100%;
  flex: 1 1 0;
  min-height: 0;
}

/* 3-column grid of panels (fills the scroll area) */
#sbsPanelsRow {
  flex: 1;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 3px;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

.sbs-panel {
  display: flex;
  flex-direction: column;
  border: 1px solid #2e3138;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

.sbs-panel-title {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  font-size: 10px;
  color: #6a6f7a;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 2px 4px;
  background: #1a1d23;
  border-bottom: 1px solid #2e3138;
}

.sbs-maximize-btn {
  margin-left: auto;
  flex-shrink: 0;
  background: transparent;
  border: none;
  color: #6a6f7a;
  cursor: pointer;
  font-size: 12px;
  padding: 0 2px;
  line-height: 1;
  font-style: normal;
  text-transform: none;
  letter-spacing: 0;
}
.sbs-maximize-btn:hover { color: #5a8fff; }

/* Per-panel horizontal scroll container — synced via JS */
.sbs-panel-scroll {
  flex: 1;
  overflow-x: scroll;
  overflow-y: hidden;
  min-height: 0;
}
.sbs-panel-scroll::-webkit-scrollbar { display: none; }
.sbs-panel-scroll { scrollbar-width: none; }

/* Scrollable content area; width set by JS from pxPerSec */
.sbs-panel-inner {
  position: relative;
  height: 100%;
}

.sbs-canvas {
  display: block;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
}

/* Cursor overlay for each SBS panel. Mirrors #cursorCanvas behaviour.
 * Sits above the spectrogram (z-index 3) but below nothing that needs
 * pointer events — pointer-events:none lets the zoom canvas underneath
 * receive all mouse/touch input. */
.sbs-cursor-canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 3;
}

.sbs-zoom-canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  cursor: crosshair;
  z-index: 4;
}

/* Wrapper covers only the LEFT panel column (half of sbsMain).
   Both columns show the same time range, so one axis suffices. */
#sbsXAxisWrapper {
  flex-shrink: 0;
  height: 36px;
  /* Width is set by JS to match the left panel scroll area exactly, so that
     the scroll range aligns perfectly with the panel scroll range. */
  overflow-x: scroll;
  overflow-y: hidden;
  border-top: 1px solid #2e3138;
}
#sbsXAxisWrapper::-webkit-scrollbar { display: none; }
#sbsXAxisWrapper { scrollbar-width: none; }

#sbsXAxisDiv {
  position: relative;
  height: 36px;
  overflow: hidden;  /* clip labels that would extend beyond the content boundary */
}

/* Maximized-panel state: hide all other panels, stretch the maximized one */
#sbsContainer.has-maximized .sbs-panel:not(.maximized) {
  display: none;
}
#sbsContainer.has-maximized .sbs-panel.maximized {
  grid-column: 1 / -1;
  grid-row: 1 / -1;
}

/* Per-panel zoom reset button */
.sbs-reset-btn {
  position: absolute;
  top: 4px;
  right: 4px;
  z-index: 10;
  background: rgba(26, 29, 35, 0.88);
  color: #9aa0ad;
  border: 1px solid #3a3f4a;
  border-radius: 2px;
  padding: 2px 6px;
  font-family: inherit;
  font-size: 11px;
  cursor: pointer;
}
.sbs-reset-btn:hover { color: #5a8fff; border-color: #5a8fff; }
.sbs-reset-btn.hidden { display: none; }

#sbsCustomThumb {
  position: absolute;
  top: 2px;
  height: 10px;
  min-width: 24px;
  background: #4a5060;
  border-radius: 3px;
  cursor: grab;
  transition: background 0.15s;
}

#sbsCustomThumb:hover  { background: #5a8fff; }
#sbsCustomThumb.active { background: #5a8fff; cursor: grabbing; }

/* Mobile scroll/zoom toggle — hidden on desktop, shown as a bar on mobile */
#sbsScrollModeBtn {
  display: none;
}

/* ── Responsive: mobile (single-column stacked layout) ─────────────────── */
/*
 * On narrow screens the sidebar stacks above the spectrogram.
 *
 * The previous horizontal-row layout caused select dropdowns to be clipped:
 * native <select> menus render in a separate browser overlay layer, and when
 * their parent has overflow:auto the browser clips the popup to that scroll
 * container on some platforms.
 *
 * Stacking the sidebar vertically (flex-direction: column, the default)
 * means the sidebar has no scroll container at all on mobile — it just
 * expands to fit its content. Dropdowns open freely into the viewport.
 */
@media (max-width: 640px) {
  /*
   * Natural scrolling layout on mobile: the page is taller than the viewport
   * and scrolls like a normal document.  #app stacks sidebar above main panel.
   * #mainPanel has an explicit height so #canvasWrapper / #spectroGrid
   * (which use flex:1) have a definite parent dimension to size against.
   * 75vw gives a roughly square spectrogram on portrait phones; the 320px
   * floor keeps it usable on very narrow screens.
   */
  #app {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto;
    height: auto;
    min-height: unset;
    overflow: visible;
  }

  #sidebar {
    border-right: none;
    border-bottom: 1px solid #2e3138;
    overflow-y: visible;  /* let content expand; page scroll handles overflow */
    overflow-x: hidden;
  }

  /*
   * Override the desktop overflow:hidden on .sidebar-section so input-field
   * boxes are never clipped mid-element.
   */
  .sidebar-section {
    overflow: visible;
    border-bottom: 1px solid #2e3138;
  }

  .status-bar {
    margin-top: 0;
  }

  #mainPanel {
    height: auto;
    overflow: visible;
  }

  #canvasWrapper {
    flex: none;         /* don't stretch infinitely in auto-height parent */
    height: 350px;      /* explicit definite height so spectroGrid fr-rows resolve */
    min-height: 350px;
  }

  /* Mobile collapsed: convert the sidebar to a compact horizontal icon bar */
  #app.sidebar-collapsed #sidebar {
    flex-direction: row;
    justify-content: center;
    align-items: stretch;
    overflow: hidden;
    max-height: none;
    border-bottom: 1px solid #2e3138;
  }

  #app.sidebar-collapsed .sidebar-title {
    border-bottom: none;
    border-right: 1px solid #2e3138;
    padding: 0 8px;
    justify-content: center;
    align-items: center;
    flex-direction: row;
  }

  #app.sidebar-collapsed .sidebar-section {
    border-bottom: none;
    border-right: 1px solid #2e3138;
    padding: 4px 2px;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    overflow: hidden;
  }

  #app.sidebar-collapsed .section-icon-btn {
    padding: 8px 10px;
  }

  /* SBS: stack panels vertically instead of 2×2 grid */
  #sbsContainer {
    display: flex;              /* override desktop display:grid */
    flex-direction: column;
    height: auto;
    overflow-y: auto;
  }

  #sbsYAxisCanvas,
  #sbsColorbarArea,
  #sbsCustomScrollbar {
    display: none;   /* hidden on mobile — each panel scrolls independently */
  }

  #sbsMain {
    grid-column: unset;
    grid-row: unset;
    overflow: visible;
    min-height: 0;
  }

  #sbsPanelsRow {
    display: flex;              /* override desktop display:grid */
    flex-direction: column;
    grid-template-columns: unset;
    grid-template-rows: unset;
    gap: 6px;
    overflow: visible;
  }

  .sbs-panel {
    min-height: 220px;  /* each panel gets its own comfortable height */
    height: auto;
  }

  .sbs-panel-inner {
    height: 180px;  /* fixed drawing height for each panel on mobile */
  }

  #sbsXAxisWrapper {
    display: none;   /* per-panel x-axis not available in list layout */
  }

  /* Mobile scroll/zoom toggle bar — shown above the panels list */
  #sbsScrollModeBtn {
    display: block;
    width: 100%;
    padding: 7px 10px;
    font-size: 11px;
    font-family: inherit;
    background: #1e2129;
    color: #9aa0ad;
    border: none;
    border-bottom: 1px solid #2e3138;
    cursor: pointer;
    text-align: left;
    flex-shrink: 0;
    letter-spacing: 0.05em;
  }

  #sbsScrollModeBtn.active {
    color: #e6e6e6;
    background: rgba(30, 74, 154, 0.85);
  }
}

/* ── Window Function Visualization panel ───────────────────────────────── */
/*
 * A single canvas (#winVizCanvas) serves all comparison modes.  Switching
 * from four stacked per-window panels to one unified overlay canvas fixes
 * two usability problems:
 *
 *   1. Stacked panels forced the sidebar to scroll past hundreds of pixels
 *      to see all four windows — only the top panel was visible without
 *      scrolling.  A single canvas stays in one screen region.
 *
 *   2. Overlaid curves on shared axes let the eye make direct amplitude
 *      comparisons (e.g. Hamming ≈ 0.08 at ends vs. Hann ≈ 0) without
 *      mentally aligning four separate plots.
 *
 * Height 140px gives room for the 8px top pad, ~100px plot area, and
 * 24px bottom pad for X-axis tick labels.  JS writes the intrinsic pixel
 * dimensions at render time for DPR-correct sharpness.
 */

/* The shared canvas element — width:100% fills the sidebar section */
.win-canvas {
  display: block;
  width: 100%;
  height: 140px;
  background: #0f1115;
  border: 1px solid #2e3138;
  border-radius: 2px;
  /* pixelated keeps axis lines crisp at non-integer DPR scales */
  image-rendering: pixelated;
}

/*
 * Frequency-response canvas: slightly taller than the time-domain canvas to
 * give the five dB tick labels (0, −20, −40, −60, −80) enough vertical room
 * without crowding.  Same styling conventions as .win-canvas.
 */
.win-freq-label {
  font-size: 10px;
  color: #4a5060;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-top: 6px;
  margin-bottom: 2px;
}

.win-freq-canvas {
  display: block;
  width: 100%;
  height: 160px;
  background: #0f1115;
  border: 1px solid #2e3138;
  border-radius: 2px;
  image-rendering: pixelated;
}

/*
 * KaTeX equation container — shown only in single mode.
 * overflow-x:auto lets the Blackman equation (widest) scroll rather than
 * overflow the sidebar.  min-height prevents collapse before katex.render()
 * has run for the first time.
 */
.win-equation {
  font-size: 12px;
  color: #9aa0ad;
  overflow-x: auto;
  padding: 2px 0;
  min-height: 28px;
  line-height: 1.5;
}

/* hidden utility class — JS adds/removes to show/hide the equation in SBS mode */
.win-equation.hidden {
  display: none;
}

/* Scale KaTeX output to match the surrounding sidebar text size */
.win-equation .katex {
  font-size: 1em;
}

/* ── Mobile notice ──────────────────────────────────────────────────────── */
#mobileNotice {
  display: none; /* hidden on desktop */
}

@media (max-width: 640px) {
  #mobileNotice {
    display: flex;
    flex-direction: column;
    gap: 8px;
    background: #1a1d23;
    border-bottom: 1px solid #4a90d9;
    padding: 14px 16px;
    font-family: inherit;
    font-size: 12px;
    color: #c8d0dc;
  }

  #mobileNotice strong {
    font-size: 13px;
    color: #e6e6e6;
    letter-spacing: 0.04em;
  }

  #mobileNotice p {
    margin: 0;
    line-height: 1.5;
  }

  #mobileNoticeDismiss {
    align-self: flex-start;
    padding: 5px 12px;
    font-family: inherit;
    font-size: 11px;
    letter-spacing: 0.05em;
    background: #252930;
    color: #9aa0ad;
    border: 1px solid #3a3f4a;
    cursor: pointer;
  }

  #mobileNoticeDismiss:hover {
    color: #e6e6e6;
    border-color: #5a6070;
  }

  #mobileNotice.hidden {
    display: none;
  }
}

/* ── Microphone recording controls ────────────────────────────────────────── */

/* Row holding Record + Stop buttons */
.rec-controls {
  display: flex;
  gap: 6px;
  margin-bottom: 6px;
}

/* Shared base for both rec buttons — matches .btn-file sizing */
.btn-rec-start,
.btn-rec-stop {
  flex: 1;
  padding: 4px 6px;
  font-family: inherit;
  font-size: 11px;
  letter-spacing: 0.05em;
  border: 1px solid #3a3f4a;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}

/* Record button: red accent when enabled */
.btn-rec-start {
  background: #2a1a1a;
  color: #cc3333;
  border-color: #5a2a2a;
}
.btn-rec-start:not(:disabled):hover {
  background: #3a2020;
  color: #ff4444;
  border-color: #884444;
}

/* Stop button: neutral when enabled, dimmed when disabled */
.btn-rec-stop {
  background: #1e2228;
  color: #9aa0ad;
  border-color: #3a3f4a;
}
.btn-rec-stop:not(:disabled):hover {
  background: #252930;
  color: #c8d0dc;
  border-color: #5a6070;
}

/* Disabled state for both */
.btn-rec-start:disabled,
.btn-rec-stop:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Pulsing animation used on Record button while recording */
@keyframes rec-pulse {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0.5; }
}
.btn-rec-start.recording {
  animation: rec-pulse 1s ease-in-out infinite;
  color: #ff2222;
  border-color: #882222;
}

/* Pulsing red for the collapsed record icon when recording */
.section-icon-btn.recording {
  animation: rec-pulse 1s ease-in-out infinite;
  color: #ff2222 !important;
}

/* Pulsing blue for the collapsed run icon (and expanded processBtn) when a file is ready */
@keyframes run-pulse {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0.45; }
}
.section-icon-btn.blinking {
  animation: run-pulse 0.9s ease-in-out infinite;
  color: #5a8fff !important;
}
#processBtn.blinking {
  animation: run-pulse 0.9s ease-in-out infinite;
  box-shadow: 0 0 0 2px #2a5bbf;
}

/* Save recording button */
.btn-rec-save {
  width: 100%;
  margin-top: 5px;
  padding: 4px 6px;
  font-family: inherit;
  font-size: 11px;
  letter-spacing: 0.05em;
  border: 1px solid #2a4a3a;
  background: #1a2a22;
  color: #44aa77;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.btn-rec-save:not(:disabled):hover {
  background: #20342a;
  color: #55cc88;
  border-color: #448866;
}
.btn-rec-save:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.btn-rec-save.hidden {
  display: none;
}

/* Status text: Idle / Recording… / Processing… */
.rec-status {
  font-family: inherit;
  font-size: 11px;
  color: #6a6f7a;
  letter-spacing: 0.04em;
  margin-bottom: 2px;
}
.rec-status.active {
  color: #ff5555;
}
.rec-status.processing {
  color: #4a90d9;
}

/* Elapsed timer */
.rec-timer {
  font-family: Consolas, Menlo, monospace;
  font-size: 12px;
  color: #9aa0ad;
  letter-spacing: 0.06em;
}
.rec-timer.hidden {
  display: none;
}

/* ── Info button (sidebar title bar) ────────────────────────────────────── */
.btn-info {
  flex-shrink: 0;
  background: transparent;
  border: 1px solid #3a3f4a;
  border-radius: 50%;
  color: #6a6f7a;
  font-size: 12px;
  font-family: inherit;
  font-weight: bold;
  cursor: pointer;
  width: 20px;
  height: 20px;
  line-height: 18px;
  text-align: center;
  padding: 0;
}

.btn-info:hover {
  color: #5a8fff;
  border-color: #5a8fff;
  background: #252830;
}

/* Hide info button text in collapsed sidebar */
#app.sidebar-collapsed .btn-info {
  display: none;
}

/* ── Info modal ──────────────────────────────────────────────────────────── */
.info-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(10, 11, 15, 0.82);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}

.info-modal.hidden {
  display: none;
}

.info-modal-box {
  background: #1a1d23;
  border: 1px solid #2e3138;
  border-radius: 3px;
  width: 100%;
  max-width: 680px;
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.info-modal-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-bottom: 1px solid #2e3138;
  flex-shrink: 0;
}

.info-modal-title {
  flex: 1;
  font-size: 12px;
  font-weight: bold;
  color: #e6e6e6;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.info-modal-close {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 2px;
  color: #6a6f7a;
  font-size: 14px;
  font-family: inherit;
  cursor: pointer;
  padding: 1px 5px;
  line-height: 1;
  flex-shrink: 0;
}

.info-modal-close:hover {
  color: #e05252;
  border-color: #6e2020;
  background: #252830;
}

.info-modal-body {
  overflow-y: auto;
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  font-size: 12px;
  line-height: 1.6;
  color: #c8cdd6;
}

.info-modal-body a {
  color: #7ab4f5;
  text-decoration: none;
}

.info-modal-body a:hover {
  text-decoration: underline;
}

.info-section {
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.info-h2 {
  font-size: 11px;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #5a8fff;
  border-bottom: 1px solid #2e3138;
  padding-bottom: 3px;
  margin: 0;
}

.info-modal-body p {
  margin: 0;
}

.info-modal-body strong {
  color: #e6e6e6;
}

.info-ol {
  margin: 0;
  padding-left: 1.3em;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.info-ul {
  margin: 4px 0 0 0;
  padding-left: 1.3em;
  display: flex;
  flex-direction: column;
  gap: 4px;
  list-style: disc;
}

/* nested ul inside table cell */
.info-table .info-ul {
  margin-top: 4px;
}

.info-modal-body code, .info-modal-body kbd {
  background: #252830;
  border: 1px solid #3a3f4a;
  border-radius: 2px;
  padding: 0 4px;
  font-family: Consolas, Menlo, "Liberation Mono", monospace;
  font-size: 11px;
  color: #e6e6e6;
}

.info-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}

.info-table th, .info-table td {
  text-align: left;
  padding: 5px 8px;
  border: 1px solid #2e3138;
  vertical-align: top;
}

.info-table thead th {
  background: #252830;
  color: #9aa0ad;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.07em;
}

.info-table tbody tr:nth-child(even) {
  background: #14171d;
}

.info-table code {
  white-space: nowrap;
}
