Files
Tha.Les 5b251ccc3b Windows portable app: dual CPU/NVIDIA builds, DAW UI improvements (#5)
* Add Windows portable launcher scaffold

* readme

* readme  update

* fix

* star thistory theme changed

* feat: responsive layout, parallel setup flow, and window constraints

- Enforce 1440×900 minimum window size in tauri.conf.json
- Rewrite setup.js: parallel workspace+gpu phase, minDelay() for
  guaranteed state visibility, IIFE chains, error cleanup on failure
- Add setup.css step indicators (pending/active/done/error) with
  gold spinner, green checkmark, red X
- Fix stems-panel overflow into transport footer (align-self + height)
- Make transport, appbar, and wave editor fully responsive with
  clamp() and fr-based grid columns
- Remove dead .stem-list span.hidden rule (covered by base.css)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: ignore data/ directory (runtime-generated)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: untrack .docs directory from git

* Add Windows portable release workflow

* fix(tests): rewrite yoda conditions in test_config.py

* ci: set UV_LINK_MODE=copy to suppress hardlink warning

* fix: security hardening, reliability fixes, and observability improvements

- Enforce MAX_PENDING_JOBS cap on job submissions (503 when queue full)
- Add done_callback to pipeline task to log any unhandled exceptions
- Add job_id regex validation to DELETE endpoint
- Sanitize pipeline error messages sent to clients (full detail stays server-side)
- Move sweep_old_jobs to hourly background task via lifespan (not per-submission)
- Add demucs stall watchdog: terminate if no stderr output for 30min
- Add SSE connection max lifetime (4h) to prevent zombie connections
- Replace shutil.rmtree(ignore_errors=True) with logged _rmtree helper
- Fix log levels: chroma/key diagnostics downgraded from WARNING to DEBUG
- Add bounds clamping for MAX_DURATION_SEC, JOB_TTL_SECONDS, MAX_PENDING_JOBS
- Remove filesystem paths from /health endpoint response
- Replace innerHTML with safe DOM construction for BPM and confidence in JS

* fix(events): use get_running_loop() instead of deprecated get_event_loop()

* fix(tests): update assertions to match sanitized error and health response

* star history

* feat(desktop): Windows portable app — dual CPU/NVIDIA builds, external links, transport colors

- Move frontend to desktop/ui/ and fix frontendDist to point there (fixes Tauri build)
- Dual portable zip variants: StemDeck-Windows-x64 (CPU) and StemDeck-Windows-x64.NVIDIA
- Strip torch .lib static libraries in StripVenv (-623 MB dnnl.lib alone)
- Force-reinstall CPU torch after main pip install to prevent CUDA wheel override
- Sentinel file data/cpu-only short-circuits GPU detection in ensure_torch_device()
- Add open_url Tauri command + JS click interceptor for Help/Tip external links
- Rename GPU setup step to "Configuring compute device" (accurate for both variants)
- Play button turns green when active, stop button turns red when pressed
- Update CI pipeline for dual Windows variants with manual branch trigger support
- Update README with Windows desktop app section and download variant guidance

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix mixer volume updates getting stuck

Clamp per-track mixer output to the browser-safe 0..1 range and isolate volume write failures so one hot fader cannot leave other channels stale. Keep the existing master-volume fallback when no master fader is present.

* fix(ui): DAW view — tracks fill window height, icons aligned per row

- stem-waveform-layer: top: 0 (waves-column already starts below ruler,
  previous top: 72px pushed waveforms 72px too far down)
- stems-panel: align-self stretch + margin-bottom 6px to match
  waves-column height exactly, keeping icon rows in sync with waveform rows
- ResizeObserver on waveScroll recalculates --wave-playhead-h and
  multitrack pxPerSec on every container resize
- Commit desktop/package-lock.json for reproducible npm ci in CI pipeline

* image:local added for windows builds

---------

Co-authored-by: Thales <>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 17:06:53 +01:00

633 lines
14 KiB
CSS

/* ───────────── Import page ───────────── */
.import-page {
min-height: clamp(420px, 60vh, 704px);
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
padding: clamp(28px, 6vw, 78px) clamp(14px, 3vw, 24px) clamp(18px, 3vw, 24px);
border: 1px solid var(--glass-line);
border-radius: 12px;
background:
radial-gradient(circle at 50% 22%, rgba(216, 168, 74, 0.1), transparent 23rem),
radial-gradient(circle at 8% 45%, rgba(216, 168, 74, 0.06), transparent 22rem),
radial-gradient(circle at 92% 45%, rgba(216, 168, 74, 0.06), transparent 22rem),
linear-gradient(180deg, rgba(16, 26, 34, 0.78), rgba(8, 15, 22, 0.88));
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035);
}
.import-copy {
text-align: center;
position: relative;
z-index: 2;
}
.import-copy h2 {
margin: 0;
color: #f1f3f5;
font-size: clamp(24px, 3.4vw + 8px, 39px);
line-height: 1.12;
font-weight: 700;
}
.import-copy h2 span {
color: var(--gold-bright);
white-space: nowrap;
}
.import-copy p {
margin: 16px 0 0;
color: #a9b0bb;
font-size: clamp(14px, 1vw + 8px, 18px);
}
.import-card {
width: min(100%, 1088px);
margin-top: clamp(20px, 4vw, 58px);
padding: clamp(14px, 2vw, 19px) clamp(16px, 3vw, 36px) clamp(18px, 2.5vw, 28px);
border-radius: 13px;
position: relative;
z-index: 2;
}
.import-input-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 24px;
align-items: center;
padding-bottom: 18px;
border-bottom: 1px solid var(--glass-line);
}
.stem-choice-row {
display: grid;
grid-template-columns: 138px repeat(6, minmax(86px, 1fr));
gap: 14px;
align-items: center;
padding-top: 22px;
}
.stem-choice-row > span {
color: #d9dde4;
font-weight: 600;
white-space: nowrap;
}
.stem-choice {
min-height: 54px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 9px;
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: var(--radius);
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.025), transparent),
rgba(8, 15, 22, 0.34);
color: currentColor;
font: inherit;
font-weight: 700;
transition:
border-color var(--t-base) var(--easing),
background-color var(--t-base) var(--easing),
box-shadow var(--t-base) var(--easing),
transform var(--t-fast) var(--easing);
}
.stem-choice:hover {
transform: translateY(-1px);
background-color: rgba(255, 255, 255, 0.045);
box-shadow: inset 0 0 22px color-mix(in srgb, currentColor 10%, transparent);
}
/* Deselected state -- the user has unchecked this stem from the
"stems to extract" set, so it's greyed out and won't appear in the
studio dashboard after processing. Maintains layout (still occupies
its grid slot) so the row doesn't reflow on toggle. */
.stem-choice[aria-pressed="false"] {
opacity: 0.32;
border-color: rgba(148, 163, 184, 0.2) !important;
background: rgba(8, 15, 22, 0.2);
}
.stem-choice[aria-pressed="false"]:hover {
opacity: 0.55;
}
.stem-choice svg {
flex: 0 0 auto;
}
.stem-choice.vocals { color: #ff3f46; border-color: rgba(255, 63, 70, 0.28); }
.stem-choice.drums { color: #ff7a1a; border-color: rgba(255, 122, 26, 0.24); }
.stem-choice.bass { color: #ffd04d; border-color: rgba(255, 208, 77, 0.24); }
.stem-choice.guitar { color: #3fc952; border-color: rgba(63, 201, 82, 0.24); }
.stem-choice.piano { color: #974cff; border-color: rgba(151, 76, 255, 0.24); }
.stem-choice.other { color: #3695ff; border-color: rgba(54, 149, 255, 0.24); }
.privacy-note {
display: inline-flex;
align-items: center;
gap: 9px;
margin-top: 34px;
color: #a6adb7;
font-size: 15px;
position: relative;
z-index: 2;
}
.feature-strip {
width: min(100%, 1160px);
min-height: 136px;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
align-items: center;
gap: clamp(18px, 2.4vw, 36px);
margin-top: clamp(22px, 4vw, 54px);
padding: clamp(16px, 2.4vw, 24px) clamp(18px, 3vw, 46px);
border-radius: 13px;
position: relative;
z-index: 2;
}
.feature-item {
display: grid;
grid-template-columns: clamp(40px, 4vw, 68px) 1fr;
align-items: center;
gap: clamp(12px, 1.5vw, 24px);
color: var(--gold);
}
.feature-item svg {
width: 100%;
height: auto;
max-width: 48px;
}
.feature-item + .feature-item {
border-left: 1px solid var(--gold-line);
padding-left: clamp(18px, 2.4vw, 42px);
}
.feature-item strong {
display: block;
color: var(--ink);
font-size: clamp(14px, 1vw + 6px, 16px);
margin-bottom: 6px;
}
.feature-item span {
color: #a6adb7;
font-size: clamp(13px, 0.8vw + 8px, 15px);
}
/* The HTML hard-codes <br> in feature descriptions for desktop layout;
on narrow viewports those forced breaks produce ragged 2-word lines.
Collapse them so the text wraps naturally. */
@media (max-width: 720px) {
.feature-item br { display: none; }
}
.wave-lines {
position: absolute;
top: 152px;
width: 32%;
height: 120px;
opacity: 0.46;
background:
repeating-linear-gradient(108deg, transparent 0 24px, rgba(216, 168, 74, 0.52) 25px 27px, transparent 28px 50px);
mask-image: radial-gradient(ellipse, black 0 48%, transparent 72%);
}
.wave-left {
left: 0;
transform: skewY(-7deg);
}
.wave-right {
right: 0;
transform: skewY(7deg);
}
/* Decorative diagonal lines crowd the headline on narrow viewports
(they're absolutely positioned at top: 152px; width: 32%) -- hide
them below the tablet breakpoint to keep the headline area clean. */
@media (max-width: 720px) {
.wave-lines { display: none; }
}
/* ───────────── Now-playing card ───────────── */
.now-playing {
display: grid;
grid-template-columns:
clamp(110px, 11vw, 160px)
minmax(0, 1.2fr)
minmax(0, 0.9fr)
minmax(0, 0.65fr)
minmax(0, 0.65fr)
minmax(0, 0.8fr);
align-items: center;
gap: 0;
min-height: 224px;
padding: 23px 24px;
border-radius: 12px;
}
.app:not(.is-import) .now-playing {
flex: 0 0 auto;
min-height: clamp(120px, 14vh, 160px);
padding-top: clamp(7px, 1vh, 12px);
padding-bottom: clamp(7px, 1vh, 12px);
}
.app:not(.is-import) .np-art {
width: clamp(92px, 13vh, 132px);
height: clamp(92px, 14vh, 144px);
}
.np-art {
position: relative;
width: 154px;
height: 168px;
border-radius: 9px;
overflow: hidden;
background:
linear-gradient(135deg, rgba(14, 22, 30, 0.95), rgba(5, 9, 13, 0.96)),
var(--panel-2);
flex-shrink: 0;
}
.np-art-placeholder {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
color: var(--ink-faint);
background: linear-gradient(135deg, #1a1a1a, #0e0e0e);
}
.np-art-img {
position: absolute;
inset: 6px;
width: 100%;
height: 100%;
width: calc(100% - 12px);
height: calc(100% - 12px);
object-fit: contain;
display: none;
border-radius: 6px;
}
.np-art-img.loaded {
display: block;
}
.np-art-play {
width: 50px;
height: 50px;
border-radius: 50%;
border: 0;
background: linear-gradient(180deg, var(--gold-bright), var(--gold));
color: #fff;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 12px 24px rgba(216, 168, 74, 0.2);
transition: transform var(--t-fast) var(--easing), background-color var(--t-base) var(--easing);
}
.np-art-play:hover {
background: linear-gradient(180deg, #ecc76e, #c78a30);
}
.np-art-play svg {
margin-left: 2px;
}
.np-info {
display: flex;
flex-direction: column;
gap: 14px;
min-width: 0;
padding: 0 clamp(14px, 2vw, 28px) 0 clamp(16px, 2.2vw, 30px);
border-right: 1px solid var(--glass-line);
}
.np-title {
font-size: clamp(19px, 1.45vw, 24px);
font-weight: 500;
color: var(--ink);
line-height: 1.2;
white-space: normal;
overflow: hidden;
text-overflow: clip;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow-wrap: anywhere;
max-width: 100%;
}
.np-chips {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.chip {
display: inline-flex;
align-items: center;
min-height: 34px;
padding: 5px 12px;
background: rgba(255, 255, 255, 0.055);
color: var(--ink);
border: var(--border-w) solid var(--line);
border-radius: var(--radius-xs);
font-size: 14px;
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
.np-play-row {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: 16px;
margin-top: 6px;
color: #c6cbd2;
font-size: 14px;
}
.np-scrub {
height: 4px;
border-radius: 999px;
background: rgba(148, 163, 184, 0.16);
position: relative;
}
.np-scrub span {
position: absolute;
left: 0;
top: 50%;
width: 14px;
height: 14px;
transform: translateY(-50%);
border-radius: 50%;
background: var(--gold-bright);
box-shadow: 0 0 0 4px rgba(216, 168, 74, 0.18);
}
.stem-energy,
.analysis-card {
min-height: clamp(100px, 14vh, 156px);
padding: 0 clamp(10px, 1.4vw, 24px);
border-left: 1px solid var(--glass-line);
}
.stem-energy {
grid-column: 3;
}
.key-card {
grid-column: 4;
}
.loudness-card {
grid-column: 5;
}
.beat-card {
grid-column: 6;
}
.loudness-card.hidden + .beat-card {
grid-column: 5 / 7;
}
.stem-energy h3,
.analysis-card h3 {
margin: 0 0 13px;
color: #c9ced6;
font-size: 14px;
text-transform: uppercase;
}
.stem-energy h3 span,
.analysis-card h3 span,
.panel-title span {
display: inline-grid;
place-items: center;
width: 16px;
height: 16px;
margin-left: 4px;
border: 1px solid currentColor;
border-radius: 50%;
font-size: 10px;
opacity: 0.72;
}
.energy-row {
display: grid;
grid-template-columns: 78px 1fr 40px;
align-items: center;
gap: 9px;
margin: 5px 0;
color: var(--stem-color);
font-size: 13px;
}
.energy-row span {
font-weight: 700;
}
.energy-row b {
height: 12px;
border-radius: 999px;
background:
linear-gradient(90deg, currentColor 0 var(--v, 0%), rgba(148, 163, 184, 0.13) var(--v, 0%) 100%);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05);
transition: background 80ms linear;
}
.energy-row em {
color: #c8cdd4;
font-style: normal;
font-weight: 700;
}
.vocals { --stem-color: #e54249; color: var(--stem-color); }
.drums { --stem-color: #f06a14; color: var(--stem-color); }
.bass { --stem-color: #eab414; color: var(--stem-color); }
.guitar { --stem-color: #5bbf50; color: var(--stem-color); }
.piano { --stem-color: #7f45d8; color: var(--stem-color); }
.other { --stem-color: #3e8dcf; color: var(--stem-color); }
.original { --stem-color: #a8b0bd; color: var(--stem-color); }
.analysis-card {
display: flex;
flex-direction: column;
justify-content: center;
gap: 8px;
}
.key-card {
align-items: center;
text-align: center;
}
.beat-card {
align-items: center;
text-align: center;
}
.analysis-card strong {
color: var(--gold-bright);
font-size: clamp(17px, 1.7vw, 26px);
line-height: 1.1;
font-weight: 500;
}
.analysis-card small {
color: #b9bfc7;
font-size: 12px;
}
.confidence-ring {
width: 66px;
height: 66px;
position: relative;
display: grid;
place-items: center;
border-radius: 50%;
background:
conic-gradient(
var(--gold) calc(var(--confidence-pct, 0) * 1%),
rgba(216, 168, 74, 0.16) 0
);
color: #e9edf2;
isolation: isolate;
font-size: 14px;
font-variant-numeric: tabular-nums;
}
.confidence-ring::before {
content: "";
position: absolute;
inset: 4px;
border-radius: 50%;
background: var(--panel);
}
.confidence-ring::after {
content: "";
position: absolute;
inset: 0;
border-radius: 50%;
box-shadow:
inset 0 0 0 1px rgba(216, 168, 74, 0.12),
0 0 14px rgba(216, 168, 74, 0.1);
pointer-events: none;
}
.confidence-ring span {
position: relative;
z-index: 1;
}
.loudness-card strong {
display: grid;
grid-template-columns: auto 1fr;
gap: 12px;
align-items: baseline;
color: #f0f2f5;
}
.beat-values {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 16px;
min-width: 0;
}
.beat-values strong {
display: grid;
grid-template-columns: auto 1fr;
gap: 12px;
align-items: baseline;
color: var(--gold-bright);
text-align: left;
}
.beat-values strong small {
display: block;
margin-top: 0;
color: #b9bfc7;
}
.np-transport {
display: flex;
align-items: center;
gap: 26px;
color: #cfd4db;
font-size: 14px;
font-variant-numeric: tabular-nums;
}
.np-transport-row {
display: flex;
align-items: center;
gap: 22px;
}
.btn-transport {
width: 58px;
height: 58px;
background: rgba(255, 255, 255, 0.055);
color: var(--ink);
border: 1px solid var(--glass-line);
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition:
background-color var(--t-base) var(--easing),
color var(--t-base) var(--easing),
border-color var(--t-base) var(--easing),
transform var(--t-fast) var(--easing);
}
.btn-transport:hover {
background: rgba(255, 255, 255, 0.09);
border-color: var(--line-strong);
}
.btn-transport:active {
transform: scale(0.94);
}
.btn-transport .pause-icon {
display: none;
}
.btn-transport.playing .pause-icon {
display: block;
}
.btn-transport.playing svg:not(.pause-icon) {
display: none;
}
.btn-transport.playing {
background: linear-gradient(180deg, #4ade80, #16a34a);
color: white;
border-color: #15803d;
box-shadow: 0 16px 34px rgba(34, 197, 94, 0.22);
}
.btn-transport.stopped {
background: linear-gradient(180deg, #f87171, #dc2626);
color: white;
border-color: #b91c1c;
box-shadow: 0 16px 34px rgba(239, 68, 68, 0.22);
}
.btn-transport.loop.active {
background: linear-gradient(180deg, var(--gold-bright), var(--gold));
color: white;
border-color: var(--gold-line);
box-shadow: 0 16px 34px rgba(216, 168, 74, 0.18);
}
.np-time {
font-size: 13px;
color: var(--ink-dim);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}