From 301e5d0b272dbcde4ac270a473945a08d429e1b8 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 21 Aug 2026 03:43:58 +0300 Subject: [PATCH] feat: improve creator cabinet navigation --- cmd/creator-web/server_test.go | 3 +++ cmd/creator-web/static/assets/console.css | 12 ++++++--- cmd/creator-web/static/assets/console.js | 32 +++++++++++++++++++++++ cmd/creator-web/static/index.html | 3 ++- 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/cmd/creator-web/server_test.go b/cmd/creator-web/server_test.go index 1d90693..c0da3a2 100644 --- a/cmd/creator-web/server_test.go +++ b/cmd/creator-web/server_test.go @@ -233,6 +233,9 @@ func TestConsoleAndSecurityHeaders(t *testing.T) { response := httptest.NewRecorder() handler.ServeHTTP(response, request) if response.Code != http.StatusOK || !strings.Contains(response.Body.String(), "Домовой") || + !strings.Contains(response.Body.String(), "К содержимому") || + !strings.Contains(response.Body.String(), "aria-current=\"page\"") || + !strings.Contains(response.Body.String(), "section-nav-link") || !strings.Contains(response.Body.String(), "satisfaction-dialog") || !strings.Contains(response.Body.String(), "Подтверждаю переход") || !strings.Contains(response.Body.String(), "action-history") || !strings.Contains(response.Body.String(), "Журнал действий") || !strings.Contains(response.Body.String(), "advisory-form") || !strings.Contains(response.Body.String(), "Спросить Домового") || diff --git a/cmd/creator-web/static/assets/console.css b/cmd/creator-web/static/assets/console.css index 3cd4feb..68791d8 100644 --- a/cmd/creator-web/static/assets/console.css +++ b/cmd/creator-web/static/assets/console.css @@ -21,11 +21,13 @@ * { box-sizing: border-box; } [hidden] { display: none !important; } -html { background: var(--base); color: var(--ink); font-family: var(--sans); } +html { background: var(--base); color: var(--ink); font-family: var(--sans); scroll-behavior: smooth; } body { margin: 0; min-width: 320px; min-height: 100vh; background: radial-gradient(circle at 73% -10%, rgba(83, 123, 101, .13), transparent 38%), var(--base); } button, input, textarea { font: inherit; } button { color: inherit; } -button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px solid var(--signal-bright); outline-offset: 3px; } +button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-visible { outline: 2px solid var(--signal-bright); outline-offset: 3px; } +.skip-link { position: fixed; top: 10px; left: 10px; z-index: 30; padding: 10px 13px; color: #11150f; background: var(--signal-bright); font: 600 10px/1 var(--mono); text-decoration: none; transform: translateY(-160%); transition: transform .18s; } +.skip-link:focus { transform: translateY(0); } .grain { position: fixed; inset: 0; pointer-events: none; opacity: .045; z-index: 10; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 160 160' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.16'/%3E%3C/svg%3E"); } .eyebrow, .rail-index { margin: 0 0 12px; color: var(--signal); font: 600 11px/1 var(--mono); letter-spacing: .18em; text-transform: uppercase; } @@ -92,7 +94,7 @@ button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px .secondary-button { height: 42px; margin-top: 20px; color: #11150f; background: var(--signal); font-size: 12px; } .section-nav { display: grid; gap: 3px; margin-top: 4px; } .section-nav-link { display: grid; grid-template-columns: 28px 1fr; gap: 2px 10px; padding: 11px 10px; border-left: 2px solid transparent; color: var(--muted); text-decoration: none; transition: background .18s, border-color .18s, color .18s; } -.section-nav-link:hover, .section-nav-link:focus-visible, .section-nav-link.active { border-left-color: var(--signal); color: var(--ink); background: rgba(201,168,95,.07); outline: 0; } +.section-nav-link:hover, .section-nav-link:focus-visible, .section-nav-link.active, .section-nav-link[aria-current="page"] { border-left-color: var(--signal); color: var(--ink); background: rgba(201,168,95,.07); outline: 0; } .section-nav-link span { grid-row: 1 / span 2; color: var(--signal); font: 9px/1.4 var(--mono); } .section-nav-link b { font: 600 11px/1.2 var(--sans); } .section-nav-link small { color: var(--faint); font: 9px/1.2 var(--mono); } @@ -101,6 +103,7 @@ button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px .session-stamp strong { overflow: hidden; color: var(--muted); font: 10px/1.4 var(--mono); text-overflow: ellipsis; } .workspace { grid-column: 2; grid-row: 2; width: min(100%, 1740px); justify-self: start; padding: 42px clamp(24px, 3vw, 48px) 64px; min-width: 0; } +.workspace > section { scroll-margin-top: 94px; } .workspace-head { display: flex; align-items: end; justify-content: space-between; gap: 24px; margin-bottom: 34px; } .workspace-head h1 { margin: 0; font: 400 clamp(34px, 4vw, 50px)/1 var(--serif); letter-spacing: -.035em; } .last-update { color: var(--faint); font: 10px/1.4 var(--mono); text-align: right; } @@ -353,6 +356,9 @@ html[data-reduced-motion="true"] *, html[data-reduced-motion="true"] *::before, .session-stamp { margin-top: 8px; grid-template-columns: auto 1fr; align-items: center; } .session-stamp span { margin: 0; } .workspace { padding: 30px 16px 50px; } + .section-nav { display: flex; gap: 6px; margin: 10px -18px -24px; padding: 10px 18px; overflow-x: auto; border-top: 1px solid var(--line-soft); background: #0e1512; scrollbar-width: thin; } + .section-nav-link { min-width: 142px; padding: 10px; border-left: 0; border-bottom: 2px solid transparent; } + .section-nav-link:hover, .section-nav-link:focus-visible, .section-nav-link.active, .section-nav-link[aria-current="page"] { border-left-color: transparent; border-bottom-color: var(--signal); } .workspace-head { align-items: start; flex-direction: column; } .last-update { text-align: left; } .signal-strip { grid-template-columns: 1fr; } diff --git a/cmd/creator-web/static/assets/console.js b/cmd/creator-web/static/assets/console.js index 2fbf0a2..707100d 100644 --- a/cmd/creator-web/static/assets/console.js +++ b/cmd/creator-web/static/assets/console.js @@ -19,6 +19,38 @@ let lastAuthorizedActionID = ""; let advisoryPolicy = {Mode: "deterministic", ExternalEgress: false, AcknowledgementRequired: false}; let uiSettings = {density: "comfortable", refresh: "off", motion: false}; +const sectionNavLinks = Array.from(document.querySelectorAll(".section-nav-link")); +const cabinetSections = ["overview", "diagnostics", "operations", "settings"] + .map((id) => byId(id)) + .filter(Boolean); + +function setActiveSection(id) { + sectionNavLinks.forEach((link) => { + const active = link.getAttribute("href") === `#${id}`; + link.classList.toggle("active", active); + if (active) link.setAttribute("aria-current", "page"); + else link.removeAttribute("aria-current"); + }); +} + +if ("IntersectionObserver" in window) { + const sectionObserver = new IntersectionObserver((entries) => { + const visible = entries.filter((entry) => entry.isIntersecting) + .sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0]; + if (visible) setActiveSection(visible.target.id); + }, {rootMargin: "-18% 0px -64% 0px", threshold: [0, .25, .6, 1]}); + cabinetSections.forEach((section) => sectionObserver.observe(section)); +} + +sectionNavLinks.forEach((link) => link.addEventListener("click", () => { + const id = link.getAttribute("href").slice(1); + setActiveSection(id); +})); +window.addEventListener("hashchange", () => { + const id = window.location.hash.slice(1); + if (cabinetSections.some((section) => section.id === id)) setActiveSection(id); +}); + function setText(id, value, fallback = "—") { byId(id).textContent = value === undefined || value === null || value === "" ? fallback : String(value); } diff --git a/cmd/creator-web/static/index.html b/cmd/creator-web/static/index.html index ac461e5..0143a86 100644 --- a/cmd/creator-web/static/index.html +++ b/cmd/creator-web/static/index.html @@ -9,6 +9,7 @@ +
@@ -81,7 +82,7 @@