8c0a96689f
Built both packages on a real Windows box and drove the whole flow. Four bugs that only surfaced by running it, none of which static checks could see. 1. Stale version after updating. app_version() read installed dist metadata, which lives in python/ -- the directory the updater deliberately never replaces. A self-updated install kept reporting the old version and would re-offer an update it had already applied, forever. It now prefers the app layer's static/version.json, which moves with backend/. Gitignored, so Docker and source checkouts still fall through to the hatch-vcs metadata. Proven: after a real update, python/ dist-info says 0.13.0 while /api/health reports 0.13.1. 2. The page CSP blocked the whole feature. The UI is served over http by the Python backend, so its connect-src applies: api.github.com is allowed, github.com and objects.githubusercontent.com are not, and that is where release assets live. Fetching the checksum and runtime id from JS was refused, so the pill would simply never appear. Those two reads moved into Rust (check_app_update), whose HTTP client is not bound by the page CSP, so the policy from #171 stays exactly as tight as it was. 3. plugin:event|listen refused by the Tauri ACL. App-defined commands are not ACL-gated but plugin commands are, and the capability does not cover the remote http origin the UI is served from. The progress bar is now indeterminate instead of granting a remote origin event permissions to put a percentage on a 5 MB download. 4. The post-strip import check re-bloated the package. Running Python regenerated 1,912 files / 39 MB of __pycache__ that the strip had just removed, cancelling nearly all of it: the net saving was 180 files. Swept once after the last interpreter run, and backend/ no longer ships a developer's local __pycache__ either. Also: the *.old sweep now runs on every launch rather than only on a version change. apply_app_update relaunches then exits, so on the first launch of the new build Windows still holds StemDeck.exe.old open, the delete fails silently, and gated on a change that already happened it would never retry. Observed for real: 15.7 MB stranded. Verified swept on the next launch. UI: "Update now" is an accent pill BESIDE Download, not a replacement, so the zip stays one click away and is the escape hatch if an update fails. Measured against the published v0.13.0 package: 18,143 -> 16,056 files (-2,087, -11.5%) and 883 -> 850 MB. The real win for #421 is the update path itself: 5 MB and 123 files instead of 284 MB and 16,056. Verified on this machine: a real 6-stem Demucs separation through the stripped package; the full notify -> Update now -> download -> restart -> relaunch cycle, after which user data (job, 7 stems, 130 MB of models), portable.txt, cpu-only and python/ were all untouched; and the safety gate correctly declining, with no download attempted, when the release's runtime id differs. Not covered: the NVIDIA package was not built, though the risk that motivated the gate is structurally gone now that python/ is never swapped.
946 lines
67 KiB
HTML
946 lines
67 KiB
HTML
<!doctype html>
|
||
<html lang="en" data-i18n-doctitle="doc.title">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>StemDeck — split any track into stems</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||
<link rel="stylesheet" href="/css/variables.css" />
|
||
<link rel="stylesheet" href="/css/waves.css" />
|
||
<link rel="stylesheet" href="/css/daw.css" />
|
||
</head>
|
||
<body>
|
||
|
||
<div class="app daw" id="app">
|
||
|
||
<!-- ══ TOPBAR ══ -->
|
||
<div class="daw-topbar">
|
||
<!-- Brand -->
|
||
<div class="daw-brand">
|
||
<span class="daw-brand-name">
|
||
<span class="fg">Stem</span><span class="accent">Deck</span>
|
||
</span>
|
||
</div>
|
||
<span id="brandVersion" class="daw-version"></span>
|
||
|
||
<!-- Separator -->
|
||
<div class="daw-sep"></div>
|
||
|
||
<!-- Composer pill -->
|
||
<form id="job-form" class="daw-composer">
|
||
|
||
<!-- URL zone (JS uses .url-wrap for drag events) -->
|
||
<div class="daw-url-zone url-wrap">
|
||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="var(--muted)" stroke-width="1.8" style="flex-shrink:0" aria-hidden="true">
|
||
<path d="M10 14a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1 M14 10a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1"/>
|
||
</svg>
|
||
<input
|
||
id="url"
|
||
type="url"
|
||
placeholder="Paste a YouTube or SoundCloud link, or drop an audio file…"
|
||
data-i18n-placeholder="topbar.urlPlaceholder"
|
||
spellcheck="false"
|
||
autocomplete="off"
|
||
/>
|
||
<!-- File pill (shown when file selected) -->
|
||
<div class="file-pill hidden" id="filePill">
|
||
<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||
<path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/>
|
||
</svg>
|
||
<span class="file-name" id="fileName"></span>
|
||
<span class="file-size" id="fileSize"></span>
|
||
<button class="file-clear" id="fileClear" type="button" aria-label="Remove file" data-i18n-aria-label="topbar.removeFile">×</button>
|
||
</div>
|
||
<button class="daw-upload-btn" type="button" title="Upload audio file" id="uploadFileBtn" aria-label="Upload audio file" data-i18n-title="topbar.uploadFile" data-i18n-aria-label="topbar.uploadFile">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
||
<path d="M12 3v12 M7 8l5-5 5 5 M5 21h14"/>
|
||
</svg>
|
||
</button>
|
||
<input id="fileInput" type="file" multiple accept=".mp3,.wav,.flac,.mp4,.m4a,.ogg,.opus,audio/mpeg,audio/wav,audio/flac,video/mp4,audio/mp4,audio/ogg,audio/opus" style="display:none" aria-hidden="true" />
|
||
</div>
|
||
|
||
<!-- Divider -->
|
||
<div class="daw-composer-sep"></div>
|
||
|
||
<!-- Stem selection -->
|
||
<div class="daw-stem-section">
|
||
<span class="daw-extract-label" data-i18n="extract.label">Extract</span>
|
||
<div class="daw-stem-chips">
|
||
<button class="stem-choice stem-choice-all" type="button" id="stemAllBtn" aria-pressed="true" data-i18n="extract.all">All</button>
|
||
<button class="stem-choice" type="button" data-stem="vocals" aria-pressed="true" style="--color:var(--vocals)">
|
||
<span class="stem-dot"></span><span data-i18n="stem.vocals">Vocals</span>
|
||
</button>
|
||
<!-- On-demand lead/backing vocal split (#275). Shown only while
|
||
Vocals is selected above; toggling "Lead + Backing" auto-runs
|
||
the split once the import finishes (see main.js/job.js). -->
|
||
<div class="vocal-mode-toggle hidden" id="vocalModeToggle" role="group" aria-label="Vocals mode" data-i18n-aria-label="vocalMode.groupAria">
|
||
<button type="button" class="vocal-mode-btn" data-mode="all" aria-pressed="true" data-i18n="vocalMode.all">All</button>
|
||
<button type="button" class="vocal-mode-btn" data-mode="split" aria-pressed="false" data-i18n="vocalMode.split">Lead + Backing</button>
|
||
</div>
|
||
<button class="stem-choice" type="button" data-stem="drums" aria-pressed="true" style="--color:var(--drums)">
|
||
<span class="stem-dot"></span><span data-i18n="stem.drums">Drums</span>
|
||
</button>
|
||
<button class="stem-choice" type="button" data-stem="bass" aria-pressed="true" style="--color:var(--bass)">
|
||
<span class="stem-dot"></span><span data-i18n="stem.bass">Bass</span>
|
||
</button>
|
||
<button class="stem-choice" type="button" data-stem="guitar" aria-pressed="true" style="--color:var(--guitar)">
|
||
<span class="stem-dot"></span><span data-i18n="stem.guitar">Guitar</span>
|
||
</button>
|
||
<button class="stem-choice" type="button" data-stem="piano" aria-pressed="true" style="--color:var(--piano)">
|
||
<span class="stem-dot"></span><span data-i18n="stem.piano">Piano</span>
|
||
</button>
|
||
<button class="stem-choice" type="button" data-stem="other" aria-pressed="false" style="--color:var(--other)">
|
||
<span class="stem-dot"></span><span data-i18n="stem.other">Other</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Process button -->
|
||
<button id="submit" class="daw-process-btn" type="submit">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||
<path d="M13 2 3 14h7l-1 8 10-12h-7z"/>
|
||
</svg>
|
||
<span data-i18n="process.splitStems">Split stems</span>
|
||
</button>
|
||
</form>
|
||
|
||
<!-- Notification bell -->
|
||
<div class="daw-notif-wrap" style="position:relative;flex-shrink:0;">
|
||
<button class="daw-iconbtn daw-notif-btn" id="notifBtn" title="Notifications" type="button" aria-label="Notifications" aria-expanded="false" data-i18n-title="notif.bell" data-i18n-aria-label="notif.bell">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true">
|
||
<path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9 M13.7 21a2 2 0 0 1-3.4 0"/>
|
||
</svg>
|
||
<span class="daw-notif-badge hidden" id="notifBadge" aria-hidden="true"></span>
|
||
</button>
|
||
<div class="daw-notif-panel" role="menu" aria-label="Notifications" data-i18n-aria-label="notif.title">
|
||
<div class="daw-notif-header">
|
||
<span class="uplabel" data-i18n="notif.title">Notifications</span>
|
||
<button class="daw-notif-clear" id="notifClearAll" type="button" data-i18n="notif.clearAll">Clear all</button>
|
||
<button class="daw-notif-close" type="button" aria-label="Close notifications" data-i18n-aria-label="notif.close">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18M6 6l12 12"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="daw-notif-list" id="notifList">
|
||
<div class="daw-notif-card daw-notif-release hidden" id="notifReleaseCard">
|
||
<div class="daw-notif-card-icon">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 2l2.4 7.4H22l-6.2 4.5 2.4 7.4L12 17l-6.2 4.3 2.4-7.4L2 9.4h7.6z"/></svg>
|
||
</div>
|
||
<div class="daw-notif-card-body">
|
||
<div class="daw-notif-card-title" data-i18n="notif.newRelease">New release available</div>
|
||
<div class="daw-notif-card-desc" id="notifReleaseDesc"></div>
|
||
</div>
|
||
<button class="daw-notif-dismiss" id="notifReleaseDismiss" type="button" aria-label="Dismiss notification" data-i18n-aria-label="notif.dismiss">
|
||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 6 6 18M6 6l12 12"/></svg>
|
||
</button>
|
||
</div>
|
||
<p class="daw-notif-empty" id="notifEmpty" data-i18n="notif.empty">No new notifications</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Hidden collapsed strip (JS compatibility) -->
|
||
<div class="hidden" id="appbarStrip" aria-hidden="true" style="display:none!important">
|
||
<div><div class="strip-sq-stems" id="appbarStripStems"></div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══ BODY ══ -->
|
||
<div class="daw-body">
|
||
|
||
<!-- ── Sidebar / Catalog ── -->
|
||
<aside class="sidebar" id="catalogPanel">
|
||
<!-- Rail -->
|
||
<nav class="sidebar-rail" aria-label="Library navigation" data-i18n-aria-label="nav.libraryNav">
|
||
<button class="rail-btn rail-collapse" id="sidebarCollapseBtn" type="button" title="Toggle library" aria-label="Toggle library" aria-expanded="true" data-i18n-title="nav.toggleLibrary" data-i18n-aria-label="nav.toggleLibrary">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true">
|
||
<line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/>
|
||
</svg>
|
||
</button>
|
||
<button class="rail-btn rail-library active" id="appMenuBtn" type="button" title="Library" aria-label="Library" aria-pressed="true" data-i18n-title="nav.library" data-i18n-aria-label="nav.library">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true">
|
||
<path d="M4 4h16v16H4z M4 9h16"/>
|
||
</svg>
|
||
<span data-i18n="nav.library">Library</span>
|
||
</button>
|
||
<button class="rail-btn rail-favorites" type="button" title="Favorites" aria-label="Favorites" aria-pressed="false" data-i18n-title="nav.favorites" data-i18n-aria-label="nav.favorites">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true">
|
||
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
|
||
</svg>
|
||
<span data-i18n="nav.favorites">Favorites</span>
|
||
</button>
|
||
<button class="rail-btn rail-trash" type="button" title="Trash" aria-label="Trash" aria-pressed="false" data-i18n-title="nav.trash" data-i18n-aria-label="nav.trash">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true">
|
||
<path d="M3 6h18 M8 6V4h8v2 M19 6l-1 14H6L5 6"/>
|
||
</svg>
|
||
<span data-i18n="nav.trash">Trash</span>
|
||
</button>
|
||
<button class="rail-btn rail-queue hidden" type="button" title="Import queue" aria-label="Import queue" aria-pressed="false" data-i18n-title="nav.importQueue" data-i18n-aria-label="nav.importQueue">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true">
|
||
<path d="M4 6h10 M4 12h10 M4 18h6"/>
|
||
<path d="M16 15l3 3 3-3 M19 18V9"/>
|
||
</svg>
|
||
<span data-i18n="nav.queue">Queue</span>
|
||
<span class="rail-badge hidden" id="queueBadge" aria-hidden="true"></span>
|
||
</button>
|
||
<div style="flex:1"></div>
|
||
<button class="rail-btn" id="settingsBtn" type="button" aria-label="Settings" aria-haspopup="dialog" data-i18n-aria-label="nav.settings">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true">
|
||
<circle cx="12" cy="12" r="3"/>
|
||
<path d="M12 2v2 M12 20v2 M4 12H2 M22 12h-2 M5 5l1.5 1.5 M17.5 17.5 19 19 M5 19l1.5-1.5 M17.5 6.5 19 5"/>
|
||
</svg>
|
||
<span data-i18n="nav.settings">Settings</span>
|
||
</button>
|
||
<button class="rail-btn rail-recommend" id="friendsBtn" type="button" title="We Recommend" aria-label="We Recommend" aria-haspopup="dialog" data-i18n-title="nav.weRecommend" data-i18n-aria-label="nav.weRecommend">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true">
|
||
<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/>
|
||
</svg>
|
||
<span><span data-i18n="nav.weRecommendLine1">We</span><br><span data-i18n="nav.weRecommendLine2">Recommend</span></span>
|
||
</button>
|
||
<button class="rail-btn" id="aboutBtn" type="button" title="Help" aria-label="Help" data-i18n-title="nav.help" data-i18n-aria-label="nav.help">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true">
|
||
<circle cx="12" cy="12" r="9"/>
|
||
<path d="M9.5 9a2.5 2.5 0 1 1 3.5 2.3c-.7.4-1 .8-1 1.7 M12 17h.01"/>
|
||
</svg>
|
||
<span data-i18n="nav.help">Help</span>
|
||
</button>
|
||
</nav>
|
||
|
||
<!-- Sidebar body -->
|
||
<div class="sidebar-body">
|
||
<!-- Search -->
|
||
<div class="daw-search" id="catalogToggle" role="button" tabindex="0" aria-label="Search library" data-i18n-aria-label="search.ariaLabel">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||
<circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/>
|
||
</svg>
|
||
<input id="catalogSearch" type="search" placeholder="Search or #tag…" data-i18n-placeholder="search.placeholder" autocomplete="off" spellcheck="false" />
|
||
<ul class="tag-suggest" id="tagSuggest" role="listbox" aria-label="Tag suggestions" data-i18n-aria-label="search.tagSuggestions"></ul>
|
||
<span class="search-kbd" aria-hidden="true">⌘K</span>
|
||
</div>
|
||
|
||
|
||
<!-- Clear bin (visible only in trash-view) -->
|
||
<div id="clearBinBar" class="clear-bin-bar">
|
||
<button id="clearBinBtn" class="clear-bin-btn" type="button">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||
<path d="M3 6h18 M8 6V4h8v2 M19 6l-1 14H6L5 6"/>
|
||
</svg>
|
||
<span data-i18n="trash.empty">Empty trash</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Catalog list (JS populates) -->
|
||
<div id="catalogList" class="daw-lib-list" role="list"></div>
|
||
|
||
<!-- Collapsed strip (JS compat) -->
|
||
<div id="catalogStrip" class="cat-collapsed-strip"></div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- ── Main area ── -->
|
||
<div class="daw-main-col">
|
||
<div id="lanes" class="daw-main">
|
||
|
||
<!-- Job progress -->
|
||
<div id="error" class="error hidden" role="alert"></div>
|
||
|
||
<!-- Track header / info panel -->
|
||
<div id="transport" class="daw-track-header">
|
||
|
||
<!-- Row 1: track card + metadata cards -->
|
||
<div class="daw-info-row">
|
||
|
||
<div class="daw-meta-card" data-meta="key">
|
||
<span class="meta-card-label" data-i18n="meta.key">KEY</span>
|
||
<div class="meta-card-main">
|
||
<span class="meta-card-value" id="summary-key">—</span>
|
||
<div class="daw-camelot-ring hidden" id="summary-confidence"></div>
|
||
</div>
|
||
<span class="meta-card-sub" id="summary-scale"></span>
|
||
<span class="meta-card-sub hidden" id="summary-confidence-label"></span>
|
||
</div>
|
||
|
||
<div class="daw-meta-card" data-meta="bpm">
|
||
<span class="meta-card-label" data-i18n="meta.bpm">BPM</span>
|
||
<div class="meta-card-main">
|
||
<span class="meta-card-value accent" id="summary-bpm">—</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="daw-meta-card" data-meta="lufs">
|
||
<span class="meta-card-label" data-i18n="meta.lufs">LUFS</span>
|
||
<div class="meta-card-main">
|
||
<span class="meta-card-value" id="summary-lufs">—</span>
|
||
</div>
|
||
<span class="meta-card-sub" id="summary-peak"></span>
|
||
</div>
|
||
|
||
<div class="daw-meta-card" data-meta="duration">
|
||
<span class="meta-card-label" data-i18n="meta.duration">DURATION</span>
|
||
<div class="meta-card-main">
|
||
<span class="meta-card-value num" id="summary-duration">—</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="daw-meta-card" data-meta="scale">
|
||
<span class="meta-card-label" data-i18n="meta.scale">SCALE</span>
|
||
<div class="meta-card-main">
|
||
<span class="meta-card-value" id="summary-scale-name">—</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="daw-meta-card" data-meta="dr">
|
||
<span class="meta-card-label" data-i18n="meta.dynamicRange">DYNAMIC RANGE</span>
|
||
<div class="meta-card-main">
|
||
<span class="meta-card-value" id="summary-dr">—</span>
|
||
</div>
|
||
<span class="meta-card-sub" id="summary-dr-label"></span>
|
||
</div>
|
||
|
||
<div class="daw-meta-card" data-meta="stability">
|
||
<span class="meta-card-label" data-i18n="meta.tempoStability">TEMPO STABILITY</span>
|
||
<div class="meta-card-main">
|
||
<span class="meta-card-value" id="summary-stability">—</span>
|
||
</div>
|
||
<span class="meta-card-sub" id="summary-stability-label"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Row 2: stem presence cards -->
|
||
<div class="stem-presence-panel">
|
||
<div class="stem-card inactive" data-stem="vocals">
|
||
<span class="stem-card-label" data-i18n="presence.vocal">VOCAL PRESENCE</span>
|
||
<span class="stem-card-pct">—</span>
|
||
</div>
|
||
<div class="stem-card inactive" data-stem="drums">
|
||
<span class="stem-card-label" data-i18n="presence.drum">DRUM INTENSITY</span>
|
||
<span class="stem-card-pct">—</span>
|
||
</div>
|
||
<div class="stem-card inactive" data-stem="bass">
|
||
<span class="stem-card-label" data-i18n="presence.bass">BASS DEPTH</span>
|
||
<span class="stem-card-pct">—</span>
|
||
</div>
|
||
<div class="stem-card inactive" data-stem="guitar">
|
||
<span class="stem-card-label" data-i18n="presence.guitar">GUITAR PRESENCE</span>
|
||
<span class="stem-card-pct">—</span>
|
||
</div>
|
||
<div class="stem-card inactive" data-stem="piano">
|
||
<span class="stem-card-label" data-i18n="presence.piano">PIANO PRESENCE</span>
|
||
<span class="stem-card-pct">—</span>
|
||
</div>
|
||
<div class="stem-card inactive" data-stem="other">
|
||
<span class="stem-card-label" data-i18n="presence.other">OTHER</span>
|
||
<span class="stem-card-pct">—</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Hidden compat -->
|
||
<div style="display:none" aria-hidden="true"><div id="loudness-card"></div></div>
|
||
</div>
|
||
|
||
<!-- Sections bar -->
|
||
<div class="daw-section-ribbon">
|
||
<div class="daw-mixer-label daw-sections-header">
|
||
<span class="daw-label-title" data-i18n="sections.title">Sections</span>
|
||
<span class="sections-save-indicator hidden" id="sectionsSaveIndicator" aria-live="polite" aria-label="Saving sections" data-i18n-aria-label="sections.savingAria"></span>
|
||
<button class="sections-add-btn-label" id="sectionsAddBtn" type="button" title="Add section" aria-label="Add section" data-i18n-title="sections.addAria" data-i18n-aria-label="sections.addAria">
|
||
<svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
|
||
<span data-i18n="sections.add">Add</span>
|
||
</button>
|
||
</div>
|
||
<div class="daw-sections-area" id="daw-sections"></div>
|
||
</div>
|
||
|
||
<!-- Waveform header / ruler -->
|
||
<div class="daw-wave-header">
|
||
<div class="daw-wave-label">
|
||
<span class="daw-label-title" data-i18n="mixer.title">Mixer</span>
|
||
<span class="uplabel daw-label-sub" data-i18n="mixer.hint">Drag fader · M/S</span>
|
||
</div>
|
||
<div class="daw-ruler-area">
|
||
<div class="lanes-ruler" data-position="top">
|
||
<div class="lanes-ruler-time" id="ruler-time">
|
||
<div class="playhead-marker" aria-hidden="true">
|
||
<svg viewBox="0 0 10 10" width="10" height="10" aria-hidden="true">
|
||
<polygon points="0,0 10,0 5,8" fill="var(--accent)"/>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Content area: stems + waveform -->
|
||
<div class="daw-content">
|
||
|
||
<!-- Stems panel (left — M/S controls per stem) -->
|
||
<div class="daw-stems-panel" aria-label="Stems" data-i18n-aria-label="stemsPanel.ariaLabel">
|
||
|
||
<!-- Presence panel hidden but in DOM for JS -->
|
||
<div style="display:none" aria-hidden="true">
|
||
<section class="presence-panel surface-panel">
|
||
<div class="presence-ruler" id="presence-ruler">
|
||
<span></span><b>0:00</b><b>0:00</b><b>0:00</b><b>0:00</b><b>0:00</b><b>0:00</b><b>0:00</b><b>0:00</b>
|
||
</div>
|
||
<div class="presence-grid">
|
||
<div class="presence-labels">
|
||
<span class="vocals" data-i18n="stem.vocals">Vocals</span><span class="drums" data-i18n="stem.drums">Drums</span>
|
||
<span class="bass" data-i18n="stem.bass">Bass</span><span class="guitar" data-i18n="stem.guitar">Guitar</span>
|
||
<span class="piano" data-i18n="stem.piano">Piano</span><span class="other" data-i18n="stem.others">Others</span>
|
||
</div>
|
||
<div class="presence-bars">
|
||
<i class="vocals" data-stem="vocals"></i>
|
||
<i class="drums" data-stem="drums"></i>
|
||
<i class="bass" data-stem="bass"></i>
|
||
<i class="guitar" data-stem="guitar"></i>
|
||
<i class="piano" data-stem="piano"></i>
|
||
<i class="other" data-stem="other"></i>
|
||
<div class="presence-playhead hidden" id="presence-playhead" aria-hidden="true"></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<!-- Stem rows with M/S -->
|
||
<div class="stem-list" aria-label="Waveform lane icons">
|
||
<span class="original" data-stem="original">
|
||
<i class="drag-handle"></i>
|
||
<em>
|
||
<svg class="stem-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" aria-hidden="true"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
||
<span data-i18n="stem.original">Original</span>
|
||
</em>
|
||
<b class="stem-mute" data-stem="original" role="button" tabindex="0" aria-label="Mute Original" aria-pressed="false">M</b>
|
||
<b class="stem-solo" data-stem="original" role="button" tabindex="0" aria-label="Solo Original" aria-pressed="false">S</b>
|
||
<button class="stem-monitor" type="button" data-stem="original" aria-label="Solo only Original"></button>
|
||
<i class="mini-meter"></i>
|
||
</span>
|
||
<span class="vocals" data-stem="vocals">
|
||
<i class="drag-handle"></i>
|
||
<em>
|
||
<svg class="stem-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><path d="M12 19v3"/></svg>
|
||
<span data-i18n="stem.vocals">Vocals</span>
|
||
</em>
|
||
<b class="stem-mute" data-stem="vocals" role="button" tabindex="0" aria-label="Mute Vocals" aria-pressed="false">M</b>
|
||
<b class="stem-solo" data-stem="vocals" role="button" tabindex="0" aria-label="Solo Vocals" aria-pressed="false">S</b>
|
||
<button class="stem-monitor" type="button" data-stem="vocals" aria-label="Solo only Vocals"></button>
|
||
<i class="mini-meter"></i>
|
||
</span>
|
||
<!-- On-demand lead/backing vocal split (#275): hidden by default
|
||
(no split has run yet); shown in place of the row above via
|
||
applyStemSelectionFilter in player.js once a job has one. -->
|
||
<span class="lead_vocals hidden" data-stem="lead_vocals">
|
||
<i class="drag-handle"></i>
|
||
<em>
|
||
<svg class="stem-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><path d="M12 19v3"/></svg>
|
||
<span data-i18n="stem.lead_vocals">Lead Vocals</span>
|
||
</em>
|
||
<b class="stem-mute" data-stem="lead_vocals" role="button" tabindex="0" aria-label="Mute Lead Vocals" aria-pressed="false">M</b>
|
||
<b class="stem-solo" data-stem="lead_vocals" role="button" tabindex="0" aria-label="Solo Lead Vocals" aria-pressed="false">S</b>
|
||
<button class="stem-monitor" type="button" data-stem="lead_vocals" aria-label="Solo only Lead Vocals"></button>
|
||
<i class="mini-meter"></i>
|
||
</span>
|
||
<span class="backing_vocals hidden" data-stem="backing_vocals">
|
||
<i class="drag-handle"></i>
|
||
<em>
|
||
<svg class="stem-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M8 3a3 3 0 0 1 3 3v6a3 3 0 0 1-6 0V6a3 3 0 0 1 3-3Z"/><path d="M16 6a3 3 0 0 1 3 3v3a3 3 0 0 1-3 3"/><path d="M5 12v1a6 6 0 0 0 6 6h1"/><path d="M11 19v2"/></svg>
|
||
<span data-i18n="stem.backing_vocals">Backing Vocals</span>
|
||
</em>
|
||
<b class="stem-mute" data-stem="backing_vocals" role="button" tabindex="0" aria-label="Mute Backing Vocals" aria-pressed="false">M</b>
|
||
<b class="stem-solo" data-stem="backing_vocals" role="button" tabindex="0" aria-label="Solo Backing Vocals" aria-pressed="false">S</b>
|
||
<button class="stem-monitor" type="button" data-stem="backing_vocals" aria-label="Solo only Backing Vocals"></button>
|
||
<i class="mini-meter"></i>
|
||
</span>
|
||
<span class="drums" data-stem="drums">
|
||
<i class="drag-handle"></i>
|
||
<em>
|
||
<svg class="stem-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" aria-hidden="true"><path d="M7 13.5a5 5 0 0 0 10 0"/><path d="M7 13.5h10"/><circle cx="9" cy="10" r="2.5"/><circle cx="15" cy="10" r="2.5"/></svg>
|
||
<span data-i18n="stem.drums">Drums</span>
|
||
</em>
|
||
<b class="stem-mute" data-stem="drums" role="button" tabindex="0" aria-label="Mute Drums" aria-pressed="false">M</b>
|
||
<b class="stem-solo" data-stem="drums" role="button" tabindex="0" aria-label="Solo Drums" aria-pressed="false">S</b>
|
||
<button class="stem-monitor" type="button" data-stem="drums" aria-label="Solo only Drums"></button>
|
||
<i class="mini-meter"></i>
|
||
</span>
|
||
<span class="bass" data-stem="bass">
|
||
<i class="drag-handle"></i>
|
||
<em>
|
||
<svg class="stem-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" aria-hidden="true"><path d="M16.5 3h4v5h-3"/><path d="M17.5 5.5 9.8 13.2"/><path d="M10 13c1.6 2.2 1.1 5.1-1.2 6.5-2.1 1.3-5 .5-6-1.6-.9-1.9-.1-4.1 1.8-5 .9-.4 1.8-.4 2.8-.1.1-1.1.6-2.1 1.6-2.6 1.2-.6 2.6-.1 3.2 1.1"/></svg>
|
||
<span data-i18n="stem.bass">Bass</span>
|
||
</em>
|
||
<b class="stem-mute" data-stem="bass" role="button" tabindex="0" aria-label="Mute Bass" aria-pressed="false">M</b>
|
||
<b class="stem-solo" data-stem="bass" role="button" tabindex="0" aria-label="Solo Bass" aria-pressed="false">S</b>
|
||
<button class="stem-monitor" type="button" data-stem="bass" aria-label="Solo only Bass"></button>
|
||
<i class="mini-meter"></i>
|
||
</span>
|
||
<span class="guitar" data-stem="guitar">
|
||
<i class="drag-handle"></i>
|
||
<em>
|
||
<svg class="stem-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" aria-hidden="true"><path d="M16 4.5 20 2l2 2-2.5 4"/><path d="M18.2 5.8 10.2 13.8"/><circle cx="7" cy="16.4" r="1.4"/></svg>
|
||
<span data-i18n="stem.guitar">Guitar</span>
|
||
</em>
|
||
<b class="stem-mute" data-stem="guitar" role="button" tabindex="0" aria-label="Mute Guitar" aria-pressed="false">M</b>
|
||
<b class="stem-solo" data-stem="guitar" role="button" tabindex="0" aria-label="Solo Guitar" aria-pressed="false">S</b>
|
||
<button class="stem-monitor" type="button" data-stem="guitar" aria-label="Solo only Guitar"></button>
|
||
<i class="mini-meter"></i>
|
||
</span>
|
||
<span class="piano" data-stem="piano">
|
||
<i class="drag-handle"></i>
|
||
<em>
|
||
<svg class="stem-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M7 5v14"/><path d="M12 5v14"/><path d="M17 5v14"/></svg>
|
||
<span data-i18n="stem.piano">Piano</span>
|
||
</em>
|
||
<b class="stem-mute" data-stem="piano" role="button" tabindex="0" aria-label="Mute Piano" aria-pressed="false">M</b>
|
||
<b class="stem-solo" data-stem="piano" role="button" tabindex="0" aria-label="Solo Piano" aria-pressed="false">S</b>
|
||
<button class="stem-monitor" type="button" data-stem="piano" aria-label="Solo only Piano"></button>
|
||
<i class="mini-meter"></i>
|
||
</span>
|
||
<span class="other" data-stem="other">
|
||
<i class="drag-handle"></i>
|
||
<em>
|
||
<svg class="stem-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" aria-hidden="true"><path d="M4 13v-2"/><path d="M8 17V7"/><path d="M12 21V3"/><path d="M16 17V7"/><path d="M20 13v-2"/></svg>
|
||
<span data-i18n="stem.others">Others</span>
|
||
</em>
|
||
<b class="stem-mute" data-stem="other" role="button" tabindex="0" aria-label="Mute Other" aria-pressed="false">M</b>
|
||
<b class="stem-solo" data-stem="other" role="button" tabindex="0" aria-label="Solo Other" aria-pressed="false">S</b>
|
||
<button class="stem-monitor" type="button" data-stem="other" aria-label="Solo only Other"></button>
|
||
<i class="mini-meter"></i>
|
||
</span>
|
||
</div>
|
||
|
||
<!-- Mixer faders (JS populates) -->
|
||
<div id="mixer" class="mixer-column"></div>
|
||
</div>
|
||
|
||
<!-- Waveform panel -->
|
||
<div class="daw-wave-panel wave-editor surface-panel">
|
||
<div id="job" class="job hidden" role="status" aria-live="polite">
|
||
<div class="job-header">
|
||
<div id="job-title" class="title"></div>
|
||
<div id="job-stage" class="stage"></div>
|
||
<button id="job-cancel" class="cancel-btn hidden" type="button" aria-label="Cancel job" data-i18n-aria-label="job.cancelAria" data-i18n="job.cancel">Cancel</button>
|
||
</div>
|
||
<progress id="progress" max="100" value="0"></progress>
|
||
<div id="job-detail" class="job-detail" aria-live="polite"></div>
|
||
</div>
|
||
<div class="beatgrid-toolbar hidden" id="beatgrid-toolbar" role="toolbar" aria-label="Beat grid editor" data-i18n-aria-label="beatgrid.toolbarAria">
|
||
<span class="bg-tool-label" data-i18n="beatgrid.gridLabel">GRID</span>
|
||
<div class="bg-tools" role="radiogroup" aria-label="Tool" data-i18n-aria-label="beatgrid.toolAria">
|
||
<button class="bg-btn active" id="bg-tool-move" type="button" role="radio" aria-checked="true" title="Drag beats" data-i18n-title="beatgrid.moveTitle" data-i18n="beatgrid.move">Move</button>
|
||
<button class="bg-btn" id="bg-tool-insert" type="button" role="radio" aria-checked="false" title="Click to add a beat" data-i18n-title="beatgrid.addTitle" data-i18n="beatgrid.add">Add</button>
|
||
<button class="bg-btn" id="bg-tool-delete" type="button" role="radio" aria-checked="false" title="Click a beat to remove it" data-i18n-title="beatgrid.deleteTitle" data-i18n="beatgrid.delete">Delete</button>
|
||
<button class="bg-btn" id="bg-tool-bar" type="button" role="radio" aria-checked="false" title="Click a beat to mark it as a downbeat" data-i18n-title="beatgrid.barLineTitle" data-i18n="beatgrid.barLine">Bar line</button>
|
||
</div>
|
||
<label class="bg-check"><input type="checkbox" id="bg-ripple" checked> <span data-i18n="beatgrid.ripple">Ripple</span></label>
|
||
<label class="bg-check"><input type="checkbox" id="bg-snap" checked> <span data-i18n="beatgrid.snap">Snap</span></label>
|
||
<label class="bg-check bg-meter"><span data-i18n="beatgrid.bar">Bar</span>
|
||
<input type="number" id="bg-barlen" min="1" max="32" step="1" value="4" aria-label="Beats per bar in this region" data-i18n-aria-label="beatgrid.barAria">
|
||
</label>
|
||
<div class="bg-spacer"></div>
|
||
<button class="bg-btn" id="bg-undo" type="button" title="Undo (Cmd/Ctrl+Z)" disabled data-i18n-title="beatgrid.undoTitle" data-i18n="beatgrid.undo">Undo</button>
|
||
<button class="bg-btn" id="bg-redo" type="button" title="Redo (Cmd/Ctrl+Shift+Z)" disabled data-i18n-title="beatgrid.redoTitle" data-i18n="beatgrid.redo">Redo</button>
|
||
<button class="bg-btn bg-danger" id="bg-reset" type="button" title="Discard all edits and restore the detected grid" data-i18n-title="beatgrid.resetTitle" data-i18n="beatgrid.reset">Reset</button>
|
||
<button class="bg-btn bg-primary" id="bg-done" type="button" data-i18n="beatgrid.done">Done</button>
|
||
<span class="bg-hint" id="bg-hint" data-i18n="beatgrid.hint">Drag a beat to retime the region. Alt-drag moves one beat.</span>
|
||
</div>
|
||
<div class="wave-scroll" id="wave-scroll">
|
||
<div class="wave-canvas" id="wave-canvas">
|
||
<div class="waves-column">
|
||
<div id="waves-grid" class="waves-grid" aria-hidden="true"></div>
|
||
<div id="multitrack-container"></div>
|
||
<div id="loop-region" class="loop-region hidden" aria-hidden="true"></div>
|
||
<canvas id="beatgrid-canvas" class="beatgrid-canvas hidden" aria-hidden="true"></canvas>
|
||
</div>
|
||
<div class="wave-loading-overlay hidden" id="waveLoadingOverlay" aria-label="Loading waveform" data-i18n-aria-label="wave.loadingAria" aria-live="polite">
|
||
<div class="wave-loading-lines" aria-hidden="true">
|
||
<i></i><i></i><i></i><i></i><i></i><i></i>
|
||
</div>
|
||
<p id="waveLoadingPhrase" class="wave-loading-msg"></p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /.daw-content -->
|
||
|
||
</div><!-- /#lanes -->
|
||
|
||
<!-- ══ FOOTER — sibling of #lanes inside .daw-main-col, not clipped ══ -->
|
||
<footer class="daw-footer">
|
||
|
||
<!-- Left column: what is loaded, and what leaves the app. Sits in the
|
||
gutter under the stems/mixer panel and carries its width, so it
|
||
reads as that column's footer rather than free-floating text. -->
|
||
<div class="footer-track">
|
||
<div class="footer-track-head">
|
||
<div class="daw-cover footer-cover" id="np-art">
|
||
<div class="np-art-placeholder" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.5">
|
||
<path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/>
|
||
</svg>
|
||
</div>
|
||
<img id="np-thumb" class="np-art-img" alt="" referrerpolicy="no-referrer" />
|
||
</div>
|
||
<div class="footer-track-info">
|
||
<div class="daw-track-title footer-track-title" id="title">—</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="daw-track-meta-line">
|
||
<span class="num" id="t-meta-duration">—</span>
|
||
<span class="daw-track-sep" aria-hidden="true">·</span>
|
||
<span id="t-stems-chip">— stems</span>
|
||
<span class="daw-track-sep" aria-hidden="true">·</span>
|
||
<span id="track-source">—</span>
|
||
<span class="daw-track-sep" aria-hidden="true">·</span>
|
||
<span id="track-quality">—</span>
|
||
<span class="daw-track-sep" aria-hidden="true">·</span>
|
||
<span><span data-i18n="footer.extractedLabel">Extracted</span> <span id="track-extracted">—</span></span>
|
||
</div>
|
||
|
||
<!-- Hidden spans for JS compat: not shown, but still written to
|
||
(t-time is superseded here by the Position group in the
|
||
controls row -- keeping it live would just duplicate that
|
||
readout next to the title). -->
|
||
<div style="display:none" aria-hidden="true">
|
||
<span id="t-time"></span><span id="t-bpm"></span><span id="t-key"></span><span id="track-artist"></span>
|
||
</div>
|
||
|
||
<div class="footer-row-actions">
|
||
<button class="daw-fav-btn" id="fav-btn" aria-label="Toggle favorite" data-i18n-aria-label="fav.toggleAria" aria-pressed="false">
|
||
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
||
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
|
||
</svg>
|
||
</button>
|
||
<div class="footer-chip-wrap" id="footer-export-wrap">
|
||
<button class="footer-chip footer-chip-primary" id="t-export-btn" type="button" aria-haspopup="menu" aria-expanded="false" aria-controls="t-export-panel">
|
||
<svg class="export-dl-icon" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
|
||
</svg>
|
||
<svg class="export-spinner" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" aria-hidden="true">
|
||
<circle cx="12" cy="12" r="9" stroke-opacity="0.25"/><path d="M21 12a9 9 0 0 0-9-9"/>
|
||
</svg>
|
||
<span class="export-btn-label" id="t-export-label" data-i18n="export.mix">Export Mix</span>
|
||
<svg class="export-caret" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true">
|
||
<polyline points="6 9 12 15 18 9"/>
|
||
</svg>
|
||
</button>
|
||
<div class="footer-chip-panel chip-panel-export hidden" id="t-export-panel" role="menu" aria-label="Export options" data-i18n-aria-label="export.optionsAria">
|
||
<div class="export-format-toggle" role="radiogroup" aria-label="Export format" data-i18n-aria-label="export.formatAria">
|
||
<button class="export-fmt active" id="t-fmt-wav" type="button" role="radio" aria-checked="true">WAV</button>
|
||
<button class="export-fmt" id="t-fmt-mp3" type="button" role="radio" aria-checked="false">MP3</button>
|
||
<button class="export-fmt" id="t-fmt-flac" type="button" role="radio" aria-checked="false">FLAC</button>
|
||
<button class="export-fmt" id="t-fmt-ogg" type="button" role="radio" aria-checked="false">OGG</button>
|
||
<button class="export-fmt export-fmt-video" id="t-fmt-mp4" type="button" role="radio" aria-checked="false">MP4</button>
|
||
</div>
|
||
<label class="export-click-opt" id="t-export-click-wrap" title="Mix the click track into the exported file" data-i18n-title="export.includeClickTitle">
|
||
<input type="checkbox" id="t-export-click">
|
||
<span data-i18n="export.includeClick">Include click track</span>
|
||
</label>
|
||
<label class="export-click-opt" id="t-export-count-in-wrap" title="Prepend one bar of count-in click before the audio" data-i18n-title="export.addCountInTitle">
|
||
<input type="checkbox" id="t-export-count-in">
|
||
<span data-i18n="export.addCountIn">Add count-in (1 bar)</span>
|
||
</label>
|
||
<button class="chip-panel-item export-item" id="t-export-mix" type="button" role="menuitem">
|
||
<span class="chip-item-icon" aria-hidden="true">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><line x1="4" y1="9" x2="4" y2="15"/><line x1="9" y1="5" x2="9" y2="19"/><line x1="14" y1="8" x2="14" y2="16"/><line x1="19" y1="4" x2="19" y2="20"/></svg>
|
||
</span>
|
||
<span class="chip-item-text">
|
||
<span class="chip-item-title" data-i18n="export.mix">Export Mix</span>
|
||
<span class="chip-item-desc" data-i18n="export.mixDesc">Export the mixed audio</span>
|
||
</span>
|
||
<svg class="chip-item-check" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg>
|
||
</button>
|
||
<button class="chip-panel-item export-item" id="t-export-stems" type="button" role="menuitem">
|
||
<span class="chip-item-icon" aria-hidden="true">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><polygon points="12 2 22 8.5 12 15 2 8.5 12 2"/><polyline points="2 15.5 12 22 22 15.5"/></svg>
|
||
</span>
|
||
<span class="chip-item-text">
|
||
<span class="chip-item-title" data-i18n="export.allStems">Export All Stems</span>
|
||
<span class="chip-item-desc" data-i18n="export.allStemsDesc">All stems as a .zip</span>
|
||
</span>
|
||
</button>
|
||
<button class="chip-panel-item export-item" id="t-export-region" type="button" role="menuitem" aria-disabled="true">
|
||
<span class="chip-item-icon" aria-hidden="true">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><line x1="20" y1="4" x2="8.12" y2="15.88"/><line x1="14.47" y1="14.48" x2="20" y2="20"/><line x1="8.12" y1="8.12" x2="12" y2="12"/></svg>
|
||
</span>
|
||
<span class="chip-item-text">
|
||
<span class="chip-item-title" data-i18n="export.currentRegion">Export Current Region</span>
|
||
<span class="chip-item-desc" data-i18n="export.currentRegionDesc">Export selected region</span>
|
||
</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div><!-- /.footer-track -->
|
||
|
||
<!-- Right column: everything time-related, aligned with the lanes. -->
|
||
<div class="footer-main">
|
||
|
||
<!-- Every playback and click control, in labelled clusters -->
|
||
<div class="footer-row-controls">
|
||
<div class="footer-group footer-group-transport">
|
||
<span class="footer-group-label" data-i18n="transport.group">Transport</span>
|
||
<div class="footer-group-body">
|
||
<button class="daw-iconbtn btn-transport" id="t-stop" type="button" title="Stop" aria-label="Stop" data-i18n-title="transport.stop" data-i18n-aria-label="transport.stop">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||
<rect x="6" y="6" width="12" height="12"/>
|
||
</svg>
|
||
</button>
|
||
<button class="daw-play-btn btn-transport" id="t-play" type="button" aria-label="Play / Pause" data-i18n-aria-label="transport.playPause">
|
||
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true" style="margin-left:2px">
|
||
<path d="M6 4l14 8L6 20z"/>
|
||
</svg>
|
||
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" class="pause-icon" aria-hidden="true" style="display:none">
|
||
<rect x="6" y="5" width="4" height="14"/><rect x="14" y="5" width="4" height="14"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<span class="footer-divider" aria-hidden="true"></span>
|
||
|
||
<!-- Loop lives with the readout it acts on: the loop is a position,
|
||
not a transport mode (design 1b). -->
|
||
<div class="footer-group footer-group-position">
|
||
<span class="footer-group-label" data-i18n="position.group">Position</span>
|
||
<div class="footer-group-body">
|
||
<span class="num footer-elapsed" id="footer-time-elapsed">0:00</span>
|
||
<span class="footer-time-sep">/</span>
|
||
<span class="num footer-total" id="footer-time-total">0:00</span>
|
||
<button class="daw-iconbtn btn-transport loop" id="t-loop" type="button" title="Loop the selected position (L)" aria-label="Loop" data-i18n-title="position.loopTitle">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||
<path d="M17 2l4 4-4 4 M3 12V8a2 2 0 0 1 2-2h16 M7 22l-4-4 4-4 M21 12v4a2 2 0 0 1-2 2H3"/>
|
||
</svg>
|
||
<span class="loop-btn-label" data-i18n="position.loopLabel">Loop position</span>
|
||
</button>
|
||
<div class="footer-loop-times" title="Exact loop start / end (mm:ss.mmm or seconds)" data-i18n-title="position.loopExactTitle">
|
||
<input type="text" id="t-loop-start" class="loop-time-input num" inputmode="decimal" spellcheck="false" autocomplete="off" aria-label="Loop start" data-i18n-aria-label="position.loopStartAria" value="00:00.000">
|
||
<span class="loop-times-sep" aria-hidden="true">-</span>
|
||
<input type="text" id="t-loop-end" class="loop-time-input num" inputmode="decimal" spellcheck="false" autocomplete="off" aria-label="Loop end" data-i18n-aria-label="position.loopEndAria" value="00:00.000">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<span class="footer-divider" aria-hidden="true"></span>
|
||
|
||
<div class="footer-group footer-group-speed">
|
||
<span class="footer-group-label" data-i18n="speed.group">Speed</span>
|
||
<div class="footer-group-body">
|
||
<div class="footer-seg speed-group" id="t-speed-wrap" role="radiogroup" aria-label="Playback speed" data-i18n-aria-label="speed.ariaLabel">
|
||
<button class="speed-btn" id="t-speed-025" type="button" role="radio" aria-checked="false" data-speed="0.25">0.25x</button>
|
||
<button class="speed-btn" id="t-speed-05" type="button" role="radio" aria-checked="false" data-speed="0.5">0.5x</button>
|
||
<button class="speed-btn active" id="t-speed-1" type="button" role="radio" aria-checked="true" data-speed="1">1x</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<span class="footer-divider" aria-hidden="true"></span>
|
||
|
||
<div class="footer-group footer-group-click" id="t-metro-wrap">
|
||
<span class="footer-group-label"><span data-i18n="click.group">Click track</span> <span class="alpha-badge" data-i18n="click.alpha">Alpha</span></span>
|
||
<div class="footer-group-body">
|
||
<button class="click-toggle-btn" id="t-metro" type="button" title="Click track (K)" aria-label="Click track" data-i18n-title="click.toggleTitle" data-i18n-aria-label="click.toggleAria" aria-pressed="false" disabled>
|
||
<svg class="click-metro-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
||
<path d="M9 3h6l4 18H5z"/><path d="M15.5 9 8 15"/>
|
||
</svg>
|
||
<span data-i18n="click.on">ON</span>
|
||
</button>
|
||
<div class="footer-metro-panel hidden" id="t-metro-panel" role="group" aria-label="Click track options" data-i18n-aria-label="click.optionsAria">
|
||
<button class="click-toggle-btn" id="t-metro-countin" type="button" title="Count one bar of click before the song starts when you press play" data-i18n-title="click.countInTitle" aria-pressed="false">
|
||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
||
<path d="M12 3a9 9 0 1 0 9 9"/><path d="M12 7v5l3 2"/>
|
||
</svg>
|
||
<span data-i18n="click.countIn">Count-in</span>
|
||
</button>
|
||
<span class="metro-select-wrap">
|
||
<select id="t-metro-bar" class="metro-select" aria-label="Accent every N beats" data-i18n-aria-label="click.barAccentAria">
|
||
<option value="-1" selected data-i18n="click.auto">Auto (detected)</option>
|
||
<option value="0" data-i18n="click.off">Off</option>
|
||
<option value="2">2/4</option>
|
||
<option value="3">3/4</option>
|
||
<option value="4">4/4</option>
|
||
<option value="6">6/8</option>
|
||
</select>
|
||
</span>
|
||
<button class="click-toggle-btn" id="t-metro-edit" type="button" title="Edit the beat grid" data-i18n-title="click.editTitle" aria-pressed="false">
|
||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true">
|
||
<path d="M4 4v16" stroke-width="2.6"/><path d="M10 6v12"/><path d="M15 6v12"/><path d="M20 6v12"/>
|
||
</svg>
|
||
<span data-i18n="click.grid">Grid</span>
|
||
</button>
|
||
<div class="footer-seg metro-mult" role="radiogroup" aria-label="Click rate" data-i18n-aria-label="click.rateAria" title="Halve or double the click rate" data-i18n-title="click.rateTitle">
|
||
<button class="metro-mult-btn" id="t-metro-half" type="button" role="radio" aria-checked="false" title="Half time" data-i18n-title="click.half">/2</button>
|
||
<button class="metro-mult-btn active" id="t-metro-one" type="button" role="radio" aria-checked="true" title="Detected tempo" data-i18n-title="click.detected">1x</button>
|
||
<button class="metro-mult-btn" id="t-metro-double" type="button" role="radio" aria-checked="false" title="Double time" data-i18n-title="click.double">x2</button>
|
||
</div>
|
||
<div class="metro-row metro-vol-row">
|
||
<svg class="metro-vol-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
||
<path d="M11 5 6 9H2v6h4l5 4V5Z"/><path d="M15.5 8.5a5 5 0 0 1 0 7"/>
|
||
</svg>
|
||
<input type="range" id="t-metro-vol" min="0" max="1" step="0.05" value="0.6" aria-label="Click volume" data-i18n-aria-label="click.volumeAria">
|
||
<span class="metro-val num" id="t-metro-vol-label">60%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div><!-- /.footer-row-controls -->
|
||
|
||
<!-- The timeline the clusters above act on -->
|
||
<div class="footer-wave-region">
|
||
<div class="footer-wave-panel">
|
||
<div class="footer-wave-ticks" id="footer-wave-ticks" aria-hidden="true"></div>
|
||
<div class="footer-wave-bar">
|
||
<canvas id="footer-waveform" aria-hidden="true"></canvas>
|
||
<div class="footer-scrub" id="footer-scrub" role="slider" aria-label="Seek" data-i18n-aria-label="footer.seekAria" tabindex="0">
|
||
<div class="footer-scrub-fill" id="footer-scrub-fill"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="metro-note" id="t-metro-note"></div>
|
||
</div>
|
||
|
||
</div><!-- /.footer-main -->
|
||
|
||
</footer>
|
||
|
||
</div><!-- /.daw-main-col -->
|
||
</div><!-- /.daw-body -->
|
||
|
||
</div><!-- /.daw -->
|
||
|
||
<!-- About dialog -->
|
||
<div class="about-backdrop hidden" id="aboutDialog" role="dialog" aria-modal="true" aria-labelledby="aboutTitle">
|
||
<div class="about-card">
|
||
<button class="about-close" id="aboutClose" type="button" aria-label="Close about dialog" data-i18n-aria-label="about.closeAria">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 6 6 18M6 6l12 12"/></svg>
|
||
</button>
|
||
|
||
<div class="about-logo" aria-hidden="true">
|
||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none">
|
||
<rect x="2" y="10" width="3" height="8" rx="1.5" fill="currentColor" opacity=".5"/>
|
||
<rect x="7" y="6" width="3" height="16" rx="1.5" fill="currentColor" opacity=".7"/>
|
||
<rect x="12" y="2" width="4" height="24" rx="2" fill="currentColor"/>
|
||
<rect x="18" y="6" width="3" height="16" rx="1.5" fill="currentColor" opacity=".7"/>
|
||
<rect x="23" y="10" width="3" height="8" rx="1.5" fill="currentColor" opacity=".5"/>
|
||
</svg>
|
||
</div>
|
||
|
||
<h2 id="aboutTitle" data-i18n="about.title">StemDeck</h2>
|
||
<p class="about-tagline" data-i18n="about.tagline">Open source. No subscriptions. Built by musicians, for musicians.</p>
|
||
<span class="about-version-badge" id="aboutVersion">…</span>
|
||
|
||
<div class="about-primary-links">
|
||
<a class="about-link about-link-primary" href="https://stemdeck.app" target="_blank" rel="noopener noreferrer">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
|
||
<span data-i18n="about.website">Website</span>
|
||
</a>
|
||
<a class="about-link about-link-secondary" href="https://github.com/stemdeckapp/stemdeck" target="_blank" rel="noopener noreferrer">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>
|
||
GitHub
|
||
</a>
|
||
</div>
|
||
|
||
<div class="about-divider"></div>
|
||
|
||
<div class="about-socials">
|
||
<a class="about-social-btn" href="https://discord.gg/JGk7FdZb9N" target="_blank" rel="noopener noreferrer" title="Discord" aria-label="Discord">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03z"/></svg>
|
||
</a>
|
||
<a class="about-social-btn" href="https://www.reddit.com/r/StemDeckApp/" target="_blank" rel="noopener noreferrer" title="Reddit" aria-label="Reddit">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 0 0-.232-.095z"/></svg>
|
||
</a>
|
||
<a class="about-social-btn" href="https://www.instagram.com/stemdeck" target="_blank" rel="noopener noreferrer" title="Instagram" aria-label="Instagram">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 1 0 0 12.324 6.162 6.162 0 0 0 0-12.324zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm6.406-11.845a1.44 1.44 0 1 0 0 2.881 1.44 1.44 0 0 0 0-2.881z"/></svg>
|
||
</a>
|
||
<a class="about-social-btn" href="https://x.com/StemDeckApp" target="_blank" rel="noopener noreferrer" title="X" aria-label="X">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.747l7.73-8.835L1.254 2.25H8.08l4.253 5.622 5.911-5.622zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Our Friends dialog -->
|
||
<div class="about-backdrop hidden" id="friendsDialog" role="dialog" aria-modal="true" aria-labelledby="friendsTitle">
|
||
<div class="about-card friends-card">
|
||
<button class="about-close" id="friendsClose" type="button" aria-label="Close friends dialog" data-i18n-aria-label="friends.closeAria">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 6 6 18M6 6l12 12"/></svg>
|
||
</button>
|
||
<div class="about-logo" aria-hidden="true">
|
||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
|
||
<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/>
|
||
</svg>
|
||
</div>
|
||
<h2 id="friendsTitle" data-i18n="friends.title">We Recommend</h2>
|
||
<p class="about-tagline" data-i18n="friends.tagline">Wonderful people doing beautiful work. Go meet them ❤️</p>
|
||
<div class="lib-friends-grid" id="friendsDialogGrid"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Release notes dialog (opened from the "New release available" card) -->
|
||
<div class="about-backdrop hidden" id="releaseDialog" role="dialog" aria-modal="true" aria-labelledby="releaseTitle">
|
||
<div class="about-card release-card">
|
||
<button class="about-close" id="releaseClose" type="button" aria-label="Close release dialog" data-i18n-aria-label="release.closeAria">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 6 6 18M6 6l12 12"/></svg>
|
||
</button>
|
||
<div class="about-logo" aria-hidden="true">
|
||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 2l2.4 7.4H22l-6.2 4.5 2.4 7.4L12 17l-6.2 4.3 2.4-7.4L2 9.4h7.6z"/></svg>
|
||
</div>
|
||
<h2 id="releaseTitle" data-i18n="release.title">New release available</h2>
|
||
<span class="about-version-badge" id="releaseVersion">…</span>
|
||
<div class="release-notes" id="releaseNotes"></div>
|
||
<!-- Server/Docker mode: updating means pulling a new image, not a file download. -->
|
||
<div class="release-docker hidden" id="releaseDocker">
|
||
<p class="release-docker-label" data-i18n="release.updateServer">Update your server</p>
|
||
<p class="release-docker-text" data-i18n="release.pullImage">Pull the new image and restart the container:</p>
|
||
<pre class="release-docker-cmd"><code id="releaseDockerCmd"></code></pre>
|
||
<p class="release-docker-note" data-i18n="release.unraidNote">On Unraid, update via the Community Applications template instead.</p>
|
||
</div>
|
||
<!-- Windows desktop only: download + apply in place instead of a browser download. -->
|
||
<div class="release-inapp hidden" id="releaseInapp">
|
||
<div class="release-inapp-progress hidden" id="releaseInappProgress">
|
||
<div class="release-inapp-progress-bar"><div class="release-inapp-progress-fill" id="releaseInappProgressFill"></div></div>
|
||
<p class="release-inapp-progress-text" id="releaseInappProgressText"></p>
|
||
</div>
|
||
<p class="release-inapp-error hidden" id="releaseInappError"></p>
|
||
</div>
|
||
<div class="about-primary-links">
|
||
<a class="about-link about-link-primary" id="releaseDownload" href="#" target="_blank" rel="noopener noreferrer" data-i18n="release.download">Download</a>
|
||
<button class="about-link about-link-accent hidden" id="releaseDownloadApp" type="button" data-i18n="release.updateNow">Update now</button>
|
||
<button class="about-link about-link-accent hidden" id="releaseApplyUpdate" type="button" data-i18n="release.restartUpdate">Restart to update</button>
|
||
<a class="about-link about-link-secondary" id="releaseAllLink" href="https://github.com/stemdeckapp/stemdeck/releases" target="_blank" rel="noopener noreferrer" data-i18n="release.allReleases">All releases</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Failure detail dialog (opened from a notification card). The report
|
||
link is a plain external anchor: the global handler in main.js routes
|
||
it through Tauri's open_url on desktop and a new tab in a browser. -->
|
||
<div class="about-backdrop hidden" id="failureDialog" role="dialog" aria-modal="true" aria-labelledby="failureTitle">
|
||
<div class="about-card failure-card">
|
||
<button class="about-close" id="failureClose" type="button" aria-label="Close failure dialog" data-i18n-aria-label="failure.closeAria">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 6 6 18M6 6l12 12"/></svg>
|
||
</button>
|
||
<div class="about-logo failure-logo" aria-hidden="true">
|
||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||
<path d="M12 9v4 M12 17h.01 M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/>
|
||
</svg>
|
||
</div>
|
||
<h2 id="failureTitle" data-i18n="failure.title">Something failed</h2>
|
||
<span class="about-version-badge failure-when" id="failureWhen"></span>
|
||
<p class="failure-message" id="failureMessage"></p>
|
||
<p class="failure-hint" data-i18n="failure.hint">These details go in the report. Your track title and source link are not included - add them yourself if they help. Clicking a button below copies this to your clipboard.</p>
|
||
<pre class="failure-tech"><code id="failureTech"></code></pre>
|
||
<button class="failure-logs-btn" id="failureIncludeLogs" type="button" data-i18n="failure.includeLogs">Include recent logs</button>
|
||
<div class="about-primary-links">
|
||
<a class="about-link about-link-primary" id="failureReport" target="_blank" rel="noopener noreferrer" data-i18n="failure.reportGithub">Report on GitHub</a>
|
||
<a class="about-link about-link-secondary" id="failureReportDiscord" target="_blank" rel="noopener noreferrer" data-i18n="failure.reportDiscord">Report on Discord</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script type="module" src="/js/main.js"></script>
|
||
<script type="module" src="/js/ui-chrome.js"></script>
|
||
</body>
|
||
</html>
|