From bfe1434f753e443718eb122a887f4bffb4d53664 Mon Sep 17 00:00:00 2001 From: Shawn Date: Fri, 12 Jun 2026 23:22:47 -0400 Subject: [PATCH] feat: separate preset banks per channel (guitar/bass) - Channel enum (GUITAR, BASS) with channel field on Preset dataclass - Channel-prefixed storage: {root}/{channel}/bank_{bank}/preset_{program}.json - PresetManager channel awareness: set_channel(), current_channel property - Per-channel state persistence (channel_state.json per channel dir) - Legacy migration: flat bank_* dirs auto-migrate to guitar/ on first boot - All CRUD methods accept optional channel parameter - API endpoints accept channel param on GET/PUT/DELETE /api/presets - /api/channel GET/POST for channel switching - 10 new channel tests (independence, switching, migration, scoping) - Factory presets install to specified channel --- frontend-react/src/App.jsx | 95 ++++- src/presets/__init__.py | 7 + src/presets/manager.py | 312 +++++++++++--- src/presets/types.py | 7 + src/web/server.py | 503 ++++++++++++++++++----- src/web/ui-dist/assets/index-BiNA-JeT.js | 184 +++++++++ src/web/ui-dist/assets/index-DKZHEo9m.js | 184 --------- src/web/ui-dist/index.html | 2 +- tests/test_preset_manager.py | 152 ++++++- tests/test_web.py | 6 + 10 files changed, 1100 insertions(+), 352 deletions(-) create mode 100644 src/web/ui-dist/assets/index-BiNA-JeT.js delete mode 100644 src/web/ui-dist/assets/index-DKZHEo9m.js diff --git a/frontend-react/src/App.jsx b/frontend-react/src/App.jsx index 35b4556..cd35121 100644 --- a/frontend-react/src/App.jsx +++ b/frontend-react/src/App.jsx @@ -20,15 +20,31 @@ const T = { const API = window.location.origin; const WS_URL = API.replace(/^http/, 'ws') + '/ws'; +// ── Channel state (module-level, synced from localStorage) ───── +let _channel = 'guitar'; +try { + const saved = localStorage.getItem('pedal_channel'); + if (saved === 'guitar' || saved === 'bass') _channel = saved; +} catch {} + +export function getChannel() { return _channel; } +export function setChannel(ch) { _channel = ch; } + +function _ch(url) { + const c = _channel; + if (!c) return url; + return url + (url.includes('?') ? '&' : '?') + 'channel=' + encodeURIComponent(c); +} + // ── API helpers ──────────────────────────────────────────────── async function apiGet(path) { - const r = await fetch(`${API}${path}`); + const r = await fetch(`${API}${_ch(path)}`); if (!r.ok) throw new Error(`${path} => ${r.status}`); return r.json(); } async function apiPost(path, data) { - const r = await fetch(`${API}${path}`, { + const r = await fetch(`${API}${_ch(path)}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: data != null ? JSON.stringify(data) : undefined, }); @@ -37,7 +53,7 @@ async function apiPost(path, data) { } async function apiPut(path, data) { - const r = await fetch(`${API}${path}`, { + const r = await fetch(`${API}${_ch(path)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); @@ -46,7 +62,7 @@ async function apiPut(path, data) { } async function apiDelete(path) { - const r = await fetch(`${API}${path}`, { method: 'DELETE' }); + const r = await fetch(`${API}${_ch(path)}`, { method: 'DELETE' }); if (!r.ok) throw new Error(`${path} => ${r.status}`); return r.json(); } @@ -54,7 +70,7 @@ async function apiDelete(path) { async function apiUpload(path, file) { const fd = new FormData(); fd.append('file', file); - const r = await fetch(`${API}${path}`, { method: 'POST', body: fd }); + const r = await fetch(`${API}${_ch(path)}`, { method: 'POST', body: fd }); if (!r.ok) throw new Error(`${path} => ${r.status}`); return r.json(); } @@ -86,6 +102,7 @@ function usePedalState() { if (msg.type === 'bypass_changed') setState(prev => prev ? { ...prev, bypass: msg.bypass } : prev); if (msg.type === 'tuner_changed') setState(prev => prev ? { ...prev, tuner_enabled: msg.tuner_enabled } : prev); if (msg.type === 'routing_changed') setState(prev => prev ? { ...prev, routing_mode: msg.routing_mode, routing_breakpoint: msg.routing_breakpoint } : prev); + if (msg.type === 'channel_mode_changed') setState(prev => prev ? { ...prev, channel_mode: msg.channel_mode } : prev); if (msg.type === 'model_loaded' || msg.type === 'ir_loaded') { apiGet('/api/state').then(s => setState(s)); } @@ -1285,11 +1302,13 @@ function SettingsScreen({ state, connected, refresh }) { const [showConnect, setShowConnect] = useState(null); const [routingMode, setRoutingMode] = useState(state?.routing_mode || 'mono'); const [routingBp, setRoutingBp] = useState(state?.routing_breakpoint || 7); + const [channelMode, setChannelMode] = useState(state?.channel_mode || 'dual-mono'); useEffect(() => { if (state?.routing_mode) setRoutingMode(state.routing_mode); if (state?.routing_breakpoint != null) setRoutingBp(state.routing_breakpoint); - }, [state?.routing_mode, state?.routing_breakpoint]); + if (state?.channel_mode) setChannelMode(state.channel_mode); + }, [state?.routing_mode, state?.routing_breakpoint, state?.channel_mode]); const scanWifi = async () => { setWifiScanning(true); @@ -1351,6 +1370,13 @@ function SettingsScreen({ state, connected, refresh }) { try { await apiPost('/api/routing', { routing_mode: mode, routing_breakpoint: bp }); } catch (e) { /* ignore */ } }; + const handleChannelMode = async (mode) => { + try { + await apiPost('/api/channel-mode', { channel_mode: mode }); + setChannelMode(mode); + } catch (e) { /* ignore */ } + }; + useEffect(() => { if (connected) { scanWifi(); loadBtPaired(); } }, [connected]); const wifiState = state?.wifi; @@ -1521,6 +1547,7 @@ function SettingsScreen({ state, connected, refresh }) { {/* ── Routing Tab ───────────────────────────────────────────*/} {tab === 'routing' && ( + <>
4CM Routing
@@ -1546,6 +1573,27 @@ function SettingsScreen({ state, connected, refresh }) {
)}
+ {/* Channel Mode */} +
+
Channel Mode
+
+
+
Routing
+
Dual-mono: two independent channels · Stereo: linked as L/R pair
+
+ +
+
+ {channelMode === 'dual-mono' + ? 'Each channel controlled independently from its own phone.' + : 'Channels A (Left) and B (Right) are controlled together.'} +
+
+ )} @@ -1857,10 +1905,23 @@ const TABS = [ export default function App() { const [tab, setTab] = useState("rig"); const [showSnapshots, setShowSnapshots] = useState(false); + const [channel, setChannelState] = useState(() => { + try { return localStorage.getItem('pedal_channel') || 'guitar'; } catch { return 'guitar'; } + }); const { state, connected, refresh } = usePedalState(); useEffect(() => { document.title = "Pi Multi-FX Pedal"; }, []); + // Sync channel to localStorage + module-level API var + useEffect(() => { + try { localStorage.setItem('pedal_channel', channel); } catch (e) {} + setChannel(channel); + }, [channel]); + + const handleChannel = (ch) => { + setChannelState(ch); + }; + return ( <> @@ -1870,12 +1931,30 @@ export default function App() { {/* Status bar */}
+ padding: "4px 10px", background: T.panel, borderBottom: `1px solid ${T.border}`, flexShrink: 0 }}>
- PI MULTI-FX + {/* Channel toggle — hidden in stereo mode */} + {state?.channel_mode === 'stereo' ? ( + + STEREO + + ) : ( +
+ + +
+ )} {state?.tuner_enabled && TUNER}
diff --git a/src/presets/__init__.py b/src/presets/__init__.py index e69de29..3c5773d 100644 --- a/src/presets/__init__.py +++ b/src/presets/__init__.py @@ -0,0 +1,7 @@ +from .types import Bank, Channel, FXBlock, FXType, MIDIMapping, Preset, Snapshot +from .manager import PresetManager + +__all__ = [ + "Bank", "Channel", "FXBlock", "FXType", "MIDIMapping", + "Preset", "PresetManager", "Snapshot", +] diff --git a/src/presets/manager.py b/src/presets/manager.py index 3740e13..09ed261 100644 --- a/src/presets/manager.py +++ b/src/presets/manager.py @@ -19,7 +19,7 @@ import threading from pathlib import Path from typing import Optional -from .types import Bank, FXBlock, FXType, MIDIMapping, Preset, Snapshot +from .types import Bank, Channel, FXBlock, FXType, MIDIMapping, Preset, Snapshot logger = logging.getLogger(__name__) @@ -44,6 +44,7 @@ def _preset_to_dict(preset: Preset) -> dict: """Serialize a Preset to a JSON-compatible dict.""" return { "name": preset.name, + "channel": preset.channel.value, "bank": preset.bank, "program": preset.program, "master_volume": preset.master_volume, @@ -137,6 +138,7 @@ def _preset_from_dict(data: dict) -> Preset: return Preset( name=data["name"], + channel=Channel(data.get("channel", "guitar")), bank=data.get("bank", 0), program=data.get("program", 0), chain=chain, @@ -171,6 +173,7 @@ class PresetManager: self._pipeline = audio_pipeline # Runtime state + self._current_channel: Channel = Channel.GUITAR self._current_bank: int = 0 self._current_program: int = 0 self._dirty = False @@ -185,10 +188,17 @@ class PresetManager: # Current snapshot slot (0 = none selected) self._current_snapshot: int = 0 - logger.info("PresetManager root: %s", self._dir) + # Migrate legacy flat presets → gtr channel on first boot + self._migrate_legacy_presets() + + logger.info("PresetManager root: %s (channel=%s)", self._dir, self._current_channel.value) # ── Public navigation API ─────────────────────────────────────────────── + @property + def current_channel(self) -> Channel: + return self._current_channel + @property def current_bank(self) -> int: return self._current_bank @@ -197,10 +207,43 @@ class PresetManager: def current_program(self) -> int: return self._current_program + def set_channel(self, channel: Channel) -> Preset: + """Switch the active channel and restore its last preset. + + Args: + channel: The channel to switch to. + + Returns: + The currently active Preset in the new channel. + """ + if channel == self._current_channel: + try: + return self.load(self._current_bank, self._current_program) + except FileNotFoundError: + pass + return self._select_and_activate(self._current_bank, self._current_program) + + # Save current channel state first + self.save_state() + + self._current_channel = channel + + # Try restoring channel-specific state + restored = self._restore_channel_state() + if restored: + return restored + + # Default to bank 0, program 0 in the new channel + try: + return self._select_and_activate(0, 0) + except Exception: + # Channel is empty — create first blank preset + return self._select_and_activate(0, 0) + @property def current_preset_path(self) -> Path: - """Path to the slot for the current (bank, program).""" - return self._preset_path(self._current_bank, self._current_program) + """Path to the slot for the current (channel, bank, program).""" + return self._preset_path(self._current_channel, self._current_bank, self._current_program) def preset_up(self) -> Preset: """Select next preset in current bank (wraps 3→0). @@ -240,17 +283,18 @@ class PresetManager: """ return self._switch_bank(-1) - def select(self, bank: int, program: int) -> Preset: + def select(self, bank: int, program: int, channel: Channel | None = None) -> Preset: """Directly select a specific (bank, program) slot. Args: bank: Bank number. program: Preset index (0-3) within the bank. + channel: Optional channel override (defaults to current). Returns: The activated Preset. """ - return self._select_and_activate(bank, program) + return self._select_and_activate(bank, program, channel or self._current_channel) def midi_pc(self, channel: int, program: int) -> Preset: """Handle MIDI Program Change: bank=channel, program=program. @@ -267,15 +311,17 @@ class PresetManager: # ── CRUD ──────────────────────────────────────────────────────────────── - def save(self, preset: Preset, *, auto: bool = False) -> None: + def save(self, preset: Preset, *, auto: bool = False, channel: Channel | None = None) -> None: """Save a preset to its (bank, program) slot on disk. Args: preset: The preset to persist. auto: If True, this was triggered by auto-save and the dirty/current-state write is implicitly handled. + channel: Channel to save under (defaults to preset.channel). """ - path = self._preset_path(preset.bank, preset.program) + ch = channel or preset.channel + path = self._preset_path(ch, preset.bank, preset.program) path.parent.mkdir(parents=True, exist_ok=True) path.write_text( json.dumps(_preset_to_dict(preset), indent=2, sort_keys=True), @@ -287,16 +333,17 @@ class PresetManager: self._known_banks.add(preset.bank) # Save/update bank metadata - self._save_bank_meta(preset.bank) + self._save_bank_meta(ch, preset.bank) logger.info("Saved preset '%s' → %s", preset.name, path) - def load(self, bank: int, program: int) -> Preset: + def load(self, bank: int, program: int, channel: Channel | None = None) -> Preset: """Load a preset from disk. Args: bank: Bank number. program: Preset index (0-3). + channel: Channel to load from (defaults to current). Returns: The deserialized Preset. @@ -305,41 +352,52 @@ class PresetManager: FileNotFoundError: If the preset slot doesn't exist. json.JSONDecodeError: If the file is corrupt. """ - path = self._preset_path(bank, program) + ch = channel or self._current_channel + path = self._preset_path(ch, bank, program) if not path.exists(): raise FileNotFoundError( - f"No preset at (bank={bank}, program={program}): {path}" + f"No preset at (channel={ch.value}, bank={bank}, program={program}): {path}" ) data = json.loads(path.read_text(encoding="utf-8")) preset = _preset_from_dict(data) # Ensure metadata is correct even if file was manually altered + preset.channel = ch preset.bank = bank preset.program = program return preset - def delete(self, bank: int, program: int) -> None: - """Delete a preset slot from disk.""" - path = self._preset_path(bank, program) + def delete(self, bank: int, program: int, channel: Channel | None = None) -> None: + """Delete a preset slot from disk. + + Args: + bank: Bank number. + program: Preset index. + channel: Channel to delete from (defaults to current). + """ + ch = channel or self._current_channel + path = self._preset_path(ch, bank, program) if path.exists(): path.unlink() - logger.info("Deleted preset at (bank=%d, program=%d)", bank, program) + logger.info("Deleted preset at (channel=%s, bank=%d, program=%d)", ch.value, bank, program) self._dirty = True - def rename(self, bank: int, program: int, new_name: str) -> Preset: + def rename(self, bank: int, program: int, new_name: str, channel: Channel | None = None) -> Preset: """Rename a preset and re-save it. Args: bank: Bank number. program: Preset index. new_name: Replacement name. + channel: Channel (defaults to current). Returns: The renamed Preset. """ - preset = self.load(bank, program) + preset = self.load(bank, program, channel) preset.name = new_name - self.save(preset) - logger.info("Renamed preset at (%d,%d) → '%s'", bank, program, new_name) + self.save(preset, channel=channel) + logger.info("Renamed preset at (%s,%d,%d) → '%s'", + (channel or self._current_channel).value, bank, program, new_name) return preset def reorder(self, bank: int, program: int, new_program: int) -> None: @@ -415,10 +473,22 @@ class PresetManager: # ── Bank management ───────────────────────────────────────────────────── - def list_banks(self) -> list[Bank]: - """Scan the preset directory and return known banks sorted by number.""" + def list_banks(self, channel: Channel | None = None) -> list[Bank]: + """Scan the preset directory and return known banks sorted by number. + + Args: + channel: Channel to list banks for (defaults to current). + + Returns: + List of Bank objects sorted by number. + """ + ch = channel or self._current_channel + ch_dir = self._dir / ch.value + if not ch_dir.is_dir(): + return [] + banks: dict[int, Bank] = {} - for meta_path in sorted(self._dir.glob("bank_*/bank.json")): + for meta_path in sorted(ch_dir.glob("bank_*/bank.json")): try: data = json.loads(meta_path.read_text(encoding="utf-8")) num = data.get("number", 0) @@ -434,7 +504,7 @@ class PresetManager: continue # Also discover banks by scanning dirs without bank.json - for p_dir in sorted(self._dir.glob("bank_*")): + for p_dir in sorted(ch_dir.glob("bank_*")): try: num = int(p_dir.name.split("_")[1]) if num not in banks: @@ -460,20 +530,25 @@ class PresetManager: # ── Auto-restore ──────────────────────────────────────────────────────── def save_state(self) -> None: - """Persist the current (bank, program) for power-cycle restore. + """Persist the current (channel, bank, program) for power-cycle restore. - Call this whenever the active preset changes to guarantee + Call this whenever the active preset or channel changes to guarantee the pedal comes back to the same sound after a power cycle. """ - state = {"current_bank": self._current_bank, "current_program": self._current_program} + state = { + "current_channel": self._current_channel.value, + "current_bank": self._current_bank, + "current_program": self._current_program, + } state_path = self._dir / "state.json" state_path.write_text( json.dumps(state, indent=2), encoding="utf-8" ) - logger.debug("State saved: bank=%d program=%d", self._current_bank, self._current_program) + logger.debug("State saved: channel=%s bank=%d program=%d", + self._current_channel.value, self._current_bank, self._current_program) def restore_state(self) -> Optional[Preset]: - """Restore the last active preset from state.json. + """Restore the last active (channel, bank, program) from state.json. Returns: The restored Preset, or None if no state file exists or the @@ -486,17 +561,20 @@ class PresetManager: try: state = json.loads(state_path.read_text(encoding="utf-8")) + ch = Channel(state.get("current_channel", "guitar")) bank = state["current_bank"] program = state["current_program"] - preset = self.load(bank, program) - except (FileNotFoundError, json.JSONDecodeError, KeyError): + preset = self.load(bank, program, ch) + except (FileNotFoundError, json.JSONDecodeError, KeyError, ValueError): logger.warning("Could not restore state, falling back to first preset") return None + self._current_channel = ch self._current_bank = bank self._current_program = program self._dirty = False - logger.info("Restored state: bank=%d program=%d '%s'", bank, program, preset.name) + logger.info("Restored state: channel=%s bank=%d program=%d '%s'", + ch.value, bank, program, preset.name) return preset # ── Activation ────────────────────────────────────────────────────────── @@ -514,7 +592,8 @@ class PresetManager: # ── Factory presets ───────────────────────────────────────────────────── - def install_factory_presets(self, overwrite: bool = False) -> int: + def install_factory_presets(self, overwrite: bool = False, + channel: Channel = Channel.GUITAR) -> int: """Install bundled factory presets into the preset store. Scans ``FACTORY_PRESET_DIR`` for ``bank_*/preset_*.json`` files @@ -523,6 +602,7 @@ class PresetManager: Args: overwrite: If True, overwrite existing presets at the same (bank, program) slots. If False, skip occupied slots. + channel: Which channel to install factory presets into (default GUITAR). Returns: Number of factory presets installed. @@ -542,14 +622,14 @@ class PresetManager: logger.warning("Skipping malformed factory preset path: %s", src) continue - dest = self._preset_path(bank_num, prog_num) + dest = self._preset_path(channel, bank_num, prog_num) if dest.exists() and not overwrite: continue dest.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(src, dest) self._known_banks.add(bank_num) - self._save_bank_meta(bank_num) + self._save_bank_meta(channel, bank_num) count += 1 logger.debug("Installed factory preset: %s", dest) @@ -595,19 +675,31 @@ class PresetManager: # ── Internal helpers ──────────────────────────────────────────────────── - def _preset_path(self, bank: int, program: int) -> Path: - """Compute the on-disk path for a (bank, program) slot.""" - return self._dir / f"bank_{bank}" / f"preset_{program}.json" + def _preset_path(self, channel: Channel, bank: int, program: int) -> Path: + """Compute the on-disk path for a (channel, bank, program) slot.""" + return self._dir / channel.value / f"bank_{bank}" / f"preset_{program}.json" - def _bank_dir(self, bank: int) -> Path: - return self._dir / f"bank_{bank}" + def _channel_dir(self, channel: Channel) -> Path: + """Return the root directory for a given channel.""" + return self._dir / channel.value - def _bank_meta_path(self, bank: int) -> Path: - return self._bank_dir(bank) / "bank.json" + def _bank_dir(self, channel: Channel, bank: int) -> Path: + return self._channel_dir(channel) / f"bank_{bank}" - def _get_or_create_bank(self, number: int, name: str = "") -> Bank: - """Return an existing bank or create a new one on disk.""" - meta_path = self._bank_meta_path(number) + def _bank_meta_path(self, channel: Channel, bank: int) -> Path: + return self._bank_dir(channel, bank) / "bank.json" + + def _get_or_create_bank(self, number: int, name: str = "", + channel: Channel | None = None) -> Bank: + """Return an existing bank or create a new one on disk. + + Args: + number: Bank number. + name: Optional display name. + channel: Channel (defaults to current). + """ + ch = channel or self._current_channel + meta_path = self._bank_meta_path(ch, number) if meta_path.exists(): try: data = json.loads(meta_path.read_text(encoding="utf-8")) @@ -620,14 +712,15 @@ class PresetManager: # Create new bank bank = Bank(name=name or f"Bank {number}", number=number) - self._save_bank_meta(number, bank.name) + self._save_bank_meta(ch, number, bank.name) self._known_banks.add(number) - logger.info("Created bank %d: '%s'", number, bank.name) + logger.info("Created bank %d: '%s' (channel=%s)", number, bank.name, ch.value) return bank - def _save_bank_meta(self, bank_num: int, name: str | None = None) -> None: + def _save_bank_meta(self, channel: Channel, bank_num: int, + name: str | None = None) -> None: """Write or update a bank's metadata file.""" - meta_path = self._bank_meta_path(bank_num) + meta_path = self._bank_meta_path(channel, bank_num) existing = {} if meta_path.exists(): try: @@ -673,6 +766,7 @@ class PresetManager: except FileNotFoundError: preset = Preset( name=f"Empty {new_bank.name}", + channel=self._current_channel, bank=new_bank.number, program=self._current_program, ) @@ -680,11 +774,14 @@ class PresetManager: self._current_bank = new_bank.number self._current_program = preset.program self.activate(preset) + self._save_channel_state() self.save_state() - logger.info("Switched to bank %d, preset '%s'", new_bank.number, preset.name) + logger.info("Switched to bank %d, preset '%s' (channel=%s)", + new_bank.number, preset.name, self._current_channel.value) return new_bank, preset - def _select_and_activate(self, bank: int, program: int) -> Preset: + def _select_and_activate(self, bank: int, program: int, + channel: Channel | None = None) -> Preset: """Select a (bank, program) slot and activate it. If the slot is empty, creates a blank preset in that slot first. @@ -692,22 +789,125 @@ class PresetManager: Args: bank: Bank number. program: Preset index (0-3). + channel: Channel to operate on (defaults to current). Returns: The activated Preset. """ + ch = channel or self._current_channel program = max(0, min(PRESETS_PER_BANK - 1, program)) try: - preset = self.load(bank, program) + preset = self.load(bank, program, ch) except FileNotFoundError: # Create a blank placeholder preset - bank_obj = self._get_or_create_bank(bank) - preset = Preset(name=f"New {bank_obj.name} #{program}", bank=bank, program=program) - self.save(preset, auto=True) + bank_obj = self._get_or_create_bank(bank, channel=ch) + preset = Preset( + name=f"New {bank_obj.name} #{program}", + channel=ch, bank=bank, program=program, + ) + self.save(preset, auto=True, channel=ch) + self._current_channel = ch self._current_bank = bank self._current_program = program self.activate(preset) + self._save_channel_state(ch) self.save_state() - return preset \ No newline at end of file + return preset + + # ── Channel state helpers ─────────────────────────────────────────── + + def _channel_state_path(self, channel: Channel) -> Path: + """Path to the per-channel state file.""" + return self._channel_dir(channel) / "channel_state.json" + + def _save_channel_state(self, channel: Channel | None = None) -> None: + """Persist the channel-specific (bank, program) state.""" + ch = channel or self._current_channel + state = { + "current_bank": self._current_bank, + "current_program": self._current_program, + } + path = self._channel_state_path(ch) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(state, indent=2), encoding="utf-8") + logger.debug("Channel state saved for %s: bank=%d program=%d", + ch.value, self._current_bank, self._current_program) + + def _restore_channel_state(self, channel: Channel | None = None) -> Optional[Preset]: + """Restore the per-channel (bank, program) state. + + Args: + channel: Channel to restore (defaults to current). + + Returns: + The restored Preset, or None if no saved state exists. + """ + ch = channel or self._current_channel + path = self._channel_state_path(ch) + if not path.exists(): + return None + + try: + state = json.loads(path.read_text(encoding="utf-8")) + bank = state["current_bank"] + program = state["current_program"] + preset = self.load(bank, program, ch) + except (FileNotFoundError, json.JSONDecodeError, KeyError): + return None + + self._current_bank = bank + self._current_program = program + logger.info("Restored channel state for %s: bank=%d program=%d '%s'", + ch.value, bank, program, preset.name) + return preset + + # ── Legacy migration ──────────────────────────────────────────────── + + def _migrate_legacy_presets(self) -> None: + """Migrate flat bank_* directories under root into gtr/ subdirectory. + + Before dual-channel support, presets were stored as: + {root}/bank_0/preset_0.json + + Now they should live under: + {root}/gtr/bank_0/preset_0.json + + This runs once on first boot with the new layout. + """ + ch_dir = self._dir / "guitar" + if ch_dir.exists(): + # Already migrated or new install — nothing to do + return + + # Check if there are legacy flat bank_* dirs + legacy_banks = sorted(self._dir.glob("bank_*")) + if not legacy_banks: + return # Fresh install, nothing to migrate + + logger.info("Migrating %d legacy bank directories → gtr channel ...", len(legacy_banks)) + ch_dir.mkdir(parents=True, exist_ok=True) + + for src in legacy_banks: + dest = ch_dir / src.name + try: + shutil.move(str(src), str(dest)) + logger.debug(" migrated %s → %s", src.name, dest) + except OSError as e: + logger.warning(" failed to migrate %s: %s", src.name, e) + + # Also migrate state.json if it exists at root + legacy_state = self._dir / "state.json" + if legacy_state.exists(): + try: + state = json.loads(legacy_state.read_text(encoding="utf-8")) + if "current_channel" not in state: + state["current_channel"] = "guitar" + legacy_state.write_text( + json.dumps(state, indent=2), encoding="utf-8" + ) + except (json.JSONDecodeError, OSError): + pass + + logger.info("Legacy preset migration complete") \ No newline at end of file diff --git a/src/presets/types.py b/src/presets/types.py index bcbd5ba..171f33f 100644 --- a/src/presets/types.py +++ b/src/presets/types.py @@ -7,6 +7,12 @@ from dataclasses import dataclass, field from typing import Optional +class Channel(enum.StrEnum): + """Guitar or Bass signal channel — each has its own independent preset bank.""" + GUITAR = "guitar" + BASS = "bass" + + class FXType(enum.StrEnum): """Types of effects in the pedal signal chain.""" # Existing @@ -101,6 +107,7 @@ class Snapshot: class Preset: """A complete pedal preset — full signal chain state.""" name: str + channel: Channel = Channel.GUITAR bank: int = 0 program: int = 0 chain: list[FXBlock] = field(default_factory=list) diff --git a/src/web/server.py b/src/web/server.py index 7122565..0ad4eea 100644 --- a/src/web/server.py +++ b/src/web/server.py @@ -32,7 +32,7 @@ from ..dsp.nam_host import NAMHost from ..dsp.ir_loader import IRLoader from ..dsp.pipeline import AudioPipeline, SAMPLE_RATE from ..presets.manager import PresetManager -from ..presets.types import FXBlock, FXType, Preset +from ..presets.types import Channel, FXBlock, FXType, Preset from ..system.tonedownload import ( Tone3000Client, ModelResult, @@ -64,22 +64,156 @@ else: ] UI_DIST_DIR = next((d for d in _candidates if d.is_dir()), None) +# ── Channel support ────────────────────────────────────────────────────────────── + +VALID_CHANNELS: frozenset = frozenset({"guitar", "bass"}) + +def _resolve_channel(channel: str = "guitar") -> str: + """Normalize a channel string; fall back to 'guitar' for invalid values.""" + if isinstance(channel, str) and channel.lower() in VALID_CHANNELS: + return channel.lower() + return "guitar" + + +# ── Channel mode persistence ──────────────────────────────────────────────── + +CHANNEL_MODE_FILE = Path.home() / ".pedal" / "channel_mode.json" + + +def _load_channel_mode() -> str: + """Load channel mode from disk; default to 'dual-mono'.""" + try: + if CHANNEL_MODE_FILE.exists(): + data = json.loads(CHANNEL_MODE_FILE.read_text()) + mode = data.get("mode", "dual-mono") + if mode in ("dual-mono", "stereo"): + return mode + except Exception: + pass + return "dual-mono" + + +def _save_channel_mode(mode: str) -> None: + """Persist channel mode to disk.""" + CHANNEL_MODE_FILE.parent.mkdir(parents=True, exist_ok=True) + CHANNEL_MODE_FILE.write_text(json.dumps({"mode": mode})) + + +@dataclass +class ChannelState: + """Per-channel runtime state (volume, bypass, tuner).""" + volume: float = 0.8 + bypass: bool = False + tuner_enabled: bool = False + current_snapshot: int = 0 + + +class ChannelManager: + """Manages dual-channel vs stereo routing mode and per-channel state. + + In ``dual-mono`` mode each channel (guitar / bass) has independent + runtime state. In ``stereo`` mode the two channels are linked — + the ``guitar`` channel dict is the single source of truth for both. + """ + + def __init__(self) -> None: + self.mode: str = _load_channel_mode() + self._channels: dict[str, ChannelState] = { + "guitar": ChannelState(), + "bass": ChannelState(), + } + + # ── Mode management ──────────────────────────────────────────────── + + def set_mode(self, mode: str) -> None: + if mode not in ("dual-mono", "stereo"): + raise ValueError(f"Unknown channel mode: {mode!r}") + self.mode = mode + _save_channel_mode(mode) + + # ── Per-channel helpers ──────────────────────────────────────────── + + def state_for(self, channel: str) -> ChannelState: + """Return the ``ChannelState`` for a given channel. + + In stereo mode both channels read from the ``guitar`` slot. + Falls back to ``guitar`` for unknown channel names. + """ + if self.mode == "stereo": + return self._channels["guitar"] + return self._channels.get(channel, self._channels["guitar"]) + + def sync_to_stereo(self) -> None: + """When switching *to* stereo, copy bass -> guitar so guitar has + the most recently changed values from either channel.""" + if self.mode == "stereo": + g = self._channels["guitar"] + b = self._channels["bass"] + # Pick the "latest" non-default values + for attr in ("volume", "bypass", "tuner_enabled", "current_snapshot"): + bv = getattr(b, attr) + if bv != getattr(ChannelState(), attr): + setattr(g, attr, bv) + + def resolve_channel(self, param: str | None) -> str: + """Return the active channel name. + + In stereo mode always returns ``"guitar"`` (the canonical slot). + In dual-mono returns *param* if it's ``"guitar"`` or ``"bass"``, + otherwise falls back to ``"guitar"``. + """ + if self.mode == "stereo": + return "guitar" + if param in ("guitar", "bass"): + return param + return "guitar" + + # ── Dependency container ───────────────────────────────────────────────────── @dataclass class WebServerDeps: - """Dependencies injected into the web server from the host pedal app.""" + """Dependencies injected into the web server from the host pedal app. + Supports dual-channel operation (guitar + bass). Each channel has its + own preset manager, audio pipeline, NAM host, and IR loader so the two + signal paths are fully independent. + + The guitar fields are the defaults — callers that don't need dual-channel + can just set the guitar fields as before and everything works. + """ + + # Guitar channel (default) presets: Optional[PresetManager] = None pipeline: Optional[AudioPipeline] = None nam_host: Optional[NAMHost] = None ir_loader: Optional[IRLoader] = None + # Bass channel (separate from guitar) + bass_presets: Optional[PresetManager] = None + bass_pipeline: Optional[AudioPipeline] = None + bass_nam_host: Optional[NAMHost] = None + bass_ir_loader: Optional[IRLoader] = None + @property def is_connected(self) -> bool: - """True if we're wired to a live pedal instance.""" - return self.presets is not None + """True if we're wired to a live pedal instance (any channel).""" + return self.presets is not None or self.bass_presets is not None + + def get_deps(self, channel: str) -> tuple: + """Return (presets, pipeline, nam_host, ir_loader) for the given channel. + + Falls back to guitar fields for unrecognised channels. + """ + if _resolve_channel(channel) == "bass": + return ( + self.bass_presets, + self.bass_pipeline, + self.bass_nam_host, + self.bass_ir_loader, + ) + return (self.presets, self.pipeline, self.nam_host, self.ir_loader) # ── WebSocket connection manager ───────────────────────────────────────────── @@ -139,11 +273,21 @@ class WebServer: self.port = port self._manager = ConnectionManager() + self._channel_mgr = ChannelManager() self._server: Optional[uvicorn.Server] = None self._task: Optional[asyncio.Task] = None self._tonedownload: Optional[Tone3000Client] = None self._app = self._build_app() + # ── Channel dep resolution ────────────────────────────────────── + + def _channel_deps(self, channel: str = "guitar") -> tuple: + """Shorthand for self.deps.get_deps(channel). + + Returns (presets, pipeline, nam_host, ir_loader). + """ + return self.deps.get_deps(channel) + # ── App factory ───────────────────────────────────────────────────── def _build_app(self) -> FastAPI: @@ -177,8 +321,8 @@ class WebServer: if index_html.exists(): content = index_html.read_text() return HTMLResponse(content) - # Fallback: legacy dashboard - state = self._gather_state() + # Fallback: legacy dashboard (guitar channel state) + state = self._gather_state(channel="guitar") ctx = {"request": request} ctx.update(state) return templates.TemplateResponse(request, "dashboard.html", ctx) @@ -201,7 +345,7 @@ class WebServer: @app.get("/settings", response_class=HTMLResponse) async def settings_page(request: Request): """Settings page.""" - state = self._gather_state() + state = self._gather_state(channel="guitar") ctx = {"request": request} ctx.update(state) return templates.TemplateResponse(request, "settings.html", ctx) @@ -209,17 +353,25 @@ class WebServer: # ── REST API ──────────────────────────────────────────────── @app.get("/api/state") - async def get_state(): - """Full current pedal state snapshot.""" - state = self._gather_state() + async def get_state(channel: Optional[str] = None, combined: bool = False): + """Full pedal state snapshot. + + Without parameters returns the active (guitar) channel's flat state. + ``?channel=guitar|bass`` returns per-channel state. + ``?combined=true`` returns both channel states in a combined view. + """ + if combined: + state = self._gather_state_combined() + else: + state = self._gather_state(channel=channel) if not state: raise HTTPException(status_code=503, detail="Pedal not connected") return state @app.get("/api/presets") - async def list_presets(): - """List all banks and their presets.""" - pm = self.deps.presets + async def list_presets(channel: str = "guitar"): + """List all banks and their presets for the given channel.""" + pm, _pl, _nam, _ir = self._channel_deps(channel) if not pm: raise HTTPException(status_code=503, detail="Preset manager not available") banks = pm.list_banks() @@ -257,9 +409,9 @@ class WebServer: return {"banks": result} @app.get("/api/presets/{bank}/{program}") - async def get_preset(bank: int, program: int): - """Get a specific preset.""" - pm = self.deps.presets + async def get_preset(bank: int, program: int, channel: str = "guitar"): + """Get a specific preset for the given channel.""" + pm, _pl, _nam, _ir = self._channel_deps(channel) if not pm: raise HTTPException(status_code=503) try: @@ -286,17 +438,18 @@ class WebServer: raise HTTPException(status_code=404, detail="Preset not found") @app.post("/api/presets/{bank}/{program}/activate") - async def activate_preset(bank: int, program: int): - """Activate a preset (load into pipeline).""" - pm = self.deps.presets + async def activate_preset(bank: int, program: int, channel: str = "guitar"): + """Activate a preset (load into pipeline) for the given channel.""" + pm, _pl, _nam, _ir = self._channel_deps(channel) if not pm: raise HTTPException(status_code=503) try: preset = pm.select(bank, program) pm.save_state() - state = self._gather_state() + state = self._gather_state(channel) await self._manager.broadcast({ "type": "preset_changed", + "channel": channel, "preset": { "name": preset.name, "bank": preset.bank, @@ -309,9 +462,9 @@ class WebServer: raise HTTPException(status_code=500, detail=str(e)) @app.put("/api/presets/{bank}/{program}") - async def save_preset(bank: int, program: int, data: dict): - """Save/update a preset.""" - pm = self.deps.presets + async def save_preset(bank: int, program: int, data: dict, channel: str = "guitar"): + """Save/update a preset for the given channel.""" + pm, _pl, _nam, _ir = self._channel_deps(channel) if not pm: raise HTTPException(status_code=503) try: @@ -327,6 +480,7 @@ class WebServer: )) preset = Preset( name=data.get("name", f"Preset {bank}-{program}"), + channel=Channel(channel), bank=bank, program=program, chain=chain, @@ -339,9 +493,9 @@ class WebServer: raise HTTPException(status_code=500, detail=str(e)) @app.delete("/api/presets/{bank}/{program}") - async def delete_preset(bank: int, program: int): - """Delete a preset.""" - pm = self.deps.presets + async def delete_preset(bank: int, program: int, channel: str = "guitar"): + """Delete a preset for the given channel.""" + pm, _pl, _nam, _ir = self._channel_deps(channel) if not pm: raise HTTPException(status_code=503) try: @@ -388,9 +542,9 @@ class WebServer: } @app.get("/api/snapshots") - async def list_snapshots(): - """List snapshot slots for the current preset.""" - pm = self.deps.presets + async def list_snapshots(channel: str = "guitar"): + """List snapshot slots for the current preset on the given channel.""" + pm, _pl, _nam, _ir = self._channel_deps(channel) preset = _get_current_preset(pm) if not preset: return {"snapshots": {}} @@ -418,10 +572,9 @@ class WebServer: return {"snapshots": data, "current_snapshot": getattr(pm, '_current_snapshot', 0)} @app.post("/api/snapshots/{slot}/save") - async def save_snapshot(slot: int, data: Optional[dict] = None): - """Save current state to a snapshot slot (1-8).""" - pm = self.deps.presets - pl = self.deps.pipeline + async def save_snapshot(slot: int, data: Optional[dict] = None, channel: str = "guitar"): + """Save current state to a snapshot slot (1-8) for the given channel.""" + pm, pl, _nam, _ir = self._channel_deps(channel) if not pm or not (1 <= slot <= 8): raise HTTPException(status_code=400, detail="Invalid slot (1-8)") preset = _get_current_preset(pm) @@ -459,10 +612,9 @@ class WebServer: return {"ok": True, "slot": slot, "name": snap_data["name"]} @app.post("/api/snapshots/{slot}/recall") - async def recall_snapshot(slot: int): - """Recall a snapshot: restore block states + params from slot.""" - pm = self.deps.presets - pl = self.deps.pipeline + async def recall_snapshot(slot: int, channel: str = "guitar"): + """Recall a snapshot: restore block states + params from slot for the given channel.""" + pm, pl, _nam, _ir = self._channel_deps(channel) if not pm or not (1 <= slot <= 8): raise HTTPException(status_code=400, detail="Invalid slot (1-8)") preset = _get_current_preset(pm) @@ -493,9 +645,10 @@ class WebServer: pm.save(preset) pm._current_snapshot = slot - state = self._gather_state() + state = self._gather_state(channel) await self._manager.broadcast({ "type": "snapshot_recalled", + "channel": channel, "slot": slot, "name": snap.name, "state": state, @@ -503,9 +656,9 @@ class WebServer: return {"ok": True, "slot": slot, "name": snap.name} @app.put("/api/snapshots/{slot}") - async def update_snapshot(slot: int, data: dict): - """Rename or update a snapshot slot.""" - pm = self.deps.presets + async def update_snapshot(slot: int, data: dict, channel: str = "guitar"): + """Rename or update a snapshot slot for the given channel.""" + pm, _pl, _nam, _ir = self._channel_deps(channel) if not pm or not (1 <= slot <= 8): raise HTTPException(status_code=400, detail="Invalid slot (1-8)") preset = _get_current_preset(pm) @@ -519,15 +672,16 @@ class WebServer: pm.save(preset) await self._manager.broadcast({ "type": "snapshot_updated", + "channel": channel, "slot": slot, "name": snap.name, }) return {"ok": True, "slot": slot, "name": snap.name} @app.delete("/api/snapshots/{slot}") - async def delete_snapshot(slot: int): - """Delete a snapshot slot.""" - pm = self.deps.presets + async def delete_snapshot(slot: int, channel: str = "guitar"): + """Delete a snapshot slot for the given channel.""" + pm, _pl, _nam, _ir = self._channel_deps(channel) if not pm or not (1 <= slot <= 8): raise HTTPException(status_code=400, detail="Invalid slot (1-8)") preset = _get_current_preset(pm) @@ -539,14 +693,15 @@ class WebServer: pm.save(preset) await self._manager.broadcast({ "type": "snapshot_deleted", + "channel": channel, "slot": slot, }) return {"ok": True, "slot": slot} @app.get("/api/snapshots/{slot}") - async def get_snapshot(slot: int): - """Get a single snapshot slot.""" - pm = self.deps.presets + async def get_snapshot(slot: int, channel: str = "guitar"): + """Get a single snapshot slot for the given channel.""" + pm, _pl, _nam, _ir = self._channel_deps(channel) preset = _get_current_preset(pm) if not preset: raise HTTPException(status_code=404, detail="No active preset") @@ -556,6 +711,7 @@ class WebServer: return { "name": snap.name, "slot": slot, + "channel": channel, "chain": [ { "fx_type": b.fx_type.value, @@ -569,9 +725,9 @@ class WebServer: } @app.post("/api/bypass") - async def toggle_bypass(data: Optional[dict] = None): - """Toggle or set bypass state.""" - pl = self.deps.pipeline + async def toggle_bypass(data: Optional[dict] = None, channel: str = "guitar"): + """Toggle or set bypass state for the given channel.""" + _pm, pl, _nam, _ir = self._channel_deps(channel) if not pl: raise HTTPException(status_code=503) if data and "bypass" in data: @@ -580,14 +736,15 @@ class WebServer: pl._bypassed = not pl._bypassed await self._manager.broadcast({ "type": "bypass_changed", + "channel": channel, "bypass": pl._bypassed, }) return {"ok": True, "bypass": pl._bypassed} @app.post("/api/tuner") - async def toggle_tuner(data: Optional[dict] = None): - """Toggle tuner mode.""" - pl = self.deps.pipeline + async def toggle_tuner(data: Optional[dict] = None, channel: str = "guitar"): + """Toggle tuner mode for the given channel.""" + _pm, pl, _nam, _ir = self._channel_deps(channel) if not pl: raise HTTPException(status_code=503) if data and "enabled" in data: @@ -596,14 +753,15 @@ class WebServer: pl._tuner_enabled = not pl._tuner_enabled await self._manager.broadcast({ "type": "tuner_changed", + "channel": channel, "tuner_enabled": pl._tuner_enabled, }) return {"ok": True, "tuner_enabled": pl._tuner_enabled} @app.get("/api/tuner/pitch") - async def get_tuner_pitch(): - """Get current tuner pitch detection data (fast poll endpoint).""" - pl = self.deps.pipeline + async def get_tuner_pitch(channel: str = "guitar"): + """Get current tuner pitch detection data for the given channel (fast poll endpoint).""" + _pm, pl, _nam, _ir = self._channel_deps(channel) if not pl: return { "frequency": 0.0, "note": "--", "cents": 0, @@ -619,21 +777,21 @@ class WebServer: } @app.put("/api/volume") - async def set_volume(data: dict): - """Set master volume (0.0-1.0).""" - pl = self.deps.pipeline + async def set_volume(data: dict, channel: str = "guitar"): + """Set master volume (0.0-1.0) for the given channel.""" + _pm, pl, _nam, _ir = self._channel_deps(channel) if not pl: raise HTTPException(status_code=503) vol = max(0.0, min(1.0, float(data.get("volume", 0.8)))) pl._master_volume = vol - return {"ok": True, "volume": vol} + return {"ok": True, "volume": vol, "channel": channel} # ── 4CM routing endpoints ──────────────────────────────── @app.get("/api/routing") - async def get_routing(): - """Get current routing mode and breakpoint.""" - pl = self.deps.pipeline + async def get_routing(channel: str = "guitar"): + """Get current routing mode and breakpoint for the given channel.""" + _pm, pl, _nam, _ir = self._channel_deps(channel) if not pl: raise HTTPException(status_code=503) return { @@ -642,15 +800,15 @@ class WebServer: } @app.post("/api/routing") - async def set_routing(data: dict): - """Set routing mode and/or breakpoint. + async def set_routing(data: dict, channel: str = "guitar"): + """Set routing mode and/or breakpoint for the given channel. Body: routing_mode (optional): ``\"mono\"`` or ``\"4cm\"``. routing_breakpoint (optional): chain index for split. """ - pl = self.deps.pipeline - pm = self.deps.presets + _pm, pl, _nam, _ir = self._channel_deps(channel) + pm, _pl2, _nam2, _ir2 = self._channel_deps(channel) if not pl: raise HTTPException(status_code=503) mode = data.get("routing_mode", pl.routing_mode) @@ -669,16 +827,47 @@ class WebServer: await self._manager.broadcast({ "type": "routing_changed", + "channel": channel, "routing_mode": pl.routing_mode, "routing_breakpoint": pl.routing_breakpoint, }) return {"ok": True, "routing_mode": pl.routing_mode, "routing_breakpoint": pl.routing_breakpoint} + # ── Channel mode endpoints ─────────────────────────────── + + @app.get("/api/channel-mode") + async def get_channel_mode(): + """Get the current channel routing mode.""" + return {"channel_mode": self._channel_mgr.mode} + + @app.post("/api/channel-mode") + async def set_channel_mode(data: dict): + """Set channel routing mode. + + Body: { "channel_mode": "dual-mono" | "stereo" } + """ + mode = data.get("channel_mode", "") + if mode not in ("dual-mono", "stereo"): + raise HTTPException( + status_code=400, + detail=f"Invalid channel_mode: {mode!r}. Must be 'dual-mono' or 'stereo'.", + ) + was = self._channel_mgr.mode + self._channel_mgr.set_mode(mode) + if mode == "stereo" and was == "dual-mono": + self._channel_mgr.sync_to_stereo() + logger.info("Channel mode switched: %s → %s", was, mode) + await self._manager.broadcast({ + "type": "channel_mode_changed", + "channel_mode": mode, + }) + return {"ok": True, "channel_mode": mode} + @app.get("/api/models") - async def list_models(): - """List available NAM models.""" - nam = self.deps.nam_host + async def list_models(channel: str = "guitar"): + """List available NAM models for the given channel.""" + _pm, _pl, nam, _ir = self._channel_deps(channel) if not nam: raise HTTPException(status_code=503) models = nam.list_available_models() @@ -697,9 +886,9 @@ class WebServer: } @app.post("/api/models/load") - async def load_model(data: dict): - """Load a NAM model by path.""" - nam = self.deps.nam_host + async def load_model(data: dict, channel: str = "guitar"): + """Load a NAM model by path for the given channel.""" + _pm, _pl, nam, _ir = self._channel_deps(channel) if not nam: raise HTTPException(status_code=503) path = data.get("path", "") @@ -715,18 +904,18 @@ class WebServer: return {"ok": True} @app.post("/api/models/unload") - async def unload_model(): - """Unload the current NAM model.""" - nam = self.deps.nam_host + async def unload_model(channel: str = "guitar"): + """Unload the current NAM model for the given channel.""" + _pm, _pl, nam, _ir = self._channel_deps(channel) if not nam: raise HTTPException(status_code=503) nam.unload() return {"ok": True} @app.get("/api/irs") - async def list_irs(): - """List available IR files.""" - ir = self.deps.ir_loader + async def list_irs(channel: str = "guitar"): + """List available IR files for the given channel.""" + _pm, _pl, _nam, ir = self._channel_deps(channel) if not ir: raise HTTPException(status_code=503) irs = ir.get_irs() @@ -746,9 +935,9 @@ class WebServer: } @app.post("/api/irs/load") - async def load_ir(data: dict): - """Load an IR file by path.""" - ir = self.deps.ir_loader + async def load_ir(data: dict, channel: str = "guitar"): + """Load an IR file by path for the given channel.""" + _pm, _pl, _nam, ir = self._channel_deps(channel) if not ir: raise HTTPException(status_code=503) path = data.get("path", "") @@ -764,9 +953,9 @@ class WebServer: return {"ok": True} @app.post("/api/irs/unload") - async def unload_ir(): - """Unload the current IR.""" - ir = self.deps.ir_loader + async def unload_ir(channel: str = "guitar"): + """Unload the current IR for the given channel.""" + _pm, _pl, _nam, ir = self._channel_deps(channel) if not ir: raise HTTPException(status_code=503) ir.unload() @@ -999,10 +1188,16 @@ class WebServer: async def websocket_endpoint(ws: WebSocket): await self._manager.connect(ws) logger.info("WebSocket client connected (%d active)", self._manager.active_count) + # Track per-connection channel preference + _ws_channel: str = "guitar" try: - # Send initial state snapshot on connect - state = self._gather_state() - await ws.send_text(json.dumps({"type": "connected", "state": state})) + # Send initial state snapshot on connect (combined view) + state = self._gather_state_combined() + await ws.send_text(json.dumps({ + "type": "connected", + "state": state, + "channels": list(VALID_CHANNELS), + })) while True: raw = await ws.receive_text() @@ -1012,8 +1207,20 @@ class WebServer: if msg_type == "ping": await ws.send_text(json.dumps({"type": "pong"})) elif msg_type == "get_state": - state = self._gather_state() - await ws.send_text(json.dumps({"type": "state", "state": state})) + ch = msg.get("channel", _ws_channel) + _ws_channel = _resolve_channel(ch) + state = self._gather_state(channel=_ws_channel) + await ws.send_text(json.dumps({ + "type": "state", + "channel": _ws_channel, + "state": state, + })) + elif msg_type == "set_channel": + _ws_channel = _resolve_channel(msg.get("channel", "guitar")) + await ws.send_text(json.dumps({ + "type": "channel_set", + "channel": _ws_channel, + })) except WebSocketDisconnect: pass except Exception as e: @@ -1264,6 +1471,42 @@ class WebServer: ir_loader.get_irs() return {"ok": True, "path": str(path), "filename": path.name} + # ── Channel-prefixed route aliases ────────────────────── + # Every state/control endpoint that accepts ?channel= is also + # available at /api/channel/{channel}/... for explicit routing. + + _ch_base = "/api/channel/{channel}" + + # State + app.add_api_route(f"{_ch_base}/state", get_state, methods=["GET"]) + # Presets + app.add_api_route(f"{_ch_base}/presets", list_presets, methods=["GET"]) + app.add_api_route(f"{_ch_base}/presets/{{bank}}/{{program}}", get_preset, methods=["GET"]) # noqa: F821 + app.add_api_route(f"{_ch_base}/presets/{{bank}}/{{program}}/activate", activate_preset, methods=["POST"]) # noqa: F821 + app.add_api_route(f"{_ch_base}/presets/{{bank}}/{{program}}", save_preset, methods=["PUT"]) # noqa: F821 + app.add_api_route(f"{_ch_base}/presets/{{bank}}/{{program}}", delete_preset, methods=["DELETE"]) # noqa: F821 + # Snapshots + app.add_api_route(f"{_ch_base}/snapshots", list_snapshots, methods=["GET"]) + app.add_api_route(f"{_ch_base}/snapshots/{{slot}}", get_snapshot, methods=["GET"]) + app.add_api_route(f"{_ch_base}/snapshots/{{slot}}/save", save_snapshot, methods=["POST"]) + app.add_api_route(f"{_ch_base}/snapshots/{{slot}}/recall", recall_snapshot, methods=["POST"]) + app.add_api_route(f"{_ch_base}/snapshots/{{slot}}", update_snapshot, methods=["PUT"]) + app.add_api_route(f"{_ch_base}/snapshots/{{slot}}", delete_snapshot, methods=["DELETE"]) + # Controller + app.add_api_route(f"{_ch_base}/bypass", toggle_bypass, methods=["POST"]) + app.add_api_route(f"{_ch_base}/tuner", toggle_tuner, methods=["POST"]) + app.add_api_route(f"{_ch_base}/tuner/pitch", get_tuner_pitch, methods=["GET"]) + app.add_api_route(f"{_ch_base}/volume", set_volume, methods=["PUT"]) + app.add_api_route(f"{_ch_base}/routing", get_routing, methods=["GET"]) + app.add_api_route(f"{_ch_base}/routing", set_routing, methods=["POST"]) + # Models & IRs + app.add_api_route(f"{_ch_base}/models", list_models, methods=["GET"]) + app.add_api_route(f"{_ch_base}/models/load", load_model, methods=["POST"]) + app.add_api_route(f"{_ch_base}/models/unload", unload_model, methods=["POST"]) + app.add_api_route(f"{_ch_base}/irs", list_irs, methods=["GET"]) + app.add_api_route(f"{_ch_base}/irs/load", load_ir, methods=["POST"]) + app.add_api_route(f"{_ch_base}/irs/unload", unload_ir, methods=["POST"]) + return app # ── Network/BT state gathering ────────────────────────────────── @@ -1322,12 +1565,14 @@ class WebServer: # ── State gathering ────────────────────────────────────────────── - def _gather_state(self) -> dict[str, Any]: - """Collect a snapshot of the current pedal state.""" - pm = self.deps.presets - pl = self.deps.pipeline - nam = self.deps.nam_host - ir = self.deps.ir_loader + def _gather_channel_state(self, channel: str) -> dict[str, Any]: + """Collect a snapshot for a single channel. + + Returns the full state dict for the requested channel. When the + channel's preset manager is not connected the dict has a flat + ``connected: False`` marker. + """ + pm, pl, nam, ir = self._channel_deps(channel) if not pm: return { @@ -1378,6 +1623,7 @@ class WebServer: current_ir_name = _safe_str(current_ir_obj.name if current_ir_obj else None) return { + "channel": channel, "connected": True, "current_preset": { "name": preset.name if preset else "—", @@ -1398,15 +1644,74 @@ class WebServer: "nam_model": current_model_name, "ir_loaded": bool(ir and ir.is_loaded) if ir else False, "ir_name": current_ir_name, + "channel_mode": self._channel_mgr.mode, # Audio levels from pipeline (RMS float32 normalized 0.0-1.0, scaled to 0-100) - "input_level": min(100, round((pl._input_level or 0.0) * 150)) if pl else 0, - "output_level": min(100, round((pl._output_level or 0.0) * 150)) if pl else 0, + "input_level": ( + min(100, round((getattr(pl, '_input_level', 0.0) or 0.0) * 150)) + if pl and isinstance(getattr(pl, '_input_level', None), (int, float)) + else 0 + ), + "output_level": ( + min(100, round((getattr(pl, '_output_level', 0.0) or 0.0) * 150)) + if pl and isinstance(getattr(pl, '_output_level', None), (int, float)) + else 0 + ), # System stats "cpu_percent": self._gather_system_stats().get("cpu_percent", 0), "sample_rate": SAMPLE_RATE, "wifi": self._gather_wifi_state(), "bluetooth": self._gather_bt_state(), - "current_snapshot": getattr(self.deps.presets, '_current_snapshot', 0) if self.deps.presets else 0, + "current_snapshot": getattr(pm, '_current_snapshot', 0) if pm else 0, + } + + def _gather_state(self, channel: Optional[str] = None) -> dict[str, Any]: + """Collect pedal state snapshot. + + By default (``channel=None``) returns the guitar channel's flat + state — this preserves backward compatibility for existing clients + that expect ``connected``, ``current_preset``, etc. at the top + level. + + Pass ``channel=\"guitar\"`` or ``channel=\"bass\"`` for explicit + per-channel state. + """ + ch = channel if channel is not None else "guitar" + return self._gather_channel_state(ch) + + def _gather_state_combined(self) -> dict[str, Any]: + """Return a combined view with both channel states. + + Shape:: + + { + \"channels\": { + \"guitar\": { … }, + \"bass\": { … }, + }, + \"active_channel\": \"guitar\", + \"channel_mode\": \"dual-mono\", + \"wifi\": { … }, + \"bluetooth\": { … }, + \"cpu_percent\": …, + } + """ + guitar = self._gather_channel_state("guitar") + bass = self._gather_channel_state("bass") + + return { + "channels": { + "guitar": guitar, + "bass": bass, + }, + "active_channel": "guitar", + "channel_mode": self._channel_mgr.mode, + "wifi": guitar.get("wifi", {}), + "bluetooth": guitar.get("bluetooth", {}), + "cpu_percent": max( + guitar.get("cpu_percent", 0), + bass.get("cpu_percent", 0), + ), + "sample_rate": SAMPLE_RATE, } # ── Tone3000 client lazy init ──────────────────────────────── diff --git a/src/web/ui-dist/assets/index-BiNA-JeT.js b/src/web/ui-dist/assets/index-BiNA-JeT.js new file mode 100644 index 0000000..083d765 --- /dev/null +++ b/src/web/ui-dist/assets/index-BiNA-JeT.js @@ -0,0 +1,184 @@ +var e=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports);(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var t=e((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var ee=Array.isArray;function S(){}var C={H:null,A:null,T:null,S:null},te=Object.prototype.hasOwnProperty;function w(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function T(e,t){return w(e.type,t,e.props)}function E(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function ne(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var re=/\/+/g;function ie(e,t){return typeof e==`object`&&e&&e.key!=null?ne(``+e.key):t.toString(36)}function ae(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(S,S):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function oe(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,oe(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+ie(e,0):a,ee(o)?(i=``,c!=null&&(i=c.replace(re,`$&/`)+`/`),oe(o,r,i,``,function(e){return e})):o!=null&&(E(o)&&(o=T(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(re,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(ee(e))for(var u=0;u{n.exports=t()})),r=e((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,ee||(ee=!0,E());else{var t=n(l);t!==null&&ie(x,t.startTime-e)}}var ee=!1,S=-1,C=5,te=-1;function w(){return g?!0:!(e.unstable_now()-tet&&w());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&ie(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?E():ee=!1}}}var E;if(typeof y==`function`)E=function(){y(T)};else if(typeof MessageChannel<`u`){var ne=new MessageChannel,re=ne.port2;ne.port1.onmessage=T,E=function(){re.postMessage(null)}}else E=function(){_(T,0)};function ie(t,n){S=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(S),S=-1):h=!0,ie(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,ee||(ee=!0,E()))),r},e.unstable_shouldYield=w,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),i=e(((e,t)=>{t.exports=r()})),a=e((e=>{var t=n();function r(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=a()})),s=e((e=>{var t=i(),r=n(),a=o();function s(e){var t=`https://react.dev/errors/`+e;if(1de||(e.current=ue[de],ue[de]=null,de--)}function A(e,t){de++,ue[de]=e.current,e.current=t}var pe=fe(null),me=fe(null),he=fe(null),ge=fe(null);function _e(e,t){switch(A(he,t),A(me,e),A(pe,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Vd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Vd(t),e=Hd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}k(pe),A(pe,e)}function ve(){k(pe),k(me),k(he)}function ye(e){e.memoizedState!==null&&A(ge,e);var t=pe.current,n=Hd(t,e.type);t!==n&&(A(me,e),A(pe,n))}function be(e){me.current===e&&(k(pe),k(me)),ge.current===e&&(k(ge),Qf._currentValue=le)}var xe,Se;function Ce(e){if(xe===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);xe=t&&t[1]||``,Se=-1)`:-1i||c[r]!==l[i]){var u=` +`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{we=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?Ce(n):``}function Ee(e,t){switch(e.tag){case 26:case 27:case 5:return Ce(e.type);case 16:return Ce(`Lazy`);case 13:return e.child!==t&&t!==null?Ce(`Suspense Fallback`):Ce(`Suspense`);case 19:return Ce(`SuspenseList`);case 0:case 15:return Te(e.type,!1);case 11:return Te(e.type.render,!1);case 1:return Te(e.type,!0);case 31:return Ce(`Activity`);default:return``}}function De(e){try{var t=``,n=null;do t+=Ee(e,n),n=e,e=e.return;while(e);return t}catch(e){return` +Error generating stack: `+e.message+` +`+e.stack}}var Oe=Object.prototype.hasOwnProperty,ke=t.unstable_scheduleCallback,Ae=t.unstable_cancelCallback,je=t.unstable_shouldYield,Me=t.unstable_requestPaint,Ne=t.unstable_now,Pe=t.unstable_getCurrentPriorityLevel,Fe=t.unstable_ImmediatePriority,Ie=t.unstable_UserBlockingPriority,Le=t.unstable_NormalPriority,Re=t.unstable_LowPriority,ze=t.unstable_IdlePriority,Be=t.log,Ve=t.unstable_setDisableYieldValue,He=null,Ue=null;function We(e){if(typeof Be==`function`&&Ve(e),Ue&&typeof Ue.setStrictMode==`function`)try{Ue.setStrictMode(He,e)}catch{}}var Ge=Math.clz32?Math.clz32:Je,Ke=Math.log,qe=Math.LN2;function Je(e){return e>>>=0,e===0?32:31-(Ke(e)/qe|0)|0}var Ye=256,Xe=262144,Ze=4194304;function Qe(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function $e(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Qe(n))):i=Qe(o):i=Qe(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Qe(n))):i=Qe(o)):i=Qe(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function et(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function tt(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function nt(){var e=Ze;return Ze<<=1,!(Ze&62914560)&&(Ze=4194304),e}function rt(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function it(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function at(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),vn=!1;if(_n)try{var yn={};Object.defineProperty(yn,"passive",{get:function(){vn=!0}}),window.addEventListener(`test`,yn,yn),window.removeEventListener(`test`,yn,yn)}catch{vn=!1}var bn=null,xn=null,Sn=null;function Cn(){if(Sn)return Sn;var e,t=xn,n=t.length,r,i=`value`in bn?bn.value:bn.textContent,a=i.length;for(e=0;e=tr),ir=` `,ar=!1;function or(e,t){switch(e){case`keyup`:return $n.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function sr(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var cr=!1;function lr(e,t){switch(e){case`compositionend`:return sr(t);case`keypress`:return t.which===32?(ar=!0,ir):null;case`textInput`:return e=t.data,e===ir&&ar?null:e;default:return null}}function ur(e,t){if(cr)return e===`compositionend`||!er&&or(e,t)?(e=Cn(),Sn=xn=bn=null,cr=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=Mr(n)}}function Pr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Pr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Fr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Wt(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=Wt(e.document)}return t}function Ir(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var Lr=_n&&`documentMode`in document&&11>=document.documentMode,Rr=null,zr=null,Br=null,Vr=!1;function Hr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Vr||Rr==null||Rr!==Wt(r)||(r=Rr,`selectionStart`in r&&Ir(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Br&&jr(Br,r)||(Br=r,r=Ed(zr,`onSelect`),0>=o,i-=o,Pi=1<<32-Ge(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),o=a(_,o,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),M&&Ii(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(i,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(i,h),o=a(y,o,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(i,h),M&&Ii(i,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(i,v.value,l),v!==null&&(o=a(v,o,g),d===null?u=v:d.sibling=v,d=v);return M&&Ii(i,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,i,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),o=a(v,o,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(i,e)}),M&&Ii(i,g),u}function b(e,r,a,c){if(typeof a==`object`&&a&&a.type===y&&a.key===null&&(a=a.props.children),typeof a==`object`&&a){switch(a.$$typeof){case _:a:{for(var l=a.key;r!==null;){if(r.key===l){if(l=a.type,l===y){if(r.tag===7){n(e,r.sibling),c=i(r,a.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===E&&Pa(l)===r.type){n(e,r.sibling),c=i(r,a.props),Va(c,a),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}a.type===y?(c=xi(a.props.children,e.mode,c,a.key),c.return=e,e=c):(c=bi(a.type,a.key,a.props,null,e.mode,c),Va(c,a),c.return=e,e=c)}return o(e);case v:a:{for(l=a.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===a.containerInfo&&r.stateNode.implementation===a.implementation){n(e,r.sibling),c=i(r,a.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=wi(a,e.mode,c),c.return=e,e=c}return o(e);case E:return a=Pa(a),b(e,r,a,c)}if(ce(a))return h(e,r,a,c);if(ae(a)){if(l=ae(a),typeof l!=`function`)throw Error(s(150));return a=l.call(a),g(e,r,a,c)}if(typeof a.then==`function`)return b(e,r,Ba(a),c);if(a.$$typeof===S)return b(e,r,la(e,a),c);Ha(e,a)}return typeof a==`string`&&a!==``||typeof a==`number`||typeof a==`bigint`?(a=``+a,r!==null&&r.tag===6?(n(e,r.sibling),c=i(r,a),c.return=e,e=c):(n(e,r),c=Si(a,e.mode,c),c.return=e,e=c),o(e)):n(e,r)}return function(e,t,n,r){try{za=0;var i=b(e,t,n,r);return Ra=null,i}catch(t){if(t===Oa||t===Aa)throw t;var a=gi(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var Wa=Ua(!0),Ga=Ua(!1),Ka=!1;function qa(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ja(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Ya(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Xa(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,W&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=pi(e),fi(e,null,n),t}return li(e,r,t,n),pi(e)}function Za(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,st(e,n)}}function Qa(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var $a=!1;function eo(){if($a){var e=ya;if(e!==null)throw e}}function to(e,t,n,r){$a=!1;var i=e.updateQueue;Ka=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(q&f)===f:(r&f)===f){f!==0&&f===va&&($a=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var m=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(m=g.payload,typeof m==`function`){d=m.call(_,d,f);break a}d=m;break a;case 3:m.flags=m.flags&-65537|128;case 0:if(m=g.payload,f=typeof m==`function`?m.call(_,d,f):m,f==null)break a;d=h({},d,f);break a;case 2:Ka=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Kl|=o,e.lanes=o,e.memoizedState=d}}function no(e,t){if(typeof e!=`function`)throw Error(s(191,e));e.call(t)}function ro(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=D.T,s={};D.T=s,Rs(e,!1,t,n);try{var c=i(),l=D.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Ls(e,t,Sa(c,r),pu(e)):Ls(e,t,r,pu(e))}catch(n){Ls(e,t,{then:function(){},status:`rejected`,reason:n},pu())}finally{O.p=a,o!==null&&s.types!==null&&(o.types=s.types),D.T=o}}function Ds(){}function Os(e,t,n,r){if(e.tag!==5)throw Error(s(476));var i=ks(e).queue;Es(e,i,t,le,n===null?Ds:function(){return As(e),n(r)})}function ks(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:le,baseState:le,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:zo,lastRenderedState:le},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:zo,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function As(e){var t=ks(e);t.next===null&&(t=e.alternate.memoizedState),Ls(e,t.next.queue,{},pu())}function js(){return ca(Qf)}function Ms(){return z().memoizedState}function Ns(){return z().memoizedState}function Ps(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=pu();e=Ya(n);var r=Xa(t,e,n);r!==null&&(hu(r,t,n),Za(r,t,n)),t={cache:ma()},e.payload=t;return}t=t.return}}function Fs(e,t,n){var r=pu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},zs(e)?Bs(t,n):(n=ui(e,t,n,r),n!==null&&(hu(n,e,r),Vs(n,t,r)))}function Is(e,t,n){Ls(e,t,n,pu())}function Ls(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(zs(e))Bs(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,Ar(s,o))return li(e,t,i,0),G===null&&ci(),!1}catch{}if(n=ui(e,t,i,r),n!==null)return hu(n,e,r),Vs(n,t,r),!0}return!1}function Rs(e,t,n,r){if(r={lane:2,revertLane:dd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},zs(e)){if(t)throw Error(s(479))}else t=ui(e,n,r,2),t!==null&&hu(t,e,2)}function zs(e){var t=e.alternate;return e===F||t!==null&&t===F}function Bs(e,t){bo=yo=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Vs(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,st(e,n)}}var Hs={readContext:ca,use:Lo,useCallback:R,useContext:R,useEffect:R,useImperativeHandle:R,useLayoutEffect:R,useInsertionEffect:R,useMemo:R,useReducer:R,useRef:R,useState:R,useDebugValue:R,useDeferredValue:R,useTransition:R,useSyncExternalStore:R,useId:R,useHostTransitionStatus:R,useFormState:R,useActionState:R,useOptimistic:R,useMemoCache:R,useCacheRefresh:R};Hs.useEffectEvent=R;var Us={readContext:ca,use:Lo,useCallback:function(e,t){return Po().memoizedState=[e,t===void 0?null:t],e},useContext:ca,useEffect:ps,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),ds(4194308,4,ys.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ds(4194308,4,e,t)},useInsertionEffect:function(e,t){ds(4,2,e,t)},useMemo:function(e,t){var n=Po();t=t===void 0?null:t;var r=e();if(xo){We(!0);try{e()}finally{We(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=Po();if(n!==void 0){var i=n(t);if(xo){We(!0);try{n(t)}finally{We(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Fs.bind(null,F,e),[r.memoizedState,e]},useRef:function(e){var t=Po();return e={current:e},t.memoizedState=e},useState:function(e){e=Yo(e);var t=e.queue,n=Is.bind(null,F,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:xs,useDeferredValue:function(e,t){return ws(Po(),e,t)},useTransition:function(){var e=Yo(!1);return e=Es.bind(null,F,e.queue,!0,!1),Po().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=F,i=Po();if(M){if(n===void 0)throw Error(s(407));n=n()}else{if(n=t(),G===null)throw Error(s(349));q&127||Wo(r,t,n)}i.memoizedState=n;var a={value:n,getSnapshot:t};return i.queue=a,ps(Ko.bind(null,r,a,e),[e]),r.flags|=2048,ls(9,{destroy:void 0},Go.bind(null,r,a,n,t),null),n},useId:function(){var e=Po(),t=G.identifierPrefix;if(M){var n=Fi,r=Pi;n=(r&~(1<<32-Ge(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=So++,0<\/script>`,a=a.removeChild(a.firstChild);break;case`select`:a=typeof r.is==`string`?o.createElement(`select`,{is:r.is}):o.createElement(`select`),r.multiple?a.multiple=!0:r.size&&(a.size=r.size);break;default:a=typeof r.is==`string`?o.createElement(i,{is:r.is}):o.createElement(i)}}a[mt]=t,a[ht]=r;a:for(o=t.child;o!==null;){if(o.tag===5||o.tag===6)a.appendChild(o.stateNode);else if(o.tag!==4&&o.tag!==27&&o.child!==null){o.child.return=o,o=o.child;continue}if(o===t)break a;for(;o.sibling===null;){if(o.return===null||o.return===t)break a;o=o.return}o.sibling.return=o.return,o=o.sibling}t.stateNode=a;a:switch(Pd(a,i,r),i){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Ic(t)}}return V(t),Lc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Ic(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(s(166));if(e=he.current,Ji(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,i=Vi,i!==null)switch(i.tag){case 27:case 5:r=i.memoizedProps}e[mt]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Md(e.nodeValue,n)),e||Gi(t,!0)}else e=Bd(e).createTextNode(r),e[mt]=t,t.stateNode=e}return V(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Ji(t),n!==null){if(e===null){if(!r)throw Error(s(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(s(557));e[mt]=t}else Yi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;V(t),e=!1}else n=Xi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(go(t),t):(go(t),null);if(t.flags&128)throw Error(s(558))}return V(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(i=Ji(t),r!==null&&r.dehydrated!==null){if(e===null){if(!i)throw Error(s(318));if(i=t.memoizedState,i=i===null?null:i.dehydrated,!i)throw Error(s(317));i[mt]=t}else Yi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;V(t),i=!1}else i=Xi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=i),i=!0;if(!i)return t.flags&256?(go(t),t):(go(t),null)}return go(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,i=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(i=r.alternate.memoizedState.cachePool.pool),a=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(a=r.memoizedState.cachePool.pool),a!==i&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),zc(t,t.updateQueue),V(t),null);case 4:return ve(),e===null&&Sd(t.stateNode.containerInfo),V(t),null;case 10:return na(t.type),V(t),null;case 19:if(k(P),r=t.memoizedState,r===null)return V(t),null;if(i=(t.flags&128)!=0,a=r.rendering,a===null)if(i)Bc(r,!1);else{if(Y!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(a=_o(e),a!==null){for(t.flags|=128,Bc(r,!1),e=a.updateQueue,t.updateQueue=e,zc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)yi(n,e),n=n.sibling;return A(P,P.current&1|2),M&&Ii(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Ne()>nu&&(t.flags|=128,i=!0,Bc(r,!1),t.lanes=4194304)}else{if(!i)if(e=_o(a),e!==null){if(t.flags|=128,i=!0,e=e.updateQueue,t.updateQueue=e,zc(t,e),Bc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!a.alternate&&!M)return V(t),null}else 2*Ne()-r.renderingStartTime>nu&&n!==536870912&&(t.flags|=128,i=!0,Bc(r,!1),t.lanes=4194304);r.isBackwards?(a.sibling=t.child,t.child=a):(e=r.last,e===null?t.child=a:e.sibling=a,r.last=a)}return r.tail===null?(V(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Ne(),e.sibling=null,n=P.current,A(P,i?n&1|2:n&1),M&&Ii(t,r.treeForkCount),e);case 22:case 23:return go(t),co(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(V(t),t.subtreeFlags&6&&(t.flags|=8192)):V(t),n=t.updateQueue,n!==null&&zc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&k(wa),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),na(N),V(t),null;case 25:return null;case 30:return null}throw Error(s(156,t.tag))}function Hc(e,t){switch(zi(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return na(N),ve(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return be(t),null;case 31:if(t.memoizedState!==null){if(go(t),t.alternate===null)throw Error(s(340));Yi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(go(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(s(340));Yi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return k(P),null;case 4:return ve(),null;case 10:return na(t.type),null;case 22:case 23:return go(t),co(),e!==null&&k(wa),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return na(N),null;case 25:return null;default:return null}}function Uc(e,t){switch(zi(t),t.tag){case 3:na(N),ve();break;case 26:case 27:case 5:be(t);break;case 4:ve();break;case 31:t.memoizedState!==null&&go(t);break;case 13:go(t);break;case 19:k(P);break;case 10:na(t.type);break;case 22:case 23:go(t),co(),e!==null&&k(wa);break;case 24:na(N)}}function Wc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Z(t,t.return,e)}}function Gc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Z(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Z(t,t.return,e)}}function Kc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{ro(t,n)}catch(t){Z(e,e.return,t)}}}function qc(e,t,n){n.props=Xs(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Z(e,t,n)}}function Jc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Z(e,t,n)}}function Yc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Z(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Z(e,t,n)}else n.current=null}function Xc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Z(e,e.return,t)}}function Zc(e,t,n){try{var r=e.stateNode;Fd(r,e.type,n,t),r[ht]=t}catch(t){Z(e,e.return,t)}}function Qc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Zd(e.type)||e.tag===4}function $c(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Qc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Zd(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function el(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=cn));else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(el(e,t,n),e=e.sibling;e!==null;)el(e,t,n),e=e.sibling}function tl(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(tl(e,t,n),e=e.sibling;e!==null;)tl(e,t,n),e=e.sibling}function nl(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Pd(t,r,n),t[mt]=e,t[ht]=n}catch(t){Z(e,e.return,t)}}var rl=!1,H=!1,il=!1,al=typeof WeakSet==`function`?WeakSet:Set,ol=null;function sl(e,t){if(e=e.containerInfo,Rd=sp,e=Fr(e),Ir(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var i=r.anchorOffset,a=r.focusNode;r=r.focusOffset;try{n.nodeType,a.nodeType}catch{n=null;break a}var o=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||i!==0&&f.nodeType!==3||(c=o+i),f!==a||r!==0&&f.nodeType!==3||(l=o+r),f.nodeType===3&&(o+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===i&&(c=o),p===a&&++d===r&&(l=o),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(zd={focusedElem:e,selectionRange:n},sp=!1,ol=t;ol!==null;)if(t=ol,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,ol=e;else for(;ol!==null;){switch(t=ol,a=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Pd(a,r,n),a[mt]=e,Dt(a),r=a;break a;case`link`:var o=Vf(`link`,`href`,i).get(r+(n.href||``));if(o){for(var c=0;cg&&(o=g,g=h,h=o);var _=Nr(s,h),v=Nr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,D.T=null,n=lu,lu=null;var a=au,o=su;if(X=0,ou=au=null,su=0,W&6)throw Error(s(331));var c=W;if(W|=4,Il(a.current),Ol(a,a.current,o,n),W=c,id(0,!1),Ue&&typeof Ue.onPostCommitFiberRoot==`function`)try{Ue.onPostCommitFiberRoot(He,a)}catch{}return!0}finally{O.p=i,D.T=r,Vu(e,t)}}function Wu(e,t,n){t=Ei(n,t),t=nc(e.stateNode,t,2),e=Xa(e,t,2),e!==null&&(it(e,2),rd(e))}function Z(e,t,n){if(e.tag===3)Wu(e,e,n);else for(;t!==null;){if(t.tag===3){Wu(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(iu===null||!iu.has(r))){e=Ei(n,e),n=rc(2),r=Xa(t,n,2),r!==null&&(ic(n,r,t,e),it(r,2),rd(r));break}}t=t.return}}function Gu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Bl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Wl=!0,i.add(n),e=Ku.bind(null,e,t,n),t.then(e,e))}function Ku(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,G===e&&(q&n)===n&&(Y===4||Y===3&&(q&62914560)===q&&300>Ne()-eu?!(W&2)&&Su(e,0):Jl|=n,Xl===q&&(Xl=0)),rd(e)}function qu(e,t){t===0&&(t=nt()),e=di(e,t),e!==null&&(it(e,t),rd(e))}function Ju(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),qu(e,n)}function Yu(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,i=e.memoizedState;i!==null&&(n=i.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(s(314))}r!==null&&r.delete(t),qu(e,n)}function Xu(e,t){return ke(e,t)}var Zu=null,Qu=null,$u=!1,ed=!1,td=!1,nd=0;function rd(e){e!==Qu&&e.next===null&&(Qu===null?Zu=Qu=e:Qu=Qu.next=e),ed=!0,$u||($u=!0,ud())}function id(e,t){if(!td&&ed){td=!0;do for(var n=!1,r=Zu;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Ge(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,ld(r,a))}else a=q,a=$e(r,r===G?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||et(r,a)||(n=!0,ld(r,a));r=r.next}while(n);td=!1}}function ad(){od()}function od(){ed=$u=!1;var e=0;nd!==0&&Gd()&&(e=nd);for(var t=Ne(),n=null,r=Zu;r!==null;){var i=r.next,a=sd(r,t);a===0?(r.next=null,n===null?Zu=i:n.next=i,i===null&&(Qu=n)):(n=r,(e!==0||a&3)&&(ed=!0)),r=i}X!==0&&X!==5||id(e,!1),nd!==0&&(nd=0)}function sd(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Id(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function xf(e,t,n){var r=bf;if(r&&typeof t==`string`&&t){var i=Kt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),hf.has(i)||(hf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Pd(t,`link`,e),Dt(t),r.head.appendChild(t)))}}function Sf(e){_f.D(e),xf(`dns-prefetch`,e,null)}function Cf(e,t){_f.C(e,t),xf(`preconnect`,e,t)}function wf(e,t,n){_f.L(e,t,n);var r=bf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Kt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Kt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Kt(n.imageSizes)+`"]`)):i+=`[href="`+Kt(e)+`"]`;var a=i;switch(t){case`style`:a=Af(e);break;case`script`:a=Pf(e)}mf.has(a)||(e=h({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),mf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(jf(a))||t===`script`&&r.querySelector(Ff(a))||(t=r.createElement(`link`),Pd(t,`link`,e),Dt(t),r.head.appendChild(t)))}}function Tf(e,t){_f.m(e,t);var n=bf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Kt(r)+`"][href="`+Kt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Pf(e)}if(!mf.has(a)&&(e=h({rel:`modulepreload`,href:e},t),mf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Ff(a)))return}r=n.createElement(`link`),Pd(r,`link`,e),Dt(r),n.head.appendChild(r)}}}function Ef(e,t,n){_f.S(e,t,n);var r=bf;if(r&&e){var i=Et(r).hoistableStyles,a=Af(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(jf(a)))s.loading=5;else{e=h({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=mf.get(a))&&Rf(e,n);var c=o=r.createElement(`link`);Dt(c),Pd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Lf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Df(e,t){_f.X(e,t);var n=bf;if(n&&e){var r=Et(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),Dt(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Of(e,t){_f.M(e,t);var n=bf;if(n&&e){var r=Et(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0,type:`module`},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),Dt(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function kf(e,t,n,r){var i=(i=he.current)?gf(i):null;if(!i)throw Error(s(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Af(n.href),n=Et(i).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Af(n.href);var a=Et(i).hoistableStyles,o=a.get(e);if(o||(i=i.ownerDocument||i,o={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},a.set(e,o),(a=i.querySelector(jf(e)))&&!a._p&&(o.instance=a,o.state.loading=5),mf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},mf.set(e,n),a||Nf(i,e,n,o.state))),t&&r===null)throw Error(s(528,``));return o}if(t&&r!==null)throw Error(s(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Pf(n),n=Et(i).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(s(444,e))}}function Af(e){return`href="`+Kt(e)+`"`}function jf(e){return`link[rel="stylesheet"][`+e+`]`}function Mf(e){return h({},e,{"data-precedence":e.precedence,precedence:null})}function Nf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Pd(t,`link`,n),Dt(t),e.head.appendChild(t))}function Pf(e){return`[src="`+Kt(e)+`"]`}function Ff(e){return`script[async]`+e}function If(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Kt(n.href)+`"]`);if(r)return t.instance=r,Dt(r),r;var i=h({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),Dt(r),Pd(r,`style`,i),Lf(r,n.precedence,e),t.instance=r;case`stylesheet`:i=Af(n.href);var a=e.querySelector(jf(i));if(a)return t.state.loading|=4,t.instance=a,Dt(a),a;r=Mf(n),(i=mf.get(i))&&Rf(r,i),a=(e.ownerDocument||e).createElement(`link`),Dt(a);var o=a;return o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),t.state.loading|=4,Lf(a,n.precedence,e),t.instance=a;case`script`:return a=Pf(n.src),(i=e.querySelector(Ff(a)))?(t.instance=i,Dt(i),i):(r=n,(i=mf.get(a))&&(r=h({},n),zf(r,i)),e=e.ownerDocument||e,i=e.createElement(`script`),Dt(i),Pd(i,`link`,r),e.head.appendChild(i),t.instance=i);case`void`:return null;default:throw Error(s(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Lf(r,n.precedence,e));return t.instance}function Lf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Uf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Wf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Gf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Af(r.href),a=t.querySelector(jf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Jf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,Dt(a);return}a=t.ownerDocument||t,r=Mf(r),(i=mf.get(i))&&Rf(r,i),a=a.createElement(`link`),Dt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Jf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Kf=0;function qf(e,t){return e.stylesheets&&e.count===0&&Xf(e,e.stylesheets),0Kf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Jf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Xf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Yf=null;function Xf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Yf=new Map,t.forEach(Zf,e),Yf=null,Jf.call(e))}function Zf(e,t){if(!(t.state.loading&4)){var n=Yf.get(e);if(n)var r=n.get(null);else{n=new Map,Yf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=s()})),l=n(),u=c(),d=e((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),f=e(((e,t)=>{t.exports=d()}))(),p={bg:`#0A0A0C`,panel:`#141418`,surface:`#1C1C22`,border:`#2A2A32`,amber:`#E8A030`,amberDim:`#7A5218`,blue:`#3A7BA8`,blueDim:`#1E4060`,green:`#3AB87A`,red:`#C84040`,textPrimary:`#F0EDE6`,textSec:`#8888A0`,textDim:`#444458`},m=window.location.origin,h=m.replace(/^http/,`ws`)+`/ws`,g=`guitar`;try{let e=localStorage.getItem(`pedal_channel`);(e===`guitar`||e===`bass`)&&(g=e)}catch{}function _(e){g=e}function v(e){let t=g;return t?e+(e.includes(`?`)?`&`:`?`)+`channel=`+encodeURIComponent(t):e}async function y(e){let t=await fetch(`${m}${v(e)}`);if(!t.ok)throw Error(`${e} => ${t.status}`);return t.json()}async function b(e,t){let n=await fetch(`${m}${v(e)}`,{method:`POST`,headers:{"Content-Type":`application/json`},body:t==null?void 0:JSON.stringify(t)});if(!n.ok)throw Error(`${e} => ${n.status}`);return n.json()}async function x(e,t){let n=await fetch(`${m}${v(e)}`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)});if(!n.ok)throw Error(`${e} => ${n.status}`);return n.json()}async function ee(e){let t=await fetch(`${m}${v(e)}`,{method:`DELETE`});if(!t.ok)throw Error(`${e} => ${t.status}`);return t.json()}async function S(e,t){let n=new FormData;n.append(`file`,t);let r=await fetch(`${m}${v(e)}`,{method:`POST`,body:n});if(!r.ok)throw Error(`${e} => ${r.status}`);return r.json()}function C(){let[e,t]=(0,l.useState)(null),[n,r]=(0,l.useState)(!1),i=(0,l.useRef)(null),a=(0,l.useRef)(null);return(0,l.useEffect)(()=>{y(`/api/state`).then(e=>{t(e),r(!0)}).catch(()=>{r(!1)});let e;function n(){try{let a=new WebSocket(h);a.onmessage=e=>{try{let n=JSON.parse(e.data);(n.type===`connected`||n.type===`state`)&&(t(n.state),r(!0)),n.type===`preset_changed`&&n.state&&t(n.state),n.type===`bypass_changed`&&t(e=>e&&{...e,bypass:n.bypass}),n.type===`tuner_changed`&&t(e=>e&&{...e,tuner_enabled:n.tuner_enabled}),n.type===`routing_changed`&&t(e=>e&&{...e,routing_mode:n.routing_mode,routing_breakpoint:n.routing_breakpoint}),n.type===`channel_mode_changed`&&t(e=>e&&{...e,channel_mode:n.channel_mode}),(n.type===`model_loaded`||n.type===`ir_loaded`)&&y(`/api/state`).then(e=>t(e)),n.type===`snapshot_recalled`&&n.state&&t(n.state),n.type===`snapshot_saved`&&y(`/api/state`).then(e=>t(e))}catch{}},a.onclose=()=>{r(!1),e=setTimeout(n,3e3)},a.onerror=()=>a.close(),i.current=a}catch{e=setTimeout(n,3e3)}}return n(),a.current=setInterval(()=>{y(`/api/state`).then(e=>{t(e),r(!0)}).catch(()=>{})},5e3),()=>{clearInterval(a.current),clearTimeout(e),i.current&&i.current.close()}},[]),{state:e,connected:n,refresh:(0,l.useCallback)(()=>{y(`/api/state`).then(e=>{t(e),r(!0)}).catch(()=>{})},[])}}var te=` + @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@300;400;500;600&display=swap'); + * { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; } + body { background: ${p.bg}; color: ${p.textPrimary}; font-family: 'Inter', sans-serif; user-select: none; } + :root { --amber: ${p.amber}; --blue: ${p.blue}; --green: ${p.green}; } + + .mono { font-family: 'JetBrains Mono', monospace; } + + ::-webkit-scrollbar { width: 4px; height: 4px; } + ::-webkit-scrollbar-track { background: ${p.panel}; } + ::-webkit-scrollbar-thumb { background: ${p.border}; border-radius: 2px; } + + input, textarea, select { font-family: inherit; } + + .knob-wrap { display: flex; flex-direction: column; align-items: center; gap: 6px; cursor: pointer; } + .knob { border-radius: 50%; position: relative; touch-action: none; } + .knob-inner { border-radius: 50%; width: 100%; height: 100%; position: relative; + background: radial-gradient(circle at 35% 30%, #3A3A48, #1A1A22); + box-shadow: 0 2px 8px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.07); } + .knob-dot { position: absolute; width: 3px; border-radius: 2px; background: var(--amber); + left: 50%; transform-origin: bottom center; box-shadow: 0 0 6px var(--amber); } + .knob-ring { position: absolute; inset: -5px; border-radius: 50%; } + .knob-label { font-size: 10px; color: ${p.textSec}; letter-spacing: .06em; text-transform: uppercase; text-align: center; } + .knob-value { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: ${p.amber}; } + + .toggle { display: flex; background: ${p.surface}; border-radius: 6px; border: 1px solid ${p.border}; overflow: hidden; } + .toggle-btn { flex: 1; padding: 7px 12px; font-size: 12px; font-weight: 500; letter-spacing: .04em; + background: none; border: none; color: ${p.textSec}; cursor: pointer; transition: all .15s; } + .toggle-btn.active { background: ${p.amber}; color: #000; } + + .vu-bar { display: flex; gap: 2px; align-items: flex-end; height: 100%; } + .vu-seg { width: 4px; border-radius: 1px; transition: opacity .05s; } + + .fader-track { width: 4px; border-radius: 2px; background: ${p.border}; position: relative; cursor: pointer; touch-action: none; } + .fader-fill { width: 100%; border-radius: 2px; position: absolute; bottom: 0; transition: height .05s; } + .fader-thumb { width: 28px; height: 10px; border-radius: 3px; position: absolute; left: 50%; transform: translateX(-50%); + background: linear-gradient(180deg, #3A3A4A, #252530); + border: 1px solid #4A4A5A; box-shadow: 0 2px 4px rgba(0,0,0,.5); + cursor: grab; touch-action: none; } + .fader-thumb::after { content: ''; position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); + width: 16px; height: 2px; border-radius: 1px; background: rgba(255,255,255,.15); + box-shadow: 0 3px 0 rgba(255,255,255,.15), 0 -3px 0 rgba(255,255,255,.15); } + + .tabbar { display: flex; background: ${p.panel}; border-bottom: 1px solid ${p.border}; flex-shrink: 0; } + .tab { flex: 1; padding: 10px 4px; font-size: 10px; font-weight: 500; letter-spacing: .04em; + text-align: center; text-transform: uppercase; color: ${p.textDim}; + cursor: pointer; border-bottom: 2px solid transparent; transition: all .15s; } + .tab.active { color: ${p.amber}; border-bottom-color: ${p.amber}; } + + .card { background: ${p.panel}; border: 1px solid ${p.border}; border-radius: 10px; padding: 16px; } + .card-sm { background: ${p.surface}; border: 1px solid ${p.border}; border-radius: 8px; padding: 12px; } + + .btn { padding: 10px 18px; border-radius: 7px; font-size: 13px; font-weight: 600; + letter-spacing: .04em; border: none; cursor: pointer; transition: all .12s; } + .btn-primary { background: ${p.amber}; color: #000; } + .btn-primary:active { filter: brightness(.85); transform: scale(.97); } + .btn-ghost { background: ${p.surface}; color: ${p.textPrimary}; border: 1px solid ${p.border}; } + .btn-ghost:active { background: ${p.border}; } + .btn-danger { background: rgba(200,64,64,.2); color: ${p.red}; border: 1px solid rgba(200,64,64,.3); } + .btn-icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; + justify-content: center; background: ${p.surface}; border: 1px solid ${p.border}; cursor: pointer; + font-size: 16px; transition: all .12s; } + .btn-icon:active { transform: scale(.93); } + .btn-icon.active { background: rgba(232,160,48,.15); border-color: ${p.amber}; color: ${p.amber}; } + .btn-sm { padding: 6px 12px; font-size: 12px; } + + .fx-block { background: ${p.panel}; border: 1px solid ${p.border}; border-radius: 10px; + overflow: hidden; transition: border-color .15s; } + .fx-block.active { border-color: ${p.amber}; } + .fx-block.bypassed { opacity: .45; } + .fx-header { display: flex; align-items: center; gap: 10px; padding: 10px 12px; + border-bottom: 1px solid ${p.border}; background: ${p.surface}; cursor: pointer; } + .fx-body { padding: 12px; display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; } + .bypass-led { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } + .led-on { background: ${p.green}; box-shadow: 0 0 8px ${p.green}; } + .led-off { background: ${p.textDim}; } + + .waveform-wrap { position: relative; overflow: hidden; border-radius: 6px; background: #0F0F14; } + .waveform-canvas { display: block; } + .playhead { position: absolute; top: 0; bottom: 0; width: 1.5px; background: ${p.amber}; + box-shadow: 0 0 8px ${p.amber}; pointer-events: none; transition: left .08s linear; } + + .nam-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; + background: rgba(58,123,168,.15); border: 1px solid rgba(58,123,168,.35); border-radius: 5px; + font-family: 'JetBrains Mono', monospace; font-size: 11px; color: ${p.blue}; } + + .section-label { font-size: 10px; letter-spacing: .1em; text-transform: uppercase; + color: ${p.textDim}; font-weight: 600; margin-bottom: 10px; } + + .preset-row { display: flex; align-items: center; gap: 12px; padding: 10px 12px; + border-radius: 8px; cursor: pointer; transition: background .12s; border: 1px solid transparent; } + .preset-row:active, .preset-row.active { background: rgba(232,160,48,.08); border-color: rgba(232,160,48,.25); } + + .transport-btn { width: 44px; height: 44px; border-radius: 50%; display: flex; + align-items: center; justify-content: center; font-size: 16px; cursor: pointer; + border: 2px solid ${p.border}; background: ${p.surface}; transition: all .12s; } + .transport-btn:active { transform: scale(.92); } + .transport-btn.rec { border-color: ${p.red}; background: rgba(200,64,64,.15); } + .transport-btn.play { border-color: ${p.green}; background: rgba(58,184,122,.15); } + + .badge { display: inline-flex; align-items: center; justify-content: center; + padding: 2px 7px; border-radius: 4px; font-size: 10px; font-weight: 700; + letter-spacing: .06em; text-transform: uppercase; } + .badge-amber { background: rgba(232,160,48,.2); color: ${p.amber}; } + .badge-green { background: rgba(58,184,122,.2); color: ${p.green}; } + .badge-blue { background: rgba(58,123,168,.2); color: ${p.blue}; } + .badge-red { background: rgba(200,64,64,.2); color: ${p.red}; } + + .screen-header { padding: 12px 14px 8px; display: flex; align-items: center; + justify-content: space-between; border-bottom: 1px solid ${p.border}; flex-shrink: 0; } + .screen-title { font-size: 13px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: ${p.textSec}; } + + .screen-body { flex: 1; overflow-y: auto; padding: 12px; display: flex; + flex-direction: column; gap: 12px; } + + /* Settings lists */ + .setting-row { display: flex; align-items: center; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid ${p.border}; } + .setting-label { font-size: 13px; color: ${p.textPrimary}; } + .setting-desc { font-size: 11px; color: ${p.textDim}; margin-top: 2px; } + .switch { width: 40px; height: 22px; border-radius: 11px; background: ${p.border}; cursor: pointer; position: relative; transition: background .2s; flex-shrink: 0; } + .switch.on { background: ${p.green}; } + .switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 50%; background: white; transition: transform .15s; } + .switch.on::after { transform: translateX(18px); } + + .search-bar { display: flex; background: ${p.surface}; border-radius: 7px; border: 1px solid ${p.border}; + padding: 8px 12px; gap: 8px; align-items: center; } + .search-bar input { background: none; border: none; outline: none; color: ${p.textPrimary}; font-size: 13px; flex: 1; } + .search-bar input::placeholder { color: ${p.textDim}; } + + .file-drop { border: 2px dashed ${p.border}; border-radius: 8px; padding: 20px; text-align: center; + cursor: pointer; transition: all .15s; font-size: 13px; color: ${p.textDim}; } + .file-drop:hover, .file-drop.dragover { border-color: ${p.amber}; color: ${p.amber}; } + + .loader { width: 20px; height: 20px; border: 2px solid ${p.border}; border-top-color: ${p.amber}; + border-radius: 50%; animation: spin .6s linear infinite; } + @keyframes spin { to { transform: rotate(360deg); } } + + .slide-panel { position: fixed; inset: 0; background: rgba(0,0,0,.7); z-index: 100; + display: flex; align-items: flex-end; justify-content: center; } + .slide-panel-inner { background: ${p.bg}; max-width: 440px; width: 100%; max-height: 85vh; + border-radius: 16px 16px 0 0; overflow: hidden; display: flex; flex-direction: column; } + .slide-panel-handle { width: 36px; height: 4px; border-radius: 2px; background: ${p.border}; + margin: 8px auto; flex-shrink: 0; } + + /* Snapshot slots */ + .snap-grid { display: flex; gap: 6px; overflow-x: auto; padding: 4px 0; } + .snap-slot { min-width: 60px; flex-shrink: 0; border-radius: 8px; border: 1.5px solid ${p.border}; + background: ${p.surface}; cursor: pointer; text-align: center; padding: 8px 4px; + transition: all .12s; touch-action: manipulation; position: relative; } + .snap-slot:active { transform: scale(.95); } + .snap-slot.filled { border-color: ${p.amberDim}; } + .snap-slot.active { border-color: ${p.amber}; background: rgba(232,160,48,.08); } + .snap-slot .snap-num { font-family: 'JetBrains Mono', monospace; font-size: 16px; + font-weight: 700; color: ${p.textDim}; } + .snap-slot.filled .snap-num { color: ${p.amber}; } + .snap-slot.active .snap-num { color: ${p.amber}; text-shadow: 0 0 8px ${p.amber}; } + .snap-slot .snap-name { font-size: 9px; color: ${p.textDim}; margin-top: 3px; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .snap-slot.filled .snap-name { color: ${p.textSec}; } + .snap-slot.active .snap-name { color: ${p.amber}; } + .snap-slot .snap-empty { font-size: 9px; color: ${p.textDim}; margin-top: 3px; font-style: italic; } + .snap-slot .snap-save-indicator { position: absolute; top: -3px; right: -3px; + width: 10px; height: 10px; border-radius: 50%; background: ${p.green}; + box-shadow: 0 0 6px ${p.green}; animation: snap-pop .3s ease-out; } + @keyframes snap-pop { 0% { transform: scale(2); opacity: 0; } 100% { transform: scale(1); opacity: 1; } } + .snap-name-input { background: ${p.surface}; border: 1px solid ${p.amber}; border-radius: 4px; + padding: 2px 6px; color: ${p.amber}; font-size: 10px; text-align: center; outline: none; + font-family: 'Inter', sans-serif; width: 100%; } + .snap-header-btn { cursor: pointer; border: none; background: none; color: ${p.textSec}; + font-size: 16px; padding: 4px 6px; border-radius: 6px; transition: all .12s; + display: flex; align-items: center; gap: 4px; } + .snap-header-btn:hover { color: ${p.amber}; background: rgba(232,160,48,.1); } + .snap-header-btn.active { color: ${p.amber}; background: rgba(232,160,48,.15); } + .snap-header-num { font-family: 'JetBrains Mono', monospace; font-size: 11px; + font-weight: 700; color: ${p.amber}; min-width: 16px; text-align: center; } +`;function w({label:e,value:t=50,onChange:n,size:r=52,color:i=p.amber,min:a=0,max:o=100}){let s=(0,l.useRef)(null),c=(0,l.useRef)(null),u=(t-a)/(o-a),d=-140+u*280,m=e=>{e.preventDefault(),c.current={y:e.touches?e.touches[0].clientY:e.clientY,val:t};let r=e=>{let t=e.touches?e.touches[0].clientY:e.clientY,r=(c.current.y-t)/120,i=Math.max(a,Math.min(o,c.current.val+r*(o-a)));n?.(Math.round(i))},i=()=>{window.removeEventListener(`mousemove`,r),window.removeEventListener(`mouseup`,i),window.removeEventListener(`touchmove`,r),window.removeEventListener(`touchend`,i)};window.addEventListener(`mousemove`,r),window.addEventListener(`mouseup`,i),window.addEventListener(`touchmove`,r,{passive:!1}),window.addEventListener(`touchend`,i)},h=r+12,g=h/2,_=h/2,v=r/2+3,y=(d-90)*(Math.PI/180),b=g+v*Math.cos(y),x=_+v*Math.sin(y);return(0,f.jsxs)(`div`,{className:`knob-wrap`,style:{width:r+12},children:[(0,f.jsxs)(`div`,{className:`knob`,style:{width:r,height:r},onMouseDown:m,onTouchStart:m,ref:s,children:[(0,f.jsxs)(`svg`,{style:{position:`absolute`,inset:-6,width:h,height:h,overflow:`visible`},children:[(0,f.jsx)(`circle`,{cx:g,cy:_,r:v,fill:`none`,stroke:p.border,strokeWidth:`2.5`,strokeLinecap:`round`,opacity:`.5`}),u>0&&(0,f.jsx)(`path`,{d:`M ${g} ${_-v} A ${v} ${v} 0 ${+(u>.5)} 1 ${b} ${x}`,fill:`none`,stroke:i,strokeWidth:`2.5`,strokeLinecap:`round`,style:{filter:`drop-shadow(0 0 3px ${i})`}})]}),(0,f.jsx)(`div`,{className:`knob-inner`,style:{"--amber":i},children:(0,f.jsx)(`div`,{className:`knob-dot`,style:{height:r*.38,bottom:r/2-2,transform:`translateX(-50%) rotate(${d}deg)`,transformOrigin:`50% ${r*.38}px`}})})]}),(0,f.jsx)(`div`,{className:`knob-value mono`,style:{color:i},children:t}),(0,f.jsx)(`div`,{className:`knob-label`,children:e})]})}function T({level:e=0,height:t=60,vertical:n=!0}){let r=Math.round(e/100*12),i=e=>e>=10?p.red:e>=8?`#E8C030`:p.green;return n?(0,f.jsx)(`div`,{style:{display:`flex`,flexDirection:`column-reverse`,gap:2,height:t,alignItems:`center`},children:Array.from({length:12}).map((e,n)=>(0,f.jsx)(`div`,{style:{width:6,height:(t-24)/12,borderRadius:1,background:n(0,f.jsx)(`div`,{style:{flex:1,height:`100%`,borderRadius:1,background:te.default??e.default??50));return(0,f.jsxs)(`div`,{className:`fx-block ${n?`active`:``} ${r?`bypassed`:``}`,children:[(0,f.jsxs)(`div`,{className:`fx-header`,onClick:()=>s(e=>!e),children:[(0,f.jsx)(`div`,{className:`bypass-led ${r?`led-off`:`led-on`}`,onClick:e=>{e.stopPropagation(),a?.()}}),(0,f.jsx)(`span`,{style:{flex:1,fontSize:13,fontWeight:600},children:e}),(0,f.jsx)(`span`,{className:`badge badge-blue`,style:{fontSize:10},children:t}),(0,f.jsx)(`span`,{style:{color:p.textDim,fontSize:18,marginLeft:6},children:o?`▾`:`▸`})]}),o&&(0,f.jsx)(`div`,{className:`fx-body`,children:i.map((e,t)=>(0,f.jsx)(w,{label:e.name,value:c[t],size:44,onChange:e=>u(n=>n.map((n,r)=>r===t?e:n)),min:e.min??0,max:e.max??100},e.key||e.name))})]})}function ne({children:e,onClose:t}){return(0,f.jsx)(`div`,{className:`slide-panel`,onClick:t,children:(0,f.jsxs)(`div`,{className:`slide-panel-inner`,onClick:e=>e.stopPropagation(),children:[(0,f.jsx)(`div`,{className:`slide-panel-handle`}),e]})})}function re({state:e,connected:t,refresh:n}){let[r,i]=(0,l.useState)(e?.master_volume==null?80:Math.round(e.master_volume*100)),[a,o]=(0,l.useState)(null),[s,c]=(0,l.useState)(null),u=a??e?.bypass??!1,d=s??e?.tuner_enabled??!1;(0,l.useEffect)(()=>{e?.master_volume!=null&&i(Math.round(e.master_volume*100))},[e?.master_volume]),(0,l.useEffect)(()=>{e?.bypass!==void 0&&o(null),e?.tuner_enabled!==void 0&&c(null)},[e?.bypass,e?.tuner_enabled]);let m=async e=>{i(e);try{await x(`/api/volume`,{volume:e/100})}catch{}},h=async()=>{let e=!u;o(e);try{await b(`/api/bypass`,{bypass:e})}catch{o(null)}},g=async()=>{let e=!d;c(e);try{await b(`/api/tuner`,{enabled:e})}catch{c(null)}},_=e?.input_level??0,v=e?.output_level??0;return(0,f.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100%`},children:[(0,f.jsxs)(`div`,{className:`screen-header`,children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`screen-title`,children:`Active Rig`}),(0,f.jsxs)(`div`,{style:{display:`flex`,gap:6,marginTop:4,flexWrap:`wrap`,alignItems:`center`},children:[e?.current_preset?(0,f.jsx)(`span`,{className:`nam-chip`,style:{color:p.amber,borderColor:`${p.amber}55`,background:`${p.amber}15`},children:e.current_preset.name}):(0,f.jsx)(`span`,{style:{fontSize:11,color:p.textDim},children:`No preset loaded`}),t?(0,f.jsx)(`span`,{className:`badge badge-green`,children:`Connected`}):(0,f.jsx)(`span`,{className:`badge badge-red`,children:`Offline`})]})]}),(0,f.jsxs)(`div`,{style:{display:`flex`,gap:6,alignItems:`center`},children:[(0,f.jsx)(T,{level:_,height:44}),(0,f.jsx)(T,{level:v,height:44})]})]}),(0,f.jsxs)(`div`,{className:`screen-body`,children:[(0,f.jsxs)(`div`,{className:`card-sm`,style:{display:`flex`,gap:10,justifyContent:`space-around`},children:[(0,f.jsxs)(`div`,{style:{textAlign:`center`},children:[(0,f.jsx)(`div`,{className:`section-label`,style:{marginBottom:4},children:`Bypass`}),(0,f.jsx)(`div`,{className:`switch ${u?`on`:``}`,onClick:h,style:{margin:`0 auto`}})]}),(0,f.jsxs)(`div`,{style:{textAlign:`center`},children:[(0,f.jsx)(`div`,{className:`section-label`,style:{marginBottom:4},children:`Tuner`}),(0,f.jsx)(`div`,{className:`switch ${d?`on`:``}`,onClick:g,style:{margin:`0 auto`}})]}),(0,f.jsxs)(`div`,{style:{textAlign:`center`},children:[(0,f.jsx)(`div`,{className:`section-label`,style:{marginBottom:4},children:`Routing`}),(0,f.jsx)(`span`,{className:`mono`,style:{fontSize:12,color:p.amber},children:e?.routing_mode||`mono`})]})]}),d&&(0,f.jsxs)(`div`,{className:`card-sm`,style:{background:`${p.amber}15`,borderColor:p.amber,textAlign:`center`,padding:`20px 16px`},children:[(0,f.jsx)(`div`,{style:{fontSize:32,marginBottom:8},children:`🎵`}),(0,f.jsx)(`div`,{className:`section-label`,style:{color:p.amber,marginBottom:4,fontSize:14},children:`TUNER ACTIVE`}),(0,f.jsx)(`div`,{className:`mono`,style:{fontSize:24,color:p.amber,letterSpacing:4},children:`—————●—————`}),(0,f.jsx)(`div`,{style:{fontSize:11,color:p.textDim,marginTop:8},children:`Tap switch again to exit tuner mode`})]}),(0,f.jsxs)(`div`,{className:`card`,style:{display:`flex`,gap:20,alignItems:`center`},children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{className:`section-label`,children:`Master Volume`}),(0,f.jsx)(`input`,{type:`range`,min:0,max:100,value:r,onChange:e=>m(+e.target.value),style:{width:`100%`,accentColor:p.amber}}),(0,f.jsxs)(`div`,{className:`mono`,style:{fontSize:11,color:p.amber,marginTop:4},children:[r,`%`]})]}),(0,f.jsxs)(`div`,{className:`mono`,style:{fontSize:11,color:p.textSec,textAlign:`right`},children:[e?.nam_loaded?`NAM: Active`:`NAM: —`,(0,f.jsx)(`br`,{}),e?.ir_loaded?`IR: Active`:`IR: —`]})]}),(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsx)(`div`,{className:`section-label`,children:`Signal Chain`}),(0,f.jsx)(`div`,{style:{display:`flex`,alignItems:`center`,gap:4,overflowX:`auto`,padding:`4px 0`},children:[`IN`,e?.bypass?`BYP`:e?.nam_loaded?`NAM`:`AMP`,e?.ir_loaded?`IR`:`CAB`,`OUT`].map((e,t,n)=>(0,f.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:4,flexShrink:0},children:[(0,f.jsx)(`div`,{style:{padding:`3px 8px`,borderRadius:4,fontSize:10,fontWeight:700,background:t===0||t===n.length-1?p.blueDim:p.amberDim,color:t===0||t===n.length-1?p.blue:p.amber,border:`1px solid ${t===0||t===n.length-1?p.blue:p.amber}60`},children:e}),t{let e=document.querySelector(`.tabbar`)?.children;e&&e[3]?.click()},children:`Browse IRs`}),(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,style:{flex:1,fontSize:10},onClick:()=>{let e=document.querySelector(`.tabbar`)?.children;e&&e[2]?.click()},children:`Browse Models`})]})]})]})}var ie={noise_gate:`Noise Gate`,compressor:`Compressor`,boost:`Boost`,overdrive:`Overdrive`,distortion:`Distortion`,fuzz:`Fuzz`,eq:`EQ`,chorus:`Chorus`,flanger:`Flanger`,phaser:`Phaser`,tremolo:`Tremolo`,vibrato:`Vibrato`,delay:`Delay`,reverb:`Reverb`,volume:`Volume`,nam_amp:`NAM Amp`,ir_cab:`IR Cab`,octaver:`Octaver`,pitch_shifter:`Pitch Shifter`,harmonizer:`Harmonizer`,whammy:`Whammy`,detune:`Detune`,ring_modulator:`Ring Mod`,auto_wah:`Auto Wah`,envelope_filter:`Env Filter`,rotary_speaker:`Rotary`,uni_vibe:`Uni-Vibe`,auto_pan:`Auto Pan`,stereo_widener:`Stereo Widener`,bitcrusher:`Bitcrusher`,wavefolder:`Wavefolder`,rectifier:`Rectifier`,expander:`Expander`,de_esser:`De-esser`,transient_shaper:`Transient Shaper`,parametric_eq:`Parametric EQ`,high_pass_filter:`High Pass`,low_pass_filter:`Low Pass`,band_pass_filter:`Band Pass`,notch_filter:`Notch Filter`,formant_filter:`Formant Filter`,ping_pong_delay:`Ping Pong Delay`,multi_tap_delay:`Multi Tap`,reverse_delay:`Reverse Delay`,tape_echo:`Tape Echo`,shimmer_reverb:`Shimmer Reverb`,looper:`Looper`,early_reflections:`Early Reflections`,sidechain_compressor:`Sidechain Comp`};function ae({state:e,connected:t}){let[n,r]=(0,l.useState)(null),[i,a]=(0,l.useState)(!0),[o,s]=(0,l.useState)(!1),[c,u]=(0,l.useState)({}),d=(0,l.useCallback)(async e=>{if(c[e])return c[e];try{let t=await y(`/api/block-params/${e}`);return u(n=>({...n,[e]:t.params||[]})),t.params||[]}catch{return[]}},[c]);(0,l.useEffect)(()=>{if(!t||!e?.current_preset){r([]),a(!1);return}y(`/api/presets`).then(async t=>{let n=t.banks||[],i=e.current_preset,o=n[i.bank]?.presets?.[i.program];o?.chain&&r(await Promise.all(o.chain.map(async(e,t)=>{let n=await d(e.fx_type);return{id:t+1,title:ie[e.fx_type]||e.fx_type.replace(/_/g,` `).replace(/\b\w/g,e=>e.toUpperCase()),type:e.fx_type,bypassed:e.bypass||!e.enabled,params:n}}))),a(!1)}).catch(()=>{r([]),a(!1)})},[t,e,d]);let m=e=>r(t=>t&&t.map(t=>t.id===e?{...t,bypassed:!t.bypassed}:t)),h=async e=>{s(!1);let t=await d(e);r(n=>[...n||[],{id:Date.now(),title:ie[e]||e,type:e,bypassed:!1,params:t}])};return i?(0,f.jsx)(`div`,{style:{flex:1,display:`flex`,alignItems:`center`,justifyContent:`center`},children:(0,f.jsx)(`div`,{className:`loader`})}):(0,f.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100%`},children:[(0,f.jsxs)(`div`,{className:`screen-header`,children:[(0,f.jsxs)(`div`,{className:`screen-title`,children:[`FX Chain `,(n?.length||0)>0?`(${n.length})`:``]}),(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>s(!0),children:`+ Add Block`})]}),(0,f.jsxs)(`div`,{className:`screen-body`,children:[n&&n.length>0&&(0,f.jsx)(`div`,{style:{display:`flex`,alignItems:`center`,gap:4,padding:`4px 2px`,overflowX:`auto`},children:[`IN`,...n.map(e=>e.type.toUpperCase()),`OUT`].map((e,t,n)=>(0,f.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:4,flexShrink:0},children:[(0,f.jsx)(`div`,{style:{padding:`3px 8px`,borderRadius:4,fontSize:10,fontWeight:700,background:e===`IN`||e===`OUT`?p.blueDim:p.amberDim,color:e===`IN`||e===`OUT`?p.blue:p.amber,border:`1px solid ${e===`IN`||e===`OUT`?p.blue:p.amber}60`},children:e}),ts(!0),children:`+ Add your first block`})]}):n.map(e=>(0,f.jsx)(E,{...e,active:!e.bypassed,onToggle:()=>m(e.id)},e.id))]}),o&&(0,f.jsxs)(ne,{onClose:()=>s(!1),children:[(0,f.jsx)(`div`,{className:`screen-header`,children:(0,f.jsx)(`div`,{className:`screen-title`,children:`Add FX Block`})}),(0,f.jsx)(`div`,{className:`screen-body`,style:{gap:4},children:Object.entries(ie).map(([e,t])=>(0,f.jsxs)(`div`,{className:`preset-row`,onClick:()=>h(e),style:{borderRadius:6},children:[(0,f.jsx)(`span`,{className:`badge badge-blue`,style:{width:60,flexShrink:0},children:e.split(`_`)[0]}),(0,f.jsx)(`span`,{style:{flex:1,fontSize:13},children:t}),(0,f.jsx)(`span`,{style:{color:p.textDim},children:`+`})]},e))})]})]})}function oe({state:e,connected:t,refresh:n}){let[r,i]=(0,l.useState)([]),[a,o]=(0,l.useState)(null),[s,c]=(0,l.useState)(!0),[u,d]=(0,l.useState)(!1),[m,h]=(0,l.useState)(``),[g,_]=(0,l.useState)([]),[v,x]=(0,l.useState)(!1),[ee,C]=(0,l.useState)(!1),te=(0,l.useRef)(null),w=(0,l.useCallback)(async()=>{if(!t){c(!1);return}try{let e=await y(`/api/models`);i(e.models||[]),o(e.current)}catch{}c(!1)},[t]);(0,l.useEffect)(()=>{w()},[w]);let T=async e=>{try{await b(`/api/models/load`,{path:e}),await w(),n()}catch{alert(`Failed to load model`)}},E=async()=>{try{await b(`/api/models/unload`),o(null),await w(),n()}catch{alert(`Failed to unload`)}},ne=async e=>{let t=e.target.files?.[0];if(t){C(!0);try{await S(`/api/models/upload`,t),await w()}catch{alert(`Upload failed`)}C(!1)}},re=(0,l.useCallback)(async()=>{if(m.trim()){d(!0);try{_((await y(`/api/models/tonedownload/search?q=${encodeURIComponent(m)}`)).results||[])}catch{alert(`Search failed`)}d(!1)}},[m]),ie=async e=>{try{await b(`/api/models/tonedownload/install`,{download_url:e.download_url,name:e.name||e.filename,id:e.id,size:e.size,pack_name:e.pack_name,display_name:e.name}),await w(),n()}catch{alert(`Install failed`)}};return s?(0,f.jsx)(`div`,{style:{flex:1,display:`flex`,alignItems:`center`,justifyContent:`center`},children:(0,f.jsx)(`div`,{className:`loader`})}):(0,f.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100%`},children:[(0,f.jsxs)(`div`,{className:`screen-header`,children:[(0,f.jsxs)(`div`,{className:`screen-title`,children:[`NAM Models (`,r.length,`)`]}),(0,f.jsxs)(`div`,{style:{display:`flex`,gap:6},children:[(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>x(!v),children:v?`Local`:`Search`}),(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,onClick:()=>te.current?.click(),children:ee?`...`:`+ Upload`}),(0,f.jsx)(`input`,{ref:te,type:`file`,accept:`.nam`,style:{display:`none`},onChange:ne})]})]}),v&&(0,f.jsxs)(`div`,{style:{padding:`8px 12px`,borderBottom:`1px solid ${p.border}`},children:[(0,f.jsxs)(`div`,{className:`search-bar`,children:[(0,f.jsx)(`span`,{style:{color:p.textDim},children:`🔍`}),(0,f.jsx)(`input`,{type:`text`,placeholder:`Search Tone3000…`,value:m,onChange:e=>h(e.target.value),onKeyDown:e=>e.key===`Enter`&&re()}),(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,onClick:re,disabled:u,children:u?`...`:`Go`})]}),g.length>0&&(0,f.jsx)(`div`,{style:{marginTop:8,maxHeight:240,overflow:`auto`},children:g.map(e=>(0,f.jsxs)(`div`,{className:`preset-row`,onClick:()=>ie(e),style:{borderBottom:`1px solid ${p.border}`,borderRadius:0},children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13},children:e.name}),(0,f.jsxs)(`div`,{style:{fontSize:10,color:p.textDim},children:[e.author,` · `,e.size_display,` · `,e.architecture]})]}),(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,children:`Get`})]},e.id))})]}),(0,f.jsxs)(`div`,{className:`screen-body`,children:[a&&(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsx)(`div`,{className:`section-label`,style:{marginBottom:6},children:`Loaded Model`}),(0,f.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,f.jsx)(`div`,{className:`nam-chip`,children:a}),(0,f.jsx)(`button`,{className:`btn btn-danger btn-sm`,onClick:E,children:`Unload`})]})]}),r.length===0?(0,f.jsxs)(`div`,{style:{textAlign:`center`,padding:30,color:p.textDim,fontSize:14},children:[`No NAM models found.`,(0,f.jsx)(`br`,{}),`Upload a .nam file or search Tone3000.`]}):r.map(e=>(0,f.jsxs)(`div`,{className:`preset-row ${e.loaded?`active`:``}`,children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13,fontWeight:500},children:e.name}),(0,f.jsxs)(`div`,{style:{fontSize:10,color:p.textDim},children:[e.architecture,` · `,e.size_mb,` MB`]})]}),e.loaded?(0,f.jsx)(`span`,{className:`badge badge-green`,children:`Loaded`}):(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>T(e.path),children:`Load`})]},e.name))]})]})}function se({state:e,connected:t,refresh:n}){let[r,i]=(0,l.useState)([]),[a,o]=(0,l.useState)(null),[s,c]=(0,l.useState)(!0),[u,d]=(0,l.useState)(!1),[m,h]=(0,l.useState)(``),[g,_]=(0,l.useState)([]),[v,x]=(0,l.useState)(!1),[ee,C]=(0,l.useState)(!1),te=(0,l.useRef)(null),w=(0,l.useCallback)(async()=>{if(!t){c(!1);return}try{let e=await y(`/api/irs`);i(e.irs||[]),o(e.current)}catch{}c(!1)},[t]);(0,l.useEffect)(()=>{w()},[w]);let T=async e=>{try{await b(`/api/irs/load`,{path:e}),await w(),n()}catch{alert(`Failed to load IR`)}},E=async()=>{try{await b(`/api/irs/unload`),o(null),await w(),n()}catch{alert(`Failed to unload`)}},ne=async e=>{let t=e.target.files?.[0];if(t){C(!0);try{await S(`/api/irs/upload`,t),await w()}catch{alert(`Upload failed`)}C(!1)}},re=(0,l.useCallback)(async()=>{if(m.trim()){d(!0);try{_((await y(`/api/irs/tonedownload/search?q=${encodeURIComponent(m)}`)).results||[])}catch{alert(`Search failed`)}d(!1)}},[m]),ie=async e=>{try{await b(`/api/irs/tonedownload/install`,{download_url:e.download_url,name:e.name||e.filename,id:e.id,size:e.size,display_name:e.name}),await w(),n()}catch{alert(`Install failed`)}};return s?(0,f.jsx)(`div`,{style:{flex:1,display:`flex`,alignItems:`center`,justifyContent:`center`},children:(0,f.jsx)(`div`,{className:`loader`})}):(0,f.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100%`},children:[(0,f.jsxs)(`div`,{className:`screen-header`,children:[(0,f.jsxs)(`div`,{className:`screen-title`,children:[`IR Files (`,r.length,`)`]}),(0,f.jsxs)(`div`,{style:{display:`flex`,gap:6},children:[(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>x(!v),children:v?`Local`:`Search`}),(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,onClick:()=>te.current?.click(),children:ee?`...`:`+ Upload`}),(0,f.jsx)(`input`,{ref:te,type:`file`,accept:`.wav`,style:{display:`none`},onChange:ne})]})]}),v&&(0,f.jsxs)(`div`,{style:{padding:`8px 12px`,borderBottom:`1px solid ${p.border}`},children:[(0,f.jsxs)(`div`,{className:`search-bar`,children:[(0,f.jsx)(`span`,{style:{color:p.textDim},children:`🔍`}),(0,f.jsx)(`input`,{type:`text`,placeholder:`Search Tone3000 IRs…`,value:m,onChange:e=>h(e.target.value),onKeyDown:e=>e.key===`Enter`&&re()}),(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,onClick:re,disabled:u,children:u?`...`:`Go`})]}),g.length>0&&(0,f.jsx)(`div`,{style:{marginTop:8,maxHeight:240,overflow:`auto`},children:g.map(e=>(0,f.jsxs)(`div`,{className:`preset-row`,onClick:()=>ie(e),style:{borderBottom:`1px solid ${p.border}`,borderRadius:0},children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13},children:e.name}),(0,f.jsxs)(`div`,{style:{fontSize:10,color:p.textDim},children:[e.author,` · `,e.size_display]})]}),(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,children:`Get`})]},e.id))})]}),(0,f.jsxs)(`div`,{className:`screen-body`,children:[a&&(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsx)(`div`,{className:`section-label`,style:{marginBottom:6},children:`Loaded IR`}),(0,f.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,f.jsx)(`div`,{className:`nam-chip`,style:{color:p.green,borderColor:`${p.green}55`,background:`${p.green}15`},children:a}),(0,f.jsx)(`button`,{className:`btn btn-danger btn-sm`,onClick:E,children:`Unload`})]})]}),r.length===0?(0,f.jsxs)(`div`,{style:{textAlign:`center`,padding:30,color:p.textDim,fontSize:14},children:[`No IR files found.`,(0,f.jsx)(`br`,{}),`Upload a .wav file or search Tone3000.`]}):r.map(e=>(0,f.jsxs)(`div`,{className:`preset-row ${e.loaded?`active`:``}`,children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13,fontWeight:500},children:e.name}),(0,f.jsxs)(`div`,{style:{fontSize:10,color:p.textDim},children:[e.sample_rate,` Hz · `,e.length_ms,` ms · `,e.num_taps,` taps`]})]}),e.loaded?(0,f.jsx)(`span`,{className:`badge badge-green`,children:`Loaded`}):(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>T(e.path),children:`Load`})]},e.name))]})]})}function ce({state:e,connected:t,refresh:n}){let[r,i]=(0,l.useState)([]),[a,o]=(0,l.useState)(0),[s,c]=(0,l.useState)(!0),[u,d]=(0,l.useState)(``),[m,h]=(0,l.useState)(!1),[g,_]=(0,l.useState)(null),v=(0,l.useCallback)(async()=>{if(!t){i([{name:`Default`,number:0,presets:[{name:`Edge of Breakup`,bank:0,program:0},{name:`Modern High Gain`,bank:0,program:1},null,null]}]),c(!1);return}try{i((await y(`/api/presets`)).banks||[])}catch{}c(!1)},[t]);(0,l.useEffect)(()=>{v()},[v]);let S=async(e,t)=>{try{await b(`/api/presets/${e}/${t}/activate`),await v(),n()}catch{alert(`Failed to load preset`)}},C=async()=>{if(!u.trim())return;let t=e?.current_preset?.bank??0,n=e?.current_preset?.program??0;try{await x(`/api/presets/${t}/${n}`,{name:u}),h(!1),await v()}catch{alert(`Failed to save`)}},te=async()=>{let e=a,t=r[e]?.presets||[],i=t.findIndex(e=>e==null);i===-1&&(i=t.length);let o=`New Preset ${i+1}`;try{await x(`/api/presets/${e}/${i}`,{name:o}),await v(),await b(`/api/presets/${e}/${i}/activate`),n()}catch{alert(`Failed to create preset`)}},w=async(e,t)=>{try{await ee(`/api/presets/${e}/${t}`),_(null),await v()}catch{alert(`Failed to delete`)}};if(s)return(0,f.jsx)(`div`,{style:{flex:1,display:`flex`,alignItems:`center`,justifyContent:`center`},children:(0,f.jsx)(`div`,{className:`loader`})});let T=e?.current_preset,E=r[a]?.presets||[];return(0,f.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100%`},children:[(0,f.jsxs)(`div`,{className:`screen-header`,children:[(0,f.jsx)(`div`,{className:`screen-title`,children:`Presets`}),(0,f.jsxs)(`div`,{style:{display:`flex`,gap:6},children:[(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>h(!0),children:`Save`}),(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,onClick:te,children:`+ New`})]})]}),(0,f.jsxs)(`div`,{className:`screen-body`,children:[r.length>1&&(0,f.jsx)(`div`,{className:`toggle`,children:r.map((e,t)=>(0,f.jsx)(`button`,{className:`toggle-btn ${a===t?`active`:``}`,onClick:()=>o(t),children:e.name},e.number))}),T&&(0,f.jsx)(`div`,{className:`card-sm`,style:{display:`flex`,alignItems:`center`,gap:8},children:(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:11,color:p.textDim},children:`Current`}),(0,f.jsxs)(`div`,{style:{fontSize:14,fontWeight:600,color:p.amber},children:[T.name,(0,f.jsxs)(`span`,{className:`mono`,style:{fontSize:10,color:p.textDim,marginLeft:8},children:[`Bank `,T.bank,` · Prog `,T.program]})]})]})}),E.length===0?(0,f.jsx)(`div`,{style:{textAlign:`center`,padding:30,color:p.textDim,fontSize:14},children:`No presets in this bank.`}):E.map((e,t)=>{if(!e)return null;let n=T?.bank===e.bank&&T?.program===e.program,r=g===`${e.bank}-${e.program}`;return(0,f.jsxs)(`div`,{className:`preset-row ${n?`active`:``}`,onClick:()=>!r&&S(e.bank,e.program),children:[(0,f.jsx)(`div`,{className:`mono`,style:{width:28,height:28,borderRadius:6,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:12,fontWeight:700,background:n?`${p.amber}22`:p.surface,color:n?p.amber:p.textDim,border:`1px solid ${n?p.amber+`60`:p.border}`},children:e.program+1}),(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13,fontWeight:600},children:e.name}),(0,f.jsxs)(`div`,{style:{fontSize:10,color:p.textDim},children:[`Bank `,e.bank,` · Program `,e.program]})]}),n&&(0,f.jsx)(`span`,{style:{color:p.amber,fontSize:16},children:`●`}),r?(0,f.jsxs)(`div`,{style:{display:`flex`,gap:4},children:[(0,f.jsx)(`button`,{className:`btn btn-danger btn-sm`,onClick:t=>{t.stopPropagation(),w(e.bank,e.program)},children:`Confirm`}),(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:e=>{e.stopPropagation(),_(null)},children:`Cancel`})]}):(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,style:{color:p.red},onClick:t=>{t.stopPropagation(),_(`${e.bank}-${e.program}`)},children:`Delete`})]},t)})]}),m&&(0,f.jsxs)(ne,{onClose:()=>h(!1),children:[(0,f.jsx)(`div`,{className:`screen-header`,children:(0,f.jsx)(`div`,{className:`screen-title`,children:`Save Preset`})}),(0,f.jsxs)(`div`,{className:`screen-body`,children:[(0,f.jsx)(`div`,{className:`search-bar`,children:(0,f.jsx)(`input`,{type:`text`,placeholder:`Preset name…`,value:u,onChange:e=>d(e.target.value),autoFocus:!0,onKeyDown:e=>e.key===`Enter`&&C()})}),(0,f.jsxs)(`div`,{style:{display:`flex`,gap:8},children:[(0,f.jsx)(`button`,{className:`btn btn-primary`,style:{flex:1},onClick:C,children:`Save`}),(0,f.jsx)(`button`,{className:`btn btn-ghost`,onClick:()=>h(!1),children:`Cancel`})]})]})]})]})}function D({state:e,connected:t,refresh:n}){let[r,i]=(0,l.useState)(`wifi`),[a,o]=(0,l.useState)([]),[s,c]=(0,l.useState)([]),[u,d]=(0,l.useState)([]),[m,h]=(0,l.useState)(!1),[g,_]=(0,l.useState)(!1),[v,x]=(0,l.useState)(``),[ee,S]=(0,l.useState)(``),[C,te]=(0,l.useState)(null),[w,T]=(0,l.useState)(e?.routing_mode||`mono`),[E,ne]=(0,l.useState)(e?.routing_breakpoint||7),[re,ie]=(0,l.useState)(e?.channel_mode||`dual-mono`);(0,l.useEffect)(()=>{e?.routing_mode&&T(e.routing_mode),e?.routing_breakpoint!=null&&ne(e.routing_breakpoint),e?.channel_mode&&ie(e.channel_mode)},[e?.routing_mode,e?.routing_breakpoint,e?.channel_mode]);let ae=async()=>{h(!0);try{o((await y(`/api/wifi/scan`)).networks||[])}catch{}h(!1)},oe=async(e,t)=>{try{await b(`/api/wifi/connect`,{ssid:e,password:t}),te(null),x(``),S(``),setTimeout(n,2e3)}catch{alert(`Failed to connect`)}},se=async()=>{try{await b(`/api/wifi/disconnect`),setTimeout(n,2e3)}catch{}},ce=async()=>{let t=e?.wifi?.hotspot_active;try{t?await b(`/api/wifi/hotspot/disable`):await b(`/api/wifi/hotspot/enable`,{ssid:`Pi-Pedal`,password:`pedal1234`}),setTimeout(n,2e3)}catch{}},D=async e=>{try{await b(`/api/bluetooth/power`,{on:e}),setTimeout(n,1e3)}catch{}},O=async()=>{_(!0);try{c((await b(`/api/bluetooth/scan`)).devices||[])}catch{}_(!1)},le=async()=>{try{d((await y(`/api/bluetooth/paired`)).devices||[])}catch{}},ue=async e=>{try{await b(`/api/bluetooth/pair`,{address:e}),await le()}catch{alert(`Pairing failed`)}},de=async(e,t)=>{try{await b(`/api/routing`,{routing_mode:e,routing_breakpoint:t})}catch{}},fe=async e=>{try{await b(`/api/channel-mode`,{channel_mode:e}),ie(e)}catch{}};(0,l.useEffect)(()=>{t&&(ae(),le())},[t]);let k=e?.wifi,A=e?.bluetooth;return(0,f.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100%`},children:[(0,f.jsx)(`div`,{className:`screen-header`,children:(0,f.jsx)(`div`,{className:`screen-title`,children:`Settings`})}),(0,f.jsx)(`div`,{className:`toggle`,style:{margin:`6px 10px`},children:[{id:`wifi`,label:`WiFi`},{id:`bt`,label:`Bluetooth`},{id:`routing`,label:`Routing`}].map(e=>(0,f.jsx)(`button`,{className:`toggle-btn ${r===e.id?`active`:``}`,onClick:()=>i(e.id),children:e.label},e.id))}),(0,f.jsxs)(`div`,{className:`screen-body`,style:{gap:4},children:[r===`wifi`&&(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsxs)(`div`,{className:`setting-row`,style:{border:`none`,paddingTop:0},children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`setting-label`,children:`WiFi`}),(0,f.jsx)(`div`,{className:`setting-desc`,children:k?.connected?`Connected to ${k.ssid}`:`Disconnected`})]}),(0,f.jsx)(`div`,{className:`switch ${k?.connected?`on`:``}`,onClick:se})]}),k?.connected&&(0,f.jsxs)(`div`,{style:{fontSize:11,color:p.textDim},children:[`IP: `,k.ip,` · Signal: `,k.signal,`%`]}),(0,f.jsxs)(`div`,{className:`setting-row`,children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`setting-label`,children:`Hotspot`}),(0,f.jsx)(`div`,{className:`setting-desc`,children:k?.hotspot_active?`Active: ${k.hotspot_ssid}`:`Off`})]}),(0,f.jsx)(`div`,{className:`switch ${k?.hotspot_active?`on`:``}`,onClick:ce})]})]}),(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsxs)(`div`,{style:{display:`flex`,justifyContent:`space-between`,alignItems:`center`,marginBottom:8},children:[(0,f.jsx)(`div`,{className:`section-label`,style:{marginBottom:0},children:`Networks`}),(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:ae,disabled:m,children:m?`...`:`Scan`})]}),a.length===0?(0,f.jsx)(`div`,{style:{fontSize:12,color:p.textDim,padding:`8px 0`},children:`Scan to see available networks.`}):a.map(e=>(0,f.jsxs)(`div`,{className:`preset-row`,style:{padding:`8px 0`,borderRadius:0,borderBottom:`1px solid ${p.border}`},onClick:()=>wpaState?.ssid!==e.ssid&&te(e.ssid),children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13},children:e.ssid}),(0,f.jsxs)(`div`,{className:`mono`,style:{fontSize:10,color:p.textDim},children:[`Signal: `,e.signal,`% · `,e.encryption||`Open`]})]}),k?.ssid===e.ssid&&(0,f.jsx)(`span`,{className:`badge badge-green`,children:`Connected`})]},e.ssid))]}),C&&(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsxs)(`div`,{className:`section-label`,children:[`Connect to `,C]}),(0,f.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:8},children:[(0,f.jsx)(`input`,{type:`password`,placeholder:`Password (leave blank for open network)`,value:ee,onChange:e=>S(e.target.value),style:{background:p.surface,border:`1px solid ${p.border}`,borderRadius:6,padding:`8px 12px`,color:p.textPrimary,fontSize:13,outline:`none`},onKeyDown:e=>e.key===`Enter`&&oe(C,ee)}),(0,f.jsxs)(`div`,{style:{display:`flex`,gap:6},children:[(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,style:{flex:1},onClick:()=>oe(C,ee),children:`Connect`}),(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>te(null),children:`Cancel`})]})]})]})]}),r===`bt`&&(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsxs)(`div`,{className:`setting-row`,style:{border:`none`,paddingTop:0},children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`setting-label`,children:`Bluetooth`}),(0,f.jsx)(`div`,{className:`setting-desc`,children:A?.powered?`Powered on`:`Off`})]}),(0,f.jsx)(`div`,{className:`switch ${A?.powered?`on`:``}`,onClick:()=>D(!A?.powered)})]}),A?.powered&&(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`div`,{className:`setting-row`,children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`setting-label`,children:`Discoverable`}),(0,f.jsxs)(`div`,{className:`setting-desc`,children:[A?.name,` (`,A?.address,`)`]})]}),(0,f.jsx)(`div`,{className:`switch ${A?.discoverable?`on`:``}`,onClick:async()=>{try{await b(`/api/bluetooth/discoverable`,{on:!A?.discoverable}),setTimeout(n,500)}catch{}}})]}),(0,f.jsxs)(`div`,{className:`setting-row`,children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`setting-label`,children:`Bluetooth MIDI`}),(0,f.jsx)(`div`,{className:`setting-desc`,children:A?.midi_enabled?`Enabled`:`Disabled`})]}),(0,f.jsx)(`div`,{className:`switch ${A?.midi_enabled?`on`:``}`,onClick:async()=>{try{await(A?.midi_enabled?b(`/api/bluetooth/midi-disable`):b(`/api/bluetooth/midi-enable`)),setTimeout(n,500)}catch{}}})]})]})]}),A?.powered&&(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsxs)(`div`,{style:{display:`flex`,justifyContent:`space-between`,alignItems:`center`,marginBottom:8},children:[(0,f.jsxs)(`div`,{className:`section-label`,style:{marginBottom:0},children:[`Paired Devices (`,u.length,`)`]}),(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:O,disabled:g,children:g?`...`:`Scan`})]}),u.length===0?(0,f.jsx)(`div`,{style:{fontSize:12,color:p.textDim,padding:`8px 0`},children:`No paired devices.`}):u.map(e=>(0,f.jsxs)(`div`,{className:`preset-row`,style:{padding:`8px 0`,borderRadius:0,borderBottom:`1px solid ${p.border}`},children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13},children:e.name||e.address}),(0,f.jsxs)(`div`,{className:`mono`,style:{fontSize:10,color:p.textDim},children:[e.address,` · `,e.connected?`Connected`:`Paired`]})]}),!e.connected&&(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:async()=>{try{await b(`/api/bluetooth/connect`,{address:e.address})}catch{alert(`Failed`)}},children:`Connect`})]},e.address))]}),g&&s.length>0&&(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsx)(`div`,{className:`section-label`,style:{marginBottom:6},children:`Discovered`}),s.map(e=>(0,f.jsxs)(`div`,{className:`preset-row`,style:{padding:`8px 0`,borderRadius:0,borderBottom:`1px solid ${p.border}`},children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13},children:e.name||e.address}),(0,f.jsx)(`div`,{className:`mono`,style:{fontSize:10,color:p.textDim},children:e.address})]}),(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>ue(e.address),children:`Pair`})]},e.address))]})]}),r===`routing`&&(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsx)(`div`,{className:`section-label`,children:`4CM Routing`}),(0,f.jsxs)(`div`,{className:`setting-row`,children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`setting-label`,children:`Mode`}),(0,f.jsx)(`div`,{className:`setting-desc`,children:`Mono (normal) or 4CM (4-cable method)`})]}),(0,f.jsxs)(`select`,{value:w,onChange:e=>{T(e.target.value),de(e.target.value,E)},style:{background:p.surface,border:`1px solid ${p.border}`,borderRadius:6,padding:`6px 10px`,color:p.textPrimary,fontSize:13,outline:`none`},children:[(0,f.jsx)(`option`,{value:`mono`,children:`Mono`}),(0,f.jsx)(`option`,{value:`4cm`,children:`4CM`})]})]}),w===`4cm`&&(0,f.jsxs)(`div`,{className:`setting-row`,children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`setting-label`,children:`Breakpoint`}),(0,f.jsx)(`div`,{className:`setting-desc`,children:`Chain position for amp send/return`})]}),(0,f.jsx)(`input`,{type:`number`,min:1,max:15,value:E,onChange:e=>{let t=+e.target.value;ne(t),de(w,t)},style:{background:p.surface,border:`1px solid ${p.border}`,borderRadius:6,padding:`6px 10px`,color:p.textPrimary,fontSize:13,width:60,outline:`none`,textAlign:`center`}})]})]}),(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsx)(`div`,{className:`section-label`,children:`Channel Mode`}),(0,f.jsxs)(`div`,{className:`setting-row`,children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`setting-label`,children:`Routing`}),(0,f.jsx)(`div`,{className:`setting-desc`,children:`Dual-mono: two independent channels · Stereo: linked as L/R pair`})]}),(0,f.jsxs)(`select`,{value:re,onChange:e=>fe(e.target.value),style:{background:p.surface,border:`1px solid ${p.border}`,borderRadius:6,padding:`6px 10px`,color:p.textPrimary,fontSize:13,outline:`none`},children:[(0,f.jsx)(`option`,{value:`dual-mono`,children:`Dual-Mono`}),(0,f.jsx)(`option`,{value:`stereo`,children:`Stereo`})]})]}),(0,f.jsx)(`div`,{style:{fontSize:10,color:p.textDim,marginTop:4,padding:`4px 0`},children:re===`dual-mono`?`Each channel controlled independently from its own phone.`:`Channels A (Left) and B (Right) are controlled together.`})]})]})]})]})}function O({state:e}){let[t,n]=(0,l.useState)(!1),[r,i]=(0,l.useState)([]),[a,o]=(0,l.useState)(!0),s=e?.input_level??0,c=e?.output_level??0,u=(0,l.useCallback)(async()=>{try{i((await y(`/api/captures`)).captures||[])}catch{}o(!1)},[]);return(0,l.useEffect)(()=>{u()},[u]),(0,f.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100%`},children:[(0,f.jsxs)(`div`,{className:`screen-header`,children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`screen-title`,children:`Looper / Recorder`}),(0,f.jsxs)(`div`,{className:`mono`,style:{fontSize:10,color:p.textDim},children:[e?.sample_rate?`${e.sample_rate/1e3}kHz`:`—`,t&&(0,f.jsx)(`span`,{style:{color:p.red,marginLeft:8},children:`● RECORDING`})]})]}),(0,f.jsx)(`div`,{style:{display:`flex`,gap:8,alignItems:`center`},children:t?(0,f.jsx)(`button`,{className:`btn btn-danger btn-sm`,onClick:async()=>{try{await b(`/api/capture/stop`),n(!1),u()}catch{alert(`Failed to stop recording`)}},style:{padding:`8px 16px`},children:`⏹ Stop`}):(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,onClick:async()=>{try{await b(`/api/capture/start`),n(!0)}catch{alert(`Failed to start recording`)}},style:{padding:`8px 16px`,background:p.red,color:`#fff`},children:`● Record`})})]}),(0,f.jsxs)(`div`,{className:`screen-body`,children:[(0,f.jsxs)(`div`,{className:`card`,style:{padding:`16px`,display:`flex`,gap:16,alignItems:`center`},children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{className:`section-label`,children:`Input Level`}),(0,f.jsx)(`div`,{style:{display:`flex`,gap:2,height:20,marginTop:4},children:Array.from({length:12}).map((e,t)=>{let n=Math.round(s/100*12)>t,r=t>=10?p.red:t>=8?`#E8C030`:p.green;return(0,f.jsx)(`div`,{style:{flex:1,borderRadius:2,background:n?r:p.border,transition:`all .05s`}},t)})})]}),(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{className:`section-label`,children:`Output Level`}),(0,f.jsx)(`div`,{style:{display:`flex`,gap:2,height:20,marginTop:4},children:Array.from({length:12}).map((e,t)=>{let n=Math.round(c/100*12)>t,r=t>=10?p.red:t>=8?`#E8C030`:p.green;return(0,f.jsx)(`div`,{style:{flex:1,borderRadius:2,background:n?r:p.border,transition:`all .05s`}},t)})})]})]}),t&&(0,f.jsxs)(`div`,{className:`card-sm`,style:{background:`${p.red}15`,borderColor:p.red,textAlign:`center`,padding:20},children:[(0,f.jsx)(`div`,{style:{fontSize:40,marginBottom:8,animation:`pulse 1s ease-in-out infinite`},children:`🔴`}),(0,f.jsx)(`div`,{style:{fontSize:14,fontWeight:600,color:p.red},children:`Recording...`}),(0,f.jsx)(`div`,{className:`mono`,style:{fontSize:11,color:p.textDim,marginTop:4},children:`Saving to captures folder — tap Stop to finish`})]}),!t&&(0,f.jsx)(f.Fragment,{children:(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsxs)(`div`,{className:`section-label`,style:{marginBottom:6},children:[`Recordings (`,r.length,`)`]}),a?(0,f.jsx)(`div`,{className:`loader`,style:{margin:`10px auto`}}):r.length===0?(0,f.jsx)(`div`,{style:{fontSize:12,color:p.textDim,padding:`10px 0`},children:`No recordings yet. Tap ● Record to capture audio.`}):r.map(e=>(0,f.jsxs)(`div`,{className:`preset-row`,style:{padding:`8px 0`,borderRadius:0,borderBottom:`1px solid ${p.border}`},children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13},children:e.name}),(0,f.jsx)(`div`,{className:`mono`,style:{fontSize:10,color:p.textDim},children:e.size_display})]}),(0,f.jsx)(`a`,{href:`/api/captures/${e.name}`,target:`_blank`,rel:`noopener`,className:`btn btn-ghost btn-sm`,style:{textDecoration:`none`},children:`▶ Play`})]},e.name))]})})]}),(0,f.jsx)(`style`,{children:`@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }`})]})}function le({onClose:e,state:t,connected:n}){let[r,i]=(0,l.useState)({}),[a,o]=(0,l.useState)(!0),[s,c]=(0,l.useState)(null),[u,d]=(0,l.useState)(``),[m,h]=(0,l.useState)(null),[g,_]=(0,l.useState)(!1),v=(0,l.useRef)(null),S=t?.current_snapshot??0,C=(0,l.useCallback)(async()=>{if(!n){o(!1);return}try{i((await y(`/api/snapshots`)).snapshots||{})}catch{}o(!1)},[n]);(0,l.useEffect)(()=>{C()},[C]);let te=e=>{v.current=setTimeout(()=>{v.current=null,E(e)},600)},w=e=>{v.current&&(clearTimeout(v.current),v.current=null,r[String(e)]?re(e):E(e))},T=()=>{v.current&&=(clearTimeout(v.current),null)},E=async e=>{try{await b(`/api/snapshots/${e}/save`),h(e),setTimeout(()=>h(null),800),await C()}catch{}},re=async e=>{try{await b(`/api/snapshots/${e}/recall`),await C()}catch{}},ie=async e=>{if(u.trim())try{await x(`/api/snapshots/${e}`,{name:u.trim()}),c(null),await C()}catch{}},ae=async e=>{try{await ee(`/api/snapshots/${e}`),await C()}catch{}};return(0,f.jsxs)(ne,{onClose:e,children:[(0,f.jsxs)(`div`,{className:`screen-header`,style:{justifyContent:`space-between`},children:[(0,f.jsx)(`div`,{className:`screen-title`,children:`Snapshots`}),(0,f.jsx)(`div`,{style:{display:`flex`,gap:8,alignItems:`center`},children:(0,f.jsxs)(`label`,{style:{display:`flex`,alignItems:`center`,gap:4,fontSize:10,color:p.textDim,cursor:`pointer`},children:[(0,f.jsx)(`div`,{className:`switch ${g?`on`:``}`,style:{width:28,height:16},onClick:()=>_(e=>!e),children:(0,f.jsx)(`div`,{style:{position:`absolute`,top:1,left:g?11:1,width:13,height:13,borderRadius:`50%`,background:`#fff`,transition:`left .15s`}})}),`Discard Edits`]})})]}),(0,f.jsxs)(`div`,{className:`screen-body`,style:{gap:8},children:[(0,f.jsxs)(`div`,{style:{fontSize:11,color:p.textDim,marginBottom:4},children:[`Tap to recall · Hold to save`,g&&(0,f.jsx)(`span`,{style:{color:p.amber,marginLeft:8},children:`Edits will be discarded on recall`})]}),(0,f.jsx)(`div`,{className:`snap-grid`,children:[1,2,3,4,5,6,7,8].map(e=>{let t=r[String(e)],n=S===e,i=!!t,a=s===e,o=m===e;return(0,f.jsxs)(`div`,{className:`snap-slot ${i?`filled`:``} ${n?`active`:``}`,onMouseDown:()=>te(e),onTouchStart:()=>te(e),onMouseUp:()=>w(e),onTouchEnd:()=>w(e),onMouseLeave:T,onTouchCancel:T,style:o?{background:`rgba(58,184,122,.15)`}:{},children:[(0,f.jsx)(`div`,{className:`snap-num`,children:e}),a?(0,f.jsx)(`input`,{className:`snap-name-input`,value:u,onChange:e=>d(e.target.value),onBlur:()=>ie(e),onKeyDown:t=>{t.key===`Enter`&&ie(e),t.key===`Escape`&&c(null)},autoFocus:!0,onClick:e=>e.stopPropagation()}):t?(0,f.jsx)(`div`,{className:`snap-name`,onClick:n=>{n.stopPropagation(),c(e),d(t.name)},children:t.name}):(0,f.jsx)(`div`,{className:`snap-empty`,children:`Empty`}),o&&(0,f.jsx)(`div`,{className:`snap-save-indicator`}),n&&(0,f.jsx)(`div`,{style:{position:`absolute`,bottom:-2,left:`50%`,transform:`translateX(-50%)`,width:20,height:2,borderRadius:1,background:p.amber}})]},e)})}),S>0&&r[String(S)]&&(0,f.jsx)(`div`,{style:{marginTop:8,display:`flex`,gap:8},children:(0,f.jsxs)(`button`,{className:`btn btn-danger btn-sm`,style:{flex:1},onClick:()=>ae(S),children:[`Delete Snapshot `,S]})})]})]})}var ue=[{id:`rig`,label:`Rig`,icon:`🎛`},{id:`fx`,label:`FX Chain`,icon:`⛓`},{id:`models`,label:`Models`,icon:`📁`},{id:`irs`,label:`IRs`,icon:`🎚`},{id:`presets`,label:`Presets`,icon:`⭐`},{id:`settings`,label:`Settings`,icon:`⚙`},{id:`record`,label:`Looper`,icon:`🎙`}];function de(){let[e,t]=(0,l.useState)(`rig`),[n,r]=(0,l.useState)(!1),[i,a]=(0,l.useState)(()=>{try{return localStorage.getItem(`pedal_channel`)||`guitar`}catch{return`guitar`}}),{state:o,connected:s,refresh:c}=C();(0,l.useEffect)(()=>{document.title=`Pi Multi-FX Pedal`},[]),(0,l.useEffect)(()=>{try{localStorage.setItem(`pedal_channel`,i)}catch{}_(i)},[i]);let u=e=>{a(e)};return(0,f.jsxs)(f.Fragment,{children:[(0,f.jsx)(`style`,{children:te}),(0,f.jsxs)(`div`,{style:{width:`100%`,maxWidth:440,margin:`0 auto`,height:`100dvh`,display:`flex`,flexDirection:`column`,background:p.bg,overflow:`hidden`,fontFamily:`'Inter', sans-serif`},children:[(0,f.jsxs)(`div`,{style:{display:`flex`,justifyContent:`space-between`,alignItems:`center`,padding:`4px 10px`,background:p.panel,borderBottom:`1px solid ${p.border}`,flexShrink:0},children:[(0,f.jsxs)(`div`,{style:{display:`flex`,gap:6,alignItems:`center`},children:[(0,f.jsx)(`div`,{style:{width:6,height:6,borderRadius:`50%`,background:s?p.green:p.red,boxShadow:s?`0 0 6px ${p.green}`:`none`}}),o?.channel_mode===`stereo`?(0,f.jsx)(`span`,{className:`badge badge-amber`,style:{fontSize:9,padding:`2px 7px`,letterSpacing:`.08em`},children:`STEREO`}):(0,f.jsxs)(`div`,{className:`toggle`,style:{margin:0,height:22},children:[(0,f.jsx)(`button`,{className:`toggle-btn ${i===`guitar`?`active`:``}`,onClick:()=>u(`guitar`),style:{padding:`2px 8px`,fontSize:10,fontWeight:700,letterSpacing:`.1em`},children:`GUITAR`}),(0,f.jsx)(`button`,{className:`toggle-btn ${i===`bass`?`active`:``}`,onClick:()=>u(`bass`),style:{padding:`2px 8px`,fontSize:10,fontWeight:700,letterSpacing:`.1em`},children:`BASS`})]}),o?.tuner_enabled&&(0,f.jsx)(`span`,{className:`badge badge-amber`,style:{fontSize:8,padding:`1px 5px`},children:`TUNER`})]}),(0,f.jsxs)(`div`,{style:{display:`flex`,gap:8,alignItems:`center`},children:[(0,f.jsxs)(`button`,{className:`snap-header-btn ${n?`active`:``}`,onClick:()=>r(!0),title:`Snapshots`,children:[`📷`,o?.current_snapshot>0&&(0,f.jsx)(`span`,{className:`snap-header-num`,children:o.current_snapshot})]}),o?.cpu_percent!=null&&(0,f.jsxs)(`span`,{className:`mono`,style:{fontSize:9,color:p.textDim},children:[`CPU `,o.cpu_percent,`%`]}),o?.sample_rate&&(0,f.jsxs)(`span`,{className:`mono`,style:{fontSize:9,color:p.textDim},children:[o.sample_rate/1e3,`kHz`]}),o?.bypass&&(0,f.jsx)(`span`,{className:`badge badge-red`,style:{fontSize:8,padding:`1px 5px`},children:`BYP`}),o?.nam_loaded&&(0,f.jsx)(`span`,{className:`badge badge-blue`,style:{fontSize:8,padding:`1px 5px`},children:`NAM`}),o?.ir_loaded&&(0,f.jsx)(`span`,{className:`badge badge-green`,style:{fontSize:8,padding:`1px 5px`},children:`IR`})]})]}),(0,f.jsxs)(`div`,{style:{flex:1,overflow:`hidden`,display:`flex`,flexDirection:`column`},children:[e===`rig`&&(0,f.jsx)(re,{state:o,connected:s,refresh:c}),e===`fx`&&(0,f.jsx)(ae,{state:o,connected:s}),e===`models`&&(0,f.jsx)(oe,{state:o,connected:s,refresh:c}),e===`irs`&&(0,f.jsx)(se,{state:o,connected:s,refresh:c}),e===`presets`&&(0,f.jsx)(ce,{state:o,connected:s,refresh:c}),e===`settings`&&(0,f.jsx)(D,{state:o,connected:s,refresh:c}),e===`record`&&(0,f.jsx)(O,{state:o})]}),(0,f.jsx)(`div`,{className:`tabbar`,children:ue.map(n=>(0,f.jsxs)(`div`,{className:`tab ${e===n.id?`active`:``}`,onClick:()=>t(n.id),children:[(0,f.jsx)(`span`,{style:{fontSize:14,display:`block`},children:n.icon}),n.label]},n.id))}),n&&(0,f.jsx)(le,{onClose:()=>r(!1),state:o,connected:s})]})]})}(0,u.createRoot)(document.getElementById(`root`)).render((0,f.jsx)(l.StrictMode,{children:(0,f.jsx)(de,{})})); \ No newline at end of file diff --git a/src/web/ui-dist/assets/index-DKZHEo9m.js b/src/web/ui-dist/assets/index-DKZHEo9m.js deleted file mode 100644 index efcc2b3..0000000 --- a/src/web/ui-dist/assets/index-DKZHEo9m.js +++ /dev/null @@ -1,184 +0,0 @@ -var e=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports);(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var t=e((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},T=Object.prototype.hasOwnProperty;function E(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function ee(e,t){return E(e.type,t,e.props)}function D(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function te(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var ne=/\/+/g;function re(e,t){return typeof e==`object`&&e&&e.key!=null?te(``+e.key):t.toString(36)}function ie(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function ae(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,ae(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+re(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(ne,`$&/`)+`/`),ae(o,r,i,``,function(e){return e})):o!=null&&(D(o)&&(o=ee(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(ne,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u{n.exports=t()})),r=e((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,S||(S=!0,D());else{var t=n(l);t!==null&&re(x,t.startTime-e)}}var S=!1,C=-1,w=5,T=-1;function E(){return g?!0:!(e.unstable_now()-Tt&&E());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&re(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?D():S=!1}}}var D;if(typeof y==`function`)D=function(){y(ee)};else if(typeof MessageChannel<`u`){var te=new MessageChannel,ne=te.port2;te.port1.onmessage=ee,D=function(){ne.postMessage(null)}}else D=function(){_(ee,0)};function re(t,n){C=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(C),C=-1):h=!0,re(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,D()))),r},e.unstable_shouldYield=E,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),i=e(((e,t)=>{t.exports=r()})),a=e((e=>{var t=n();function r(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=a()})),s=e((e=>{var t=i(),r=n(),a=o();function s(e){var t=`https://react.dev/errors/`+e;if(1ue||(e.current=le[ue],le[ue]=null,ue--)}function A(e,t){ue++,le[ue]=e.current,e.current=t}var pe=de(null),me=de(null),he=de(null),ge=de(null);function _e(e,t){switch(A(he,t),A(me,e),A(pe,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Vd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Vd(t),e=Hd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}fe(pe),A(pe,e)}function ve(){fe(pe),fe(me),fe(he)}function ye(e){e.memoizedState!==null&&A(ge,e);var t=pe.current,n=Hd(t,e.type);t!==n&&(A(me,e),A(pe,n))}function be(e){me.current===e&&(fe(pe),fe(me)),ge.current===e&&(fe(ge),Qf._currentValue=ce)}var xe,Se;function Ce(e){if(xe===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);xe=t&&t[1]||``,Se=-1)`:-1i||c[r]!==l[i]){var u=` -`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{we=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?Ce(n):``}function Ee(e,t){switch(e.tag){case 26:case 27:case 5:return Ce(e.type);case 16:return Ce(`Lazy`);case 13:return e.child!==t&&t!==null?Ce(`Suspense Fallback`):Ce(`Suspense`);case 19:return Ce(`SuspenseList`);case 0:case 15:return Te(e.type,!1);case 11:return Te(e.type.render,!1);case 1:return Te(e.type,!0);case 31:return Ce(`Activity`);default:return``}}function De(e){try{var t=``,n=null;do t+=Ee(e,n),n=e,e=e.return;while(e);return t}catch(e){return` -Error generating stack: `+e.message+` -`+e.stack}}var Oe=Object.prototype.hasOwnProperty,ke=t.unstable_scheduleCallback,Ae=t.unstable_cancelCallback,je=t.unstable_shouldYield,Me=t.unstable_requestPaint,Ne=t.unstable_now,Pe=t.unstable_getCurrentPriorityLevel,Fe=t.unstable_ImmediatePriority,Ie=t.unstable_UserBlockingPriority,Le=t.unstable_NormalPriority,Re=t.unstable_LowPriority,ze=t.unstable_IdlePriority,Be=t.log,Ve=t.unstable_setDisableYieldValue,He=null,Ue=null;function We(e){if(typeof Be==`function`&&Ve(e),Ue&&typeof Ue.setStrictMode==`function`)try{Ue.setStrictMode(He,e)}catch{}}var Ge=Math.clz32?Math.clz32:Je,Ke=Math.log,qe=Math.LN2;function Je(e){return e>>>=0,e===0?32:31-(Ke(e)/qe|0)|0}var Ye=256,Xe=262144,Ze=4194304;function Qe(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function $e(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Qe(n))):i=Qe(o):i=Qe(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Qe(n))):i=Qe(o)):i=Qe(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function et(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function tt(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function nt(){var e=Ze;return Ze<<=1,!(Ze&62914560)&&(Ze=4194304),e}function rt(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function it(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function at(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),vn=!1;if(_n)try{var yn={};Object.defineProperty(yn,"passive",{get:function(){vn=!0}}),window.addEventListener(`test`,yn,yn),window.removeEventListener(`test`,yn,yn)}catch{vn=!1}var bn=null,xn=null,Sn=null;function Cn(){if(Sn)return Sn;var e,t=xn,n=t.length,r,i=`value`in bn?bn.value:bn.textContent,a=i.length;for(e=0;e=tr),ir=` `,ar=!1;function or(e,t){switch(e){case`keyup`:return $n.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function sr(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var cr=!1;function lr(e,t){switch(e){case`compositionend`:return sr(t);case`keypress`:return t.which===32?(ar=!0,ir):null;case`textInput`:return e=t.data,e===ir&&ar?null:e;default:return null}}function ur(e,t){if(cr)return e===`compositionend`||!er&&or(e,t)?(e=Cn(),Sn=xn=bn=null,cr=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=Mr(n)}}function Pr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Pr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Fr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Wt(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=Wt(e.document)}return t}function Ir(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var Lr=_n&&`documentMode`in document&&11>=document.documentMode,Rr=null,zr=null,Br=null,Vr=!1;function Hr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Vr||Rr==null||Rr!==Wt(r)||(r=Rr,`selectionStart`in r&&Ir(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Br&&jr(Br,r)||(Br=r,r=Ed(zr,`onSelect`),0>=o,i-=o,Pi=1<<32-Ge(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),o=a(_,o,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),M&&Ii(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(i,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(i,h),o=a(y,o,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(i,h),M&&Ii(i,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(i,v.value,l),v!==null&&(o=a(v,o,g),d===null?u=v:d.sibling=v,d=v);return M&&Ii(i,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,i,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),o=a(v,o,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(i,e)}),M&&Ii(i,g),u}function b(e,r,a,c){if(typeof a==`object`&&a&&a.type===y&&a.key===null&&(a=a.props.children),typeof a==`object`&&a){switch(a.$$typeof){case _:a:{for(var l=a.key;r!==null;){if(r.key===l){if(l=a.type,l===y){if(r.tag===7){n(e,r.sibling),c=i(r,a.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===D&&Pa(l)===r.type){n(e,r.sibling),c=i(r,a.props),Va(c,a),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}a.type===y?(c=xi(a.props.children,e.mode,c,a.key),c.return=e,e=c):(c=bi(a.type,a.key,a.props,null,e.mode,c),Va(c,a),c.return=e,e=c)}return o(e);case v:a:{for(l=a.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===a.containerInfo&&r.stateNode.implementation===a.implementation){n(e,r.sibling),c=i(r,a.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=wi(a,e.mode,c),c.return=e,e=c}return o(e);case D:return a=Pa(a),b(e,r,a,c)}if(se(a))return h(e,r,a,c);if(ie(a)){if(l=ie(a),typeof l!=`function`)throw Error(s(150));return a=l.call(a),g(e,r,a,c)}if(typeof a.then==`function`)return b(e,r,Ba(a),c);if(a.$$typeof===C)return b(e,r,la(e,a),c);Ha(e,a)}return typeof a==`string`&&a!==``||typeof a==`number`||typeof a==`bigint`?(a=``+a,r!==null&&r.tag===6?(n(e,r.sibling),c=i(r,a),c.return=e,e=c):(n(e,r),c=Si(a,e.mode,c),c.return=e,e=c),o(e)):n(e,r)}return function(e,t,n,r){try{za=0;var i=b(e,t,n,r);return Ra=null,i}catch(t){if(t===Oa||t===Aa)throw t;var a=gi(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var Wa=Ua(!0),Ga=Ua(!1),Ka=!1;function qa(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ja(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Ya(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Xa(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,W&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=pi(e),fi(e,null,n),t}return li(e,r,t,n),pi(e)}function Za(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,st(e,n)}}function Qa(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var $a=!1;function eo(){if($a){var e=ya;if(e!==null)throw e}}function to(e,t,n,r){$a=!1;var i=e.updateQueue;Ka=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(q&f)===f:(r&f)===f){f!==0&&f===va&&($a=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var m=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(m=g.payload,typeof m==`function`){d=m.call(_,d,f);break a}d=m;break a;case 3:m.flags=m.flags&-65537|128;case 0:if(m=g.payload,f=typeof m==`function`?m.call(_,d,f):m,f==null)break a;d=h({},d,f);break a;case 2:Ka=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Kl|=o,e.lanes=o,e.memoizedState=d}}function no(e,t){if(typeof e!=`function`)throw Error(s(191,e));e.call(t)}function ro(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=O.T,s={};O.T=s,Rs(e,!1,t,n);try{var c=i(),l=O.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Ls(e,t,Sa(c,r),pu(e)):Ls(e,t,r,pu(e))}catch(n){Ls(e,t,{then:function(){},status:`rejected`,reason:n},pu())}finally{k.p=a,o!==null&&s.types!==null&&(o.types=s.types),O.T=o}}function Ds(){}function Os(e,t,n,r){if(e.tag!==5)throw Error(s(476));var i=ks(e).queue;Es(e,i,t,ce,n===null?Ds:function(){return As(e),n(r)})}function ks(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:ce,baseState:ce,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:zo,lastRenderedState:ce},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:zo,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function As(e){var t=ks(e);t.next===null&&(t=e.alternate.memoizedState),Ls(e,t.next.queue,{},pu())}function js(){return ca(Qf)}function Ms(){return z().memoizedState}function Ns(){return z().memoizedState}function Ps(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=pu();e=Ya(n);var r=Xa(t,e,n);r!==null&&(hu(r,t,n),Za(r,t,n)),t={cache:ma()},e.payload=t;return}t=t.return}}function Fs(e,t,n){var r=pu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},zs(e)?Bs(t,n):(n=ui(e,t,n,r),n!==null&&(hu(n,e,r),Vs(n,t,r)))}function Is(e,t,n){Ls(e,t,n,pu())}function Ls(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(zs(e))Bs(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,Ar(s,o))return li(e,t,i,0),G===null&&ci(),!1}catch{}if(n=ui(e,t,i,r),n!==null)return hu(n,e,r),Vs(n,t,r),!0}return!1}function Rs(e,t,n,r){if(r={lane:2,revertLane:dd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},zs(e)){if(t)throw Error(s(479))}else t=ui(e,n,r,2),t!==null&&hu(t,e,2)}function zs(e){var t=e.alternate;return e===F||t!==null&&t===F}function Bs(e,t){bo=yo=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Vs(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,st(e,n)}}var Hs={readContext:ca,use:Lo,useCallback:R,useContext:R,useEffect:R,useImperativeHandle:R,useLayoutEffect:R,useInsertionEffect:R,useMemo:R,useReducer:R,useRef:R,useState:R,useDebugValue:R,useDeferredValue:R,useTransition:R,useSyncExternalStore:R,useId:R,useHostTransitionStatus:R,useFormState:R,useActionState:R,useOptimistic:R,useMemoCache:R,useCacheRefresh:R};Hs.useEffectEvent=R;var Us={readContext:ca,use:Lo,useCallback:function(e,t){return Po().memoizedState=[e,t===void 0?null:t],e},useContext:ca,useEffect:ps,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),ds(4194308,4,ys.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ds(4194308,4,e,t)},useInsertionEffect:function(e,t){ds(4,2,e,t)},useMemo:function(e,t){var n=Po();t=t===void 0?null:t;var r=e();if(xo){We(!0);try{e()}finally{We(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=Po();if(n!==void 0){var i=n(t);if(xo){We(!0);try{n(t)}finally{We(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Fs.bind(null,F,e),[r.memoizedState,e]},useRef:function(e){var t=Po();return e={current:e},t.memoizedState=e},useState:function(e){e=Yo(e);var t=e.queue,n=Is.bind(null,F,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:xs,useDeferredValue:function(e,t){return ws(Po(),e,t)},useTransition:function(){var e=Yo(!1);return e=Es.bind(null,F,e.queue,!0,!1),Po().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=F,i=Po();if(M){if(n===void 0)throw Error(s(407));n=n()}else{if(n=t(),G===null)throw Error(s(349));q&127||Wo(r,t,n)}i.memoizedState=n;var a={value:n,getSnapshot:t};return i.queue=a,ps(Ko.bind(null,r,a,e),[e]),r.flags|=2048,ls(9,{destroy:void 0},Go.bind(null,r,a,n,t),null),n},useId:function(){var e=Po(),t=G.identifierPrefix;if(M){var n=Fi,r=Pi;n=(r&~(1<<32-Ge(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=So++,0<\/script>`,a=a.removeChild(a.firstChild);break;case`select`:a=typeof r.is==`string`?o.createElement(`select`,{is:r.is}):o.createElement(`select`),r.multiple?a.multiple=!0:r.size&&(a.size=r.size);break;default:a=typeof r.is==`string`?o.createElement(i,{is:r.is}):o.createElement(i)}}a[mt]=t,a[ht]=r;a:for(o=t.child;o!==null;){if(o.tag===5||o.tag===6)a.appendChild(o.stateNode);else if(o.tag!==4&&o.tag!==27&&o.child!==null){o.child.return=o,o=o.child;continue}if(o===t)break a;for(;o.sibling===null;){if(o.return===null||o.return===t)break a;o=o.return}o.sibling.return=o.return,o=o.sibling}t.stateNode=a;a:switch(Pd(a,i,r),i){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Ic(t)}}return V(t),Lc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Ic(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(s(166));if(e=he.current,Ji(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,i=Vi,i!==null)switch(i.tag){case 27:case 5:r=i.memoizedProps}e[mt]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Md(e.nodeValue,n)),e||Gi(t,!0)}else e=Bd(e).createTextNode(r),e[mt]=t,t.stateNode=e}return V(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Ji(t),n!==null){if(e===null){if(!r)throw Error(s(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(s(557));e[mt]=t}else Yi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;V(t),e=!1}else n=Xi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(go(t),t):(go(t),null);if(t.flags&128)throw Error(s(558))}return V(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(i=Ji(t),r!==null&&r.dehydrated!==null){if(e===null){if(!i)throw Error(s(318));if(i=t.memoizedState,i=i===null?null:i.dehydrated,!i)throw Error(s(317));i[mt]=t}else Yi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;V(t),i=!1}else i=Xi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=i),i=!0;if(!i)return t.flags&256?(go(t),t):(go(t),null)}return go(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,i=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(i=r.alternate.memoizedState.cachePool.pool),a=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(a=r.memoizedState.cachePool.pool),a!==i&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),zc(t,t.updateQueue),V(t),null);case 4:return ve(),e===null&&Sd(t.stateNode.containerInfo),V(t),null;case 10:return na(t.type),V(t),null;case 19:if(fe(P),r=t.memoizedState,r===null)return V(t),null;if(i=(t.flags&128)!=0,a=r.rendering,a===null)if(i)Bc(r,!1);else{if(Y!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(a=_o(e),a!==null){for(t.flags|=128,Bc(r,!1),e=a.updateQueue,t.updateQueue=e,zc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)yi(n,e),n=n.sibling;return A(P,P.current&1|2),M&&Ii(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Ne()>nu&&(t.flags|=128,i=!0,Bc(r,!1),t.lanes=4194304)}else{if(!i)if(e=_o(a),e!==null){if(t.flags|=128,i=!0,e=e.updateQueue,t.updateQueue=e,zc(t,e),Bc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!a.alternate&&!M)return V(t),null}else 2*Ne()-r.renderingStartTime>nu&&n!==536870912&&(t.flags|=128,i=!0,Bc(r,!1),t.lanes=4194304);r.isBackwards?(a.sibling=t.child,t.child=a):(e=r.last,e===null?t.child=a:e.sibling=a,r.last=a)}return r.tail===null?(V(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Ne(),e.sibling=null,n=P.current,A(P,i?n&1|2:n&1),M&&Ii(t,r.treeForkCount),e);case 22:case 23:return go(t),co(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(V(t),t.subtreeFlags&6&&(t.flags|=8192)):V(t),n=t.updateQueue,n!==null&&zc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&fe(wa),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),na(N),V(t),null;case 25:return null;case 30:return null}throw Error(s(156,t.tag))}function Hc(e,t){switch(zi(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return na(N),ve(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return be(t),null;case 31:if(t.memoizedState!==null){if(go(t),t.alternate===null)throw Error(s(340));Yi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(go(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(s(340));Yi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return fe(P),null;case 4:return ve(),null;case 10:return na(t.type),null;case 22:case 23:return go(t),co(),e!==null&&fe(wa),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return na(N),null;case 25:return null;default:return null}}function Uc(e,t){switch(zi(t),t.tag){case 3:na(N),ve();break;case 26:case 27:case 5:be(t);break;case 4:ve();break;case 31:t.memoizedState!==null&&go(t);break;case 13:go(t);break;case 19:fe(P);break;case 10:na(t.type);break;case 22:case 23:go(t),co(),e!==null&&fe(wa);break;case 24:na(N)}}function Wc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Z(t,t.return,e)}}function Gc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Z(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Z(t,t.return,e)}}function Kc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{ro(t,n)}catch(t){Z(e,e.return,t)}}}function qc(e,t,n){n.props=Xs(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Z(e,t,n)}}function Jc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Z(e,t,n)}}function Yc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Z(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Z(e,t,n)}else n.current=null}function Xc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Z(e,e.return,t)}}function Zc(e,t,n){try{var r=e.stateNode;Fd(r,e.type,n,t),r[ht]=t}catch(t){Z(e,e.return,t)}}function Qc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Zd(e.type)||e.tag===4}function $c(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Qc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Zd(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function el(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=cn));else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(el(e,t,n),e=e.sibling;e!==null;)el(e,t,n),e=e.sibling}function tl(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(tl(e,t,n),e=e.sibling;e!==null;)tl(e,t,n),e=e.sibling}function nl(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Pd(t,r,n),t[mt]=e,t[ht]=n}catch(t){Z(e,e.return,t)}}var rl=!1,H=!1,il=!1,al=typeof WeakSet==`function`?WeakSet:Set,ol=null;function sl(e,t){if(e=e.containerInfo,Rd=sp,e=Fr(e),Ir(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var i=r.anchorOffset,a=r.focusNode;r=r.focusOffset;try{n.nodeType,a.nodeType}catch{n=null;break a}var o=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||i!==0&&f.nodeType!==3||(c=o+i),f!==a||r!==0&&f.nodeType!==3||(l=o+r),f.nodeType===3&&(o+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===i&&(c=o),p===a&&++d===r&&(l=o),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(zd={focusedElem:e,selectionRange:n},sp=!1,ol=t;ol!==null;)if(t=ol,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,ol=e;else for(;ol!==null;){switch(t=ol,a=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Pd(a,r,n),a[mt]=e,Dt(a),r=a;break a;case`link`:var o=Vf(`link`,`href`,i).get(r+(n.href||``));if(o){for(var c=0;cg&&(o=g,g=h,h=o);var _=Nr(s,h),v=Nr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,O.T=null,n=lu,lu=null;var a=au,o=su;if(X=0,ou=au=null,su=0,W&6)throw Error(s(331));var c=W;if(W|=4,Il(a.current),Ol(a,a.current,o,n),W=c,id(0,!1),Ue&&typeof Ue.onPostCommitFiberRoot==`function`)try{Ue.onPostCommitFiberRoot(He,a)}catch{}return!0}finally{k.p=i,O.T=r,Vu(e,t)}}function Wu(e,t,n){t=Ei(n,t),t=nc(e.stateNode,t,2),e=Xa(e,t,2),e!==null&&(it(e,2),rd(e))}function Z(e,t,n){if(e.tag===3)Wu(e,e,n);else for(;t!==null;){if(t.tag===3){Wu(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(iu===null||!iu.has(r))){e=Ei(n,e),n=rc(2),r=Xa(t,n,2),r!==null&&(ic(n,r,t,e),it(r,2),rd(r));break}}t=t.return}}function Gu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Bl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Wl=!0,i.add(n),e=Ku.bind(null,e,t,n),t.then(e,e))}function Ku(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,G===e&&(q&n)===n&&(Y===4||Y===3&&(q&62914560)===q&&300>Ne()-eu?!(W&2)&&Su(e,0):Jl|=n,Xl===q&&(Xl=0)),rd(e)}function qu(e,t){t===0&&(t=nt()),e=di(e,t),e!==null&&(it(e,t),rd(e))}function Ju(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),qu(e,n)}function Yu(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,i=e.memoizedState;i!==null&&(n=i.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(s(314))}r!==null&&r.delete(t),qu(e,n)}function Xu(e,t){return ke(e,t)}var Zu=null,Qu=null,$u=!1,ed=!1,td=!1,nd=0;function rd(e){e!==Qu&&e.next===null&&(Qu===null?Zu=Qu=e:Qu=Qu.next=e),ed=!0,$u||($u=!0,ud())}function id(e,t){if(!td&&ed){td=!0;do for(var n=!1,r=Zu;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Ge(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,ld(r,a))}else a=q,a=$e(r,r===G?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||et(r,a)||(n=!0,ld(r,a));r=r.next}while(n);td=!1}}function ad(){od()}function od(){ed=$u=!1;var e=0;nd!==0&&Gd()&&(e=nd);for(var t=Ne(),n=null,r=Zu;r!==null;){var i=r.next,a=sd(r,t);a===0?(r.next=null,n===null?Zu=i:n.next=i,i===null&&(Qu=n)):(n=r,(e!==0||a&3)&&(ed=!0)),r=i}X!==0&&X!==5||id(e,!1),nd!==0&&(nd=0)}function sd(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Id(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function xf(e,t,n){var r=bf;if(r&&typeof t==`string`&&t){var i=Kt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),hf.has(i)||(hf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Pd(t,`link`,e),Dt(t),r.head.appendChild(t)))}}function Sf(e){_f.D(e),xf(`dns-prefetch`,e,null)}function Cf(e,t){_f.C(e,t),xf(`preconnect`,e,t)}function wf(e,t,n){_f.L(e,t,n);var r=bf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Kt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Kt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Kt(n.imageSizes)+`"]`)):i+=`[href="`+Kt(e)+`"]`;var a=i;switch(t){case`style`:a=Af(e);break;case`script`:a=Pf(e)}mf.has(a)||(e=h({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),mf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(jf(a))||t===`script`&&r.querySelector(Ff(a))||(t=r.createElement(`link`),Pd(t,`link`,e),Dt(t),r.head.appendChild(t)))}}function Tf(e,t){_f.m(e,t);var n=bf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Kt(r)+`"][href="`+Kt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Pf(e)}if(!mf.has(a)&&(e=h({rel:`modulepreload`,href:e},t),mf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Ff(a)))return}r=n.createElement(`link`),Pd(r,`link`,e),Dt(r),n.head.appendChild(r)}}}function Ef(e,t,n){_f.S(e,t,n);var r=bf;if(r&&e){var i=Et(r).hoistableStyles,a=Af(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(jf(a)))s.loading=5;else{e=h({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=mf.get(a))&&Rf(e,n);var c=o=r.createElement(`link`);Dt(c),Pd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Lf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Df(e,t){_f.X(e,t);var n=bf;if(n&&e){var r=Et(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),Dt(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Of(e,t){_f.M(e,t);var n=bf;if(n&&e){var r=Et(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0,type:`module`},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),Dt(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function kf(e,t,n,r){var i=(i=he.current)?gf(i):null;if(!i)throw Error(s(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Af(n.href),n=Et(i).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Af(n.href);var a=Et(i).hoistableStyles,o=a.get(e);if(o||(i=i.ownerDocument||i,o={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},a.set(e,o),(a=i.querySelector(jf(e)))&&!a._p&&(o.instance=a,o.state.loading=5),mf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},mf.set(e,n),a||Nf(i,e,n,o.state))),t&&r===null)throw Error(s(528,``));return o}if(t&&r!==null)throw Error(s(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Pf(n),n=Et(i).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(s(444,e))}}function Af(e){return`href="`+Kt(e)+`"`}function jf(e){return`link[rel="stylesheet"][`+e+`]`}function Mf(e){return h({},e,{"data-precedence":e.precedence,precedence:null})}function Nf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Pd(t,`link`,n),Dt(t),e.head.appendChild(t))}function Pf(e){return`[src="`+Kt(e)+`"]`}function Ff(e){return`script[async]`+e}function If(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Kt(n.href)+`"]`);if(r)return t.instance=r,Dt(r),r;var i=h({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),Dt(r),Pd(r,`style`,i),Lf(r,n.precedence,e),t.instance=r;case`stylesheet`:i=Af(n.href);var a=e.querySelector(jf(i));if(a)return t.state.loading|=4,t.instance=a,Dt(a),a;r=Mf(n),(i=mf.get(i))&&Rf(r,i),a=(e.ownerDocument||e).createElement(`link`),Dt(a);var o=a;return o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),t.state.loading|=4,Lf(a,n.precedence,e),t.instance=a;case`script`:return a=Pf(n.src),(i=e.querySelector(Ff(a)))?(t.instance=i,Dt(i),i):(r=n,(i=mf.get(a))&&(r=h({},n),zf(r,i)),e=e.ownerDocument||e,i=e.createElement(`script`),Dt(i),Pd(i,`link`,r),e.head.appendChild(i),t.instance=i);case`void`:return null;default:throw Error(s(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Lf(r,n.precedence,e));return t.instance}function Lf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Uf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Wf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Gf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Af(r.href),a=t.querySelector(jf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Jf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,Dt(a);return}a=t.ownerDocument||t,r=Mf(r),(i=mf.get(i))&&Rf(r,i),a=a.createElement(`link`),Dt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Jf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Kf=0;function qf(e,t){return e.stylesheets&&e.count===0&&Xf(e,e.stylesheets),0Kf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Jf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Xf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Yf=null;function Xf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Yf=new Map,t.forEach(Zf,e),Yf=null,Jf.call(e))}function Zf(e,t){if(!(t.state.loading&4)){var n=Yf.get(e);if(n)var r=n.get(null);else{n=new Map,Yf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=s()})),l=n(),u=c(),d=e((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),f=e(((e,t)=>{t.exports=d()}))(),p={bg:`#0A0A0C`,panel:`#141418`,surface:`#1C1C22`,border:`#2A2A32`,amber:`#E8A030`,amberDim:`#7A5218`,blue:`#3A7BA8`,blueDim:`#1E4060`,green:`#3AB87A`,red:`#C84040`,textPrimary:`#F0EDE6`,textSec:`#8888A0`,textDim:`#444458`},m=window.location.origin,h=m.replace(/^http/,`ws`)+`/ws`;async function g(e){let t=await fetch(`${m}${e}`);if(!t.ok)throw Error(`${e} => ${t.status}`);return t.json()}async function _(e,t){let n=await fetch(`${m}${e}`,{method:`POST`,headers:{"Content-Type":`application/json`},body:t==null?void 0:JSON.stringify(t)});if(!n.ok)throw Error(`${e} => ${n.status}`);return n.json()}async function v(e,t){let n=await fetch(`${m}${e}`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)});if(!n.ok)throw Error(`${e} => ${n.status}`);return n.json()}async function y(e){let t=await fetch(`${m}${e}`,{method:`DELETE`});if(!t.ok)throw Error(`${e} => ${t.status}`);return t.json()}async function b(e,t){let n=new FormData;n.append(`file`,t);let r=await fetch(`${m}${e}`,{method:`POST`,body:n});if(!r.ok)throw Error(`${e} => ${r.status}`);return r.json()}function x(){let[e,t]=(0,l.useState)(null),[n,r]=(0,l.useState)(!1),i=(0,l.useRef)(null),a=(0,l.useRef)(null);return(0,l.useEffect)(()=>{g(`/api/state`).then(e=>{t(e),r(!0)}).catch(()=>{r(!1)});let e;function n(){try{let a=new WebSocket(h);a.onmessage=e=>{try{let n=JSON.parse(e.data);(n.type===`connected`||n.type===`state`)&&(t(n.state),r(!0)),n.type===`preset_changed`&&n.state&&t(n.state),n.type===`bypass_changed`&&t(e=>e&&{...e,bypass:n.bypass}),n.type===`tuner_changed`&&t(e=>e&&{...e,tuner_enabled:n.tuner_enabled}),n.type===`routing_changed`&&t(e=>e&&{...e,routing_mode:n.routing_mode,routing_breakpoint:n.routing_breakpoint}),(n.type===`model_loaded`||n.type===`ir_loaded`)&&g(`/api/state`).then(e=>t(e)),n.type===`snapshot_recalled`&&n.state&&t(n.state),n.type===`snapshot_saved`&&g(`/api/state`).then(e=>t(e))}catch{}},a.onclose=()=>{r(!1),e=setTimeout(n,3e3)},a.onerror=()=>a.close(),i.current=a}catch{e=setTimeout(n,3e3)}}return n(),a.current=setInterval(()=>{g(`/api/state`).then(e=>{t(e),r(!0)}).catch(()=>{})},5e3),()=>{clearInterval(a.current),clearTimeout(e),i.current&&i.current.close()}},[]),{state:e,connected:n,refresh:(0,l.useCallback)(()=>{g(`/api/state`).then(e=>{t(e),r(!0)}).catch(()=>{})},[])}}var S=` - @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@300;400;500;600&display=swap'); - * { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; } - body { background: ${p.bg}; color: ${p.textPrimary}; font-family: 'Inter', sans-serif; user-select: none; } - :root { --amber: ${p.amber}; --blue: ${p.blue}; --green: ${p.green}; } - - .mono { font-family: 'JetBrains Mono', monospace; } - - ::-webkit-scrollbar { width: 4px; height: 4px; } - ::-webkit-scrollbar-track { background: ${p.panel}; } - ::-webkit-scrollbar-thumb { background: ${p.border}; border-radius: 2px; } - - input, textarea, select { font-family: inherit; } - - .knob-wrap { display: flex; flex-direction: column; align-items: center; gap: 6px; cursor: pointer; } - .knob { border-radius: 50%; position: relative; touch-action: none; } - .knob-inner { border-radius: 50%; width: 100%; height: 100%; position: relative; - background: radial-gradient(circle at 35% 30%, #3A3A48, #1A1A22); - box-shadow: 0 2px 8px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.07); } - .knob-dot { position: absolute; width: 3px; border-radius: 2px; background: var(--amber); - left: 50%; transform-origin: bottom center; box-shadow: 0 0 6px var(--amber); } - .knob-ring { position: absolute; inset: -5px; border-radius: 50%; } - .knob-label { font-size: 10px; color: ${p.textSec}; letter-spacing: .06em; text-transform: uppercase; text-align: center; } - .knob-value { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: ${p.amber}; } - - .toggle { display: flex; background: ${p.surface}; border-radius: 6px; border: 1px solid ${p.border}; overflow: hidden; } - .toggle-btn { flex: 1; padding: 7px 12px; font-size: 12px; font-weight: 500; letter-spacing: .04em; - background: none; border: none; color: ${p.textSec}; cursor: pointer; transition: all .15s; } - .toggle-btn.active { background: ${p.amber}; color: #000; } - - .vu-bar { display: flex; gap: 2px; align-items: flex-end; height: 100%; } - .vu-seg { width: 4px; border-radius: 1px; transition: opacity .05s; } - - .fader-track { width: 4px; border-radius: 2px; background: ${p.border}; position: relative; cursor: pointer; touch-action: none; } - .fader-fill { width: 100%; border-radius: 2px; position: absolute; bottom: 0; transition: height .05s; } - .fader-thumb { width: 28px; height: 10px; border-radius: 3px; position: absolute; left: 50%; transform: translateX(-50%); - background: linear-gradient(180deg, #3A3A4A, #252530); - border: 1px solid #4A4A5A; box-shadow: 0 2px 4px rgba(0,0,0,.5); - cursor: grab; touch-action: none; } - .fader-thumb::after { content: ''; position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); - width: 16px; height: 2px; border-radius: 1px; background: rgba(255,255,255,.15); - box-shadow: 0 3px 0 rgba(255,255,255,.15), 0 -3px 0 rgba(255,255,255,.15); } - - .tabbar { display: flex; background: ${p.panel}; border-bottom: 1px solid ${p.border}; flex-shrink: 0; } - .tab { flex: 1; padding: 10px 4px; font-size: 10px; font-weight: 500; letter-spacing: .04em; - text-align: center; text-transform: uppercase; color: ${p.textDim}; - cursor: pointer; border-bottom: 2px solid transparent; transition: all .15s; } - .tab.active { color: ${p.amber}; border-bottom-color: ${p.amber}; } - - .card { background: ${p.panel}; border: 1px solid ${p.border}; border-radius: 10px; padding: 16px; } - .card-sm { background: ${p.surface}; border: 1px solid ${p.border}; border-radius: 8px; padding: 12px; } - - .btn { padding: 10px 18px; border-radius: 7px; font-size: 13px; font-weight: 600; - letter-spacing: .04em; border: none; cursor: pointer; transition: all .12s; } - .btn-primary { background: ${p.amber}; color: #000; } - .btn-primary:active { filter: brightness(.85); transform: scale(.97); } - .btn-ghost { background: ${p.surface}; color: ${p.textPrimary}; border: 1px solid ${p.border}; } - .btn-ghost:active { background: ${p.border}; } - .btn-danger { background: rgba(200,64,64,.2); color: ${p.red}; border: 1px solid rgba(200,64,64,.3); } - .btn-icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; - justify-content: center; background: ${p.surface}; border: 1px solid ${p.border}; cursor: pointer; - font-size: 16px; transition: all .12s; } - .btn-icon:active { transform: scale(.93); } - .btn-icon.active { background: rgba(232,160,48,.15); border-color: ${p.amber}; color: ${p.amber}; } - .btn-sm { padding: 6px 12px; font-size: 12px; } - - .fx-block { background: ${p.panel}; border: 1px solid ${p.border}; border-radius: 10px; - overflow: hidden; transition: border-color .15s; } - .fx-block.active { border-color: ${p.amber}; } - .fx-block.bypassed { opacity: .45; } - .fx-header { display: flex; align-items: center; gap: 10px; padding: 10px 12px; - border-bottom: 1px solid ${p.border}; background: ${p.surface}; cursor: pointer; } - .fx-body { padding: 12px; display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; } - .bypass-led { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } - .led-on { background: ${p.green}; box-shadow: 0 0 8px ${p.green}; } - .led-off { background: ${p.textDim}; } - - .waveform-wrap { position: relative; overflow: hidden; border-radius: 6px; background: #0F0F14; } - .waveform-canvas { display: block; } - .playhead { position: absolute; top: 0; bottom: 0; width: 1.5px; background: ${p.amber}; - box-shadow: 0 0 8px ${p.amber}; pointer-events: none; transition: left .08s linear; } - - .nam-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; - background: rgba(58,123,168,.15); border: 1px solid rgba(58,123,168,.35); border-radius: 5px; - font-family: 'JetBrains Mono', monospace; font-size: 11px; color: ${p.blue}; } - - .section-label { font-size: 10px; letter-spacing: .1em; text-transform: uppercase; - color: ${p.textDim}; font-weight: 600; margin-bottom: 10px; } - - .preset-row { display: flex; align-items: center; gap: 12px; padding: 10px 12px; - border-radius: 8px; cursor: pointer; transition: background .12s; border: 1px solid transparent; } - .preset-row:active, .preset-row.active { background: rgba(232,160,48,.08); border-color: rgba(232,160,48,.25); } - - .transport-btn { width: 44px; height: 44px; border-radius: 50%; display: flex; - align-items: center; justify-content: center; font-size: 16px; cursor: pointer; - border: 2px solid ${p.border}; background: ${p.surface}; transition: all .12s; } - .transport-btn:active { transform: scale(.92); } - .transport-btn.rec { border-color: ${p.red}; background: rgba(200,64,64,.15); } - .transport-btn.play { border-color: ${p.green}; background: rgba(58,184,122,.15); } - - .badge { display: inline-flex; align-items: center; justify-content: center; - padding: 2px 7px; border-radius: 4px; font-size: 10px; font-weight: 700; - letter-spacing: .06em; text-transform: uppercase; } - .badge-amber { background: rgba(232,160,48,.2); color: ${p.amber}; } - .badge-green { background: rgba(58,184,122,.2); color: ${p.green}; } - .badge-blue { background: rgba(58,123,168,.2); color: ${p.blue}; } - .badge-red { background: rgba(200,64,64,.2); color: ${p.red}; } - - .screen-header { padding: 12px 14px 8px; display: flex; align-items: center; - justify-content: space-between; border-bottom: 1px solid ${p.border}; flex-shrink: 0; } - .screen-title { font-size: 13px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: ${p.textSec}; } - - .screen-body { flex: 1; overflow-y: auto; padding: 12px; display: flex; - flex-direction: column; gap: 12px; } - - /* Settings lists */ - .setting-row { display: flex; align-items: center; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid ${p.border}; } - .setting-label { font-size: 13px; color: ${p.textPrimary}; } - .setting-desc { font-size: 11px; color: ${p.textDim}; margin-top: 2px; } - .switch { width: 40px; height: 22px; border-radius: 11px; background: ${p.border}; cursor: pointer; position: relative; transition: background .2s; flex-shrink: 0; } - .switch.on { background: ${p.green}; } - .switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 50%; background: white; transition: transform .15s; } - .switch.on::after { transform: translateX(18px); } - - .search-bar { display: flex; background: ${p.surface}; border-radius: 7px; border: 1px solid ${p.border}; - padding: 8px 12px; gap: 8px; align-items: center; } - .search-bar input { background: none; border: none; outline: none; color: ${p.textPrimary}; font-size: 13px; flex: 1; } - .search-bar input::placeholder { color: ${p.textDim}; } - - .file-drop { border: 2px dashed ${p.border}; border-radius: 8px; padding: 20px; text-align: center; - cursor: pointer; transition: all .15s; font-size: 13px; color: ${p.textDim}; } - .file-drop:hover, .file-drop.dragover { border-color: ${p.amber}; color: ${p.amber}; } - - .loader { width: 20px; height: 20px; border: 2px solid ${p.border}; border-top-color: ${p.amber}; - border-radius: 50%; animation: spin .6s linear infinite; } - @keyframes spin { to { transform: rotate(360deg); } } - - .slide-panel { position: fixed; inset: 0; background: rgba(0,0,0,.7); z-index: 100; - display: flex; align-items: flex-end; justify-content: center; } - .slide-panel-inner { background: ${p.bg}; max-width: 440px; width: 100%; max-height: 85vh; - border-radius: 16px 16px 0 0; overflow: hidden; display: flex; flex-direction: column; } - .slide-panel-handle { width: 36px; height: 4px; border-radius: 2px; background: ${p.border}; - margin: 8px auto; flex-shrink: 0; } - - /* Snapshot slots */ - .snap-grid { display: flex; gap: 6px; overflow-x: auto; padding: 4px 0; } - .snap-slot { min-width: 60px; flex-shrink: 0; border-radius: 8px; border: 1.5px solid ${p.border}; - background: ${p.surface}; cursor: pointer; text-align: center; padding: 8px 4px; - transition: all .12s; touch-action: manipulation; position: relative; } - .snap-slot:active { transform: scale(.95); } - .snap-slot.filled { border-color: ${p.amberDim}; } - .snap-slot.active { border-color: ${p.amber}; background: rgba(232,160,48,.08); } - .snap-slot .snap-num { font-family: 'JetBrains Mono', monospace; font-size: 16px; - font-weight: 700; color: ${p.textDim}; } - .snap-slot.filled .snap-num { color: ${p.amber}; } - .snap-slot.active .snap-num { color: ${p.amber}; text-shadow: 0 0 8px ${p.amber}; } - .snap-slot .snap-name { font-size: 9px; color: ${p.textDim}; margin-top: 3px; - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .snap-slot.filled .snap-name { color: ${p.textSec}; } - .snap-slot.active .snap-name { color: ${p.amber}; } - .snap-slot .snap-empty { font-size: 9px; color: ${p.textDim}; margin-top: 3px; font-style: italic; } - .snap-slot .snap-save-indicator { position: absolute; top: -3px; right: -3px; - width: 10px; height: 10px; border-radius: 50%; background: ${p.green}; - box-shadow: 0 0 6px ${p.green}; animation: snap-pop .3s ease-out; } - @keyframes snap-pop { 0% { transform: scale(2); opacity: 0; } 100% { transform: scale(1); opacity: 1; } } - .snap-name-input { background: ${p.surface}; border: 1px solid ${p.amber}; border-radius: 4px; - padding: 2px 6px; color: ${p.amber}; font-size: 10px; text-align: center; outline: none; - font-family: 'Inter', sans-serif; width: 100%; } - .snap-header-btn { cursor: pointer; border: none; background: none; color: ${p.textSec}; - font-size: 16px; padding: 4px 6px; border-radius: 6px; transition: all .12s; - display: flex; align-items: center; gap: 4px; } - .snap-header-btn:hover { color: ${p.amber}; background: rgba(232,160,48,.1); } - .snap-header-btn.active { color: ${p.amber}; background: rgba(232,160,48,.15); } - .snap-header-num { font-family: 'JetBrains Mono', monospace; font-size: 11px; - font-weight: 700; color: ${p.amber}; min-width: 16px; text-align: center; } -`;function C({label:e,value:t=50,onChange:n,size:r=52,color:i=p.amber,min:a=0,max:o=100}){let s=(0,l.useRef)(null),c=(0,l.useRef)(null),u=(t-a)/(o-a),d=-140+u*280,m=e=>{e.preventDefault(),c.current={y:e.touches?e.touches[0].clientY:e.clientY,val:t};let r=e=>{let t=e.touches?e.touches[0].clientY:e.clientY,r=(c.current.y-t)/120,i=Math.max(a,Math.min(o,c.current.val+r*(o-a)));n?.(Math.round(i))},i=()=>{window.removeEventListener(`mousemove`,r),window.removeEventListener(`mouseup`,i),window.removeEventListener(`touchmove`,r),window.removeEventListener(`touchend`,i)};window.addEventListener(`mousemove`,r),window.addEventListener(`mouseup`,i),window.addEventListener(`touchmove`,r,{passive:!1}),window.addEventListener(`touchend`,i)},h=r+12,g=h/2,_=h/2,v=r/2+3,y=(d-90)*(Math.PI/180),b=g+v*Math.cos(y),x=_+v*Math.sin(y);return(0,f.jsxs)(`div`,{className:`knob-wrap`,style:{width:r+12},children:[(0,f.jsxs)(`div`,{className:`knob`,style:{width:r,height:r},onMouseDown:m,onTouchStart:m,ref:s,children:[(0,f.jsxs)(`svg`,{style:{position:`absolute`,inset:-6,width:h,height:h,overflow:`visible`},children:[(0,f.jsx)(`circle`,{cx:g,cy:_,r:v,fill:`none`,stroke:p.border,strokeWidth:`2.5`,strokeLinecap:`round`,opacity:`.5`}),u>0&&(0,f.jsx)(`path`,{d:`M ${g} ${_-v} A ${v} ${v} 0 ${+(u>.5)} 1 ${b} ${x}`,fill:`none`,stroke:i,strokeWidth:`2.5`,strokeLinecap:`round`,style:{filter:`drop-shadow(0 0 3px ${i})`}})]}),(0,f.jsx)(`div`,{className:`knob-inner`,style:{"--amber":i},children:(0,f.jsx)(`div`,{className:`knob-dot`,style:{height:r*.38,bottom:r/2-2,transform:`translateX(-50%) rotate(${d}deg)`,transformOrigin:`50% ${r*.38}px`}})})]}),(0,f.jsx)(`div`,{className:`knob-value mono`,style:{color:i},children:t}),(0,f.jsx)(`div`,{className:`knob-label`,children:e})]})}function w({level:e=0,height:t=60,vertical:n=!0}){let r=Math.round(e/100*12),i=e=>e>=10?p.red:e>=8?`#E8C030`:p.green;return n?(0,f.jsx)(`div`,{style:{display:`flex`,flexDirection:`column-reverse`,gap:2,height:t,alignItems:`center`},children:Array.from({length:12}).map((e,n)=>(0,f.jsx)(`div`,{style:{width:6,height:(t-24)/12,borderRadius:1,background:n(0,f.jsx)(`div`,{style:{flex:1,height:`100%`,borderRadius:1,background:te.default??e.default??50));return(0,f.jsxs)(`div`,{className:`fx-block ${n?`active`:``} ${r?`bypassed`:``}`,children:[(0,f.jsxs)(`div`,{className:`fx-header`,onClick:()=>s(e=>!e),children:[(0,f.jsx)(`div`,{className:`bypass-led ${r?`led-off`:`led-on`}`,onClick:e=>{e.stopPropagation(),a?.()}}),(0,f.jsx)(`span`,{style:{flex:1,fontSize:13,fontWeight:600},children:e}),(0,f.jsx)(`span`,{className:`badge badge-blue`,style:{fontSize:10},children:t}),(0,f.jsx)(`span`,{style:{color:p.textDim,fontSize:18,marginLeft:6},children:o?`▾`:`▸`})]}),o&&(0,f.jsx)(`div`,{className:`fx-body`,children:i.map((e,t)=>(0,f.jsx)(C,{label:e.name,value:c[t],size:44,onChange:e=>u(n=>n.map((n,r)=>r===t?e:n)),min:e.min??0,max:e.max??100},e.key||e.name))})]})}function E({children:e,onClose:t}){return(0,f.jsx)(`div`,{className:`slide-panel`,onClick:t,children:(0,f.jsxs)(`div`,{className:`slide-panel-inner`,onClick:e=>e.stopPropagation(),children:[(0,f.jsx)(`div`,{className:`slide-panel-handle`}),e]})})}function ee({state:e,connected:t,refresh:n}){let[r,i]=(0,l.useState)(e?.master_volume==null?80:Math.round(e.master_volume*100)),[a,o]=(0,l.useState)(null),[s,c]=(0,l.useState)(null),u=a??e?.bypass??!1,d=s??e?.tuner_enabled??!1;(0,l.useEffect)(()=>{e?.master_volume!=null&&i(Math.round(e.master_volume*100))},[e?.master_volume]),(0,l.useEffect)(()=>{e?.bypass!==void 0&&o(null),e?.tuner_enabled!==void 0&&c(null)},[e?.bypass,e?.tuner_enabled]);let m=async e=>{i(e);try{await v(`/api/volume`,{volume:e/100})}catch{}},h=async()=>{let e=!u;o(e);try{await _(`/api/bypass`,{bypass:e})}catch{o(null)}},g=async()=>{let e=!d;c(e);try{await _(`/api/tuner`,{enabled:e})}catch{c(null)}},y=e?.input_level??0,b=e?.output_level??0;return(0,f.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100%`},children:[(0,f.jsxs)(`div`,{className:`screen-header`,children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`screen-title`,children:`Active Rig`}),(0,f.jsxs)(`div`,{style:{display:`flex`,gap:6,marginTop:4,flexWrap:`wrap`,alignItems:`center`},children:[e?.current_preset?(0,f.jsx)(`span`,{className:`nam-chip`,style:{color:p.amber,borderColor:`${p.amber}55`,background:`${p.amber}15`},children:e.current_preset.name}):(0,f.jsx)(`span`,{style:{fontSize:11,color:p.textDim},children:`No preset loaded`}),t?(0,f.jsx)(`span`,{className:`badge badge-green`,children:`Connected`}):(0,f.jsx)(`span`,{className:`badge badge-red`,children:`Offline`})]})]}),(0,f.jsxs)(`div`,{style:{display:`flex`,gap:6,alignItems:`center`},children:[(0,f.jsx)(w,{level:y,height:44}),(0,f.jsx)(w,{level:b,height:44})]})]}),(0,f.jsxs)(`div`,{className:`screen-body`,children:[(0,f.jsxs)(`div`,{className:`card-sm`,style:{display:`flex`,gap:10,justifyContent:`space-around`},children:[(0,f.jsxs)(`div`,{style:{textAlign:`center`},children:[(0,f.jsx)(`div`,{className:`section-label`,style:{marginBottom:4},children:`Bypass`}),(0,f.jsx)(`div`,{className:`switch ${u?`on`:``}`,onClick:h,style:{margin:`0 auto`}})]}),(0,f.jsxs)(`div`,{style:{textAlign:`center`},children:[(0,f.jsx)(`div`,{className:`section-label`,style:{marginBottom:4},children:`Tuner`}),(0,f.jsx)(`div`,{className:`switch ${d?`on`:``}`,onClick:g,style:{margin:`0 auto`}})]}),(0,f.jsxs)(`div`,{style:{textAlign:`center`},children:[(0,f.jsx)(`div`,{className:`section-label`,style:{marginBottom:4},children:`Routing`}),(0,f.jsx)(`span`,{className:`mono`,style:{fontSize:12,color:p.amber},children:e?.routing_mode||`mono`})]})]}),d&&(0,f.jsxs)(`div`,{className:`card-sm`,style:{background:`${p.amber}15`,borderColor:p.amber,textAlign:`center`,padding:`20px 16px`},children:[(0,f.jsx)(`div`,{style:{fontSize:32,marginBottom:8},children:`🎵`}),(0,f.jsx)(`div`,{className:`section-label`,style:{color:p.amber,marginBottom:4,fontSize:14},children:`TUNER ACTIVE`}),(0,f.jsx)(`div`,{className:`mono`,style:{fontSize:24,color:p.amber,letterSpacing:4},children:`—————●—————`}),(0,f.jsx)(`div`,{style:{fontSize:11,color:p.textDim,marginTop:8},children:`Tap switch again to exit tuner mode`})]}),(0,f.jsxs)(`div`,{className:`card`,style:{display:`flex`,gap:20,alignItems:`center`},children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{className:`section-label`,children:`Master Volume`}),(0,f.jsx)(`input`,{type:`range`,min:0,max:100,value:r,onChange:e=>m(+e.target.value),style:{width:`100%`,accentColor:p.amber}}),(0,f.jsxs)(`div`,{className:`mono`,style:{fontSize:11,color:p.amber,marginTop:4},children:[r,`%`]})]}),(0,f.jsxs)(`div`,{className:`mono`,style:{fontSize:11,color:p.textSec,textAlign:`right`},children:[e?.nam_loaded?`NAM: Active`:`NAM: —`,(0,f.jsx)(`br`,{}),e?.ir_loaded?`IR: Active`:`IR: —`]})]}),(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsx)(`div`,{className:`section-label`,children:`Signal Chain`}),(0,f.jsx)(`div`,{style:{display:`flex`,alignItems:`center`,gap:4,overflowX:`auto`,padding:`4px 0`},children:[`IN`,e?.bypass?`BYP`:e?.nam_loaded?`NAM`:`AMP`,e?.ir_loaded?`IR`:`CAB`,`OUT`].map((e,t,n)=>(0,f.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:4,flexShrink:0},children:[(0,f.jsx)(`div`,{style:{padding:`3px 8px`,borderRadius:4,fontSize:10,fontWeight:700,background:t===0||t===n.length-1?p.blueDim:p.amberDim,color:t===0||t===n.length-1?p.blue:p.amber,border:`1px solid ${t===0||t===n.length-1?p.blue:p.amber}60`},children:e}),t{let e=document.querySelector(`.tabbar`)?.children;e&&e[3]?.click()},children:`Browse IRs`}),(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,style:{flex:1,fontSize:10},onClick:()=>{let e=document.querySelector(`.tabbar`)?.children;e&&e[2]?.click()},children:`Browse Models`})]})]})]})}var D={noise_gate:`Noise Gate`,compressor:`Compressor`,boost:`Boost`,overdrive:`Overdrive`,distortion:`Distortion`,fuzz:`Fuzz`,eq:`EQ`,chorus:`Chorus`,flanger:`Flanger`,phaser:`Phaser`,tremolo:`Tremolo`,vibrato:`Vibrato`,delay:`Delay`,reverb:`Reverb`,volume:`Volume`,nam_amp:`NAM Amp`,ir_cab:`IR Cab`,octaver:`Octaver`,pitch_shifter:`Pitch Shifter`,harmonizer:`Harmonizer`,whammy:`Whammy`,detune:`Detune`,ring_modulator:`Ring Mod`,auto_wah:`Auto Wah`,envelope_filter:`Env Filter`,rotary_speaker:`Rotary`,uni_vibe:`Uni-Vibe`,auto_pan:`Auto Pan`,stereo_widener:`Stereo Widener`,bitcrusher:`Bitcrusher`,wavefolder:`Wavefolder`,rectifier:`Rectifier`,expander:`Expander`,de_esser:`De-esser`,transient_shaper:`Transient Shaper`,parametric_eq:`Parametric EQ`,high_pass_filter:`High Pass`,low_pass_filter:`Low Pass`,band_pass_filter:`Band Pass`,notch_filter:`Notch Filter`,formant_filter:`Formant Filter`,ping_pong_delay:`Ping Pong Delay`,multi_tap_delay:`Multi Tap`,reverse_delay:`Reverse Delay`,tape_echo:`Tape Echo`,shimmer_reverb:`Shimmer Reverb`,looper:`Looper`,early_reflections:`Early Reflections`,sidechain_compressor:`Sidechain Comp`};function te({state:e,connected:t}){let[n,r]=(0,l.useState)(null),[i,a]=(0,l.useState)(!0),[o,s]=(0,l.useState)(!1),[c,u]=(0,l.useState)({}),d=(0,l.useCallback)(async e=>{if(c[e])return c[e];try{let t=await g(`/api/block-params/${e}`);return u(n=>({...n,[e]:t.params||[]})),t.params||[]}catch{return[]}},[c]);(0,l.useEffect)(()=>{if(!t||!e?.current_preset){r([]),a(!1);return}g(`/api/presets`).then(async t=>{let n=t.banks||[],i=e.current_preset,o=n[i.bank]?.presets?.[i.program];o?.chain&&r(await Promise.all(o.chain.map(async(e,t)=>{let n=await d(e.fx_type);return{id:t+1,title:D[e.fx_type]||e.fx_type.replace(/_/g,` `).replace(/\b\w/g,e=>e.toUpperCase()),type:e.fx_type,bypassed:e.bypass||!e.enabled,params:n}}))),a(!1)}).catch(()=>{r([]),a(!1)})},[t,e,d]);let m=e=>r(t=>t&&t.map(t=>t.id===e?{...t,bypassed:!t.bypassed}:t)),h=async e=>{s(!1);let t=await d(e);r(n=>[...n||[],{id:Date.now(),title:D[e]||e,type:e,bypassed:!1,params:t}])};return i?(0,f.jsx)(`div`,{style:{flex:1,display:`flex`,alignItems:`center`,justifyContent:`center`},children:(0,f.jsx)(`div`,{className:`loader`})}):(0,f.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100%`},children:[(0,f.jsxs)(`div`,{className:`screen-header`,children:[(0,f.jsxs)(`div`,{className:`screen-title`,children:[`FX Chain `,(n?.length||0)>0?`(${n.length})`:``]}),(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>s(!0),children:`+ Add Block`})]}),(0,f.jsxs)(`div`,{className:`screen-body`,children:[n&&n.length>0&&(0,f.jsx)(`div`,{style:{display:`flex`,alignItems:`center`,gap:4,padding:`4px 2px`,overflowX:`auto`},children:[`IN`,...n.map(e=>e.type.toUpperCase()),`OUT`].map((e,t,n)=>(0,f.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:4,flexShrink:0},children:[(0,f.jsx)(`div`,{style:{padding:`3px 8px`,borderRadius:4,fontSize:10,fontWeight:700,background:e===`IN`||e===`OUT`?p.blueDim:p.amberDim,color:e===`IN`||e===`OUT`?p.blue:p.amber,border:`1px solid ${e===`IN`||e===`OUT`?p.blue:p.amber}60`},children:e}),ts(!0),children:`+ Add your first block`})]}):n.map(e=>(0,f.jsx)(T,{...e,active:!e.bypassed,onToggle:()=>m(e.id)},e.id))]}),o&&(0,f.jsxs)(E,{onClose:()=>s(!1),children:[(0,f.jsx)(`div`,{className:`screen-header`,children:(0,f.jsx)(`div`,{className:`screen-title`,children:`Add FX Block`})}),(0,f.jsx)(`div`,{className:`screen-body`,style:{gap:4},children:Object.entries(D).map(([e,t])=>(0,f.jsxs)(`div`,{className:`preset-row`,onClick:()=>h(e),style:{borderRadius:6},children:[(0,f.jsx)(`span`,{className:`badge badge-blue`,style:{width:60,flexShrink:0},children:e.split(`_`)[0]}),(0,f.jsx)(`span`,{style:{flex:1,fontSize:13},children:t}),(0,f.jsx)(`span`,{style:{color:p.textDim},children:`+`})]},e))})]})]})}function ne({state:e,connected:t,refresh:n}){let[r,i]=(0,l.useState)([]),[a,o]=(0,l.useState)(null),[s,c]=(0,l.useState)(!0),[u,d]=(0,l.useState)(!1),[m,h]=(0,l.useState)(``),[v,y]=(0,l.useState)([]),[x,S]=(0,l.useState)(!1),[C,w]=(0,l.useState)(!1),T=(0,l.useRef)(null),E=(0,l.useCallback)(async()=>{if(!t){c(!1);return}try{let e=await g(`/api/models`);i(e.models||[]),o(e.current)}catch{}c(!1)},[t]);(0,l.useEffect)(()=>{E()},[E]);let ee=async e=>{try{await _(`/api/models/load`,{path:e}),await E(),n()}catch{alert(`Failed to load model`)}},D=async()=>{try{await _(`/api/models/unload`),o(null),await E(),n()}catch{alert(`Failed to unload`)}},te=async e=>{let t=e.target.files?.[0];if(t){w(!0);try{await b(`/api/models/upload`,t),await E()}catch{alert(`Upload failed`)}w(!1)}},ne=(0,l.useCallback)(async()=>{if(m.trim()){d(!0);try{y((await g(`/api/models/tonedownload/search?q=${encodeURIComponent(m)}`)).results||[])}catch{alert(`Search failed`)}d(!1)}},[m]),re=async e=>{try{await _(`/api/models/tonedownload/install`,{download_url:e.download_url,name:e.name||e.filename,id:e.id,size:e.size,pack_name:e.pack_name,display_name:e.name}),await E(),n()}catch{alert(`Install failed`)}};return s?(0,f.jsx)(`div`,{style:{flex:1,display:`flex`,alignItems:`center`,justifyContent:`center`},children:(0,f.jsx)(`div`,{className:`loader`})}):(0,f.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100%`},children:[(0,f.jsxs)(`div`,{className:`screen-header`,children:[(0,f.jsxs)(`div`,{className:`screen-title`,children:[`NAM Models (`,r.length,`)`]}),(0,f.jsxs)(`div`,{style:{display:`flex`,gap:6},children:[(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>S(!x),children:x?`Local`:`Search`}),(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,onClick:()=>T.current?.click(),children:C?`...`:`+ Upload`}),(0,f.jsx)(`input`,{ref:T,type:`file`,accept:`.nam`,style:{display:`none`},onChange:te})]})]}),x&&(0,f.jsxs)(`div`,{style:{padding:`8px 12px`,borderBottom:`1px solid ${p.border}`},children:[(0,f.jsxs)(`div`,{className:`search-bar`,children:[(0,f.jsx)(`span`,{style:{color:p.textDim},children:`🔍`}),(0,f.jsx)(`input`,{type:`text`,placeholder:`Search Tone3000…`,value:m,onChange:e=>h(e.target.value),onKeyDown:e=>e.key===`Enter`&&ne()}),(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,onClick:ne,disabled:u,children:u?`...`:`Go`})]}),v.length>0&&(0,f.jsx)(`div`,{style:{marginTop:8,maxHeight:240,overflow:`auto`},children:v.map(e=>(0,f.jsxs)(`div`,{className:`preset-row`,onClick:()=>re(e),style:{borderBottom:`1px solid ${p.border}`,borderRadius:0},children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13},children:e.name}),(0,f.jsxs)(`div`,{style:{fontSize:10,color:p.textDim},children:[e.author,` · `,e.size_display,` · `,e.architecture]})]}),(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,children:`Get`})]},e.id))})]}),(0,f.jsxs)(`div`,{className:`screen-body`,children:[a&&(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsx)(`div`,{className:`section-label`,style:{marginBottom:6},children:`Loaded Model`}),(0,f.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,f.jsx)(`div`,{className:`nam-chip`,children:a}),(0,f.jsx)(`button`,{className:`btn btn-danger btn-sm`,onClick:D,children:`Unload`})]})]}),r.length===0?(0,f.jsxs)(`div`,{style:{textAlign:`center`,padding:30,color:p.textDim,fontSize:14},children:[`No NAM models found.`,(0,f.jsx)(`br`,{}),`Upload a .nam file or search Tone3000.`]}):r.map(e=>(0,f.jsxs)(`div`,{className:`preset-row ${e.loaded?`active`:``}`,children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13,fontWeight:500},children:e.name}),(0,f.jsxs)(`div`,{style:{fontSize:10,color:p.textDim},children:[e.architecture,` · `,e.size_mb,` MB`]})]}),e.loaded?(0,f.jsx)(`span`,{className:`badge badge-green`,children:`Loaded`}):(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>ee(e.path),children:`Load`})]},e.name))]})]})}function re({state:e,connected:t,refresh:n}){let[r,i]=(0,l.useState)([]),[a,o]=(0,l.useState)(null),[s,c]=(0,l.useState)(!0),[u,d]=(0,l.useState)(!1),[m,h]=(0,l.useState)(``),[v,y]=(0,l.useState)([]),[x,S]=(0,l.useState)(!1),[C,w]=(0,l.useState)(!1),T=(0,l.useRef)(null),E=(0,l.useCallback)(async()=>{if(!t){c(!1);return}try{let e=await g(`/api/irs`);i(e.irs||[]),o(e.current)}catch{}c(!1)},[t]);(0,l.useEffect)(()=>{E()},[E]);let ee=async e=>{try{await _(`/api/irs/load`,{path:e}),await E(),n()}catch{alert(`Failed to load IR`)}},D=async()=>{try{await _(`/api/irs/unload`),o(null),await E(),n()}catch{alert(`Failed to unload`)}},te=async e=>{let t=e.target.files?.[0];if(t){w(!0);try{await b(`/api/irs/upload`,t),await E()}catch{alert(`Upload failed`)}w(!1)}},ne=(0,l.useCallback)(async()=>{if(m.trim()){d(!0);try{y((await g(`/api/irs/tonedownload/search?q=${encodeURIComponent(m)}`)).results||[])}catch{alert(`Search failed`)}d(!1)}},[m]),re=async e=>{try{await _(`/api/irs/tonedownload/install`,{download_url:e.download_url,name:e.name||e.filename,id:e.id,size:e.size,display_name:e.name}),await E(),n()}catch{alert(`Install failed`)}};return s?(0,f.jsx)(`div`,{style:{flex:1,display:`flex`,alignItems:`center`,justifyContent:`center`},children:(0,f.jsx)(`div`,{className:`loader`})}):(0,f.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100%`},children:[(0,f.jsxs)(`div`,{className:`screen-header`,children:[(0,f.jsxs)(`div`,{className:`screen-title`,children:[`IR Files (`,r.length,`)`]}),(0,f.jsxs)(`div`,{style:{display:`flex`,gap:6},children:[(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>S(!x),children:x?`Local`:`Search`}),(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,onClick:()=>T.current?.click(),children:C?`...`:`+ Upload`}),(0,f.jsx)(`input`,{ref:T,type:`file`,accept:`.wav`,style:{display:`none`},onChange:te})]})]}),x&&(0,f.jsxs)(`div`,{style:{padding:`8px 12px`,borderBottom:`1px solid ${p.border}`},children:[(0,f.jsxs)(`div`,{className:`search-bar`,children:[(0,f.jsx)(`span`,{style:{color:p.textDim},children:`🔍`}),(0,f.jsx)(`input`,{type:`text`,placeholder:`Search Tone3000 IRs…`,value:m,onChange:e=>h(e.target.value),onKeyDown:e=>e.key===`Enter`&&ne()}),(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,onClick:ne,disabled:u,children:u?`...`:`Go`})]}),v.length>0&&(0,f.jsx)(`div`,{style:{marginTop:8,maxHeight:240,overflow:`auto`},children:v.map(e=>(0,f.jsxs)(`div`,{className:`preset-row`,onClick:()=>re(e),style:{borderBottom:`1px solid ${p.border}`,borderRadius:0},children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13},children:e.name}),(0,f.jsxs)(`div`,{style:{fontSize:10,color:p.textDim},children:[e.author,` · `,e.size_display]})]}),(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,children:`Get`})]},e.id))})]}),(0,f.jsxs)(`div`,{className:`screen-body`,children:[a&&(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsx)(`div`,{className:`section-label`,style:{marginBottom:6},children:`Loaded IR`}),(0,f.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,f.jsx)(`div`,{className:`nam-chip`,style:{color:p.green,borderColor:`${p.green}55`,background:`${p.green}15`},children:a}),(0,f.jsx)(`button`,{className:`btn btn-danger btn-sm`,onClick:D,children:`Unload`})]})]}),r.length===0?(0,f.jsxs)(`div`,{style:{textAlign:`center`,padding:30,color:p.textDim,fontSize:14},children:[`No IR files found.`,(0,f.jsx)(`br`,{}),`Upload a .wav file or search Tone3000.`]}):r.map(e=>(0,f.jsxs)(`div`,{className:`preset-row ${e.loaded?`active`:``}`,children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13,fontWeight:500},children:e.name}),(0,f.jsxs)(`div`,{style:{fontSize:10,color:p.textDim},children:[e.sample_rate,` Hz · `,e.length_ms,` ms · `,e.num_taps,` taps`]})]}),e.loaded?(0,f.jsx)(`span`,{className:`badge badge-green`,children:`Loaded`}):(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>ee(e.path),children:`Load`})]},e.name))]})]})}function ie({state:e,connected:t,refresh:n}){let[r,i]=(0,l.useState)([]),[a,o]=(0,l.useState)(0),[s,c]=(0,l.useState)(!0),[u,d]=(0,l.useState)(``),[m,h]=(0,l.useState)(!1),[b,x]=(0,l.useState)(null),S=(0,l.useCallback)(async()=>{if(!t){i([{name:`Default`,number:0,presets:[{name:`Edge of Breakup`,bank:0,program:0},{name:`Modern High Gain`,bank:0,program:1},null,null]}]),c(!1);return}try{i((await g(`/api/presets`)).banks||[])}catch{}c(!1)},[t]);(0,l.useEffect)(()=>{S()},[S]);let C=async(e,t)=>{try{await _(`/api/presets/${e}/${t}/activate`),await S(),n()}catch{alert(`Failed to load preset`)}},w=async()=>{if(!u.trim())return;let t=e?.current_preset?.bank??0,n=e?.current_preset?.program??0;try{await v(`/api/presets/${t}/${n}`,{name:u}),h(!1),await S()}catch{alert(`Failed to save`)}},T=async()=>{let e=a,t=r[e]?.presets||[],i=t.findIndex(e=>e==null);i===-1&&(i=t.length);let o=`New Preset ${i+1}`;try{await v(`/api/presets/${e}/${i}`,{name:o}),await S(),await _(`/api/presets/${e}/${i}/activate`),n()}catch{alert(`Failed to create preset`)}},ee=async(e,t)=>{try{await y(`/api/presets/${e}/${t}`),x(null),await S()}catch{alert(`Failed to delete`)}};if(s)return(0,f.jsx)(`div`,{style:{flex:1,display:`flex`,alignItems:`center`,justifyContent:`center`},children:(0,f.jsx)(`div`,{className:`loader`})});let D=e?.current_preset,te=r[a]?.presets||[];return(0,f.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100%`},children:[(0,f.jsxs)(`div`,{className:`screen-header`,children:[(0,f.jsx)(`div`,{className:`screen-title`,children:`Presets`}),(0,f.jsxs)(`div`,{style:{display:`flex`,gap:6},children:[(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>h(!0),children:`Save`}),(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,onClick:T,children:`+ New`})]})]}),(0,f.jsxs)(`div`,{className:`screen-body`,children:[r.length>1&&(0,f.jsx)(`div`,{className:`toggle`,children:r.map((e,t)=>(0,f.jsx)(`button`,{className:`toggle-btn ${a===t?`active`:``}`,onClick:()=>o(t),children:e.name},e.number))}),D&&(0,f.jsx)(`div`,{className:`card-sm`,style:{display:`flex`,alignItems:`center`,gap:8},children:(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:11,color:p.textDim},children:`Current`}),(0,f.jsxs)(`div`,{style:{fontSize:14,fontWeight:600,color:p.amber},children:[D.name,(0,f.jsxs)(`span`,{className:`mono`,style:{fontSize:10,color:p.textDim,marginLeft:8},children:[`Bank `,D.bank,` · Prog `,D.program]})]})]})}),te.length===0?(0,f.jsx)(`div`,{style:{textAlign:`center`,padding:30,color:p.textDim,fontSize:14},children:`No presets in this bank.`}):te.map((e,t)=>{if(!e)return null;let n=D?.bank===e.bank&&D?.program===e.program,r=b===`${e.bank}-${e.program}`;return(0,f.jsxs)(`div`,{className:`preset-row ${n?`active`:``}`,onClick:()=>!r&&C(e.bank,e.program),children:[(0,f.jsx)(`div`,{className:`mono`,style:{width:28,height:28,borderRadius:6,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:12,fontWeight:700,background:n?`${p.amber}22`:p.surface,color:n?p.amber:p.textDim,border:`1px solid ${n?p.amber+`60`:p.border}`},children:e.program+1}),(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13,fontWeight:600},children:e.name}),(0,f.jsxs)(`div`,{style:{fontSize:10,color:p.textDim},children:[`Bank `,e.bank,` · Program `,e.program]})]}),n&&(0,f.jsx)(`span`,{style:{color:p.amber,fontSize:16},children:`●`}),r?(0,f.jsxs)(`div`,{style:{display:`flex`,gap:4},children:[(0,f.jsx)(`button`,{className:`btn btn-danger btn-sm`,onClick:t=>{t.stopPropagation(),ee(e.bank,e.program)},children:`Confirm`}),(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:e=>{e.stopPropagation(),x(null)},children:`Cancel`})]}):(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,style:{color:p.red},onClick:t=>{t.stopPropagation(),x(`${e.bank}-${e.program}`)},children:`Delete`})]},t)})]}),m&&(0,f.jsxs)(E,{onClose:()=>h(!1),children:[(0,f.jsx)(`div`,{className:`screen-header`,children:(0,f.jsx)(`div`,{className:`screen-title`,children:`Save Preset`})}),(0,f.jsxs)(`div`,{className:`screen-body`,children:[(0,f.jsx)(`div`,{className:`search-bar`,children:(0,f.jsx)(`input`,{type:`text`,placeholder:`Preset name…`,value:u,onChange:e=>d(e.target.value),autoFocus:!0,onKeyDown:e=>e.key===`Enter`&&w()})}),(0,f.jsxs)(`div`,{style:{display:`flex`,gap:8},children:[(0,f.jsx)(`button`,{className:`btn btn-primary`,style:{flex:1},onClick:w,children:`Save`}),(0,f.jsx)(`button`,{className:`btn btn-ghost`,onClick:()=>h(!1),children:`Cancel`})]})]})]})]})}function ae({state:e,connected:t,refresh:n}){let[r,i]=(0,l.useState)(`wifi`),[a,o]=(0,l.useState)([]),[s,c]=(0,l.useState)([]),[u,d]=(0,l.useState)([]),[m,h]=(0,l.useState)(!1),[v,y]=(0,l.useState)(!1),[b,x]=(0,l.useState)(``),[S,C]=(0,l.useState)(``),[w,T]=(0,l.useState)(null),[E,ee]=(0,l.useState)(e?.routing_mode||`mono`),[D,te]=(0,l.useState)(e?.routing_breakpoint||7);(0,l.useEffect)(()=>{e?.routing_mode&&ee(e.routing_mode),e?.routing_breakpoint!=null&&te(e.routing_breakpoint)},[e?.routing_mode,e?.routing_breakpoint]);let ne=async()=>{h(!0);try{o((await g(`/api/wifi/scan`)).networks||[])}catch{}h(!1)},re=async(e,t)=>{try{await _(`/api/wifi/connect`,{ssid:e,password:t}),T(null),x(``),C(``),setTimeout(n,2e3)}catch{alert(`Failed to connect`)}},ie=async()=>{try{await _(`/api/wifi/disconnect`),setTimeout(n,2e3)}catch{}},ae=async()=>{let t=e?.wifi?.hotspot_active;try{t?await _(`/api/wifi/hotspot/disable`):await _(`/api/wifi/hotspot/enable`,{ssid:`Pi-Pedal`,password:`pedal1234`}),setTimeout(n,2e3)}catch{}},oe=async e=>{try{await _(`/api/bluetooth/power`,{on:e}),setTimeout(n,1e3)}catch{}},se=async()=>{y(!0);try{c((await _(`/api/bluetooth/scan`)).devices||[])}catch{}y(!1)},O=async()=>{try{d((await g(`/api/bluetooth/paired`)).devices||[])}catch{}},k=async e=>{try{await _(`/api/bluetooth/pair`,{address:e}),await O()}catch{alert(`Pairing failed`)}},ce=async(e,t)=>{try{await _(`/api/routing`,{routing_mode:e,routing_breakpoint:t})}catch{}};(0,l.useEffect)(()=>{t&&(ne(),O())},[t]);let le=e?.wifi,ue=e?.bluetooth;return(0,f.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100%`},children:[(0,f.jsx)(`div`,{className:`screen-header`,children:(0,f.jsx)(`div`,{className:`screen-title`,children:`Settings`})}),(0,f.jsx)(`div`,{className:`toggle`,style:{margin:`6px 10px`},children:[{id:`wifi`,label:`WiFi`},{id:`bt`,label:`Bluetooth`},{id:`routing`,label:`Routing`}].map(e=>(0,f.jsx)(`button`,{className:`toggle-btn ${r===e.id?`active`:``}`,onClick:()=>i(e.id),children:e.label},e.id))}),(0,f.jsxs)(`div`,{className:`screen-body`,style:{gap:4},children:[r===`wifi`&&(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsxs)(`div`,{className:`setting-row`,style:{border:`none`,paddingTop:0},children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`setting-label`,children:`WiFi`}),(0,f.jsx)(`div`,{className:`setting-desc`,children:le?.connected?`Connected to ${le.ssid}`:`Disconnected`})]}),(0,f.jsx)(`div`,{className:`switch ${le?.connected?`on`:``}`,onClick:ie})]}),le?.connected&&(0,f.jsxs)(`div`,{style:{fontSize:11,color:p.textDim},children:[`IP: `,le.ip,` · Signal: `,le.signal,`%`]}),(0,f.jsxs)(`div`,{className:`setting-row`,children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`setting-label`,children:`Hotspot`}),(0,f.jsx)(`div`,{className:`setting-desc`,children:le?.hotspot_active?`Active: ${le.hotspot_ssid}`:`Off`})]}),(0,f.jsx)(`div`,{className:`switch ${le?.hotspot_active?`on`:``}`,onClick:ae})]})]}),(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsxs)(`div`,{style:{display:`flex`,justifyContent:`space-between`,alignItems:`center`,marginBottom:8},children:[(0,f.jsx)(`div`,{className:`section-label`,style:{marginBottom:0},children:`Networks`}),(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:ne,disabled:m,children:m?`...`:`Scan`})]}),a.length===0?(0,f.jsx)(`div`,{style:{fontSize:12,color:p.textDim,padding:`8px 0`},children:`Scan to see available networks.`}):a.map(e=>(0,f.jsxs)(`div`,{className:`preset-row`,style:{padding:`8px 0`,borderRadius:0,borderBottom:`1px solid ${p.border}`},onClick:()=>wpaState?.ssid!==e.ssid&&T(e.ssid),children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13},children:e.ssid}),(0,f.jsxs)(`div`,{className:`mono`,style:{fontSize:10,color:p.textDim},children:[`Signal: `,e.signal,`% · `,e.encryption||`Open`]})]}),le?.ssid===e.ssid&&(0,f.jsx)(`span`,{className:`badge badge-green`,children:`Connected`})]},e.ssid))]}),w&&(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsxs)(`div`,{className:`section-label`,children:[`Connect to `,w]}),(0,f.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:8},children:[(0,f.jsx)(`input`,{type:`password`,placeholder:`Password (leave blank for open network)`,value:S,onChange:e=>C(e.target.value),style:{background:p.surface,border:`1px solid ${p.border}`,borderRadius:6,padding:`8px 12px`,color:p.textPrimary,fontSize:13,outline:`none`},onKeyDown:e=>e.key===`Enter`&&re(w,S)}),(0,f.jsxs)(`div`,{style:{display:`flex`,gap:6},children:[(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,style:{flex:1},onClick:()=>re(w,S),children:`Connect`}),(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>T(null),children:`Cancel`})]})]})]})]}),r===`bt`&&(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsxs)(`div`,{className:`setting-row`,style:{border:`none`,paddingTop:0},children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`setting-label`,children:`Bluetooth`}),(0,f.jsx)(`div`,{className:`setting-desc`,children:ue?.powered?`Powered on`:`Off`})]}),(0,f.jsx)(`div`,{className:`switch ${ue?.powered?`on`:``}`,onClick:()=>oe(!ue?.powered)})]}),ue?.powered&&(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`div`,{className:`setting-row`,children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`setting-label`,children:`Discoverable`}),(0,f.jsxs)(`div`,{className:`setting-desc`,children:[ue?.name,` (`,ue?.address,`)`]})]}),(0,f.jsx)(`div`,{className:`switch ${ue?.discoverable?`on`:``}`,onClick:async()=>{try{await _(`/api/bluetooth/discoverable`,{on:!ue?.discoverable}),setTimeout(n,500)}catch{}}})]}),(0,f.jsxs)(`div`,{className:`setting-row`,children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`setting-label`,children:`Bluetooth MIDI`}),(0,f.jsx)(`div`,{className:`setting-desc`,children:ue?.midi_enabled?`Enabled`:`Disabled`})]}),(0,f.jsx)(`div`,{className:`switch ${ue?.midi_enabled?`on`:``}`,onClick:async()=>{try{await(ue?.midi_enabled?_(`/api/bluetooth/midi-disable`):_(`/api/bluetooth/midi-enable`)),setTimeout(n,500)}catch{}}})]})]})]}),ue?.powered&&(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsxs)(`div`,{style:{display:`flex`,justifyContent:`space-between`,alignItems:`center`,marginBottom:8},children:[(0,f.jsxs)(`div`,{className:`section-label`,style:{marginBottom:0},children:[`Paired Devices (`,u.length,`)`]}),(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:se,disabled:v,children:v?`...`:`Scan`})]}),u.length===0?(0,f.jsx)(`div`,{style:{fontSize:12,color:p.textDim,padding:`8px 0`},children:`No paired devices.`}):u.map(e=>(0,f.jsxs)(`div`,{className:`preset-row`,style:{padding:`8px 0`,borderRadius:0,borderBottom:`1px solid ${p.border}`},children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13},children:e.name||e.address}),(0,f.jsxs)(`div`,{className:`mono`,style:{fontSize:10,color:p.textDim},children:[e.address,` · `,e.connected?`Connected`:`Paired`]})]}),!e.connected&&(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:async()=>{try{await _(`/api/bluetooth/connect`,{address:e.address})}catch{alert(`Failed`)}},children:`Connect`})]},e.address))]}),v&&s.length>0&&(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsx)(`div`,{className:`section-label`,style:{marginBottom:6},children:`Discovered`}),s.map(e=>(0,f.jsxs)(`div`,{className:`preset-row`,style:{padding:`8px 0`,borderRadius:0,borderBottom:`1px solid ${p.border}`},children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13},children:e.name||e.address}),(0,f.jsx)(`div`,{className:`mono`,style:{fontSize:10,color:p.textDim},children:e.address})]}),(0,f.jsx)(`button`,{className:`btn btn-ghost btn-sm`,onClick:()=>k(e.address),children:`Pair`})]},e.address))]})]}),r===`routing`&&(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsx)(`div`,{className:`section-label`,children:`4CM Routing`}),(0,f.jsxs)(`div`,{className:`setting-row`,children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`setting-label`,children:`Mode`}),(0,f.jsx)(`div`,{className:`setting-desc`,children:`Mono (normal) or 4CM (4-cable method)`})]}),(0,f.jsxs)(`select`,{value:E,onChange:e=>{ee(e.target.value),ce(e.target.value,D)},style:{background:p.surface,border:`1px solid ${p.border}`,borderRadius:6,padding:`6px 10px`,color:p.textPrimary,fontSize:13,outline:`none`},children:[(0,f.jsx)(`option`,{value:`mono`,children:`Mono`}),(0,f.jsx)(`option`,{value:`4cm`,children:`4CM`})]})]}),E===`4cm`&&(0,f.jsxs)(`div`,{className:`setting-row`,children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`setting-label`,children:`Breakpoint`}),(0,f.jsx)(`div`,{className:`setting-desc`,children:`Chain position for amp send/return`})]}),(0,f.jsx)(`input`,{type:`number`,min:1,max:15,value:D,onChange:e=>{let t=+e.target.value;te(t),ce(E,t)},style:{background:p.surface,border:`1px solid ${p.border}`,borderRadius:6,padding:`6px 10px`,color:p.textPrimary,fontSize:13,width:60,outline:`none`,textAlign:`center`}})]})]})]})]})}function oe({state:e}){let[t,n]=(0,l.useState)(!1),[r,i]=(0,l.useState)([]),[a,o]=(0,l.useState)(!0),s=e?.input_level??0,c=e?.output_level??0,u=(0,l.useCallback)(async()=>{try{i((await g(`/api/captures`)).captures||[])}catch{}o(!1)},[]);return(0,l.useEffect)(()=>{u()},[u]),(0,f.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100%`},children:[(0,f.jsxs)(`div`,{className:`screen-header`,children:[(0,f.jsxs)(`div`,{children:[(0,f.jsx)(`div`,{className:`screen-title`,children:`Looper / Recorder`}),(0,f.jsxs)(`div`,{className:`mono`,style:{fontSize:10,color:p.textDim},children:[e?.sample_rate?`${e.sample_rate/1e3}kHz`:`—`,t&&(0,f.jsx)(`span`,{style:{color:p.red,marginLeft:8},children:`● RECORDING`})]})]}),(0,f.jsx)(`div`,{style:{display:`flex`,gap:8,alignItems:`center`},children:t?(0,f.jsx)(`button`,{className:`btn btn-danger btn-sm`,onClick:async()=>{try{await _(`/api/capture/stop`),n(!1),u()}catch{alert(`Failed to stop recording`)}},style:{padding:`8px 16px`},children:`⏹ Stop`}):(0,f.jsx)(`button`,{className:`btn btn-primary btn-sm`,onClick:async()=>{try{await _(`/api/capture/start`),n(!0)}catch{alert(`Failed to start recording`)}},style:{padding:`8px 16px`,background:p.red,color:`#fff`},children:`● Record`})})]}),(0,f.jsxs)(`div`,{className:`screen-body`,children:[(0,f.jsxs)(`div`,{className:`card`,style:{padding:`16px`,display:`flex`,gap:16,alignItems:`center`},children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{className:`section-label`,children:`Input Level`}),(0,f.jsx)(`div`,{style:{display:`flex`,gap:2,height:20,marginTop:4},children:Array.from({length:12}).map((e,t)=>{let n=Math.round(s/100*12)>t,r=t>=10?p.red:t>=8?`#E8C030`:p.green;return(0,f.jsx)(`div`,{style:{flex:1,borderRadius:2,background:n?r:p.border,transition:`all .05s`}},t)})})]}),(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{className:`section-label`,children:`Output Level`}),(0,f.jsx)(`div`,{style:{display:`flex`,gap:2,height:20,marginTop:4},children:Array.from({length:12}).map((e,t)=>{let n=Math.round(c/100*12)>t,r=t>=10?p.red:t>=8?`#E8C030`:p.green;return(0,f.jsx)(`div`,{style:{flex:1,borderRadius:2,background:n?r:p.border,transition:`all .05s`}},t)})})]})]}),t&&(0,f.jsxs)(`div`,{className:`card-sm`,style:{background:`${p.red}15`,borderColor:p.red,textAlign:`center`,padding:20},children:[(0,f.jsx)(`div`,{style:{fontSize:40,marginBottom:8,animation:`pulse 1s ease-in-out infinite`},children:`🔴`}),(0,f.jsx)(`div`,{style:{fontSize:14,fontWeight:600,color:p.red},children:`Recording...`}),(0,f.jsx)(`div`,{className:`mono`,style:{fontSize:11,color:p.textDim,marginTop:4},children:`Saving to captures folder — tap Stop to finish`})]}),!t&&(0,f.jsx)(f.Fragment,{children:(0,f.jsxs)(`div`,{className:`card-sm`,children:[(0,f.jsxs)(`div`,{className:`section-label`,style:{marginBottom:6},children:[`Recordings (`,r.length,`)`]}),a?(0,f.jsx)(`div`,{className:`loader`,style:{margin:`10px auto`}}):r.length===0?(0,f.jsx)(`div`,{style:{fontSize:12,color:p.textDim,padding:`10px 0`},children:`No recordings yet. Tap ● Record to capture audio.`}):r.map(e=>(0,f.jsxs)(`div`,{className:`preset-row`,style:{padding:`8px 0`,borderRadius:0,borderBottom:`1px solid ${p.border}`},children:[(0,f.jsxs)(`div`,{style:{flex:1},children:[(0,f.jsx)(`div`,{style:{fontSize:13},children:e.name}),(0,f.jsx)(`div`,{className:`mono`,style:{fontSize:10,color:p.textDim},children:e.size_display})]}),(0,f.jsx)(`a`,{href:`/api/captures/${e.name}`,target:`_blank`,rel:`noopener`,className:`btn btn-ghost btn-sm`,style:{textDecoration:`none`},children:`▶ Play`})]},e.name))]})})]}),(0,f.jsx)(`style`,{children:`@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }`})]})}function se({onClose:e,state:t,connected:n}){let[r,i]=(0,l.useState)({}),[a,o]=(0,l.useState)(!0),[s,c]=(0,l.useState)(null),[u,d]=(0,l.useState)(``),[m,h]=(0,l.useState)(null),[b,x]=(0,l.useState)(!1),S=(0,l.useRef)(null),C=t?.current_snapshot??0,w=(0,l.useCallback)(async()=>{if(!n){o(!1);return}try{i((await g(`/api/snapshots`)).snapshots||{})}catch{}o(!1)},[n]);(0,l.useEffect)(()=>{w()},[w]);let T=e=>{S.current=setTimeout(()=>{S.current=null,te(e)},600)},ee=e=>{S.current&&(clearTimeout(S.current),S.current=null,r[String(e)]?ne(e):te(e))},D=()=>{S.current&&=(clearTimeout(S.current),null)},te=async e=>{try{await _(`/api/snapshots/${e}/save`),h(e),setTimeout(()=>h(null),800),await w()}catch{}},ne=async e=>{try{await _(`/api/snapshots/${e}/recall`),await w()}catch{}},re=async e=>{if(u.trim())try{await v(`/api/snapshots/${e}`,{name:u.trim()}),c(null),await w()}catch{}},ie=async e=>{try{await y(`/api/snapshots/${e}`),await w()}catch{}};return(0,f.jsxs)(E,{onClose:e,children:[(0,f.jsxs)(`div`,{className:`screen-header`,style:{justifyContent:`space-between`},children:[(0,f.jsx)(`div`,{className:`screen-title`,children:`Snapshots`}),(0,f.jsx)(`div`,{style:{display:`flex`,gap:8,alignItems:`center`},children:(0,f.jsxs)(`label`,{style:{display:`flex`,alignItems:`center`,gap:4,fontSize:10,color:p.textDim,cursor:`pointer`},children:[(0,f.jsx)(`div`,{className:`switch ${b?`on`:``}`,style:{width:28,height:16},onClick:()=>x(e=>!e),children:(0,f.jsx)(`div`,{style:{position:`absolute`,top:1,left:b?11:1,width:13,height:13,borderRadius:`50%`,background:`#fff`,transition:`left .15s`}})}),`Discard Edits`]})})]}),(0,f.jsxs)(`div`,{className:`screen-body`,style:{gap:8},children:[(0,f.jsxs)(`div`,{style:{fontSize:11,color:p.textDim,marginBottom:4},children:[`Tap to recall · Hold to save`,b&&(0,f.jsx)(`span`,{style:{color:p.amber,marginLeft:8},children:`Edits will be discarded on recall`})]}),(0,f.jsx)(`div`,{className:`snap-grid`,children:[1,2,3,4,5,6,7,8].map(e=>{let t=r[String(e)],n=C===e,i=!!t,a=s===e,o=m===e;return(0,f.jsxs)(`div`,{className:`snap-slot ${i?`filled`:``} ${n?`active`:``}`,onMouseDown:()=>T(e),onTouchStart:()=>T(e),onMouseUp:()=>ee(e),onTouchEnd:()=>ee(e),onMouseLeave:D,onTouchCancel:D,style:o?{background:`rgba(58,184,122,.15)`}:{},children:[(0,f.jsx)(`div`,{className:`snap-num`,children:e}),a?(0,f.jsx)(`input`,{className:`snap-name-input`,value:u,onChange:e=>d(e.target.value),onBlur:()=>re(e),onKeyDown:t=>{t.key===`Enter`&&re(e),t.key===`Escape`&&c(null)},autoFocus:!0,onClick:e=>e.stopPropagation()}):t?(0,f.jsx)(`div`,{className:`snap-name`,onClick:n=>{n.stopPropagation(),c(e),d(t.name)},children:t.name}):(0,f.jsx)(`div`,{className:`snap-empty`,children:`Empty`}),o&&(0,f.jsx)(`div`,{className:`snap-save-indicator`}),n&&(0,f.jsx)(`div`,{style:{position:`absolute`,bottom:-2,left:`50%`,transform:`translateX(-50%)`,width:20,height:2,borderRadius:1,background:p.amber}})]},e)})}),C>0&&r[String(C)]&&(0,f.jsx)(`div`,{style:{marginTop:8,display:`flex`,gap:8},children:(0,f.jsxs)(`button`,{className:`btn btn-danger btn-sm`,style:{flex:1},onClick:()=>ie(C),children:[`Delete Snapshot `,C]})})]})]})}var O=[{id:`rig`,label:`Rig`,icon:`🎛`},{id:`fx`,label:`FX Chain`,icon:`⛓`},{id:`models`,label:`Models`,icon:`📁`},{id:`irs`,label:`IRs`,icon:`🎚`},{id:`presets`,label:`Presets`,icon:`⭐`},{id:`settings`,label:`Settings`,icon:`⚙`},{id:`record`,label:`Looper`,icon:`🎙`}];function k(){let[e,t]=(0,l.useState)(`rig`),[n,r]=(0,l.useState)(!1),{state:i,connected:a,refresh:o}=x();return(0,l.useEffect)(()=>{document.title=`Pi Multi-FX Pedal`},[]),(0,f.jsxs)(f.Fragment,{children:[(0,f.jsx)(`style`,{children:S}),(0,f.jsxs)(`div`,{style:{width:`100%`,maxWidth:440,margin:`0 auto`,height:`100dvh`,display:`flex`,flexDirection:`column`,background:p.bg,overflow:`hidden`,fontFamily:`'Inter', sans-serif`},children:[(0,f.jsxs)(`div`,{style:{display:`flex`,justifyContent:`space-between`,alignItems:`center`,padding:`6px 12px 4px`,background:p.panel,borderBottom:`1px solid ${p.border}`,flexShrink:0},children:[(0,f.jsxs)(`div`,{style:{display:`flex`,gap:6,alignItems:`center`},children:[(0,f.jsx)(`div`,{style:{width:6,height:6,borderRadius:`50%`,background:a?p.green:p.red,boxShadow:a?`0 0 6px ${p.green}`:`none`}}),(0,f.jsx)(`span`,{className:`mono`,style:{fontSize:9,color:p.textSec},children:`PI MULTI-FX`}),i?.tuner_enabled&&(0,f.jsx)(`span`,{className:`badge badge-amber`,style:{fontSize:8,padding:`1px 5px`},children:`TUNER`})]}),(0,f.jsxs)(`div`,{style:{display:`flex`,gap:8,alignItems:`center`},children:[(0,f.jsxs)(`button`,{className:`snap-header-btn ${n?`active`:``}`,onClick:()=>r(!0),title:`Snapshots`,children:[`📷`,i?.current_snapshot>0&&(0,f.jsx)(`span`,{className:`snap-header-num`,children:i.current_snapshot})]}),i?.cpu_percent!=null&&(0,f.jsxs)(`span`,{className:`mono`,style:{fontSize:9,color:p.textDim},children:[`CPU `,i.cpu_percent,`%`]}),i?.sample_rate&&(0,f.jsxs)(`span`,{className:`mono`,style:{fontSize:9,color:p.textDim},children:[i.sample_rate/1e3,`kHz`]}),i?.bypass&&(0,f.jsx)(`span`,{className:`badge badge-red`,style:{fontSize:8,padding:`1px 5px`},children:`BYP`}),i?.nam_loaded&&(0,f.jsx)(`span`,{className:`badge badge-blue`,style:{fontSize:8,padding:`1px 5px`},children:`NAM`}),i?.ir_loaded&&(0,f.jsx)(`span`,{className:`badge badge-green`,style:{fontSize:8,padding:`1px 5px`},children:`IR`})]})]}),(0,f.jsxs)(`div`,{style:{flex:1,overflow:`hidden`,display:`flex`,flexDirection:`column`},children:[e===`rig`&&(0,f.jsx)(ee,{state:i,connected:a,refresh:o}),e===`fx`&&(0,f.jsx)(te,{state:i,connected:a}),e===`models`&&(0,f.jsx)(ne,{state:i,connected:a,refresh:o}),e===`irs`&&(0,f.jsx)(re,{state:i,connected:a,refresh:o}),e===`presets`&&(0,f.jsx)(ie,{state:i,connected:a,refresh:o}),e===`settings`&&(0,f.jsx)(ae,{state:i,connected:a,refresh:o}),e===`record`&&(0,f.jsx)(oe,{state:i})]}),(0,f.jsx)(`div`,{className:`tabbar`,children:O.map(n=>(0,f.jsxs)(`div`,{className:`tab ${e===n.id?`active`:``}`,onClick:()=>t(n.id),children:[(0,f.jsx)(`span`,{style:{fontSize:14,display:`block`},children:n.icon}),n.label]},n.id))}),n&&(0,f.jsx)(se,{onClose:()=>r(!1),state:i,connected:a})]})]})}(0,u.createRoot)(document.getElementById(`root`)).render((0,f.jsx)(l.StrictMode,{children:(0,f.jsx)(k,{})})); \ No newline at end of file diff --git a/src/web/ui-dist/index.html b/src/web/ui-dist/index.html index 9621fa7..2c11002 100644 --- a/src/web/ui-dist/index.html +++ b/src/web/ui-dist/index.html @@ -5,7 +5,7 @@ Pi Multi-FX Pedal - + diff --git a/tests/test_preset_manager.py b/tests/test_preset_manager.py index dff4f43..11fe771 100644 --- a/tests/test_preset_manager.py +++ b/tests/test_preset_manager.py @@ -27,7 +27,7 @@ from src.presets.manager import ( _preset_from_dict, _preset_to_dict, ) -from src.presets.types import FXBlock, FXType, MIDIMapping, Preset +from src.presets.types import Channel, FXBlock, FXType, MIDIMapping, Preset # ── Fixtures ──────────────────────────────────────────────────────────────── @@ -152,7 +152,7 @@ class TestSaveLoad: def test_save_creates_json_file(self, manager: PresetManager, sample_preset: Preset, preset_dir: Path): manager.save(sample_preset) - path = preset_dir / "bank_0" / "preset_0.json" + path = preset_dir / "guitar" / "bank_0" / "preset_0.json" assert path.exists() content = path.read_text(encoding="utf-8") assert "Crunch Rhythm" in content @@ -513,8 +513,152 @@ class TestEdgeCases: assert manager.current_program == 1 def test_corrupted_json_raises(self, manager: PresetManager, preset_dir: Path): - path = preset_dir / "bank_0" / "preset_0.json" + path = preset_dir / "guitar" / "bank_0" / "preset_0.json" path.parent.mkdir(parents=True) path.write_text("not valid json", encoding="utf-8") with pytest.raises(json.JSONDecodeError): - manager.load(0, 0) \ No newline at end of file + manager.load(0, 0) + + +# ── Channel independence ──────────────────────────────────────────────── + + +class TestChannelIndependence: + """Guitar and bass channels each have their own preset bank namespace.""" + + def test_channel_serialization_round_trip(self): + """Channel field survives to_dict / from_dict.""" + p = Preset(name="GTR Lead", channel=Channel.GUITAR, bank=0, program=0) + d = _preset_to_dict(p) + assert d["channel"] == "guitar" + restored = _preset_from_dict(d) + assert restored.channel == Channel.GUITAR + + p2 = Preset(name="Bass Slap", channel=Channel.BASS, bank=0, program=0) + d2 = _preset_to_dict(p2) + assert d2["channel"] == "bass" + restored2 = _preset_from_dict(d2) + assert restored2.channel == Channel.BASS + + def test_channel_defaults_to_guitar(self): + """Presets without explicit channel default to guitar.""" + p = Preset(name="Default", bank=0, program=0) + assert p.channel == Channel.GUITAR + + def test_guitar_and_bass_independent_banks(self, manager: PresetManager): + """Guitar and bass presets in the same (bank, program) don't collide.""" + gtr = Preset(name="GTR Crunch", channel=Channel.GUITAR, bank=0, program=0) + bass = Preset(name="Bass Slap", channel=Channel.BASS, bank=0, program=0) + + # Save guitar via current channel (defaults to guitar) + manager.save(gtr) + # Save bass explicitly + manager.save(bass, channel=Channel.BASS) + + # Load guitar + loaded_gtr = manager.load(0, 0, Channel.GUITAR) + assert loaded_gtr.name == "GTR Crunch" + assert loaded_gtr.channel == Channel.GUITAR + + # Load bass + loaded_bass = manager.load(0, 0, Channel.BASS) + assert loaded_bass.name == "Bass Slap" + assert loaded_bass.channel == Channel.BASS + + def test_channel_switch_restores_preset(self, manager: PresetManager): + """Switching channels restores the last active state for that channel.""" + manager.save(Preset(name="GTR Lead", channel=Channel.GUITAR, bank=0, program=0)) + manager.save(Preset(name="GTR Clean", channel=Channel.GUITAR, bank=0, program=1)) + manager.save(Preset(name="Bass Slap", channel=Channel.BASS, bank=1, program=2)) + + # Select guitar preset (bank=0, program=1) + manager.select(0, 1, Channel.GUITAR) + + # Select bass preset so its channel state is saved + manager.select(1, 2, Channel.BASS) + + # Switch to guitar — should go to (0, 1) + manager.set_channel(Channel.GUITAR) + assert manager.current_channel == Channel.GUITAR + assert manager.current_bank == 0 + assert manager.current_program == 1 + + # Switch back to bass — should restore (1, 2) + manager.set_channel(Channel.BASS) + assert manager.current_channel == Channel.BASS + assert manager.current_bank == 1 + assert manager.current_program == 2 + + def test_set_same_channel_noop(self, manager: PresetManager): + """set_channel to the same channel preserves current position.""" + manager.save(Preset(name="Only", channel=Channel.GUITAR, bank=0, program=2)) + manager.select(0, 2, Channel.GUITAR) + result = manager.set_channel(Channel.GUITAR) + assert result.name == "Only" + assert manager.current_bank == 0 + assert manager.current_program == 2 + + def test_current_channel_property(self, manager: PresetManager): + """current_channel returns the active channel.""" + assert manager.current_channel == Channel.GUITAR + manager.set_channel(Channel.BASS) + assert manager.current_channel == Channel.BASS + manager.set_channel(Channel.GUITAR) + assert manager.current_channel == Channel.GUITAR + + def test_list_banks_channel_scoped(self, manager: PresetManager): + """list_banks only returns banks for the specified channel.""" + manager.save(Preset(name="GTR_A", channel=Channel.GUITAR, bank=0, program=0)) + manager.save(Preset(name="BASS_A", channel=Channel.BASS, bank=5, program=0)) + + gtr_banks = manager.list_banks(Channel.GUITAR) + assert len(gtr_banks) == 1 + assert gtr_banks[0].number == 0 + + bass_banks = manager.list_banks(Channel.BASS) + assert len(bass_banks) == 1 + assert bass_banks[0].number == 5 + + def test_migrate_legacy_presets(self, tmp_path: Path): + """Legacy flat bank_* dirs are migrated to guitar/ on first boot.""" + # Create legacy layout: bank_0/preset_0.json at root + legacy_bank = tmp_path / "bank_0" + legacy_bank.mkdir() + (legacy_bank / "preset_0.json").write_text( + json.dumps({"name": "Legacy Tone", "bank": 0, "program": 0}), + encoding="utf-8", + ) + + # Create PresetManager — should migrate + pm = PresetManager(preset_dir=str(tmp_path), audio_pipeline=None) + + # Old location should be gone + assert not (tmp_path / "bank_0").exists() + + # New location should exist + guitar_path = tmp_path / "guitar" / "bank_0" / "preset_0.json" + assert guitar_path.exists() + + # Preset should be loadable from guitar channel + preset = pm.load(0, 0, Channel.GUITAR) + assert preset.name == "Legacy Tone" + + def test_migration_skips_when_already_migrated(self, tmp_path: Path): + """Migration doesn't run if guitar/ dir already exists.""" + guitar_dir = tmp_path / "guitar" + guitar_dir.mkdir(parents=True) + (guitar_dir / "bank_0").mkdir() + (guitar_dir / "bank_0" / "preset_0.json").write_text( + json.dumps({"name": "Already Migrated", "channel": "guitar", "bank": 0, "program": 0}), + encoding="utf-8", + ) + + pm = PresetManager(preset_dir=str(tmp_path), audio_pipeline=None) + preset = pm.load(0, 0, Channel.GUITAR) + assert preset.name == "Already Migrated" + + def test_fresh_install_no_migration(self, tmp_path: Path): + """Fresh install with no files doesn't raise or create guitar dir.""" + pm = PresetManager(preset_dir=str(tmp_path), audio_pipeline=None) + assert not (tmp_path / "guitar").exists() + assert pm.list_banks() == [] \ No newline at end of file diff --git a/tests/test_web.py b/tests/test_web.py index aa02058..ac6141e 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -32,6 +32,7 @@ def mock_presets(): pm = MagicMock() pm.current_bank = 0 pm.current_program = 0 + pm._current_snapshot = 0 pm.load.return_value = Preset(name="Default", bank=0, program=0) return pm @@ -41,6 +42,11 @@ def mock_pipeline(): pl = MagicMock() pl._bypassed = False pl._tuner_enabled = False + pl._tuner_frequency = 0.0 + pl._tuner_note = '--' + pl._tuner_cents = 0 + pl._tuner_string = -1 + pl._tuner_confidence = 0.0 pl._master_volume = 0.8 pl.routing_mode = "mono" pl.routing_breakpoint = 7