71 lines
4.5 KiB
PHP
71 lines
4.5 KiB
PHP
<?php
|
|
/** Общий inline runtime API/независимого PHP-преленда. Переменные задаёт вызывающий layout. */
|
|
$themeInitial = isset($themeInitial) && $themeInitial === 'dark' ? 'dark' : 'light';
|
|
$themeSurface = isset($themeSurface) && in_array($themeSurface, ['public', 'private', 'preland'], true) ? $themeSurface : 'public';
|
|
?>
|
|
<script>
|
|
(function (initial, surface) {
|
|
'use strict';
|
|
if (window.ZoomzillaTheme) { window.ZoomzillaTheme.refresh(); return; }
|
|
const key = 'theme_choice';
|
|
let shown = initial;
|
|
const canonical = value => typeof value === 'string' && value.length <= 128 && /^v1:(light|dark):(manual|preland):[A-Za-z0-9_-]{16,64}(?![\s\S])/.test(value) ? value : null;
|
|
const themeOf = value => value.split(':')[1];
|
|
function read() {
|
|
try {
|
|
for (const part of document.cookie.split(';')) {
|
|
const cookie = part.trim();
|
|
if (!cookie.startsWith(key + '=')) continue;
|
|
const value = canonical(decodeURIComponent(cookie.slice(key.length + 1)));
|
|
if (value) return value;
|
|
}
|
|
} catch (_) {}
|
|
try { return canonical(window.localStorage.getItem(key)); } catch (_) { return null; }
|
|
}
|
|
function apply(theme) {
|
|
shown = theme;
|
|
document.documentElement.dataset.theme = theme;
|
|
document.documentElement.style.colorScheme = theme;
|
|
if (document.body) document.body.classList.toggle('theme-dark', theme === 'dark');
|
|
document.querySelectorAll('[data-theme-choice]').forEach(button => button.setAttribute('aria-pressed', String(button.dataset.themeChoice === theme)));
|
|
}
|
|
function uuid() {
|
|
if (window.crypto && typeof window.crypto.randomUUID === 'function') return window.crypto.randomUUID();
|
|
const bytes = new Uint8Array(16);
|
|
if (window.crypto && window.crypto.getRandomValues) window.crypto.getRandomValues(bytes);
|
|
else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
bytes[6] = (bytes[6] & 15) | 64; bytes[8] = (bytes[8] & 63) | 128;
|
|
const hex = Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
|
|
return hex.slice(0, 8) + '-' + hex.slice(8, 12) + '-' + hex.slice(12, 16) + '-' + hex.slice(16, 20) + '-' + hex.slice(20);
|
|
}
|
|
function refresh() { const value = read(); apply(value ? themeOf(value) : initial); }
|
|
function choose(theme) {
|
|
if (theme !== 'light' && theme !== 'dark') return;
|
|
const from = shown;
|
|
const value = 'v1:' + theme + ':manual:' + uuid().replace(/-/g, '');
|
|
try { document.cookie = key + '=' + encodeURIComponent(value) + '; Expires=Tue, 19 Jan 2038 03:14:07 GMT; Path=/; SameSite=Lax' + (location.protocol === 'https:' ? '; Secure' : ''); } catch (_) {}
|
|
try { window.localStorage.setItem(key, value); } catch (_) {}
|
|
apply(theme);
|
|
if (from === theme) return;
|
|
const detail = {schema_version: 1, event_id: uuid(), from, to: theme, source: 'manual', surface, host: location.hostname, path: location.pathname, occurred_at: new Date().toISOString()};
|
|
try { document.dispatchEvent(new CustomEvent('theme_changed', {detail})); } catch (_) {}
|
|
}
|
|
window.ZoomzillaTheme = Object.freeze({current: () => shown, choose, refresh});
|
|
// Pointer-клик не сбрасывает ввод/selection; клавиатурная навигация остаётся штатной.
|
|
document.addEventListener('mousedown', event => {
|
|
const button = event.target instanceof Element ? event.target.closest('[data-theme-choice]') : null;
|
|
const active = document.activeElement;
|
|
if (button && active && (active.matches('input,textarea') || active.isContentEditable)) event.preventDefault();
|
|
});
|
|
document.addEventListener('click', event => {
|
|
const button = event.target instanceof Element ? event.target.closest('[data-theme-choice]') : null;
|
|
if (button && !button.disabled) choose(button.dataset.themeChoice);
|
|
});
|
|
window.addEventListener('storage', event => { if (event.key === key || event.key === null) refresh(); });
|
|
window.addEventListener('pageshow', refresh);
|
|
document.addEventListener('DOMContentLoaded', refresh);
|
|
document.addEventListener('pjax:end', refresh);
|
|
refresh();
|
|
})(<?= json_encode($themeInitial, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>, <?= json_encode($themeSurface, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>);
|
|
</script>
|