добавление возможности создания своего цвета акцента
This commit is contained in:
parent
37220d7d17
commit
6aa5daf9ea
39
index.html
39
index.html
@ -173,6 +173,45 @@
|
||||
data-accent="amber"
|
||||
aria-label="Янтарный акцент"
|
||||
></button>
|
||||
<button
|
||||
class="accent-swatch accent-swatch-custom"
|
||||
type="button"
|
||||
data-accent="custom"
|
||||
aria-label="Свой градиент"
|
||||
title="Свой градиент"
|
||||
>
|
||||
<span class="accent-swatch-label">Свой</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Настройки пользовательского linear-gradient акцента. -->
|
||||
<div class="accent-custom-panel" data-accent-custom-panel hidden>
|
||||
<p class="accent-custom-title">Свой градиент</p>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Угол</span>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="360"
|
||||
step="1"
|
||||
value="135"
|
||||
data-custom-accent-angle
|
||||
/>
|
||||
<output data-custom-accent-angle-value>135°</output>
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Цвет от</span>
|
||||
<input type="color" value="#8b5cf6" data-custom-accent-from />
|
||||
</label>
|
||||
|
||||
<label class="accent-custom-row">
|
||||
<span>Цвет до</span>
|
||||
<input type="color" value="#22d3ee" data-custom-accent-to />
|
||||
</label>
|
||||
|
||||
<div class="accent-custom-preview" data-custom-accent-preview aria-hidden="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
115
script.js
115
script.js
@ -166,12 +166,16 @@ const defaultPreferences = {
|
||||
theme: window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark",
|
||||
view: "cards",
|
||||
accent: "violet",
|
||||
customAccentFrom: "#8b5cf6",
|
||||
customAccentTo: "#22d3ee",
|
||||
customAccentAngle: 135,
|
||||
showNumbers: false,
|
||||
showTreeTags: true,
|
||||
autoCollapseSearch: true,
|
||||
sidebarWidth: 390,
|
||||
};
|
||||
const availableViews = ["cards", "list", "compact-cards", "compact-list"];
|
||||
const availableAccents = ["violet", "cyan", "lime", "amber", "custom"];
|
||||
|
||||
// ============================================================================
|
||||
// Runtime state and DOM references
|
||||
@ -194,6 +198,12 @@ const settingsClose = document.querySelector("[data-settings-close]");
|
||||
const themeButtons = document.querySelectorAll("[data-theme]");
|
||||
const viewButtons = document.querySelectorAll("[data-view]");
|
||||
const accentButtons = document.querySelectorAll("[data-accent]");
|
||||
const accentCustomPanel = document.querySelector("[data-accent-custom-panel]");
|
||||
const customAccentFromInput = document.querySelector("[data-custom-accent-from]");
|
||||
const customAccentToInput = document.querySelector("[data-custom-accent-to]");
|
||||
const customAccentAngleInput = document.querySelector("[data-custom-accent-angle]");
|
||||
const customAccentAngleValue = document.querySelector("[data-custom-accent-angle-value]");
|
||||
const customAccentPreview = document.querySelector("[data-custom-accent-preview]");
|
||||
const showNumbersInput = document.querySelector("[data-show-numbers]");
|
||||
const showTreeTagsInput = document.querySelector("[data-show-tree-tags]");
|
||||
const autoCollapseSearchInput = document.querySelector("[data-auto-collapse-search]");
|
||||
@ -285,7 +295,7 @@ viewButtons.forEach((button) => {
|
||||
});
|
||||
});
|
||||
|
||||
// Выбор цветового акцента.
|
||||
// Выбор цветового акцента из пресетов или пользовательского градиента.
|
||||
accentButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
preferences.accent = button.dataset.accent;
|
||||
@ -294,6 +304,20 @@ accentButtons.forEach((button) => {
|
||||
});
|
||||
});
|
||||
|
||||
// Изменение параметров своего градиента сразу переключает accent на custom.
|
||||
function handleCustomAccentChange() {
|
||||
preferences.customAccentFrom = customAccentFromInput.value;
|
||||
preferences.customAccentTo = customAccentToInput.value;
|
||||
preferences.customAccentAngle = Number(customAccentAngleInput.value);
|
||||
preferences.accent = "custom";
|
||||
applyPreferences(preferences);
|
||||
savePreferences(preferences);
|
||||
}
|
||||
|
||||
customAccentFromInput.addEventListener("input", handleCustomAccentChange);
|
||||
customAccentToInput.addEventListener("input", handleCustomAccentChange);
|
||||
customAccentAngleInput.addEventListener("input", handleCustomAccentChange);
|
||||
|
||||
// Показывает/скрывает номера в секциях, группах и карточках.
|
||||
showNumbersInput.addEventListener("change", () => {
|
||||
preferences.showNumbers = showNumbersInput.checked;
|
||||
@ -718,8 +742,21 @@ function applyPreferences(nextPreferences) {
|
||||
nextPreferences.view = defaultPreferences.view;
|
||||
}
|
||||
|
||||
if (!availableAccents.includes(nextPreferences.accent)) {
|
||||
nextPreferences.accent = defaultPreferences.accent;
|
||||
}
|
||||
|
||||
nextPreferences.customAccentFrom = normalizeHexColor(nextPreferences.customAccentFrom) || defaultPreferences.customAccentFrom;
|
||||
nextPreferences.customAccentTo = normalizeHexColor(nextPreferences.customAccentTo) || defaultPreferences.customAccentTo;
|
||||
nextPreferences.customAccentAngle = clamp(
|
||||
Number(nextPreferences.customAccentAngle) || defaultPreferences.customAccentAngle,
|
||||
0,
|
||||
360,
|
||||
);
|
||||
|
||||
document.documentElement.dataset.theme = nextPreferences.theme;
|
||||
document.documentElement.dataset.accent = nextPreferences.accent;
|
||||
applyAccentVariables(nextPreferences);
|
||||
document.documentElement.style.setProperty(
|
||||
"--sidebar-width",
|
||||
`${clamp(Number(nextPreferences.sidebarWidth) || defaultPreferences.sidebarWidth, 260, 560)}px`,
|
||||
@ -731,11 +768,87 @@ function applyPreferences(nextPreferences) {
|
||||
showNumbersInput.checked = Boolean(nextPreferences.showNumbers);
|
||||
showTreeTagsInput.checked = Boolean(nextPreferences.showTreeTags);
|
||||
autoCollapseSearchInput.checked = Boolean(nextPreferences.autoCollapseSearch);
|
||||
customAccentFromInput.value = nextPreferences.customAccentFrom;
|
||||
customAccentToInput.value = nextPreferences.customAccentTo;
|
||||
customAccentAngleInput.value = String(nextPreferences.customAccentAngle);
|
||||
customAccentAngleValue.textContent = `${nextPreferences.customAccentAngle}°`;
|
||||
accentCustomPanel.hidden = nextPreferences.accent !== "custom";
|
||||
updateCustomAccentPreview(nextPreferences);
|
||||
setActiveButton(themeButtons, "theme", nextPreferences.theme);
|
||||
setActiveButton(viewButtons, "view", nextPreferences.view);
|
||||
setActiveButton(accentButtons, "accent", nextPreferences.accent);
|
||||
}
|
||||
|
||||
// Применяет CSS-переменные акцента для пресетов или пользовательского градиента.
|
||||
function applyAccentVariables(nextPreferences) {
|
||||
const root = document.documentElement;
|
||||
|
||||
if (nextPreferences.accent === "custom") {
|
||||
root.style.setProperty("--accent", nextPreferences.customAccentFrom);
|
||||
root.style.setProperty("--accent-2", nextPreferences.customAccentTo);
|
||||
root.style.setProperty("--accent-angle", `${nextPreferences.customAccentAngle}deg`);
|
||||
|
||||
const rgb = hexToRgb(nextPreferences.customAccentFrom);
|
||||
if (rgb) {
|
||||
root.style.setProperty("--accent-rgb", `${rgb.r}, ${rgb.g}, ${rgb.b}`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
root.style.removeProperty("--accent");
|
||||
root.style.removeProperty("--accent-2");
|
||||
root.style.removeProperty("--accent-rgb");
|
||||
root.style.removeProperty("--accent-angle");
|
||||
}
|
||||
|
||||
// Обновляет превью пользовательского градиента в настройках и на кнопке custom.
|
||||
function updateCustomAccentPreview(nextPreferences) {
|
||||
const gradient = `linear-gradient(${nextPreferences.customAccentAngle}deg, ${nextPreferences.customAccentFrom}, ${nextPreferences.customAccentTo})`;
|
||||
customAccentPreview.style.background = gradient;
|
||||
|
||||
const customSwatch = document.querySelector('[data-accent="custom"]');
|
||||
if (customSwatch) {
|
||||
customSwatch.style.setProperty("--custom-accent-from", nextPreferences.customAccentFrom);
|
||||
customSwatch.style.setProperty("--custom-accent-to", nextPreferences.customAccentTo);
|
||||
customSwatch.style.setProperty("--custom-accent-angle", `${nextPreferences.customAccentAngle}deg`);
|
||||
}
|
||||
}
|
||||
|
||||
// Приводит hex-цвет к формату #rrggbb для input[type="color"].
|
||||
function normalizeHexColor(value) {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = value.trim().toLowerCase();
|
||||
|
||||
if (/^#[0-9a-f]{6}$/.test(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
if (/^#[0-9a-f]{3}$/.test(normalized)) {
|
||||
return `#${normalized[1]}${normalized[1]}${normalized[2]}${normalized[2]}${normalized[3]}${normalized[3]}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Конвертирует hex в RGB для CSS-переменной --accent-rgb.
|
||||
function hexToRgb(value) {
|
||||
const hex = normalizeHexColor(value);
|
||||
|
||||
if (!hex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
r: Number.parseInt(hex.slice(1, 3), 16),
|
||||
g: Number.parseInt(hex.slice(3, 5), 16),
|
||||
b: Number.parseInt(hex.slice(5, 7), 16),
|
||||
};
|
||||
}
|
||||
|
||||
// Обновляет визуальное состояние группы кнопок.
|
||||
function setActiveButton(buttons, key, value) {
|
||||
buttons.forEach((button) => {
|
||||
|
||||
141
styles.css
141
styles.css
@ -20,6 +20,7 @@
|
||||
--accent: #8b5cf6;
|
||||
--accent-2: #22d3ee;
|
||||
--accent-rgb: 139, 92, 246;
|
||||
--accent-angle: 135deg;
|
||||
--card-x: 50%;
|
||||
--card-y: 50%;
|
||||
--sidebar-width: 390px;
|
||||
@ -273,7 +274,7 @@ html[data-show-tree-tags="false"] .tree-node small {
|
||||
border-radius: 4px;
|
||||
padding: 0 2px;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, var(--accent), var(--accent-2));
|
||||
background: linear-gradient(var(--accent-angle), var(--accent), var(--accent-2));
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
@ -494,7 +495,7 @@ html[data-show-tree-tags="false"] .tree-node small {
|
||||
|
||||
.toggle-row input:checked {
|
||||
border-color: rgba(var(--accent-rgb), 0.6);
|
||||
background: linear-gradient(135deg, var(--accent), var(--accent-2));
|
||||
background: linear-gradient(var(--accent-angle), var(--accent), var(--accent-2));
|
||||
}
|
||||
|
||||
.toggle-row input:checked::before {
|
||||
@ -556,12 +557,18 @@ html[data-show-tree-tags="false"] .tree-node small {
|
||||
}
|
||||
|
||||
.segmented.is-active {
|
||||
background: linear-gradient(135deg, var(--accent), var(--accent-2));
|
||||
background: linear-gradient(var(--accent-angle), var(--accent), var(--accent-2));
|
||||
color: white;
|
||||
}
|
||||
|
||||
.accent-group {
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
row-gap: 8px;
|
||||
}
|
||||
|
||||
.accent-group > span {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.accent-swatch {
|
||||
@ -594,6 +601,130 @@ html[data-show-tree-tags="false"] .tree-node small {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* Кнопка своего градиента: pill, пунктир, подпись и превию через CSS-переменные. */
|
||||
.accent-swatch-custom {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
width: auto;
|
||||
min-width: 58px;
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
border: 2px dashed rgba(255, 255, 255, 0.42);
|
||||
box-shadow: none;
|
||||
background:
|
||||
linear-gradient(45deg, rgba(255, 255, 255, 0.08) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, 0.08) 75%),
|
||||
linear-gradient(45deg, rgba(255, 255, 255, 0.08) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, 0.08) 75%);
|
||||
background-size: 8px 8px;
|
||||
background-position: 0 0, 4px 4px;
|
||||
}
|
||||
|
||||
html[data-theme="light"] .accent-swatch-custom {
|
||||
border-color: rgba(15, 23, 42, 0.28);
|
||||
background:
|
||||
linear-gradient(45deg, rgba(15, 23, 42, 0.06) 25%, transparent 25%, transparent 75%, rgba(15, 23, 42, 0.06) 75%),
|
||||
linear-gradient(45deg, rgba(15, 23, 42, 0.06) 25%, transparent 25%, transparent 75%, rgba(15, 23, 42, 0.06) 75%);
|
||||
background-size: 8px 8px;
|
||||
background-position: 0 0, 4px 4px;
|
||||
}
|
||||
|
||||
.accent-swatch-custom::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 2px;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(
|
||||
var(--custom-accent-angle, 135deg),
|
||||
var(--custom-accent-from, #8b5cf6),
|
||||
var(--custom-accent-to, #22d3ee)
|
||||
);
|
||||
}
|
||||
|
||||
.accent-swatch-label {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
color: white;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.04em;
|
||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.accent-swatch-custom.is-active {
|
||||
border-style: solid;
|
||||
border-color: var(--text);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* Панель настройки своего градиента появляется при data-accent="custom". */
|
||||
.accent-custom-panel {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 18px;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.accent-custom-panel[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.accent-custom-title {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.accent-custom-row {
|
||||
display: grid;
|
||||
grid-template-columns: 72px minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--muted);
|
||||
font-size: 0.84rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.accent-custom-row input[type="range"] {
|
||||
width: 100%;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
|
||||
.accent-custom-row input[type="color"] {
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
padding: 2px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
background: var(--panel);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.accent-custom-row:has(input[type="color"]) {
|
||||
grid-template-columns: 72px 1fr;
|
||||
}
|
||||
|
||||
.accent-custom-row output {
|
||||
min-width: 3.2rem;
|
||||
color: var(--accent);
|
||||
font-size: 0.82rem;
|
||||
font-weight: 800;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.accent-custom-preview {
|
||||
height: 36px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(var(--accent-angle), var(--accent), var(--accent-2));
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
08. File-Tree Navigation
|
||||
--------------------------------------------------------------------------
|
||||
@ -1038,7 +1169,7 @@ html[data-show-tree-tags="false"] .tree-node small {
|
||||
.nav-card.is-hovered .arrow,
|
||||
.nav-card:focus-visible .arrow {
|
||||
transform: translateX(4px);
|
||||
background: linear-gradient(135deg, var(--accent), var(--accent-2));
|
||||
background: linear-gradient(var(--accent-angle), var(--accent), var(--accent-2));
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user