Viewer: Combined/Source groups with a clear break, recording specs, current-call colour, sibling-set link
Browse files- app.js +58 -37
- index.html +10 -6
- style.css +16 -5
app.js
CHANGED
|
@@ -21,8 +21,9 @@
|
|
| 21 |
title: $("call-title"), meta: $("call-meta"), desc: $("call-desc"),
|
| 22 |
play: $("play"), clock: $("clock"), muteAgent: $("mute-agent"), muteCustomer: $("mute-customer"), muteNoise: $("mute-noise"),
|
| 23 |
listenSources: $("listen-sources"), listenCombined: $("listen-combined"), groupSources: $("group-sources"), groupCombined: $("group-combined"),
|
| 24 |
-
combinedName: $("combined-name"), combinedNote: $("combined-note"), sourcesNote: $("sources-note"), keyNoise: $("key-noise"),
|
| 25 |
-
|
|
|
|
| 26 |
stage: $("stage"), waveAgent: $("wave-agent"), waveCustomer: $("wave-customer"), waveNoise: $("wave-noise"), rowNoise: $("row-noise"),
|
| 27 |
waveCombL: $("wave-combined-l"), waveCombR: $("wave-combined-r"), markers: $("markers"), axis: $("axis"),
|
| 28 |
playhead: $("playhead"), tip: $("tip"), nowBody: $("now-body"), logBody: $("log").querySelector("tbody"), logCount: $("log-count"),
|
|
@@ -85,14 +86,20 @@
|
|
| 85 |
const title = PINNED ? set.title : (sets.title || document.body.dataset.title);
|
| 86 |
el.setTitle.textContent = title; document.title = title;
|
| 87 |
el.setBlurb.textContent = PINNED ? (set.blurb || "") : (set.blurb ? `${set.label}: ${set.blurb}` : set.label);
|
| 88 |
-
el.siblings.replaceChildren();
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
}
|
| 97 |
if (!PINNED && sets.sets.length > 1) {
|
| 98 |
el.setSwitch.hidden = false;
|
|
@@ -142,11 +149,12 @@
|
|
| 142 |
if (state.hasNoise) setSource(el.aNoise, paths.proxy(row.take_id, "noise"), paths.wav(row.noise_file));
|
| 143 |
else { el.aNoise.removeAttribute("src"); el.aNoise.load(); }
|
| 144 |
setSource(el.aCombined, paths.proxy(row.take_id, "combined"), paths.wav(row.file_name));
|
| 145 |
-
const renoised = row.stereo_content === "renoised";
|
| 146 |
-
el.combinedName.textContent =
|
| 147 |
-
el.combinedNote.textContent = renoised ? "
|
| 148 |
-
el.
|
| 149 |
-
|
|
|
|
| 150 |
applyListen();
|
| 151 |
renderLog(); setNow(-1); drawAll(); frame();
|
| 152 |
try {
|
|
@@ -219,25 +227,31 @@
|
|
| 219 |
el.groupSources.classList.toggle("inactive", !src); el.groupCombined.classList.toggle("inactive", src);
|
| 220 |
el.badgeSources.hidden = !src; el.badgeCombined.hidden = src;
|
| 221 |
}
|
| 222 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
let r = {};
|
| 224 |
try { r = JSON.parse(row.renoised || "{}") || {}; } catch { r = {}; }
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
}
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
el.laneL.textContent = `customer, cabin + ${bed} bed`
|
| 234 |
-
el.
|
| 235 |
-
`Combined call: customer channel through a simulated car cabin${room.rt60_s != null ? ` (RT60 ${Number(room.rt60_s).toFixed(2)} s)` : ""}, ` +
|
| 236 |
-
`mixed with the ${bed} bed at ${snr.target_db_a ?? "?"} dB(A) signal-to-noise ratio over the customer's speech` +
|
| 237 |
-
`${snr.measured_db_a != null ? ` (measured ${snr.measured_db_a})` : ""}; agent channel clean; both channels normalised to ` +
|
| 238 |
-
`${loud.target_lufs ?? "?"} LUFS under a ${loud.true_peak_ceiling_dbtp ?? -1} dBTP ceiling; 24-bit. ` +
|
| 239 |
-
"Studio recordings play at their recorded levels, so loudness differs between the two groups by design. " +
|
| 240 |
-
"The combined lanes reuse the customer and agent colours: same people, processed.";
|
| 241 |
}
|
| 242 |
document.addEventListener("keydown", (e) => {
|
| 243 |
if (!state.take || e.target.closest("input, textarea, select")) return;
|
|
@@ -267,17 +281,24 @@
|
|
| 267 |
const r = el.waveAgent.getBoundingClientRect(), s = el.stage.getBoundingClientRect();
|
| 268 |
el.playhead.hidden = false; el.playhead.style.left = `${r.left - s.left + xOf(t)}px`;
|
| 269 |
}
|
| 270 |
-
/* One hairline per tool call through
|
|
|
|
| 271 |
function drawCallLines() {
|
| 272 |
const s = el.stage.getBoundingClientRect();
|
| 273 |
-
const
|
|
|
|
|
|
|
| 274 |
Object.assign(el.calllines.style, { left: `${first.left - s.left}px`, width: `${first.width}px`, top: `${first.top - s.top}px`, height: `${m.top - first.top}px` });
|
|
|
|
| 275 |
el.calllines.replaceChildren();
|
| 276 |
state.events.forEach((ev, i) => {
|
| 277 |
-
const
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
|
|
|
|
|
|
|
|
|
| 281 |
});
|
| 282 |
}
|
| 283 |
for (const c of WAVES()) {
|
|
|
|
| 21 |
title: $("call-title"), meta: $("call-meta"), desc: $("call-desc"),
|
| 22 |
play: $("play"), clock: $("clock"), muteAgent: $("mute-agent"), muteCustomer: $("mute-customer"), muteNoise: $("mute-noise"),
|
| 23 |
listenSources: $("listen-sources"), listenCombined: $("listen-combined"), groupSources: $("group-sources"), groupCombined: $("group-combined"),
|
| 24 |
+
combinedName: $("combined-name"), combinedNote: $("combined-note"), sourcesName: $("sources-name"), sourcesNote: $("sources-note"), keyNoise: $("key-noise"),
|
| 25 |
+
specs: $("specs"), emptySibling: $("empty-sibling"),
|
| 26 |
+
badgeSources: $("badge-sources"), badgeCombined: $("badge-combined"), laneL: $("lane-l-note"), laneR: $("lane-r-note"), calllines: $("calllines"),
|
| 27 |
stage: $("stage"), waveAgent: $("wave-agent"), waveCustomer: $("wave-customer"), waveNoise: $("wave-noise"), rowNoise: $("row-noise"),
|
| 28 |
waveCombL: $("wave-combined-l"), waveCombR: $("wave-combined-r"), markers: $("markers"), axis: $("axis"),
|
| 29 |
playhead: $("playhead"), tip: $("tip"), nowBody: $("now-body"), logBody: $("log").querySelector("tbody"), logCount: $("log-count"),
|
|
|
|
| 86 |
const title = PINNED ? set.title : (sets.title || document.body.dataset.title);
|
| 87 |
el.setTitle.textContent = title; document.title = title;
|
| 88 |
el.setBlurb.textContent = PINNED ? (set.blurb || "") : (set.blurb ? `${set.label}: ${set.blurb}` : set.label);
|
| 89 |
+
el.siblings.replaceChildren(); el.emptySibling.replaceChildren();
|
| 90 |
+
const others = sets.sets.filter((o) => o.name !== set.name);
|
| 91 |
+
if (others.length) el.siblings.append(text("span", "Other set:", "muted"));
|
| 92 |
+
el.emptySibling.append(document.createTextNode(`This page is the ${set.label} set (${set.n_takes} calls). `));
|
| 93 |
+
for (const other of others) {
|
| 94 |
+
const link = () => {
|
| 95 |
+
const a = text("a", `${other.label} set ${PINNED && other.space_url ? "↗" : "→"}`, "sibling");
|
| 96 |
+
a.href = PINNED && other.space_url ? other.space_url : `?set=${encodeURIComponent(other.name)}`;
|
| 97 |
+
if (PINNED && other.space_url) { a.target = "_blank"; a.rel = "noopener"; }
|
| 98 |
+
a.title = other.title;
|
| 99 |
+
return a;
|
| 100 |
+
};
|
| 101 |
+
el.siblings.append(link());
|
| 102 |
+
el.emptySibling.append(document.createTextNode(`The ${other.label} set (${other.n_takes} calls) has its own page: `), link(), document.createTextNode(". "));
|
| 103 |
}
|
| 104 |
if (!PINNED && sets.sets.length > 1) {
|
| 105 |
el.setSwitch.hidden = false;
|
|
|
|
| 149 |
if (state.hasNoise) setSource(el.aNoise, paths.proxy(row.take_id, "noise"), paths.wav(row.noise_file));
|
| 150 |
else { el.aNoise.removeAttribute("src"); el.aNoise.load(); }
|
| 151 |
setSource(el.aCombined, paths.proxy(row.take_id, "combined"), paths.wav(row.file_name));
|
| 152 |
+
const renoised = row.stereo_content === "renoised", f = formatsOf(row);
|
| 153 |
+
el.combinedName.textContent = "Combined";
|
| 154 |
+
el.combinedNote.textContent = renoised ? "single post-processed stereo file" : "single stereo file, the two source tracks side by side, unprocessed";
|
| 155 |
+
el.sourcesName.textContent = "Source";
|
| 156 |
+
el.sourcesNote.textContent = `studio recordings · ${fmtSpec(f.agent_audio)} speaker-isolated recordings${state.hasNoise ? " and ambient noise" : ""}; switch each on or off`;
|
| 157 |
+
renderSpecs(row, f);
|
| 158 |
applyListen();
|
| 159 |
renderLog(); setNow(-1); drawAll(); frame();
|
| 160 |
try {
|
|
|
|
| 227 |
el.groupSources.classList.toggle("inactive", !src); el.groupCombined.classList.toggle("inactive", src);
|
| 228 |
el.badgeSources.hidden = !src; el.badgeCombined.hidden = src;
|
| 229 |
}
|
| 230 |
+
const formatsOf = (row) => { try { return JSON.parse(row.formats || "{}") || {}; } catch { return {}; } };
|
| 231 |
+
const fmtSpec = (f) => f ? `${Math.round((f.sample_rate_hz || 48000) / 1000)} kHz / ${f.bit_depth || "?"}-bit` : "48 kHz / 32-bit";
|
| 232 |
+
/* The "Recording specs" block under the waveforms: what each group is, from the row's format blocks and
|
| 233 |
+
the renoise recipe, so the numbers are the take's own. */
|
| 234 |
+
function renderSpecs(row, f) {
|
| 235 |
let r = {};
|
| 236 |
try { r = JSON.parse(row.renoised || "{}") || {}; } catch { r = {}; }
|
| 237 |
+
const renoised = row.stereo_content === "renoised";
|
| 238 |
+
const bed = (row.noise_bed || (r.noise_mix || {}).bed || "").toLowerCase() || "ambient";
|
| 239 |
+
const rows = [["Source", `Agent and customer: ${fmtSpec(f.agent_audio)} mono WAV, one microphone each, unprocessed.`
|
| 240 |
+
+ (state.hasNoise ? ` Ambient: the ${bed} bed played into both talents' headphones, ${fmtSpec(f.noise_audio)} mono WAV, studio playback level.` : "")]];
|
| 241 |
+
if (renoised) {
|
| 242 |
+
const snr = (r.noise_mix || {}).snr || {}, room = r.room_simulation || {}, loud = r.loudness_normalisation || {};
|
| 243 |
+
rows.push(["Combined", `${fmtSpec(f.stereo)} stereo WAV. Customer LEFT: the source track through a simulated car cabin`
|
| 244 |
+
+ `${room.rt60_s != null ? ` (RT60 ${Number(room.rt60_s).toFixed(2)} s)` : ""}, mixed with the ${bed} bed at ${snr.target_db_a ?? "?"} dB(A) `
|
| 245 |
+
+ `signal-to-noise ratio over the customer's speech${snr.measured_db_a != null ? ` (measured ${snr.measured_db_a})` : ""}. Agent RIGHT: the source track, clean. `
|
| 246 |
+
+ `Both channels normalised to ${loud.target_lufs ?? "?"} LUFS under a ${loud.true_peak_ceiling_dbtp ?? -1} dBTP ceiling.`]);
|
| 247 |
+
rows.push(["Levels", "Source tracks play at their recorded levels; the combined file is loudness-normalised, so the two groups differ in loudness by design."]);
|
| 248 |
+
} else {
|
| 249 |
+
rows.push(["Combined", `${fmtSpec(f.stereo)} stereo WAV: customer LEFT, agent RIGHT, the two source tracks side by side, unprocessed.`]);
|
| 250 |
}
|
| 251 |
+
rows.push(["Timeline", "Every file of the take has the same length and starts on the same instant as the tool-call clock."]);
|
| 252 |
+
el.specs.replaceChildren(...rows.flatMap(([k, v]) => [text("dt", k), text("dd", v)]));
|
| 253 |
+
el.laneL.textContent = renoised ? `customer, cabin + ${bed} bed` : "customer, clean";
|
| 254 |
+
el.laneR.textContent = renoised ? "agent, normalised" : "agent, clean";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
}
|
| 256 |
document.addEventListener("keydown", (e) => {
|
| 257 |
if (!state.take || e.target.closest("input, textarea, select")) return;
|
|
|
|
| 281 |
const r = el.waveAgent.getBoundingClientRect(), s = el.stage.getBoundingClientRect();
|
| 282 |
el.playhead.hidden = false; el.playhead.style.left = `${r.left - s.left + xOf(t)}px`;
|
| 283 |
}
|
| 284 |
+
/* One hairline per tool call through the waveform rows of each group: a segment per group, so the
|
| 285 |
+
lines stop at the gap between Combined and Source and resume below it. */
|
| 286 |
function drawCallLines() {
|
| 287 |
const s = el.stage.getBoundingClientRect();
|
| 288 |
+
const visible = (g) => [...g.querySelectorAll(".row canvas")].filter((c) => !c.closest(".row").hidden).map((c) => c.getBoundingClientRect());
|
| 289 |
+
const groups = [...el.stage.querySelectorAll(".group")].map(visible).filter((cs) => cs.length);
|
| 290 |
+
const first = groups[0][0], m = el.markers.getBoundingClientRect();
|
| 291 |
Object.assign(el.calllines.style, { left: `${first.left - s.left}px`, width: `${first.width}px`, top: `${first.top - s.top}px`, height: `${m.top - first.top}px` });
|
| 292 |
+
const segments = groups.map((cs) => [cs[0].top - first.top, cs[cs.length - 1].bottom - first.top]);
|
| 293 |
el.calllines.replaceChildren();
|
| 294 |
state.events.forEach((ev, i) => {
|
| 295 |
+
for (const [top, bottom] of segments) {
|
| 296 |
+
const line = document.createElement("div");
|
| 297 |
+
line.className = "callline" + (ev.is_error ? " err" : "") + (i === state.current ? " current" : "");
|
| 298 |
+
line.dataset.idx = i;
|
| 299 |
+
Object.assign(line.style, { left: `${xOf(ev.onset_time)}px`, top: `${top}px`, height: `${bottom - top}px` });
|
| 300 |
+
el.calllines.append(line);
|
| 301 |
+
}
|
| 302 |
});
|
| 303 |
}
|
| 304 |
for (const c of WAVES()) {
|
index.html
CHANGED
|
@@ -35,8 +35,8 @@
|
|
| 35 |
<span id="clock" class="clock">0:00 / 0:00</span>
|
| 36 |
<div class="listen" role="radiogroup" aria-label="Listen to">
|
| 37 |
<span class="muted">Listen to</span>
|
| 38 |
-
<button class="seg on" id="listen-combined" role="radio" aria-checked="true" title="The
|
| 39 |
-
<button class="seg" id="listen-sources" role="radio" aria-checked="false" title="The studio tracks, each with its own on/off (C toggles)">
|
| 40 |
</div>
|
| 41 |
<div class="legend" role="group" aria-label="Colours">
|
| 42 |
<span class="key static"><span class="swatch s1"></span>Agent</span>
|
|
@@ -45,18 +45,18 @@
|
|
| 45 |
<span class="key static"><span class="swatch dot s3"></span>Tool call</span>
|
| 46 |
<span class="key static"><span class="swatch dot err">!</span>Error</span>
|
| 47 |
</div>
|
| 48 |
-
<span class="hint muted">space play · ← → 5 s · [ ] tool calls · C combined/
|
| 49 |
</div>
|
| 50 |
<div class="work">
|
| 51 |
<div class="left">
|
| 52 |
<div class="stage" id="stage">
|
| 53 |
<div class="group" id="group-combined">
|
| 54 |
-
<div class="group-head"><span class="group-name" id="combined-name">
|
| 55 |
<div class="row"><div class="row-label">Combined L<small id="lane-l-note">customer + bed</small></div><canvas id="wave-combined-l" class="wave lane"></canvas></div>
|
| 56 |
<div class="row"><div class="row-label">Combined R<small id="lane-r-note">agent</small></div><canvas id="wave-combined-r" class="wave lane"></canvas></div>
|
| 57 |
</div>
|
| 58 |
<div class="group" id="group-sources">
|
| 59 |
-
<div class="group-head"><span class="group-name"
|
| 60 |
<div class="row"><div class="row-label">Agent<small>clean microphone</small><button class="row-mute" id="mute-agent" aria-pressed="false" title="Switch the agent track off or on">on</button></div><canvas id="wave-agent" class="wave"></canvas></div>
|
| 61 |
<div class="row"><div class="row-label">Customer<small>clean microphone</small><button class="row-mute" id="mute-customer" aria-pressed="false" title="Switch the customer track off or on">on</button></div><canvas id="wave-customer" class="wave"></canvas></div>
|
| 62 |
<div class="row" id="row-noise"><div class="row-label">Ambient<small>noise bed in headphones</small><button class="row-mute" id="mute-noise" aria-pressed="false" title="Switch the noise bed off or on">on</button></div><canvas id="wave-noise" class="wave"></canvas></div>
|
|
@@ -67,7 +67,10 @@
|
|
| 67 |
<div id="playhead" class="playhead" hidden></div>
|
| 68 |
<div id="tip" class="tip" role="tooltip" hidden></div>
|
| 69 |
</div>
|
| 70 |
-
<
|
|
|
|
|
|
|
|
|
|
| 71 |
</div>
|
| 72 |
<div class="right">
|
| 73 |
<section class="now" aria-live="polite">
|
|
@@ -97,6 +100,7 @@
|
|
| 97 |
</section>
|
| 98 |
<section class="empty" id="empty">
|
| 99 |
<p class="muted">Select a call on the left.</p>
|
|
|
|
| 100 |
<p class="muted small">Each call plays as one timeline: the waveforms of every recorded form on the left, the agent's tool calls as markers through them, and on the right the current call's function signature and result, with the full log below it.</p>
|
| 101 |
<p id="load-error" class="error" hidden></p>
|
| 102 |
</section>
|
|
|
|
| 35 |
<span id="clock" class="clock">0:00 / 0:00</span>
|
| 36 |
<div class="listen" role="radiogroup" aria-label="Listen to">
|
| 37 |
<span class="muted">Listen to</span>
|
| 38 |
+
<button class="seg on" id="listen-combined" role="radio" aria-checked="true" title="The Combined group: one stereo file (C toggles)">Combined</button>
|
| 39 |
+
<button class="seg" id="listen-sources" role="radio" aria-checked="false" title="The Source group: the studio tracks, each with its own on/off (C toggles)">Source</button>
|
| 40 |
</div>
|
| 41 |
<div class="legend" role="group" aria-label="Colours">
|
| 42 |
<span class="key static"><span class="swatch s1"></span>Agent</span>
|
|
|
|
| 45 |
<span class="key static"><span class="swatch dot s3"></span>Tool call</span>
|
| 46 |
<span class="key static"><span class="swatch dot err">!</span>Error</span>
|
| 47 |
</div>
|
| 48 |
+
<span class="hint muted">space play · ← → 5 s · [ ] tool calls · C combined/source</span>
|
| 49 |
</div>
|
| 50 |
<div class="work">
|
| 51 |
<div class="left">
|
| 52 |
<div class="stage" id="stage">
|
| 53 |
<div class="group" id="group-combined">
|
| 54 |
+
<div class="group-head"><span class="group-name" id="combined-name">Combined</span><span class="muted" id="combined-note">single post-processed stereo file</span><span class="badge listening" id="badge-combined">listening</span></div>
|
| 55 |
<div class="row"><div class="row-label">Combined L<small id="lane-l-note">customer + bed</small></div><canvas id="wave-combined-l" class="wave lane"></canvas></div>
|
| 56 |
<div class="row"><div class="row-label">Combined R<small id="lane-r-note">agent</small></div><canvas id="wave-combined-r" class="wave lane"></canvas></div>
|
| 57 |
</div>
|
| 58 |
<div class="group" id="group-sources">
|
| 59 |
+
<div class="group-head"><span class="group-name" id="sources-name">Source</span><span class="muted" id="sources-note">studio recordings · 48 kHz / 32-bit speaker-isolated recordings and ambient noise; switch each on or off</span><span class="badge listening" id="badge-sources" hidden>listening</span></div>
|
| 60 |
<div class="row"><div class="row-label">Agent<small>clean microphone</small><button class="row-mute" id="mute-agent" aria-pressed="false" title="Switch the agent track off or on">on</button></div><canvas id="wave-agent" class="wave"></canvas></div>
|
| 61 |
<div class="row"><div class="row-label">Customer<small>clean microphone</small><button class="row-mute" id="mute-customer" aria-pressed="false" title="Switch the customer track off or on">on</button></div><canvas id="wave-customer" class="wave"></canvas></div>
|
| 62 |
<div class="row" id="row-noise"><div class="row-label">Ambient<small>noise bed in headphones</small><button class="row-mute" id="mute-noise" aria-pressed="false" title="Switch the noise bed off or on">on</button></div><canvas id="wave-noise" class="wave"></canvas></div>
|
|
|
|
| 67 |
<div id="playhead" class="playhead" hidden></div>
|
| 68 |
<div id="tip" class="tip" role="tooltip" hidden></div>
|
| 69 |
</div>
|
| 70 |
+
<section class="specs" aria-label="Recording specs">
|
| 71 |
+
<div class="panel-head">Recording specs</div>
|
| 72 |
+
<dl id="specs"></dl>
|
| 73 |
+
</section>
|
| 74 |
</div>
|
| 75 |
<div class="right">
|
| 76 |
<section class="now" aria-live="polite">
|
|
|
|
| 100 |
</section>
|
| 101 |
<section class="empty" id="empty">
|
| 102 |
<p class="muted">Select a call on the left.</p>
|
| 103 |
+
<p id="empty-sibling" class="muted small"></p>
|
| 104 |
<p class="muted small">Each call plays as one timeline: the waveforms of every recorded form on the left, the agent's tool calls as markers through them, and on the right the current call's function signature and result, with the full log below it.</p>
|
| 105 |
<p id="load-error" class="error" hidden></p>
|
| 106 |
</section>
|
style.css
CHANGED
|
@@ -10,6 +10,7 @@
|
|
| 10 |
--series-noise: #898781;
|
| 11 |
--status-critical: #d03b3b;
|
| 12 |
--wash-1: rgba(42,120,214,0.10);
|
|
|
|
| 13 |
}
|
| 14 |
@media (prefers-color-scheme: dark) {
|
| 15 |
:root:where(:not([data-theme="light"])) {
|
|
@@ -21,6 +22,7 @@
|
|
| 21 |
--series-noise: #898781;
|
| 22 |
--status-critical: #d03b3b;
|
| 23 |
--wash-1: rgba(57,135,229,0.14);
|
|
|
|
| 24 |
}
|
| 25 |
}
|
| 26 |
* { box-sizing: border-box; }
|
|
@@ -43,7 +45,8 @@ code { font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
| 43 |
.links { display: flex; align-items: center; flex-wrap: wrap; justify-content: flex-end; gap: 6px 16px; font-size: 13px; color: var(--text-secondary); white-space: nowrap; padding-top: 2px; }
|
| 44 |
.links a { text-decoration: none; }
|
| 45 |
.links a:hover { text-decoration: underline; }
|
| 46 |
-
.siblings a { padding:
|
|
|
|
| 47 |
.set-switch select { font: inherit; font-size: 13px; padding: 2px 6px; border-radius: 6px; border: 1px solid var(--border); background: var(--surface-1); color: var(--text-primary); }
|
| 48 |
|
| 49 |
.layout { display: grid; grid-template-columns: 300px minmax(0, 1fr); min-height: calc(100% - 46px); }
|
|
@@ -81,7 +84,10 @@ code { font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
| 81 |
.seg { font: inherit; font-size: 13px; border: 1px solid var(--border); background: var(--surface-1); color: var(--text-primary); padding: 4px 10px; cursor: pointer; }
|
| 82 |
.seg:first-of-type { border-radius: 6px 0 0 6px; } .seg:last-of-type { border-radius: 0 6px 6px 0; margin-left: -1px; }
|
| 83 |
.seg.on { background: var(--text-primary); color: var(--surface-1); border-color: var(--text-primary); }
|
| 84 |
-
.
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
/* waveforms left, tool-call panels right when the window is wide enough; stacked otherwise */
|
| 87 |
.work { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-items: start; }
|
|
@@ -95,7 +101,7 @@ code { font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
| 95 |
@media (max-width: 1100px) { .right { grid-template-columns: 1fr; } .layout { grid-template-columns: 240px minmax(0, 1fr); } }
|
| 96 |
|
| 97 |
.stage { position: relative; background: var(--surface-1); border: 1px solid var(--gridline); border-radius: 8px; padding: 4px 12px 6px 0; }
|
| 98 |
-
.group + .group { border-top:
|
| 99 |
.group-head { display: flex; align-items: baseline; gap: 10px; padding: 6px 12px 2px; font-size: 12px; }
|
| 100 |
.group-head .group-name { text-transform: uppercase; letter-spacing: .04em; color: var(--text-secondary); font-weight: 600; }
|
| 101 |
.group.inactive .row { opacity: .5; }
|
|
@@ -124,7 +130,7 @@ code { font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
| 124 |
.markers g:hover .dot, .markers g.current .dot { r: 7; }
|
| 125 |
.markers g:hover .tick, .markers g.current .tick { stroke-width: 3; }
|
| 126 |
.calllines { position: absolute; pointer-events: none; }
|
| 127 |
-
.callline { position: absolute;
|
| 128 |
.callline.err { background: var(--status-critical); }
|
| 129 |
.callline.current { opacity: .9; width: 2px; margin-left: -0.5px; }
|
| 130 |
.playhead { position: absolute; top: 4px; bottom: 28px; width: 2px; margin-left: -1px; background: var(--text-primary); pointer-events: none; opacity: .9; }
|
|
@@ -139,6 +145,9 @@ code { font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
| 139 |
.now-body .when { color: var(--text-secondary); font-variant-numeric: tabular-nums; font-size: 12px; display: block; margin-bottom: 6px; }
|
| 140 |
.sig { padding: 8px 10px; border-radius: 6px; background: var(--page); border: 1px solid var(--gridline); font-size: 12.5px; }
|
| 141 |
.sig .fn { font-weight: 700; color: var(--text-primary); }
|
|
|
|
|
|
|
|
|
|
| 142 |
.now-body .result { margin-top: 8px; padding: 8px; border-radius: 6px; background: var(--page); max-height: 220px; overflow: auto; }
|
| 143 |
.now-body .sub { font-size: 12px; color: var(--text-secondary); margin: 8px 0 2px; }
|
| 144 |
.now-body.changed { animation: lift .6s ease-out; }
|
|
@@ -151,7 +160,9 @@ th { text-align: left; font-weight: 600; color: var(--text-secondary); font-size
|
|
| 151 |
td { padding: 6px 10px; border-bottom: 1px solid var(--gridline); vertical-align: top; }
|
| 152 |
tbody tr { cursor: pointer; }
|
| 153 |
tbody tr:hover { background: var(--wash-1); }
|
| 154 |
-
tbody tr[aria-current="true"] { background: var(--wash-
|
|
|
|
|
|
|
| 155 |
td.num, td.time { font-variant-numeric: tabular-nums; color: var(--text-secondary); white-space: nowrap; }
|
| 156 |
td.call { max-width: 0; width: 100%; }
|
| 157 |
td.call code { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
| 10 |
--series-noise: #898781;
|
| 11 |
--status-critical: #d03b3b;
|
| 12 |
--wash-1: rgba(42,120,214,0.10);
|
| 13 |
+
--wash-3: rgba(27,175,122,0.14);
|
| 14 |
}
|
| 15 |
@media (prefers-color-scheme: dark) {
|
| 16 |
:root:where(:not([data-theme="light"])) {
|
|
|
|
| 22 |
--series-noise: #898781;
|
| 23 |
--status-critical: #d03b3b;
|
| 24 |
--wash-1: rgba(57,135,229,0.14);
|
| 25 |
+
--wash-3: rgba(25,158,112,0.22);
|
| 26 |
}
|
| 27 |
}
|
| 28 |
* { box-sizing: border-box; }
|
|
|
|
| 45 |
.links { display: flex; align-items: center; flex-wrap: wrap; justify-content: flex-end; gap: 6px 16px; font-size: 13px; color: var(--text-secondary); white-space: nowrap; padding-top: 2px; }
|
| 46 |
.links a { text-decoration: none; }
|
| 47 |
.links a:hover { text-decoration: underline; }
|
| 48 |
+
.siblings a.sibling { padding: 4px 12px; border: 1px solid var(--series-1); color: var(--series-1); border-radius: 14px; font-weight: 600; margin-left: 6px; }
|
| 49 |
+
.siblings a.sibling:hover { background: var(--wash-1); text-decoration: none; }
|
| 50 |
.set-switch select { font: inherit; font-size: 13px; padding: 2px 6px; border-radius: 6px; border: 1px solid var(--border); background: var(--surface-1); color: var(--text-primary); }
|
| 51 |
|
| 52 |
.layout { display: grid; grid-template-columns: 300px minmax(0, 1fr); min-height: calc(100% - 46px); }
|
|
|
|
| 84 |
.seg { font: inherit; font-size: 13px; border: 1px solid var(--border); background: var(--surface-1); color: var(--text-primary); padding: 4px 10px; cursor: pointer; }
|
| 85 |
.seg:first-of-type { border-radius: 6px 0 0 6px; } .seg:last-of-type { border-radius: 0 6px 6px 0; margin-left: -1px; }
|
| 86 |
.seg.on { background: var(--text-primary); color: var(--surface-1); border-color: var(--text-primary); }
|
| 87 |
+
.specs { margin-top: 12px; background: var(--surface-1); border: 1px solid var(--gridline); border-radius: 8px; padding: 0 0 10px; }
|
| 88 |
+
.specs dl { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 4px 16px; margin: 0; padding: 2px 16px 0; font-size: 12px; }
|
| 89 |
+
.specs dt { color: var(--text-primary); font-weight: 600; }
|
| 90 |
+
.specs dd { margin: 0; color: var(--text-secondary); max-width: 130ch; }
|
| 91 |
|
| 92 |
/* waveforms left, tool-call panels right when the window is wide enough; stacked otherwise */
|
| 93 |
.work { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-items: start; }
|
|
|
|
| 101 |
@media (max-width: 1100px) { .right { grid-template-columns: 1fr; } .layout { grid-template-columns: 240px minmax(0, 1fr); } }
|
| 102 |
|
| 103 |
.stage { position: relative; background: var(--surface-1); border: 1px solid var(--gridline); border-radius: 8px; padding: 4px 12px 6px 0; }
|
| 104 |
+
.group + .group { border-top: 3px solid var(--baseline); margin-top: 14px; padding-top: 4px; }
|
| 105 |
.group-head { display: flex; align-items: baseline; gap: 10px; padding: 6px 12px 2px; font-size: 12px; }
|
| 106 |
.group-head .group-name { text-transform: uppercase; letter-spacing: .04em; color: var(--text-secondary); font-weight: 600; }
|
| 107 |
.group.inactive .row { opacity: .5; }
|
|
|
|
| 130 |
.markers g:hover .dot, .markers g.current .dot { r: 7; }
|
| 131 |
.markers g:hover .tick, .markers g.current .tick { stroke-width: 3; }
|
| 132 |
.calllines { position: absolute; pointer-events: none; }
|
| 133 |
+
.callline { position: absolute; width: 1px; background: var(--series-3); opacity: .4; }
|
| 134 |
.callline.err { background: var(--status-critical); }
|
| 135 |
.callline.current { opacity: .9; width: 2px; margin-left: -0.5px; }
|
| 136 |
.playhead { position: absolute; top: 4px; bottom: 28px; width: 2px; margin-left: -1px; background: var(--text-primary); pointer-events: none; opacity: .9; }
|
|
|
|
| 145 |
.now-body .when { color: var(--text-secondary); font-variant-numeric: tabular-nums; font-size: 12px; display: block; margin-bottom: 6px; }
|
| 146 |
.sig { padding: 8px 10px; border-radius: 6px; background: var(--page); border: 1px solid var(--gridline); font-size: 12.5px; }
|
| 147 |
.sig .fn { font-weight: 700; color: var(--text-primary); }
|
| 148 |
+
.now-body .sig { border-left: 3px solid var(--series-3); }
|
| 149 |
+
.now-body .sig .fn { color: var(--series-3); }
|
| 150 |
+
.now-body .when::before { content: ''; display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--series-3); margin-right: 6px; vertical-align: 0; }
|
| 151 |
.now-body .result { margin-top: 8px; padding: 8px; border-radius: 6px; background: var(--page); max-height: 220px; overflow: auto; }
|
| 152 |
.now-body .sub { font-size: 12px; color: var(--text-secondary); margin: 8px 0 2px; }
|
| 153 |
.now-body.changed { animation: lift .6s ease-out; }
|
|
|
|
| 160 |
td { padding: 6px 10px; border-bottom: 1px solid var(--gridline); vertical-align: top; }
|
| 161 |
tbody tr { cursor: pointer; }
|
| 162 |
tbody tr:hover { background: var(--wash-1); }
|
| 163 |
+
tbody tr[aria-current="true"] { background: var(--wash-3); box-shadow: inset 3px 0 0 var(--series-3); }
|
| 164 |
+
tbody tr[aria-current="true"] .fn { color: var(--series-3); }
|
| 165 |
+
tbody tr[aria-current="true"] td.num, tbody tr[aria-current="true"] td.time { color: var(--text-primary); font-weight: 600; }
|
| 166 |
td.num, td.time { font-variant-numeric: tabular-nums; color: var(--text-secondary); white-space: nowrap; }
|
| 167 |
td.call { max-width: 0; width: 100%; }
|
| 168 |
td.call code { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|