click the squares?!?!?!?!!?!?!?

This commit is contained in:
2026-09-30 19:15:10 +02:00
parent c8b722a7bc
commit a5c75dd8e5
21 changed files with 6747 additions and 3694 deletions
+173 -5
View File
@@ -3723,6 +3723,10 @@ window.cancelAnimFrame = (function () {
const newSrc = item.src || item.dest;
const mime = (item.mime || '').toLowerCase();
// Current entry for other scripts (Square Clicker keys its maps by album entry)
container.dataset.currentSubId = String(item.id || '');
container.dataset.currentSubMime = mime;
setTimeout(() => document.dispatchEvent(new CustomEvent('f0ck:albumChange', { detail: { container, id: item.id, mime } })), 0);
// Update URL hash with subf0ck slug (or id fallback) so links are shareable directly to this subf0ck
const subKey = item.slug || item.subf0ck_id || item.id;
@@ -6006,6 +6010,8 @@ window.cancelAnimFrame = (function () {
glowSmoothness: 0.95,
showCoverInEye: 0,
showCoverInBg: 0,
hideEye: 0,
hideEyeGlow: 0,
showCoverArt: 0,
solidCover: 1,
coverPosX: 0,
@@ -6533,6 +6539,8 @@ window.cancelAnimFrame = (function () {
// Cover Art & Glow Section
{ section: 'Cover Art & Glow Reactivity', key: 'showCoverInEye', label: 'Show Cover in Eye / Disc (1=On, 0=Off)', min: 0, max: 1, step: 1, unit: '' },
{ section: 'Cover Art & Glow Reactivity', key: 'showCoverInBg', label: 'Show Cover in Background (1=On, 0=Off)', min: 0, max: 1, step: 1, unit: '' },
{ section: 'Cover Art & Glow Reactivity', key: 'hideEye', label: 'Hide Eye / Disc Completely (1=Hidden, 0=Visible)', min: 0, max: 1, step: 1, unit: '' },
{ section: 'Cover Art & Glow Reactivity', key: 'hideEyeGlow', label: 'Hide Eye Glow & Reactive Gradient (1=Hidden, 0=Visible)', min: 0, max: 1, step: 1, unit: '' },
{ section: 'Cover Art & Glow Reactivity', key: 'coverSize', label: 'Eye / Disc Diameter', min: 40, max: 600, step: 5, unit: 'px' },
{ section: 'Cover Art & Glow Reactivity', key: 'coverPosX', label: 'Eye Position Offset X', min: -400, max: 400, step: 5, unit: 'px' },
{ section: 'Cover Art & Glow Reactivity', key: 'coverPosY', label: 'Eye Position Offset Y', min: -300, max: 300, step: 5, unit: 'px' },
@@ -6710,6 +6718,7 @@ window.cancelAnimFrame = (function () {
? Number(cfg.showCoverInEye) === 1
: (cfg.showCoverArt !== undefined ? Number(cfg.showCoverArt) === 1 : Number(cfg.solidCover) === 0);
const showBg = Number(cfg.showCoverInBg) === 1;
const hideEye = Number(cfg.hideEye) === 1;
const color = cfg.coverColor || '#000000';
const coverOp = cfg.coverOpacity !== undefined ? Math.min(1, Math.max(0, Number(cfg.coverOpacity))) : 1.0;
@@ -6734,6 +6743,7 @@ window.cancelAnimFrame = (function () {
});
playerPlaceholders.forEach(ph => {
ph.classList.toggle('eye-hidden', hideEye);
let backdrop = ph.querySelector('.audio-cover-backdrop');
const c = ph.querySelector('.audio-cover-circle');
const note = c ? c.querySelector('i') : ph.querySelector(':scope > i');
@@ -6918,6 +6928,9 @@ window.cancelAnimFrame = (function () {
<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>
<button type="button" class="f0ck-tuner-subtab-btn" data-subtab="sqc" title="Square Clicker: sounds, gameplay, auto maps" style="display: none;">
<i class="fa-solid fa-circle-dot"></i> Square Clicker
</button>
</div>
<button type="button" class="f0ck-tuner-close" id="f0ck-tuner-close" title="Close">✕</button>
</div>
@@ -7073,6 +7086,23 @@ window.cancelAnimFrame = (function () {
</div>
</div>
<!-- Pane: Square Clicker (window.f0ckSqcSettings from square-clicker.js, loaded when the feature is on) -->
<div id="f0ck-tuner-pane-sqc" 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-circle-dot"></i> Square Clicker
</div>
<div class="f0ck-tuner-presets-actions">
<button type="button" id="sqc-tuner-preview" class="f0ck-tuner-btn-factory-reset" title="Play the hit sound"><i class="fa-solid fa-volume-high"></i> Test sound</button>
<button type="button" id="sqc-tuner-reset" class="f0ck-tuner-btn-factory-reset" title="Reset all Square Clicker settings to their defaults"><i class="fa-solid fa-rotate-left"></i> Reset</button>
</div>
</div>
<div id="sqc-tuner-rows"></div>
<div id="sqc-tuner-info" style="padding: 4px 12px 12px; font-size: 11px; opacity: 0.6; line-height: 1.4;">Changes apply to the next map you play and are saved on this device.</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">
@@ -7118,7 +7148,7 @@ window.cancelAnimFrame = (function () {
});
const savedSubtab = localStorage.getItem('f0ck_tuner_active_subtab');
if (savedSubtab && (savedSubtab === 'audio' || savedSubtab === 'danmaku' || savedSubtab === 'background' || savedSubtab === 'flashyank' || savedSubtab === 'laser')) {
if (savedSubtab && (savedSubtab === 'audio' || savedSubtab === 'danmaku' || savedSubtab === 'background' || savedSubtab === 'flashyank' || savedSubtab === 'laser' || (savedSubtab === 'sqc' && window.f0ckSqcSettings))) {
switchSubtab(savedSubtab);
}
@@ -7267,6 +7297,139 @@ window.cancelAnimFrame = (function () {
render();
})();
// ── Square Clicker pane (window.f0ckSqcSettings from square-clicker.js) ─────────────────────
// square-clicker.js is only loaded when the feature is on: the tab stays hidden until it
// announces itself, then the rows are built from its schema.
(() => {
const tabBtn = panel.querySelector('.f0ck-tuner-subtab-btn[data-subtab="sqc"]');
const rowsEl = panel.querySelector('#sqc-tuner-rows');
if (!tabBtn || !rowsEl) return;
const api = () => window.f0ckSqcSettings || 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 selStyle = 'width: 100%; height: 30px; background: var(--bg, #111); color: var(--white, #fff); border: 1px solid var(--nav-border-color, rgba(255,255,255,0.2)); font-family: inherit; font-size: 12px;';
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; }
if (row.type === 'actions') {
html += '<div class="f0ck-tuner-row"><div style="display: flex; gap: 6px; flex-wrap: wrap;">' +
row.buttons.map(([name, label]) => '<button type="button" class="f0ck-tuner-mode-btn" data-sqc-action="' + esc(name) + '" style="flex: 1; padding: 6px 8px; font-size: 11px;">' + esc(label) + '</button>').join('') +
'</div></div>';
return;
}
const v = st[row.key];
html += '<div class="f0ck-tuner-row" data-sqc-key="' + row.key + '"' + (row.showIf ? ' data-sqc-show-if="' + esc(row.showIf) + '"' : '') + '>';
if (row.type === 'toggle') {
html += '<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-sqc-toggle="0" style="flex: 1; padding: 4px 8px; font-size: 11px;">Off</button>' +
'<button type="button" class="f0ck-tuner-mode-btn" data-sqc-toggle="1" style="flex: 1; padding: 4px 8px; font-size: 11px;">On</button>' +
'</div>';
} else if (row.type === 'select') {
html += '<div class="f0ck-tuner-row-label"><span>' + esc(row.label) + '</span></div>' +
'<select style="' + selStyle + '">' + row.options.map(([k, l]) => '<option value="' + esc(k) + '">' + esc(l) + '</option>').join('') + '</select>';
} else if (row.type === 'color') {
html += '<div class="f0ck-tuner-row-label"><span>' + esc(row.label) + '</span><span data-sqc-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;" />';
} else if (row.type === 'file') {
html += '<div class="f0ck-tuner-row-label"><span>' + esc(row.label) + '</span><span data-sqc-value></span></div>' +
'<div style="display: flex; gap: 6px;">' +
'<label class="f0ck-tuner-mode-btn" style="flex: 1; padding: 4px 8px; font-size: 11px; text-align: center; cursor: pointer;">Upload<input type="file" accept="' + esc(row.accept || '') + '" style="display: none;"></label>' +
'<button type="button" class="f0ck-tuner-mode-btn" data-sqc-clear style="flex: 1; padding: 4px 8px; font-size: 11px;">Remove</button>' +
'</div>';
} else {
html += '<div class="f0ck-tuner-row-label"><span>' + esc(row.label) + '</span><span data-sqc-value>' + fmt(row, v) + '</span></div>' +
'<input type="range" min="' + row.min + '" max="' + row.max + '" step="' + row.step + '" value="' + esc(v) + '" />';
}
html += '</div>';
});
rowsEl.innerHTML = html;
rowsEl.dataset.built = '1';
rowsEl.querySelectorAll('[data-sqc-action]').forEach(b => {
b.addEventListener('click', () => {
if (!api()) return;
const r = api().action(b.dataset.sqcAction) || {};
if (r.msg && window.flashMessage) window.flashMessage(r.msg, 2500, r.ok ? 'success' : 'error');
});
});
rowsEl.querySelectorAll('[data-sqc-key]').forEach(rowEl => {
const key = rowEl.dataset.sqcKey;
const row = a.schema.find(r => r.key === key);
const valEl = rowEl.querySelector('[data-sqc-value]');
if (row.type === 'file') {
const input = rowEl.querySelector('input[type="file"]');
input.addEventListener('change', async () => {
const f = input.files && input.files[0];
input.value = '';
if (!f || !api()) return;
const r = await api().setCustomSound(f);
if (!r.ok && window.flashMessage) window.flashMessage(r.msg || 'Could not use that file', 3000, 'error');
else if (api()) api().preview();
});
rowEl.querySelector('[data-sqc-clear]').addEventListener('click', () => { if (api()) api().clearCustomSound(); });
return;
}
const input = rowEl.querySelector('input, select');
if (input) {
input.addEventListener(input.tagName === 'SELECT' ? 'change' : 'input', () => {
if (valEl && row.type === 'range') valEl.textContent = fmt(row, input.value);
if (valEl && row.type === 'color') valEl.textContent = input.value;
if (api()) api().set(key, input.value);
if (key === 'hitSound' || key === 'volume') { if (api()) api().preview(); }
});
}
rowEl.querySelectorAll('[data-sqc-toggle]').forEach(b => {
b.addEventListener('click', () => { if (api()) api().set(key, +b.dataset.sqcToggle); });
});
});
};
const render = (st) => {
const a = api();
if (!a) return;
tabBtn.style.display = '';
build();
st = st || a.getState();
rowsEl.querySelectorAll('[data-sqc-key]').forEach(rowEl => {
const key = rowEl.dataset.sqcKey;
const row = a.schema.find(r => r.key === key);
const valEl = rowEl.querySelector('[data-sqc-value]');
if (row.type === 'file') {
if (valEl) valEl.textContent = st.customSound || 'none';
rowEl.style.opacity = st.hitSound === 'custom' || !st.customSound ? '' : '0.6';
return;
}
if (row.showIf) rowEl.style.display = +st[row.showIf] ? '' : 'none';
const input = rowEl.querySelector('input, select');
if (input && document.activeElement !== input) input.value = st[key];
if (valEl && row.type === 'range') valEl.textContent = fmt(row, st[key]);
if (valEl && row.type === 'color') valEl.textContent = st[key];
rowEl.querySelectorAll('[data-sqc-toggle]').forEach(b => b.classList.toggle('active', +b.dataset.sqcToggle === (+st[key] ? 1 : 0)));
});
};
panel.querySelector('#sqc-tuner-reset').addEventListener('click', () => { if (api()) api().reset(); });
panel.querySelector('#sqc-tuner-preview').addEventListener('click', () => { if (api()) api().preview(); });
const onChange = (e) => {
if (!panel.isConnected) { window.removeEventListener('f0ck:sqc_settings_changed', onChange); return; }
render(e.detail);
};
window.addEventListener('f0ck:sqc_settings_changed', onChange);
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');
@@ -8016,6 +8179,8 @@ window.cancelAnimFrame = (function () {
window.audioVisualizerTuning.showCoverArt = (isShow || Number(window.audioVisualizerTuning.showCoverInEye) === 1) ? 1 : 0;
window.audioVisualizerTuning.solidCover = (window.audioVisualizerTuning.showCoverArt === 1) ? 0 : 1;
updateCoverArtSolidMode();
} else if (s.key === 'hideEye') {
updateCoverArtSolidMode();
} else if (s.key === 'showCoverArt') {
const isShow = Number(input.value) === 1;
window.audioVisualizerTuning.showCoverArt = isShow ? 1 : 0;
@@ -9330,6 +9495,7 @@ window.cancelAnimFrame = (function () {
noteIcon = ph ? ph.querySelector('i') : null;
}
const coverCircle = ph ? ph.querySelector('.audio-cover-circle') : null;
if (ph) ph.classList.toggle('eye-hidden', Number(cfg.hideEye) === 1);
let bgEl = ph ? ph.querySelector('.audio-reactive-bg') : null;
if (ph && !bgEl) {
bgEl = document.createElement('div');
@@ -10037,7 +10203,7 @@ window.cancelAnimFrame = (function () {
if (oldGhosts.length) oldGhosts.forEach(g => g.remove());
}
const eyeNoGlow = Number(cfg.eyeDisableGlow !== undefined ? cfg.eyeDisableGlow : 0) === 1;
const eyeNoGlow = Number(cfg.eyeDisableGlow !== undefined ? cfg.eyeDisableGlow : 0) === 1 || Number(cfg.hideEyeGlow) === 1;
const eyeNoShadow = Number(cfg.eyeDisableShadow !== undefined ? cfg.eyeDisableShadow : 0) === 1;
if (!coverCircle._sqHiddenByTunnel) {
if (eyeNoShadow) {
@@ -10057,7 +10223,7 @@ window.cancelAnimFrame = (function () {
// Dynamic Background Gradient Layer Rendering
if (bgEl) {
const enableBg = Number(cfg.enableBgGradient !== undefined ? cfg.enableBgGradient : 1) === 1;
if (!enableBg || audioElement.paused) {
if (!enableBg || audioElement.paused || Number(cfg.hideEyeGlow) === 1) {
bgEl.style.opacity = '0';
} else {
const baseOp = cfg.bgGradientOpacity !== undefined ? Number(cfg.bgGradientOpacity) : 0.40;
@@ -10878,7 +11044,7 @@ window.cancelAnimFrame = (function () {
const pct1 = Math.min(100, Math.max(0, Math.round((0.36 + smoothGlow * 0.64) * bMult * 100)));
const pct2 = Math.min(100, Math.max(0, Math.round((0.22 + smoothGlow * 0.72) * bMult * 100)));
const pct3 = Math.min(100, Math.max(0, Math.round((0.10 + smoothGlow * 0.68) * bMult * 100)));
const eyeNoGlowB = Number(cfg.eyeDisableGlow !== undefined ? cfg.eyeDisableGlow : 0) === 1;
const eyeNoGlowB = Number(cfg.eyeDisableGlow !== undefined ? cfg.eyeDisableGlow : 0) === 1 || Number(cfg.hideEyeGlow) === 1;
const eyeNoShadowB = Number(cfg.eyeDisableShadow !== undefined ? cfg.eyeDisableShadow : 0) === 1;
if (!coverCircle._sqHiddenByTunnel) {
if (eyeNoShadowB) {
@@ -10901,7 +11067,7 @@ window.cancelAnimFrame = (function () {
coverCircle._visualizerDriving = false;
const baseGlow = cfg.coverGlowBase !== undefined ? cfg.coverGlowBase : 35;
const glowMult = cfg.glowBrightness !== undefined ? cfg.glowBrightness : 1.0;
const eyeNoGlowC = Number(cfg.eyeDisableGlow !== undefined ? cfg.eyeDisableGlow : 0) === 1;
const eyeNoGlowC = Number(cfg.eyeDisableGlow !== undefined ? cfg.eyeDisableGlow : 0) === 1 || Number(cfg.hideEyeGlow) === 1;
const eyeNoShadowC = Number(cfg.eyeDisableShadow !== undefined ? cfg.eyeDisableShadow : 0) === 1;
if (!coverCircle._sqHiddenByTunnel) {
if (eyeNoShadowC) {
@@ -15141,6 +15307,8 @@ window.cancelAnimFrame = (function () {
},
"i": clickOnElementBinding("a#a_addtag"),
"l": () => window.toggleBackground(),
// Square Clicker panel (square-clicker.js; says so when the item has nothing to play)
"o": () => { if (typeof window.f0ckSqcOpen === 'function') window.f0ckSqcOpen(); },
"y": () => {
copyCurrentUrl();
},