ChatGPT commited on
Commit
e33cc90
·
1 Parent(s): 3e9170f

feat: add fixed workstation layout

Browse files
README.md CHANGED
@@ -39,7 +39,7 @@ Implemented:
39
  - event log,
40
  - suggestions,
41
  - undo stack.
42
- - Reference-aligned minimal waveform-first UI: compact file/action bar, quiet large waveform card, one custom transport row, right-side core controls, sample-card grid, and one collapsed review/edit workbench for power features.
43
  - Supervision UI:
44
  - selected-hit actions,
45
  - move hit to cluster,
@@ -72,6 +72,7 @@ See:
72
  - `docs/interactive-ux/README.md`
73
  - `docs/REMAINING_WORK.md`
74
  - `docs/SUPERVISED_EXPORT_AND_FORCE_ONSET.md`
 
75
 
76
  ## Run locally
77
 
@@ -154,7 +155,7 @@ curl http://127.0.0.1:7860/api/jobs
154
  | `sample_extractor.py` | Core DSP/sample extraction implementation |
155
  | `supervised_state.py` | Persistent semantic state, confidence, constraints, events, suggestions, force-onset, restore, undo |
156
  | `supervised_export.py` | Renders edited semantic state into supervised WAV/MIDI/reconstruction/ZIP artifacts |
157
- | `web/` | Custom no-build browser frontend with the reference-aligned light waveform-first UI, sample-card grid, hidden-audio audition, add-onset mode, edited export, and collapsed supervision workbench |
158
  | `scripts/benchmark_subprocesses.py` | Synthetic benchmark runner for stage timings |
159
  | `scripts/test_interactive_supervision.py` | Smoke test for supervised state endpoints |
160
  | `scripts/test_supervised_export_and_force_onset.py` | Smoke test for force-onset, restore, suggestion diffs, and edited exports |
 
39
  - event log,
40
  - suggestions,
41
  - undo stack.
42
+ - Fixed, non-scrolling workstation UI: explicit top-bar upload button, whole-app drag/drop overlay, left/right sidebars for tool panels, large center waveform/sample workspace, and bottom dock for review/edit tools.
43
  - Supervision UI:
44
  - selected-hit actions,
45
  - move hit to cluster,
 
72
  - `docs/interactive-ux/README.md`
73
  - `docs/REMAINING_WORK.md`
74
  - `docs/SUPERVISED_EXPORT_AND_FORCE_ONSET.md`
75
+ - `docs/FIXED_WORKSTATION_UI.md`
76
 
77
  ## Run locally
78
 
 
155
  | `sample_extractor.py` | Core DSP/sample extraction implementation |
156
  | `supervised_state.py` | Persistent semantic state, confidence, constraints, events, suggestions, force-onset, restore, undo |
157
  | `supervised_export.py` | Renders edited semantic state into supervised WAV/MIDI/reconstruction/ZIP artifacts |
158
+ | `web/` | Custom no-build browser frontend with fixed non-scrolling workstation layout, explicit upload/whole-page drag-drop, sidebars, bottom dock, sample-card grid, hidden-audio audition, add-onset mode, and edited export |
159
  | `scripts/benchmark_subprocesses.py` | Synthetic benchmark runner for stage timings |
160
  | `scripts/test_interactive_supervision.py` | Smoke test for supervised state endpoints |
161
  | `scripts/test_supervised_export_and_force_onset.py` | Smoke test for force-onset, restore, suggestion diffs, and edited exports |
docs/FEATURES.md CHANGED
@@ -11,7 +11,9 @@ Turn an input audio file into a practical drum sample pack: detected hits, group
11
  | Area | Feature | Status | Notes |
12
  |---|---|---:|---|
13
  | UI | Custom browser frontend | Implemented | `web/index.html`, `web/styles.css`, `web/app.js`; no Gradio dependency in active app. |
14
- | UI | Drag/drop audio upload | Implemented | Uses multipart upload to `POST /api/jobs`. |
 
 
15
  | UI | Minimal custom transport | Implemented | One image-aligned play/time/progress row drives source preview before extraction and stem preview after extraction. |
16
  | UI | Pipeline controls | Implemented | Stem/model/onset/clustering/MIDI/synthesis/cache controls. |
17
  | UI | Streaming progress | Implemented | Uses `EventSource` over `GET /api/jobs/{id}/events`, with polling fallback. |
 
11
  | Area | Feature | Status | Notes |
12
  |---|---|---:|---|
13
  | UI | Custom browser frontend | Implemented | `web/index.html`, `web/styles.css`, `web/app.js`; no Gradio dependency in active app. |
14
+ | UI | Explicit upload button | Implemented | Top bar contains a visible `Upload audio` control. |
15
+ | UI | Whole-app drag/drop audio upload | Implemented | Dropping files anywhere on the app selects the file and shows a drop overlay during drag. |
16
+ | UI | Fixed non-scrolling workstation layout | Implemented | Body is viewport-locked; tools live in left/right sidebars and a bottom dock; long content scrolls inside panels only. |
17
  | UI | Minimal custom transport | Implemented | One image-aligned play/time/progress row drives source preview before extraction and stem preview after extraction. |
18
  | UI | Pipeline controls | Implemented | Stem/model/onset/clustering/MIDI/synthesis/cache controls. |
19
  | UI | Streaming progress | Implemented | Uses `EventSource` over `GET /api/jobs/{id}/events`, with polling fallback. |
docs/FIXED_WORKSTATION_UI.md ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Fixed workstation UI
2
+
3
+ Last updated: 2026-05-12
4
+
5
+ ## Goal
6
+
7
+ The web app should behave like a compact workstation rather than a long document page. The default screen must not require document scrolling. Secondary tools live in left and right sidebars plus a bottom bar, with each tool panel independently expandable/collapsible.
8
+
9
+ ## Implemented layout
10
+
11
+ | Region | Contents | Scroll behavior |
12
+ |---|---|---|
13
+ | Top bar | App title, explicit `Upload audio` button, selected-file metadata, backend status, primary `Extract Samples` action | Fixed height; no scroll |
14
+ | Left sidebar | Source/drop guidance, selected-hit/sample context, pipeline stages/logs, run history | Sidebar-internal scroll only |
15
+ | Center workspace | Large waveform/transport and representative sample cards | Sample grid scrolls internally when needed |
16
+ | Right sidebar | Core extraction controls, exports, advanced model/DSP settings | Sidebar-internal scroll only |
17
+ | Bottom bar | Interactive review/edit tools and raw tables | Panel-internal scroll only |
18
+
19
+ The document itself is locked with `overflow: hidden`; long content is constrained to the relevant tool panel.
20
+
21
+ ## Upload behavior
22
+
23
+ Implemented upload affordances:
24
+
25
+ 1. A visible `Upload audio` control in the top bar.
26
+ 2. A selected-file label beside the upload button.
27
+ 3. Whole-app drag/drop for audio files.
28
+ 4. A full-screen drop overlay while dragging files over the app.
29
+ 5. Existing multipart upload path through `POST /api/jobs` remains unchanged.
30
+
31
+ ## Design constraints
32
+
33
+ - Keep the center workspace visually simple: waveform, transport, sample cards.
34
+ - Do not reintroduce long-page scrolling.
35
+ - Avoid duplicate controls; power tools should live in sidebars or the bottom dock.
36
+ - Keep tool panels based on native `<details>` so they remain simple and keyboard-accessible.
37
+ - Preserve all existing DOM ids used by the no-build JavaScript app.
38
+
39
+ ## Validation
40
+
41
+ Static checks added/performed for this pass:
42
+
43
+ - `node --check web/app.js`
44
+ - DOM id/reference check for every `$()` lookup in `web/app.js`
45
+ - Duplicate id check for `web/index.html`
46
+ - Python compile check for active Python files
47
+ - FastAPI extraction smoke test
docs/PROGRESS.md CHANGED
@@ -240,3 +240,22 @@ Completed in this pass:
240
  Outcome:
241
 
242
  The default UI is now aligned with the supplied image: file/action bar, large waveform card, compact right controls, one transport row, and sample cards. Advanced extraction and semantic editing remain available but no longer dominate the first screen.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
  Outcome:
241
 
242
  The default UI is now aligned with the supplied image: file/action bar, large waveform card, compact right controls, one transport row, and sample cards. Advanced extraction and semantic editing remain available but no longer dominate the first screen.
243
+
244
+
245
+ ## Pass 8: fixed no-scroll workstation layout and upload repair
246
+
247
+ Completed in this pass:
248
+
249
+ 1. Reworked `web/index.html` into a fixed workstation shell: top bar, left sidebar, center workspace, right sidebar, and bottom dock.
250
+ 2. Locked the document viewport with `overflow: hidden` and moved long content into panel-local scroll containers.
251
+ 3. Moved pipeline logs/history/selection context into the left sidebar.
252
+ 4. Moved extraction, export, and advanced settings into the right sidebar.
253
+ 5. Moved semantic review/edit tools and raw tables into the bottom dock.
254
+ 6. Added an explicit top-bar `Upload audio` affordance.
255
+ 7. Added whole-window drag/drop handling so dropping a file anywhere on the app selects it instead of opening it in the browser.
256
+ 8. Added a full-screen drag overlay to make the drop target obvious.
257
+ 9. Preserved existing frontend ids and backend APIs so extraction, SSE, supervision, force-onset, and edited export remain wired.
258
+
259
+ Outcome:
260
+
261
+ The UI no longer behaves like a scrollable webpage. It now behaves like a compact desktop-style sample extraction workstation with simple expandable tool panels around a central waveform/sample workspace.
docs/REMAINING_WORK.md CHANGED
@@ -74,4 +74,4 @@ Highest-priority remaining work now:
74
 
75
  ## UI status note
76
 
77
- The reference-aligned default UI is complete for the current no-build frontend. The remaining UI work is interaction depth and engineering hardening, not matching the supplied visual direction: browser screenshot regression tests, TypeScript/Vite migration, edited-vs-original comparison, waveform zoom/pan, and richer cluster merge/split/relabel workflows.
 
74
 
75
  ## UI status note
76
 
77
+ The default UI is now a fixed, non-scrolling workstation layout with left/right sidebars, a bottom dock, explicit upload, and whole-app drag/drop. Remaining UI work is interaction depth and engineering hardening: browser screenshot regression tests, TypeScript/Vite migration, edited-vs-original comparison, waveform zoom/pan, and richer cluster merge/split/relabel workflows.
docs/TASKS.md CHANGED
@@ -94,3 +94,15 @@ Last updated: 2026-05-12
94
  - [x] `python3 -m py_compile app.py pipeline_runner.py sample_extractor.py supervised_state.py supervised_export.py scripts/*.py`
