|
|
|
@ -1311,16 +1311,20 @@ HTML = """<!doctype html>
|
|
|
|
.config-deleted-list { display: grid; gap: 5px; padding-top: 4px; border-top: 1px solid var(--ui-border); }
|
|
|
|
.config-deleted-list { display: grid; gap: 5px; padding-top: 4px; border-top: 1px solid var(--ui-border); }
|
|
|
|
.config-deleted-list[hidden] { display: none !important; }
|
|
|
|
.config-deleted-list[hidden] { display: none !important; }
|
|
|
|
.config-restore-panel[hidden], .config-trash[hidden] { display: none !important; }
|
|
|
|
.config-restore-panel[hidden], .config-trash[hidden] { display: none !important; }
|
|
|
|
.config-canvas { position: relative; display: block; min-height: 760px; padding: 8px 6px 18px; overflow: auto; overscroll-behavior: contain; }
|
|
|
|
.config-canvas { position: relative; display: block; min-height: 760px; padding: 8px 6px 18px; overflow: auto; overscroll-behavior: contain; background: radial-gradient(circle at 12% 8%, color-mix(in srgb, var(--ui-accent) 7%, transparent), transparent 28%), linear-gradient(135deg, color-mix(in srgb, var(--ui-bg) 74%, var(--ui-panel)), var(--ui-bg)); }
|
|
|
|
.config-canvas > .config-block { position: absolute; box-sizing: border-box; container: config-block / inline-size; margin: 0; min-width: 240px; min-height: 110px; border: 1px solid var(--ui-border) !important; border-radius: 10px; background: var(--ui-panel) !important; box-shadow: 0 6px 18px rgba(0, 0, 0, .1); overflow: auto; transition: border-color .2s ease, box-shadow .2s ease, opacity .2s ease; }
|
|
|
|
.config-canvas::before { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; opacity: .34; background-image: linear-gradient(color-mix(in srgb, var(--ui-text) 5%, transparent) 1px, transparent 1px), linear-gradient(90deg, color-mix(in srgb, var(--ui-text) 5%, transparent) 1px, transparent 1px); background-size: 28px 28px; mask-image: linear-gradient(to bottom, black, transparent 86%); }
|
|
|
|
|
|
|
|
.config-canvas > .config-block { position: absolute; z-index: 1; box-sizing: border-box; container: config-block / inline-size; margin: 0; min-width: 240px; min-height: 110px; border: 1px solid var(--ui-border) !important; border-radius: 14px; background: color-mix(in srgb, var(--ui-panel) 94%, var(--ui-accent)) !important; box-shadow: 0 8px 22px color-mix(in srgb, var(--ui-bg) 22%, transparent); overflow: auto; transition: border-color .2s ease, box-shadow .2s ease, opacity .2s ease, transform .2s ease; }
|
|
|
|
|
|
|
|
body[data-card-style="flat"] .config-canvas > .config-block { box-shadow: none; border-radius: 8px; }
|
|
|
|
|
|
|
|
body[data-card-style="neon"] .config-canvas > .config-block { border-color: color-mix(in srgb, var(--ui-accent) 48%, var(--ui-border)) !important; box-shadow: 0 0 0 1px color-mix(in srgb, var(--ui-accent) 12%, transparent), 0 10px 24px color-mix(in srgb, var(--ui-accent) 10%, transparent); }
|
|
|
|
.config-canvas > .config-block { cursor: grab; touch-action: none; }
|
|
|
|
.config-canvas > .config-block { cursor: grab; touch-action: none; }
|
|
|
|
.config-canvas > .config-block:hover { border-color: color-mix(in srgb, var(--ui-accent) 45%, var(--ui-border)) !important; }
|
|
|
|
.config-canvas > .config-block:hover { border-color: color-mix(in srgb, var(--ui-accent) 45%, var(--ui-border)) !important; }
|
|
|
|
.config-canvas > .config-block.is-selected { border-color: color-mix(in srgb, var(--ui-accent) 70%, var(--ui-border)) !important; box-shadow: 0 0 0 2px color-mix(in srgb, var(--ui-accent) 12%, transparent), 0 6px 18px rgba(0, 0, 0, .1); }
|
|
|
|
.config-canvas > .config-block.is-selected { border-color: color-mix(in srgb, var(--ui-accent) 70%, var(--ui-border)) !important; box-shadow: 0 0 0 2px color-mix(in srgb, var(--ui-accent) 12%, transparent), 0 6px 18px rgba(0, 0, 0, .1); }
|
|
|
|
.config-canvas > .config-block.is-dragging, .config-canvas > .config-block.is-resizing { z-index: 5; border-color: var(--ui-accent) !important; box-shadow: 0 10px 28px color-mix(in srgb, var(--ui-accent) 16%, transparent); transition: none; }
|
|
|
|
.config-canvas > .config-block.is-dragging, .config-canvas > .config-block.is-resizing { z-index: 5; border-color: var(--ui-accent) !important; box-shadow: 0 10px 28px color-mix(in srgb, var(--ui-accent) 16%, transparent); transition: none; }
|
|
|
|
.config-canvas > .config-custom-block { border-style: dashed !important; }
|
|
|
|
.config-canvas > .config-custom-block { border-style: dashed !important; }
|
|
|
|
.config-block > summary { position: sticky; top: 0; z-index: 2; background: var(--ui-panel); }
|
|
|
|
.config-block > summary { position: sticky; top: 0; z-index: 2; background: color-mix(in srgb, var(--ui-panel) 92%, var(--ui-accent)); border-bottom: 1px solid color-mix(in srgb, var(--ui-border) 80%, transparent); }
|
|
|
|
.config-block > summary::after { display: none !important; }
|
|
|
|
.config-block > summary::after { display: none !important; }
|
|
|
|
.block-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
.block-title { min-width: 0; display: inline-flex; align-items: center; gap: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
|
|
|
|
.block-title::before { content: "✦"; flex: 0 0 auto; color: var(--ui-accent); font-size: .85em; opacity: .8; }
|
|
|
|
.config-block > summary { cursor: grab; touch-action: none; }
|
|
|
|
.config-block > summary { cursor: grab; touch-action: none; }
|
|
|
|
.config-block > summary:active, .config-block.is-dragging > summary { cursor: grabbing; }
|
|
|
|
.config-block > summary:active, .config-block.is-dragging > summary { cursor: grabbing; }
|
|
|
|
.config-block.is-dragging, .config-block.is-resizing { user-select: none; }
|
|
|
|
.config-block.is-dragging, .config-block.is-resizing { user-select: none; }
|
|
|
|
@ -1512,7 +1516,7 @@ HTML = """<!doctype html>
|
|
|
|
<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="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-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><div class="appearance-setting"><div class="appearance-setting-head"><span>Карточки блоков</span><small id="cardStylePreview">Мягкие</small></div><div class="appearance-segmented" id="cardStyleOptions"><button type="button" data-setting="cardStyle" data-value="flat">Плоские</button><button type="button" data-setting="cardStyle" data-value="soft">Мягкие</button><button type="button" data-setting="cardStyle" data-value="neon">Неон</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>
|
|
|
|
@ -2985,12 +2989,13 @@ HTML = """<!doctype html>
|
|
|
|
plex: '"IBM Plex Sans", 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'
|
|
|
|
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_DEFAULTS = {font: 'system', fontSize: '100', density: 'balanced', radius: 'soft', motion: 'subtle', contrast: 'normal', cardStyle: 'soft', decor: '42'};
|
|
|
|
const APPEARANCE_LABELS = {
|
|
|
|
const APPEARANCE_LABELS = {
|
|
|
|
density: {compact: 'Компактная', balanced: 'Сбалансированная', spacious: 'Свободная'},
|
|
|
|
density: {compact: 'Компактная', balanced: 'Сбалансированная', spacious: 'Свободная'},
|
|
|
|
radius: {sharp: 'Чёткое', soft: 'Мягкое', round: 'Круглое'},
|
|
|
|
radius: {sharp: 'Чёткое', soft: 'Мягкое', round: 'Круглое'},
|
|
|
|
motion: {none: 'Без', subtle: 'Плавная', expressive: 'Выразительная'},
|
|
|
|
motion: {none: 'Без', subtle: 'Плавная', expressive: 'Выразительная'},
|
|
|
|
contrast: {soft: 'Мягкий', normal: 'Обычный', high: 'Высокий'}
|
|
|
|
contrast: {soft: 'Мягкий', normal: 'Обычный', high: 'Высокий'},
|
|
|
|
|
|
|
|
cardStyle: {flat: 'Плоские', soft: 'Мягкие', neon: 'Неон'}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
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));
|
|
|
|
@ -3046,7 +3051,7 @@ HTML = """<!doctype html>
|
|
|
|
if (name === 'fontSize') root.style.setProperty('--ui-font-scale', (Number(next) / 100).toFixed(2));
|
|
|
|
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 === 'decor') root.style.setProperty('--ui-decor', next);
|
|
|
|
if (name === 'contrast') applyContrastVariables(next);
|
|
|
|
if (name === 'contrast') applyContrastVariables(next);
|
|
|
|
if (name === 'density' || name === 'radius' || name === 'motion' || name === 'contrast') document.body.dataset[name] = next;
|
|
|
|
if (name === 'density' || name === 'radius' || name === 'motion' || name === 'contrast' || name === 'cardStyle') document.body.dataset[name] = next;
|
|
|
|
if (name === 'font') setValue('fontSelect', next);
|
|
|
|
if (name === 'font') setValue('fontSelect', next);
|
|
|
|
if (name === 'fontSize') setValue('fontSize', next);
|
|
|
|
if (name === 'fontSize') setValue('fontSize', next);
|
|
|
|
if (name === 'decor') setValue('decorIntensity', next);
|
|
|
|
if (name === 'decor') setValue('decorIntensity', next);
|
|
|
|
@ -3086,7 +3091,7 @@ HTML = """<!doctype html>
|
|
|
|
setText('appearancePreviewTitle', `${themeLabel} · ${accentLabel}`);
|
|
|
|
setText('appearancePreviewTitle', `${themeLabel} · ${accentLabel}`);
|
|
|
|
setText('appearancePreviewMeta', `${$('fontSelect')?.selectedOptions[0]?.textContent || font} · ${$('fontSize')?.value || 100}% · ${APPEARANCE_LABELS.density[document.body.dataset.density || APPEARANCE_DEFAULTS.density] || ''}`);
|
|
|
|
setText('appearancePreviewMeta', `${$('fontSelect')?.selectedOptions[0]?.textContent || font} · ${$('fontSize')?.value || 100}% · ${APPEARANCE_LABELS.density[document.body.dataset.density || APPEARANCE_DEFAULTS.density] || ''}`);
|
|
|
|
setText('appearancePreviewStatus', `акцент ${$('accentIntensity')?.value || 100}%`);
|
|
|
|
setText('appearancePreviewStatus', `акцент ${$('accentIntensity')?.value || 100}%`);
|
|
|
|
for (const name of ['density', 'radius', 'motion', 'contrast']) setText(`${name}Preview`, APPEARANCE_LABELS[name]?.[document.body.dataset[name] || APPEARANCE_DEFAULTS[name]] || '');
|
|
|
|
for (const name of ['density', 'radius', 'motion', 'contrast', 'cardStyle']) setText(`${name}Preview`, APPEARANCE_LABELS[name]?.[document.body.dataset[name] || APPEARANCE_DEFAULTS[name]] || '');
|
|
|
|
const motion = document.body.dataset.motion || APPEARANCE_DEFAULTS.motion;
|
|
|
|
const motion = document.body.dataset.motion || APPEARANCE_DEFAULTS.motion;
|
|
|
|
const radius = document.body.dataset.radius || APPEARANCE_DEFAULTS.radius;
|
|
|
|
const radius = document.body.dataset.radius || APPEARANCE_DEFAULTS.radius;
|
|
|
|
setText('stylePreview', `${APPEARANCE_LABELS.radius[radius] || ''} · ${APPEARANCE_LABELS.motion[motion] || ''}`);
|
|
|
|
setText('stylePreview', `${APPEARANCE_LABELS.radius[radius] || ''} · ${APPEARANCE_LABELS.motion[motion] || ''}`);
|
|
|
|
|