diff --git a/index.html b/index.html index 8175617..fbc0c09 100644 --- a/index.html +++ b/index.html @@ -23,15 +23,23 @@
+ +

Infrastructure map

Панель экосистемы

Единая точка входа в пользовательские, административные, мониторинговые и инфраструктурные интерфейсы.

-
-
-
- Тема - - -
+
+
+ 0 +

раздела

+
+
+ 0 +

подразделов

+
+
+ 0 +

элементов

+
+
+ 0 +

найдено

+
+
-
- Вид - - - - -
- -
- Акцент - - - - -
-
-
- 0 -

раздела

+
+
+
+
+

View settings

+

Настройки

+
+
-
- 0 -

подразделов

+ +
+ Тема + +
-
- 0 -

элементов

+ +
+ Вид + + + +
-
- 0 -

найдено

+ + + +
+ Акцент + + + +
-
+
diff --git a/script.js b/script.js index 54fb7a3..5d0141a 100644 --- a/script.js +++ b/script.js @@ -136,15 +136,21 @@ const defaultPreferences = { theme: window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark", view: "cards", accent: "violet", + showNumbers: false, }; const availableViews = ["cards", "list", "compact-cards", "compact-list"]; const preferences = { ...defaultPreferences, ...readPreferences() }; const catalog = document.querySelector("[data-catalog-root]"); const searchInput = document.querySelector("[data-search-input]"); +const settingsToggle = document.querySelector("[data-settings-toggle]"); +const settingsPanel = document.querySelector("[data-settings-panel]"); +const settingsOverlay = document.querySelector("[data-settings-overlay]"); +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 showNumbersInput = document.querySelector("[data-show-numbers]"); const emptyTemplate = document.querySelector("#empty-template"); const totals = calculateTotals(ecosystemSections); @@ -157,6 +163,33 @@ renderCatalog(); searchInput.addEventListener("input", renderCatalog); +settingsToggle.addEventListener("click", () => { + const isOpen = settingsPanel.classList.toggle("is-open"); + settingsOverlay.classList.toggle("is-open", isOpen); + settingsToggle.setAttribute("aria-expanded", String(isOpen)); +}); + +settingsClose.addEventListener("click", closeSettingsPanel); +settingsOverlay.addEventListener("click", closeSettingsPanel); + +document.addEventListener("keydown", (event) => { + if (event.key === "Escape") { + closeSettingsPanel(); + } +}); + +document.addEventListener("click", (event) => { + if (!settingsPanel.classList.contains("is-open")) { + return; + } + + if (settingsPanel.contains(event.target) || settingsToggle.contains(event.target)) { + return; + } + + closeSettingsPanel(); +}); + themeButtons.forEach((button) => { button.addEventListener("click", () => { preferences.theme = button.dataset.theme; @@ -181,6 +214,12 @@ accentButtons.forEach((button) => { }); }); +showNumbersInput.addEventListener("change", () => { + preferences.showNumbers = showNumbersInput.checked; + applyPreferences(preferences); + savePreferences(preferences); +}); + function createItem(number, title, type, tags = [], url = "#") { return { number, title, type, tags, url }; } @@ -227,36 +266,44 @@ function renderCatalog() { } function createSectionElement(section) { - const sectionElement = document.createElement("article"); + const sectionElement = document.createElement("details"); sectionElement.className = "catalog-section"; + sectionElement.open = true; sectionElement.innerHTML = ` -
+
-

${section.number} раздел

+

${section.number} раздел

${section.title}

${section.description}

- 0 элементов -
+ + 0 элементов + + +
`; return sectionElement; } function createGroupElement(group, items) { - const groupElement = document.createElement("section"); + const groupElement = document.createElement("details"); groupElement.className = "catalog-group"; + groupElement.open = true; const cards = items.map(createCardElement); groupElement.innerHTML = ` -
+

${group.number}

${group.title}

${group.description}

- ${items.length} -
+ + ${items.length} + + + `; const grid = document.createElement("div"); @@ -364,6 +411,8 @@ function applyPreferences(nextPreferences) { document.documentElement.dataset.accent = nextPreferences.accent; catalog.classList.remove("cards-view", "list-view", "compact-cards-view", "compact-list-view"); catalog.classList.add(`${nextPreferences.view}-view`); + document.documentElement.dataset.showNumbers = String(Boolean(nextPreferences.showNumbers)); + showNumbersInput.checked = Boolean(nextPreferences.showNumbers); setActiveButton(themeButtons, "theme", nextPreferences.theme); setActiveButton(viewButtons, "view", nextPreferences.view); setActiveButton(accentButtons, "accent", nextPreferences.accent); @@ -408,3 +457,9 @@ function readPreferences() { function savePreferences(nextPreferences) { localStorage.setItem(STORAGE_KEY, JSON.stringify(nextPreferences)); } + +function closeSettingsPanel() { + settingsPanel.classList.remove("is-open"); + settingsOverlay.classList.remove("is-open"); + settingsToggle.setAttribute("aria-expanded", "false"); +} diff --git a/styles.css b/styles.css index 08eeb77..cf411d3 100644 --- a/styles.css +++ b/styles.css @@ -104,20 +104,16 @@ a { .shell { width: min(1320px, calc(100% - 32px)); margin: 0 auto; - padding: 42px 0; + padding: 28px 0; } .hero { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(330px, 460px); - gap: 24px; - align-items: stretch; + display: block; margin-bottom: 18px; } .hero-content, .control-panel, -.overview, .catalog-section, .empty-state { border: 1px solid var(--border); @@ -127,23 +123,25 @@ a { } .hero-content { - min-height: 330px; + min-height: 210px; display: flex; flex-direction: column; - justify-content: flex-end; - padding: clamp(28px, 5vw, 56px); - border-radius: 38px; + justify-content: center; + gap: 10px; + padding: clamp(18px, 3vw, 28px); + border-radius: 26px; position: relative; - overflow: hidden; + overflow: visible; } .hero-content::before { content: ""; position: absolute; inset: 0; + border-radius: inherit; background: - linear-gradient(120deg, rgba(var(--accent-rgb), 0.28), transparent 34%), - url("data:image/svg+xml,%3Csvg width='140' height='140' viewBox='0 0 140 140' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' stroke='white' stroke-opacity='.09'%3E%3Cpath d='M0 70h140M70 0v140'/%3E%3Ccircle cx='70' cy='70' r='38'/%3E%3C/g%3E%3C/svg%3E"); + linear-gradient(120deg, rgba(var(--accent-rgb), 0.25), transparent 34%), + url("data:image/svg+xml,%3Csvg width='96' height='84' viewBox='0 0 96 84' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' stroke='white' stroke-opacity='.10' stroke-width='1.4'%3E%3Cpath d='M24 2 46 14v25L24 52 2 39V14L24 2Z'/%3E%3Cpath d='M72 32 94 44v25L72 82 50 69V44l22-12Z'/%3E%3C/g%3E%3C/svg%3E"); mask-image: linear-gradient(to bottom, black, transparent 88%); pointer-events: none; } @@ -153,6 +151,7 @@ a { } .eyebrow, +.section-number, .group-number, .item-number { margin: 0; @@ -165,30 +164,129 @@ a { h1 { max-width: 840px; - margin: 14px 0 18px; - font-size: clamp(2.6rem, 8vw, 6.4rem); - line-height: 0.9; - letter-spacing: -0.08em; + margin: 4px 0 0; + font-size: clamp(1.9rem, 4.4vw, 3.7rem); + line-height: 0.98; + letter-spacing: -0.07em; } .lead { max-width: 740px; margin: 0; color: var(--muted); - font-size: clamp(1rem, 2vw, 1.2rem); - line-height: 1.7; + font-size: clamp(0.9rem, 1.25vw, 1rem); + line-height: 1.5; +} + +html:not([data-show-numbers="true"]) .section-number, +html:not([data-show-numbers="true"]) .group-number, +html:not([data-show-numbers="true"]) .item-number { + display: none; } .control-panel { + position: fixed; + z-index: 1001; + top: 24px; + right: 24px; + width: min(460px, calc(100% - 48px)); + max-height: calc(100vh - 48px); + overflow: auto; display: grid; gap: 14px; padding: 18px; - border-radius: 28px; + border-radius: 24px; + opacity: 0; + visibility: hidden; + pointer-events: none; + transform: translateY(-8px) scale(0.98); + transform-origin: top right; + transition: + opacity 180ms ease, + visibility 180ms ease, + transform 180ms ease; +} + +.settings-overlay { + position: fixed; + inset: 0; + z-index: 1000; + background: rgba(2, 6, 23, 0.42); + opacity: 0; + visibility: hidden; + pointer-events: none; + backdrop-filter: blur(4px); + transition: opacity 180ms ease, visibility 180ms ease; +} + +.settings-overlay.is-open { + opacity: 1; + visibility: visible; + pointer-events: auto; +} + +.control-panel.is-open { + opacity: 1; + visibility: visible; + pointer-events: auto; + transform: translateY(0) scale(1); +} + +.settings-button, +.settings-close { + border: 1px solid var(--border); + cursor: pointer; + color: var(--text); + background: var(--surface); + transition: transform 160ms ease, background 160ms ease, border-color 160ms ease; +} + +.settings-button { + position: absolute; + z-index: 21; + top: 20px; + right: 20px; + width: 40px; + height: 40px; + display: grid; + place-items: center; + border-radius: 999px; + font-size: 1.18rem; +} + +.settings-button:hover, +.settings-close:hover { + transform: translateY(-1px); + border-color: rgba(var(--accent-rgb), 0.5); + background: rgba(var(--accent-rgb), 0.12); +} + +.settings-panel-head { + display: flex; + justify-content: space-between; + gap: 14px; + align-items: flex-start; +} + +.settings-panel-head h2 { + margin: 4px 0 0; + font-size: 1.35rem; + letter-spacing: -0.04em; +} + +.settings-close { + width: 34px; + height: 34px; + flex: 0 0 auto; + border-radius: 999px; + font-size: 1.25rem; + line-height: 1; } .search-field { display: grid; - gap: 8px; + gap: 6px; + max-width: 720px; } .search-field span, @@ -200,9 +298,9 @@ h1 { .search-field input { width: 100%; - min-height: 48px; + min-height: 42px; border: 1px solid var(--border); - border-radius: 16px; + border-radius: 14px; background: var(--surface); color: var(--text); outline: none; @@ -216,6 +314,66 @@ h1 { background: var(--panel-strong); } +.toggle-row { + display: flex; + justify-content: space-between; + gap: 14px; + align-items: center; + min-height: 54px; + padding: 10px 12px; + border: 1px solid var(--border); + border-radius: 18px; + background: var(--surface); +} + +.toggle-row span { + display: grid; + gap: 2px; +} + +.toggle-row strong { + font-size: 0.9rem; +} + +.toggle-row small { + color: var(--muted); + font-weight: 700; +} + +.toggle-row input { + appearance: none; + width: 48px; + height: 28px; + flex: 0 0 auto; + border: 1px solid var(--border); + border-radius: 999px; + background: rgba(148, 163, 184, 0.26); + cursor: pointer; + position: relative; + transition: background 160ms ease, border-color 160ms ease; +} + +.toggle-row input::before { + content: ""; + position: absolute; + top: 3px; + left: 3px; + width: 20px; + height: 20px; + border-radius: 999px; + background: white; + transition: transform 160ms ease; +} + +.toggle-row input:checked { + border-color: rgba(var(--accent-rgb), 0.6); + background: linear-gradient(135deg, var(--accent), var(--accent-2)); +} + +.toggle-row input:checked::before { + transform: translateX(20px); +} + .control-group { display: flex; align-items: center; @@ -297,28 +455,34 @@ h1 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; - margin-bottom: 18px; - border-radius: 26px; + max-width: 620px; + margin-top: 0; + border: 1px solid var(--border); + border-radius: 16px; + background: rgba(255, 255, 255, 0.035); overflow: hidden; } .overview div { - padding: 18px 20px; + padding: 8px 10px; background: rgba(255, 255, 255, 0.035); } .overview span { display: block; - font-size: clamp(1.8rem, 4vw, 2.8rem); + font-size: clamp(1rem, 1.8vw, 1.35rem); font-weight: 850; line-height: 1; letter-spacing: -0.06em; } .overview p { - margin: 8px 0 0; + margin: 4px 0 0; color: var(--muted); font-weight: 700; + font-size: 0.68rem; + letter-spacing: 0.02em; + text-transform: uppercase; } .catalog { @@ -331,6 +495,22 @@ h1 { overflow: hidden; } +.catalog-section, +.catalog-group { + display: block; +} + +.catalog-section > summary, +.catalog-group > summary { + list-style: none; + cursor: pointer; +} + +.catalog-section > summary::-webkit-details-marker, +.catalog-group > summary::-webkit-details-marker { + display: none; +} + .section-head { display: flex; justify-content: space-between; @@ -343,6 +523,14 @@ h1 { rgba(255, 255, 255, 0.035); } +.section-meta, +.group-meta { + display: inline-flex; + align-items: center; + gap: 8px; + flex: 0 0 auto; +} + .section-head h2, .group-head h3, .nav-card h4, @@ -357,7 +545,7 @@ h1 { font-size: clamp(1.6rem, 4vw, 2.6rem); } -.section-head p:not(.eyebrow), +.section-head p:not(.section-number), .group-head p:not(.group-number) { max-width: 760px; margin: 8px 0 0; @@ -376,6 +564,24 @@ h1 { font-weight: 800; } +.collapse-indicator { + width: 34px; + height: 34px; + display: grid; + place-items: center; + border: 1px solid var(--border); + border-radius: 999px; + background: var(--surface); + color: var(--accent); + font-weight: 900; + transition: transform 160ms ease; +} + +.catalog-section:not([open]) > .section-head .collapse-indicator, +.catalog-group:not([open]) > .group-head .collapse-indicator { + transform: rotate(-90deg); +} + .section-groups { display: grid; gap: 18px; @@ -402,7 +608,7 @@ h1 { font-size: 1.22rem; } -.group-head > span { +.group-count { width: 36px; height: 36px; display: grid; @@ -589,8 +795,8 @@ h1 { padding: 18px 20px; } -.catalog.compact-cards-view .section-head p:not(.eyebrow), -.catalog.compact-list-view .section-head p:not(.eyebrow), +.catalog.compact-cards-view .section-head p:not(.section-number), +.catalog.compact-list-view .section-head p:not(.section-number), .catalog.compact-cards-view .group-head p:not(.group-number), .catalog.compact-list-view .group-head p:not(.group-number), .catalog.compact-cards-view .tags, @@ -681,10 +887,6 @@ h1 { } @media (max-width: 1120px) { - .hero { - grid-template-columns: 1fr; - } - .items-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } @@ -701,8 +903,9 @@ h1 { } .hero-content { - min-height: 300px; - border-radius: 28px; + min-height: auto; + padding: 22px 16px; + border-radius: 24px; } .control-panel, @@ -711,6 +914,13 @@ h1 { border-radius: 24px; } + .control-panel { + top: 12px; + right: 12px; + width: calc(100vw - 24px); + max-height: calc(100vh - 24px); + } + .control-group { flex-wrap: wrap; }