95
  - [x] `node --check web/app.js`
96
  - [x] `python3 scripts/test_api_job.py`
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  - [x] `python3 -m py_compile app.py pipeline_runner.py sample_extractor.py supervised_state.py supervised_export.py scripts/*.py`
95
  - [x] `node --check web/app.js`
96
  - [x] `python3 scripts/test_api_job.py`
97
+
98
+ ## Fixed workstation UI tasks
99
+
100
+ | Task | Status | Notes |
101
+ |---|---:|---|
102
+ | Remove document-level page scrolling | Done | `html, body` and `.shell` are viewport-locked; long content uses panel-local overflow. |
103
+ | Add left sidebar for secondary tools | Done | Source guidance, selection context, pipeline logs, and run history live in the left sidebar. |
104
+ | Add right sidebar for controls | Done | Core extraction, exports, and advanced model/DSP settings live in the right sidebar. |
105
+ | Add bottom bar for expandable editor panels | Done | Review/edit and raw tables live in `bottom-dock`. |
106
+ | Add explicit upload button | Done | Top bar now has a visible `Upload audio` control. |
107
+ | Make whole-app file dropping work | Done | Window-level drag/drop handlers select dropped files and prevent browser navigation. |
108
+ | Add drag overlay | Done | `globalDropOverlay` appears while dragging files over the app. |
docs/UI_REPLACEMENT.md CHANGED
@@ -41,15 +41,29 @@ This pass closed the visual fidelity gaps from the previous approximation:
41
 
42
  The UI is still not a pixel-for-pixel clone because it must remain functional across arbitrary audio files and preserve the project’s editing tools, but the default screen is now intentionally aligned with the reference composition.
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  ## UI structure
45
 
46
  | Area | Purpose |
47
  |---|---|
48
- | Top file bar | File identity/drop target and one primary purple `Extract Samples` action. |
49
- | Waveform workspace | Quiet canvas with grey waveform, colored lollipop markers, one custom transport row, and compact downloads. |
50
- | Core control card | Stem, sensitivity, cluster count, export samples, and collapsed advanced settings. |
51
- | Sample card grid | Primary representative-sample browsing and audition surface. |
52
- | Review & edit workbench | Collapsed secondary area containing pipeline logs, run history, supervision tools, and raw tables. |
53
 
54
  ## Frontend implementation
55
 
@@ -96,7 +110,7 @@ The active UI uses Server-Sent Events through `GET /api/jobs/{job_id}/events` fo
96
 
97
  ## Remaining UI improvements
98
 
99
- - Add waveform zoom/pan while keeping the default view uncluttered.
100
  - Add inline cluster merge/split/relabel workflows inside `Review & edit`.
101
  - Add A/B comparison between parameter runs.
102
  - Add downloadable timing report per job.
 
41
 
42
  The UI is still not a pixel-for-pixel clone because it must remain functional across arbitrary audio files and preserve the project’s editing tools, but the default screen is now intentionally aligned with the reference composition.
43
 
44
+
45
+ ## Pass 8 fixed workstation layout
46
+
47
+ This pass responds to the no-scroll workstation requirement and the missing-upload affordance:
48
+
49
+ - the document body is fixed to the viewport with `overflow: hidden`;
50
+ - the app uses a top bar, left sidebar, center workspace, right sidebar, and bottom dock;
51
+ - pipeline/history/selection tools moved into the left sidebar;
52
+ - extraction/export/advanced controls moved into the right sidebar;
53
+ - semantic review/edit tools and raw tables moved into the bottom bar;
54
+ - all long content now scrolls only inside its own panel;
55
+ - the upload affordance is now an explicit `Upload audio` button in the top bar;
56
+ - dropping a file anywhere on the app loads it and shows a full-screen drop overlay while dragging.
57
+
58
  ## UI structure
59
 
60
  | Area | Purpose |
61
  |---|---|
62
+ | Top bar | App identity, explicit upload button, selected-file metadata, backend status, and one primary purple `Extract Samples` action. |
63
+ | Left sidebar | Source/drop guidance, selected-hit/sample context, pipeline logs, and run history. |
64
+ | Center workspace | Quiet waveform canvas, one custom transport row, and representative sample cards. |
65
+ | Right sidebar | Stem, sensitivity, cluster count, exports, and collapsed advanced DSP/model controls. |
66
+ | Bottom dock | Review/edit semantic supervision tools and raw tables in expandable panels. |
67
 
68
  ## Frontend implementation
69
 
 
110
 
111
  ## Remaining UI improvements
112
 
113
+ - Add waveform zoom/pan while keeping the fixed workstation layout uncluttered.
114
  - Add inline cluster merge/split/relabel workflows inside `Review & edit`.
115
  - Add A/B comparison between parameter runs.
116
  - Add downloadable timing report per job.
web/app.js CHANGED
@@ -973,13 +973,67 @@ for (const id of ["sourcePreview", "stemAudio"]) {
973
  }
974
 
975
  const dropzone = $("dropzone");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
976
  for (const eventName of ["dragenter", "dragover"]) {
977
- dropzone.addEventListener(eventName, (event) => { event.preventDefault(); dropzone.classList.add("dragging"); });
 
 
 
 
 
 
978
  }
979
  for (const eventName of ["dragleave", "drop"]) {
980
- dropzone.addEventListener(eventName, (event) => { event.preventDefault(); dropzone.classList.remove("dragging"); });
 
 
 
 
 
981
  }
