This commit is contained in:
2026-09-29 20:35:16 +02:00
parent b94b260f20
commit c8b722a7bc
4 changed files with 704 additions and 1 deletions
+129 -1
View File
@@ -6915,6 +6915,9 @@ window.cancelAnimFrame = (function () {
<button type="button" class="f0ck-tuner-subtab-btn" data-subtab="flashyank" title="Flash Yank: retro low-res / low-fps / few-colour video filter">
<i class="fa-solid fa-bolt-lightning"></i> Flash Yank
</button>
<button type="button" class="f0ck-tuner-subtab-btn" data-subtab="laser" title="Laser cursor: colour, trail, glow, click and drag effects">
<i class="fa-solid fa-wand-magic-sparkles"></i> Laser
</button>
</div>
<button type="button" class="f0ck-tuner-close" id="f0ck-tuner-close" title="Close">✕</button>
</div>
@@ -7069,6 +7072,26 @@ window.cancelAnimFrame = (function () {
</div>
</div>
</div>
<!-- Pane: Laser cursor (window.f0ckLaser from laser-cursor.js; rows built from its schema) -->
<div id="f0ck-tuner-pane-laser" class="f0ck-tuner-subtab-pane" style="display: none;">
<div class="f0ck-tuner-body">
<div class="f0ck-tuner-presets-bar">
<div style="font-size: 11px; opacity: 0.85; font-weight: 700; display: flex; align-items: center; gap: 6px;">
<i class="fa-solid fa-wand-magic-sparkles"></i> Laser cursor
</div>
<div class="f0ck-tuner-presets-actions">
<button type="button" id="laser-tuner-reset" class="f0ck-tuner-btn-factory-reset" title="Reset all laser settings to their defaults"><i class="fa-solid fa-rotate-left"></i> Reset</button>
</div>
</div>
<div class="f0ck-tuner-mode-bar" style="display: flex; gap: 6px; padding: 6px 12px; background: rgba(255,255,255,0.04); border-bottom: 1px solid rgba(255,255,255,0.08);">
<button type="button" id="laser-btn-off" class="f0ck-tuner-mode-btn" style="flex: 1; padding: 6px 10px; font-size: 11px; text-align: center;">Off</button>
<button type="button" id="laser-btn-on" class="f0ck-tuner-mode-btn" style="flex: 1; padding: 6px 10px; font-size: 11px; text-align: center;"><i class="fa-solid fa-wand-magic-sparkles"></i> On</button>
</div>
<div id="laser-tuner-rows"></div>
<div id="laser-tuner-info" style="padding: 4px 12px 12px; font-size: 11px; opacity: 0.6; line-height: 1.4;"></div>
</div>
</div>
`;
// ── Sub-Tab Switching Logic ─────────────────────────────────────────────
@@ -7095,7 +7118,7 @@ window.cancelAnimFrame = (function () {
});
const savedSubtab = localStorage.getItem('f0ck_tuner_active_subtab');
if (savedSubtab && (savedSubtab === 'audio' || savedSubtab === 'danmaku' || savedSubtab === 'background' || savedSubtab === 'flashyank')) {
if (savedSubtab && (savedSubtab === 'audio' || savedSubtab === 'danmaku' || savedSubtab === 'background' || savedSubtab === 'flashyank' || savedSubtab === 'laser')) {
switchSubtab(savedSubtab);
}
@@ -7139,6 +7162,111 @@ window.cancelAnimFrame = (function () {
render();
})();
// ── Laser cursor pane (window.f0ckLaser from laser-cursor.js) ──────────
// laser-cursor.js loads after this file: rows are built from its schema the first time the API
// exists, and the pane re-reads whenever the tab is opened.
(() => {
const rowsEl = panel.querySelector('#laser-tuner-rows');
const onBtn = panel.querySelector('#laser-btn-on');
const offBtn = panel.querySelector('#laser-btn-off');
const resetBtn = panel.querySelector('#laser-tuner-reset');
const infoEl = panel.querySelector('#laser-tuner-info');
if (!rowsEl || !onBtn) return;
const api = () => window.f0ckLaser || null;
const esc = (v) => String(v).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
const fmt = (row, v) => {
const n = Number(v);
const shown = (row.step && row.step < 1) ? n.toFixed(row.step < 0.1 ? 2 : 1) : String(Math.round(n));
return shown + (row.unit || '');
};
const build = () => {
const a = api();
if (!a || rowsEl.dataset.built) return;
const st = a.getState();
let html = '';
a.schema.forEach(row => {
if (row.section) { html += '<div class="f0ck-tuner-section-title">' + esc(row.section) + '</div>'; return; }
const v = st[row.key];
if (row.type === 'toggle') {
html += '<div class="f0ck-tuner-row" data-laser-key="' + row.key + '">' +
'<div class="f0ck-tuner-row-label"><span>' + esc(row.label) + '</span></div>' +
'<div style="display: flex; gap: 6px;">' +
'<button type="button" class="f0ck-tuner-mode-btn" data-laser-toggle="0" style="flex: 1; padding: 4px 8px; font-size: 11px;">Off</button>' +
'<button type="button" class="f0ck-tuner-mode-btn" data-laser-toggle="1" style="flex: 1; padding: 4px 8px; font-size: 11px;">On</button>' +
'</div></div>';
} else if (row.type === 'color') {
html += '<div class="f0ck-tuner-row" data-laser-key="' + row.key + '">' +
'<div class="f0ck-tuner-row-label"><span>' + esc(row.label) + '</span><span data-laser-value>' + esc(v) + '</span></div>' +
'<input type="color" value="' + esc(v) + '" style="width: 100%; height: 30px; padding: 2px 4px; border: 1px solid var(--nav-border-color, rgba(255,255,255,0.2)); background: var(--bg, #111); cursor: pointer;" />' +
'</div>';
} else {
html += '<div class="f0ck-tuner-row" data-laser-key="' + row.key + '">' +
'<div class="f0ck-tuner-row-label"><span>' + esc(row.label) + '</span><span data-laser-value>' + fmt(row, v) + '</span></div>' +
'<input type="range" min="' + row.min + '" max="' + row.max + '" step="' + row.step + '" value="' + esc(v) + '" />' +
'</div>';
}
});
rowsEl.innerHTML = html;
rowsEl.dataset.built = '1';
rowsEl.querySelectorAll('[data-laser-key]').forEach(rowEl => {
const key = rowEl.dataset.laserKey;
const row = a.schema.find(r => r.key === key);
const valEl = rowEl.querySelector('[data-laser-value]');
const input = rowEl.querySelector('input');
if (input) {
input.addEventListener('input', () => {
if (valEl) valEl.textContent = row.type === 'color' ? input.value : fmt(row, input.value);
if (api()) api().set(key, input.value);
});
}
rowEl.querySelectorAll('[data-laser-toggle]').forEach(b => {
b.addEventListener('click', () => { if (api()) api().set(key, +b.dataset.laserToggle); });
});
});
};
const render = (st) => {
const a = api();
build();
st = st || (a && a.getState());
const ready = !!st;
[onBtn, offBtn, resetBtn].forEach(el => { el.disabled = !ready; });
if (!ready) { infoEl.textContent = 'The laser cursor is not loaded on this page.'; return; }
onBtn.classList.toggle('active', !!st.enabled);
offBtn.classList.toggle('active', !st.enabled);
rowsEl.querySelectorAll('[data-laser-key]').forEach(rowEl => {
const key = rowEl.dataset.laserKey;
const row = a.schema.find(r => r.key === key);
const v = st[key];
const input = rowEl.querySelector('input');
const valEl = rowEl.querySelector('[data-laser-value]');
if (input && document.activeElement !== input) input.value = v;
if (valEl) valEl.textContent = row.type === 'color' ? v : fmt(row, v);
rowEl.querySelectorAll('[data-laser-toggle]').forEach(b => b.classList.toggle('active', +b.dataset.laserToggle === (st[key] ? 1 : 0)));
// Custom colour only matters when the accent is off; rainbow speed only with rainbow on
if (key === 'color') rowEl.style.opacity = st.useAccent ? '0.45' : '';
if (key === 'rainbowSpeed') rowEl.style.opacity = st.rainbow ? '' : '0.45';
});
infoEl.textContent = st.supported
? 'Changes apply live and are saved on this device.'
: 'Only for mouse/trackpad without "reduce motion": settings are saved but the laser stays off here.';
};
onBtn.addEventListener('click', () => { if (api()) api().setEnabled(true); });
offBtn.addEventListener('click', () => { if (api()) api().setEnabled(false); });
resetBtn.addEventListener('click', () => { if (api()) api().reset(); });
const onChange = (e) => {
if (!panel.isConnected) { window.removeEventListener('f0ck:laser_changed', onChange); return; }
render(e.detail);
};
window.addEventListener('f0ck:laser_changed', onChange);
const tabBtn = panel.querySelector('.f0ck-tuner-subtab-btn[data-subtab="laser"]');
if (tabBtn) tabBtn.addEventListener('click', () => render());
render();
})();
// ── Einfach / Erweitert Toggle (Background Tab) ────────────────────────
const bgBasicRows = panel.querySelector('#f0ck-bg-basic-rows');
const bgAdvancedRows = panel.querySelector('#f0ck-bg-advanced-rows');