|
|
|
@ -1397,10 +1397,37 @@ HTML = """<!doctype html>
|
|
|
|
.appearance-intensity { display: grid !important; gap: 7px !important; padding: 12px 0 10px; color: var(--ui-muted) !important; font-size: 10px !important; }
|
|
|
|
.appearance-intensity { display: grid !important; gap: 7px !important; padding: 12px 0 10px; color: var(--ui-muted) !important; font-size: 10px !important; }
|
|
|
|
.appearance-intensity output { color: var(--ui-text); font-variant-numeric: tabular-nums; }
|
|
|
|
.appearance-intensity output { color: var(--ui-text); font-variant-numeric: tabular-nums; }
|
|
|
|
.appearance-intensity .accent-intensity { width: 100%; height: 18px; margin: 0; accent-color: var(--ui-accent); cursor: pointer; }
|
|
|
|
.appearance-intensity .accent-intensity { width: 100%; height: 18px; margin: 0; accent-color: var(--ui-accent); cursor: pointer; }
|
|
|
|
|
|
|
|
.appearance-field { display: grid !important; grid-template-columns: 1fr minmax(0, 1.35fr); align-items: center; gap: 8px !important; color: var(--ui-muted) !important; font-size: 10px !important; }
|
|
|
|
|
|
|
|
.appearance-field select { min-width: 0; height: 29px; padding: 0 7px; border: 1px solid var(--ui-border); border-radius: 8px; background: var(--ui-input); color: var(--ui-text); font: 11px ui-sans-serif, system-ui, sans-serif; cursor: pointer; }
|
|
|
|
|
|
|
|
.appearance-field select:focus { outline: 0; border-color: var(--ui-accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--ui-accent) 12%, transparent); }
|
|
|
|
|
|
|
|
.appearance-setting { display: grid; gap: 6px; }
|
|
|
|
|
|
|
|
.appearance-setting-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; color: var(--ui-muted); font-size: 10px; }
|
|
|
|
|
|
|
|
.appearance-setting-head small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
|
|
|
|
.appearance-segmented { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; }
|
|
|
|
|
|
|
|
.appearance-segmented button { min-width: 0; min-height: 27px; padding: 0 5px; border: 1px solid var(--ui-border); border-radius: 7px; background: var(--ui-input); color: var(--ui-muted); font: 10px ui-sans-serif, system-ui, sans-serif; cursor: pointer; transition: border-color .18s ease, color .18s ease, background .18s ease, transform .18s ease; }
|
|
|
|
|
|
|
|
.appearance-segmented button:hover { color: var(--ui-text); border-color: color-mix(in srgb, var(--ui-accent) 62%, var(--ui-border)); transform: translateY(-1px); }
|
|
|
|
|
|
|
|
.appearance-segmented button.is-active { color: var(--ui-text); border-color: var(--ui-accent); background: color-mix(in srgb, var(--ui-accent) 12%, var(--ui-input)); }
|
|
|
|
|
|
|
|
.appearance-decor { padding: 5px 0 0; border-top: 1px solid var(--ui-border); }
|
|
|
|
.appearance-foot { padding-top: 2px; }
|
|
|
|
.appearance-foot { padding-top: 2px; }
|
|
|
|
.appearance-foot button { min-height: 25px; padding: 0 8px; border: 1px solid var(--ui-border); border-radius: 7px; background: transparent; color: var(--ui-muted); font: 700 10px ui-sans-serif, system-ui, sans-serif; cursor: pointer; transition: color .18s ease, border-color .18s ease, background .18s ease; }
|
|
|
|
.appearance-foot button { min-height: 25px; padding: 0 8px; border: 1px solid var(--ui-border); border-radius: 7px; background: transparent; color: var(--ui-muted); font: 700 10px ui-sans-serif, system-ui, sans-serif; cursor: pointer; transition: color .18s ease, border-color .18s ease, background .18s ease; }
|
|
|
|
.appearance-foot button:hover { color: var(--ui-accent); border-color: var(--ui-accent); background: color-mix(in srgb, var(--ui-accent) 8%, transparent); }
|
|
|
|
.appearance-foot button:hover { color: var(--ui-accent); border-color: var(--ui-accent); background: color-mix(in srgb, var(--ui-accent) 8%, transparent); }
|
|
|
|
.appearance-native { position: absolute; width: 1px; height: 1px; overflow: hidden; opacity: 0; pointer-events: none; }
|
|
|
|
.appearance-native { position: absolute; width: 1px; height: 1px; overflow: hidden; opacity: 0; pointer-events: none; }
|
|
|
|
|
|
|
|
body { font-family: var(--ui-font, Inter, ui-sans-serif, system-ui, sans-serif) !important; font-size: calc(14px * var(--ui-font-scale, 1)) !important; }
|
|
|
|
|
|
|
|
body button, body input, body select, body textarea { font-family: var(--ui-font, Inter, ui-sans-serif, system-ui, sans-serif) !important; }
|
|
|
|
|
|
|
|
body::before { opacity: calc(var(--ui-decor, 42) * .001); }
|
|
|
|
|
|
|
|
body[data-density="compact"] .stream-tab, body[data-density="compact"] .archive-tab, body[data-density="compact"] .settings-tab, body[data-density="compact"] .model-tab { padding: 6px; }
|
|
|
|
|
|
|
|
body[data-density="compact"] .settings-group, body[data-density="compact"] .model-group { padding: 6px 8px; gap: 4px 6px; }
|
|
|
|
|
|
|
|
body[data-density="spacious"] .stream-tab, body[data-density="spacious"] .archive-tab, body[data-density="spacious"] .settings-tab, body[data-density="spacious"] .model-tab { padding: 16px; }
|
|
|
|
|
|
|
|
body[data-density="spacious"] .settings-group, body[data-density="spacious"] .model-group { padding: 15px 16px; gap: 10px 12px; }
|
|
|
|
|
|
|
|
body[data-density="spacious"] .quickbar { gap: 10px; }
|
|
|
|
|
|
|
|
body[data-radius="sharp"] .stage, body[data-radius="sharp"] .settings-panel, body[data-radius="sharp"] .model-panel, body[data-radius="sharp"] .archive-panel, body[data-radius="sharp"] .log-drawer, body[data-radius="sharp"] .player-box, body[data-radius="sharp"] .netron-panel { border-radius: 5px; }
|
|
|
|
|
|
|
|
body[data-radius="sharp"] button, body[data-radius="sharp"] select, body[data-radius="sharp"] input, body[data-radius="sharp"] textarea, body[data-radius="sharp"] .config-block { border-radius: 4px !important; }
|
|
|
|
|
|
|
|
body[data-radius="round"] .stage, body[data-radius="round"] .settings-panel, body[data-radius="round"] .model-panel, body[data-radius="round"] .archive-panel, body[data-radius="round"] .log-drawer, body[data-radius="round"] .player-box, body[data-radius="round"] .netron-panel { border-radius: 20px; }
|
|
|
|
|
|
|
|
body[data-radius="round"] button, body[data-radius="round"] select, body[data-radius="round"] input, body[data-radius="round"] textarea, body[data-radius="round"] .config-block { border-radius: 14px !important; }
|
|
|
|
|
|
|
|
body[data-contrast="soft"] { --ui-muted: var(--ui-muted-contrast); --ui-border: var(--ui-border-contrast); }
|
|
|
|
|
|
|
|
body[data-contrast="high"] { --ui-muted: var(--ui-muted-contrast); --ui-border: var(--ui-border-contrast); }
|
|
|
|
|
|
|
|
body[data-motion="none"] *, body[data-motion="none"] *::before, body[data-motion="none"] *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; scroll-behavior: auto !important; }
|
|
|
|
|
|
|
|
body[data-motion="expressive"] .settings-panel, body[data-motion="expressive"] .model-panel, body[data-motion="expressive"] .archive-panel { animation-duration: .62s; }
|
|
|
|
@keyframes appearance-in { from { opacity: 0; transform: translateY(-5px) scale(.98); } to { opacity: 1; transform: none; } }
|
|
|
|
@keyframes appearance-in { from { opacity: 0; transform: translateY(-5px) scale(.98); } to { opacity: 1; transform: none; } }
|
|
|
|
@media (max-width: 760px) {
|
|
|
|
@media (max-width: 760px) {
|
|
|
|
.appearance-trigger { min-width: 38px; width: 38px; padding: 0; justify-content: center; }
|
|
|
|
.appearance-trigger { min-width: 38px; width: 38px; padding: 0; justify-content: center; }
|
|
|
|
@ -1433,6 +1460,8 @@ HTML = """<!doctype html>
|
|
|
|
<div class="appearance-group"><div class="appearance-group-head"><span>Тема</span><small id="themePreview">Тёмная</small></div><div id="themeOptions" class="appearance-options" role="listbox" aria-label="Выбор темы"></div></div>
|
|
|
|
<div class="appearance-group"><div class="appearance-group-head"><span>Тема</span><small id="themePreview">Тёмная</small></div><div id="themeOptions" class="appearance-options" role="listbox" aria-label="Выбор темы"></div></div>
|
|
|
|
<div class="appearance-group"><div class="appearance-group-head"><span>Акцент</span><small id="accentPreview">Синий</small></div><div id="accentOptions" class="appearance-options accent-options" role="listbox" aria-label="Выбор акцента"></div><label class="appearance-custom"><span>Свой цвет</span><input id="accentCustom" class="accent-custom" type="color" value="#8fb8ff" aria-label="Произвольный акцент" title="Произвольный акцент"></label></div>
|
|
|
|
<div class="appearance-group"><div class="appearance-group-head"><span>Акцент</span><small id="accentPreview">Синий</small></div><div id="accentOptions" class="appearance-options accent-options" role="listbox" aria-label="Выбор акцента"></div><label class="appearance-custom"><span>Свой цвет</span><input id="accentCustom" class="accent-custom" type="color" value="#8fb8ff" aria-label="Произвольный акцент" title="Произвольный акцент"></label></div>
|
|
|
|
<label class="appearance-intensity"><span><span>Насыщенность</span><output id="accentIntensityValue">100%</output></span><input id="accentIntensity" class="accent-intensity" type="range" min="55" max="100" step="5" value="100" aria-label="Интенсивность акцента" title="Интенсивность акцента"></label>
|
|
|
|
<label class="appearance-intensity"><span><span>Насыщенность</span><output id="accentIntensityValue">100%</output></span><input id="accentIntensity" class="accent-intensity" type="range" min="55" max="100" step="5" value="100" aria-label="Интенсивность акцента" title="Интенсивность акцента"></label>
|
|
|
|
|
|
|
|
<div class="appearance-group appearance-interface-group"><div class="appearance-group-head"><span>Интерфейс</span><small id="interfacePreview">Система · 100%</small></div><label class="appearance-field"><span>Шрифт</span><select id="fontSelect" aria-label="Шрифт"><option value="system">Системный</option><option value="inter">Inter</option><option value="manrope">Manrope</option><option value="plex">IBM Plex Sans</option><option value="mono">Моноширинный</option></select></label><label class="appearance-intensity appearance-font-size"><span><span>Размер текста</span><output id="fontSizeValue">100%</output></span><input id="fontSize" type="range" min="90" max="120" step="5" value="100" aria-label="Размер текста"></label><div class="appearance-setting"><div class="appearance-setting-head"><span>Плотность</span><small id="densityPreview">Сбалансированная</small></div><div class="appearance-segmented" id="densityOptions"><button type="button" data-setting="density" data-value="compact">Компактная</button><button type="button" data-setting="density" data-value="balanced">Сбалансированная</button><button type="button" data-setting="density" data-value="spacious">Свободная</button></div></div></div>
|
|
|
|
|
|
|
|
<div class="appearance-group appearance-interface-group"><div class="appearance-group-head"><span>Характер</span><small id="stylePreview">Мягкий · Плавный</small></div><div class="appearance-setting"><div class="appearance-setting-head"><span>Скругление</span><small id="radiusPreview">Мягкое</small></div><div class="appearance-segmented" id="radiusOptions"><button type="button" data-setting="radius" data-value="sharp">Чёткое</button><button type="button" data-setting="radius" data-value="soft">Мягкое</button><button type="button" data-setting="radius" data-value="round">Круглое</button></div></div><div class="appearance-setting"><div class="appearance-setting-head"><span>Анимация</span><small id="motionPreview">Плавная</small></div><div class="appearance-segmented" id="motionOptions"><button type="button" data-setting="motion" data-value="none">Без</button><button type="button" data-setting="motion" data-value="subtle">Плавная</button><button type="button" data-setting="motion" data-value="expressive">Выразительная</button></div></div><div class="appearance-setting"><div class="appearance-setting-head"><span>Контраст</span><small id="contrastPreview">Обычный</small></div><div class="appearance-segmented" id="contrastOptions"><button type="button" data-setting="contrast" data-value="soft">Мягкий</button><button type="button" data-setting="contrast" data-value="normal">Обычный</button><button type="button" data-setting="contrast" data-value="high">Высокий</button></div></div><label class="appearance-intensity appearance-decor"><span><span>Декор фона</span><output id="decorValue">42%</output></span><input id="decorIntensity" type="range" min="0" max="100" step="5" value="42" aria-label="Интенсивность декора фона"></label></div>
|
|
|
|
<div class="appearance-foot"><span>Настройки сохраняются автоматически</span><button id="appearanceReset" type="button">Сбросить</button></div>
|
|
|
|
<div class="appearance-foot"><span>Настройки сохраняются автоматически</span><button id="appearanceReset" type="button">Сбросить</button></div>
|
|
|
|
<select id="themeSelect" class="appearance-native" aria-label="Тема">
|
|
|
|
<select id="themeSelect" class="appearance-native" aria-label="Тема">
|
|
|
|
<option value="dark">Тёмная</option><option value="light">Светлая</option><option value="amber">Янтарная</option><option value="midnight">Полночь</option><option value="forest">Лесная</option><option value="rose">Розовая</option><option value="graphite">Графитовая</option><option value="solarized">Solarized</option><option value="ocean">Океан</option><option value="neon">Неон</option><option value="copper">Медь</option><option value="mono">Монохром</option>
|
|
|
|
<option value="dark">Тёмная</option><option value="light">Светлая</option><option value="amber">Янтарная</option><option value="midnight">Полночь</option><option value="forest">Лесная</option><option value="rose">Розовая</option><option value="graphite">Графитовая</option><option value="solarized">Solarized</option><option value="ocean">Океан</option><option value="neon">Неон</option><option value="copper">Медь</option><option value="mono">Монохром</option>
|
|
|
|
@ -2783,6 +2812,20 @@ HTML = """<!doctype html>
|
|
|
|
const ACCENTS = {
|
|
|
|
const ACCENTS = {
|
|
|
|
blue: '#8fb8ff', cyan: '#67e8f9', green: '#7ee787', violet: '#c4b5fd', orange: '#ffb86b', red: '#ff8e9e', pink: '#f9a8d4', lime: '#b7f36b', indigo: '#818cf8', teal: '#2dd4bf', gold: '#facc15', sky: '#38bdf8', coral: '#fb7185'
|
|
|
|
blue: '#8fb8ff', cyan: '#67e8f9', green: '#7ee787', violet: '#c4b5fd', orange: '#ffb86b', red: '#ff8e9e', pink: '#f9a8d4', lime: '#b7f36b', indigo: '#818cf8', teal: '#2dd4bf', gold: '#facc15', sky: '#38bdf8', coral: '#fb7185'
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const FONT_STACKS = {
|
|
|
|
|
|
|
|
system: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
|
|
|
|
|
|
inter: 'Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
|
|
|
|
|
|
manrope: 'Manrope, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
|
|
|
|
|
|
plex: '"IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
|
|
|
|
|
|
mono: '"SFMono-Regular", Consolas, "Liberation Mono", monospace'
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
const APPEARANCE_DEFAULTS = {font: 'system', fontSize: '100', density: 'balanced', radius: 'soft', motion: 'subtle', contrast: 'normal', decor: '42'};
|
|
|
|
|
|
|
|
const APPEARANCE_LABELS = {
|
|
|
|
|
|
|
|
density: {compact: 'Компактная', balanced: 'Сбалансированная', spacious: 'Свободная'},
|
|
|
|
|
|
|
|
radius: {sharp: 'Чёткое', soft: 'Мягкое', round: 'Круглое'},
|
|
|
|
|
|
|
|
motion: {none: 'Без', subtle: 'Плавная', expressive: 'Выразительная'},
|
|
|
|
|
|
|
|
contrast: {soft: 'Мягкий', normal: 'Обычный', high: 'Высокий'}
|
|
|
|
|
|
|
|
};
|
|
|
|
function mixHex(first, second, amount) {
|
|
|
|
function mixHex(first, second, amount) {
|
|
|
|
const parse = value => [0, 2, 4].map(offset => parseInt(value.slice(offset + 1, offset + 3), 16));
|
|
|
|
const parse = value => [0, 2, 4].map(offset => parseInt(value.slice(offset + 1, offset + 3), 16));
|
|
|
|
const a = parse(first); const b = parse(second); const t = Math.max(0, Math.min(1, amount));
|
|
|
|
const a = parse(first); const b = parse(second); const t = Math.max(0, Math.min(1, amount));
|
|
|
|
@ -2815,6 +2858,7 @@ HTML = """<!doctype html>
|
|
|
|
Object.entries(palette).forEach(([key, value]) => root.style.setProperty(`--ui-${key}`, value));
|
|
|
|
Object.entries(palette).forEach(([key, value]) => root.style.setProperty(`--ui-${key}`, value));
|
|
|
|
document.body.dataset.theme = theme;
|
|
|
|
document.body.dataset.theme = theme;
|
|
|
|
setValue('themeSelect', theme);
|
|
|
|
setValue('themeSelect', theme);
|
|
|
|
|
|
|
|
applyContrastVariables(document.body.dataset.contrast || APPEARANCE_DEFAULTS.contrast);
|
|
|
|
try { localStorage.setItem('fpv-theme', theme); } catch (_error) {}
|
|
|
|
try { localStorage.setItem('fpv-theme', theme); } catch (_error) {}
|
|
|
|
applyAccent($('accentSelect').value || 'blue', $('accentCustom').value);
|
|
|
|
applyAccent($('accentSelect').value || 'blue', $('accentCustom').value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -2825,6 +2869,39 @@ HTML = """<!doctype html>
|
|
|
|
panel.hidden = !open;
|
|
|
|
panel.hidden = !open;
|
|
|
|
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
|
|
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function applyAppearanceSetting(name, value) {
|
|
|
|
|
|
|
|
const root = document.documentElement;
|
|
|
|
|
|
|
|
let next = String(value ?? APPEARANCE_DEFAULTS[name] ?? '');
|
|
|
|
|
|
|
|
if (name === 'font' && !Object.prototype.hasOwnProperty.call(FONT_STACKS, next)) next = APPEARANCE_DEFAULTS.font;
|
|
|
|
|
|
|
|
if (Object.prototype.hasOwnProperty.call(APPEARANCE_LABELS, name) && !Object.prototype.hasOwnProperty.call(APPEARANCE_LABELS[name], next)) next = APPEARANCE_DEFAULTS[name];
|
|
|
|
|
|
|
|
if (name === 'fontSize') next = String(Math.max(90, Math.min(120, Number(next) || 100)));
|
|
|
|
|
|
|
|
if (name === 'decor') next = String(Math.max(0, Math.min(100, Number(next) || 0)));
|
|
|
|
|
|
|
|
if (name === 'font') root.style.setProperty('--ui-font', FONT_STACKS[next]);
|
|
|
|
|
|
|
|
if (name === 'fontSize') root.style.setProperty('--ui-font-scale', (Number(next) / 100).toFixed(2));
|
|
|
|
|
|
|
|
if (name === 'decor') root.style.setProperty('--ui-decor', next);
|
|
|
|
|
|
|
|
if (name === 'contrast') applyContrastVariables(next);
|
|
|
|
|
|
|
|
if (name === 'density' || name === 'radius' || name === 'motion' || name === 'contrast') document.body.dataset[name] = next;
|
|
|
|
|
|
|
|
if (name === 'font') setValue('fontSelect', next);
|
|
|
|
|
|
|
|
if (name === 'fontSize') setValue('fontSize', next);
|
|
|
|
|
|
|
|
if (name === 'decor') setValue('decorIntensity', next);
|
|
|
|
|
|
|
|
try { localStorage.setItem(`fpv-${name === 'fontSize' ? 'font-size' : name}`, next); } catch (_error) {}
|
|
|
|
|
|
|
|
syncAppearanceModule();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function applyContrastVariables(name) {
|
|
|
|
|
|
|
|
const theme = THEMES[$('themeSelect')?.value] || THEMES.dark;
|
|
|
|
|
|
|
|
const root = document.documentElement;
|
|
|
|
|
|
|
|
const muted = name === 'soft' ? mixHex(theme.muted, theme.surface, .26) : name === 'high' ? mixHex(theme.muted, theme.text, .52) : theme.muted;
|
|
|
|
|
|
|
|
const border = name === 'soft' ? mixHex(theme.border, theme.surface, .28) : name === 'high' ? mixHex(theme.border, theme.text, .42) : theme.border;
|
|
|
|
|
|
|
|
root.style.setProperty('--ui-muted-contrast', muted);
|
|
|
|
|
|
|
|
root.style.setProperty('--ui-border-contrast', border);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function restoreAppearanceSettings() {
|
|
|
|
|
|
|
|
for (const [name, fallback] of Object.entries(APPEARANCE_DEFAULTS)) {
|
|
|
|
|
|
|
|
let value = fallback;
|
|
|
|
|
|
|
|
try { value = localStorage.getItem(`fpv-${name === 'fontSize' ? 'font-size' : name}`) || fallback; } catch (_error) {}
|
|
|
|
|
|
|
|
applyAppearanceSetting(name, value);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
function syncAppearanceModule() {
|
|
|
|
function syncAppearanceModule() {
|
|
|
|
const theme = $('themeSelect')?.value || 'dark';
|
|
|
|
const theme = $('themeSelect')?.value || 'dark';
|
|
|
|
const accent = $('accentSelect')?.value || 'blue';
|
|
|
|
const accent = $('accentSelect')?.value || 'blue';
|
|
|
|
@ -2836,6 +2913,14 @@ HTML = """<!doctype html>
|
|
|
|
setText('themePreview', themeLabel);
|
|
|
|
setText('themePreview', themeLabel);
|
|
|
|
setText('accentPreview', accentLabel);
|
|
|
|
setText('accentPreview', accentLabel);
|
|
|
|
setText('accentIntensityValue', `${$('accentIntensity')?.value || 100}%`);
|
|
|
|
setText('accentIntensityValue', `${$('accentIntensity')?.value || 100}%`);
|
|
|
|
|
|
|
|
const font = $('fontSelect')?.value || APPEARANCE_DEFAULTS.font;
|
|
|
|
|
|
|
|
setText('interfacePreview', `${$('fontSelect')?.selectedOptions[0]?.textContent || font} · ${$('fontSize')?.value || 100}%`);
|
|
|
|
|
|
|
|
setText('fontSizeValue', `${$('fontSize')?.value || 100}%`);
|
|
|
|
|
|
|
|
setText('decorValue', `${$('decorIntensity')?.value || 42}%`);
|
|
|
|
|
|
|
|
for (const name of ['density', 'radius', 'motion', 'contrast']) setText(`${name}Preview`, APPEARANCE_LABELS[name]?.[document.body.dataset[name] || APPEARANCE_DEFAULTS[name]] || '');
|
|
|
|
|
|
|
|
const motion = document.body.dataset.motion || APPEARANCE_DEFAULTS.motion;
|
|
|
|
|
|
|
|
const radius = document.body.dataset.radius || APPEARANCE_DEFAULTS.radius;
|
|
|
|
|
|
|
|
setText('stylePreview', `${APPEARANCE_LABELS.radius[radius] || ''} · ${APPEARANCE_LABELS.motion[motion] || ''}`);
|
|
|
|
const dot = $('appearanceToggle')?.querySelector('.appearance-trigger-dot');
|
|
|
|
const dot = $('appearanceToggle')?.querySelector('.appearance-trigger-dot');
|
|
|
|
if (dot) dot.style.background = document.documentElement.style.getPropertyValue('--ui-accent') || ACCENTS.blue;
|
|
|
|
if (dot) dot.style.background = document.documentElement.style.getPropertyValue('--ui-accent') || ACCENTS.blue;
|
|
|
|
document.querySelectorAll('#themeOptions [data-theme-value]').forEach(button => {
|
|
|
|
document.querySelectorAll('#themeOptions [data-theme-value]').forEach(button => {
|
|
|
|
@ -2848,6 +2933,7 @@ HTML = """<!doctype html>
|
|
|
|
button.classList.toggle('is-active', active);
|
|
|
|
button.classList.toggle('is-active', active);
|
|
|
|
button.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
|
|
button.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
document.querySelectorAll('[data-setting]').forEach(button => button.classList.toggle('is-active', button.dataset.value === (document.body.dataset[button.dataset.setting] || (button.dataset.setting === 'fontSize' ? $('fontSize')?.value : APPEARANCE_DEFAULTS[button.dataset.setting]))));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function renderAppearanceModule() {
|
|
|
|
function renderAppearanceModule() {
|
|
|
|
const makeOption = (value, label, kind, colors) => {
|
|
|
|
const makeOption = (value, label, kind, colors) => {
|
|
|
|
@ -3508,6 +3594,7 @@ HTML = """<!doctype html>
|
|
|
|
$('accentCustom').value = ACCENTS.blue;
|
|
|
|
$('accentCustom').value = ACCENTS.blue;
|
|
|
|
setValue('accentIntensity', '100');
|
|
|
|
setValue('accentIntensity', '100');
|
|
|
|
applyTheme('dark');
|
|
|
|
applyTheme('dark');
|
|
|
|
|
|
|
|
Object.entries(APPEARANCE_DEFAULTS).forEach(([name, value]) => applyAppearanceSetting(name, value));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
document.addEventListener('pointerdown', e => {
|
|
|
|
document.addEventListener('pointerdown', e => {
|
|
|
|
if (!$('appearancePanel').hidden && !e.target.closest('.appearance-module')) setAppearanceOpen(false);
|
|
|
|
if (!$('appearancePanel').hidden && !e.target.closest('.appearance-module')) setAppearanceOpen(false);
|
|
|
|
@ -3520,6 +3607,10 @@ HTML = """<!doctype html>
|
|
|
|
$('accentSelect').addEventListener('change', e => applyAccent(e.target.value, $('accentCustom').value));
|
|
|
|
$('accentSelect').addEventListener('change', e => applyAccent(e.target.value, $('accentCustom').value));
|
|
|
|
$('accentCustom').addEventListener('input', e => applyAccent('custom', e.target.value));
|
|
|
|
$('accentCustom').addEventListener('input', e => applyAccent('custom', e.target.value));
|
|
|
|
$('accentIntensity').addEventListener('input', e => applyAccent($('accentSelect').value, $('accentCustom').value, e.target.value));
|
|
|
|
$('accentIntensity').addEventListener('input', e => applyAccent($('accentSelect').value, $('accentCustom').value, e.target.value));
|
|
|
|
|
|
|
|
$('fontSelect').addEventListener('change', e => applyAppearanceSetting('font', e.target.value));
|
|
|
|
|
|
|
|
$('fontSize').addEventListener('input', e => applyAppearanceSetting('fontSize', e.target.value));
|
|
|
|
|
|
|
|
$('decorIntensity').addEventListener('input', e => applyAppearanceSetting('decor', e.target.value));
|
|
|
|
|
|
|
|
document.querySelectorAll('[data-setting]').forEach(button => button.addEventListener('click', () => applyAppearanceSetting(button.dataset.setting, button.dataset.value)));
|
|
|
|
$('modelSelect').addEventListener('change', e => {
|
|
|
|
$('modelSelect').addEventListener('change', e => {
|
|
|
|
setValue('modelPath', e.target.value);
|
|
|
|
setValue('modelPath', e.target.value);
|
|
|
|
setText('modelState', e.target.selectedOptions[0]?.textContent || 'модель выбрана');
|
|
|
|
setText('modelState', e.target.selectedOptions[0]?.textContent || 'модель выбрана');
|
|
|
|
@ -3746,6 +3837,7 @@ HTML = """<!doctype html>
|
|
|
|
applyTheme(localStorage.getItem('fpv-theme') || 'dark');
|
|
|
|
applyTheme(localStorage.getItem('fpv-theme') || 'dark');
|
|
|
|
} catch (_error) { applyTheme('dark'); }
|
|
|
|
} catch (_error) { applyTheme('dark'); }
|
|
|
|
renderAppearanceModule();
|
|
|
|
renderAppearanceModule();
|
|
|
|
|
|
|
|
restoreAppearanceSettings();
|
|
|
|
initCollapsibleSections();
|
|
|
|
initCollapsibleSections();
|
|
|
|
initConfigCanvas();
|
|
|
|
initConfigCanvas();
|
|
|
|
loadArchive();
|
|
|
|
loadArchive();
|
|
|
|
|