diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js index 2112983..cf27e16 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js @@ -526,6 +526,24 @@ const CANVAS_PALETTE = { waterfallHue: [45, 18], waterfallSat: 86, waterfallLight: [92, 42], waterfallAlpha: [0.34, 0.82], }, }, + fire: { + dark: { + bg: "#130706", + spectrumLine: "#ff7a1f", spectrumFill: "rgba(255,122,31,0.14)", + spectrumGrid: "rgba(255,110,40,0.09)", spectrumLabel: "rgba(255,202,164,0.54)", + waveformLine: "rgba(255,134,54,0.94)", waveformPeak: "rgba(255,220,96,0.92)", + waveformGrid: "rgba(255,120,36,0.11)", waveformLabel: "rgba(255,214,176,0.66)", + waterfallHue: [8, 42], waterfallSat: 96, waterfallLight: [8, 58], waterfallAlpha: [0.26, 0.88], + }, + light: { + bg: "#fff2e7", + spectrumLine: "#c24500", spectrumFill: "rgba(194,69,0,0.14)", + spectrumGrid: "rgba(125,52,0,0.09)", spectrumLabel: "rgba(90,38,0,0.56)", + waveformLine: "rgba(176,62,0,0.95)", waveformPeak: "rgba(224,132,0,0.90)", + waveformGrid: "rgba(125,52,0,0.10)", waveformLabel: "rgba(90,38,0,0.68)", + waterfallHue: [18, 48], waterfallSat: 90, waterfallLight: [92, 42], waterfallAlpha: [0.34, 0.84], + }, + }, }; function currentStyle() { @@ -540,7 +558,7 @@ function canvasPalette() { function setStyle(style) { const remapped = style === "nord" ? "arctic" : style === "monokai" ? "lime" : style; - const valid = ["original", "arctic", "lime", "contrast", "neon-disco", "golden-rain"]; + const valid = ["original", "arctic", "lime", "contrast", "neon-disco", "golden-rain", "fire"]; const next = valid.includes(remapped) ? remapped : "original"; if (next === "original") { document.documentElement.removeAttribute("data-style"); @@ -4910,19 +4928,35 @@ function bmCategoryColorMap() { return map; } -function createBookmarkChip(bm, colorMap) { +function createBookmarkChip(bm, colorMap, options = {}) { const span = document.createElement("span"); const freqStr = typeof bmFmtFreq === "function" ? bmFmtFreq(bm.freq_hz) : bm.freq_hz + "\u202fHz"; const esc = (s) => String(s) .replace(/&/g, "&").replace(//g, ">"); span.className = "spectrum-bookmark-chip"; + if (options.sideStack) { + span.classList.add("spectrum-bookmark-chip-side"); + } span.title = bm.name + " \u2014 " + freqStr + (bm.comment ? "\n" + bm.comment : ""); span.dataset.bmId = bm.id; + const labelHtml = options.sideStack + ? ( + `` + + `` + + `${esc(freqStr)}` + + `` + + `${esc(bm.name)}` + ) + : ( + "\u00a0" + esc(bm.name) + ); span.innerHTML = - "\u00a0" + esc(bm.name); + labelHtml; const col = colorMap[bm.category || ""]; span.style.setProperty("--bm-cat-bg", col); span.style.setProperty("--bm-cat-fg", bmContrastFg(col)); @@ -4948,7 +4982,7 @@ function updateSideBookmarkStack(container, bookmarks, colorMap) { container.dataset.bmKey = nextKey; container.innerHTML = ""; for (const bm of bookmarks) { - container.appendChild(createBookmarkChip(bm, colorMap)); + container.appendChild(createBookmarkChip(bm, colorMap, { sideStack: true })); } } container.classList.add("bm-side-visible"); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html index 56a924e..a52cf06 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html @@ -46,6 +46,7 @@ + @@ -273,6 +274,9 @@ + diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/bookmarks.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/bookmarks.js index 1cda0fa..9006f10 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/bookmarks.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/bookmarks.js @@ -51,32 +51,51 @@ async function bmFetch(categoryFilter) { function bmApplyFilters() { const text = (document.getElementById("bm-text-filter")?.value || "").trim().toLowerCase(); - const filtered = text - ? bmList.filter((bm) => + const modeFilter = (document.getElementById("bm-mode-filter")?.value || "").trim().toUpperCase(); + let filtered = modeFilter + ? bmList.filter((bm) => String(bm.mode || "").toUpperCase() === modeFilter) + : bmList; + filtered = text + ? filtered.filter((bm) => (bm.name || "").toLowerCase().includes(text) || (bm.category || "").toLowerCase().includes(text) || (bm.comment || "").toLowerCase().includes(text) ) - : bmList; + : filtered; bmRender(filtered); } async function bmRefreshCategoryFilter(keepValue) { const sel = document.getElementById("bm-category-filter"); - if (!sel) return; + const modeSel = document.getElementById("bm-mode-filter"); + if (!sel && !modeSel) return; try { const resp = await fetch("/bookmarks"); if (!resp.ok) return; const all = await resp.json(); - const cats = [...new Set(all.map((b) => b.category || "").filter(Boolean))].sort(); - while (sel.options.length > 1) sel.remove(1); - cats.forEach((cat) => { - const opt = document.createElement("option"); - opt.value = cat; - opt.textContent = cat; - sel.add(opt); - }); - if (keepValue && cats.includes(keepValue)) sel.value = keepValue; + if (sel) { + const cats = [...new Set(all.map((b) => b.category || "").filter(Boolean))].sort(); + while (sel.options.length > 1) sel.remove(1); + cats.forEach((cat) => { + const opt = document.createElement("option"); + opt.value = cat; + opt.textContent = cat; + sel.add(opt); + }); + if (keepValue && cats.includes(keepValue)) sel.value = keepValue; + } + if (modeSel) { + const keepMode = modeSel.value; + const modes = [...new Set(all.map((b) => String(b.mode || "").trim().toUpperCase()).filter(Boolean))].sort(); + while (modeSel.options.length > 1) modeSel.remove(1); + modes.forEach((mode) => { + const opt = document.createElement("option"); + opt.value = mode; + opt.textContent = mode; + modeSel.add(opt); + }); + if (keepMode && modes.includes(keepMode)) modeSel.value = keepMode; + } } catch (_) {} } @@ -301,6 +320,11 @@ async function bmApply(bm) { bmFetch(e.target.value); }); + // Mode filter dropdown (client-side, no re-fetch) + document.getElementById("bm-mode-filter").addEventListener("change", () => { + bmApplyFilters(); + }); + // Text search filter (client-side, no re-fetch) document.getElementById("bm-text-filter").addEventListener("input", () => { bmApplyFilters(); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css index f6036a3..d6829f1 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css @@ -1656,6 +1656,51 @@ button:focus-visible, input:focus-visible, select:focus-visible { transform: none; max-width: 100%; } +.spectrum-bookmark-chip-side { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.16rem; + width: 100%; + min-height: 0; + padding: 0.38rem 0.5rem 0.42rem; + border-radius: 0.55rem; + white-space: normal; + line-height: 1.1; +} +.spectrum-bookmark-side-head { + display: inline-flex; + align-items: center; + gap: 0.32rem; + min-width: 0; + width: 100%; +} +.spectrum-bookmark-chip-side .bm-icon-svg { + flex: 0 0 auto; + width: 0.5rem; + height: 0.72rem; + opacity: 0.95; +} +.spectrum-bookmark-chip-side .spectrum-bookmark-freq, +.spectrum-bookmark-chip-side .spectrum-bookmark-name { + display: block; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; +} +.spectrum-bookmark-chip-side .spectrum-bookmark-freq { + min-width: 0; + font-size: 0.54rem; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + opacity: 0.9; +} +.spectrum-bookmark-chip-side .spectrum-bookmark-name { + width: 100%; + font-size: 0.64rem; + font-weight: 600; +} .bm-icon-svg path { fill: var(--bm-cat-fg, #1a202c); } @@ -2261,3 +2306,61 @@ button:focus-visible, input:focus-visible, select:focus-visible { --wavelength-fg: #87663a; --spectrum-bg: #f5ecd9; } + +/* ── Fire style ───────────────────────────────────────────────────────── */ +[data-style="fire"] { + --bg: #120706; + --card-bg: #1b0c0a; + --input-bg: #180907; + --border: #4c1a12; + --border-light: #7a2e1a; + --text: #ffe7d2; + --text-muted: #c78361; + --text-heading: #fff3e7; + --btn-bg: #2c110d; + --btn-border: #8f3a20; + --accent-green: #ff6f1f; + --accent-yellow: #ffb347; + --accent-red: #ff4a24; + --jog-hi: #381510; + --jog-lo: #24100c; + --jog-shadow: rgba(0,0,0,0.62); + --jog-inset: rgba(255,164,76,0.07); + --audio-level-bg: #1f0d0a; + --audio-level-border: #7a2e1a; + --audio-level-fill-start: #ff4a24; + --audio-level-fill-end: #ffb347; + --filter-bg: #2b120d; + --filter-fg: #ffe7d2; + --filter-border: #8f3a20; + --wavelength-fg: #d38d6a; + --spectrum-bg: #140907; +} +[data-style="fire"][data-theme="light"] { + --bg: #fff3ea; + --card-bg: #fff7f0; + --input-bg: #ffe9da; + --border: #efc7b1; + --border-light: #d9a487; + --text: #42180d; + --text-muted: #8a4b31; + --text-heading: #2f120a; + --btn-bg: #ffe2cf; + --btn-border: #cc8563; + --accent-green: #d24c12; + --accent-yellow: #d88400; + --accent-red: #c53114; + --jog-hi: #ffe2cf; + --jog-lo: #ffd5bc; + --jog-shadow: rgba(108,44,15,0.18); + --jog-inset: rgba(255,255,255,0.72); + --audio-level-bg: #ffe7d7; + --audio-level-border: #d9a487; + --audio-level-fill-start: #c53114; + --audio-level-fill-end: #d88400; + --filter-bg: #ffe2cf; + --filter-fg: #42180d; + --filter-border: #cc8563; + --wavelength-fg: #9a5a3a; + --spectrum-bg: #fff0e4; +}