From 3da8f19b94eeed5668444d4d847e7d87979247b9 Mon Sep 17 00:00:00 2001 From: Gleb Gorelov Date: Sat, 3 Oct 2026 00:03:36 +0400 Subject: [PATCH 01/75] Add design spec for the docs UI restyle --- .../2026-10-03-docs-ui-restyle-design.md | 309 ++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 docs/superpowers/specs/2026-10-03-docs-ui-restyle-design.md diff --git a/docs/superpowers/specs/2026-10-03-docs-ui-restyle-design.md b/docs/superpowers/specs/2026-10-03-docs-ui-restyle-design.md new file mode 100644 index 00000000..bc7546d2 --- /dev/null +++ b/docs/superpowers/specs/2026-10-03-docs-ui-restyle-design.md @@ -0,0 +1,309 @@ +# Docs UI restyle: design + +Branch: `feature/ui-restyle`, created from `release_3_1`. This document is temporary: delete it before the branch is merged. + +## Goal + +Give the documentation site clear boundaries between page regions, a visible focus indicator on every control, support for forced colors mode, and a layer of CSS custom properties that the dark theme (a separate, later PR) can build on by changing values only. The site stays a documentation site: no marketing layout, no decoration that competes with the content. + +## Findings that drive the design + +These come from the live site and the UI bundle as of 2026-10-02. + +- The UI bundle is not pinned. Both playbooks use the `antora-ui-default` `master` snapshot, so every CI build takes whatever upstream has. The bundle of a November 2023 local build and the current one (built 2026-07-16) differ in explore panel spacing, breadcrumb line height, lead paragraph size, checklist indent and image alignment. Nobody reviewed those changes. +- The published `site.css` has no custom properties. The default UI sources use them (`src/css/vars.css`), but the production build resolves them (`postcssVar({ preserve: preview })`). +- Styling is spread over the upstream `site.css`, `overrides.css`, `search.css`, `feedback-form.css`, `dropdown-menu.css` and an inline ` \ No newline at end of file + + + From bc03f8d3ee6c165c7ccf63a85b8396faeee7f911 Mon Sep 17 00:00:00 2001 From: Gleb Gorelov Date: Sat, 3 Oct 2026 02:54:22 +0400 Subject: [PATCH 11/75] Keep the event banner styles above the navbar rules --- content/supplemental/css/site.css | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/content/supplemental/css/site.css b/content/supplemental/css/site.css index c9aae9bd..c3dbc2d3 100644 --- a/content/supplemental/css/site.css +++ b/content/supplemental/css/site.css @@ -2595,7 +2595,9 @@ footer.footer { /* ---- Event banner (markup added for events: #jmix-banner.jmix-banner in the header) ---- */ -.jmix-banner { +/* the .navbar-brand prefix ties these rules with part 1's .navbar-brand .navbar-item rules, so source order decides */ + +.navbar-brand .jmix-banner { display: none; flex: 1 1 auto; align-items: center; @@ -2612,7 +2614,7 @@ footer.footer { font-size: 0.85rem; } -.jmix-banner-content { +.navbar-brand .jmix-banner-content { flex: 1 1 auto; min-width: 0; line-height: 1.2; @@ -2620,13 +2622,13 @@ footer.footer { white-space: normal; } -.jmix-banner-link, -.jmix-banner-link:visited { +.navbar-brand .jmix-banner-link, +.navbar-brand .jmix-banner-link:visited { color: var(--banner-text); text-decoration: underline; } -.jmix-banner-close-button { +.navbar-brand .jmix-banner-close-button { margin-left: 1rem; border: none; background: none; @@ -2637,33 +2639,34 @@ footer.footer { } @media screen and (max-width: 1400px) { - .jmix-banner { + .navbar-brand .jmix-banner { font-size: 0.8rem; } } @media screen and (max-width: 1250px) { - .jmix-banner { + .navbar-brand .jmix-banner { margin-left: 1rem; padding: 0.2rem 0.5rem; font-size: 0.72rem; } - .jmix-banner-close-button { + .navbar-brand .jmix-banner-close-button { margin-left: 0.5rem; font-size: 1rem; } } @media screen and (max-width: 1150px) { - .jmix-banner { + .navbar-brand .jmix-banner { font-size: 0.65rem; } } @media screen and (max-width: 1080px) { - .jmix-banner { - display: none; + .navbar-brand .jmix-banner { + /* event scripts show the banner with an inline style; only !important hides it on narrow screens */ + display: none !important; } } From 668072979f2988c49299fd7d92c93458bc04db26 Mon Sep 17 00:00:00 2001 From: Gleb Gorelov Date: Sat, 3 Oct 2026 03:04:52 +0400 Subject: [PATCH 12/75] Add Task 5 review follow-ups to the UI restyle plan --- docs/superpowers/plans/2026-10-03-docs-ui-restyle.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/superpowers/plans/2026-10-03-docs-ui-restyle.md b/docs/superpowers/plans/2026-10-03-docs-ui-restyle.md index 73050e20..aa52771c 100644 --- a/docs/superpowers/plans/2026-10-03-docs-ui-restyle.md +++ b/docs/superpowers/plans/2026-10-03-docs-ui-restyle.md @@ -3665,6 +3665,17 @@ footer.footer { } ``` +Two fixes in existing part 2 sections, found in the Task 5 review: + +- In "Content additions", the live demo hover keeps its label color (`.doc a:hover` recolored it before): change the rule to + ```css + a.live-demo-btn:hover { + background-color: var(--live-demo-background-hover); + color: var(--live-demo-text); + } + ``` +- In "Event banner", make the section comment state the markup contract: `/* ---- Event banner: #jmix-banner.jmix-banner, a .navbar-item inside .navbar-brand after the version dropdown, shown by its event script with an inline style ---- */`. + - [ ] **Step 3: Rewrite `feedback-form.css`** ```css From ade0aadd3a941a269237213e5793a3f67bd4536f Mon Sep 17 00:00:00 2001 From: Gleb Gorelov Date: Sat, 3 Oct 2026 03:24:45 +0400 Subject: [PATCH 13/75] Restyle the header and the search field --- content/supplemental/css/dropdown-menu.css | 151 ++++++----- content/supplemental/css/search.css | 252 ++++++++---------- content/supplemental/css/site.css | 100 +++++++ content/supplemental/img/git-icon.svg | 3 - content/supplemental/img/icons/addon.svg | 1 + content/supplemental/img/icons/caution.svg | 1 + content/supplemental/img/icons/chevron.svg | 1 + content/supplemental/img/icons/copy.svg | 1 + content/supplemental/img/icons/edit.svg | 1 + content/supplemental/img/icons/home.svg | 1 + content/supplemental/img/icons/important.svg | 1 + content/supplemental/img/icons/note.svg | 1 + content/supplemental/img/icons/search.svg | 1 + content/supplemental/img/icons/tip.svg | 1 + content/supplemental/img/icons/warning.svg | 1 + .../img/img/feedback-form__thumb-up.svg | 3 - .../img/jmix-ai-assistant-icon.svg | 15 -- content/supplemental/img/jmix-icon.svg | 14 - content/supplemental/img/warning-icon.svg | 5 - .../supplemental/partials/header-content.hbs | 75 +++--- tools/ui-audit.mjs | 11 + 21 files changed, 348 insertions(+), 292 deletions(-) delete mode 100644 content/supplemental/img/git-icon.svg create mode 100644 content/supplemental/img/icons/addon.svg create mode 100644 content/supplemental/img/icons/caution.svg create mode 100644 content/supplemental/img/icons/chevron.svg create mode 100644 content/supplemental/img/icons/copy.svg create mode 100644 content/supplemental/img/icons/edit.svg create mode 100644 content/supplemental/img/icons/home.svg create mode 100644 content/supplemental/img/icons/important.svg create mode 100644 content/supplemental/img/icons/note.svg create mode 100644 content/supplemental/img/icons/search.svg create mode 100644 content/supplemental/img/icons/tip.svg create mode 100644 content/supplemental/img/icons/warning.svg delete mode 100644 content/supplemental/img/img/feedback-form__thumb-up.svg delete mode 100644 content/supplemental/img/jmix-ai-assistant-icon.svg delete mode 100644 content/supplemental/img/jmix-icon.svg delete mode 100644 content/supplemental/img/warning-icon.svg diff --git a/content/supplemental/css/dropdown-menu.css b/content/supplemental/css/dropdown-menu.css index 3ead5d3d..2fe1caab 100644 --- a/content/supplemental/css/dropdown-menu.css +++ b/content/supplemental/css/dropdown-menu.css @@ -1,93 +1,106 @@ -/* Same as for `.navbar-brand .navbar-item:first-child` */ +/* Version menu in the header. Behavior: js/dropdown-menu.js. */ + .navbar-item-dropdown { - align-self: center; - padding: 0; - font-size: 1rem; - line-height: 1; + align-self: center; + padding: 0; + font-size: 1rem; + line-height: 1; } -@media screen and (max-width: 420px) { - .navbar-item-dropdown { - font-size: .75rem; - } - - .version-dropdown-icon { - width: 0.5rem; - height: 0.5rem; - } - - .version-dropdown-menu { - padding: 0.5rem 0; - margin: 0.25rem 0; - } - - .version-dropdown-item { - padding: 0.2rem .75rem; - } +.version-dropdown-toggle { + position: relative; + display: flex; + gap: 0.4rem; + align-items: center; + padding: 0.35rem 0.55rem 0.35rem 0.65rem; + border: 0; + border-radius: var(--radius-md); + background: var(--color-accent-tint); + color: var(--color-accent); + font: inherit; + font-size: 0.8rem; + font-weight: 600; + line-height: inherit; } -@media screen and (max-width: 340px) { - .navbar-item.navbar-item-dropdown { - display: none; - } +.version-dropdown-toggle:hover { + background: var(--color-accent-line); } -.version-dropdown-toggle { - color: inherit; - background: transparent; - border: none; - outline: none; - line-height: inherit; - padding: 0; - position: relative; - display: flex; - align-items: center; +.version-dropdown-icon { + width: 0.6rem; + height: 0.6rem; + color: inherit; } .version-dropdown-toggle.opened .version-dropdown-icon { - transform: rotate(180deg); + transform: rotate(180deg); } -.version-dropdown-icon { - color: inherit; - width: 0.75rem; - height: 0.75rem; - margin-left: 0.5rem; - transition: all .1s ease-in; +@media (prefers-reduced-motion: no-preference) { + .version-dropdown-icon { + transition: transform 0.1s ease-in; + } } .version-dropdown-menu { - position: absolute; - z-index: 1000; - display: none; - padding: 0.75rem 0; - margin: 0.5rem 0; - text-align: left; - list-style: none; - background-color: var(--color-surface); - background-clip: padding-box; - border: 1px solid var(--color-line); - border-radius: 0.25rem; + position: absolute; + z-index: 1000; + display: none; + min-width: 10rem; + margin: 0.5rem 0 0; + padding: 0.375rem; + border: 1px solid var(--color-line); + border-radius: var(--radius-lg); + background-color: var(--color-surface); + box-shadow: var(--shadow-menu); + list-style: none; + text-align: left; } .version-dropdown-menu.opened { - display: block; + display: block; } .version-dropdown-item { - display: block; - width: 100%; - padding: 0.25rem 1rem; - clear: both; - font-weight: 400; - color: var(--color-text); - text-align: inherit; - text-decoration: none; - white-space: nowrap; - background-color: transparent; - border: 0; + display: block; + padding: 0.45rem 0.625rem; + border-radius: 5px; + color: var(--color-text); + font-size: 0.8rem; + font-weight: 400; + text-decoration: none; + white-space: nowrap; } .version-dropdown-item:hover { - text-decoration: underline; -} \ No newline at end of file + background: var(--color-surface-hover); + text-decoration: none; +} + +@media screen and (max-width: 420px) { + .navbar-item-dropdown { + font-size: 0.75rem; + } + + .version-dropdown-menu { + margin: 0.25rem 0; + padding: 0.25rem; + } +} + +@media screen and (max-width: 340px) { + .navbar-item.navbar-item-dropdown { + display: none; + } +} + +@media (forced-colors: active) { + .version-dropdown-toggle { + border: 1px solid ButtonText; + } + + .version-dropdown-menu { + border-color: CanvasText; + } +} diff --git a/content/supplemental/css/search.css b/content/supplemental/css/search.css index f27d59d0..4c45b21a 100644 --- a/content/supplemental/css/search.css +++ b/content/supplemental/css/search.css @@ -1,83 +1,96 @@ -.navbar-brand .navbar-item + .navbar-item { - flex-grow: 1; - justify-content: flex-end; -} +/* Search field in the header and the Lunr search results (@jmix-framework/lunr-extension). */ -.navbar-brand .vertical-line { - margin: .5rem 1rem; - border-left: 1px solid var(--navbar-font-color); +.search-field { + position: relative; + display: inline-flex; + align-items: center; } -@media screen and (min-width: 1024px) { - .navbar-brand { - flex-grow: 1; - } - - .navbar-menu { - flex-grow: 0; - } +.search-field::before { + content: ""; + position: absolute; + left: 0.65rem; + width: 0.9rem; + height: 0.9rem; + background-color: var(--color-text-muted); + mask: url(../img/icons/search.svg) center / contain no-repeat; + pointer-events: none; } #search-input { - color: var(--color-text); - font-family: inherit; - font-size: 0.95rem; - width: 150px; + width: 18rem; + height: 2.25rem; + padding: 0 2.1rem; border: 1px solid var(--color-control-border); - border-radius: 0.1em; + border-radius: var(--radius-lg); + background: var(--color-surface-subtle); + color: var(--color-text); + font: inherit; + font-size: 0.8rem; line-height: 1.5; - padding: 0 0.25em; +} + +#search-input::placeholder { + color: var(--color-text-muted); + opacity: 1; +} + +#search-input:hover { + background: var(--color-surface-hover); +} + +#search-input:focus { + border-color: var(--color-accent); + background: var(--color-surface); +} + +.search-kbd { + position: absolute; + right: 0.55rem; + padding: 0 0.35rem; + border: 1px solid var(--color-line-strong); + border-radius: var(--radius-sm); + color: var(--color-text-muted); + font: 400 0.68rem/1.35 var(--monospace-font-family); + pointer-events: none; +} + +#search-input:focus + .search-kbd { + display: none; } @media screen and (max-width: 1023px) { + .search-field, #search-input { - margin-left: 4px; - margin-right: 4px; width: 100%; } - .topbar-nav .navbar-end { - display: flex; - justify-content: center; - } - - div.header-btn, .navbar-menu { - background-color: var(--navbar-menu-background); - } - - .header-btn { - justify-content: center; - flex-direction: row; - flex-wrap: wrap; - margin-left: 20px; + #search-input { + /* 16px or more, or iOS Safari zooms in when the field gets focus (the old inline style did this) */ + font-size: 1rem; } } .search-result-dropdown-menu { position: absolute; z-index: 100; - display: block; + top: 100%; right: 0; left: inherit; - top: 100%; - border-radius: 4px; - margin: 6px 0 0; - padding: 0; - text-align: left; + display: block; + min-width: 500px; + max-width: 600px; height: auto; - background: transparent; + margin: 0.5rem 0 0; + padding: 0; border: none; - max-width: 600px; - min-width: 500px; + border-radius: var(--radius-lg); + background: transparent; box-shadow: var(--shadow-menu); + text-align: left; } @media screen and (max-width: 768px) { - .navbar-brand .navbar-item + .navbar-item { - padding-left: 0; - padding-right: 0; - } - .search-result-dropdown-menu { min-width: calc(100vw - 3.75rem); } @@ -85,143 +98,86 @@ .search-result-dataset { position: relative; - border: 1px solid var(--color-line); - background: var(--color-surface); - border-radius: 4px; overflow: auto; - padding: 0 8px 8px; max-height: calc(100vh - 5.25rem); + padding: 0.25rem 0.75rem 0.75rem; + border: 1px solid var(--color-line); + border-radius: var(--radius-lg); + background: var(--color-surface); color: var(--color-text); } +.search-result-component-header { + padding: 0.5rem 0 0.25rem; + color: var(--color-text-muted); + font-size: 0.75rem; + font-weight: 500; +} + .search-result-highlight { - color: var(--color-accent); + padding: 0.1em 0.05em; background: var(--color-accent-tint); - padding: .1em .05em; + color: var(--color-accent); + font-weight: 600; } .search-result-item { display: flex; + margin: 0.5rem 0; font-size: 1rem; - margin-bottom: 0.5rem; - margin-top: 0.5rem; } .search-result-document-title { + position: relative; width: 33%; + padding: 0.25rem 0.5rem 0.25rem 0; border-right: 1px solid var(--color-line); color: var(--color-text-muted); font-size: 0.8rem; - padding: 0.25rem 0.5rem 0.25rem 0; text-align: right; - position: relative; - word-wrap: break-word; + overflow-wrap: break-word; } .search-result-document-hit { flex: 1; - font-size: 0.75em; color: var(--color-text); - font-weight: 700; + font-size: 0.75em; + font-weight: 400; } .search-result-document-hit > a { - color: inherit; display: block; - padding: 0.5rem 0 0.5rem 1rem; margin-bottom: 0.25rem; + padding: 0.5rem 0 0.5rem 1rem; + border-radius: var(--radius-md); + color: inherit; } .search-result-document-hit > a:hover { - background-color: var(--color-surface-hover); -} - -.header-btn { - display: flex; - margin-right: 20px; - text-align: center; -} - -.header-purple-link { - display: inline-block; - padding: 12px 30px; - margin-top: 11px; - margin-bottom: 11px; - font-weight: 600; - font-size: 14px; - border-radius: 6px; + background: var(--color-surface-hover); text-decoration: none; - cursor: pointer; -} - -.header-purple-link { - padding: 10px 30px; - color: var(--navbar-font-color); - background-color: var(--navbar-background); -} - -.header-purple-link:hover { - text-decoration: underline; } -.navbar-brand .navbar-item:first-child { - font-size: 1rem; -} - -@media screen and (max-width: 420px) { - .navbar-brand .navbar-item:first-child { - font-size: .75rem; +@media screen and (max-width: 1023px) { + /* the menu panel scrolls on small screens and would clip the results: let them hang over the page below it */ + .navbar-menu { + overflow: visible; } -} -@media screen and (max-width: 340px) { - .navbar-brand .vertical-line { - display: none; + /* the panel starts lower here, below the field in the open menu, so it gets less height */ + .search-result-dataset { + max-height: calc(100vh - 8rem); } } -a.jmix-link, a.slack-link, a.git-link, a.ai-assistant-link { - font-size: 0; - padding-left: 2px; - padding-right: 10px; -} - -:is(a.slack-link, a.jmix-link, a.git-link, a.ai-assistant-link) img { - margin-left: 0; -} - -:is(a.slack-link, a.jmix-link, a.git-link, a.ai-assistant-link):hover img { - transform: scale(1.07); -} - -.git-link, .slack-link, .jmix-link, .ai-assistant-link { - margin-right: 4px; -} - -div.header-btn a img { - height: 1.25rem; - vertical-align: text-bottom; - margin-left: 8px; - height: 1.25rem; - display: flex; -} - -a.ai-assistant-link { - margin-right: 4px; -} - -a.ai-assistant-link img { - content: url("../img/jmix-ai-assistant-icon.svg"); -} - -a.git-link img { - content: url("../img/git-icon.svg"); -} +@media (forced-colors: active) { + #search-input, + .search-result-dataset { + border-color: CanvasText; + } -a.slack-link img { - content: url("../img/slack-icon.svg"); + .search-field::before { + forced-color-adjust: none; + background-color: GrayText; + } } - -a.jmix-link img { - content: url("../img/jmix-icon.svg"); -} \ No newline at end of file diff --git a/content/supplemental/css/site.css b/content/supplemental/css/site.css index c3dbc2d3..081f2792 100644 --- a/content/supplemental/css/site.css +++ b/content/supplemental/css/site.css @@ -2769,3 +2769,103 @@ a.echarts-docs-btn:hover { background-color: var(--echarts-docs-background); color: var(--echarts-docs-text); } + +/* ---- Header ---- */ + +.navbar { + border-bottom: 1px solid var(--color-line); +} + +.navbar-brand .navbar-item:first-child { + font-size: 1rem; + font-weight: 600; + letter-spacing: -0.005em; +} + +.navbar-logo-center { + fill: var(--header-logo-center); +} + +.navbar-brand .vertical-line { + margin: 0.95rem 0.875rem; + border-left: 1px solid var(--color-line); +} + +@media screen and (min-width: 1024px) { + .navbar-brand { + flex-grow: 1; + } + + .navbar-menu { + flex-grow: 0; + } +} + +.header-btn { + display: flex; + gap: 0.25rem; + align-items: center; + margin-right: 0.75rem; +} + +.header-icon-link { + display: grid; + place-items: center; + width: 2.25rem; + height: 2.25rem; + border-radius: var(--radius-lg); + color: var(--header-text); +} + +.header-icon-link:hover { + background: var(--navbar_hover-background); +} + +.header-icon-link svg { + width: auto; + height: 1.25rem; + fill: currentColor; + /* keeps the AI Assistant mask intact; the forced colors block below then names the icon color itself */ + forced-color-adjust: none; +} + +.header-icon-link.ai-assistant-link { + width: 2.75rem; + background: var(--color-heading); + color: var(--color-text-on-accent); +} + +.header-icon-link.ai-assistant-link:hover { + background: var(--color-accent); +} + +@media screen and (max-width: 1023px) { + .header-btn { + flex-wrap: wrap; + justify-content: center; + margin: 0.5rem 0; + } +} + +@media screen and (max-width: 420px) { + .navbar-brand .navbar-item:first-child { + font-size: 0.75rem; + } +} + +@media screen and (max-width: 340px) { + .navbar-brand .vertical-line { + display: none; + } +} + +@media (forced-colors: active) { + /* with forced-color-adjust: none the icon inherits the author color of its link (white or Space), not the forced one */ + .header-icon-link svg { + color: LinkText; + } + + .header-icon-link.ai-assistant-link { + border: 1px solid ButtonText; + } +} diff --git a/content/supplemental/img/git-icon.svg b/content/supplemental/img/git-icon.svg deleted file mode 100644 index a3f68774..00000000 --- a/content/supplemental/img/git-icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/content/supplemental/img/icons/addon.svg b/content/supplemental/img/icons/addon.svg new file mode 100644 index 00000000..30aa7204 --- /dev/null +++ b/content/supplemental/img/icons/addon.svg @@ -0,0 +1 @@ + diff --git a/content/supplemental/img/icons/caution.svg b/content/supplemental/img/icons/caution.svg new file mode 100644 index 00000000..125b0dbe --- /dev/null +++ b/content/supplemental/img/icons/caution.svg @@ -0,0 +1 @@ + diff --git a/content/supplemental/img/icons/chevron.svg b/content/supplemental/img/icons/chevron.svg new file mode 100644 index 00000000..c3ee8e79 --- /dev/null +++ b/content/supplemental/img/icons/chevron.svg @@ -0,0 +1 @@ + diff --git a/content/supplemental/img/icons/copy.svg b/content/supplemental/img/icons/copy.svg new file mode 100644 index 00000000..a75bbc31 --- /dev/null +++ b/content/supplemental/img/icons/copy.svg @@ -0,0 +1 @@ + diff --git a/content/supplemental/img/icons/edit.svg b/content/supplemental/img/icons/edit.svg new file mode 100644 index 00000000..2f36244e --- /dev/null +++ b/content/supplemental/img/icons/edit.svg @@ -0,0 +1 @@ + diff --git a/content/supplemental/img/icons/home.svg b/content/supplemental/img/icons/home.svg new file mode 100644 index 00000000..9e114f2d --- /dev/null +++ b/content/supplemental/img/icons/home.svg @@ -0,0 +1 @@ + diff --git a/content/supplemental/img/icons/important.svg b/content/supplemental/img/icons/important.svg new file mode 100644 index 00000000..322e355a --- /dev/null +++ b/content/supplemental/img/icons/important.svg @@ -0,0 +1 @@ + diff --git a/content/supplemental/img/icons/note.svg b/content/supplemental/img/icons/note.svg new file mode 100644 index 00000000..eb19cea2 --- /dev/null +++ b/content/supplemental/img/icons/note.svg @@ -0,0 +1 @@ + diff --git a/content/supplemental/img/icons/search.svg b/content/supplemental/img/icons/search.svg new file mode 100644 index 00000000..c595b20f --- /dev/null +++ b/content/supplemental/img/icons/search.svg @@ -0,0 +1 @@ + diff --git a/content/supplemental/img/icons/tip.svg b/content/supplemental/img/icons/tip.svg new file mode 100644 index 00000000..0d191fbd --- /dev/null +++ b/content/supplemental/img/icons/tip.svg @@ -0,0 +1 @@ + diff --git a/content/supplemental/img/icons/warning.svg b/content/supplemental/img/icons/warning.svg new file mode 100644 index 00000000..916f49fd --- /dev/null +++ b/content/supplemental/img/icons/warning.svg @@ -0,0 +1 @@ + diff --git a/content/supplemental/img/img/feedback-form__thumb-up.svg b/content/supplemental/img/img/feedback-form__thumb-up.svg deleted file mode 100644 index b31bdb75..00000000 --- a/content/supplemental/img/img/feedback-form__thumb-up.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/content/supplemental/img/jmix-ai-assistant-icon.svg b/content/supplemental/img/jmix-ai-assistant-icon.svg deleted file mode 100644 index d7e7b800..00000000 --- a/content/supplemental/img/jmix-ai-assistant-icon.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/content/supplemental/img/jmix-icon.svg b/content/supplemental/img/jmix-icon.svg deleted file mode 100644 index 46ea78d1..00000000 --- a/content/supplemental/img/jmix-icon.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/content/supplemental/img/warning-icon.svg b/content/supplemental/img/warning-icon.svg deleted file mode 100644 index 533e3a45..00000000 --- a/content/supplemental/img/warning-icon.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/content/supplemental/partials/header-content.hbs b/content/supplemental/partials/header-content.hbs index e9d56b43..765fc1fd 100644 --- a/content/supplemental/partials/header-content.hbs +++ b/content/supplemental/partials/header-content.hbs @@ -5,24 +5,14 @@ diff --git a/tools/ui-audit.mjs b/tools/ui-audit.mjs index a07c758e..83fb3d4f 100644 --- a/tools/ui-audit.mjs +++ b/tools/ui-audit.mjs @@ -57,6 +57,17 @@ const TEXT_CONTRAST = [ // Computed styles the spec fixes: [page, selector (may end with ::before or ::after), property, expected]. // Colors can be written as #rrggbb. Tasks 7 to 11 add entries. const STYLE_EXPECTATIONS = [ + // Task 6: header and search + ['manager', 'nav.navbar', 'background-color', '#ffffff'], + ['manager', 'nav.navbar', 'border-bottom-color', '#e4e6eb'], + ['manager', '.navbar-logo-center', 'fill', '#17124b'], + ['manager', '.version-dropdown-toggle', 'background-color', '#f0eeff'], + ['manager', '.version-dropdown-toggle', 'color', '#342a98'], + ['manager', '#search-input', 'width', '324px'], + ['manager', '#search-input', 'border-top-color', '#8a8f99'], + ['manager', '.search-kbd', 'font-weight', '400'], + ['manager', '.header-icon-link.ai-assistant-link', 'background-color', '#17124b'], + ['manager', '.header-icon-link.git-link', 'color', '#17124b'], ]; const SNAPSHOT_PAGES = ['manager', 'events', 'geomap', 'features', 'intro']; From 948f1162f2d79241c3e75788981d7c58a406a82a Mon Sep 17 00:00:00 2001 From: Gleb Gorelov Date: Sat, 3 Oct 2026 03:29:24 +0400 Subject: [PATCH 14/75] Move the mask icons to tokens in the UI restyle plan --- .../plans/2026-10-03-docs-ui-restyle.md | 24 +++++++++---------- .../2026-10-03-docs-ui-restyle-design.md | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/superpowers/plans/2026-10-03-docs-ui-restyle.md b/docs/superpowers/plans/2026-10-03-docs-ui-restyle.md index aa52771c..28d6d2c5 100644 --- a/docs/superpowers/plans/2026-10-03-docs-ui-restyle.md +++ b/docs/superpowers/plans/2026-10-03-docs-ui-restyle.md @@ -49,7 +49,7 @@ Set `U=$SP/upstream/src-0e38223adfd81eb74d4b1779e158f8ad05ff8923` in shells that | `content/supplemental/css/dropdown-menu.css` | Header version menu | | `content/supplemental/css/feedback-form.css` | "Was this page helpful?" form | | `content/supplemental/font/` | Roboto and JetBrains Mono woff2 files and their licenses | -| `content/supplemental/img/icons/*.svg` | Mask icons: chevron, home, edit, search, copy, note, tip, warning, important, caution, addon | +| `--icon-*` tokens in `tokens.css` | Mask icons as data-URI SVGs: chevron, home, edit, search, copy, note, tip, warning, important, caution, addon (amended in Task 6: Chromium blocks mask images loaded from files when the site is opened from `file://`, which is how `AGENTS.md` tells authors to preview a build) | | `content/supplemental/partials/head-styles.hbs` | The five stylesheet links | | `content/supplemental/partials/head-meta.hbs` | Favicons | | `content/supplemental/partials/header-content.hbs` | Skip link, header, inline icons, search field | @@ -2378,7 +2378,7 @@ Append to part 2 of `site.css`: width: 1em; height: 1em; background-color: currentColor; - mask: url(../img/icons/copy.svg) center / contain no-repeat; + mask: var(--icon-copy) center / contain no-repeat; } /* the explore panel toggle is a button now */ @@ -2741,7 +2741,7 @@ Append to part 2 of `site.css`: width: 100%; height: 100%; background-color: var(--color-text-muted); - mask: url(../img/icons/chevron.svg) center / 14px no-repeat; + mask: var(--icon-chevron) center / 14px no-repeat; } .nav-item-toggle:hover { @@ -2774,7 +2774,7 @@ Append to part 2 of `site.css`: width: 1rem; height: 1rem; background: currentColor; - mask: url(../img/icons/chevron.svg) center / 12px no-repeat; + mask: var(--icon-chevron) center / 12px no-repeat; rotate: -90deg; } @@ -2880,7 +2880,7 @@ Append to part 2 of `site.css`: width: 1rem; height: 1rem; background-color: currentColor; - mask: url(../img/icons/home.svg) center / contain no-repeat; + mask: var(--icon-home) center / contain no-repeat; } .home-link:hover { @@ -2921,7 +2921,7 @@ Append to part 2 of `site.css`: width: 0.875rem; height: 0.875rem; background-color: currentColor; - mask: url(../img/icons/edit.svg) center / contain no-repeat; + mask: var(--icon-edit) center / contain no-repeat; } .toolbar .edit-this-page a:hover { @@ -3365,7 +3365,7 @@ Append to part 2 of `site.css`: --adm-background: var(--note-background); --adm-border: var(--note-border-color); --adm-accent: var(--note-accent); - --adm-icon: url(../img/icons/note.svg); + --adm-icon: var(--icon-note); } .doc .admonitionblock.tip { @@ -3373,7 +3373,7 @@ Append to part 2 of `site.css`: --adm-background: var(--tip-background); --adm-border: var(--tip-border-color); --adm-accent: var(--tip-accent); - --adm-icon: url(../img/icons/tip.svg); + --adm-icon: var(--icon-tip); } .doc .admonitionblock.warning { @@ -3381,7 +3381,7 @@ Append to part 2 of `site.css`: --adm-background: var(--warning-background); --adm-border: var(--warning-border-color); --adm-accent: var(--warning-accent); - --adm-icon: url(../img/icons/warning.svg); + --adm-icon: var(--icon-warning); } .doc .admonitionblock.important { @@ -3389,7 +3389,7 @@ Append to part 2 of `site.css`: --adm-background: var(--important-background); --adm-border: var(--important-border-color); --adm-accent: var(--important-accent); - --adm-icon: url(../img/icons/important.svg); + --adm-icon: var(--icon-important); } .doc .admonitionblock.caution { @@ -3397,7 +3397,7 @@ Append to part 2 of `site.css`: --adm-background: var(--caution-background); --adm-border: var(--caution-border-color); --adm-accent: var(--caution-accent); - --adm-icon: url(../img/icons/caution.svg); + --adm-icon: var(--icon-caution); } /* the add-on role sits on top of NOTE ("admonitionblock note addon-component"), so it comes last */ @@ -3406,7 +3406,7 @@ Append to part 2 of `site.css`: --adm-background: var(--addon-background); --adm-border: var(--addon-border-color); --adm-accent: var(--addon-accent); - --adm-icon: url(../img/icons/addon.svg); + --adm-icon: var(--icon-addon); } .doc .admonitionblock { diff --git a/docs/superpowers/specs/2026-10-03-docs-ui-restyle-design.md b/docs/superpowers/specs/2026-10-03-docs-ui-restyle-design.md index cc211c19..c9c84a8b 100644 --- a/docs/superpowers/specs/2026-10-03-docs-ui-restyle-design.md +++ b/docs/superpowers/specs/2026-10-03-docs-ui-restyle-design.md @@ -149,7 +149,7 @@ Self-hosted in `content/supplemental/font/` from Fontsource 5.3.0, OFL-1.1, lice ### Icons -- Mask icons as SVG files in `content/supplemental/img/icons/`: chevron, home, edit, search, copy, note, tip, warning, important, caution, add-on. CSS draws them with `mask` and `background-color: currentColor` (or a token). +- Mask icons as tier 3 tokens in `tokens.css`, `--icon-chevron`, `--icon-home`, `--icon-edit`, `--icon-search`, `--icon-copy`, `--icon-note`, `--icon-tip`, `--icon-warning`, `--icon-important`, `--icon-caution` and `--icon-addon`, each a data-URI SVG. CSS draws them with `mask: var(--icon-…)` and `background-color: currentColor` (or a token). They are not separate files because Chromium blocks mask images loaded from files when the site is opened from `file://`, which is how authors preview a build. - Header icons inline in `header-content.hbs`. The AI Assistant icon is redrawn as a single-color icon (shapes with cut-outs instead of navy strokes on white), so it works on the navy button, on a light background and in forced colors. `git-icon.svg`, `jmix-icon.svg` and `jmix-ai-assistant-icon.svg` are removed once inlined, together with the unused `warning-icon.svg`, the duplicate `img/img/feedback-form__thumb-up.svg` and the dead Slack icon rule in `search.css`. ## Accessibility From a0a12cdbd8d52c4610a5d9ffe4b1a177612596fe Mon Sep 17 00:00:00 2001 From: Gleb Gorelov Date: Sat, 3 Oct 2026 03:36:28 +0400 Subject: [PATCH 15/75] Draw the mask icons from tokens --- content/supplemental/css/search.css | 7 ++++++- content/supplemental/css/tokens.css | 13 +++++++++++++ content/supplemental/img/icons/addon.svg | 1 - content/supplemental/img/icons/caution.svg | 1 - content/supplemental/img/icons/chevron.svg | 1 - content/supplemental/img/icons/copy.svg | 1 - content/supplemental/img/icons/edit.svg | 1 - content/supplemental/img/icons/home.svg | 1 - content/supplemental/img/icons/important.svg | 1 - content/supplemental/img/icons/note.svg | 1 - content/supplemental/img/icons/search.svg | 1 - content/supplemental/img/icons/tip.svg | 1 - content/supplemental/img/icons/warning.svg | 1 - 13 files changed, 19 insertions(+), 12 deletions(-) delete mode 100644 content/supplemental/img/icons/addon.svg delete mode 100644 content/supplemental/img/icons/caution.svg delete mode 100644 content/supplemental/img/icons/chevron.svg delete mode 100644 content/supplemental/img/icons/copy.svg delete mode 100644 content/supplemental/img/icons/edit.svg delete mode 100644 content/supplemental/img/icons/home.svg delete mode 100644 content/supplemental/img/icons/important.svg delete mode 100644 content/supplemental/img/icons/note.svg delete mode 100644 content/supplemental/img/icons/search.svg delete mode 100644 content/supplemental/img/icons/tip.svg delete mode 100644 content/supplemental/img/icons/warning.svg diff --git a/content/supplemental/css/search.css b/content/supplemental/css/search.css index 4c45b21a..c4c6052b 100644 --- a/content/supplemental/css/search.css +++ b/content/supplemental/css/search.css @@ -13,7 +13,7 @@ width: 0.9rem; height: 0.9rem; background-color: var(--color-text-muted); - mask: url(../img/icons/search.svg) center / contain no-repeat; + mask: var(--icon-search) center / contain no-repeat; pointer-events: none; } @@ -69,6 +69,11 @@ /* 16px or more, or iOS Safari zooms in when the field gets focus (the old inline style did this) */ font-size: 1rem; } + + .search-kbd { + /* the / shortcut is meant for keyboards, so the hint stays on wide screens */ + display: none; + } } .search-result-dropdown-menu { diff --git a/content/supplemental/css/tokens.css b/content/supplemental/css/tokens.css index 5bcdc5cb..39465ab1 100644 --- a/content/supplemental/css/tokens.css +++ b/content/supplemental/css/tokens.css @@ -135,6 +135,19 @@ --echarts-docs-background: #b03a5b; --echarts-docs-text: #fdfdfd; + /* mask icons; data URIs because mask images from files are blocked on file:// pages */ + --icon-chevron: url("data:image/svg+xml,"); + --icon-home: url("data:image/svg+xml,"); + --icon-edit: url("data:image/svg+xml,"); + --icon-search: url("data:image/svg+xml,"); + --icon-copy: url("data:image/svg+xml,"); + --icon-note: url("data:image/svg+xml,"); + --icon-tip: url("data:image/svg+xml,"); + --icon-warning: url("data:image/svg+xml,"); + --icon-important: url("data:image/svg+xml,"); + --icon-caution: url("data:image/svg+xml,"); + --icon-addon: url("data:image/svg+xml,"); + /* upstream: fonts */ --rem-base: 18; /* used to compute rem value from desired pixel value (e.g., calc(18 / var(--rem-base) * 1rem) = 18px) */ --body-font-size: 1.0625em; /* 17px */ diff --git a/content/supplemental/img/icons/addon.svg b/content/supplemental/img/icons/addon.svg deleted file mode 100644 index 30aa7204..00000000 --- a/content/supplemental/img/icons/addon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/content/supplemental/img/icons/caution.svg b/content/supplemental/img/icons/caution.svg deleted file mode 100644 index 125b0dbe..00000000 --- a/content/supplemental/img/icons/caution.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/content/supplemental/img/icons/chevron.svg b/content/supplemental/img/icons/chevron.svg deleted file mode 100644 index c3ee8e79..00000000 --- a/content/supplemental/img/icons/chevron.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/content/supplemental/img/icons/copy.svg b/content/supplemental/img/icons/copy.svg deleted file mode 100644 index a75bbc31..00000000 --- a/content/supplemental/img/icons/copy.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/content/supplemental/img/icons/edit.svg b/content/supplemental/img/icons/edit.svg deleted file mode 100644 index 2f36244e..00000000 --- a/content/supplemental/img/icons/edit.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/content/supplemental/img/icons/home.svg b/content/supplemental/img/icons/home.svg deleted file mode 100644 index 9e114f2d..00000000 --- a/content/supplemental/img/icons/home.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/content/supplemental/img/icons/important.svg b/content/supplemental/img/icons/important.svg deleted file mode 100644 index 322e355a..00000000 --- a/content/supplemental/img/icons/important.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/content/supplemental/img/icons/note.svg b/content/supplemental/img/icons/note.svg deleted file mode 100644 index eb19cea2..00000000 --- a/content/supplemental/img/icons/note.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/content/supplemental/img/icons/search.svg b/content/supplemental/img/icons/search.svg deleted file mode 100644 index c595b20f..00000000 --- a/content/supplemental/img/icons/search.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/content/supplemental/img/icons/tip.svg b/content/supplemental/img/icons/tip.svg deleted file mode 100644 index 0d191fbd..00000000 --- a/content/supplemental/img/icons/tip.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/content/supplemental/img/icons/warning.svg b/content/supplemental/img/icons/warning.svg deleted file mode 100644 index 916f49fd..00000000 --- a/content/supplemental/img/icons/warning.svg +++ /dev/null @@ -1 +0,0 @@ - From 7a7b3b767ed8bee7bb61391c7370c0520beac06f Mon Sep 17 00:00:00 2001 From: Gleb Gorelov Date: Sat, 3 Oct 2026 03:49:57 +0400 Subject: [PATCH 16/75] Record Task 6 review follow-ups in the UI restyle plan and spec --- docs/superpowers/plans/2026-10-03-docs-ui-restyle.md | 7 +++++++ .../superpowers/specs/2026-10-03-docs-ui-restyle-design.md | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/superpowers/plans/2026-10-03-docs-ui-restyle.md b/docs/superpowers/plans/2026-10-03-docs-ui-restyle.md index 28d6d2c5..3a1bfa3c 100644 --- a/docs/superpowers/plans/2026-10-03-docs-ui-restyle.md +++ b/docs/superpowers/plans/2026-10-03-docs-ui-restyle.md @@ -2401,9 +2401,16 @@ Append to part 2 of `site.css`: forced-color-adjust: none; background-color: ButtonText; } + + /* the navy center of the logo would vanish on a dark Canvas */ + .navbar-logo-center { + fill: CanvasText; + } } ``` +Also align our mobile breakpoints with part 1, which switches at `1023.5px`: in part 2 of `site.css` and in `search.css`, replace every `max-width: 1023px` with `max-width: 1023.5px` (a viewport between the two, possible under browser zoom, would otherwise get the mobile panel without our mobile rules). + - [ ] **Step 4: Add the skip link and the main target** In `content/supplemental/partials/header-content.hbs`, insert directly before `
`: diff --git a/docs/superpowers/specs/2026-10-03-docs-ui-restyle-design.md b/docs/superpowers/specs/2026-10-03-docs-ui-restyle-design.md index c9c84a8b..714b9b5b 100644 --- a/docs/superpowers/specs/2026-10-03-docs-ui-restyle-design.md +++ b/docs/superpowers/specs/2026-10-03-docs-ui-restyle-design.md @@ -178,7 +178,7 @@ Forced colors, written next to each component in a `@media (forced-colors: activ - Borders in `CanvasText` for the header bottom, nav right, toolbar bottom, code blocks and their title tabs, admonitions, sidebar and example blocks, the feedback form and the search field. `GrayText` border for inline code. - Active states lose their tinted backgrounds, so the current nav item, the active TOC item and the current version get a `Highlight` border. -- Inline SVG icons follow `currentColor` and need nothing. Mask icons get `forced-color-adjust: none` and a system color (`CanvasText`, `LinkText`, `ButtonText` or `GrayText`), because the icon carries meaning. +- The header icons are inline SVG with `fill: currentColor`; because the AI Assistant icon uses an SVG mask, the SVGs set `forced-color-adjust: none` and, in forced colors, `color: LinkText` (Chromium would otherwise let them inherit the author color). The logo center gets `fill: CanvasText`. Mask icons get `forced-color-adjust: none` and a system color (`CanvasText`, `LinkText`, `ButtonText` or `GrayText`), because the icon carries meaning. - Callout numbers in code get a border. Syntax highlighting is left to the browser: in forced colors it collapses to the text color, which is fine because color carries no meaning there. Motion and scheme: @@ -198,7 +198,7 @@ Values are for desktop (root font size 18 px from 1024 px up, 17 px below, as up - Search: input 18rem wide (100% below 1024px), 2.25rem high, padding 0 2.1rem, border 1px `--color-control-border`, radius 8px, background `--color-surface-subtle`, text `--color-text` 0.8rem, placeholder `--color-text-muted`; hover background `--color-surface-hover`; focus background `--color-surface` and border `--color-accent` plus the focus ring. Magnifier icon 0.9rem at 0.65rem from the left, `--color-text-muted`. The `/` hint at 0.55rem from the right: border 1px `--color-line-strong`, radius 4px, JetBrains Mono 400 0.68rem, `--color-text-muted`; hidden while the input has focus. - Search results: radius 8px, `--shadow-menu`; the list has a 1px `--color-line` border and padding 0.25rem 0.75rem 0.75rem; component header `--color-text-muted` 500 0.75rem; document titles `--color-text-muted` with a right border `--color-line`; snippets at regular weight (today they are bold); result links radius 6px with `--color-surface-hover` on hover; matches with `--color-accent-tint` background, `--color-accent` text, 600. - Icon buttons: 2.25rem square, radius 8px, `--header-text`, hover `--color-surface-hover`. AI Assistant: 2.75rem wide, background `--color-heading`, icon `--color-text-on-accent`; hover background `--color-accent`. -- Below 1024px: burger lines `--header-text`, the menu panel `--color-surface`, the search text 1rem, because iOS Safari zooms into fields with text below 16px. +- Below 1024px: burger lines `--header-text`, the menu panel `--color-surface`, the search text 1rem, because iOS Safari zooms into fields with text below 16px, the `/` hint hidden (no keyboard shortcut there), and the search results allowed to overflow the menu panel instead of being clipped by it. ### Navigation From 9f490a4116908848de464d6d2bcedbe0aad71e18 Mon Sep 17 00:00:00 2001 From: Gleb Gorelov Date: Sat, 3 Oct 2026 04:25:56 +0400 Subject: [PATCH 17/75] Add focus styles, skip link and keyboard access --- content/supplemental/css/search.css | 4 +- content/supplemental/css/site.css | 104 +++++++++++++++--- content/supplemental/js/a11y.js | 67 +++++++++++ content/supplemental/js/dropdown-menu.js | 26 +++-- .../supplemental/partials/footer-scripts.hbs | 13 +-- .../supplemental/partials/header-content.hbs | 1 + content/supplemental/partials/main.hbs | 14 +++ content/supplemental/partials/nav-explore.hbs | 4 +- content/supplemental/partials/nav-tree.hbs | 24 ++++ 9 files changed, 225 insertions(+), 32 deletions(-) create mode 100644 content/supplemental/js/a11y.js create mode 100644 content/supplemental/partials/main.hbs create mode 100644 content/supplemental/partials/nav-tree.hbs diff --git a/content/supplemental/css/search.css b/content/supplemental/css/search.css index c4c6052b..b16a00cd 100644 --- a/content/supplemental/css/search.css +++ b/content/supplemental/css/search.css @@ -59,7 +59,7 @@ display: none; } -@media screen and (max-width: 1023px) { +@media screen and (max-width: 1023.5px) { .search-field, #search-input { width: 100%; @@ -163,7 +163,7 @@ text-decoration: none; } -@media screen and (max-width: 1023px) { +@media screen and (max-width: 1023.5px) { /* the menu panel scrolls on small screens and would clip the results: let them hang over the page below it */ .navbar-menu { overflow: visible; diff --git a/content/supplemental/css/site.css b/content/supplemental/css/site.css index 081f2792..8a5c5539 100644 --- a/content/supplemental/css/site.css +++ b/content/supplemental/css/site.css @@ -117,7 +117,6 @@ button::-moz-focus-inner { summary { cursor: pointer; -webkit-tap-highlight-color: transparent; - outline: none; } table { @@ -295,7 +294,6 @@ object[type="image/svg+xml"]:not([width]) { height: 1em; margin-right: -0.5rem; opacity: 0.75; - outline: none; padding: 0; position: sticky; top: calc((var(--nav-line-height) - 1 + 0.5) * 1rem); @@ -351,7 +349,6 @@ object[type="image/svg+xml"]:not([width]) { .nav-item-toggle { background: transparent url(../img/caret.svg) no-repeat center / 50%; border: none; - outline: none; line-height: inherit; padding: 0; position: absolute; @@ -544,7 +541,6 @@ body.-toc aside.toc.sidebar { background: url(../img/menu.svg) no-repeat 50% 47.5%; background-size: 49%; border: none; - outline: none; line-height: inherit; padding: 0; height: var(--toolbar-height); @@ -652,7 +648,6 @@ a + .breadcrumbs { background-position: right 0.5rem top 50%; background-size: auto 0.75em; border: none; - outline: none; line-height: inherit; padding: 0.5rem 1.5rem 0.5rem 0.5rem; position: relative; @@ -776,7 +771,6 @@ a + .breadcrumbs { .sidebar.toc .toc-menu a { display: block; - outline: none; } .toc .toc-menu a:hover { @@ -1642,7 +1636,6 @@ a + .breadcrumbs { background: none; border: none; color: inherit; - outline: none; padding: 0; font-size: inherit; line-height: inherit; @@ -2028,14 +2021,9 @@ body { color: var(--color-text-muted); } -#search-input:focus { - outline: none; -} - .navbar-burger { background: none; border: none; - outline: none; line-height: 1; position: relative; width: 3rem; @@ -2638,6 +2626,11 @@ footer.footer { cursor: pointer; } +.navbar-brand .jmix-banner :focus-visible { + /* the banner is as dark as the focus ring color, so its ring takes the text color */ + outline-color: var(--banner-text); +} + @media screen and (max-width: 1400px) { .navbar-brand .jmix-banner { font-size: 0.8rem; @@ -2839,7 +2832,7 @@ a.echarts-docs-btn:hover { background: var(--color-accent); } -@media screen and (max-width: 1023px) { +@media screen and (max-width: 1023.5px) { .header-btn { flex-wrap: wrap; justify-content: center; @@ -2869,3 +2862,88 @@ a.echarts-docs-btn:hover { border: 1px solid ButtonText; } } + +/* ---- Accessibility ---- */ + +:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} + +/* these fill a scrolling list or touch the window edge, where a ring outside the box is cut off: it goes inside */ +.navbar-burger:focus-visible, +.nav-toggle:focus-visible, +.nav-panel-explore .context:focus-visible, +.sidebar.toc .toc-menu a:focus-visible { + outline-offset: -2px; +} + +#main-content:focus { + /* the skip link target is a region, not a control */ + outline: none; +} + +.skip-link { + position: fixed; + z-index: 100; + top: 0.5rem; + left: 0.75rem; + padding: 0.5rem 0.875rem; + border: 1px solid var(--color-line); + border-radius: var(--radius-md); + background: var(--color-surface); + box-shadow: var(--shadow-menu); + color: var(--color-heading); + font-weight: 600; + text-decoration: none; + translate: 0 -200%; +} + +.skip-link:focus-visible { + translate: 0 0; +} + +/* the copy button is visible and reachable by keyboard, not only on hover */ +.doc .source-toolbox { + visibility: visible; +} + +.doc .source-toolbox .copy-icon { + display: none; +} + +.doc .source-toolbox .copy-button::before { + content: ""; + flex: none; + width: 1em; + height: 1em; + background-color: currentColor; + mask: var(--icon-copy) center / contain no-repeat; +} + +/* the explore panel toggle is a button now */ +.nav-panel-explore .context { + width: 100%; + border: 0; + background: transparent; + font: inherit; + text-align: left; +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } +} + +@media (forced-colors: active) { + .doc .source-toolbox .copy-button::before { + forced-color-adjust: none; + background-color: ButtonText; + } + + /* the navy center of the logo would vanish on a dark Canvas */ + .navbar-logo-center { + fill: CanvasText; + } +} diff --git a/content/supplemental/js/a11y.js b/content/supplemental/js/a11y.js new file mode 100644 index 00000000..94a635f7 --- /dev/null +++ b/content/supplemental/js/a11y.js @@ -0,0 +1,67 @@ +// Accessibility for markup that site.js and the Lunr search UI control: aria-expanded on the toggles +// whose state site.js keeps in classes, a name for the copy buttons, focus for the skip link, and +// keyboard access to the search results. +(function () { + // site.js toggles .is-active on nav items, the explore panel and the burger + const syncNavToggle = function (item) { + const toggle = item.querySelector(':scope > .nav-item-toggle'); + if (toggle) toggle.setAttribute('aria-expanded', String(item.classList.contains('is-active'))); + }; + const explorePanel = document.querySelector('.nav-panel-explore'); + const exploreToggle = explorePanel && explorePanel.querySelector('.context'); + const burger = document.querySelector('.navbar-burger'); + const syncPanels = function () { + if (exploreToggle) exploreToggle.setAttribute('aria-expanded', String(explorePanel.classList.contains('is-active'))); + if (burger) burger.setAttribute('aria-expanded', String(burger.classList.contains('is-active'))); + }; + document.querySelectorAll('.nav-item').forEach(syncNavToggle); + syncPanels(); + const observer = new MutationObserver(function (records) { + records.forEach(function (record) { + if (record.target.classList.contains('nav-item')) syncNavToggle(record.target); + }); + syncPanels(); + }); + const nav = document.querySelector('.nav'); + if (nav) observer.observe(nav, { subtree: true, attributes: true, attributeFilter: ['class'] }); + if (burger) observer.observe(burger, { attributes: true, attributeFilter: ['class'] }); + + // site.js names the copy buttons through a hidden icon's alt text and an invisible toast + document.querySelectorAll('.doc .source-toolbox .copy-button').forEach(function (button) { + button.setAttribute('aria-label', 'Copy to clipboard'); + const toast = button.querySelector('.copy-toast'); + if (toast) toast.setAttribute('aria-hidden', 'true'); + }); + + // site.js intercepts in-page links to scroll them, so the skip link moves focus itself + const skipLink = document.querySelector('.skip-link'); + const main = document.getElementById('main-content'); + if (skipLink && main) { + skipLink.addEventListener('click', function () { + main.focus({ preventScroll: true }); + }); + } + + // search-ui.js runs the search 100 ms after every keydown in the input and re-renders the results; + // Escape there clears the query and the results + const searchInput = document.getElementById('search-input'); + const searchArea = searchInput && searchInput.closest('.search-field'); + if (searchArea) { + const clearSearch = function () { + searchInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })); + }; + // keep Tab away from search-ui.js, or the result that just received focus is replaced + document.addEventListener('keydown', function (event) { + if (event.target === searchInput && event.key === 'Tab') event.stopPropagation(); + }, true); + searchArea.addEventListener('keydown', function (event) { + if (event.key === 'Escape' && event.target !== searchInput) { + searchInput.focus(); + clearSearch(); + } + }); + searchArea.addEventListener('focusout', function (event) { + if (!event.relatedTarget || !searchArea.contains(event.relatedTarget)) clearSearch(); + }); + } +})() diff --git a/content/supplemental/js/dropdown-menu.js b/content/supplemental/js/dropdown-menu.js index df37df2f..69a40c71 100644 --- a/content/supplemental/js/dropdown-menu.js +++ b/content/supplemental/js/dropdown-menu.js @@ -3,14 +3,24 @@ versionDropdownWrapperList.forEach(versionDropdownWrapper => { const dropdownToggle = versionDropdownWrapper.querySelector('.version-dropdown-toggle'); const dropdownMenu = versionDropdownWrapper.querySelector('.version-dropdown-menu'); - dropdownToggle.addEventListener('click', function (event) { - if (dropdownToggle.classList.contains('opened')) { - dropdownToggle.classList.remove('opened'); - dropdownMenu.classList.remove('opened'); - } else { - dropdownToggle.classList.add('opened'); - dropdownMenu.classList.add('opened'); + const setOpened = function (opened) { + dropdownToggle.classList.toggle('opened', opened); + dropdownMenu.classList.toggle('opened', opened); + dropdownToggle.setAttribute('aria-expanded', String(opened)); + }; + dropdownToggle.addEventListener('click', function () { + setOpened(!dropdownToggle.classList.contains('opened')); + }); + versionDropdownWrapper.addEventListener('keydown', function (event) { + if (event.key === 'Escape' && dropdownToggle.classList.contains('opened')) { + setOpened(false); + dropdownToggle.focus(); + } + }); + document.addEventListener('click', function (event) { + if (!versionDropdownWrapper.contains(event.target)) { + setOpened(false); } }); }); -})() \ No newline at end of file +})() diff --git a/content/supplemental/partials/footer-scripts.hbs b/content/supplemental/partials/footer-scripts.hbs index 8f4e9ece..db73bd55 100644 --- a/content/supplemental/partials/footer-scripts.hbs +++ b/content/supplemental/partials/footer-scripts.hbs @@ -9,23 +9,22 @@ hljs.highlightAll(); - + + - diff --git a/content/supplemental/partials/header-content.hbs b/content/supplemental/partials/header-content.hbs index 765fc1fd..954ff54a 100644 --- a/content/supplemental/partials/header-content.hbs +++ b/content/supplemental/partials/header-content.hbs @@ -1,6 +1,7 @@ {{! This Source Code Form is subject to the terms of the Mozilla Public }} {{! License, v. 2.0. If a copy of the MPL was not distributed with this }} {{! file, You can obtain one at http://mozilla.org/MPL/2.0/. }} +