ChatGPT commited on
Commit
f026127
·
1 Parent(s): ab6f318

feat: align ui with reference sample extractor layout

Browse files
README.md CHANGED
@@ -211,3 +211,8 @@ The default UI is now intentionally simple:
211
  Advanced parameters, run history, raw tables, and supervised semantic editing remain available in collapsed panels, but they are no longer required for the common path.
212
 
213
  See `docs/AUTOMATIC_CARD_FLOW_UI.md`.
 
 
 
 
 
 
211
  Advanced parameters, run history, raw tables, and supervised semantic editing remain available in collapsed panels, but they are no longer required for the common path.
212
 
213
  See `docs/AUTOMATIC_CARD_FLOW_UI.md`.
214
+
215
+
216
+ ### Reference-style UI update
217
+
218
+ The web UI now follows the supplied Sample Extractor reference: waveform-first canvas, grouped sample columns, persistent right settings panel, compact export bar, and a bottom selection/tools bar. Drop/upload still starts processing automatically.
docs/FEATURES.md CHANGED
@@ -143,3 +143,10 @@ Status: implemented.
143
  | UI | Zoomable/pannable waveform | Implemented | Pinch/ctrl-wheel zoom and horizontal/shift-wheel pan. |
144
  | Pipeline | Automatic tuning | Implemented | `auto_tune=true` chooses onset sensitivity and grouping bounds from the separated target stem. |
145
  | Export | MIDI fallback | Implemented | Missing `pretty_midi` no longer breaks extraction; a compact fallback MIDI writer is used. |
 
 
 
 
 
 
 
 
143
  | UI | Zoomable/pannable waveform | Implemented | Pinch/ctrl-wheel zoom and horizontal/shift-wheel pan. |
144
  | Pipeline | Automatic tuning | Implemented | `auto_tune=true` chooses onset sensitivity and grouping bounds from the separated target stem. |
145
  | Export | MIDI fallback | Implemented | Missing `pretty_midi` no longer breaks extraction; a compact fallback MIDI writer is used. |
146
+
147
+
148
+ ## Reference-style visual workflow
149
+
150
+ - The default web UI is now a reference-style sample extractor workspace: compact top bar, large waveform, persistent settings panel, grouped sample columns, and bottom selection bar.
151
+ - Users can still just drop audio anywhere; waveform rendering and extraction begin automatically.
152
+ - Expert parameters and semantic editing tools are available without cluttering the default path.
docs/PROGRESS.md CHANGED
@@ -362,3 +362,11 @@ Validation performed:
362
  - `scripts/test_sse_and_review_hits.py`.
363
  - `scripts/test_supervised_export_and_force_onset.py`.
364
  - `scripts/test_interactive_supervision.py`.
 
 
 
 
 
 
 
 
 
362
  - `scripts/test_sse_and_review_hits.py`.
363
  - `scripts/test_supervised_export_and_force_onset.py`.
364
  - `scripts/test_interactive_supervision.py`.
365
+
366
+
367
+ ## 2026-05-12 Reference image UI alignment
368
+
369
+ - Reworked the frontend shell to match the provided Sample Extractor reference image.
370
+ - Added centered file picker/current filename, right-aligned export actions, persistent right settings panel, waveform-first canvas, grouped sample columns, and compact bottom selection bar.
371
+ - Kept automatic drop-to-process behavior and progressive sample-card rendering.
372
+ - Moved secondary pipeline/history/supervision/tables into a compact tools drawer.
docs/REFERENCE_IMAGE_UI_ALIGNMENT.md ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Reference Image UI Alignment
2
+
3
+ This pass aligns the automatic card-flow web app with the supplied reference screenshot.
4
+
5
+ ## Implemented
6
+
7
+ - Top application bar now follows the reference structure:
8
+ - compact waveform logo
9
+ - `Sample Extractor` title
10
+ - beta pill
11
+ - centered file picker/current filename
12
+ - right-aligned export actions
13
+ - Main canvas is now split into:
14
+ - large waveform area on the left
15
+ - persistent settings panel on the right
16
+ - grouped sample board below the waveform
17
+ - compact bottom selection/action bar
18
+ - The waveform panel includes:
19
+ - zoom controls
20
+ - current file/progress status
21
+ - time display
22
+ - transport row
23
+ - progress-tinted waveform rendering already provided by the previous real-progress pass
24
+ - Sample results now visually match the card/column model:
25
+ - columns by detected type
26
+ - per-column count and draw button
27
+ - compact cards with mini-waveform, label, metadata, and small action strip
28
+ - Everyday controls are visible in the right panel, while expert pipeline controls are collapsed under `Expert pipeline controls`.
29
+ - Existing automatic behavior is preserved:
30
+ - drop/upload starts processing immediately
31
+ - browser-side waveform renders immediately
32
+ - progressive sample cards appear while processing
33
+ - dismiss, draw another, trim, extend, and save-edit actions remain available
34
+
35
+ ## Intentional differences from the reference
36
+
37
+ - The screenshot's exact icons are approximated with Unicode/text controls to avoid adding an icon dependency.
38
+ - `Export Selected` currently exports the generated pack because backend-level selected-card export is not yet implemented as a distinct artifact path.
39
+ - Selection is currently all visible cards by default; the next iteration should add true per-card checkboxes and export only selected representatives.
40
+ - The right panel remains visible by default because the reference shows it open.
41
+
42
+ ## Validation targets
43
+
44
+ - No document-level scrolling on desktop sizes.
45
+ - Waveform and sample board fill the viewport.
46
+ - Advanced controls are available but not part of the primary visual path.
47
+ - All previous backend APIs and semantic editing features remain reachable through the tools drawer.
docs/REMAINING_WORK.md CHANGED
@@ -95,3 +95,11 @@ The default UI is now a cleaner fixed, non-scrolling workstation layout with col
95
  - Make card trim/extend rewrite preview audio immediately, not only create a forced hit for edited export.
96
  - Add true cached feature-vector local reclustering after dismiss/draw/move decisions.
97
  - Add browser-level tests for drag/drop auto-run, progressive cards, waveform zoom/pan, dismiss, draw, and clip edit flows.
 
 
 
 
 
 
 
 
 
95
  - Make card trim/extend rewrite preview audio immediately, not only create a forced hit for edited export.
96
  - Add true cached feature-vector local reclustering after dismiss/draw/move decisions.
97
  - Add browser-level tests for drag/drop auto-run, progressive cards, waveform zoom/pan, dismiss, draw, and clip edit flows.
98
+
99
+
100
+ ## Remaining UI fidelity work
101
+
102
+ - Add true per-card checkbox state instead of treating all visible cards as selected.
103
+ - Add selected-only backend export so `Export Selected` creates an artifact containing only selected representatives.
104
+ - Replace Unicode icons with a small icon system if exact visual parity is required.
105
+ - Validate with a real browser screenshot comparison against the supplied reference image.
docs/TASKS.md CHANGED
@@ -191,3 +191,14 @@ Next:
191
  - [ ] Promote drawn candidate cards into backend representative choices.
192
  - [ ] Add immediate local reclustering after dismiss/draw constraints.
193
  - [ ] Add browser tests for the automatic card-flow path.
 
 
 
 
 
 
 
 
 
 
 
 
191
  - [ ] Promote drawn candidate cards into backend representative choices.
192
  - [ ] Add immediate local reclustering after dismiss/draw constraints.
193
  - [ ] Add browser tests for the automatic card-flow path.
194
+
195
+
196
+ ## Reference image UI alignment
197
+
198
+ - [x] Match top bar structure: brand, beta badge, current file, export buttons.
199
+ - [x] Match main layout: waveform left, settings right, sample columns below.
200
+ - [x] Keep automatic upload/process flow.
201
+ - [x] Keep advanced controls hidden behind a collapsed expert section.
202
+ - [x] Preserve existing review/edit tools in a secondary drawer.
203
+ - [ ] Implement true per-card selection and selected-only export artifacts.
204
+ - [ ] Run browser screenshot comparison in an environment that allows localhost rendering.
web/app.js CHANGED
@@ -721,6 +721,23 @@ function groupedSamples(samples) {
721
  });
722
  }
