Aux text = full ticker; scene effect cues get a target screen
The per-monitor overlay text now reuses the main output's ticker wholesale (same DOM structure and style.css classes): direction h/vertical/side-edges, letter effects (wave/zoom/flash/rotate/updown), the 8 fonts, position, size, weight, color, speed, and the audio-reactive glow — the complete Testo tab option set. Scene 'effect' elements gain a 'Su' selector (main output / one aux display / all screens); each target is an independent cue channel so same-time cues aimed at different monitors all fire, and aux windows in follow mode ignore the all-screens broadcast (they already follow). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5d4747f780
commit
fe1cada045
4 files changed
+144
-56
No files matched your search
+10
-21
@@ -5,33 +5,22 @@
|
|||||||
<meta http-equiv="Content-Security-Policy"
|
<meta http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self'; img-src 'self' blob: data: file:; style-src 'self' 'unsafe-inline'; script-src 'self'" />
|
content="default-src 'self'; img-src 'self' blob: data: file:; style-src 'self' 'unsafe-inline'; script-src 'self'" />
|
||||||
<title>DJ Visualizer — Uscita</title>
|
<title>DJ Visualizer — Uscita</title>
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
<style>
|
<style>
|
||||||
html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: #000; overflow: hidden; cursor: none; }
|
|
||||||
#gl { position: absolute; inset: 0; width: 100%; height: 100%; }
|
|
||||||
#aux-image { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; display: none; }
|
#aux-image { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; display: none; }
|
||||||
/* Overlay text: static (centered) or scrolling marquee, over any mode */
|
|
||||||
#aux-text {
|
|
||||||
position: absolute; left: 0; width: 100%;
|
|
||||||
display: none; overflow: hidden; white-space: nowrap;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Arial Black', sans-serif;
|
|
||||||
text-shadow: 0 0 18px rgba(0,0,0,0.9), 0 2px 6px rgba(0,0,0,0.8);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
#aux-text.static { text-align: center; }
|
|
||||||
#aux-text.scroll #aux-text-inner {
|
|
||||||
display: inline-block; padding-left: 100vw;
|
|
||||||
animation: auxscroll var(--aux-scroll-dur, 14s) linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes auxscroll {
|
|
||||||
from { transform: translateX(0); }
|
|
||||||
to { transform: translateX(-100%); }
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="hide-cursor">
|
||||||
<canvas id="gl"></canvas>
|
<canvas id="gl"></canvas>
|
||||||
<img id="aux-image" alt="" />
|
<img id="aux-image" alt="" />
|
||||||
<div id="aux-text"><span id="aux-text-inner"></span></div>
|
|
||||||
|
<!-- Same ticker structure (and CSS) as the main output -->
|
||||||
|
<div id="ticker"><div id="ticker-track"><span class="tick-copy"></span><span class="tick-copy"></span></div></div>
|
||||||
|
<div id="side-text">
|
||||||
|
<div class="side-col left"><div class="side-track"><span class="side-copy"></span><span class="side-copy"></span></div></div>
|
||||||
|
<div class="side-col right"><div class="side-track"><span class="side-copy"></span><span class="side-copy"></span></div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="shaders.js"></script>
|
<script src="shaders.js"></script>
|
||||||
<script src="effects.js"></script>
|
<script src="effects.js"></script>
|
||||||
<script src="fluid.js"></script>
|
<script src="fluid.js"></script>
|
||||||
|
|||||||
+58
-19
@@ -36,25 +36,47 @@ function applyMode() {
|
|||||||
if (mode === 'follow' && followEffect) viz.setEffect(safeEffect(followEffect));
|
if (mode === 'follow' && followEffect) viz.setEffect(safeEffect(followEffect));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overlay text on top of whatever the mode shows (even plain black).
|
// Overlay text on top of whatever the mode shows (even plain black): the SAME
|
||||||
const txtEl = document.getElementById('aux-text');
|
// ticker as the main output (structure + style.css), with the full option set
|
||||||
const txtInner = document.getElementById('aux-text-inner');
|
// of the Testo tab — direction, letter effect, font, position, size, speed.
|
||||||
|
const ticker = document.getElementById('ticker');
|
||||||
|
const tickerTrack = document.getElementById('ticker-track');
|
||||||
|
const tickCopies = ticker.querySelectorAll('.tick-copy');
|
||||||
|
const sideText = document.getElementById('side-text');
|
||||||
|
const sideCopies = sideText.querySelectorAll('.side-copy');
|
||||||
|
function buildLetters(txt) {
|
||||||
|
const full = (txt || '') + ' • ';
|
||||||
|
let html = '';
|
||||||
|
for (let i = 0; i < full.length; i++) {
|
||||||
|
const c = full[i];
|
||||||
|
const ch = c === ' ' ? ' ' : c.replace(/&/g, '&').replace(/</g, '<');
|
||||||
|
html += '<span class="tl" style="--i:' + i + '">' + ch + '</span>';
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
function styleBoth(prop, value) { tickerTrack.style[prop] = value; sideText.style[prop] = value; }
|
||||||
function applyText(c) {
|
function applyText(c) {
|
||||||
const on = !!(c && c.on && c.value && c.value.trim());
|
const on = !!(c && c.on && c.value && c.value.trim());
|
||||||
txtEl.style.display = on ? 'block' : 'none';
|
const dir = (c && c.dir) || 'h';
|
||||||
|
ticker.classList.toggle('show', on && dir !== 'sides');
|
||||||
|
sideText.classList.toggle('show', on && dir === 'sides');
|
||||||
if (!on) return;
|
if (!on) return;
|
||||||
txtInner.textContent = c.value;
|
const h = buildLetters(c.value);
|
||||||
txtEl.className = c.scroll ? 'scroll' : 'static';
|
tickCopies.forEach(el => el.innerHTML = h);
|
||||||
txtEl.style.fontSize = (c.size || 8) + 'vh';
|
sideCopies.forEach(el => el.innerHTML = h);
|
||||||
txtEl.style.color = c.color || '#ffffff';
|
ticker.classList.remove('dir-h', 'dir-vup', 'dir-vdown');
|
||||||
txtEl.style.fontWeight = c.weight === false ? 'normal' : '900';
|
if (dir !== 'sides') ticker.classList.add('dir-' + dir);
|
||||||
const pos = c.pos || 'middle';
|
ticker.classList.remove('pos-bottom', 'pos-top', 'pos-middle');
|
||||||
txtEl.style.top = pos === 'top' ? '6%' : pos === 'bottom' ? 'auto' : '50%';
|
ticker.classList.add('pos-' + (c.pos || 'bottom'));
|
||||||
txtEl.style.bottom = pos === 'bottom' ? '6%' : 'auto';
|
styleBoth('fontFamily', c.font || "-apple-system, BlinkMacSystemFont, sans-serif");
|
||||||
txtEl.style.transform = pos === 'middle' ? 'translateY(-50%)' : 'none';
|
styleBoth('fontSize', (c.size || 6) + 'vh');
|
||||||
// Longer texts scroll at the same apparent speed: duration scales with length.
|
styleBoth('fontWeight', c.weight === false ? '400' : '800');
|
||||||
const base = 8 + c.value.length * 0.35;
|
styleBoth('color', c.color || '#ffffff');
|
||||||
txtEl.style.setProperty('--aux-scroll-dur', (base / (c.speed || 1)).toFixed(1) + 's');
|
[tickerTrack, sideText].forEach(el => {
|
||||||
|
el.classList.remove('fx-updown', 'fx-wave', 'fx-zoom', 'fx-flash', 'fx-rotate');
|
||||||
|
if (c.fx && c.fx !== 'none') el.classList.add('fx-' + c.fx);
|
||||||
|
});
|
||||||
|
document.documentElement.style.setProperty('--ticker-dur', (18 / (c.speed || 1)) + 's');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- audio + clock -------------------------------------------------------
|
// ---- audio + clock -------------------------------------------------------
|
||||||
@@ -95,16 +117,33 @@ window.djv.onControl((m) => {
|
|||||||
applyText(m.text);
|
applyText(m.text);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'auxFx': {
|
||||||
|
// Scene cue targeted at aux outputs. displayId -1 = every aux window,
|
||||||
|
// but follow-mode windows ignore the broadcast form: they receive the
|
||||||
|
// same effect through the normal 'effect' broadcast and switching them
|
||||||
|
// to 'effect' mode would silently stop them from following afterwards.
|
||||||
|
if (m.displayId !== MY_ID && !(m.displayId === -1 && mode !== 'follow')) break;
|
||||||
|
const e = EFFECTS.list[m.effectIndex];
|
||||||
|
if (e) { mode = 'effect'; ownEffect = e; applyMode(); }
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function frame() {
|
function frame() {
|
||||||
|
const now = performance.now();
|
||||||
|
clockT += (now - clockAt) / 1000;
|
||||||
|
clockAt = now;
|
||||||
if (mode === 'follow' || mode === 'effect') {
|
if (mode === 'follow' || mode === 'effect') {
|
||||||
const now = performance.now();
|
|
||||||
clockT += (now - clockAt) / 1000;
|
|
||||||
clockAt = now;
|
|
||||||
viz.render(clockT, audioData || SILENT);
|
viz.render(clockT, audioData || SILENT);
|
||||||
}
|
}
|
||||||
|
// Ticker letters glow with the music, same formula as the main output.
|
||||||
|
if (audioData) {
|
||||||
|
tickerTrack.style.setProperty('--ticker-glow',
|
||||||
|
(audioData.level * 24 + audioData.beat * 20).toFixed(0) + 'px');
|
||||||
|
sideText.style.setProperty('--ticker-glow',
|
||||||
|
(audioData.level * 24 + audioData.beat * 20).toFixed(0) + 'px');
|
||||||
|
}
|
||||||
requestAnimationFrame(frame);
|
requestAnimationFrame(frame);
|
||||||
}
|
}
|
||||||
requestAnimationFrame(frame);
|
requestAnimationFrame(frame);
|
||||||
|
|||||||
+73
-15
@@ -690,7 +690,7 @@ const IMG_POS_LABELS = { center: 'Centro', top: 'Alto', bottom: 'Basso', left: '
|
|||||||
function optList(pairs, sel) {
|
function optList(pairs, sel) {
|
||||||
return pairs.map(([v, l]) => '<option value="' + String(v).replace(/"/g, '"') + '"' + (v === sel ? ' selected' : '') + '>' + l + '</option>').join('');
|
return pairs.map(([v, l]) => '<option value="' + String(v).replace(/"/g, '"') + '"' + (v === sel ? ' selected' : '') + '>' + l + '</option>').join('');
|
||||||
}
|
}
|
||||||
function newEffectEl(time) { return { type: 'effect', time, effectIndex: EFFECTS.list.indexOf(currentEffect), effectName: currentEffect.name, dur: 0 }; }
|
function newEffectEl(time) { return { type: 'effect', time, effectIndex: EFFECTS.list.indexOf(currentEffect), effectName: currentEffect.name, dur: 0, target: 'main' }; }
|
||||||
function newTextEl(time) { return { type: 'text', time, text: '', dir: 'h', fx: 'none', font: TXT_FONTS[0][0], size: 6, weight: true, color: '#ffffff', pos: 'bottom', speed: 1, dur: 0 }; }
|
function newTextEl(time) { return { type: 'text', time, text: '', dir: 'h', fx: 'none', font: TXT_FONTS[0][0], size: 6, weight: true, color: '#ffffff', pos: 'bottom', speed: 1, dur: 0 }; }
|
||||||
function newImageEl(time) { return { type: 'image', time, image: null, imageSize: 60, imagePos: 'center', dur: 0 }; }
|
function newImageEl(time) { return { type: 'image', time, image: null, imageSize: 60, imagePos: 'center', dur: 0 }; }
|
||||||
|
|
||||||
@@ -736,9 +736,43 @@ function sceneActive(type, t) {
|
|||||||
}
|
}
|
||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
// Effect cues carry a target (main output, one aux display, or all screens):
|
||||||
|
// each target is its own channel so cues aimed at different monitors never
|
||||||
|
// supersede one another.
|
||||||
|
function sceneActiveEffects(t) {
|
||||||
|
const best = {};
|
||||||
|
for (const el of activeCues) {
|
||||||
|
if (el.type !== 'effect' || el.time > t) continue;
|
||||||
|
if (el.dur && el.time + el.dur <= t) continue;
|
||||||
|
const k = String(el.target || 'main');
|
||||||
|
if (!best[k] || el.time >= best[k].time) best[k] = el;
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
function applyEffectTo(targetKey, el) {
|
||||||
|
const e = EFFECTS.list[el.effectIndex];
|
||||||
|
if (!e) return;
|
||||||
|
if (targetKey === 'main') applyEffect(e);
|
||||||
|
else if (targetKey === 'all') {
|
||||||
|
applyEffect(e);
|
||||||
|
// -1 = every aux window (follow-mode ones get it via the effect broadcast)
|
||||||
|
send({ type: 'auxFx', displayId: -1, effectIndex: el.effectIndex });
|
||||||
|
} else {
|
||||||
|
send({ type: 'auxFx', displayId: parseInt(targetKey, 10), effectIndex: el.effectIndex });
|
||||||
|
}
|
||||||
|
}
|
||||||
function advanceCues(t) {
|
function advanceCues(t) {
|
||||||
const ef = sceneActive('effect', t);
|
const efMap = sceneActiveEffects(t);
|
||||||
if (ef !== lastScene.effect) { lastScene.effect = ef; if (ef && EFFECTS.list[ef.effectIndex]) applyEffect(EFFECTS.list[ef.effectIndex]); }
|
const force = lastScene.effect === undefined;
|
||||||
|
const prevMap = (lastScene.effect && typeof lastScene.effect === 'object') ? lastScene.effect : {};
|
||||||
|
const keys = new Set([...Object.keys(efMap), ...Object.keys(prevMap)]);
|
||||||
|
keys.forEach(k => {
|
||||||
|
const el = efMap[k] || null;
|
||||||
|
if (!force && prevMap[k] === el) return;
|
||||||
|
if (el) applyEffectTo(k, el); // apply-once semantics: no revert on expiry
|
||||||
|
});
|
||||||
|
lastScene.effect = efMap;
|
||||||
|
const ef = efMap['main'] || efMap['all'] || null;
|
||||||
// Auto-blend: on a video track, when a scene effect is active, blend the video
|
// Auto-blend: on a video track, when a scene effect is active, blend the video
|
||||||
// ('screen') so the effect shows through underneath. Only when the user left
|
// ('screen') so the effect shows through underneath. Only when the user left
|
||||||
// the blend at the default 'normal' — a manual choice is always respected.
|
// the blend at the default 'normal' — a manual choice is always respected.
|
||||||
@@ -971,12 +1005,26 @@ function buildSceneEditor(tr, i, li) {
|
|||||||
if (c.type === 'effect') {
|
if (c.type === 'effect') {
|
||||||
const ce = (c.effectIndex != null && EFFECTS.list[c.effectIndex]) ? EFFECTS.list[c.effectIndex] : null;
|
const ce = (c.effectIndex != null && EFFECTS.list[c.effectIndex]) ? EFFECTS.list[c.effectIndex] : null;
|
||||||
const cFam = ce ? ce.family : -1;
|
const cFam = ce ? ce.family : -1;
|
||||||
|
// Destination screen: main output, one aux display, or all of them.
|
||||||
|
const tgt = String(c.target || 'main');
|
||||||
|
let tgtOpts = '<option value="main"' + (tgt === 'main' ? ' selected' : '') + '>🖥 Principale</option>';
|
||||||
|
auxDisplays.forEach(ds => {
|
||||||
|
tgtOpts += '<option value="' + ds.id + '"' + (tgt === String(ds.id) ? ' selected' : '') + '>🖥 ' + ds.label + '</option>';
|
||||||
|
});
|
||||||
|
tgtOpts += '<option value="all"' + (tgt === 'all' ? ' selected' : '') + '>🖥🖥 Tutti gli schermi</option>';
|
||||||
|
if (tgt !== 'main' && tgt !== 'all' && !auxDisplays.some(ds => String(ds.id) === tgt)) {
|
||||||
|
tgtOpts += '<option value="' + tgt + '" selected>🖥 Schermo scollegato</option>';
|
||||||
|
}
|
||||||
html +=
|
html +=
|
||||||
'<div class="cue-body">' +
|
'<div class="cue-body">' +
|
||||||
'<div class="cue-row">' +
|
'<div class="cue-row">' +
|
||||||
'<select class="cue-eff-fam" title="Categoria">' + cueFamilyOptions(cFam) + '</select>' +
|
'<select class="cue-eff-fam" title="Categoria">' + cueFamilyOptions(cFam) + '</select>' +
|
||||||
'<select class="cue-eff-pre" title="Preset"' + (cFam < 0 ? ' disabled' : '') + '>' + cuePresetOptions(cFam, c.effectIndex) + '</select>' +
|
'<select class="cue-eff-pre" title="Preset"' + (cFam < 0 ? ' disabled' : '') + '>' + cuePresetOptions(cFam, c.effectIndex) + '</select>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
|
'<div class="cue-row">' +
|
||||||
|
'<span class="cue-dim">Su</span>' +
|
||||||
|
'<select class="cue-eff-target" title="Su quale schermo applicare l\'effetto">' + tgtOpts + '</select>' +
|
||||||
|
'</div>' +
|
||||||
'<div class="cue-row">' +
|
'<div class="cue-row">' +
|
||||||
'<button class="cue-eff-cur" title="Usa l\'effetto visibile ora">🎯 Usa corrente</button>' +
|
'<button class="cue-eff-cur" title="Usa l\'effetto visibile ora">🎯 Usa corrente</button>' +
|
||||||
'<span class="cue-eff-name">' + (ce ? ce.name : 'nessuno') + '</span>' +
|
'<span class="cue-eff-name">' + (ce ? ce.name : 'nessuno') + '</span>' +
|
||||||
@@ -1114,6 +1162,11 @@ function buildSceneEditor(tr, i, li) {
|
|||||||
if (idx >= 0 && EFFECTS.list[idx]) { c.effectIndex = idx; c.effectName = EFFECTS.list[idx].name; save(); }
|
if (idx >= 0 && EFFECTS.list[idx]) { c.effectIndex = idx; c.effectName = EFFECTS.list[idx].name; save(); }
|
||||||
});
|
});
|
||||||
cueEl.querySelector('.cue-eff-cur').addEventListener('click', () => { c.effectIndex = EFFECTS.list.indexOf(currentEffect); c.effectName = currentEffect.name; save(); });
|
cueEl.querySelector('.cue-eff-cur').addEventListener('click', () => { c.effectIndex = EFFECTS.list.indexOf(currentEffect); c.effectName = currentEffect.name; save(); });
|
||||||
|
cueEl.querySelector('.cue-eff-target').addEventListener('change', (e) => {
|
||||||
|
const v = e.target.value;
|
||||||
|
c.target = (v === 'main' || v === 'all') ? v : parseInt(v, 10);
|
||||||
|
saveLive();
|
||||||
|
});
|
||||||
} else if (c.type === 'text') {
|
} else if (c.type === 'text') {
|
||||||
const ti = cueEl.querySelector('.cue-text'); ti.value = c.text || '';
|
const ti = cueEl.querySelector('.cue-text'); ti.value = c.text || '';
|
||||||
ti.addEventListener('input', (e) => { c.text = e.target.value; saveLive(); });
|
ti.addEventListener('input', (e) => { c.text = e.target.value; saveLive(); });
|
||||||
@@ -1678,22 +1731,25 @@ function renderAuxList() {
|
|||||||
extra = '<button class="aux-img">Carica…</button><span class="aux-img-name">' +
|
extra = '<button class="aux-img">Carica…</button><span class="aux-img-name">' +
|
||||||
(c.image ? c.image.split(/[\\/]/).pop() : 'nessuna') + '</span>';
|
(c.image ? c.image.split(/[\\/]/).pop() : 'nessuna') + '</span>';
|
||||||
}
|
}
|
||||||
const tx = c.text = c.text || { on: false, value: '', pos: 'middle', size: 8, color: '#ffffff', weight: true, scroll: true, speed: 1 };
|
const tx = c.text = c.text || { on: false, value: '', dir: 'h', fx: 'none', font: TXT_FONTS[0][0], pos: 'bottom', size: 6, color: '#ffffff', weight: true, speed: 1 };
|
||||||
|
if (tx.dir === undefined) { // migrate pre-ticker configs (scroll-flag era)
|
||||||
|
tx.dir = 'h'; tx.fx = 'none'; tx.font = TXT_FONTS[0][0]; delete tx.scroll;
|
||||||
|
}
|
||||||
let textRow = '';
|
let textRow = '';
|
||||||
if (tx.on) {
|
if (tx.on) {
|
||||||
textRow =
|
textRow =
|
||||||
'<div class="aux-text-row">' +
|
'<div class="aux-text-row">' +
|
||||||
'<input class="atx-val" type="text" placeholder="Testo da mostrare su questo schermo…" />' +
|
'<input class="atx-val" type="text" placeholder="Testo da mostrare su questo schermo…" />' +
|
||||||
'<select class="atx-pos">' +
|
'</div>' +
|
||||||
[['top', 'Alto'], ['middle', 'Centro'], ['bottom', 'Basso']].map(([v, l]) =>
|
'<div class="aux-text-row">' +
|
||||||
'<option value="' + v + '"' + (v === tx.pos ? ' selected' : '') + '>' + l + '</option>').join('') +
|
'<select class="atx-dir" title="Direzione">' + optList(TXT_DIRS, tx.dir) + '</select>' +
|
||||||
'</select>' +
|
'<select class="atx-fx" title="Effetto lettere">' + optList(TXT_FX, tx.fx) + '</select>' +
|
||||||
'<select class="atx-scroll">' +
|
'<select class="atx-font" title="Font">' + optList(TXT_FONTS, tx.font) + '</select>' +
|
||||||
'<option value="1"' + (tx.scroll ? ' selected' : '') + '>Scorrevole</option>' +
|
'<select class="atx-pos" title="Posizione">' + optList(TXT_POS, tx.pos) + '</select>' +
|
||||||
'<option value="0"' + (!tx.scroll ? ' selected' : '') + '>Fisso</option>' +
|
'</div>' +
|
||||||
'</select>' +
|
'<div class="aux-text-row">' +
|
||||||
'<span class="atx-lbl">Dim</span><input class="atx-size" type="range" min="3" max="20" step="0.5" value="' + tx.size + '" />' +
|
'<span class="atx-lbl">Dim</span><input class="atx-size" type="range" min="2" max="18" step="0.5" value="' + tx.size + '" />' +
|
||||||
'<span class="atx-lbl">Vel</span><input class="atx-speed" type="range" min="0.2" max="3" step="0.1" value="' + tx.speed + '" />' +
|
'<span class="atx-lbl">Vel</span><input class="atx-speed" type="range" min="0.2" max="4" step="0.1" value="' + tx.speed + '" />' +
|
||||||
'<input class="atx-color" type="color" value="' + tx.color + '" />' +
|
'<input class="atx-color" type="color" value="' + tx.color + '" />' +
|
||||||
'<label class="atx-b"><input type="checkbox" class="atx-bold"' + (tx.weight ? ' checked' : '') + ' /> B</label>' +
|
'<label class="atx-b"><input type="checkbox" class="atx-bold"' + (tx.weight ? ' checked' : '') + ' /> B</label>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
@@ -1745,8 +1801,10 @@ function renderAuxList() {
|
|||||||
txVal.value = tx.value || '';
|
txVal.value = tx.value || '';
|
||||||
const live = () => { auxSave(); auxSendCfg(d.id); };
|
const live = () => { auxSave(); auxSendCfg(d.id); };
|
||||||
txVal.addEventListener('input', (e) => { tx.value = e.target.value; live(); });
|
txVal.addEventListener('input', (e) => { tx.value = e.target.value; live(); });
|
||||||
|
row.querySelector('.atx-dir').addEventListener('change', (e) => { tx.dir = e.target.value; live(); });
|
||||||
|
row.querySelector('.atx-fx').addEventListener('change', (e) => { tx.fx = e.target.value; live(); });
|
||||||
|
row.querySelector('.atx-font').addEventListener('change', (e) => { tx.font = e.target.value; live(); });
|
||||||
row.querySelector('.atx-pos').addEventListener('change', (e) => { tx.pos = e.target.value; live(); });
|
row.querySelector('.atx-pos').addEventListener('change', (e) => { tx.pos = e.target.value; live(); });
|
||||||
row.querySelector('.atx-scroll').addEventListener('change', (e) => { tx.scroll = e.target.value === '1'; live(); });
|
|
||||||
row.querySelector('.atx-size').addEventListener('input', (e) => { tx.size = parseFloat(e.target.value); live(); });
|
row.querySelector('.atx-size').addEventListener('input', (e) => { tx.size = parseFloat(e.target.value); live(); });
|
||||||
row.querySelector('.atx-speed').addEventListener('input', (e) => { tx.speed = parseFloat(e.target.value); live(); });
|
row.querySelector('.atx-speed').addEventListener('input', (e) => { tx.speed = parseFloat(e.target.value); live(); });
|
||||||
row.querySelector('.atx-color').addEventListener('input', (e) => { tx.color = e.target.value; live(); });
|
row.querySelector('.atx-color').addEventListener('input', (e) => { tx.color = e.target.value; live(); });
|
||||||
|
|||||||
+3
-1
@@ -974,7 +974,9 @@ kbd {
|
|||||||
margin-top: 4px; padding-top: 6px; border-top: 1px dashed rgba(255,255,255,0.1);
|
margin-top: 4px; padding-top: 6px; border-top: 1px dashed rgba(255,255,255,0.1);
|
||||||
}
|
}
|
||||||
.aux-row .atx-val { flex: 1 1 160px; min-width: 0; width: auto; padding: 4px 6px; font-size: 11px; }
|
.aux-row .atx-val { flex: 1 1 160px; min-width: 0; width: auto; padding: 4px 6px; font-size: 11px; }
|
||||||
.aux-row .atx-pos, .aux-row .atx-scroll { flex: 0 0 auto; width: 92px; padding: 4px 6px; font-size: 11px; }
|
.aux-row .atx-dir, .aux-row .atx-fx, .aux-row .atx-font, .aux-row .atx-pos {
|
||||||
|
flex: 1 1 0; min-width: 0; width: auto; padding: 4px 6px; font-size: 11px;
|
||||||
|
}
|
||||||
.aux-row .atx-lbl { flex: 0 0 auto; font-size: 10px; opacity: 0.6; }
|
.aux-row .atx-lbl { flex: 0 0 auto; font-size: 10px; opacity: 0.6; }
|
||||||
.aux-row .atx-size, .aux-row .atx-speed { flex: 0 0 70px; }
|
.aux-row .atx-size, .aux-row .atx-speed { flex: 0 0 70px; }
|
||||||
.aux-row .atx-color { flex: 0 0 30px; width: 30px; height: 22px; padding: 1px; }
|
.aux-row .atx-color { flex: 0 0 30px; width: 30px; height: 22px; padding: 1px; }
|
||||||
|
|||||||
Reference in new issue
Block a user