982
- dropzone.addEventListener("drop", (event) => setFile(event.dataTransfer.files?.[0] ?? null));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
983
  window.addEventListener("resize", () => {
984
  const current = window.__lastOverview;
985
  if (current) drawWaveform(current);
 
973
  }
974
 
975
  const dropzone = $("dropzone");
976
+ const globalDropOverlay = $("globalDropOverlay");
977
+ let pageDragDepth = 0;
978
+
979
+ function eventHasFiles(event) {
980
+ return Array.from(event.dataTransfer?.types ?? []).includes("Files");
981
+ }
982
+
983
+ function setDragActive(active) {
984
+ dropzone?.classList.toggle("dragging", Boolean(active));
985
+ globalDropOverlay?.classList.toggle("visible", Boolean(active));
986
+ }
987
+
988
+ function acceptDroppedFile(event) {
989
+ const file = event.dataTransfer?.files?.[0] ?? null;
990
+ if (file) setFile(file);
991
+ }
992
+
993
  for (const eventName of ["dragenter", "dragover"]) {
994
+ dropzone.addEventListener(eventName, (event) => {
995
+ if (!eventHasFiles(event)) return;
996
+ event.preventDefault();
997
+ event.stopPropagation();
998
+ event.dataTransfer.dropEffect = "copy";
999
+ setDragActive(true);
1000
+ });
1001
  }
1002
  for (const eventName of ["dragleave", "drop"]) {
1003
+ dropzone.addEventListener(eventName, (event) => {
1004
+ if (!eventHasFiles(event)) return;
1005
+ event.preventDefault();
1006
+ event.stopPropagation();
1007
+ setDragActive(false);
1008
+ });
1009
  }
1010
+ dropzone.addEventListener("drop", acceptDroppedFile);
1011
+
1012
+ window.addEventListener("dragenter", (event) => {
1013
+ if (!eventHasFiles(event)) return;
1014
+ event.preventDefault();
1015
+ pageDragDepth += 1;
1016
+ setDragActive(true);
1017
+ });
1018
+ window.addEventListener("dragover", (event) => {
1019
+ if (!eventHasFiles(event)) return;
1020
+ event.preventDefault();
1021
+ event.dataTransfer.dropEffect = "copy";
1022
+ setDragActive(true);
1023
+ });
1024
+ window.addEventListener("dragleave", (event) => {
1025
+ if (!eventHasFiles(event)) return;
1026
+ pageDragDepth = Math.max(0, pageDragDepth - 1);
1027
+ if (pageDragDepth === 0) setDragActive(false);
1028
+ });
1029
+ window.addEventListener("drop", (event) => {
1030
+ if (!eventHasFiles(event)) return;
1031
+ event.preventDefault();
1032
+ pageDragDepth = 0;
1033
+ setDragActive(false);
1034
+ acceptDroppedFile(event);
1035
+ });
1036
+ window.addEventListener("blur", () => { pageDragDepth = 0; setDragActive(false); });
1037
  window.addEventListener("resize", () => {
1038
  const current = window.__lastOverview;
1039
  if (current) drawWaveform(current);
web/index.html CHANGED
@@ -7,16 +7,32 @@
7
  <link rel="stylesheet" href="/web/styles.css" />
8
  </head>
9
  <body>
 
 
 
 
 
 
 
10
  <div class="shell">
11
  <header class="topbar">
12
- <label class="file-chip" id="dropzone" title="Drop audio here or click to browse">
13
- <input id="fileInput" type="file" accept="audio/*,.wav,.mp3,.flac,.aiff,.ogg,.m4a" />
14
  <span class="brand-mark" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i></span>
15
- <span class="file-copy">
16
- <strong id="dropTitle">Choose an audio file</strong>
17
- <small id="dropMeta">WAV, MP3, FLAC, AIFF, OGG, M4A</small>
 
 
 
 
 
 
 
 
 
18
  </span>
19
  </label>
 
20
  <div class="topbar-actions">
21
  <div class="backend-pill" aria-live="polite">
22
  <span class="status-dot" id="healthDot"></span>
@@ -26,56 +42,105 @@
26
  </div>
27
  </header>
28
 
29
- <main class="app-layout">
30
- <section class="wave-card panel" aria-label="Waveform overview">
31
- <div class="wave-status-row">
32
- <span id="jobPill" class="job-pill">idle</span>
33
- <p id="resultSummary" class="status-text">Load audio, tune sensitivity, extract clean drum hits.</p>
34
- </div>
35
- <canvas id="waveform" class="waveform" height="420"></canvas>
36
- <div class="transport-row" aria-label="Preview transport">
37
- <button id="transportPlayButton" class="round-play" type="button" aria-label="Play preview">▶</button>
38
- <span id="transportTime" class="transport-time">0:00 / 0:00</span>
39
- <input id="transportSeek" class="transport-seek" type="range" min="0" max="1000" value="0" step="1" aria-label="Seek preview" />
40
- </div>
41
- <div id="downloads" class="downloads compact-downloads"></div>
42
- <div class="hidden-audio-bank" aria-hidden="true">
43
- <audio id="sourcePreview"></audio>
44
- <audio id="stemAudio"></audio>
45
- <audio id="reconAudio"></audio>
46
- <audio id="hitAudio"></audio>
47
- <audio id="sampleAudio"></audio>
48
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  </section>
50
 
51
- <aside class="control-card panel" aria-label="Extraction controls">
52
- <div class="control-group">
53
- <label>Stem
54
- <select id="stem"></select>
55
- </label>
56
- </div>
 
 
57
 
58
- <div class="control-group sensitivity-group">
59
- <label for="onset_delta">Sensitivity</label>
60
- <input id="onset_delta" type="range" min="0.01" max="0.35" step="0.005" />
61
- <div class="range-caption"><span>Low</span><span>High</span></div>
62
- </div>
63
 
64
- <div class="control-group">
65
- <label>Cluster Count</label>
66
- <div class="stepper">
67
- <button id="clusterMinusButton" type="button" class="step-button" aria-label="Decrease cluster count">−</button>
68
- <input id="target_max" type="number" min="0" max="256" step="1" aria-label="Cluster count" />
69
- <button id="clusterPlusButton" type="button" class="step-button" aria-label="Increase cluster count">+</button>
 
70
  </div>
71
- </div>
72
 
73
- <div class="primary-actions-stack">
74
- <button id="exportStateButton" class="export-button" type="button" disabled> Export Samples</button>
75
- </div>
 
 
 
76
 
77
- <details class="advanced-controls">
78
- <summary>Advanced</summary>
79
  <div class="preset-row">
80
  <button id="usePreviewButton" class="ghost-button" type="button">Online preview mode</button>
81
  <button id="useFastButton" class="ghost-button" type="button">Fast full-mix mode</button>
@@ -155,54 +220,12 @@
155
  <button id="clearCacheButton" class="ghost-button full-width" type="button">Clear cache</button>
156
  </details>
157
  </aside>
 
158
 
159
- <section class="samples-section" aria-label="Extracted samples">
160
- <div class="section-heading">
161
- <h2>Extracted Samples <span id="sampleCountLabel">(0)</span></h2>
162
- </div>
163
- <div id="samplesGrid" class="sample-grid"></div>
164
- </section>
165
-
166
- <details class="review-workbench panel utility-panel">
167
- <summary>
168
- <span>Review & edit</span>
169
- <small>Pipeline progress, run history, detailed hit rows, semantic editing, force-onset, and edited export.</small>
170
- </summary>
171
-
172
- <section class="review-strip">
173
- <article class="review-card">
174
- <strong>Selected hit</strong>
175
- <span id="selectedHitMeta">Click an onset marker or hit row to audition the detected slice.</span>
176
- </article>
177
- <article class="review-card">
178
- <strong>Selected sample</strong>
179
- <span id="selectedSampleMeta">Click a sample card to hear the representative sample.</span>
180
- </article>
181
- </section>
182
-
183
- <details class="panel progress-panel utility-panel" open>
184
- <summary>
185
- <span>Pipeline</span>
186
- <small>Stage timings, logs, and streaming job progress</small>
187
- </summary>
188
- <div id="stageList" class="stage-list"></div>
189
- <pre id="logs" class="logs" aria-live="polite"></pre>
190
- </details>
191
-
192
- <details class="panel history-panel utility-panel">
193
- <summary>
194
- <span>Run history</span>
195
- <small>Completed manifests under .runs/</small>
196
- </summary>
197
- <button id="refreshHistoryButton" class="ghost-button" type="button">Refresh history</button>
198
- <div id="historyList" class="history-list"></div>
199
- </details>
200
-
201
- <details class="panel supervision-panel utility-panel">
202
- <summary>
203
- <span>Interactive supervision</span>
204
- <small>Move, suppress, restore, force-onset, explain, and export edited packs</small>
205
- </summary>
206
  <div class="supervision-header">
207
  <div>
208
  <h3>Semantic edit state</h3>
@@ -215,7 +238,6 @@
215
  </div>
216
  </div>
217
  <div id="supervisionSummary" class="state-summary">No interactive state loaded.</div>
218
- <div id="editedDownloads" class="downloads edited-downloads"></div>
219
  <div class="supervision-tools">
220
  <label>Target cluster
221
  <select id="targetClusterSelect"></select>
@@ -223,11 +245,11 @@
223
  <button id="moveHitButton" class="secondary-button" type="button" disabled>Move selected hit</button>
224
  <button id="pullHitButton" class="secondary-button" type="button" disabled>Pull into new cluster</button>
225
  <button id="acceptHitButton" class="secondary-button" type="button" disabled>Accept hit</button>
226
- <button id="favoriteHitButton" class="secondary-button" type="button" disabled>Favorite as representative</button>
227
- <button id="suppressHitButton" class="secondary-button danger-button" type="button" disabled>Suppress as bleed</button>
228
- <button id="restoreHitButton" class="secondary-button" type="button" disabled>Restore selected hit</button>
229
- <button id="lockClusterButton" class="secondary-button" type="button" disabled>Lock target cluster</button>
230
- <button id="explainClusterButton" class="secondary-button" type="button" disabled>Explain target cluster</button>
231
  </div>
232
  <div class="supervision-grid">
233
  <article>
@@ -248,13 +270,12 @@
248
  </article>
249
  </div>
250
  <pre id="clusterExplanation" class="explanation empty">Select a cluster and click Explain.</pre>
251
- </details>
 
252
 
253
- <details class="panel utility-panel data-panel">
254
- <summary>
255
- <span>Detailed tables</span>
256
- <small>Raw sample and detected-hit review rows</small>
257
- </summary>
258
  <div class="result-columns">
259
  <section>
260
  <h3>Representative samples</h3>
@@ -284,9 +305,9 @@
284
  </div>
285
  </section>
286
  </div>
287
- </details>
288
  </details>
289
- </main>
290
  </div>
291
  <script type="module" src="/web/app.js"></script>
292
  </body>
 
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
  <div class="shell">
18
  <header class="topbar">
19
+ <div class="app-title">
 
20
  <span class="brand-mark" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i></span>
21
+ <div>
22
+ <strong>Drum Sample Extractor</strong>
23
+ <small id="resultSummary">Load audio, tune sensitivity, extract clean drum hits.</small>
24
+ </div>
25
+ </div>
26
+
27
+ <label class="upload-chip" id="dropzone" title="Click to browse, or drop audio anywhere on the app">
28
+ <input id="fileInput" type="file" accept="audio/*,.wav,.mp3,.flac,.aiff,.ogg,.m4a" />
29
+ <span class="upload-button-face">Upload audio</span>
30
+ <span class="upload-copy">
31
+ <strong id="dropTitle">No file selected</strong>
32
+ <small id="dropMeta">Drop a file anywhere or click Upload audio</small>
33
  </span>
34
  </label>
35
+
36
  <div class="topbar-actions">
37
  <div class="backend-pill" aria-live="polite">
38
  <span class="status-dot" id="healthDot"></span>
 
42
  </div>
43
  </header>
44
 
45
+ <main class="workstation">
46
+ <aside class="sidebar left-sidebar" aria-label="Left tool sidebar">
47
+ <details class="tool-panel source-panel" open>
48
+ <summary><span>Source</span><small>Upload and status</small></summary>
49
+ <div class="drop-hint-card">
50
+ <strong>Upload button is in the top bar.</strong>
51
+ <span>Drag an audio file onto any part of the app to load it.</span>
52
+ </div>
53
+ <div class="job-status-card">
54
+ <span id="jobPill" class="job-pill">idle</span>
55
+ <small>Current extraction job</small>
56
+ </div>
57
+ </details>
58
+
59
+ <details class="tool-panel selection-panel" open>
60
+ <summary><span>Selection</span><small>Audition context</small></summary>
61
+ <article class="review-card">
62
+ <strong>Selected hit</strong>
63
+ <span id="selectedHitMeta">Click an onset marker or hit row to audition the detected slice.</span>
64
+ </article>
65
+ <article class="review-card">
66
+ <strong>Selected sample</strong>
67
+ <span id="selectedSampleMeta">Click a sample card to hear the representative sample.</span>
68
+ </article>
69
+ </details>
70
+
71
+ <details class="tool-panel progress-panel" open>
72
+ <summary><span>Pipeline</span><small>Stage timings and logs</small></summary>
73
+ <div id="stageList" class="stage-list"></div>
74
+ <pre id="logs" class="logs" aria-live="polite"></pre>
75
+ </details>
76
+
77
+ <details class="tool-panel history-panel">
78
+ <summary><span>Run history</span><small>Completed manifests</small></summary>
79
+ <button id="refreshHistoryButton" class="ghost-button full-width" type="button">Refresh history</button>
80
+ <div id="historyList" class="history-list"></div>
81
+ </details>
82
+ </aside>
83
+
84
+ <section class="center-workspace" aria-label="Waveform and extracted samples">
85
+ <section class="wave-card panel" aria-label="Waveform overview">
86
+ <canvas id="waveform" class="waveform" height="420"></canvas>
87
+ <div class="transport-row" aria-label="Preview transport">
88
+ <button id="transportPlayButton" class="round-play" type="button" aria-label="Play preview">▶</button>
89
+ <span id="transportTime" class="transport-time">0:00 / 0:00</span>
90
+ <input id="transportSeek" class="transport-seek" type="range" min="0" max="1000" value="0" step="1" aria-label="Seek preview" />
91
+ </div>
92
+ <div class="hidden-audio-bank" aria-hidden="true">
93
+ <audio id="sourcePreview"></audio>
94
+ <audio id="stemAudio"></audio>
95
+ <audio id="reconAudio"></audio>
96
+ <audio id="hitAudio"></audio>
97
+ <audio id="sampleAudio"></audio>
98
+ </div>
99
+ </section>
100
+
101
+ <section class="samples-section panel" aria-label="Extracted samples">
102
+ <div class="section-heading">
103
+ <h2>Extracted Samples <span id="sampleCountLabel">(0)</span></h2>
104
+ <small>Representative cards from the current run</small>
105
+ </div>
106
+ <div id="samplesGrid" class="sample-grid"></div>
107
+ </section>
108
  </section>
109
 
110
+ <aside class="sidebar right-sidebar" aria-label="Right tool sidebar">
111
+ <details class="tool-panel control-card" open>
112
+ <summary><span>Extract</span><small>Core controls</small></summary>
113
+ <div class="control-group">
114
+ <label>Stem
115
+ <select id="stem"></select>
116
+ </label>
117
+ </div>
118
 
119
+ <div class="control-group sensitivity-group">
120
+ <label for="onset_delta">Sensitivity</label>
121
+ <input id="onset_delta" type="range" min="0.01" max="0.35" step="0.005" />
122
+ <div class="range-caption"><span>Low</span><span>High</span></div>
123
+ </div>
124
 
125
+ <div class="control-group">
126
+ <label>Cluster Count</label>
127
+ <div class="stepper">
128
+ <button id="clusterMinusButton" type="button" class="step-button" aria-label="Decrease cluster count">−</button>
129
+ <input id="target_max" type="number" min="0" max="256" step="1" aria-label="Cluster count" />
130
+ <button id="clusterPlusButton" type="button" class="step-button" aria-label="Increase cluster count">+</button>
131
+ </div>
132
  </div>
133
+ </details>
134
 
135
+ <details class="tool-panel downloads-panel" open>
136
+ <summary><span>Exports</span><small>Current artifacts</small></summary>
137
+ <div id="downloads" class="downloads compact-downloads"></div>
138
+ <div id="editedDownloads" class="downloads edited-downloads"></div>
139
+ <button id="exportStateButton" class="export-button full-width" type="button" disabled>Export edited pack</button>
140
+ </details>
141
 
142
+ <details class="tool-panel advanced-controls">
143
+ <summary><span>Advanced</span><small>Model and DSP settings</small></summary>
144
  <div class="preset-row">
145
  <button id="usePreviewButton" class="ghost-button" type="button">Online preview mode</button>
146
  <button id="useFastButton" class="ghost-button" type="button">Fast full-mix mode</button>
 
220
  <button id="clearCacheButton" class="ghost-button full-width" type="button">Clear cache</button>
221
  </details>
222
  </aside>
223
+ </main>
224
 
225
+ <footer class="bottom-dock" aria-label="Bottom tool bar">
226
+ <details class="bottom-tool supervision-panel" open>
227
+ <summary><span>Review & edit</span><small>Move, suppress, restore, force-onset, explain, and export edited packs</small></summary>
228
+ <div class="bottom-tool-content">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  <div class="supervision-header">
230
  <div>
231
  <h3>Semantic edit state</h3>
 
238
  </div>
239
  </div>
240
  <div id="supervisionSummary" class="state-summary">No interactive state loaded.</div>
 
241
  <div class="supervision-tools">
242
  <label>Target cluster
243
  <select id="targetClusterSelect"></select>
 
245
  <button id="moveHitButton" class="secondary-button" type="button" disabled>Move selected hit</button>
246
  <button id="pullHitButton" class="secondary-button" type="button" disabled>Pull into new cluster</button>
247
  <button id="acceptHitButton" class="secondary-button" type="button" disabled>Accept hit</button>
248
+ <button id="favoriteHitButton" class="secondary-button" type="button" disabled>Favorite</button>
249
+ <button id="suppressHitButton" class="secondary-button danger-button" type="button" disabled>Suppress</button>
250
+ <button id="restoreHitButton" class="secondary-button" type="button" disabled>Restore</button>
251
+ <button id="lockClusterButton" class="secondary-button" type="button" disabled>Lock cluster</button>
252
+ <button id="explainClusterButton" class="secondary-button" type="button" disabled>Explain</button>
253
  </div>
254
  <div class="supervision-grid">
255
  <article>
 
270
  </article>
271
  </div>
272
  <pre id="clusterExplanation" class="explanation empty">Select a cluster and click Explain.</pre>
273
+ </div>
274
+ </details>
275
 
276
+ <details class="bottom-tool data-panel">
277
+ <summary><span>Tables</span><small>Raw sample and detected-hit rows</small></summary>
278
+ <div class="bottom-tool-content">
 
 
279
  <div class="result-columns">
280
  <section>
281
  <h3>Representative samples</h3>
 
305
  </div>
306
  </section>
307
  </div>
308
+ </div>
309
  </details>
310
+ </footer>
311
  </div>
312
  <script type="module" src="/web/app.js"></script>
313
  </body>
web/styles.css CHANGED
@@ -1,29 +1,33 @@
1
  :root {
2
  color-scheme: light;
3
- --bg: #f7f7f8;
4
  --surface: #ffffff;
5
  --surface-soft: #fbfbfc;
6
- --line: #e4e5e9;
7
- --line-strong: #d4d6dd;
8
- --muted: #8d929d;
9
- --text: #25262b;
10
- --text-soft: #4d5058;
11
  --accent: #6d45ec;
12
  --accent-strong: #552bd8;
13
- --accent-soft: #eee9ff;
14
- --good: #6cae48;
15
  --bad: #d85867;
16
  --warn: #ef9343;
17
- --shadow: 0 16px 42px rgba(18, 21, 30, .055);
18
  --radius-xl: 18px;
19
  --radius-lg: 13px;
 
 
20
  font-synthesis-weight: none;
21
  }
22
 
23
  * { box-sizing: border-box; }
24
  html, body {
 
 
25
  margin: 0;
26
- min-height: 100%;
27
  background: var(--bg);
28
  color: var(--text);
29
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", sans-serif;
@@ -33,45 +37,87 @@ button { cursor: pointer; }
33
  button:disabled { opacity: .45; cursor: not-allowed; }
34
  code { color: var(--accent-strong); }
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  .shell {
37
- width: min(100% - 74px, 1740px);
38
- margin: 0 auto;
39
- padding: 38px 0 48px;
 
 
 
 
 
40
  }
41
 
42
  .topbar {
43
- display: flex;
 
 
44
  align-items: center;
45
- justify-content: space-between;
46
- gap: 28px;
47
- margin-bottom: 34px;
48
  }
49
- .file-chip {
50
- position: relative;
51
  display: inline-flex;
52
  align-items: center;
53
- gap: 24px;
54
- min-width: min(620px, 100%);
55
- cursor: pointer;
56
- color: var(--text);
57
  }
58
- .file-chip input {
59
- position: absolute;
60
- inset: 0;
61
- opacity: 0;
62
- cursor: pointer;
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  }
64
- .file-chip.dragging .brand-mark,
65
- .file-chip:hover .brand-mark { transform: translateY(-1px); }
66
  .brand-mark {
67
- width: 48px;
68
- height: 48px;
69
  display: inline-flex;
 
70
  align-items: center;
71
  justify-content: center;
72
  gap: 4px;
73
  border-radius: 14px;
74
- transition: transform .18s ease;
 
 
75
  }
76
  .brand-mark i {
77
  width: 4px;
@@ -79,32 +125,76 @@ code { color: var(--accent-strong); }
79
  background: #202125;
80
  display: block;
81
  }
82
- .brand-mark i:nth-child(1) { height: 16px; }
83
- .brand-mark i:nth-child(2) { height: 28px; }
84
- .brand-mark i:nth-child(3) { height: 38px; }
85
- .brand-mark i:nth-child(4) { height: 24px; }
86
- .brand-mark i:nth-child(5) { height: 12px; }
87
- .file-copy strong {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  display: block;
89
- max-width: 760px;
90
  overflow: hidden;
91
  text-overflow: ellipsis;
92
  white-space: nowrap;
93
- font-size: 23px;
 
94
  line-height: 1.1;
95
- letter-spacing: -.035em;
96
- font-weight: 540;
97
  }
98
- .file-copy small {
99
  display: block;
100
  margin-top: 4px;
 
 
 
101
  color: var(--muted);
102
- font-size: 12px;
103
  }
104
  .topbar-actions {
105
  display: inline-flex;
106
  align-items: center;
107
- gap: 16px;
 
108
  }
109
  .backend-pill {
110
  display: inline-flex;
@@ -112,7 +202,8 @@ code { color: var(--accent-strong); }
112
  gap: 7px;
113
  color: var(--muted);
114
  font-size: 11px;
115
- opacity: .62;
 
116
  }
117
  .backend-pill strong { display: block; font-size: 11px; line-height: 1; }
118
  .backend-pill small { display: none; }
@@ -125,61 +216,119 @@ code { color: var(--accent-strong); }
125
  .status-dot.ok { background: var(--good); }
126
  .status-dot.bad { background: var(--bad); }
127
  .primary-button {
128
- min-width: 252px;
129
  border: 0;
130
- border-radius: 8px;
131
- padding: 19px 28px;
132
  background: linear-gradient(135deg, #7048f5, #552bd8);
133
  color: #fff;
134
- font-weight: 610;
135
- font-size: 20px;
136
  box-shadow: 0 14px 34px rgba(85, 43, 216, .20);
137
  transition: transform .16s ease, box-shadow .16s ease, opacity .16s ease;
138
  }
139
  .primary-button:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 18px 40px rgba(85, 43, 216, .24); }
140
- .primary-button span { margin-right: 9px; }
141
 
142
- .app-layout {
 
143
  display: grid;
144
- grid-template-columns: minmax(0, 1fr) 356px;
145
- grid-template-areas:
146
- "wave controls"
147
- "samples samples"
148
- "workbench workbench";
149
- gap: 28px;
150
- align-items: start;
151
- }
152
- .panel {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  border: 1px solid var(--line);
154
  border-radius: var(--radius-xl);
155
- background: rgba(255, 255, 255, .88);
156
  box-shadow: var(--shadow);
157
  }
158
- .wave-card {
159
- grid-area: wave;
160
- position: relative;
161
- overflow: hidden;
162
- min-height: 574px;
163
  }
164
- .control-card {
165
- grid-area: controls;
166
- padding: 31px 29px 28px;
167
- min-height: 574px;
 
 
 
 
 
 
 
168
  }
169
- .samples-section { grid-area: samples; margin-top: 16px; }
170
- .review-workbench { grid-area: workbench; }
171
-
172
- .wave-status-row {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  position: absolute;
174
- z-index: 2;
175
- top: 14px;
176
- left: 18px;
177
  right: 18px;
178
- display: flex;
179
- justify-content: space-between;
180
- align-items: flex-start;
181
- gap: 16px;
182
- pointer-events: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  }
184
  .job-pill {
185
  display: inline-flex;
@@ -193,50 +342,45 @@ code { color: var(--accent-strong); }
193
  font-size: 11px;
194
  backdrop-filter: blur(10px);
195
  }
196
- .status-text {
197
- margin: 0;
198
- max-width: 560px;
199
- color: rgba(77, 80, 88, .72);
200
- text-align: right;
201
- font-size: 12px;
202
- line-height: 1.3;
203
- background: rgba(255, 255, 255, .72);
204
- border: 1px solid rgba(228, 229, 233, .7);
205
- border-radius: 999px;
206
- padding: 6px 10px;
207
- backdrop-filter: blur(10px);
208
  }
209
  .waveform {
210
  display: block;
211
  width: 100%;
212
- height: 430px;
213
- min-height: 430px;
214
  background: linear-gradient(180deg, #fff, #fbfbfc);
215
  cursor: crosshair;
216
  }
217
  .transport-row {
218
  display: grid;
219
- grid-template-columns: 54px 112px minmax(0, 1fr);
220
- gap: 24px;
221
  align-items: center;
222
- padding: 0 29px 28px;
223
  }
224
  .round-play {
225
- width: 54px;
226
- height: 54px;
227
  display: grid;
228
  place-items: center;
229
  border: 1px solid var(--line);
230
  border-radius: 999px;
231
  background: #fff;
232
  color: #1f2024;
233
- font-size: 18px;
234
  line-height: 1;
235
  box-shadow: 0 2px 8px rgba(18, 21, 30, .035);
236
  }
237
  .transport-time {
238
  color: #6d717b;
239
- font-size: 18px;
240
  font-variant-numeric: tabular-nums;
241
  white-space: nowrap;
242
  }
@@ -270,46 +414,108 @@ code { color: var(--accent-strong); }
270
  }
271
  .transport-seek:hover::-moz-range-thumb { opacity: 1; }
272
  .hidden-audio-bank { display: none; }
273
- .downloads {
 
 
 
 
 
 
 
 
274
  display: flex;
275
- flex-wrap: wrap;
276
- gap: 9px;
 
 
277
  }
278
- .compact-downloads {
279
- padding: 0 29px 24px;
280
- margin-top: -10px;
281
- min-height: 30px;
 
 
 
 
 
 
 
282
  }
283
- .downloads:empty { display: none; }
284
- .downloads a, .table-wrap a {
285
- color: var(--accent-strong);
286
- text-decoration: none;
287
- font-weight: 680;
288
- background: var(--accent-soft);
289
- border: 1px solid rgba(109, 69, 236, .16);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  border-radius: 999px;
291
- padding: 7px 11px;
292
- font-size: 12px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  }
 
294
 
295
  label {
296
  display: block;
297
  color: var(--text-soft);
298
- font-size: 20px;
299
- font-weight: 500;
300
- letter-spacing: -.026em;
301
  }
302
- .control-group { margin-bottom: 36px; }
303
  input, select {
304
  width: 100%;
305
- margin-top: 13px;
306
  border: 1px solid var(--line);
307
- border-radius: 9px;
308
- padding: 15px 18px;
309
  color: var(--text);
310
  background: #fff;
311
  outline: none;
312
- font-size: 18px;
313
  }
314
  select { appearance: auto; }
315
  input:focus, select:focus {
@@ -326,16 +532,16 @@ input[type="range"] {
326
  }
327
  input[type="range"]::-webkit-slider-thumb {
328
  appearance: none;
329
- width: 26px;
330
- height: 26px;
331
  border-radius: 50%;
332
  background: var(--accent);
333
  border: 0;
334
  box-shadow: 0 6px 18px rgba(109, 69, 236, .26);
335
  }
336
  input[type="range"]::-moz-range-thumb {
337
- width: 26px;
338
- height: 26px;
339
  border-radius: 50%;
340
  background: var(--accent);
341
  border: 0;
@@ -344,294 +550,189 @@ input[type="range"]::-moz-range-thumb {
344
  .range-caption {
345
  display: flex;
346
  justify-content: space-between;
347
- margin-top: 14px;
348
  color: var(--muted);
349
- font-size: 16px;
350
  }
351
  .stepper {
352
  display: grid;
353
- grid-template-columns: 58px minmax(0, 1fr) 58px;
354
- margin-top: 13px;
355
- border: 1px solid var(--line);
356
- border-radius: 9px;
357
- overflow: hidden;
358
- background: #fff;
359
- }
360
- .stepper input {
361
- margin: 0;
362
- border: 0;
363
- border-left: 1px solid var(--line);
364
- border-right: 1px solid var(--line);
365
- border-radius: 0;
366
- text-align: center;
367
- font-size: 20px;
368
- padding: 14px 8px;
369
- }
370
- .step-button {
371
- border: 0;
372
- border-radius: 0;
373
- background: #fff;
374
- color: var(--text);
375
- font-size: 25px;
376
- line-height: 1;
377
  }
378
- .primary-actions-stack { display: grid; gap: 14px; margin-top: 42px; }
379
- .export-button, .secondary-button, .ghost-button {
 
 
 
 
380
  border: 1px solid var(--line-strong);
381
- border-radius: 9px;
382
  background: #fff;
383
  color: var(--text-soft);
384
- font-weight: 580;
385
- transition: transform .16s ease, box-shadow .16s ease, opacity .16s ease, border-color .16s ease, background .16s ease;
386
  }
 
 
 
387
  .export-button {
388
- min-height: 64px;
389
- padding: 16px 18px;
390
- font-size: 18px;
391
  }
392
- .ghost-button, .secondary-button { padding: 10px 13px; font-size: 14px; }
393
- button:hover:not(:disabled) { transform: translateY(-1px); }
394
- .ghost-button.active, .secondary-button.active {
395
- border-color: rgba(108, 174, 72, .55);
396
- background: rgba(108, 174, 72, .10);
397
- color: #3f702b;
 
 
398
  }
 
399
  .danger-button { color: var(--bad); }
400
- .advanced-controls {
401
- margin-top: 28px;
402
- border-top: 1px solid var(--line);
403
- padding-top: 17px;
404
- }
405
- .advanced-controls summary,
406
- .utility-panel summary {
407
- cursor: pointer;
408
- color: var(--text-soft);
409
- font-weight: 650;
410
- }
411
  .preset-row {
412
  display: grid;
413
  grid-template-columns: 1fr;
414
- gap: 10px;
415
- margin: 16px 0;
416
  }
417
  .control-grid {
418
  display: grid;
419
- grid-template-columns: repeat(2, minmax(0, 1fr));
420
- gap: 12px;
421
- }
422
- .compact-controls label {
423
- font-size: 12px;
424
- font-weight: 700;
425
- letter-spacing: .01em;
426
- }
427
- .compact-controls input, .compact-controls select {
428
- padding: 10px 11px;
429
- font-size: 13px;
430
- margin-top: 7px;
431
  }
432
  .toggles {
433
  display: grid;
434
- gap: 10px;
435
- margin: 16px 0 0;
436
  }
437
  .toggles label {
438
- display: flex;
 
439
  align-items: center;
440
  gap: 8px;
441
- font-size: 13px;
442
- font-weight: 630;
443
  }
444
  .toggles input { width: auto; margin: 0; }
445
- .full-width { width: 100%; margin-top: 16px; }
446
-
447
- .section-heading {
448
  display: flex;
449
- align-items: end;
450
- justify-content: space-between;
451
- gap: 20px;
452
- margin: 0 8px 20px;
453
- }
454
- h2 { margin: 0; font-size: 21px; letter-spacing: -.035em; font-weight: 540; }
455
- .sample-grid {
456
- display: grid;
457
- grid-template-columns: repeat(8, minmax(135px, 1fr));
458
- gap: 34px;
459
- }
460
- .sample-card {
461
- display: grid;
462
- grid-template-rows: 132px 72px;
463
- min-height: 204px;
464
- border: 1px solid var(--line);
465
- border-top: 4px solid var(--accent);
466
- border-radius: 9px;
467
- background: #fff;
468
- box-shadow: 0 9px 23px rgba(18, 21, 30, .035);
469
- overflow: hidden;
470
- text-align: left;
471
- padding: 0;
472
- }
473
- .sample-card:hover { box-shadow: 0 14px 34px rgba(18, 21, 30, .07); }
474
- .sample-card.selected { outline: 3px solid rgba(109, 69, 236, .14); }
475
- .sample-wave {
476
- width: 100%;
477
- height: 132px;
478
- display: block;
479
- background: #fff;
480
- }
481
- .sample-card-footer {
482
- display: grid;
483
- grid-template-columns: 44px minmax(0, 1fr);
484
- gap: 12px;
485
- align-items: center;
486
- padding: 13px 13px 15px;
487
- }
488
- .play-dot {
489
- width: 42px;
490
- height: 42px;
491
- display: grid;
492
- place-items: center;
493
- border-radius: 999px;
494
- background: #fff;
495
- border: 1px solid var(--line);
496
- color: var(--text);
497
- font-size: 13px;
498
- }
499
- .sample-name {
500
- display: block;
501
- overflow: hidden;
502
- text-overflow: ellipsis;
503
- white-space: nowrap;
504
- font-size: 17px;
505
- font-weight: 500;
506
- letter-spacing: -.02em;
507
- }
508
- .sample-meta {
509
- display: none;
510
- margin-top: 4px;
511
- color: var(--muted);
512
- font-size: 12px;
513
- font-weight: 500;
514
- }
515
- .empty { color: var(--muted); }
516
-
517
- .review-workbench {
518
- padding: 18px 20px;
519
  margin-top: 10px;
520
  }
521
- .utility-panel {
522
- padding: 18px 20px;
523
- }
524
- .utility-panel > summary,
525
- .review-workbench > summary {
526
- display: grid;
527
- grid-template-columns: minmax(0, 1fr);
528
- gap: 4px;
529
- list-style: none;
530
- }
531
- .utility-panel > summary::-webkit-details-marker,
532
- .review-workbench > summary::-webkit-details-marker { display: none; }
533
- .utility-panel > summary span,
534
- .review-workbench > summary span { font-size: 17px; color: var(--text); }
535
- .utility-panel > summary small,
536
- .review-workbench > summary small { color: var(--muted); font-weight: 500; }
537
- .review-workbench > * + * { margin-top: 18px; }
538
- .review-strip {
539
- display: grid;
540
- grid-template-columns: repeat(2, minmax(0, 1fr));
541
- gap: 18px;
542
- }
543
- .review-card {
544
- border: 1px solid var(--line);
545
- border-radius: var(--radius-lg);
546
- background: var(--surface-soft);
547
- padding: 16px;
548
  }
549
- .review-card strong, .review-card span { display: block; }
550
- .review-card span { color: var(--muted); font-size: 13px; margin-top: 5px; line-height: 1.4; }
551
 
552
  .stage-list {
553
  display: grid;
554
- grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
555
- gap: 10px;
556
- margin-top: 16px;
557
  }
558
  .stage {
559
  display: grid;
560
- grid-template-columns: 20px 1fr auto;
561
- gap: 10px;
562
  align-items: center;
563
- padding: 12px;
564
  border: 1px solid var(--line);
565
- border-radius: 14px;
566
  background: var(--surface-soft);
567
  }
568
- .stage .badge { width: 14px; height: 14px; border-radius: 999px; background: #d7d9e1; }
569
  .stage.running .badge { background: var(--accent); box-shadow: 0 0 0 5px rgba(109, 69, 236, .12); }
570
  .stage.done .badge { background: var(--good); }
571
  .stage.error .badge { background: var(--bad); }
572
- .stage strong { display: block; font-size: 13px; color: var(--text); }
573
- .stage small { display: block; color: var(--muted); margin-top: 2px; }
574
- .stage time { color: var(--text-soft); font-variant-numeric: tabular-nums; }
575
  .logs {
576
- min-height: 110px;
577
- max-height: 240px;
578
  overflow: auto;
579
  border: 1px solid var(--line);
580
- border-radius: 14px;
581
- padding: 14px;
582
- margin: 14px 0 0;
583
  background: #fbfbfc;
584
  color: #4f535d;
585
- font-size: 12px;
586
- line-height: 1.5;
587
  }
588
  .history-list, .compact-list {
589
  display: grid;
590
- gap: 10px;
591
- margin-top: 14px;
592
  }
593
  .history-row, .compact-row, .suggestion-row, .log-row {
594
  display: grid;
595
  grid-template-columns: minmax(0, 1fr) auto auto auto;
596
- gap: 12px;
597
  align-items: center;
598
  width: 100%;
599
  border: 1px solid var(--line);
600
- border-radius: 14px;
601
  background: #fff;
602
- padding: 12px;
603
  text-align: left;
604
  }
605
  .compact-row, .suggestion-row, .log-row { grid-template-columns: minmax(0, 1fr) auto; }
606
- .history-row strong, .compact-row strong, .suggestion-row strong, .log-row strong { display: block; color: var(--text); }
607
- .history-row small, .compact-row small, .suggestion-row small, .log-row small { display: block; color: var(--muted); margin-top: 3px; line-height: 1.35; }
608
  .compact-row.locked { border-color: rgba(109, 69, 236, .35); background: #fbf9ff; }
609
  .compact-row.suppressed, tr.suppressed { opacity: .55; }
610
- .row-actions { display: flex; flex-wrap: wrap; gap: 7px; justify-content: flex-end; }
611
  .mini-button {
612
- border: 1px solid var(--line-strong);
613
  border-radius: 999px;
614
- background: #fff;
615
- color: var(--text-soft);
616
- padding: 6px 10px;
617
- font-size: 12px;
618
- font-weight: 650;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
619
  }
620
  .supervision-header {
621
  display: flex;
622
  justify-content: space-between;
623
- gap: 20px;
624
  align-items: flex-start;
625
- margin-top: 16px;
626
  }
627
- .supervision-header h3, .result-columns h3 { margin: 0; }
628
- .subtle { color: var(--muted); font-size: 13px; line-height: 1.45; }
629
  .supervision-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
630
  .state-summary {
631
  display: flex;
632
  flex-wrap: wrap;
633
  gap: 8px;
634
- margin: 14px 0;
635
  }
636
  .state-summary span {
637
  border: 1px solid var(--line);
@@ -639,93 +740,120 @@ h2 { margin: 0; font-size: 21px; letter-spacing: -.035em; font-weight: 540; }
639
  background: var(--surface-soft);
640
  padding: 6px 10px;
641
  color: var(--text-soft);
642
- font-size: 12px;
643
  }
644
  .supervision-tools {
645
  display: grid;
646
- grid-template-columns: minmax(220px, 1fr) repeat(4, auto);
647
- gap: 10px;
648
  align-items: end;
649
- margin-top: 14px;
650
  }
651
- .supervision-tools label { font-size: 13px; font-weight: 700; }
652
  .supervision-grid {
653
  display: grid;
654
- grid-template-columns: repeat(2, minmax(0, 1fr));
655
- gap: 16px;
656
- margin-top: 18px;
657
  }
658
  .supervision-grid article {
 
659
  border: 1px solid var(--line);
660
- border-radius: 16px;
661
- padding: 14px;
662
  background: var(--surface-soft);
 
663
  }
664
- .supervision-grid h4 { margin: 0 0 10px; }
665
  .explanation {
666
- margin: 18px 0 0;
667
- max-height: 360px;
668
  overflow: auto;
669
  border: 1px solid var(--line);
670
- border-radius: 14px;
671
- padding: 14px;
672
  background: #fbfbfc;
673
- font-size: 12px;
674
  }
675
- .edited-downloads { padding: 0; }
676
-
677
  .result-columns {
678
  display: grid;
679
  grid-template-columns: minmax(0, 1fr);
680
- gap: 20px;
681
- margin-top: 16px;
682
  }
683
  .table-wrap {
684
  overflow: auto;
685
  border: 1px solid var(--line);
686
- border-radius: 16px;
687
- margin-top: 12px;
688
  background: #fff;
689
  }
690
- table { width: 100%; border-collapse: collapse; font-size: 13px; }
691
  th, td {
692
  text-align: left;
693
- padding: 10px 12px;
694
  border-bottom: 1px solid var(--line);
695
  white-space: nowrap;
696
  }
697
- th { color: var(--muted); font-weight: 700; background: var(--surface-soft); }
698
  tr.selected { background: #f6f2ff; }
699
  tr.low-confidence { background: #fff8ed; }
700
  tr:last-child td { border-bottom: 0; }
701
 
702
  @media (max-width: 1180px) {
703
- .shell { width: min(100% - 34px, 1740px); }
704
- .app-layout {
705
- grid-template-columns: 1fr;
706
- grid-template-areas:
707
- "wave"
708
- "controls"
709
- "samples"
710
- "workbench";
711
- }
712
- .control-card { min-height: auto; }
713
- .sample-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 18px; }
714
- }
715
- @media (max-width: 760px) {
716
- .shell { width: min(100% - 22px, 1740px); padding-top: 18px; }
717
- .topbar { align-items: stretch; flex-direction: column; gap: 16px; }
718
- .topbar-actions { justify-content: space-between; }
719
- .primary-button { width: 100%; min-width: 0; }
720
  .backend-pill { display: none; }
721
- .file-copy strong { font-size: 19px; }
722
- .waveform { height: 320px; min-height: 320px; }
723
- .wave-status-row { position: static; padding: 14px 14px 0; }
724
- .status-text { display: none; }
725
- .transport-row { grid-template-columns: 48px 92px 1fr; gap: 12px; padding: 0 18px 20px; }
726
- .round-play { width: 48px; height: 48px; }
727
- .transport-time { font-size: 14px; }
728
- .review-strip, .supervision-grid { grid-template-columns: 1fr; }
729
- .supervision-tools { grid-template-columns: 1fr; }
730
- .control-grid { grid-template-columns: 1fr; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
731
  }
 
 
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;
 
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
  .shell {
66
+ width: 100%;
67
+ height: 100vh;
68
+ min-height: 720px;
69
+ display: grid;
70
+ grid-template-rows: var(--topbar-h) minmax(0, 1fr) var(--bottom-h);
71
+ gap: 12px;
72
+ padding: 12px;
73
+ overflow: hidden;
74
  }
75
 
76
  .topbar {
77
+ min-height: 0;
78
+ display: grid;
79
+ grid-template-columns: minmax(250px, 1fr) minmax(360px, 560px) auto;
80
  align-items: center;
81
+ gap: 14px;
82
+ padding: 0 4px;
 
83
  }
84
+ .app-title {
85
+ min-width: 0;
86
  display: inline-flex;
87
  align-items: center;
88
+ gap: 14px;
 
 
 
89
  }
90
+ .app-title strong {
91
+ display: block;
92
+ overflow: hidden;
93
+ text-overflow: ellipsis;
94
+ white-space: nowrap;
95
+ font-size: 19px;
96
+ line-height: 1.05;
97
+ letter-spacing: -.035em;
98
+ font-weight: 620;
99
+ }
100
+ .app-title small {
101
+ display: block;
102
+ margin-top: 3px;
103
+ overflow: hidden;
104
+ text-overflow: ellipsis;
105
+ white-space: nowrap;
106
+ color: var(--muted);
107
+ font-size: 12px;
108
  }
 
 
109
  .brand-mark {
110
+ width: 44px;
111
+ height: 44px;
112
  display: inline-flex;
113
+ flex: 0 0 auto;
114
  align-items: center;
115
  justify-content: center;
116
  gap: 4px;
117
  border-radius: 14px;
118
+ background: #fff;
119
+ border: 1px solid var(--line);
120
+ box-shadow: 0 3px 10px rgba(18, 21, 30, .035);
121
  }
122
  .brand-mark i {
123
  width: 4px;
 
125
  background: #202125;
126
  display: block;
127
  }
128
+ .brand-mark i:nth-child(1) { height: 14px; }
129
+ .brand-mark i:nth-child(2) { height: 25px; }
130
+ .brand-mark i:nth-child(3) { height: 34px; }
131
+ .brand-mark i:nth-child(4) { height: 22px; }
132
+ .brand-mark i:nth-child(5) { height: 11px; }
133
+
134
+ .upload-chip {
135
+ position: relative;
136
+ min-width: 0;
137
+ height: 58px;
138
+ display: grid;
139
+ grid-template-columns: auto minmax(0, 1fr);
140
+ align-items: center;
141
+ gap: 12px;
142
+ padding: 7px 14px 7px 7px;
143
+ border: 1px solid var(--line);
144
+ border-radius: 16px;
145
+ background: rgba(255, 255, 255, .88);
146
+ box-shadow: 0 6px 18px rgba(18, 21, 30, .04);
147
+ cursor: pointer;
148
+ transition: border-color .16s ease, box-shadow .16s ease, transform .16s ease;
149
+ }
150
+ .upload-chip:hover,
151
+ .upload-chip.dragging {
152
+ border-color: rgba(109, 69, 236, .45);
153
+ box-shadow: 0 10px 26px rgba(109, 69, 236, .10);
154
+ transform: translateY(-1px);
155
+ }
156
+ .upload-chip input {
157
+ position: absolute;
158
+ inset: 0;
159
+ opacity: 0;
160
+ cursor: pointer;
161
+ }
162
+ .upload-button-face {
163
+ display: inline-grid;
164
+ place-items: center;
165
+ height: 42px;
166
+ padding: 0 16px;
167
+ border-radius: 11px;
168
+ background: #1f2025;
169
+ color: #fff;
170
+ font-size: 13px;
171
+ font-weight: 760;
172
+ white-space: nowrap;
173
+ }
174
+ .upload-copy { min-width: 0; }
175
+ .upload-copy strong {
176
  display: block;
 
177
  overflow: hidden;
178
  text-overflow: ellipsis;
179
  white-space: nowrap;
180
+ color: var(--text);
181
+ font-size: 13px;
182
  line-height: 1.1;
 
 
183
  }
184
+ .upload-copy small {
185
  display: block;
186
  margin-top: 4px;
187
+ overflow: hidden;
188
+ text-overflow: ellipsis;
189
+ white-space: nowrap;
190
  color: var(--muted);
191
+ font-size: 11px;
192
  }
193
  .topbar-actions {
194
  display: inline-flex;
195
  align-items: center;
196
+ justify-content: end;
197
+ gap: 12px;
198
  }
199
  .backend-pill {
200
  display: inline-flex;
 
202
  gap: 7px;
203
  color: var(--muted);
204
  font-size: 11px;
205
+ opacity: .72;
206
+ white-space: nowrap;
207
  }
208
  .backend-pill strong { display: block; font-size: 11px; line-height: 1; }
209
  .backend-pill small { display: none; }
 
216
  .status-dot.ok { background: var(--good); }
217
  .status-dot.bad { background: var(--bad); }
218
  .primary-button {
219
+ min-width: 192px;
220
  border: 0;
221
+ border-radius: 12px;
222
+ padding: 16px 22px;
223
  background: linear-gradient(135deg, #7048f5, #552bd8);
224
  color: #fff;
225
+ font-weight: 700;
226
+ font-size: 16px;
227
  box-shadow: 0 14px 34px rgba(85, 43, 216, .20);
228
  transition: transform .16s ease, box-shadow .16s ease, opacity .16s ease;
229
  }
230
  .primary-button:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 18px 40px rgba(85, 43, 216, .24); }
231
+ .primary-button span { margin-right: 8px; }
232
 
233
+ .workstation {
234
+ min-height: 0;
235
  display: grid;
236
+ grid-template-columns: 292px minmax(0, 1fr) 332px;
237
+ gap: 12px;
238
+ overflow: hidden;
239
+ }
240
+ .sidebar {
241
+ min-height: 0;
242
+ display: flex;
243
+ flex-direction: column;
244
+ gap: 10px;
245
+ overflow: auto;
246
+ scrollbar-width: thin;
247
+ }
248
+ .center-workspace {
249
+ min-width: 0;
250
+ min-height: 0;
251
+ display: grid;
252
+ grid-template-rows: minmax(270px, 1fr) minmax(172px, 220px);
253
+ gap: 12px;
254
+ overflow: hidden;
255
+ }
256
+ .panel,
257
+ .tool-panel,
258
+ .bottom-tool {
259
  border: 1px solid var(--line);
260
  border-radius: var(--radius-xl);
261
+ background: rgba(255, 255, 255, .90);
262
  box-shadow: var(--shadow);
263
  }
264
+ .tool-panel {
265
+ flex: 0 0 auto;
266
+ padding: 12px;
 
 
267
  }
268
+ .tool-panel[open] { padding-bottom: 14px; }
269
+ .tool-panel > summary,
270
+ .bottom-tool > summary {
271
+ min-height: 38px;
272
+ display: grid;
273
+ grid-template-columns: minmax(0, 1fr);
274
+ align-items: center;
275
+ gap: 2px;
276
+ list-style: none;
277
+ cursor: pointer;
278
+ user-select: none;
279
  }
280
+ .tool-panel > summary::-webkit-details-marker,
281
+ .bottom-tool > summary::-webkit-details-marker { display: none; }
282
+ .tool-panel > summary span,
283
+ .bottom-tool > summary span {
284
+ color: var(--text);
285
+ font-size: 14px;
286
+ font-weight: 760;
287
+ letter-spacing: -.02em;
288
+ }
289
+ .tool-panel > summary small,
290
+ .bottom-tool > summary small {
291
+ overflow: hidden;
292
+ text-overflow: ellipsis;
293
+ white-space: nowrap;
294
+ color: var(--muted);
295
+ font-size: 11px;
296
+ font-weight: 520;
297
+ }
298
+ .tool-panel > summary::after,
299
+ .bottom-tool > summary::after {
300
+ content: "+";
301
  position: absolute;
 
 
 
302
  right: 18px;
303
+ color: var(--muted);
304
+ font-weight: 760;
305
+ }
306
+ .tool-panel,
307
+ .bottom-tool { position: relative; }
308
+ .tool-panel[open] > summary::after,
309
+ .bottom-tool[open] > summary::after { content: "–"; }
310
+
311
+ .drop-hint-card,
312
+ .job-status-card,
313
+ .review-card {
314
+ margin-top: 10px;
315
+ border: 1px solid var(--line);
316
+ border-radius: var(--radius-lg);
317
+ background: var(--surface-soft);
318
+ padding: 12px;
319
+ }
320
+ .drop-hint-card strong,
321
+ .drop-hint-card span,
322
+ .review-card strong,
323
+ .review-card span,
324
+ .job-status-card small { display: block; }
325
+ .drop-hint-card span,
326
+ .review-card span,
327
+ .job-status-card small {
328
+ margin-top: 4px;
329
+ color: var(--muted);
330
+ font-size: 12px;
331
+ line-height: 1.35;
332
  }
333
  .job-pill {
334
  display: inline-flex;
 
342
  font-size: 11px;
343
  backdrop-filter: blur(10px);
344
  }
345
+
346
+ .wave-card {
347
+ position: relative;
348
+ min-height: 0;
349
+ overflow: hidden;
350
+ display: grid;
351
+ grid-template-rows: minmax(0, 1fr) 72px;
 
 
 
 
 
352
  }
353
  .waveform {
354
  display: block;
355
  width: 100%;
356
+ height: 100%;
357
+ min-height: 0;
358
  background: linear-gradient(180deg, #fff, #fbfbfc);
359
  cursor: crosshair;
360
  }
361
  .transport-row {
362
  display: grid;
363
+ grid-template-columns: 48px 100px minmax(0, 1fr);
364
+ gap: 18px;
365
  align-items: center;
366
+ padding: 10px 22px 18px;
367
  }
368
  .round-play {
369
+ width: 48px;
370
+ height: 48px;
371
  display: grid;
372
  place-items: center;
373
  border: 1px solid var(--line);
374
  border-radius: 999px;
375
  background: #fff;
376
  color: #1f2024;
377
+ font-size: 16px;
378
  line-height: 1;
379
  box-shadow: 0 2px 8px rgba(18, 21, 30, .035);
380
  }
381
  .transport-time {
382
  color: #6d717b;
383
+ font-size: 15px;
384
  font-variant-numeric: tabular-nums;
385
  white-space: nowrap;
386
  }
 
414
  }
415
  .transport-seek:hover::-moz-range-thumb { opacity: 1; }
416
  .hidden-audio-bank { display: none; }
417
+
418
+ .samples-section {
419
+ min-height: 0;
420
+ overflow: hidden;
421
+ padding: 14px;
422
+ display: grid;
423
+ grid-template-rows: auto minmax(0, 1fr);
424
+ }
425
+ .section-heading {
426
  display: flex;
427
+ align-items: end;
428
+ justify-content: space-between;
429
+ gap: 20px;
430
+ margin-bottom: 12px;
431
  }
432
+ h2 { margin: 0; font-size: 17px; letter-spacing: -.035em; font-weight: 620; }
433
+ .section-heading small { color: var(--muted); font-size: 12px; }
434
+ .sample-grid {
435
+ min-height: 0;
436
+ display: grid;
437
+ grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
438
+ grid-auto-rows: 146px;
439
+ gap: 12px;
440
+ overflow: auto;
441
+ padding-right: 4px;
442
+ scrollbar-width: thin;
443
  }
444
+ .sample-card {
445
+ display: grid;
446
+ grid-template-rows: minmax(0, 1fr) 54px;
447
+ min-height: 0;
448
+ border: 1px solid var(--line);
449
+ border-top: 4px solid var(--accent);
450
+ border-radius: 10px;
451
+ background: #fff;
452
+ box-shadow: 0 8px 20px rgba(18, 21, 30, .035);
453
+ overflow: hidden;
454
+ text-align: left;
455
+ padding: 0;
456
+ }
457
+ .sample-card:hover { box-shadow: 0 12px 28px rgba(18, 21, 30, .07); }
458
+ .sample-card.selected { outline: 3px solid rgba(109, 69, 236, .14); }
459
+ .sample-wave {
460
+ width: 100%;
461
+ height: 100%;
462
+ display: block;
463
+ background: #fff;
464
+ }
465
+ .sample-card-footer {
466
+ display: grid;
467
+ grid-template-columns: 34px minmax(0, 1fr);
468
+ gap: 9px;
469
+ align-items: center;
470
+ padding: 9px 10px;
471
+ }
472
+ .play-dot {
473
+ width: 32px;
474
+ height: 32px;
475
+ display: grid;
476
+ place-items: center;
477
  border-radius: 999px;
478
+ background: #fff;
479
+ border: 1px solid var(--line);
480
+ color: var(--text);
481
+ font-size: 11px;
482
+ }
483
+ .sample-name {
484
+ display: block;
485
+ overflow: hidden;
486
+ text-overflow: ellipsis;
487
+ white-space: nowrap;
488
+ font-size: 13px;
489
+ font-weight: 650;
490
+ letter-spacing: -.02em;
491
+ }
492
+ .sample-meta {
493
+ display: none;
494
+ margin-top: 3px;
495
+ color: var(--muted);
496
+ font-size: 11px;
497
+ font-weight: 500;
498
  }
499
+ .empty { color: var(--muted); }
500
 
501
  label {
502
  display: block;
503
  color: var(--text-soft);
504
+ font-size: 13px;
505
+ font-weight: 700;
506
+ letter-spacing: -.01em;
507
  }
508
+ .control-group { margin-top: 12px; }
509
  input, select {
510
  width: 100%;
511
+ margin-top: 8px;
512
  border: 1px solid var(--line);
513
+ border-radius: 10px;
514
+ padding: 10px 11px;
515
  color: var(--text);
516
  background: #fff;
517
  outline: none;
518
+ font-size: 13px;
519
  }
520
  select { appearance: auto; }
521
  input:focus, select:focus {
 
532
  }
533
  input[type="range"]::-webkit-slider-thumb {
534
  appearance: none;
535
+ width: 20px;
536
+ height: 20px;
537
  border-radius: 50%;
538
  background: var(--accent);
539
  border: 0;
540
  box-shadow: 0 6px 18px rgba(109, 69, 236, .26);
541
  }
542
  input[type="range"]::-moz-range-thumb {
543
+ width: 20px;
544
+ height: 20px;
545
  border-radius: 50%;
546
  background: var(--accent);
547
  border: 0;
 
550
  .range-caption {
551
  display: flex;
552
  justify-content: space-between;
553
+ margin-top: 7px;
554
  color: var(--muted);
555
+ font-size: 11px;
556
  }
557
  .stepper {
558
  display: grid;
559
+ grid-template-columns: 40px minmax(0, 1fr) 40px;
560
+ gap: 8px;
561
+ align-items: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
562
  }
563
+ .stepper input { text-align: center; }
564
+ .step-button,
565
+ .ghost-button,
566
+ .secondary-button,
567
+ .export-button,
568
+ .mini-button {
569
  border: 1px solid var(--line-strong);
570
+ border-radius: 10px;
571
  background: #fff;
572
  color: var(--text-soft);
573
+ font-weight: 700;
 
574
  }
575
+ .step-button { height: 38px; margin-top: 8px; font-size: 18px; }
576
+ .ghost-button,
577
+ .secondary-button,
578
  .export-button {
579
+ padding: 9px 11px;
580
+ font-size: 12px;
 
581
  }
582
+ .ghost-button:hover:not(:disabled),
583
+ .secondary-button:hover:not(:disabled),
584
+ .step-button:hover:not(:disabled) { border-color: rgba(109, 69, 236, .34); color: var(--accent-strong); }
585
+ .export-button {
586
+ margin-top: 10px;
587
+ border: 0;
588
+ background: #1f2025;
589
+ color: #fff;
590
  }
591
+ .full-width { width: 100%; }
592
  .danger-button { color: var(--bad); }
 
 
 
 
 
 
 
 
 
 
 
593
  .preset-row {
594
  display: grid;
595
  grid-template-columns: 1fr;
596
+ gap: 8px;
597
+ margin-top: 10px;
598
  }
599
  .control-grid {
600
  display: grid;
601
+ grid-template-columns: 1fr;
602
+ gap: 9px;
603
+ margin-top: 10px;
 
 
 
 
 
 
 
 
 
604
  }
605
  .toggles {
606
  display: grid;
607
+ gap: 8px;
608
+ margin: 12px 0;
609
  }
610
  .toggles label {
611
+ display: grid;
612
+ grid-template-columns: 16px minmax(0, 1fr);
613
  align-items: center;
614
  gap: 8px;
615
+ font-size: 12px;
616
+ color: var(--text-soft);
617
  }
618
  .toggles input { width: auto; margin: 0; }
619
+ .downloads {
 
 
620
  display: flex;
621
+ flex-wrap: wrap;
622
+ gap: 8px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
623
  margin-top: 10px;
624
  }
625
+ .downloads:empty { display: none; }
626
+ .downloads a, .table-wrap a {
627
+ color: var(--accent-strong);
628
+ text-decoration: none;
629
+ font-weight: 760;
630
+ background: var(--accent-soft);
631
+ border: 1px solid rgba(109, 69, 236, .16);
632
+ border-radius: 999px;
633
+ padding: 7px 10px;
634
+ font-size: 11px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
635
  }
 
 
636
 
637
  .stage-list {
638
  display: grid;
639
+ gap: 8px;
640
+ margin-top: 10px;
 
641
  }
642
  .stage {
643
  display: grid;
644
+ grid-template-columns: 14px minmax(0, 1fr) auto;
645
+ gap: 8px;
646
  align-items: center;
647
+ padding: 9px;
648
  border: 1px solid var(--line);
649
+ border-radius: 12px;
650
  background: var(--surface-soft);
651
  }
652
+ .stage .badge { width: 10px; height: 10px; border-radius: 999px; background: #d7d9e1; }
653
  .stage.running .badge { background: var(--accent); box-shadow: 0 0 0 5px rgba(109, 69, 236, .12); }
654
  .stage.done .badge { background: var(--good); }
655
  .stage.error .badge { background: var(--bad); }
656
+ .stage strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--text); }
657
+ .stage small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); margin-top: 2px; font-size: 11px; }
658
+ .stage time { color: var(--text-soft); font-size: 11px; font-variant-numeric: tabular-nums; }
659
  .logs {
660
+ min-height: 88px;
661
+ max-height: 156px;
662
  overflow: auto;
663
  border: 1px solid var(--line);
664
+ border-radius: 12px;
665
+ padding: 10px;
666
+ margin: 10px 0 0;
667
  background: #fbfbfc;
668
  color: #4f535d;
669
+ font-size: 11px;
670
+ line-height: 1.45;
671
  }
672
  .history-list, .compact-list {
673
  display: grid;
674
+ gap: 8px;
675
+ margin-top: 10px;
676
  }
677
  .history-row, .compact-row, .suggestion-row, .log-row {
678
  display: grid;
679
  grid-template-columns: minmax(0, 1fr) auto auto auto;
680
+ gap: 8px;
681
  align-items: center;
682
  width: 100%;
683
  border: 1px solid var(--line);
684
+ border-radius: 12px;
685
  background: #fff;
686
+ padding: 10px;
687
  text-align: left;
688
  }
689
  .compact-row, .suggestion-row, .log-row { grid-template-columns: minmax(0, 1fr) auto; }
690
+ .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; }
691
+ .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; }
692
  .compact-row.locked { border-color: rgba(109, 69, 236, .35); background: #fbf9ff; }
693
  .compact-row.suppressed, tr.suppressed { opacity: .55; }
694
+ .row-actions { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }
695
  .mini-button {
 
696
  border-radius: 999px;
697
+ padding: 5px 8px;
698
+ font-size: 11px;
699
+ }
700
+
701
+ .bottom-dock {
702
+ min-height: 0;
703
+ display: grid;
704
+ grid-template-columns: minmax(0, 1.35fr) minmax(360px, .85fr);
705
+ gap: 12px;
706
+ overflow: hidden;
707
+ }
708
+ .bottom-tool {
709
+ min-height: 0;
710
+ overflow: hidden;
711
+ padding: 12px;
712
+ display: grid;
713
+ grid-template-rows: auto minmax(0, 1fr);
714
+ }
715
+ .bottom-tool:not([open]) { display: block; }
716
+ .bottom-tool-content {
717
+ min-height: 0;
718
+ overflow: auto;
719
+ padding-top: 8px;
720
+ scrollbar-width: thin;
721
  }
722
  .supervision-header {
723
  display: flex;
724
  justify-content: space-between;
725
+ gap: 14px;
726
  align-items: flex-start;
 
727
  }
728
+ .supervision-header h3, .result-columns h3 { margin: 0; font-size: 14px; }
729
+ .subtle { color: var(--muted); font-size: 12px; line-height: 1.4; margin: 4px 0 0; }
730
  .supervision-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
731
  .state-summary {
732
  display: flex;
733
  flex-wrap: wrap;
734
  gap: 8px;
735
+ margin: 10px 0;
736
  }
737
  .state-summary span {
738
  border: 1px solid var(--line);
 
740
  background: var(--surface-soft);
741
  padding: 6px 10px;
742
  color: var(--text-soft);
743
+ font-size: 11px;
744
  }
745
  .supervision-tools {
746
  display: grid;
747
+ grid-template-columns: minmax(180px, 1fr) repeat(4, auto);
748
+ gap: 8px;
749
  align-items: end;
750
+ margin-top: 10px;
751
  }
 
752
  .supervision-grid {
753
  display: grid;
754
+ grid-template-columns: repeat(4, minmax(210px, 1fr));
755
+ gap: 10px;
756
+ margin-top: 12px;
757
  }
758
  .supervision-grid article {
759
+ min-height: 120px;
760
  border: 1px solid var(--line);
761
+ border-radius: 14px;
762
+ padding: 10px;
763
  background: var(--surface-soft);
764
+ overflow: auto;
765
  }
766
+ .supervision-grid h4 { margin: 0 0 8px; font-size: 12px; }
767
  .explanation {
768
+ margin: 12px 0 0;
769
+ max-height: 160px;
770
  overflow: auto;
771
  border: 1px solid var(--line);
772
+ border-radius: 12px;
773
+ padding: 10px;
774
  background: #fbfbfc;
775
+ font-size: 11px;
776
  }
 
 
777
  .result-columns {
778
  display: grid;
779
  grid-template-columns: minmax(0, 1fr);
780
+ gap: 12px;
 
781
  }
782
  .table-wrap {
783
  overflow: auto;
784
  border: 1px solid var(--line);
785
+ border-radius: 12px;
786
+ margin-top: 8px;
787
  background: #fff;
788
  }
789
+ table { width: 100%; border-collapse: collapse; font-size: 12px; }
790
  th, td {
791
  text-align: left;
792
+ padding: 8px 10px;
793
  border-bottom: 1px solid var(--line);
794
  white-space: nowrap;
795
  }
796
+ th { color: var(--muted); font-weight: 760; background: var(--surface-soft); }
797
  tr.selected { background: #f6f2ff; }
798
  tr.low-confidence { background: #fff8ed; }
799
  tr:last-child td { border-bottom: 0; }
800
 
801
  @media (max-width: 1180px) {
802
+ :root { --bottom-h: 244px; }
803
+ .shell { min-height: 650px; }
804
+ .topbar { grid-template-columns: minmax(180px, 1fr) minmax(260px, 420px) auto; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
805
  .backend-pill { display: none; }
806
+ .workstation { grid-template-columns: 248px minmax(0, 1fr) 286px; }
807
+ .supervision-grid { grid-template-columns: repeat(2, minmax(210px, 1fr)); }
808
+ }
809
+ @media (max-width: 900px) {
810
+ :root { --topbar-h: 134px; --bottom-h: 250px; }
811
+ .shell { padding: 10px; }
812
+ .topbar { grid-template-columns: 1fr; gap: 8px; }
813
+ .topbar-actions { justify-content: stretch; }
814
+ .primary-button { flex: 1; }
815
+ .workstation { grid-template-columns: 1fr; grid-template-rows: 0 minmax(0, 1fr) 0; }
816
+ .left-sidebar, .right-sidebar { display: none; }
817
+ .center-workspace { grid-template-rows: minmax(250px, 1fr) 170px; }
818
+ .bottom-dock { grid-template-columns: 1fr; }
819
+ .data-panel { display: none; }
820
+ }
821
+
822
+ /* Final control-specific overrides after the generic form styles. */
823
+ .upload-chip input {
824
+ margin: 0;
825
+ padding: 0;
826
+ border: 0;
827
+ border-radius: inherit;
828
+ background: transparent;
829
+ }
830
+ .transport-row .transport-seek {
831
+ appearance: none;
832
+ height: 2px;
833
+ margin: 0;
834
+ padding: 0;
835
+ border: 0;
836
+ border-radius: 999px;
837
+ background: #d9dbe2;
838
+ }
839
+ .transport-row .transport-seek::-webkit-slider-thumb {
840
+ appearance: none;
841
+ width: 14px;
842
+ height: 14px;
843
+ border-radius: 50%;
844
+ background: var(--accent);
845
+ border: 0;
846
+ opacity: 0;
847
+ box-shadow: none;
848
+ }
849
+ .transport-row .transport-seek:hover::-webkit-slider-thumb { opacity: 1; }
850
+ .transport-row .transport-seek::-moz-range-thumb {
851
+ width: 14px;
852
+ height: 14px;
853
+ border-radius: 50%;
854
+ background: var(--accent);
855
+ border: 0;
856
+ opacity: 0;
857
+ box-shadow: none;
858
  }
859
+ .transport-row .transport-seek:hover::-moz-range-thumb { opacity: 1; }