723
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
724
  async function dismissSample(sample) {
725
  dismissedSampleKeys.add(sample._key || sampleKey(sample));
726
  renderSamples(lastResult || { samples: [] });
@@ -788,12 +805,13 @@ async function saveSampleEdit(sample) {
788
  function renderSamples(result) {
789
  const samples = visibleSamples(result);
790
  if ($("sampleCountLabel")) $("sampleCountLabel").textContent = `(${samples.length})`;
 
791
 
792
  const grid = $("samplesGrid");
793
  if (grid) {
794
  const groups = groupedSamples(samples);
795
  grid.innerHTML = groups.map(([type, rows]) => `
796
- <section class="sample-column" data-sample-type="${esc(type)}">
797
  <header class="sample-column-head">
798
  <strong>${esc(type)}</strong>
799
  <span>${rows.length}</span>
@@ -806,7 +824,7 @@ function renderSamples(result) {
806
  const edit = sample._edit || { startMs: 0, tailMs: 0 };
807
  const editLabel = (edit.startMs || edit.tailMs) ? ` · edit ${edit.startMs >= 0 ? "+" : ""}${edit.startMs}ms/${edit.tailMs >= 0 ? "+" : ""}${edit.tailMs}ms` : "";
808
  return `
809
- <article class="sample-card ${absoluteIndex === selectedSampleIndex ? "selected" : ""}" style="border-top-color: ${esc(color)}" data-sample-card="${absoluteIndex}">
810
  <button class="sample-play-zone" type="button" data-sample-audition="${absoluteIndex}">
811
  <canvas class="sample-wave" data-wave-url="${esc(sample.url)}" data-wave-color="${esc(color)}"></canvas>
812
  <span class="sample-card-footer">
@@ -1336,8 +1354,8 @@ function setFile(file) {
1336
  clearError();
1337
  selectedFile = file;
1338
  clearRunViews();
1339
- $("dropTitle").textContent = file ? file.name : "Choose an audio file";
1340
- $("dropMeta").textContent = file ? `${(file.size / 1024 / 1024).toFixed(2)} MB · ${file.type || "audio"}` : "WAV, MP3, FLAC, AIFF, OGG, M4A";
1341
  $("runButton").disabled = !file;
1342
  currentProgress = { fraction: 0, status: "idle", stage_label: null, stage_fraction: 0 };
1343
  currentJobStatus = "idle";
@@ -1556,4 +1574,30 @@ window.addEventListener("resize", () => {
1556
  drawWaveform(activeOverview());
1557
  });
1558
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1559
  boot();
 
721
  });
722
  }
723
 
724
+ function updateSelectedExportCount(count) {
725
+ const text = `${count} Selected`;
726
+ if ($("selectedCountTop")) $("selectedCountTop").textContent = `(${count})`;
727
+ if ($("selectedCountBottom")) $("selectedCountBottom").textContent = text;
728
+ if ($("exportSelectedButton")) $("exportSelectedButton").disabled = count === 0 || !lastResult;
729
+ if ($("exportAllButton")) $("exportAllButton").disabled = !lastResult;
730
+ }
731
+
732
+ function updateControlOutputs() {
733
+ const pct = Math.round((Number($("onset_delta")?.value || 0) / 0.35) * 100);
734
+ if ($("sensitivityOutput")) $("sensitivityOutput").textContent = Number.isFinite(pct) ? `${pct}%` : "Auto";
735
+ if ($("minGapOutput")) $("minGapOutput").textContent = `${Math.round(Number($("min_gap")?.value || 0) * 1000)} ms`;
736
+ if ($("energyOutput")) $("energyOutput").textContent = `${Number($("energy_threshold_db")?.value || 0).toFixed(0)} dB`;
737
+ if ($("targetMaxOutput")) $("targetMaxOutput").textContent = String($("target_max")?.value || "Auto");
738
+ if ($("nccOutput")) $("nccOutput").textContent = `${Math.round(Number($("ncc_threshold")?.value || 0) * 100)}%`;
739
+ }
740
+
741
  async function dismissSample(sample) {
742
  dismissedSampleKeys.add(sample._key || sampleKey(sample));
743
  renderSamples(lastResult || { samples: [] });
 
805
  function renderSamples(result) {
806
  const samples = visibleSamples(result);
807
  if ($("sampleCountLabel")) $("sampleCountLabel").textContent = `(${samples.length})`;
808
+ updateSelectedExportCount(samples.length);
809
 
810
  const grid = $("samplesGrid");
811
  if (grid) {
812
  const groups = groupedSamples(samples);
813
  grid.innerHTML = groups.map(([type, rows]) => `
814
+ <section class="sample-column" data-sample-type="${esc(type)}" style="--column-color: ${esc(clusterColor(rows[0]?.cluster_id ?? type.length))}">
815
  <header class="sample-column-head">
816
  <strong>${esc(type)}</strong>
817
  <span>${rows.length}</span>
 
824
  const edit = sample._edit || { startMs: 0, tailMs: 0 };
825
  const editLabel = (edit.startMs || edit.tailMs) ? ` · edit ${edit.startMs >= 0 ? "+" : ""}${edit.startMs}ms/${edit.tailMs >= 0 ? "+" : ""}${edit.tailMs}ms` : "";
826
  return `
827
+ <article class="sample-card ${absoluteIndex === selectedSampleIndex ? "selected" : ""}" style="--card-color: ${esc(color)}" data-sample-card="${absoluteIndex}">
828
  <button class="sample-play-zone" type="button" data-sample-audition="${absoluteIndex}">
829
  <canvas class="sample-wave" data-wave-url="${esc(sample.url)}" data-wave-color="${esc(color)}"></canvas>
830
  <span class="sample-card-footer">
 
1354
  clearError();
1355
  selectedFile = file;
1356
  clearRunViews();
1357
+ $("dropTitle").textContent = file ? file.name : "Drop or choose audio";
1358
+ $("dropMeta").textContent = file ? `${(file.size / 1024 / 1024).toFixed(2)} MB` : "Processing starts automatically";
1359
  $("runButton").disabled = !file;
1360
  currentProgress = { fraction: 0, status: "idle", stage_label: null, stage_fraction: 0 };
1361
  currentJobStatus = "idle";
 
1574
  drawWaveform(activeOverview());
1575
  });
1576
 
1577
+ for (const id of ["onset_delta", "min_gap", "energy_threshold_db", "target_max", "ncc_threshold"]) {
1578
+ const input = $(id);
1579
+ if (input) input.addEventListener("input", updateControlOutputs);
1580
+ }
1581
+ for (const [id, factor] of [["zoomOutButton", 1 / 1.35], ["zoomInButton", 1.35]]) {
1582
+ const button = $(id);
1583
+ if (button) button.addEventListener("click", () => zoomWaveformAround($("waveform").getBoundingClientRect().left + $("waveform").getBoundingClientRect().width / 2, factor));
1584
+ }
1585
+ if ($("zoomFitButton")) $("zoomFitButton").addEventListener("click", () => { waveZoom = 1; waveOffset = 0; drawWaveform(activeOverview()); });
1586
+ for (const button of document.querySelectorAll("[data-zoom-command]")) {
1587
+ button.addEventListener("click", () => zoomWaveformAround($("waveform").getBoundingClientRect().left + $("waveform").getBoundingClientRect().width / 2, button.dataset.zoomCommand === "in" ? 1.35 : 1 / 1.35));
1588
+ }
1589
+ if ($("openToolsButton")) $("openToolsButton").addEventListener("click", () => { const drawer = $("toolsDrawer"); drawer.hidden = !drawer.hidden; });
1590
+ if ($("selectAllSamplesButton")) $("selectAllSamplesButton").addEventListener("click", () => updateSelectedExportCount(visibleSamples(lastResult || { samples: [] }).length));
1591
+ if ($("clearSelectionButton")) $("clearSelectionButton").addEventListener("click", () => updateSelectedExportCount(0));
1592
+ function clickArchiveDownload() {
1593
+ const link = $("downloads")?.querySelector('a[href*="sample-pack"], a[download], a');
1594
+ if (link) link.click();
1595
+ else showError("Nothing to export yet", new Error("Run extraction first; sample-pack ZIP appears when processing completes."));
1596
+ }
1597
+ if ($("exportAllButton")) $("exportAllButton").addEventListener("click", clickArchiveDownload);
1598
+ if ($("exportSelectedButton")) $("exportSelectedButton").addEventListener("click", clickArchiveDownload);
1599
+ if ($("resetUiButton")) $("resetUiButton").addEventListener("click", () => { populateConfig(); updateControlOutputs(); });
1600
+ if ($("groupSimilarToggle")) $("groupSimilarToggle").addEventListener("change", () => { $("clustering_mode").value = $("groupSimilarToggle").checked ? "batch_quality" : "online_preview"; });
1601
+ updateControlOutputs();
1602
+
1603
  boot();
web/index.html CHANGED
@@ -3,114 +3,73 @@
3
  <head>
4
  <meta charset="utf-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>Drum Sample Extractor</title>
7
  <link rel="stylesheet" href="/web/styles.css" />
8
  </head>
9
  <body>
10
  <div id="globalDropOverlay" class="global-drop-overlay" aria-hidden="true">
11
  <div>
12
- <strong>Drop audio to load</strong>
13
- <span>WAV, MP3, FLAC, AIFF, OGG, M4A</span>
14
  </div>
15
  </div>
16
 
17
-
18
  <div id="errorBanner" class="error-banner" role="alert" hidden>
19
  <div class="error-copy">
20
  <strong id="errorTitle">Request failed</strong>
21
  <span id="errorMessage">Something went wrong.</span>
22
  <small id="errorDetail"></small>
23
  </div>
24
- <button id="dismissErrorButton" class="error-dismiss" type="button" aria-label="Dismiss error">×</button>
25
  </div>
26
 
27
- <div class="shell">
28
  <header class="topbar">
29
- <div class="app-title">
30
  <span class="brand-mark" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i></span>
31
- <div>
32
- <strong>Drum Sample Extractor</strong>
33
- <small id="resultSummary">Drop audio. Extraction starts automatically. Review the sample cards.</small>
34
- </div>
35
  </div>
36
 
37
- <label class="upload-chip" id="dropzone" title="Click to browse, or drop audio anywhere on the app">
38
  <input id="fileInput" type="file" accept="audio/*,.wav,.mp3,.flac,.aiff,.ogg,.m4a" />
39
- <span class="upload-button-face">Drop / upload</span>
40
- <span class="upload-copy">
41
- <strong id="dropTitle">Drop a song here</strong>
42
- <small id="dropMeta">Processing starts automatically</small>
43
- </span>
44
  </label>
45
 
46
- <div class="topbar-actions">
47
  <div class="backend-pill" aria-live="polite">
48
  <span class="status-dot" id="healthDot"></span>
49
  <span><strong id="healthText">Connecting</strong><small id="healthSubtext">FastAPI backend</small></span>
50
  </div>
51
- <button id="runButton" class="primary-button" type="button" disabled><span></span> Reprocess</button>
 
 
52
  </div>
53
  </header>
54
 
55
- <main class="workstation">
56
- <aside class="sidebar left-sidebar" aria-label="Left tool sidebar">
57
- <details class="tool-panel workflow-panel" open>
58
- <summary><span>Start here</span><small>Sequential extraction flow</small></summary>
59
- <ol class="workflow-list" aria-label="Sample extraction steps">
60
- <li id="flowLoad" class="flow-step active"><strong>1. Drop audio</strong><small>Waveform appears immediately.</small></li>
61
- <li id="flowControls" class="flow-step"><strong>2. Automatic setup</strong><small>The app chooses practical detection and grouping values.</small></li>
62
- <li id="flowExtract" class="flow-step"><strong>3. Processing</strong><small>Stem chunks and pipeline stages stream real progress.</small></li>
63
- <li id="flowReview" class="flow-step"><strong>4. Review cards</strong><small>Dismiss weak samples or draw another candidate.</small></li>
64
- </ol>
65
- </details>
66
-
67
- <details class="tool-panel source-panel">
68
- <summary><span>Source</span><small>Upload and status</small></summary>
69
- <div class="drop-hint-card">
70
- <strong>Upload button is in the top bar.</strong>
71
- <span>Drag an audio file onto any part of the app to load it.</span>
72
- </div>
73
- <div class="job-status-card">
74
- <span id="jobPill" class="job-pill">idle</span>
75
- <small>Current extraction job</small>
76
  </div>
77
- </details>
78
 
79
- <details class="tool-panel selection-panel">
80
- <summary><span>Selection</span><small>Audition context</small></summary>
81
- <article class="review-card">
82
- <strong>Selected hit</strong>
83
- <span id="selectedHitMeta">Click an onset marker or hit row to audition the detected slice.</span>
84
- </article>
85
- <article class="review-card">
86
- <strong>Selected sample</strong>
87
- <span id="selectedSampleMeta">Click a sample card to hear the representative sample.</span>
88
- </article>
89
- </details>
90
 
91
- <details class="tool-panel progress-panel">
92
- <summary><span>Pipeline</span><small>Stage timings and logs</small></summary>
93
- <div id="stageList" class="stage-list"></div>
94
- <pre id="logs" class="logs" aria-live="polite"></pre>
95
- </details>
96
-
97
- <details class="tool-panel history-panel">
98
- <summary><span>Run history</span><small>Completed manifests</small></summary>
99
- <button id="refreshHistoryButton" class="ghost-button full-width" type="button">Refresh history</button>
100
- <div id="historyList" class="history-list"></div>
101
- </details>
102
- </aside>
103
-
104
- <section class="center-workspace" aria-label="Waveform and extracted samples">
105
- <section class="wave-card panel" aria-label="Waveform overview">
106
- <div class="wave-hud" aria-live="polite">
107
- <strong id="waveTitle">1. Load audio</strong>
108
- <small id="waveProgressText">Upload or drop an audio file to render its waveform before extraction.</small>
109
- </div>
110
- <canvas id="waveform" class="waveform" height="420"></canvas>
111
  <div class="transport-row" aria-label="Preview transport">
112
  <button id="transportPlayButton" class="round-play" type="button" aria-label="Play preview">▶</button>
113
- <span id="transportTime" class="transport-time">0:00 / 0:00</span>
114
  <input id="transportSeek" class="transport-seek" type="range" min="0" max="1000" value="0" step="1" aria-label="Seek preview" />
115
  <div class="preview-tabs" aria-label="Preview source">
116
  <button id="previewSourceButton" class="preview-tab active" type="button" data-preview-mode="source">Source</button>
@@ -118,6 +77,7 @@
118
  <button id="previewReproductionButton" class="preview-tab" type="button" data-preview-mode="reproduction">Reproduced</button>
119
  </div>
120
  </div>
 
121
  <div class="hidden-audio-bank" aria-hidden="true">
122
  <audio id="sourcePreview"></audio>
123
  <audio id="stemAudio"></audio>
@@ -127,241 +87,160 @@
127
  </div>
128
  </section>
129
 
130
- <section class="samples-section panel" aria-label="Extracted samples">
131
- <div class="section-heading">
132
- <h2>Sample cards <span id="sampleCountLabel">(0)</span></h2>
133
- <small>Grouped by type. Click a card to audition; draw another when one is missing.</small>
134
  </div>
135
  <div id="samplesGrid" class="sample-grid"></div>
136
  </section>
137
  </section>
138
 
139
- <aside class="sidebar right-sidebar" aria-label="Right tool sidebar">
140
- <details class="tool-panel control-card" open>
141
- <summary><span>Simple controls</span><small>Optional overrides</small></summary>
142
- <p class="panel-help">Most users can ignore this panel. Drop audio and the app starts automatically.</p>
143
- <div class="control-group">
144
- <label>Stem to sample
145
- <select id="stem"></select>
146
- <small class="field-hint">Default: drums. Use all for quick full-mix tests.</small>
147
- </label>
148
- </div>
 
 
 
 
 
 
 
 
 
 
 
149
 
150
- <div class="control-group sensitivity-group">
151
- <label for="onset_delta">Hit sensitivity</label>
152
- <input id="onset_delta" type="range" min="0.01" max="0.35" step="0.005" />
153
- <div class="range-caption"><span>Fewer hits</span><span>More hits</span></div>
154
- <small class="field-hint">Increase when quiet hits are missed; decrease when bleed or ghost transients are over-detected.</small>
155
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
- <div class="control-group">
158
- <label>Sample groups
159
- <div class="stepper">
160
- <button id="clusterMinusButton" type="button" class="step-button" aria-label="Decrease cluster count"></button>
161
- <input id="target_max" type="number" min="0" max="256" step="1" aria-label="Sample group count" />
162
- <button id="clusterPlusButton" type="button" class="step-button" aria-label="Increase cluster count">+</button>
163
- </div>
164
- <small class="field-hint">Approximate maximum number of sample cards to produce.</small>
165
- </label>
 
 
 
 
 
 
 
166
  </div>
167
- <div class="preset-row common-presets">
168
- <button id="usePreviewButton" class="ghost-button" type="button">Fast preview</button>
169
- <button id="useQualityButton" class="ghost-button" type="button">Best quality</button>
 
 
 
 
 
 
 
170
  </div>
171
  </details>
172
 
173
- <details class="tool-panel downloads-panel">
174
- <summary><span>Exports</span><small>Current artifacts</small></summary>
175
- <div id="downloads" class="downloads compact-downloads"></div>
176
- <div id="editedDownloads" class="downloads edited-downloads"></div>
177
- <button id="exportStateButton" class="export-button full-width" type="button" disabled>Export edited pack</button>
178
- </details>
179
-
180
- <details class="tool-panel advanced-controls">
181
- <summary><span>Advanced parameters</span><small>Only adjust when the common controls are not enough</small></summary>
182
- <p class="panel-help">Advanced controls are grouped by pipeline stage. They are intentionally hidden from the normal extraction loop.</p>
183
- <section class="advanced-section">
184
- <h4>Stem separation</h4>
185
- <div class="control-grid compact-controls">
186
- <label>Demucs model
187
- <select id="demucs_model"></select>
188
- </label>
189
- <label>Shifts
190
- <input id="demucs_shifts" type="number" min="0" max="8" step="1" />
191
- </label>
192
- <label>Overlap
193
- <input id="demucs_overlap" type="number" min="0" max="0.9" step="0.05" />
194
- </label>
195
- </div>
196
- </section>
197
- <section class="advanced-section">
198
- <h4>Hit detection</h4>
199
- <div class="control-grid compact-controls">
200
- <label>Onset mode
201
- <select id="onset_mode">
202
- <option value="auto">auto / multiband</option>
203
- <option value="percussive">percussive</option>
204
- <option value="harmonic">harmonic</option>
205
- <option value="broadband">broadband</option>
206
- </select>
207
- </label>
208
- <label>Energy threshold dB
209
- <input id="energy_threshold_db" type="number" min="-100" max="0" step="1" />
210
- </label>
211
- <label>Minimum gap seconds
212
- <input id="min_gap" type="number" min="0.001" max="1" step="0.005" />
213
- </label>
214
- <label>Pre-pad seconds
215
- <input id="pre_pad" type="number" min="0" max="0.25" step="0.001" />
216
- </label>
217
- <label>Min duration seconds
218
- <input id="min_dur" type="number" min="0.001" max="10" step="0.005" />
219
- </label>
220
- <label>Max duration seconds
221
- <input id="max_dur" type="number" min="0.01" max="10" step="0.1" />
222
- </label>
223
- </div>
224
- </section>
225
- <section class="advanced-section">
226
- <h4>Grouping</h4>
227
- <div class="control-grid compact-controls">
228
- <label>Clustering mode
229
- <select id="clustering_mode">
230
- <option value="batch_quality">batch quality</option>
231
- <option value="online_preview">online preview</option>
232
- </select>
233
- </label>
234
- <label>Target min clusters
235
- <input id="target_min" type="number" min="0" max="256" step="1" />
236
- </label>
237
- <label>NCC threshold
238
- <input id="ncc_threshold" type="number" min="0" max="1" step="0.01" />
239
- </label>
240
- <label>Attack window ms
241
- <input id="attack_ms" type="number" min="1" max="250" step="1" />
242
- </label>
243
- <label>Mel prefilter
244
- <input id="mel_threshold" type="number" min="0" max="1" step="0.01" />
245
- </label>
246
- <label>Linkage
247
- <select id="linkage">
248
- <option value="average">average</option>
249
- <option value="complete">complete</option>
250
- <option value="single">single</option>
251
- </select>
252
- </label>
253
- </div>
254
- </section>
255
- <section class="advanced-section">
256
- <h4>Export and cache</h4>
257
- <div class="control-grid compact-controls">
258
- <label>MIDI grid
259
- <select id="subdivision">
260
- <option value="8">8th</option>
261
- <option value="16">16th</option>
262
- <option value="32">32nd</option>
263
- <option value="64">64th</option>
264
- </select>
265
- </label>
266
- </div>
267
- <div class="toggles">
268
- <label><input id="synthesize" type="checkbox" /> synthesize alternates</label>
269
- <label><input id="quantize_midi" type="checkbox" /> quantize MIDI</label>
270
- <label class="advanced-hidden"><input id="auto_tune" type="checkbox" checked /> automatic parameter tuning</label>
271
- <label><input id="use_disk_cache" type="checkbox" /> disk cache stems/source loads</label>
272
- </div>
273
- <button id="clearCacheButton" class="ghost-button full-width" type="button">Clear cache</button>
274
- </section>
275
- </details>
276
  </aside>
277
  </main>
278
 
279
- <footer class="bottom-dock" aria-label="Bottom tool bar">
280
- <details class="bottom-tool supervision-panel">
281
- <summary><span>Review & edit</span><small>Move, suppress, restore, force-onset, explain, and export edited packs</small></summary>
282
- <div class="bottom-tool-content">
283
- <div class="supervision-header">
284
- <div>
285
- <h3>Semantic edit state</h3>
286
- <p class="subtle">Moves, locks, suppressions, favorites, and accepted suggestions are saved as replayable semantic state next to the run manifest.</p>
287
- </div>
288
- <div class="supervision-actions">
289
- <button id="refreshStateButton" class="ghost-button" type="button">Refresh state</button>
290
- <button id="forceOnsetButton" class="ghost-button" type="button" disabled>Add-onset mode off</button>
291
- <button id="undoButton" class="ghost-button" type="button" disabled>Undo edit</button>
292
- </div>
293
- </div>
 
 
 
 
 
 
 
 
 
294
  <div id="supervisionSummary" class="state-summary">No interactive state loaded.</div>
295
- <div class="supervision-tools">
296
- <label>Target cluster
297
- <select id="targetClusterSelect"></select>
298
- </label>
299
- <button id="moveHitButton" class="secondary-button" type="button" disabled>Move selected hit</button>
300
- <button id="pullHitButton" class="secondary-button" type="button" disabled>Pull into new cluster</button>
301
- <button id="acceptHitButton" class="secondary-button" type="button" disabled>Accept hit</button>
302
- <button id="favoriteHitButton" class="secondary-button" type="button" disabled>Favorite</button>
303
- <button id="suppressHitButton" class="secondary-button danger-button" type="button" disabled>Suppress</button>
304
- <button id="restoreHitButton" class="secondary-button" type="button" disabled>Restore</button>
305
- <button id="lockClusterButton" class="secondary-button" type="button" disabled>Lock cluster</button>
306
- <button id="explainClusterButton" class="secondary-button" type="button" disabled>Explain</button>
307
- </div>
308
- <div class="supervision-grid">
309
- <article>
310
- <h4>Outlier-first review queue</h4>
311
- <div id="reviewQueue" class="compact-list"></div>
312
- </article>
313
- <article>
314
- <h4>Cluster board</h4>
315
- <div id="clusterBoard" class="compact-list"></div>
316
- </article>
317
- <article>
318
- <h4>Suggestion inbox</h4>
319
- <div id="suggestionInbox" class="compact-list"></div>
320
- </article>
321
- <article>
322
- <h4>Constraint / event log</h4>
323
- <div id="stateLog" class="compact-list"></div>
324
- </article>
325
- </div>
326
- <pre id="clusterExplanation" class="explanation empty">Select a cluster and click Explain.</pre>
327
  </div>
 
 
 
 
 
 
 
328
  </details>
329
 
330
- <details class="bottom-tool data-panel">
331
- <summary><span>Tables</span><small>Raw sample and detected-hit rows</small></summary>
332
- <div class="bottom-tool-content">
333
- <div class="result-columns">
334
- <section>
335
- <h3>Representative samples</h3>
336
- <div class="table-wrap">
337
- <table id="samplesTable">
338
- <thead>
339
- <tr>
340
- <th>Audition</th><th>Sample</th><th>Class</th><th>Hits</th><th>Score</th><th>Duration</th><th>First hit</th><th>File</th>
341
- </tr>
342
- </thead>
343
- <tbody></tbody>
344
- </table>
345
- </div>
346
- </section>
347
- <section>
348
- <h3>Detected hit review</h3>
349
- <p class="subtle">Every detected slice is exported under <code>review/hits/</code>. Click rows or waveform markers to audition.</p>
350
- <div class="table-wrap hit-table-wrap">
351
- <table id="hitsTable">
352
- <thead>
353
- <tr>
354
- <th>Audition</th><th>#</th><th>Label</th><th>Cluster</th><th>Confidence</th><th>Flags</th><th>Onset</th><th>Duration</th><th>Energy</th><th>File</th>
355
- </tr>
356
- </thead>
357
- <tbody></tbody>
358
- </table>
359
- </div>
360
- </section>
361
- </div>
362
  </div>
363
  </details>
364
- </footer>
365
  </div>
366
  <script type="module" src="/web/app.js"></script>
367
  </body>
 
3
  <head>
4
  <meta charset="utf-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Sample Extractor</title>
7
  <link rel="stylesheet" href="/web/styles.css" />
8
  </head>
9
  <body>
10
  <div id="globalDropOverlay" class="global-drop-overlay" aria-hidden="true">
11
  <div>
12
+ <strong>Drop audio to extract samples</strong>
13
+ <span>The waveform appears immediately and processing starts automatically.</span>
14
  </div>
15
  </div>
16
 
 
17
  <div id="errorBanner" class="error-banner" role="alert" hidden>
18
  <div class="error-copy">
19
  <strong id="errorTitle">Request failed</strong>
20
  <span id="errorMessage">Something went wrong.</span>
21
  <small id="errorDetail"></small>
22
  </div>
23
+ <button id="dismissErrorButton" class="icon-button" type="button" aria-label="Dismiss error">×</button>
24
  </div>
25
 
26
+ <div class="app-shell">
27
  <header class="topbar">
28
+ <div class="brand">
29
  <span class="brand-mark" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i></span>
30
+ <strong>Sample Extractor</strong>
31
+ <span class="beta-pill">BETA</span>
 
 
32
  </div>
33
 
34
+ <label id="dropzone" class="file-picker" title="Click to browse, or drop audio anywhere">
35
  <input id="fileInput" type="file" accept="audio/*,.wav,.mp3,.flac,.aiff,.ogg,.m4a" />
36
+ <span id="dropTitle">Drop or choose audio</span>
37
+ <small id="dropMeta">Processing starts automatically</small>
38
+ <span aria-hidden="true"></span>
 
 
39
  </label>
40
 
41
+ <div class="top-actions">
42
  <div class="backend-pill" aria-live="polite">
43
  <span class="status-dot" id="healthDot"></span>
44
  <span><strong id="healthText">Connecting</strong><small id="healthSubtext">FastAPI backend</small></span>
45
  </div>
46
+ <button id="runButton" class="secondary-action" type="button" disabled>↻ Reprocess</button>
47
+ <button id="exportAllButton" class="secondary-action" type="button" disabled>⇧ Export All</button>
48
+ <button id="exportSelectedButton" class="primary-action" type="button" disabled>⇄ Export Selected <span id="selectedCountTop">(0)</span></button>
49
  </div>
50
  </header>
51
 
52
+ <main class="main-grid">
53
+ <section class="canvas-pane" aria-label="Waveform and samples">
54
+ <section class="wave-panel" aria-label="Waveform overview">
55
+ <div class="wave-toolbar">
56
+ <div class="zoom-tools" aria-label="Waveform zoom controls">
57
+ <button id="zoomInspectButton" class="tool-button" type="button" title="Zoom with pinch or wheel"></button>
58
+ <button id="zoomOutButton" class="tool-button" type="button" title="Zoom out"></button>
59
+ <button id="zoomInButton" class="tool-button" type="button" title="Zoom in">+</button>
60
+ <button id="zoomFitButton" class="tool-button fit" type="button" title="Fit waveform">Fit</button>
61
+ </div>
62
+ <div class="wave-status">
63
+ <strong id="waveTitle">Load audio</strong>
64
+ <span id="waveProgressText">Drop a file anywhere. Extraction starts automatically.</span>
65
+ </div>
66
+ <time id="transportTime" class="top-time">0:00 / 0:00</time>
 
 
 
 
 
 
67
  </div>
 
68
 
69
+ <canvas id="waveform" class="waveform" height="360"></canvas>
 
 
 
 
 
 
 
 
 
 
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  <div class="transport-row" aria-label="Preview transport">
72
  <button id="transportPlayButton" class="round-play" type="button" aria-label="Play preview">▶</button>
 
73
  <input id="transportSeek" class="transport-seek" type="range" min="0" max="1000" value="0" step="1" aria-label="Seek preview" />
74
  <div class="preview-tabs" aria-label="Preview source">
75
  <button id="previewSourceButton" class="preview-tab active" type="button" data-preview-mode="source">Source</button>
 
77
  <button id="previewReproductionButton" class="preview-tab" type="button" data-preview-mode="reproduction">Reproduced</button>
78
  </div>
79
  </div>
80
+
81
  <div class="hidden-audio-bank" aria-hidden="true">
82
  <audio id="sourcePreview"></audio>
83
  <audio id="stemAudio"></audio>
 
87
  </div>
88
  </section>
89
 
90
+ <section class="sample-board" aria-label="Extracted samples">
91
+ <div class="board-header">
92
+ <h2>Extracted Samples <span id="sampleCountLabel">(0)</span></h2>
93
+ <span id="resultSummary">Drop audio. The app uploads, separates stems, tunes parameters, and deals sample cards.</span>
94
  </div>
95
  <div id="samplesGrid" class="sample-grid"></div>
96
  </section>
97
  </section>
98
 
99
+ <aside class="settings-pane" aria-label="Settings">
100
+ <header class="settings-head">
101
+ <strong>Advanced Settings</strong>
102
+ <button class="icon-button" type="button" aria-label="Hide settings">×</button>
103
+ </header>
104
+
105
+ <section class="settings-section simple-section">
106
+ <h3>Detection</h3>
107
+ <label class="control-line">Stem
108
+ <select id="stem"></select>
109
+ </label>
110
+ <label class="range-line">Sensitivity
111
+ <span class="range-wrap"><input id="onset_delta" type="range" min="0.01" max="0.35" step="0.005" /><output id="sensitivityOutput">Auto</output></span>
112
+ </label>
113
+ <label class="range-line">Minimum interval
114
+ <span class="range-wrap"><input id="min_gap" type="range" min="0.001" max="1" step="0.005" /><output id="minGapOutput">Auto</output></span>
115
+ </label>
116
+ <label class="range-line">Minimum amplitude
117
+ <span class="range-wrap"><input id="energy_threshold_db" type="range" min="-100" max="0" step="1" /><output id="energyOutput">Auto</output></span>
118
+ </label>
119
+ </section>
120
 
121
+ <section class="settings-section simple-section">
122
+ <h3>Grouping</h3>
123
+ <label class="toggle-line"><span>Group Similar Hits</span><input id="groupSimilarToggle" type="checkbox" checked /></label>
124
+ <label class="range-line">Max groups
125
+ <span class="range-wrap"><input id="target_max" type="range" min="1" max="64" step="1" /><output id="targetMaxOutput">Auto</output></span>
126
+ </label>
127
+ <label class="range-line">Max group distance
128
+ <span class="range-wrap"><input id="ncc_threshold" type="range" min="0" max="1" step="0.01" /><output id="nccOutput">Auto</output></span>
129
+ </label>
130
+ </section>
131
+
132
+ <section class="settings-section simple-section">
133
+ <h3>Waveform</h3>
134
+ <label class="control-line">Resolution
135
+ <select id="waveformResolution"><option>High</option><option>Medium</option><option>Low</option></select>
136
+ </label>
137
+ <label class="control-line">Channels
138
+ <select id="waveformChannels"><option>Left & Right</option><option>Mono</option></select>
139
+ </label>
140
+ </section>
141
 
142
+ <details class="settings-section advanced-fold">
143
+ <summary>Expert pipeline controls</summary>
144
+ <div class="expert-grid">
145
+ <label>Demucs model<select id="demucs_model"></select></label>
146
+ <label>Clustering mode<select id="clustering_mode"><option value="batch_quality">batch quality</option><option value="online_preview">online preview</option></select></label>
147
+ <label>Shifts<input id="demucs_shifts" type="number" min="0" max="8" step="1" /></label>
148
+ <label>Overlap<input id="demucs_overlap" type="number" min="0" max="0.9" step="0.05" /></label>
149
+ <label>Onset mode<select id="onset_mode"><option value="auto">auto / multiband</option><option value="percussive">percussive</option><option value="harmonic">harmonic</option><option value="broadband">broadband</option></select></label>
150
+ <label>Pre-pad<input id="pre_pad" type="number" min="0" max="0.25" step="0.001" /></label>
151
+ <label>Min duration<input id="min_dur" type="number" min="0.001" max="10" step="0.005" /></label>
152
+ <label>Max duration<input id="max_dur" type="number" min="0.01" max="10" step="0.1" /></label>
153
+ <label>Target min<input id="target_min" type="number" min="0" max="256" step="1" /></label>
154
+ <label>Attack ms<input id="attack_ms" type="number" min="1" max="250" step="1" /></label>
155
+ <label>Mel prefilter<input id="mel_threshold" type="number" min="0" max="1" step="0.01" /></label>
156
+ <label>Linkage<select id="linkage"><option value="average">average</option><option value="complete">complete</option><option value="single">single</option></select></label>
157
+ <label>MIDI grid<select id="subdivision"><option value="4">4th</option><option value="8">8th</option><option value="16">16th</option><option value="32">32nd</option><option value="64">64th</option></select></label>
158
  </div>
159
+ <div class="toggles">
160
+ <label><input id="synthesize" type="checkbox" /> synthesize alternates</label>
161
+ <label><input id="quantize_midi" type="checkbox" /> quantize MIDI</label>
162
+ <label><input id="auto_tune" type="checkbox" checked /> automatic parameter tuning</label>
163
+ <label><input id="use_disk_cache" type="checkbox" /> disk cache stems/source loads</label>
164
+ </div>
165
+ <div class="preset-row">
166
+ <button id="usePreviewButton" class="secondary-action" type="button">Fast preview</button>
167
+ <button id="useQualityButton" class="secondary-action" type="button">Best quality</button>
168
+ <button id="clearCacheButton" class="secondary-action" type="button">Clear cache</button>
169
  </div>
170
  </details>
171
 
172
+ <button id="resetUiButton" class="reset-button" type="button">Reset to Defaults</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  </aside>
174
  </main>
175
 
176
+ <footer class="bottom-bar" aria-label="Selection and tools">
177
+ <div class="selection-count"><span class="checked-box">✓</span><strong id="selectedCountBottom">0 Selected</strong></div>
178
+ <div class="bottom-zoom"><button class="tool-button" type="button" data-zoom-command="out">⌕ −</button><input class="mini-slider" type="range" min="1" max="64" value="1" /><button class="tool-button" type="button" data-zoom-command="in">+</button></div>
179
+ <div class="bottom-actions"><button id="selectAllSamplesButton" class="secondary-action" type="button">☑ Select All</button><button id="clearSelectionButton" class="secondary-action" type="button">🗑 Clear</button><button id="openToolsButton" class="help-button" type="button">?</button></div>
180
+ </footer>
181
+
182
+ <section id="toolsDrawer" class="tools-drawer" aria-label="Review tools" hidden>
183
+ <div class="drawer-grid">
184
+ <article>
185
+ <h3>Pipeline</h3>
186
+ <span id="jobPill" class="job-pill">idle</span>
187
+ <div id="stageList" class="stage-list"></div>
188
+ <pre id="logs" class="logs" aria-live="polite"></pre>
189
+ </article>
190
+ <article>
191
+ <h3>Exports</h3>
192
+ <div id="downloads" class="downloads compact-downloads"></div>
193
+ <div id="editedDownloads" class="downloads edited-downloads"></div>
194
+ <button id="exportStateButton" class="secondary-action" type="button" disabled>Export edited pack</button>
195
+ </article>
196
+ <article>
197
+ <h3>Selection</h3>
198
+ <p id="selectedHitMeta">Click an onset marker or hit row to audition the detected slice.</p>
199
+ <p id="selectedSampleMeta">Click a sample card to hear the representative sample.</p>
200
  <div id="supervisionSummary" class="state-summary">No interactive state loaded.</div>
201
+ </article>
202
+ <article>
203
+ <h3>Run history</h3>
204
+ <button id="refreshHistoryButton" class="secondary-action" type="button">Refresh history</button>
205
+ <div id="historyList" class="history-list"></div>
206
+ </article>
207
+ </div>
208
+
209
+ <details class="drawer-advanced">
210
+ <summary>Review & edit state</summary>
211
+ <div class="supervision-actions">
212
+ <button id="refreshStateButton" class="secondary-action" type="button">Refresh state</button>
213
+ <button id="forceOnsetButton" class="secondary-action" type="button" disabled>Add-onset mode off</button>
214
+ <button id="undoButton" class="secondary-action" type="button" disabled>Undo edit</button>
215
+ </div>
216
+ <div class="supervision-tools">
217
+ <label>Target cluster<select id="targetClusterSelect"></select></label>
218
+ <button id="moveHitButton" class="secondary-action" type="button" disabled>Move selected hit</button>
219
+ <button id="pullHitButton" class="secondary-action" type="button" disabled>Pull into new cluster</button>
220
+ <button id="acceptHitButton" class="secondary-action" type="button" disabled>Accept hit</button>
221
+ <button id="favoriteHitButton" class="secondary-action" type="button" disabled>Favorite</button>
222
+ <button id="suppressHitButton" class="secondary-action danger" type="button" disabled>Suppress</button>
223
+ <button id="restoreHitButton" class="secondary-action" type="button" disabled>Restore</button>
224
+ <button id="lockClusterButton" class="secondary-action" type="button" disabled>Lock cluster</button>
225
+ <button id="explainClusterButton" class="secondary-action" type="button" disabled>Explain</button>
 
 
 
 
 
 
 
226
  </div>
227
+ <div class="supervision-grid">
228
+ <article><h4>Outlier-first review queue</h4><div id="reviewQueue" class="compact-list"></div></article>
229
+ <article><h4>Cluster board</h4><div id="clusterBoard" class="compact-list"></div></article>
230
+ <article><h4>Suggestion inbox</h4><div id="suggestionInbox" class="compact-list"></div></article>
231
+ <article><h4>Constraint / event log</h4><div id="stateLog" class="compact-list"></div></article>
232
+ </div>
233
+ <pre id="clusterExplanation" class="explanation empty">Select a cluster and click Explain.</pre>
234
  </details>
235
 
236
+ <details class="drawer-advanced">
237
+ <summary>Raw tables</summary>
238
+ <div class="result-columns">
239
+ <section><h3>Representative samples</h3><div class="table-wrap"><table id="samplesTable"><thead><tr><th>Audition</th><th>Sample</th><th>Class</th><th>Hits</th><th>Score</th><th>Duration</th><th>First hit</th><th>File</th></tr></thead><tbody></tbody></table></div></section>
240
+ <section><h3>Detected hit review</h3><div class="table-wrap hit-table-wrap"><table id="hitsTable"><thead><tr><th>Audition</th><th>#</th><th>Label</th><th>Cluster</th><th>Confidence</th><th>Flags</th><th>Onset</th><th>Duration</th><th>Energy</th><th>File</th></tr></thead><tbody></tbody></table></div></section>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  </div>
242
  </details>
243
+ </section>
244
  </div>
245
  <script type="module" src="/web/app.js"></script>
246
  </body>
web/styles.css CHANGED
@@ -1,1575 +1,172 @@
1
  :root {
2
  color-scheme: light;
3
- --bg: #f6f6f8;
4
- --surface: #ffffff;
5
- --surface-soft: #fbfbfc;
6
- --line: #e2e3e8;
7
- --line-strong: #d1d3db;
8
- --muted: #8a8f9b;
9
- --text: #24252b;
10
- --text-soft: #4f535d;
11
- --accent: #6d45ec;
12
- --accent-strong: #552bd8;
13
- --accent-soft: #f0ebff;
14
- --good: #64a946;
15
- --bad: #d85867;
16
- --warn: #ef9343;
17
- --shadow: 0 14px 38px rgba(18, 21, 30, .055);
18
- --radius-xl: 18px;
19
- --radius-lg: 13px;
20
- --topbar-h: 72px;
21
- --bottom-h: 236px;
22
- font-synthesis-weight: none;
23
  }
24
-
25
  * { box-sizing: border-box; }
26
- html, body {
27
- width: 100%;
28
- height: 100%;
29
- margin: 0;
30
- overflow: hidden;
31
- background: var(--bg);
32
- color: var(--text);
33
- font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", sans-serif;
34
- }
35
  button, input, select { font: inherit; }
36
  button { cursor: pointer; }
37
- button:disabled { opacity: .45; cursor: not-allowed; }
38
- code { color: var(--accent-strong); }
39
-
40
- .global-drop-overlay {
41
- position: fixed;
42
- inset: 0;
43
- z-index: 100;
44
- display: none;
45
- place-items: center;
46
- background: rgba(246, 246, 248, .74);
47
- backdrop-filter: blur(10px);
48
- pointer-events: none;
49
- }
50
- .global-drop-overlay.visible { display: grid; }
51
- .global-drop-overlay div {
52
- display: grid;
53
- gap: 8px;
54
- min-width: min(520px, calc(100vw - 48px));
55
- padding: 36px;
56
- border: 2px dashed rgba(109, 69, 236, .45);
57
- border-radius: 24px;
58
- background: rgba(255, 255, 255, .92);
59
- text-align: center;
60
- box-shadow: var(--shadow);
61
- }
62
- .global-drop-overlay strong { font-size: 26px; letter-spacing: -.035em; }
63
- .global-drop-overlay span { color: var(--muted); }
64
-
65
- .error-banner {
66
- position: fixed;
67
- top: 14px;
68
- left: 50%;
69
- z-index: 140;
70
- width: min(760px, calc(100vw - 32px));
71
- display: grid;
72
- grid-template-columns: minmax(0, 1fr) auto;
73
- align-items: start;
74
- gap: 14px;
75
- padding: 14px 14px 14px 16px;
76
- border: 1px solid rgba(216, 88, 103, .34);
77
- border-radius: 16px;
78
- background: rgba(255, 248, 249, .97);
79
- color: #7c202c;
80
- box-shadow: 0 18px 44px rgba(126, 24, 42, .14);
81
- transform: translateX(-50%);
82
- }
83
- .error-banner[hidden] { display: none; }
84
- .error-copy { min-width: 0; display: grid; gap: 3px; }
85
- .error-copy strong { font-size: 13px; letter-spacing: -.01em; }
86
- .error-copy span { overflow-wrap: anywhere; font-size: 13px; line-height: 1.35; }
87
- .error-copy small { overflow-wrap: anywhere; color: rgba(124, 32, 44, .72); line-height: 1.35; }
88
- .error-dismiss {
89
- width: 28px;
90
- height: 28px;
91
- border: 1px solid rgba(216, 88, 103, .26);
92
- border-radius: 999px;
93
- background: #fff;
94
- color: #7c202c;
95
- font-size: 18px;
96
- line-height: 1;
97
- }
98
- .error-dismiss:hover { border-color: rgba(216, 88, 103, .52); }
99
-
100
- .shell {
101
- width: 100%;
102
- height: 100vh;
103
- min-height: 720px;
104
- display: grid;
105
- grid-template-rows: var(--topbar-h) minmax(0, 1fr) var(--bottom-h);
106
- gap: 12px;
107
- padding: 12px;
108
- overflow: hidden;
109
- }
110
-
111
- .topbar {
112
- min-height: 0;
113
- display: grid;
114
- grid-template-columns: minmax(250px, 1fr) minmax(360px, 560px) auto;
115
- align-items: center;
116
- gap: 14px;
117
- padding: 0 4px;
118
- }
119
- .app-title {
120
- min-width: 0;
121
- display: inline-flex;
122
- align-items: center;
123
- gap: 14px;
124
- }
125
- .app-title strong {
126
- display: block;
127
- overflow: hidden;
128
- text-overflow: ellipsis;
129
- white-space: nowrap;
130
- font-size: 19px;
131
- line-height: 1.05;
132
- letter-spacing: -.035em;
133
- font-weight: 620;
134
- }
135
- .app-title small {
136
- display: block;
137
- margin-top: 3px;
138
- overflow: hidden;
139
- text-overflow: ellipsis;
140
- white-space: nowrap;
141
- color: var(--muted);
142
- font-size: 12px;
143
- }
144
- .brand-mark {
145
- width: 44px;
146
- height: 44px;
147
- display: inline-flex;
148
- flex: 0 0 auto;
149
- align-items: center;
150
- justify-content: center;
151
- gap: 4px;
152
- border-radius: 14px;
153
- background: #fff;
154
- border: 1px solid var(--line);
155
- box-shadow: 0 3px 10px rgba(18, 21, 30, .035);
156
- }
157
- .brand-mark i {
158
- width: 4px;
159
- border-radius: 999px;
160
- background: #202125;
161
- display: block;
162
- }
163
- .brand-mark i:nth-child(1) { height: 14px; }
164
- .brand-mark i:nth-child(2) { height: 25px; }
165
- .brand-mark i:nth-child(3) { height: 34px; }
166
- .brand-mark i:nth-child(4) { height: 22px; }
167
- .brand-mark i:nth-child(5) { height: 11px; }
168
-
169
- .upload-chip {
170
- position: relative;
171
- min-width: 0;
172
- height: 58px;
173
- display: grid;
174
- grid-template-columns: auto minmax(0, 1fr);
175
- align-items: center;
176
- gap: 12px;
177
- padding: 7px 14px 7px 7px;
178
- border: 1px solid var(--line);
179
- border-radius: 16px;
180
- background: rgba(255, 255, 255, .88);
181
- box-shadow: 0 6px 18px rgba(18, 21, 30, .04);
182
- cursor: pointer;
183
- transition: border-color .16s ease, box-shadow .16s ease, transform .16s ease;
184
- }
185
- .upload-chip:hover,
186
- .upload-chip.dragging {
187
- border-color: rgba(109, 69, 236, .45);
188
- box-shadow: 0 10px 26px rgba(109, 69, 236, .10);
189
- transform: translateY(-1px);
190
- }
191
- .upload-chip input {
192
- position: absolute;
193
- inset: 0;
194
- opacity: 0;
195
- cursor: pointer;
196
- }
197
- .upload-button-face {
198
- display: inline-grid;
199
- place-items: center;
200
- height: 42px;
201
- padding: 0 16px;
202
- border-radius: 11px;
203
- background: #1f2025;
204
- color: #fff;
205
- font-size: 13px;
206
- font-weight: 760;
207
- white-space: nowrap;
208
- }
209
- .upload-copy { min-width: 0; }
210
- .upload-copy strong {
211
- display: block;
212
- overflow: hidden;
213
- text-overflow: ellipsis;
214
- white-space: nowrap;
215
- color: var(--text);
216
- font-size: 13px;
217
- line-height: 1.1;
218
- }
219
- .upload-copy small {
220
- display: block;
221
- margin-top: 4px;
222
- overflow: hidden;
223
- text-overflow: ellipsis;
224
- white-space: nowrap;
225
- color: var(--muted);
226
- font-size: 11px;
227
- }
228
- .topbar-actions {
229
- display: inline-flex;
230
- align-items: center;
231
- justify-content: end;
232
- gap: 12px;
233
- }
234
- .backend-pill {
235
- display: inline-flex;
236
- align-items: center;
237
- gap: 7px;
238
- color: var(--muted);
239
- font-size: 11px;
240
- opacity: .72;
241
- white-space: nowrap;
242
- }
243
- .backend-pill strong { display: block; font-size: 11px; line-height: 1; }
244
  .backend-pill small { display: none; }
245
- .status-dot {
246
- width: 7px;
247
- height: 7px;
248
- border-radius: 999px;
249
- background: var(--warn);
250
- }
251
  .status-dot.ok { background: var(--good); }
252
  .status-dot.bad { background: var(--bad); }
253
- .primary-button {
254
- min-width: 192px;
255
- border: 0;
256
- border-radius: 12px;
257
- padding: 16px 22px;
258
- background: linear-gradient(135deg, #7048f5, #552bd8);
259
- color: #fff;
260
- font-weight: 700;
261
- font-size: 16px;
262
- box-shadow: 0 14px 34px rgba(85, 43, 216, .20);
263
- transition: transform .16s ease, box-shadow .16s ease, opacity .16s ease;
264
- }
265
- .primary-button:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 18px 40px rgba(85, 43, 216, .24); }
266
- .primary-button span { margin-right: 8px; }
267
-
268
- .workstation {
269
- min-height: 0;
270
- display: grid;
271
- grid-template-columns: 292px minmax(0, 1fr) 332px;
272
- gap: 12px;
273
- overflow: hidden;
274
- }
275
- .sidebar {
276
- min-height: 0;
277
- display: flex;
278
- flex-direction: column;
279
- gap: 10px;
280
- overflow: auto;
281
- scrollbar-width: thin;
282
- }
283
- .center-workspace {
284
- min-width: 0;
285
- min-height: 0;
286
- display: grid;
287
- grid-template-rows: minmax(270px, 1fr) minmax(172px, 220px);
288
- gap: 12px;
289
- overflow: hidden;
290
- }
291
- .panel,
292
- .tool-panel,
293
- .bottom-tool {
294
- border: 1px solid var(--line);
295
- border-radius: var(--radius-xl);
296
- background: rgba(255, 255, 255, .90);
297
- box-shadow: var(--shadow);
298
- }
299
- .tool-panel {
300
- flex: 0 0 auto;
301
- padding: 12px;
302
- }
303
- .tool-panel[open] { padding-bottom: 14px; }
304
- .tool-panel > summary,
305
- .bottom-tool > summary {
306
- min-height: 38px;
307
- display: grid;
308
- grid-template-columns: minmax(0, 1fr);
309
- align-items: center;
310
- gap: 2px;
311
- list-style: none;
312
- cursor: pointer;
313
- user-select: none;
314
- }
315
- .tool-panel > summary::-webkit-details-marker,
316
- .bottom-tool > summary::-webkit-details-marker { display: none; }
317
- .tool-panel > summary span,
318
- .bottom-tool > summary span {
319
- color: var(--text);
320
- font-size: 14px;
321
- font-weight: 760;
322
- letter-spacing: -.02em;
323
- }
324
- .tool-panel > summary small,
325
- .bottom-tool > summary small {
326
- overflow: hidden;
327
- text-overflow: ellipsis;
328
- white-space: nowrap;
329
- color: var(--muted);
330
- font-size: 11px;
331
- font-weight: 520;
332
- }
333
- .tool-panel > summary::after,
334
- .bottom-tool > summary::after {
335
- content: "+";
336
- position: absolute;
337
- right: 18px;
338
- color: var(--muted);
339
- font-weight: 760;
340
- }
341
- .tool-panel,
342
- .bottom-tool { position: relative; }
343
- .tool-panel[open] > summary::after,
344
- .bottom-tool[open] > summary::after { content: "–"; }
345
-
346
- .drop-hint-card,
347
- .job-status-card,
348
- .review-card {
349
- margin-top: 10px;
350
- border: 1px solid var(--line);
351
- border-radius: var(--radius-lg);
352
- background: var(--surface-soft);
353
- padding: 12px;
354
- }
355
- .drop-hint-card strong,
356
- .drop-hint-card span,
357
- .review-card strong,
358
- .review-card span,
359
- .job-status-card small { display: block; }
360
- .drop-hint-card span,
361
- .review-card span,
362
- .job-status-card small {
363
- margin-top: 4px;
364
- color: var(--muted);
365
- font-size: 12px;
366
- line-height: 1.35;
367
- }
368
- .job-pill {
369
- display: inline-flex;
370
- align-items: center;
371
- white-space: nowrap;
372
- border: 1px solid rgba(228, 229, 233, .86);
373
- border-radius: 999px;
374
- padding: 6px 10px;
375
- color: var(--muted);
376
- background: rgba(255, 255, 255, .78);
377
- font-size: 11px;
378
- backdrop-filter: blur(10px);
379
- }
380
-
381
- .wave-card {
382
- position: relative;
383
- min-height: 0;
384
- overflow: hidden;
385
- display: grid;
386
- grid-template-rows: minmax(0, 1fr) 72px;
387
- }
388
- .waveform {
389
- display: block;
390
- width: 100%;
391
- height: 100%;
392
- min-height: 0;
393
- background: linear-gradient(180deg, #fff, #fbfbfc);
394
- cursor: crosshair;
395
- }
396
- .transport-row {
397
- display: grid;
398
- grid-template-columns: 48px 100px minmax(0, 1fr);
399
- gap: 18px;
400
- align-items: center;
401
- padding: 10px 22px 18px;
402
- }
403
- .round-play {
404
- width: 48px;
405
- height: 48px;
406
- display: grid;
407
- place-items: center;
408
- border: 1px solid var(--line);
409
- border-radius: 999px;
410
- background: #fff;
411
- color: #1f2024;
412
- font-size: 16px;
413
- line-height: 1;
414
- box-shadow: 0 2px 8px rgba(18, 21, 30, .035);
415
- }
416
- .transport-time {
417
- color: #6d717b;
418
- font-size: 15px;
419
- font-variant-numeric: tabular-nums;
420
- white-space: nowrap;
421
- }
422
- .transport-seek {
423
- appearance: none;
424
- width: 100%;
425
- height: 2px;
426
- margin: 0;
427
- padding: 0;
428
- border: 0;
429
- border-radius: 999px;
430
- background: #d9dbe2;
431
- }
432
- .transport-seek::-webkit-slider-thumb {
433
- appearance: none;
434
- width: 14px;
435
- height: 14px;
436
- border-radius: 50%;
437
- background: var(--accent);
438
- border: 0;
439
- opacity: 0;
440
- }
441
- .transport-seek:hover::-webkit-slider-thumb { opacity: 1; }
442
- .transport-seek::-moz-range-thumb {
443
- width: 14px;
444
- height: 14px;
445
- border-radius: 50%;
446
- background: var(--accent);
447
- border: 0;
448
- opacity: 0;
449
- }
450
- .transport-seek:hover::-moz-range-thumb { opacity: 1; }
451
  .hidden-audio-bank { display: none; }
452
 
453
- .samples-section {
454
- min-height: 0;
455
- overflow: hidden;
456
- padding: 14px;
457
- display: grid;
458
- grid-template-rows: auto minmax(0, 1fr);
459
- }
460
- .section-heading {
461
- display: flex;
462
- align-items: end;
463
- justify-content: space-between;
464
- gap: 20px;
465
- margin-bottom: 12px;
466
- }
467
- h2 { margin: 0; font-size: 17px; letter-spacing: -.035em; font-weight: 620; }
468
- .section-heading small { color: var(--muted); font-size: 12px; }
469
- .sample-grid {
470
- min-height: 0;
471
- display: grid;
472
- grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
473
- grid-auto-rows: 146px;
474
- gap: 12px;
475
- overflow: auto;
476
- padding-right: 4px;
477
- scrollbar-width: thin;
478
- }
479
- .sample-card {
480
- display: grid;
481
- grid-template-rows: minmax(0, 1fr) 54px;
482
- min-height: 0;
483
- border: 1px solid var(--line);
484
- border-top: 4px solid var(--accent);
485
- border-radius: 10px;
486
- background: #fff;
487
- box-shadow: 0 8px 20px rgba(18, 21, 30, .035);
488
- overflow: hidden;
489
- text-align: left;
490
- padding: 0;
491
- }
492
- .sample-card:hover { box-shadow: 0 12px 28px rgba(18, 21, 30, .07); }
493
- .sample-card.selected { outline: 3px solid rgba(109, 69, 236, .14); }
494
- .sample-wave {
495
- width: 100%;
496
- height: 100%;
497
- display: block;
498
- background: #fff;
499
- }
500
- .sample-card-footer {
501
- display: grid;
502
- grid-template-columns: 34px minmax(0, 1fr);
503
- gap: 9px;
504
- align-items: center;
505
- padding: 9px 10px;
506
- }
507
- .play-dot {
508
- width: 32px;
509
- height: 32px;
510
- display: grid;
511
- place-items: center;
512
- border-radius: 999px;
513
- background: #fff;
514
- border: 1px solid var(--line);
515
- color: var(--text);
516
- font-size: 11px;
517
- }
518
- .sample-name {
519
- display: block;
520
- overflow: hidden;
521
- text-overflow: ellipsis;
522
- white-space: nowrap;
523
- font-size: 13px;
524
- font-weight: 650;
525
- letter-spacing: -.02em;
526
- }
527
- .sample-meta {
528
- display: none;
529
- margin-top: 3px;
530
- color: var(--muted);
531
- font-size: 11px;
532
- font-weight: 500;
533
- }
534
- .empty { color: var(--muted); }
535
-
536
- label {
537
- display: block;
538
- color: var(--text-soft);
539
- font-size: 13px;
540
- font-weight: 700;
541
- letter-spacing: -.01em;
542
- }
543
- .control-group { margin-top: 12px; }
544
- input, select {
545
- width: 100%;
546
- margin-top: 8px;
547
- border: 1px solid var(--line);
548
- border-radius: 10px;
549
- padding: 10px 11px;
550
- color: var(--text);
551
- background: #fff;
552
- outline: none;
553
- font-size: 13px;
554
- }
555
- select { appearance: auto; }
556
- input:focus, select:focus {
557
- border-color: rgba(109, 69, 236, .58);
558
- box-shadow: 0 0 0 4px rgba(109, 69, 236, .075);
559
- }
560
- input[type="range"] {
561
- appearance: none;
562
- padding: 0;
563
- height: 6px;
564
- border: 0;
565
- border-radius: 999px;
566
- background: linear-gradient(90deg, var(--accent), #ececf1);
567
- }
568
- input[type="range"]::-webkit-slider-thumb {
569
- appearance: none;
570
- width: 20px;
571
- height: 20px;
572
- border-radius: 50%;
573
- background: var(--accent);
574
- border: 0;
575
- box-shadow: 0 6px 18px rgba(109, 69, 236, .26);
576
- }
577
- input[type="range"]::-moz-range-thumb {
578
- width: 20px;
579
- height: 20px;
580
- border-radius: 50%;
581
- background: var(--accent);
582
- border: 0;
583
- box-shadow: 0 6px 18px rgba(109, 69, 236, .26);
584
- }
585
- .range-caption {
586
- display: flex;
587
- justify-content: space-between;
588
- margin-top: 7px;
589
- color: var(--muted);
590
- font-size: 11px;
591
- }
592
- .stepper {
593
- display: grid;
594
- grid-template-columns: 40px minmax(0, 1fr) 40px;
595
- gap: 8px;
596
- align-items: center;
597
- }
598
- .stepper input { text-align: center; }
599
- .step-button,
600
- .ghost-button,
601
- .secondary-button,
602
- .export-button,
603
- .mini-button {
604
- border: 1px solid var(--line-strong);
605
- border-radius: 10px;
606
- background: #fff;
607
- color: var(--text-soft);
608
- font-weight: 700;
609
- }
610
- .step-button { height: 38px; margin-top: 8px; font-size: 18px; }
611
- .ghost-button,
612
- .secondary-button,
613
- .export-button {
614
- padding: 9px 11px;
615
- font-size: 12px;
616
- }
617
- .ghost-button:hover:not(:disabled),
618
- .secondary-button:hover:not(:disabled),
619
- .step-button:hover:not(:disabled) { border-color: rgba(109, 69, 236, .34); color: var(--accent-strong); }
620
- .export-button {
621
- margin-top: 10px;
622
- border: 0;
623
- background: #1f2025;
624
- color: #fff;
625
- }
626
- .full-width { width: 100%; }
627
- .danger-button { color: var(--bad); }
628
- .preset-row {
629
- display: grid;
630
- grid-template-columns: 1fr;
631
- gap: 8px;
632
- margin-top: 10px;
633
- }
634
- .control-grid {
635
- display: grid;
636
- grid-template-columns: 1fr;
637
- gap: 9px;
638
- margin-top: 10px;
639
- }
640
- .toggles {
641
- display: grid;
642
- gap: 8px;
643
- margin: 12px 0;
644
- }
645
- .toggles label {
646
- display: grid;
647
- grid-template-columns: 16px minmax(0, 1fr);
648
- align-items: center;
649
- gap: 8px;
650
- font-size: 12px;
651
- color: var(--text-soft);
652
- }
653
- .toggles input { width: auto; margin: 0; }
654
- .downloads {
655
- display: flex;
656
- flex-wrap: wrap;
657
- gap: 8px;
658
- margin-top: 10px;
659
- }
660
- .downloads:empty { display: none; }
661
- .downloads a, .table-wrap a {
662
- color: var(--accent-strong);
663
- text-decoration: none;
664
- font-weight: 760;
665
- background: var(--accent-soft);
666
- border: 1px solid rgba(109, 69, 236, .16);
667
- border-radius: 999px;
668
- padding: 7px 10px;
669
- font-size: 11px;
670
- }
671
-
672
- .stage-list {
673
- display: grid;
674
- gap: 8px;
675
- margin-top: 10px;
676
- }
677
- .stage {
678
- display: grid;
679
- grid-template-columns: 14px minmax(0, 1fr) auto;
680
- gap: 8px;
681
- align-items: center;
682
- padding: 9px;
683
- border: 1px solid var(--line);
684
- border-radius: 12px;
685
- background: var(--surface-soft);
686
- }
687
- .stage .badge { width: 10px; height: 10px; border-radius: 999px; background: #d7d9e1; }
688
- .stage.running .badge { background: var(--accent); box-shadow: 0 0 0 5px rgba(109, 69, 236, .12); }
689
- .stage.done .badge { background: var(--good); }
690
- .stage.error .badge { background: var(--bad); }
691
- .stage strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--text); }
692
- .stage small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); margin-top: 2px; font-size: 11px; }
693
- .stage time { color: var(--text-soft); font-size: 11px; font-variant-numeric: tabular-nums; }
694
- .logs {
695
- min-height: 88px;
696
- max-height: 156px;
697
- overflow: auto;
698
- border: 1px solid var(--line);
699
- border-radius: 12px;
700
- padding: 10px;
701
- margin: 10px 0 0;
702
- background: #fbfbfc;
703
- color: #4f535d;
704
- font-size: 11px;
705
- line-height: 1.45;
706
- }
707
- .history-list, .compact-list {
708
- display: grid;
709
- gap: 8px;
710
- margin-top: 10px;
711
- }
712
- .history-row, .compact-row, .suggestion-row, .log-row {
713
- display: grid;
714
- grid-template-columns: minmax(0, 1fr) auto auto auto;
715
- gap: 8px;
716
- align-items: center;
717
- width: 100%;
718
- border: 1px solid var(--line);
719
- border-radius: 12px;
720
- background: #fff;
721
- padding: 10px;
722
- text-align: left;
723
- }
724
- .compact-row, .suggestion-row, .log-row { grid-template-columns: minmax(0, 1fr) auto; }
725
- .history-row strong, .compact-row strong, .suggestion-row strong, .log-row strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); font-size: 12px; }
726
- .history-row small, .compact-row small, .suggestion-row small, .log-row small { display: block; color: var(--muted); margin-top: 3px; line-height: 1.35; font-size: 11px; }
727
- .compact-row.locked { border-color: rgba(109, 69, 236, .35); background: #fbf9ff; }
728
- .compact-row.suppressed, tr.suppressed { opacity: .55; }
729
- .row-actions { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }
730
- .mini-button {
731
- border-radius: 999px;
732
- padding: 5px 8px;
733
- font-size: 11px;
734
- }
735
-
736
- .bottom-dock {
737
- min-height: 0;
738
- display: grid;
739
- grid-template-columns: minmax(0, 1.35fr) minmax(360px, .85fr);
740
- gap: 12px;
741
- overflow: hidden;
742
- }
743
- .bottom-tool {
744
- min-height: 0;
745
- overflow: hidden;
746
- padding: 12px;
747
- display: grid;
748
- grid-template-rows: auto minmax(0, 1fr);
749
- }
750
- .bottom-tool:not([open]) { display: block; }
751
- .bottom-tool-content {
752
- min-height: 0;
753
- overflow: auto;
754
- padding-top: 8px;
755
- scrollbar-width: thin;
756
- }
757
- .supervision-header {
758
- display: flex;
759
- justify-content: space-between;
760
- gap: 14px;
761
- align-items: flex-start;
762
- }
763
- .supervision-header h3, .result-columns h3 { margin: 0; font-size: 14px; }
764
- .subtle { color: var(--muted); font-size: 12px; line-height: 1.4; margin: 4px 0 0; }
765
- .supervision-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
766
- .state-summary {
767
- display: flex;
768
- flex-wrap: wrap;
769
- gap: 8px;
770
- margin: 10px 0;
771
- }
772
- .state-summary span {
773
- border: 1px solid var(--line);
774
- border-radius: 999px;
775
- background: var(--surface-soft);
776
- padding: 6px 10px;
777
- color: var(--text-soft);
778
- font-size: 11px;
779
- }
780
- .supervision-tools {
781
- display: grid;
782
- grid-template-columns: minmax(180px, 1fr) repeat(4, auto);
783
- gap: 8px;
784
- align-items: end;
785
- margin-top: 10px;
786
- }
787
- .supervision-grid {
788
- display: grid;
789
- grid-template-columns: repeat(4, minmax(210px, 1fr));
790
- gap: 10px;
791
- margin-top: 12px;
792
- }
793
- .supervision-grid article {
794
- min-height: 120px;
795
- border: 1px solid var(--line);
796
- border-radius: 14px;
797
- padding: 10px;
798
- background: var(--surface-soft);
799
- overflow: auto;
800
- }
801
- .supervision-grid h4 { margin: 0 0 8px; font-size: 12px; }
802
- .explanation {
803
- margin: 12px 0 0;
804
- max-height: 160px;
805
- overflow: auto;
806
- border: 1px solid var(--line);
807
- border-radius: 12px;
808
- padding: 10px;
809
- background: #fbfbfc;
810
- font-size: 11px;
811
- }
812
- .result-columns {
813
- display: grid;
814
- grid-template-columns: minmax(0, 1fr);
815
- gap: 12px;
816
- }
817
- .table-wrap {
818
- overflow: auto;
819
- border: 1px solid var(--line);
820
- border-radius: 12px;
821
- margin-top: 8px;
822
- background: #fff;
823
- }
824
- table { width: 100%; border-collapse: collapse; font-size: 12px; }
825
- th, td {
826
- text-align: left;
827
- padding: 8px 10px;
828
- border-bottom: 1px solid var(--line);
829
- white-space: nowrap;
830
- }
831
- th { color: var(--muted); font-weight: 760; background: var(--surface-soft); }
832
- tr.selected { background: #f6f2ff; }
833
- tr.low-confidence { background: #fff8ed; }
834
- tr:last-child td { border-bottom: 0; }
835
-
836
- @media (max-width: 1180px) {
837
- :root { --bottom-h: 244px; }
838
- .shell { min-height: 650px; }
839
- .topbar { grid-template-columns: minmax(180px, 1fr) minmax(260px, 420px) auto; }
840
- .backend-pill { display: none; }
841
- .workstation { grid-template-columns: 248px minmax(0, 1fr) 286px; }
842
- .supervision-grid { grid-template-columns: repeat(2, minmax(210px, 1fr)); }
843
- }
844
- @media (max-width: 900px) {
845
- :root { --topbar-h: 134px; --bottom-h: 250px; }
846
- .shell { padding: 10px; }
847
- .topbar { grid-template-columns: 1fr; gap: 8px; }
848
- .topbar-actions { justify-content: stretch; }
849
- .primary-button { flex: 1; }
850
- .workstation { grid-template-columns: 1fr; grid-template-rows: 0 minmax(0, 1fr) 0; }
851
- .left-sidebar, .right-sidebar { display: none; }
852
- .center-workspace { grid-template-rows: minmax(250px, 1fr) 170px; }
853
- .bottom-dock { grid-template-columns: 1fr; }
854
- .data-panel { display: none; }
855
- }
856
-
857
- /* Final control-specific overrides after the generic form styles. */
858
- .upload-chip input {
859
- margin: 0;
860
- padding: 0;
861
- border: 0;
862
- border-radius: inherit;
863
- background: transparent;
864
- }
865
- .transport-row .transport-seek {
866
- appearance: none;
867
- height: 2px;
868
- margin: 0;
869
- padding: 0;
870
- border: 0;
871
- border-radius: 999px;
872
- background: #d9dbe2;
873
- }
874
- .transport-row .transport-seek::-webkit-slider-thumb {
875
- appearance: none;
876
- width: 14px;
877
- height: 14px;
878
- border-radius: 50%;
879
- background: var(--accent);
880
- border: 0;
881
- opacity: 0;
882
- box-shadow: none;
883
- }
884
- .transport-row .transport-seek:hover::-webkit-slider-thumb { opacity: 1; }
885
- .transport-row .transport-seek::-moz-range-thumb {
886
- width: 14px;
887
- height: 14px;
888
- border-radius: 50%;
889
- background: var(--accent);
890
- border: 0;
891
- opacity: 0;
892
- box-shadow: none;
893
- }
894
- .transport-row .transport-seek:hover::-moz-range-thumb { opacity: 1; }
895
-
896
- /* Pass 9: clearer preview and parameter hierarchy. */
897
- .transport-row {
898
- grid-template-columns: 48px 100px minmax(120px, 1fr) auto;
899
- gap: 14px;
900
- }
901
- .preview-tabs {
902
- display: inline-flex;
903
- align-items: center;
904
- gap: 4px;
905
- padding: 4px;
906
- border: 1px solid var(--line);
907
- border-radius: 999px;
908
- background: #f7f7fa;
909
- white-space: nowrap;
910
- }
911
- .preview-tab {
912
- border: 0;
913
- border-radius: 999px;
914
- padding: 7px 10px;
915
- background: transparent;
916
- color: var(--muted);
917
- font-size: 11px;
918
- font-weight: 800;
919
- cursor: pointer;
920
- }
921
- .preview-tab.active {
922
- background: #fff;
923
- color: var(--accent-strong);
924
- box-shadow: 0 2px 8px rgba(18, 21, 30, .06);
925
- }
926
- .panel-help,
927
- .field-hint {
928
- display: block;
929
- color: var(--muted);
930
- font-size: 11px;
931
- line-height: 1.4;
932
- font-weight: 560;
933
- }
934
- .panel-help {
935
- margin: 10px 0 4px;
936
- }
937
- .field-hint {
938
- margin-top: 7px;
939
- }
940
- .common-presets {
941
- grid-template-columns: 1fr 1fr;
942
- }
943
- .advanced-section {
944
- margin-top: 14px;
945
- padding-top: 12px;
946
- border-top: 1px solid rgba(228, 229, 233, .75);
947
- }
948
- .advanced-section:first-of-type {
949
- border-top: 0;
950
- padding-top: 0;
951
- }
952
- .advanced-section h4 {
953
- margin: 0 0 8px;
954
- color: var(--text);
955
- font-size: 12px;
956
- letter-spacing: -.01em;
957
- }
958
- @media (max-width: 920px) {
959
- .transport-row {
960
- grid-template-columns: 44px 86px minmax(0, 1fr);
961
- }
962
- .preview-tabs {
963
- grid-column: 1 / -1;
964
- justify-content: center;
965
- }
966
- }
967
-
968
- /* Clean default UI pass: keep the app usable at a glance and hide workstation depth until requested. */
969
- :root {
970
- --topbar-h: 58px;
971
- --bottom-h: 44px;
972
- --radius-xl: 14px;
973
- --radius-lg: 10px;
974
- --shadow: 0 8px 24px rgba(18, 21, 30, .045);
975
- }
976
-
977
- .shell {
978
- grid-template-rows: var(--topbar-h) minmax(0, 1fr) var(--bottom-h);
979
- gap: 8px;
980
- padding: 8px;
981
- }
982
- .shell:has(.bottom-tool[open]) {
983
- grid-template-rows: var(--topbar-h) minmax(0, 1fr) minmax(230px, 33vh);
984
- }
985
-
986
- .topbar {
987
- grid-template-columns: minmax(210px, 1fr) minmax(260px, 420px) auto;
988
- gap: 10px;
989
- padding: 0;
990
- }
991
- .app-title { gap: 10px; }
992
- .brand-mark {
993
- width: 34px;
994
- height: 34px;
995
- border-radius: 10px;
996
- box-shadow: none;
997
- }
998
- .brand-mark i { width: 3px; }
999
- .brand-mark i:nth-child(1) { height: 10px; }
1000
- .brand-mark i:nth-child(2) { height: 18px; }
1001
- .brand-mark i:nth-child(3) { height: 25px; }
1002
- .brand-mark i:nth-child(4) { height: 15px; }
1003
- .brand-mark i:nth-child(5) { height: 8px; }
1004
- .app-title strong { font-size: 16px; }
1005
- .app-title small { display: none; }
1006
-
1007
- .upload-chip {
1008
- height: 44px;
1009
- gap: 9px;
1010
- padding: 5px 10px 5px 5px;
1011
- border-radius: 12px;
1012
- box-shadow: none;
1013
- }
1014
- .upload-button-face {
1015
- height: 32px;
1016
- padding: 0 12px;
1017
- border-radius: 9px;
1018
- font-size: 12px;
1019
- }
1020
- .upload-copy strong { font-size: 12px; }
1021
- .upload-copy small { display: none; }
1022
- .backend-pill { max-width: 88px; overflow: hidden; }
1023
- .backend-pill strong { max-width: 68px; overflow: hidden; text-overflow: ellipsis; }
1024
- .primary-button {
1025
- min-width: 156px;
1026
- padding: 12px 16px;
1027
- border-radius: 11px;
1028
- font-size: 14px;
1029
- box-shadow: 0 10px 26px rgba(85, 43, 216, .18);
1030
- }
1031
- .primary-button span { margin-right: 5px; }
1032
-
1033
- .workstation {
1034
- grid-template-columns: 214px minmax(0, 1fr) 292px;
1035
- gap: 8px;
1036
- }
1037
- .sidebar { gap: 6px; overflow: hidden; }
1038
- .sidebar:hover { overflow: auto; }
1039
- .center-workspace {
1040
- grid-template-rows: minmax(280px, 1fr) minmax(142px, 190px);
1041
- gap: 8px;
1042
- }
1043
- .panel,
1044
- .tool-panel,
1045
- .bottom-tool {
1046
- border-color: rgba(226, 227, 232, .78);
1047
- box-shadow: none;
1048
- background: rgba(255, 255, 255, .94);
1049
- }
1050
- .tool-panel {
1051
- padding: 9px 10px;
1052
- border-radius: 12px;
1053
- }
1054
- .tool-panel[open] { padding-bottom: 10px; }
1055
- .tool-panel > summary,
1056
- .bottom-tool > summary {
1057
- min-height: 28px;
1058
- gap: 0;
1059
- }
1060
- .tool-panel > summary span,
1061
- .bottom-tool > summary span {
1062
- font-size: 12px;
1063
- letter-spacing: -.01em;
1064
- }
1065
- .tool-panel > summary small,
1066
- .bottom-tool > summary small {
1067
- font-size: 10px;
1068
- }
1069
- .left-sidebar .tool-panel > summary small,
1070
- .control-card .panel-help,
1071
- .control-card .field-hint,
1072
- .control-card .range-caption,
1073
- .section-heading small,
1074
- .drop-hint-card,
1075
- .review-card strong {
1076
- display: none;
1077
- }
1078
- .tool-panel > summary::after,
1079
- .bottom-tool > summary::after {
1080
- right: 12px;
1081
- font-size: 12px;
1082
- }
1083
-
1084
- .drop-hint-card,
1085
- .job-status-card,
1086
- .review-card {
1087
- margin-top: 8px;
1088
- padding: 9px;
1089
- border-radius: 10px;
1090
- }
1091
- .review-card span,
1092
- .job-status-card small {
1093
- font-size: 11px;
1094
- line-height: 1.3;
1095
- }
1096
- .stage-list { gap: 5px; margin-top: 8px; }
1097
- .stage {
1098
- grid-template-columns: 10px minmax(0, 1fr) auto;
1099
- gap: 6px;
1100
- padding: 7px;
1101
- border-radius: 9px;
1102
- }
1103
- .stage small { display: none; }
1104
- .logs { display: none; }
1105
- .history-list, .compact-list { gap: 6px; }
1106
- .history-row, .compact-row, .suggestion-row, .log-row {
1107
- padding: 8px;
1108
- border-radius: 10px;
1109
- }
1110
-
1111
- .wave-card {
1112
- grid-template-rows: minmax(0, 1fr) 54px;
1113
- border-radius: 16px;
1114
- }
1115
- .transport-row {
1116
- grid-template-columns: 38px 76px minmax(0, 1fr) auto;
1117
- gap: 10px;
1118
- padding: 8px 14px 10px;
1119
- }
1120
- .round-play {
1121
- width: 38px;
1122
- height: 38px;
1123
- font-size: 13px;
1124
- }
1125
- .transport-time { font-size: 12px; }
1126
- .preview-tabs {
1127
- padding: 3px;
1128
- gap: 2px;
1129
- }
1130
- .preview-tab {
1131
- padding: 6px 8px;
1132
- font-size: 10px;
1133
- }
1134
-
1135
- .samples-section {
1136
- padding: 10px;
1137
- border-radius: 14px;
1138
- }
1139
- .section-heading { margin-bottom: 8px; }
1140
- h2 { font-size: 14px; }
1141
- .sample-grid {
1142
- grid-template-columns: repeat(auto-fill, minmax(116px, 1fr));
1143
- grid-auto-rows: 124px;
1144
- gap: 8px;
1145
- }
1146
- .sample-card {
1147
- grid-template-rows: minmax(0, 1fr) 42px;
1148
- border-top-width: 3px;
1149
- border-radius: 9px;
1150
- box-shadow: none;
1151
- }
1152
- .sample-card-footer {
1153
- grid-template-columns: 26px minmax(0, 1fr);
1154
- gap: 7px;
1155
- padding: 7px;
1156
- }
1157
- .play-dot {
1158
- width: 25px;
1159
- height: 25px;
1160
- font-size: 10px;
1161
- }
1162
- .sample-name { font-size: 11px; }
1163
-
1164
- .control-group { margin-top: 9px; }
1165
- label { font-size: 12px; }
1166
- input, select {
1167
- margin-top: 6px;
1168
- padding: 8px 9px;
1169
- border-radius: 9px;
1170
- font-size: 12px;
1171
- }
1172
- input[type="range"] { height: 4px; }
1173
- input[type="range"]::-webkit-slider-thumb { width: 16px; height: 16px; }
1174
- input[type="range"]::-moz-range-thumb { width: 16px; height: 16px; }
1175
- .stepper { grid-template-columns: 34px minmax(0, 1fr) 34px; gap: 6px; }
1176
- .step-button { height: 32px; margin-top: 6px; }
1177
- .ghost-button,
1178
- .secondary-button,
1179
- .export-button {
1180
- padding: 7px 9px;
1181
- border-radius: 9px;
1182
- font-size: 11px;
1183
- }
1184
- .preset-row { gap: 6px; margin-top: 8px; }
1185
- .downloads { gap: 6px; margin-top: 8px; }
1186
- .downloads a, .table-wrap a { padding: 6px 8px; font-size: 10px; }
1187
- .advanced-section { margin-top: 10px; padding-top: 9px; }
1188
- .advanced-section h4 { font-size: 11px; margin-bottom: 6px; }
1189
- .control-grid { gap: 7px; margin-top: 7px; }
1190
-
1191
- .bottom-dock {
1192
- grid-template-columns: repeat(2, minmax(0, 1fr));
1193
- gap: 8px;
1194
- }
1195
- .bottom-tool {
1196
- padding: 8px 10px;
1197
- border-radius: 12px;
1198
- }
1199
- .bottom-tool[open] {
1200
- padding: 10px;
1201
- }
1202
- .bottom-tool:not([open]) {
1203
- display: grid;
1204
- align-items: center;
1205
- }
1206
- .bottom-tool:not([open]) > summary small { display: none; }
1207
- .bottom-tool-content { padding-top: 8px; }
1208
- .supervision-header p,
1209
- .supervision-header h3,
1210
- .result-columns h3,
1211
- .subtle {
1212
- display: none;
1213
- }
1214
- .supervision-header { justify-content: end; }
1215
- .state-summary { margin: 7px 0; gap: 5px; }
1216
- .state-summary span { padding: 5px 8px; font-size: 10px; }
1217
- .supervision-tools {
1218
- grid-template-columns: minmax(160px, 1fr) repeat(4, auto);
1219
- gap: 6px;
1220
- margin-top: 8px;
1221
- }
1222
- .supervision-grid {
1223
- grid-template-columns: repeat(4, minmax(160px, 1fr));
1224
- gap: 8px;
1225
- margin-top: 8px;
1226
- }
1227
- .supervision-grid article {
1228
- min-height: 96px;
1229
- padding: 8px;
1230
- border-radius: 10px;
1231
- }
1232
- .supervision-grid h4 { font-size: 11px; margin-bottom: 6px; }
1233
- .explanation { margin-top: 8px; max-height: 110px; }
1234
 
1235
  @media (max-width: 1180px) {
1236
- :root { --bottom-h: 44px; }
1237
- .workstation { grid-template-columns: 180px minmax(0, 1fr) 270px; }
1238
- .shell:has(.bottom-tool[open]) { grid-template-rows: var(--topbar-h) minmax(0, 1fr) minmax(230px, 36vh); }
 
1239
  }
1240
  @media (max-width: 900px) {
1241
- :root { --topbar-h: 118px; --bottom-h: 44px; }
1242
- .workstation { grid-template-columns: 1fr; grid-template-rows: 0 minmax(0, 1fr) 0; }
1243
- .topbar { grid-template-columns: 1fr; }
1244
- .left-sidebar, .right-sidebar { display: none; }
1245
- .shell:has(.bottom-tool[open]) { grid-template-rows: var(--topbar-h) minmax(0, 1fr) minmax(240px, 38vh); }
1246
- }
1247
-
1248
- .workflow-list {
1249
- display: grid;
1250
- gap: 8px;
1251
- margin: 12px 0 0;
1252
- padding: 0;
1253
- list-style: none;
1254
- }
1255
- .flow-step {
1256
- display: grid;
1257
- gap: 2px;
1258
- padding: 10px 11px;
1259
- border: 1px solid var(--line);
1260
- border-radius: 12px;
1261
- background: var(--surface-soft);
1262
- opacity: .72;
1263
- }
1264
- .flow-step strong {
1265
- color: var(--text);
1266
- font-size: 12px;
1267
- letter-spacing: -.01em;
1268
- }
1269
- .flow-step small {
1270
- color: var(--muted);
1271
- font-size: 11px;
1272
- line-height: 1.25;
1273
- }
1274
- .flow-step.active {
1275
- border-color: rgba(109, 69, 236, .42);
1276
- background: var(--accent-soft);
1277
- opacity: 1;
1278
- }
1279
- .flow-step.done {
1280
- border-color: rgba(100, 169, 70, .34);
1281
- background: rgba(100, 169, 70, .08);
1282
- opacity: .95;
1283
- }
1284
- .wave-hud {
1285
- position: absolute;
1286
- z-index: 2;
1287
- top: 18px;
1288
- left: 20px;
1289
- max-width: min(520px, calc(100% - 40px));
1290
- display: grid;
1291
- gap: 3px;
1292
- padding: 10px 12px;
1293
- border: 1px solid rgba(226, 227, 232, .82);
1294
- border-radius: 14px;
1295
- background: rgba(255, 255, 255, .72);
1296
- backdrop-filter: blur(12px);
1297
- pointer-events: none;
1298
- }
1299
- .wave-hud strong {
1300
- font-size: 13px;
1301
- line-height: 1.15;
1302
- letter-spacing: -.02em;
1303
- }
1304
- .wave-hud small {
1305
- color: var(--muted);
1306
- font-size: 11px;
1307
- line-height: 1.25;
1308
- }
1309
- .stage-progress {
1310
- grid-column: 2 / 4;
1311
- height: 3px;
1312
- overflow: hidden;
1313
- border-radius: 999px;
1314
- background: rgba(226, 227, 232, .9);
1315
- }
1316
- .stage-progress span {
1317
- display: block;
1318
- height: 100%;
1319
- width: 0%;
1320
- border-radius: inherit;
1321
- background: var(--accent);
1322
- transition: width .16s ease;
1323
- }
1324
-
1325
- /* Automatic card-flow UI pass. Keep the default screen quiet: drop, watch waveform, review cards. */
1326
- :root {
1327
- --topbar-h: 64px;
1328
- --bottom-h: 54px;
1329
- }
1330
- .shell {
1331
- gap: 8px;
1332
- padding: 10px;
1333
- min-height: 620px;
1334
- }
1335
- .topbar {
1336
- grid-template-columns: minmax(220px, .8fr) minmax(320px, 560px) auto;
1337
- gap: 10px;
1338
- }
1339
- .app-title small { max-width: 520px; }
1340
- .upload-chip {
1341
- height: 52px;
1342
- border: 1.5px dashed rgba(109, 69, 236, .34);
1343
- background: rgba(255,255,255,.94);
1344
- }
1345
- .upload-button-face {
1346
- height: 38px;
1347
- background: var(--accent);
1348
- }
1349
- .primary-button {
1350
- min-width: 132px;
1351
- padding: 13px 16px;
1352
- background: #232329;
1353
- box-shadow: none;
1354
- }
1355
- .backend-pill { display: none; }
1356
- .workstation {
1357
- grid-template-columns: 218px minmax(0, 1fr) 248px;
1358
- gap: 8px;
1359
- }
1360
- .sidebar {
1361
- gap: 7px;
1362
- overflow: hidden;
1363
- }
1364
- .sidebar:hover { overflow: auto; }
1365
- .tool-panel {
1366
- border-radius: 14px;
1367
- box-shadow: none;
1368
- }
1369
- .tool-panel > summary {
1370
- min-height: 40px;
1371
- padding: 10px 12px;
1372
- }
1373
- .tool-panel > summary small,
1374
- .panel-help,
1375
- .field-hint,
1376
- .drop-hint-card,
1377
- .selection-panel,
1378
- .history-panel,
1379
- .downloads-panel,
1380
- .advanced-controls,
1381
- .progress-panel {
1382
- display: none;
1383
- }
1384
- .workflow-panel { display: block; }
1385
- .workflow-list { padding: 8px; gap: 6px; }
1386
- .flow-step {
1387
- padding: 9px 10px;
1388
- border-radius: 11px;
1389
- }
1390
- .flow-step small { font-size: 10px; line-height: 1.25; }
1391
- .control-card[open] { max-height: 360px; overflow: auto; }
1392
- .control-card .control-group { margin: 8px 10px; }
1393
- .common-presets { display: none; }
1394
- .advanced-hidden { display: none !important; }
1395
- .center-workspace {
1396
- grid-template-rows: minmax(260px, .58fr) minmax(240px, .42fr);
1397
- gap: 8px;
1398
- }
1399
- .panel,
1400
- .wave-card,
1401
- .samples-section {
1402
- border-radius: 18px;
1403
- box-shadow: none;
1404
- }
1405
- .wave-card { padding: 12px; }
1406
- .wave-hud {
1407
- left: 18px;
1408
- top: 16px;
1409
- max-width: min(560px, calc(100% - 36px));
1410
- padding: 9px 12px;
1411
- border-radius: 999px;
1412
- background: rgba(255,255,255,.76);
1413
- backdrop-filter: blur(8px);
1414
- display: inline-flex;
1415
- align-items: baseline;
1416
- gap: 10px;
1417
- }
1418
- .wave-hud strong { font-size: 12px; }
1419
- .wave-hud small { font-size: 11px; }
1420
- .waveform {
1421
- height: 100%;
1422
- border-radius: 16px;
1423
- cursor: crosshair;
1424
- }
1425
- .transport-row {
1426
- height: 42px;
1427
- padding: 6px 8px;
1428
- margin-top: 8px;
1429
- }
1430
- .preview-tabs { margin-left: auto; }
1431
- .preview-tab { padding: 5px 8px; font-size: 11px; }
1432
- .samples-section {
1433
- padding: 12px;
1434
- overflow: hidden;
1435
- }
1436
- .section-heading {
1437
- margin-bottom: 8px;
1438
- }
1439
- .section-heading h2 { font-size: 16px; }
1440
- .section-heading small { font-size: 11px; }
1441
- .sample-grid {
1442
- height: calc(100% - 34px);
1443
- display: grid;
1444
- grid-auto-flow: column;
1445
- grid-auto-columns: minmax(170px, 1fr);
1446
- grid-template-columns: none;
1447
- gap: 10px;
1448
- overflow-x: auto;
1449
- overflow-y: hidden;
1450
- padding: 1px 2px 8px;
1451
- }
1452
- .sample-column {
1453
- min-width: 170px;
1454
- display: grid;
1455
- grid-template-rows: auto minmax(0, 1fr);
1456
- gap: 8px;
1457
- }
1458
- .sample-column-head {
1459
- display: grid;
1460
- grid-template-columns: minmax(0, 1fr) auto auto;
1461
- align-items: center;
1462
- gap: 7px;
1463
- padding: 0 2px;
1464
- text-transform: capitalize;
1465
- }
1466
- .sample-column-head strong { font-size: 13px; letter-spacing: -.02em; }
1467
- .sample-column-head span {
1468
- color: var(--muted);
1469
- font-size: 11px;
1470
- }
1471
- .draw-card-button {
1472
- border: 1px solid var(--line);
1473
- border-radius: 999px;
1474
- padding: 4px 8px;
1475
- background: #fff;
1476
- color: var(--accent-strong);
1477
- font-size: 11px;
1478
- font-weight: 750;
1479
- }
1480
- .sample-column-list {
1481
- display: grid;
1482
- align-content: start;
1483
- gap: 8px;
1484
- min-height: 0;
1485
- overflow: auto;
1486
- padding-right: 2px;
1487
- }
1488
- .sample-card {
1489
- min-height: 138px;
1490
- display: grid;
1491
- grid-template-rows: minmax(0, 1fr) auto;
1492
- padding: 0;
1493
- border-top-width: 3px;
1494
- overflow: hidden;
1495
- }
1496
- .sample-play-zone {
1497
- min-width: 0;
1498
- display: grid;
1499
- grid-template-rows: minmax(72px, 1fr) auto;
1500
- gap: 0;
1501
- padding: 0;
1502
- border: 0;
1503
- background: transparent;
1504
- color: inherit;
1505
- text-align: left;
1506
- }
1507
- .sample-wave {
1508
- height: 84px;
1509
- border-radius: 0;
1510
- }
1511
- .sample-card-footer {
1512
- padding: 8px 10px;
1513
- }
1514
- .sample-name { font-size: 12px; }
1515
- .sample-meta { font-size: 10px; }
1516
- .sample-card-actions {
1517
- display: grid;
1518
- grid-template-columns: repeat(2, minmax(0, 1fr));
1519
- gap: 4px;
1520
- padding: 0 8px 8px;
1521
- opacity: .22;
1522
- transition: opacity .16s ease;
1523
- }
1524
- .sample-card:hover .sample-card-actions,
1525
- .sample-card.selected .sample-card-actions { opacity: 1; }
1526
- .sample-card-actions button {
1527
- min-width: 0;
1528
- border: 1px solid var(--line);
1529
- border-radius: 8px;
1530
- padding: 4px 5px;
1531
- background: #fff;
1532
- color: var(--text-soft);
1533
- font-size: 10px;
1534
- font-weight: 650;
1535
- }
1536
- .sample-card-actions button:hover { color: var(--accent-strong); border-color: rgba(109,69,236,.35); }
1537
- .empty-drop-state {
1538
- grid-column: 1 / -1;
1539
- display: grid;
1540
- place-items: center;
1541
- align-content: center;
1542
- gap: 6px;
1543
- min-height: 100%;
1544
- color: var(--muted);
1545
- border: 1.5px dashed var(--line-strong);
1546
- border-radius: 16px;
1547
- background: rgba(255,255,255,.58);
1548
- text-align: center;
1549
- }
1550
- .empty-drop-state strong { color: var(--text); font-size: 18px; }
1551
- .bottom-dock {
1552
- grid-template-columns: repeat(2, minmax(0, 1fr));
1553
- gap: 8px;
1554
- min-height: 0;
1555
- }
1556
- .bottom-tool > summary {
1557
- min-height: 42px;
1558
- padding: 8px 12px;
1559
- }
1560
- .bottom-tool:not([open]) { height: 42px; }
1561
- .bottom-tool[open] {
1562
- position: fixed;
1563
- left: 10px;
1564
- right: 10px;
1565
- bottom: 10px;
1566
- height: min(46vh, 420px);
1567
- z-index: 50;
1568
- box-shadow: 0 24px 80px rgba(18,21,30,.18);
1569
- }
1570
- @media (max-width: 1000px) {
1571
- .workstation { grid-template-columns: 1fr; grid-template-rows: minmax(0, 1fr); }
1572
- .left-sidebar, .right-sidebar { display: none; }
1573
- .topbar { grid-template-columns: minmax(0, 1fr) auto; }
1574
- .app-title { display: none; }
1575
  }
 
 
1
  :root {
2
  color-scheme: light;
3
+ --bg: #f7f7f9;
4
+ --panel: #ffffff;
5
+ --panel-soft: #fbfbfc;
6
+ --line: #e6e6eb;
7
+ --line-strong: #d7d8df;
8
+ --text: #1f2430;
9
+ --muted: #767b8a;
10
+ --faint: #a4a8b3;
11
+ --purple: #6d3df2;
12
+ --purple-dark: #5528d7;
13
+ --purple-soft: #eee8ff;
14
+ --bad: #d7374a;
15
+ --good: #29a36a;
16
+ --warn: #d99b22;
17
+ --shadow: 0 1px 2px rgba(18, 21, 28, .04), 0 12px 32px rgba(18, 21, 28, .06);
18
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
 
 
 
 
19
  }
 
20
  * { box-sizing: border-box; }
21
+ html, body { margin: 0; height: 100%; overflow: hidden; background: var(--bg); color: var(--text); }
 
 
 
 
 
 
 
 
22
  button, input, select { font: inherit; }
23
  button { cursor: pointer; }
24
+ button:disabled { cursor: not-allowed; opacity: .48; }
25
+
26
+ .app-shell { height: 100vh; display: grid; grid-template-rows: 70px minmax(0, 1fr) 54px; }
27
+ .topbar { display: grid; grid-template-columns: minmax(240px, 1fr) minmax(280px, 460px) minmax(440px, 1fr); align-items: center; gap: 18px; padding: 14px 26px; background: rgba(255,255,255,.86); border-bottom: 1px solid var(--line); backdrop-filter: blur(16px); }
28
+ .brand, .top-actions, .backend-pill { display: flex; align-items: center; }
29
+ .brand { gap: 12px; min-width: 0; }
30
+ .brand strong { font-size: 18px; letter-spacing: -.02em; }
31
+ .brand-mark { display: inline-flex; align-items: center; gap: 3px; height: 24px; }
32
+ .brand-mark i { display: block; width: 3px; border-radius: 999px; background: #151821; }
33
+ .brand-mark i:nth-child(1), .brand-mark i:nth-child(5) { height: 10px; opacity: .68; }
34
+ .brand-mark i:nth-child(2), .brand-mark i:nth-child(4) { height: 20px; opacity: .86; }
35
+ .brand-mark i:nth-child(3) { height: 28px; }
36
+ .beta-pill { color: var(--purple); background: var(--purple-soft); border: 1px solid #dfd2ff; border-radius: 999px; padding: 2px 8px; font-size: 11px; font-weight: 700; }
37
+ .file-picker { justify-self: center; max-width: 460px; min-width: 260px; display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 8px 12px; border-radius: 12px; border: 1px solid transparent; color: var(--text); font-weight: 600; white-space: nowrap; }
38
+ .file-picker:hover { background: var(--panel); border-color: var(--line); }
39
+ .file-picker input { display: none; }
40
+ .file-picker small { max-width: 140px; overflow: hidden; text-overflow: ellipsis; color: var(--muted); font-size: 11px; font-weight: 500; }
41
+ .top-actions { justify-content: end; gap: 10px; min-width: 0; }
42
+ .backend-pill { gap: 7px; color: var(--muted); font-size: 11px; opacity: .75; }
43
+ .backend-pill strong { display: block; line-height: 1; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  .backend-pill small { display: none; }
45
+ .status-dot { width: 7px; height: 7px; border-radius: 99px; background: var(--warn); }
 
 
 
 
 
46
  .status-dot.ok { background: var(--good); }
47
  .status-dot.bad { background: var(--bad); }
48
+ .primary-action, .secondary-action, .tool-button, .icon-button, .reset-button, .help-button { border: 1px solid var(--line); background: var(--panel); color: var(--text); border-radius: 9px; min-height: 34px; padding: 0 13px; font-weight: 650; box-shadow: 0 1px 1px rgba(0,0,0,.02); }
49
+ .primary-action { border-color: transparent; color: white; background: linear-gradient(135deg, #7348f5, #552bd8); box-shadow: 0 8px 18px rgba(109,61,242,.20); }
50
+ .secondary-action:hover, .tool-button:hover, .icon-button:hover, .reset-button:hover { border-color: var(--line-strong); background: #fafafd; }
51
+ .icon-button { width: 34px; padding: 0; font-size: 19px; }
52
+ .help-button { width: 34px; height: 34px; padding: 0; border-radius: 999px; font-size: 18px; }
53
+
54
+ .main-grid { min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) 344px; gap: 0; }
55
+ .canvas-pane { min-width: 0; display: grid; grid-template-rows: minmax(310px, 48vh) minmax(0, 1fr); border-right: 1px solid var(--line); }
56
+ .wave-panel { position: relative; display: grid; grid-template-rows: 52px minmax(0, 1fr) 58px; background: var(--panel); border-bottom: 1px solid var(--line); }
57
+ .wave-toolbar { display: grid; grid-template-columns: auto minmax(0,1fr) auto; align-items: center; gap: 16px; padding: 12px 18px; }
58
+ .zoom-tools { display: flex; align-items: center; gap: 0; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: var(--panel); }
59
+ .zoom-tools .tool-button { border: 0; border-right: 1px solid var(--line); border-radius: 0; min-width: 38px; padding: 0 10px; box-shadow: none; }
60
+ .zoom-tools .tool-button:last-child { border-right: 0; }
61
+ .zoom-tools .fit { min-width: 52px; }
62
+ .wave-status { min-width: 0; display: flex; align-items: baseline; justify-content: center; gap: 10px; color: var(--muted); font-size: 13px; }
63
+ .wave-status strong { color: var(--text); font-size: 13px; }
64
+ .wave-status span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
65
+ .top-time { color: #575c68; font-variant-numeric: tabular-nums; font-size: 14px; justify-self: end; }
66
+ .waveform { width: 100%; height: 100%; display: block; background: linear-gradient(90deg, transparent, rgba(109,61,242,.035), transparent); }
67
+ .transport-row { margin: 0 18px 14px; min-height: 44px; display: grid; grid-template-columns: 48px minmax(0, 1fr) auto; align-items: center; gap: 16px; padding: 8px 12px; border: 1px solid var(--line); border-radius: 10px; background: rgba(255,255,255,.88); box-shadow: 0 8px 26px rgba(18, 21, 28, .06); }
68
+ .round-play { width: 42px; height: 42px; border-radius: 999px; border: 1px solid var(--line); background: var(--panel); color: #12151b; font-size: 16px; }
69
+ .transport-seek, input[type="range"] { accent-color: var(--purple); }
70
+ .transport-seek { width: 100%; }
71
+ .preview-tabs { display: flex; gap: 4px; padding: 3px; border: 1px solid var(--line); background: #f5f5f8; border-radius: 9px; }
72
+ .preview-tab { border: 0; border-radius: 7px; background: transparent; padding: 6px 10px; color: var(--muted); font-size: 12px; font-weight: 650; }
73
+ .preview-tab.active { background: var(--panel); color: var(--purple); box-shadow: 0 1px 2px rgba(18,21,28,.08); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  .hidden-audio-bank { display: none; }
75
 
76
+ .sample-board { min-height: 0; display: grid; grid-template-rows: 52px minmax(0, 1fr); background: #fbfbfc; }
77
+ .board-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 20px; border-bottom: 1px solid var(--line); }
78
+ .board-header h2 { margin: 0; font-size: 15px; letter-spacing: -.01em; }
79
+ .board-header span { color: var(--muted); font-size: 12px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
80
+ .sample-grid { min-height: 0; display: grid; grid-auto-flow: column; grid-auto-columns: minmax(170px, 1fr); overflow-x: auto; overflow-y: hidden; padding: 0; }
81
+ .sample-column { min-width: 170px; display: grid; grid-template-rows: 42px minmax(0,1fr) 42px; border-right: 1px solid var(--line); background: #fcfcfd; }
82
+ .sample-column-head { display: grid; grid-template-columns: auto 1fr auto auto; align-items: center; gap: 8px; padding: 0 12px; border-bottom: 1px solid var(--line); color: #4b505b; font-size: 13px; }
83
+ .sample-column-head::before { content: ""; width: 10px; height: 10px; border-radius: 999px; background: var(--column-color, var(--purple)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--column-color, var(--purple)) 14%, transparent); }
84
+ .sample-column-head strong { text-transform: capitalize; }
85
+ .sample-column-head span { justify-self: start; min-width: 22px; height: 22px; display: grid; place-items: center; border-radius: 7px; background: #f0f1f5; color: var(--muted); font-size: 12px; }
86
+ .draw-card-button { border: 0; background: transparent; color: #596070; font-size: 20px; line-height: 1; padding: 0 2px; }
87
+ .sample-column-list { min-height: 0; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 10px; }
88
+ .sample-card { position: relative; border: 1px solid color-mix(in srgb, var(--card-color, var(--purple)) 58%, var(--line)); border-radius: 9px; background: var(--panel); box-shadow: 0 8px 20px rgba(18, 21, 28, .05); overflow: hidden; }
89
+ .sample-card.selected { box-shadow: 0 0 0 2px color-mix(in srgb, var(--card-color, var(--purple)) 24%, transparent), 0 10px 26px rgba(18,21,28,.08); }
90
+ .sample-play-zone { width: 100%; border: 0; background: transparent; padding: 0; text-align: left; }
91
+ .sample-wave { width: 100%; height: 74px; display: block; }
92
+ .sample-card-footer { display: grid; grid-template-columns: 18px minmax(0,1fr); align-items: center; gap: 7px; padding: 0 10px 7px; }
93
+ .play-dot { width: 14px; height: 14px; display: inline-grid; place-items: center; color: var(--purple); font-size: 10px; }
94
+ .sample-name { display: block; color: #2c303a; font-size: 13px; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
95
+ .sample-meta { display: flex; justify-content: space-between; color: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
96
+ .sample-card-actions { display: grid; grid-template-columns: repeat(4,1fr); border-top: 1px solid var(--line); }
97
+ .sample-card-actions button { height: 30px; border: 0; border-right: 1px solid var(--line); background: #fff; color: #3e4350; font-size: 0; }
98
+ .sample-card-actions button::before { font-size: 13px; }
99
+ .sample-card-actions button:nth-child(1)::before { content: "×"; }
100
+ .sample-card-actions button:nth-child(2)::before { content: "◁"; }
101
+ .sample-card-actions button:nth-child(3)::before { content: "▷"; }
102
+ .sample-card-actions button:nth-child(4)::before { content: "⋯"; }
103
+ .sample-card-actions button:last-child { border-right: 0; }
104
+ .empty-drop-state, .empty { color: var(--muted); padding: 18px; font-size: 13px; }
105
+
106
+ .settings-pane { min-width: 0; background: var(--panel); display: grid; grid-template-rows: 58px minmax(0, auto) minmax(0, auto) minmax(0, auto) minmax(0, 1fr) 58px; border-left: 1px solid var(--line); overflow: hidden; }
107
+ .settings-head { display: flex; align-items: center; justify-content: space-between; padding: 0 20px; border-bottom: 1px solid var(--line); }
108
+ .settings-head strong { font-size: 14px; }
109
+ .settings-section { padding: 18px 20px; border-bottom: 1px solid var(--line); }
110
+ .settings-section h3 { margin: 0 0 14px; font-size: 13px; color: #383d49; }
111
+ .control-line, .range-line, .toggle-line { display: grid; gap: 8px; margin-bottom: 16px; color: #4b505c; font-size: 13px; font-weight: 560; }
112
+ .control-line select, .expert-grid select, .expert-grid input { width: 100%; height: 38px; border-radius: 8px; border: 1px solid var(--line); background: var(--panel); padding: 0 10px; color: var(--text); }
113
+ .range-wrap { display: grid; grid-template-columns: minmax(0,1fr) 54px; align-items: center; gap: 12px; }
114
+ .range-wrap output { text-align: right; color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
115
+ .toggle-line { grid-template-columns: 1fr auto; align-items: center; }
116
+ .toggle-line input { width: 32px; height: 18px; accent-color: var(--purple); }
117
+ .advanced-fold { min-height: 0; overflow-y: auto; padding-bottom: 12px; }
118
+ .advanced-fold summary { cursor: pointer; font-size: 13px; font-weight: 700; color: #383d49; }
119
+ .expert-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 14px; }
120
+ .expert-grid label, .toggles label { display: grid; gap: 6px; color: var(--muted); font-size: 11px; }
121
+ .toggles { display: grid; gap: 8px; margin: 14px 0; }
122
+ .preset-row { display: flex; gap: 8px; flex-wrap: wrap; }
123
+ .reset-button { align-self: center; justify-self: stretch; margin: 10px 20px; }
124
+
125
+ .bottom-bar { display: grid; grid-template-columns: minmax(160px,1fr) minmax(260px,360px) minmax(320px,1fr); align-items: center; gap: 16px; padding: 10px 26px; border-top: 1px solid var(--line); background: rgba(255,255,255,.9); backdrop-filter: blur(16px); }
126
+ .selection-count { display: flex; align-items: center; gap: 10px; color: #4f5563; font-size: 13px; }
127
+ .checked-box { width: 18px; height: 18px; border-radius: 4px; display: grid; place-items: center; color: white; background: var(--purple); font-size: 12px; }
128
+ .bottom-zoom { justify-self: center; display: grid; grid-template-columns: auto minmax(160px, 1fr) auto; align-items: center; gap: 8px; width: 100%; }
129
+ .mini-slider { width: 100%; }
130
+ .bottom-actions { justify-self: end; display: flex; align-items: center; gap: 10px; }
131
+
132
+ .tools-drawer { position: absolute; left: 24px; right: 24px; bottom: 64px; max-height: 52vh; overflow: auto; background: rgba(255,255,255,.97); border: 1px solid var(--line); border-radius: 16px; box-shadow: var(--shadow); padding: 18px; z-index: 10; }
133
+ .drawer-grid { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 14px; }
134
+ .drawer-grid article, .drawer-advanced { border: 1px solid var(--line); border-radius: 12px; background: #fff; padding: 14px; }
135
+ .drawer-grid h3 { margin: 0 0 10px; font-size: 14px; }
136
+ .logs, .explanation { max-height: 180px; overflow: auto; white-space: pre-wrap; border: 1px solid var(--line); border-radius: 8px; padding: 10px; background: #f8f8fa; color: #4f5563; font-size: 11px; }
137
+ .stage-list, .history-list, .compact-list, .downloads { display: grid; gap: 8px; }
138
+ .downloads a { color: var(--purple); text-decoration: none; font-weight: 650; font-size: 13px; }
139
+ .job-pill { display: inline-flex; padding: 3px 8px; border-radius: 999px; background: var(--purple-soft); color: var(--purple); font-size: 12px; font-weight: 700; }
140
+ .supervision-tools, .supervision-actions { display: flex; gap: 8px; flex-wrap: wrap; margin: 10px 0; }
141
+ .supervision-grid, .result-columns { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 12px; }
142
+ .table-wrap { overflow: auto; max-height: 260px; border: 1px solid var(--line); border-radius: 10px; }
143
+ table { border-collapse: collapse; width: 100%; font-size: 12px; }
144
+ th, td { padding: 8px; border-bottom: 1px solid var(--line); text-align: left; white-space: nowrap; }
145
+ .compact-row, .history-row { width: 100%; border: 1px solid var(--line); border-radius: 8px; background: #fff; padding: 8px; display: flex; align-items: center; justify-content: space-between; gap: 10px; text-align: left; }
146
+ .compact-row small, .history-row small { display: block; color: var(--muted); }
147
+ .danger { color: var(--bad); }
148
+
149
+ .error-banner { position: fixed; z-index: 30; left: 50%; top: 78px; transform: translateX(-50%); width: min(760px, calc(100vw - 32px)); display: flex; align-items: start; justify-content: space-between; gap: 16px; padding: 14px 16px; border-radius: 14px; border: 1px solid #fac8d0; background: #fff4f6; box-shadow: var(--shadow); color: #611923; }
150
+ .error-copy { display: grid; gap: 3px; }
151
+ .error-copy span { font-size: 13px; }
152
+ .error-copy small { color: #8a3440; white-space: pre-wrap; }
153
+ .global-drop-overlay { position: fixed; inset: 0; z-index: 25; display: none; place-items: center; background: rgba(109,61,242,.12); backdrop-filter: blur(4px); }
154
+ .global-drop-overlay.active { display: grid; }
155
+ .global-drop-overlay > div { display: grid; gap: 6px; place-items: center; padding: 32px 42px; border-radius: 18px; border: 1px solid #d9ccff; background: rgba(255,255,255,.94); box-shadow: var(--shadow); }
156
+ .global-drop-overlay strong { font-size: 20px; }
157
+ .global-drop-overlay span { color: var(--muted); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
  @media (max-width: 1180px) {
160
+ .topbar { grid-template-columns: auto minmax(220px,1fr) auto; padding-inline: 16px; }
161
+ .backend-pill, #runButton { display: none; }
162
+ .main-grid { grid-template-columns: minmax(0, 1fr) 300px; }
163
+ .sample-grid { grid-auto-columns: 160px; }
164
  }
165
  @media (max-width: 900px) {
166
+ html, body { overflow: auto; }
167
+ .app-shell { height: auto; min-height: 100vh; grid-template-rows: auto auto auto; }
168
+ .topbar, .main-grid, .bottom-bar { grid-template-columns: 1fr; }
169
+ .canvas-pane { grid-template-rows: 360px 520px; }
170
+ .settings-pane { border-left: 0; border-top: 1px solid var(--line); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  }
172
+ .global-drop-overlay.visible { display: grid; }