refactor(a11y): comprehensive WCAG 2.2 AA accessibility improvements (#924)

* refactor(a11y): comprehensive WCAG 2.2 AA accessibility improvements

Add skip-to-content link, landmark regions, ARIA attributes, keyboard
navigation, focus styles, reduced-motion support, and i18n keys across
all layouts, partials, shortcodes, and JS components.

- Add skip nav link in baseof.html and id="content" on all <main> tags
- Fix 404 page lang/dir attributes and add <main> landmark
- Add aria-label to banner close, PDF iframe, search input/results
- Remove aria-hidden from back-to-top button
- Add aria-hidden to decorative external link icon
- Add role="tablist" to tabs, aria-expanded to filetree/dropdowns
- Wrap mermaid diagrams in role="img", asciinema in role="region"
- Change theme toggle <p> items to <button role="menuitem"> with
  full keyboard navigation (Arrow/Home/End/Escape)
- Add arrow-key keyboard navigation to tabs component
- Separate sidebar collapsible button from link for independent
  keyboard access with aria-expanded sync
- Sync aria-expanded on all dropdown toggles (theme, lang, navbar,
  hamburger, page context menu)
- Add aria-live search status announcements
- Add 13 new i18n keys, replace hardcoded aria-label strings
- Add prefers-reduced-motion CSS override and focus-visible base styles
- Add aria-label swap on code copy ("Copied!" feedback for AT)
- Add aria-current to active TOC links
- Wrap filetree in <ul> container for proper list semantics
- Add unique aria-label to blog "Read more" links
- Document accessibility guidelines in AGENTS.md

* feat(a11y): enhance focus styles and accessibility for various components

- Add focus-visible styles to badges, buttons, and links for improved keyboard navigation.
- Update breadcrumb, sidebar, and TOC components to include focus-visible outlines.
- Introduce new classes for focus states in the badge and tabs shortcodes.
- Ensure consistent focus styles across all interactive elements to meet WCAG 2.2 AA standards.

* feat(a11y): implement new focus-visible utilities and enhance accessibility styles

- Introduce new utility classes for focus-visible states to improve keyboard navigation.
- Update various components including badges, buttons, and search inputs to utilize new focus-visible styles.
- Refactor existing focus styles to ensure consistency and compliance with accessibility standards.
- Enhance breadcrumb, sidebar, and TOC components with updated focus-visible classes for better user experience.

* chore: add .gitattributes to collapse generated files in PR diffs

* fix: enhance accessibility and improve documentation

- Added alt attributes to images in multiple language documentation files for better accessibility.
- Updated the navbar title partial to remove unnecessary title attribute.
- Improved search input accessibility by adding autocomplete="off".
- Enhanced search partials in both navbar and sidebar with location context.
- Updated SVG icons in various components to include aria-hidden and focusable attributes for improved accessibility compliance.

* fix: improve giscus theme toggle functionality

- Updated the theme toggle options selector to use a data attribute for better specificity.
- Modified the event listener to use a setTimeout for the theme update, ensuring smoother transitions when the theme switcher is clicked.

* fix: resolve axe-core WCAG AA violations across docs pages

Add aria-labels to Hugo task list checkboxes, fix asciinema player
timer accessible names, make Jupyter output cells keyboard-focusable,
and add missing heading hierarchy in shortcodes docs for fa/ja/zh-cn.

* feat: integrate accessibility testing with Playwright and enhance CI workflow

- Added Playwright configuration for accessibility testing.
- Implemented accessibility tests using axe-core for all English pages.
- Created a GitHub Actions workflow to automate accessibility tests on pull requests.
- Updated package dependencies to include @axe-core/playwright and @playwright/test.
- Enhanced sidebar component with data attributes for improved accessibility styling.

* fix: update base URL and improve accessibility labels across multiple languages

- Changed the base URL in Playwright configuration and CI workflow from localhost:3000 to localhost:1313.
- Added accessibility labels for screen readers in various language files, enhancing user experience for visually impaired users.
- Updated the Asciinema script to dynamically set the playback time label for better accessibility compliance.

* refactor: reorganize accessibility tests and update test directory structure

- Moved accessibility tests from the e2e directory to a new tests directory for better organization.
- Updated the test directory path in Playwright configuration.
- Refactored the accessibility test implementation to improve code clarity and maintainability.

* chore: update .gitignore to include Playwright test output directories

- Added entries for 'playwright-report/' and 'test-results/' to the .gitignore file to prevent cluttering the repository with test artifacts.

* refactor: enhance accessibility and improve focus styles across components

- Removed unused utility for focus visibility in CSS and consolidated focus-visible styles for better maintainability.
- Updated various components to use `role` attributes for improved accessibility, including menu items and buttons.
- Enhanced theme toggle and language switch components with appropriate ARIA roles and attributes for better screen reader support.
- Improved the handling of focus states in the navigation and context menus to ensure a consistent user experience.

* chore: update dependencies and enhance accessibility features

- Updated the 'serve' package version in package.json and package-lock.json for improved performance.
- Removed unused 'xml2js' dependency to streamline the project.
- Enhanced the Playwright configuration to better manage the web server setup for testing.
- Improved accessibility in the language switcher and navigation menu by refining focus management and keyboard interactions.
- Updated the back-to-top button to manage tabindex for better accessibility compliance.

* feat: enhance mobile menu accessibility and keyboard interactions

- Added ARIA attributes to manage visibility of the sidebar on mobile devices.
- Implemented focus management for the sidebar when the menu is toggled.
- Introduced keyboard support to close the menu with the Escape key.
- Improved overall accessibility for the hamburger menu and sidebar interactions.

* fix: refine mobile menu keyboard interaction and enhance navbar accessibility

- Updated the Escape key functionality to close the menu only on mobile devices.
- Added a new ARIA attribute to the hamburger menu button for improved accessibility.
This commit is contained in:
Xin
2026-02-14 20:06:35 +00:00
committed by GitHub
parent 04803c4071
commit 88aa6098f0
97 changed files with 2603 additions and 238 deletions

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
# Mark generated files so they are collapsed by default in GitHub diffs
assets/css/compiled/main.css linguist-generated=true
docs/hugo_stats.json linguist-generated=true

62
.github/workflows/accessibility.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
name: Accessibility Tests
on:
pull_request:
branches: [main]
concurrency:
group: accessibility-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
a11y:
runs-on: ubuntu-latest
environment: accessibility
env:
HUGO_VERSION: 0.147.7
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.24"
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
- name: Setup Hugo
run: |
wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
&& sudo dpkg -i ${{ runner.temp }}/hugo.deb
- name: Install dependencies
run: npm ci
- name: Install Playwright Chromium
run: npx playwright install chromium
- name: Build site
run: npm run build
- name: Run accessibility tests
run: npm run test:a11y
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: accessibility-report
path: playwright-report/
retention-days: 14

4
.gitignore vendored
View File

@@ -3,3 +3,7 @@ public/
resources/ resources/
.hugo_build.lock .hugo_build.lock
# Playwright
playwright-report/
test-results/

View File

@@ -94,6 +94,28 @@ The `docs/` directory serves as both documentation and testing ground:
- Chroma syntax highlighting themes in `assets/css/chroma/` - Chroma syntax highlighting themes in `assets/css/chroma/`
- CSS compilation requires Node.js dependencies (PostCSS, Tailwind CSS v4+) - CSS compilation requires Node.js dependencies (PostCSS, Tailwind CSS v4+)
#### Rebuilding CSS after template changes
Tailwind CSS relies on `docs/hugo_stats.json` to know which HTML tags, classes, and IDs are actually used in the built site, so it can tree-shake unused styles. When you modify layouts, partials, or shortcodes you must **regenerate `hugo_stats.json` first**, then rebuild the CSS:
1. **Generate `docs/hugo_stats.json`** — Run Hugo with the `dev.toml` config (which sets `build.buildStats.enable = true`):
```bash
# Using npm (starts a dev server that writes hugo_stats.json on every rebuild):
npm run dev:theme
# Or a one-shot build using the raw Hugo command:
hugo --config=hugo.yaml,../dev.toml --themesDir=../.. --source=docs
```
2. **Build the CSS** — With an up-to-date `hugo_stats.json` in place, compile the stylesheet:
```bash
npm run build:css
```
> **Why two steps?** `dev.toml` mounts `docs/hugo_stats.json` into the Hugo asset pipeline (`assets/notwatching/hugo_stats.json`) and configures a cache-buster so that changes to the stats file trigger a CSS recompile during `dev:theme`. When running outside the dev server you need to perform these steps manually in order.
### Customization Points ### Customization Points
- Custom partials: `layouts/_partials/custom/` - Custom partials: `layouts/_partials/custom/`
@@ -147,6 +169,22 @@ The `docs/` directory serves as both documentation and testing ground:
- Compiled output goes to `assets/css/compiled/main.css` - Compiled output goes to `assets/css/compiled/main.css`
- Prettier formatting for Go templates and code consistency - Prettier formatting for Go templates and code consistency
### Accessibility (WCAG Compliance)
All new features and UI changes must follow the [Web Content Accessibility Guidelines (WCAG) 2.2](https://www.w3.org/TR/WCAG22/) at the **AA** conformance level. Key requirements:
- **Semantic HTML**: Use appropriate elements (`<nav>`, `<main>`, `<aside>`, `<button>`, `<ul>`, etc.) instead of generic `<div>`/`<span>` where applicable.
- **ARIA attributes**: Add `aria-label`, `aria-expanded`, `aria-controls`, `aria-current`, `role`, and other ARIA attributes to interactive components (menus, toggles, dropdowns, modals) so screen readers can interpret them.
- **Keyboard navigation**: All interactive elements must be reachable and operable via keyboard (`Tab`, `Enter`, `Escape`, arrow keys). Manage focus appropriately when opening/closing menus, modals, and drawers.
- **Focus indicators**: Never remove visible focus outlines. Use the existing `hextra-focus` utility or equivalent visible focus ring styles.
- **Color contrast**: Text and interactive elements must meet WCAG AA contrast ratios (4.5:1 for normal text, 3:1 for large text). Verify in both light and dark modes.
- **Images and icons**: Decorative SVGs/icons should have `aria-hidden="true"`. Meaningful images need descriptive `alt` text.
- **Skip links and landmarks**: Preserve existing skip-navigation links and ARIA landmark roles (`role="navigation"`, `role="search"`, etc.).
- **Live regions**: Use `aria-live` for dynamic content updates (e.g., search results, status messages) so assistive technology announces changes.
- **Form controls**: Associate `<label>` elements with inputs. Provide accessible names for buttons that contain only icons.
When introducing a new component or modifying an existing one, verify it works with keyboard-only navigation and review the rendered HTML for proper semantics and ARIA usage.
### Testing & Quality Assurance ### Testing & Quality Assurance
- Test all changes in `docs/` before releasing - Test all changes in `docs/` before releasing

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,3 @@
.hextra-archive-timeline { .hextra-archive-timeline {
border-left: 2px solid rgba(0, 0, 0, 0.15); @apply hx:border-l-2 hx:border-black/15 hx:dark:border-white/15;
}
.dark .hextra-archive-timeline {
border-left-color: rgba(255, 255, 255, 0.15);
} }

View File

@@ -2,7 +2,7 @@
li { li {
@apply hx:mx-2.5 hx:wrap-break-word hx:rounded-md hx:contrast-more:border hx:text-gray-800 hx:contrast-more:border-transparent hx:dark:text-gray-300; @apply hx:mx-2.5 hx:wrap-break-word hx:rounded-md hx:contrast-more:border hx:text-gray-800 hx:contrast-more:border-transparent hx:dark:text-gray-300;
a { a {
@apply hx:focus-visible:outline-none hx:focus:outline-none hx:block hx:scroll-m-12 hx:px-2.5 hx:py-2; @apply hx:focus-visible:outline-none hx:block hx:scroll-m-12 hx:px-2.5 hx:py-2;
} }
.hextra-search-title { .hextra-search-title {

View File

@@ -9,13 +9,13 @@
} }
.hextra-sidebar-container { .hextra-sidebar-container {
li > div { li > .hextra-sidebar-children {
@apply hx:h-0; @apply hx:h-0;
} }
li.open > div { li.open > .hextra-sidebar-children {
@apply hx:h-auto hx:pt-1; @apply hx:h-auto hx:pt-1;
} }
li.open > a > span > svg > path { li.open > .hextra-sidebar-item > .hextra-sidebar-collapsible-button > svg > path {
@apply hx:rotate-90; @apply hx:rotate-90;
} }
} }

View File

@@ -43,12 +43,37 @@ body {
@apply hx:outline-none hx:ring-2 hx:ring-primary-200 hx:ring-offset-1 hx:ring-offset-primary-300 hx:dark:ring-primary-800 hx:dark:ring-offset-primary-700; @apply hx:outline-none hx:ring-2 hx:ring-primary-200 hx:ring-offset-1 hx:ring-offset-primary-300 hx:dark:ring-primary-800 hx:dark:ring-offset-primary-700;
} }
@utility hextra-focus-visible {
@apply hx:focus-visible:outline-none hx:focus-visible:ring-2 hx:focus-visible:ring-primary-200 hx:focus-visible:ring-offset-1 hx:focus-visible:ring-offset-primary-300 hx:dark:focus-visible:ring-primary-800 hx:dark:focus-visible:ring-offset-primary-700;
}
@utility hextra-focus-visible-inset {
@apply hx:focus-visible:outline-none hx:focus-visible:ring-inset hx:focus-visible:ring-2 hx:focus-visible:ring-primary-200 hx:dark:focus-visible:ring-primary-800 hx:focus-visible:ring-offset-0;
}
@layer base { @layer base {
abbr:where([title]) { abbr:where([title]) {
cursor: help; cursor: help;
} }
} }
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
@layer base {
:where(a, button, [role="tab"], [role="menuitem"], [role="menuitemradio"], input, select, textarea, [tabindex="0"]):not(
[class*="hextra-focus-visible"]
):focus-visible {
@apply hx:hextra-focus;
}
}
@import "./typography.css"; @import "./typography.css";
@import "./highlight.css"; @import "./highlight.css";
@import "./components/cards.css"; @import "./components/cards.css";

View File

@@ -109,7 +109,7 @@
span:target + &, span:target + &,
:hover > &, :hover > &,
&:focus { &:focus-visible {
@apply hx:opacity-100; @apply hx:opacity-100;
} }

View File

@@ -6,17 +6,20 @@ document.addEventListener("DOMContentLoaded", function () {
document.addEventListener("scroll", (e) => { document.addEventListener("scroll", (e) => {
if (window.scrollY > 300) { if (window.scrollY > 300) {
backToTop.classList.remove("hx:opacity-0"); backToTop.classList.remove("hx:opacity-0");
backToTop.removeAttribute("tabindex");
} else { } else {
backToTop.classList.add("hx:opacity-0"); backToTop.classList.add("hx:opacity-0");
backToTop.setAttribute("tabindex", "-1");
} }
}); });
} }
}); });
function scrollUp() { function scrollUp() {
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
window.scroll({ window.scroll({
top: 0, top: 0,
left: 0, left: 0,
behavior: "smooth", behavior: prefersReducedMotion ? 'auto' : 'smooth',
}); });
} }

View File

@@ -25,6 +25,27 @@ document.addEventListener('DOMContentLoaded', function () {
return svg; return svg;
} }
// Make scrollable code blocks focusable for keyboard users.
const updateScrollableCodeBlocks = () => {
document.querySelectorAll('.hextra-code-block pre, .highlight pre').forEach(function (pre) {
if (pre.scrollWidth > pre.clientWidth) {
pre.setAttribute('tabindex', '0');
} else {
pre.removeAttribute('tabindex');
}
});
};
updateScrollableCodeBlocks();
let resizeRaf;
window.addEventListener('resize', () => {
if (resizeRaf) {
cancelAnimationFrame(resizeRaf);
}
resizeRaf = requestAnimationFrame(updateScrollableCodeBlocks);
});
document.querySelectorAll('.hextra-code-copy-btn').forEach(function (button) { document.querySelectorAll('.hextra-code-copy-btn').forEach(function (button) {
// Add copy and success icons // Add copy and success icons
button.querySelector('.hextra-copy-icon')?.appendChild(getCopyIcon()); button.querySelector('.hextra-copy-icon')?.appendChild(getCopyIcon());
@@ -52,8 +73,12 @@ document.addEventListener('DOMContentLoaded', function () {
} }
navigator.clipboard.writeText(code).then(function () { navigator.clipboard.writeText(code).then(function () {
button.classList.add('copied'); button.classList.add('copied');
var originalLabel = button.getAttribute('aria-label');
var copiedLabel = button.dataset.copiedLabel || 'Copied!';
button.setAttribute('aria-label', copiedLabel);
setTimeout(function () { setTimeout(function () {
button.classList.remove('copied'); button.classList.remove('copied');
button.setAttribute('aria-label', originalLabel);
}, 1000); }, 1000);
}).catch(function (err) { }).catch(function (err) {
console.error('Failed to copy text: ', err); console.error('Failed to copy text: ', err);

View File

@@ -7,7 +7,9 @@ document.addEventListener("DOMContentLoaded", function () {
Array.from(folder.children).forEach(function (el) { Array.from(folder.children).forEach(function (el) {
el.dataset.state = el.dataset.state === "open" ? "closed" : "open"; el.dataset.state = el.dataset.state === "open" ? "closed" : "open";
}); });
folder.nextElementSibling.dataset.state = folder.nextElementSibling.dataset.state === "open" ? "closed" : "open"; var newState = folder.nextElementSibling.dataset.state === "open" ? "closed" : "open";
folder.nextElementSibling.dataset.state = newState;
folder.setAttribute('aria-expanded', newState === 'open' ? 'true' : 'false');
}); });
}); });
}); });

View File

@@ -1,25 +1,102 @@
(function () { (function () {
const languageSwitchers = document.querySelectorAll('.hextra-language-switcher'); const languageSwitchers = document.querySelectorAll('.hextra-language-switcher');
const closeSwitcher = (switcher, focusSwitcher = false) => {
switcher.dataset.state = 'closed';
switcher.setAttribute('aria-expanded', 'false');
const optionsElement = switcher.nextElementSibling;
optionsElement.classList.add('hx:hidden');
if (focusSwitcher) {
switcher.focus();
}
};
const openSwitcher = (switcher, focusTarget = "none") => {
switcher.dataset.state = 'open';
switcher.setAttribute('aria-expanded', 'true');
const optionsElement = switcher.nextElementSibling;
if (optionsElement.classList.contains('hx:hidden')) {
toggleMenu(switcher);
} else {
resizeMenu(switcher);
}
if (focusTarget !== "none") {
const items = Array.from(optionsElement.querySelectorAll('[role="menuitem"]'));
if (items.length > 0) {
const target = focusTarget === "last" ? items[items.length - 1] : items[0];
target.focus();
}
}
};
languageSwitchers.forEach((switcher) => { languageSwitchers.forEach((switcher) => {
switcher.addEventListener('click', (e) => { switcher.addEventListener('click', (e) => {
e.preventDefault(); e.preventDefault();
switcher.dataset.state = switcher.dataset.state === 'open' ? 'closed' : 'open'; if (switcher.dataset.state === 'open') {
closeSwitcher(switcher);
} else {
openSwitcher(switcher);
}
});
toggleMenu(switcher); switcher.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown') {
e.preventDefault();
openSwitcher(switcher, 'first');
} else if (e.key === 'ArrowUp') {
e.preventDefault();
openSwitcher(switcher, 'last');
}
}); });
}); });
window.addEventListener("resize", () => languageSwitchers.forEach(resizeMenu)) document.querySelectorAll('.hextra-language-options[role=menu]').forEach((menu) => {
menu.addEventListener('keydown', (e) => {
const items = Array.from(menu.querySelectorAll('[role="menuitem"]'));
if (items.length === 0) return;
// Dismiss language switcher when clicking outside const currentIndex = items.indexOf(document.activeElement);
let newIndex;
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
newIndex = (currentIndex + 1) % items.length;
items[newIndex].focus();
break;
case 'ArrowUp':
e.preventDefault();
newIndex = (currentIndex - 1 + items.length) % items.length;
items[newIndex].focus();
break;
case 'Home':
e.preventDefault();
items[0].focus();
break;
case 'End':
e.preventDefault();
items[items.length - 1].focus();
break;
case 'Escape': {
e.preventDefault();
const switcher = menu.previousElementSibling;
if (switcher) {
closeSwitcher(switcher, true);
}
break;
}
}
});
});
window.addEventListener("resize", () => languageSwitchers.forEach(resizeMenu));
// Dismiss language switcher when clicking outside.
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
if (e.target.closest('.hextra-language-switcher') === null) { if (!e.target.closest('.hextra-language-switcher') && !e.target.closest('.hextra-language-options')) {
languageSwitchers.forEach((switcher) => { languageSwitchers.forEach((switcher) => {
switcher.dataset.state = 'closed'; closeSwitcher(switcher);
const optionsElement = switcher.nextElementSibling;
optionsElement.classList.add('hx:hidden');
}); });
} }
}); });

View File

@@ -3,6 +3,24 @@
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
const menu = document.querySelector('.hextra-hamburger-menu'); const menu = document.querySelector('.hextra-hamburger-menu');
const sidebarContainer = document.querySelector('.hextra-sidebar-container'); const sidebarContainer = document.querySelector('.hextra-sidebar-container');
const mobileQuery = window.matchMedia('(max-width: 767px)');
function isMenuOpen() {
return menu.querySelector('svg').classList.contains('open');
}
// On mobile, the sidebar is off-screen so hide it from assistive tech
function syncAriaHidden() {
if (mobileQuery.matches) {
sidebarContainer.setAttribute('aria-hidden', isMenuOpen() ? 'false' : 'true');
} else {
sidebarContainer.removeAttribute('aria-hidden');
}
}
// Set initial state
syncAriaHidden();
mobileQuery.addEventListener('change', syncAriaHidden);
function toggleMenu() { function toggleMenu() {
// Toggle the hamburger menu // Toggle the hamburger menu
@@ -15,6 +33,19 @@ document.addEventListener('DOMContentLoaded', function () {
// When the menu is open, we want to prevent the body from scrolling // When the menu is open, we want to prevent the body from scrolling
document.body.classList.toggle('hx:overflow-hidden'); document.body.classList.toggle('hx:overflow-hidden');
document.body.classList.toggle('hx:md:overflow-auto'); document.body.classList.toggle('hx:md:overflow-auto');
// Sync aria-expanded and aria-hidden
const isOpen = isMenuOpen();
menu.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
syncAriaHidden();
// Move focus into sidebar when opening, restore when closing
if (isOpen) {
const firstFocusable = sidebarContainer.querySelector('a, button, input, [tabindex="0"]');
if (firstFocusable) firstFocusable.focus();
} else {
menu.focus();
}
} }
menu.addEventListener('click', (e) => { menu.addEventListener('click', (e) => {
@@ -22,6 +53,13 @@ document.addEventListener('DOMContentLoaded', function () {
toggleMenu(); toggleMenu();
}); });
// Close menu on Escape key (mobile only)
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && mobileQuery.matches && isMenuOpen()) {
toggleMenu();
}
});
// Select all anchor tags in the sidebar container // Select all anchor tags in the sidebar container
const sidebarLinks = sidebarContainer.querySelectorAll('a'); const sidebarLinks = sidebarContainer.querySelectorAll('a');

View File

@@ -1,61 +1,125 @@
(function () { (function () {
const hiddenClass = "hx:hidden"; const hiddenClass = "hx:hidden";
const dropdownToggles = document.querySelectorAll(".hextra-nav-menu-toggle"); const dropdownToggles = document.querySelectorAll(".hextra-nav-menu-toggle");
const closeDropdown = (toggle, focusToggle = false) => {
toggle.dataset.state = "closed";
toggle.setAttribute("aria-expanded", "false");
const menuItemsElement = toggle.nextElementSibling;
menuItemsElement.classList.add(hiddenClass);
if (focusToggle) {
toggle.focus();
}
};
const openDropdown = (toggle, focusTarget = "none") => {
// Close all other dropdowns first.
dropdownToggles.forEach((otherToggle) => {
if (otherToggle !== toggle) {
closeDropdown(otherToggle);
}
});
toggle.dataset.state = "open";
toggle.setAttribute("aria-expanded", "true");
const menuItemsElement = toggle.nextElementSibling;
// Position dropdown centered with toggle.
menuItemsElement.style.position = "absolute";
menuItemsElement.style.top = "100%";
menuItemsElement.style.left = "50%";
menuItemsElement.style.transform = "translateX(-50%)";
menuItemsElement.style.zIndex = "1000";
menuItemsElement.classList.remove(hiddenClass);
if (focusTarget !== "none") {
const items = Array.from(menuItemsElement.querySelectorAll('[role="menuitem"]'));
if (items.length > 0) {
const target = focusTarget === "last" ? items[items.length - 1] : items[0];
target.focus();
}
}
};
dropdownToggles.forEach((toggle) => { dropdownToggles.forEach((toggle) => {
toggle.addEventListener("click", (e) => { toggle.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
// Close all other dropdowns first // Toggle current dropdown.
dropdownToggles.forEach((otherToggle) => {
if (otherToggle !== toggle) {
otherToggle.dataset.state = "closed";
const otherMenuItems = otherToggle.nextElementSibling;
otherMenuItems.classList.add(hiddenClass);
}
});
// Toggle current dropdown
const isOpen = toggle.dataset.state === "open"; const isOpen = toggle.dataset.state === "open";
toggle.dataset.state = isOpen ? "closed" : "open"; if (isOpen) {
const menuItemsElement = toggle.nextElementSibling; closeDropdown(toggle);
if (!isOpen) {
// Position dropdown centered with toggle
menuItemsElement.style.position = "absolute";
menuItemsElement.style.top = "100%";
menuItemsElement.style.left = "50%";
menuItemsElement.style.transform = "translateX(-50%)";
menuItemsElement.style.zIndex = "1000";
// Show dropdown
menuItemsElement.classList.remove(hiddenClass);
} else { } else {
// Hide dropdown openDropdown(toggle);
menuItemsElement.classList.add(hiddenClass); }
});
toggle.addEventListener("keydown", (e) => {
if (e.key === "ArrowDown") {
e.preventDefault();
openDropdown(toggle, "first");
} else if (e.key === "ArrowUp") {
e.preventDefault();
openDropdown(toggle, "last");
} }
}); });
}); });
// Dismiss dropdown when clicking outside document.querySelectorAll(".hextra-nav-menu-items[role=menu]").forEach((menu) => {
menu.addEventListener("keydown", (e) => {
const items = Array.from(menu.querySelectorAll('[role="menuitem"]'));
if (items.length === 0) return;
const currentIndex = items.indexOf(document.activeElement);
let newIndex;
switch (e.key) {
case "ArrowDown":
e.preventDefault();
newIndex = (currentIndex + 1) % items.length;
items[newIndex].focus();
break;
case "ArrowUp":
e.preventDefault();
newIndex = (currentIndex - 1 + items.length) % items.length;
items[newIndex].focus();
break;
case "Home":
e.preventDefault();
items[0].focus();
break;
case "End":
e.preventDefault();
items[items.length - 1].focus();
break;
case "Escape": {
e.preventDefault();
const toggle = menu.previousElementSibling;
if (toggle) {
closeDropdown(toggle, true);
}
break;
}
}
});
});
// Dismiss dropdown when clicking outside.
document.addEventListener("click", (e) => { document.addEventListener("click", (e) => {
if (e.target.closest(".hextra-nav-menu-toggle") === null) { if (!e.target.closest(".hextra-nav-menu-toggle") && !e.target.closest(".hextra-nav-menu-items")) {
dropdownToggles.forEach((toggle) => { dropdownToggles.forEach((toggle) => {
toggle.dataset.state = "closed"; closeDropdown(toggle);
const menuItemsElement = toggle.nextElementSibling;
menuItemsElement.classList.add(hiddenClass);
}); });
} }
}); });
// Close dropdowns on escape key // Close dropdowns on escape key.
document.addEventListener("keydown", (e) => { document.addEventListener("keydown", (e) => {
if (e.key === "Escape") { if (e.key === "Escape") {
dropdownToggles.forEach((toggle) => { dropdownToggles.forEach((toggle) => {
toggle.dataset.state = "closed"; if (toggle.dataset.state === "open") {
const menuItemsElement = toggle.nextElementSibling; closeDropdown(toggle, true);
menuItemsElement.classList.add(hiddenClass); }
}); });
} }
}); });

View File

@@ -67,6 +67,7 @@ document.addEventListener('DOMContentLoaded', () => {
dropdownToggles.forEach(t => { dropdownToggles.forEach(t => {
if (t !== toggle) { if (t !== toggle) {
t.dataset.state = 'closed'; t.dataset.state = 'closed';
t.setAttribute('aria-expanded', 'false');
const otherContainer = t.closest('.hextra-page-context-menu'); const otherContainer = t.closest('.hextra-page-context-menu');
const otherMenu = otherContainer.querySelector('.hextra-page-context-menu-dropdown'); const otherMenu = otherContainer.querySelector('.hextra-page-context-menu-dropdown');
const otherChevron = t.querySelector('[data-chevron]'); const otherChevron = t.querySelector('[data-chevron]');
@@ -79,6 +80,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Toggle current // Toggle current
toggle.dataset.state = isOpen ? 'closed' : 'open'; toggle.dataset.state = isOpen ? 'closed' : 'open';
toggle.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
menu.classList.toggle('hx:hidden', isOpen); menu.classList.toggle('hx:hidden', isOpen);
// Rotate chevron icon // Rotate chevron icon
@@ -95,6 +97,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (isOutside) { if (isOutside) {
dropdownToggles.forEach(toggle => { dropdownToggles.forEach(toggle => {
toggle.dataset.state = 'closed'; toggle.dataset.state = 'closed';
toggle.setAttribute('aria-expanded', 'false');
const container = toggle.closest('.hextra-page-context-menu'); const container = toggle.closest('.hextra-page-context-menu');
const menu = container.querySelector('.hextra-page-context-menu-dropdown'); const menu = container.querySelector('.hextra-page-context-menu-dropdown');
const chevron = toggle.querySelector('[data-chevron]'); const chevron = toggle.querySelector('[data-chevron]');
@@ -106,6 +109,19 @@ document.addEventListener('DOMContentLoaded', () => {
} }
}); });
// Close dropdown on Escape key and return focus to toggle
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
dropdownToggles.forEach(toggle => {
if (toggle.dataset.state === 'open') {
const container = toggle.closest('.hextra-page-context-menu');
closeDropdown(container);
toggle.focus();
}
});
}
});
// Helper to close dropdown // Helper to close dropdown
const closeDropdown = (container) => { const closeDropdown = (container) => {
if (!container) return; if (!container) return;
@@ -117,6 +133,7 @@ document.addEventListener('DOMContentLoaded', () => {
const chevron = toggle.querySelector('[data-chevron]'); const chevron = toggle.querySelector('[data-chevron]');
toggle.dataset.state = 'closed'; toggle.dataset.state = 'closed';
toggle.setAttribute('aria-expanded', 'false');
menu.classList.add('hx:hidden'); menu.classList.add('hx:hidden');
if (chevron) { if (chevron) {
chevron.style.transform = ''; chevron.style.transform = '';

View File

@@ -8,9 +8,10 @@ function enableCollapsibles() {
buttons.forEach(function (button) { buttons.forEach(function (button) {
button.addEventListener("click", function (e) { button.addEventListener("click", function (e) {
e.preventDefault(); e.preventDefault();
const list = button.parentElement.parentElement; const list = button.closest('li');
if (list) { if (list) {
list.classList.toggle("open") list.classList.toggle("open");
button.setAttribute('aria-expanded', list.classList.contains('open') ? 'true' : 'false');
} }
}); });
}); });

View File

@@ -7,14 +7,15 @@
tab.setAttribute('aria-selected', 'true'); tab.setAttribute('aria-selected', 'true');
tab.tabIndex = 0; tab.tabIndex = 0;
} else { } else {
tab.removeAttribute('aria-selected'); tab.setAttribute('aria-selected', 'false');
tab.removeAttribute('tabindex'); tab.tabIndex = -1;
} }
}); });
const panelsContainer = container.parentElement.nextElementSibling; const panelsContainer = container.parentElement.nextElementSibling;
if (!panelsContainer) return; if (!panelsContainer) return;
Array.from(panelsContainer.children).forEach((panel, i) => { Array.from(panelsContainer.children).forEach((panel, i) => {
panel.dataset.state = i === index ? 'selected' : ''; panel.dataset.state = i === index ? 'selected' : '';
panel.setAttribute('aria-hidden', i === index ? 'false' : 'true');
if (i === index) { if (i === index) {
panel.tabIndex = 0; panel.tabIndex = 0;
} else { } else {
@@ -53,5 +54,48 @@
updateGroup(container, index); updateGroup(container, index);
} }
}); });
// Keyboard navigation for tabs
button.addEventListener('keydown', function (e) {
const container = button.parentElement;
const tabs = Array.from(container.querySelectorAll('.hextra-tabs-toggle'));
const currentIndex = tabs.indexOf(button);
let newIndex;
switch (e.key) {
case 'ArrowRight':
case 'ArrowDown':
e.preventDefault();
newIndex = (currentIndex + 1) % tabs.length;
break;
case 'ArrowLeft':
case 'ArrowUp':
e.preventDefault();
newIndex = (currentIndex - 1 + tabs.length) % tabs.length;
break;
case 'Home':
e.preventDefault();
newIndex = 0;
break;
case 'End':
e.preventDefault();
newIndex = tabs.length - 1;
break;
default:
return;
}
if (container.dataset.tabGroup) {
const tabGroupValue = container.dataset.tabGroup;
const key = encodeURIComponent(tabGroupValue);
document
.querySelectorAll('[data-tab-group="' + tabGroupValue + '"]')
.forEach((grp) => updateGroup(grp, newIndex));
localStorage.setItem('hextra-tab-' + key, newIndex.toString());
} else {
updateGroup(container, newIndex);
}
tabs[newIndex].focus();
});
}); });
})(); })();

View File

@@ -0,0 +1,15 @@
document.addEventListener("DOMContentLoaded", function () {
// Hugo task lists render bare checkboxes; provide an accessible name.
document.querySelectorAll("main#content li > input[type='checkbox']").forEach(function (checkbox) {
if (checkbox.hasAttribute("aria-label") || checkbox.hasAttribute("aria-labelledby")) {
return;
}
var listItem = checkbox.closest("li");
if (!listItem) return;
var labelText = listItem.textContent.replace(/\s+/g, " ").trim();
if (labelText) {
checkbox.setAttribute("aria-label", labelText);
}
});
});

View File

@@ -4,12 +4,15 @@
const themes = ["light", "dark"]; const themes = ["light", "dark"];
const themeToggleButtons = document.querySelectorAll(".hextra-theme-toggle"); const themeToggleButtons = document.querySelectorAll(".hextra-theme-toggle");
const themeToggleOptions = document.querySelectorAll(".hextra-theme-toggle-options p"); const themeToggleOptions = document.querySelectorAll(".hextra-theme-toggle-options button[role=menuitemradio]");
function applyTheme(theme) { function applyTheme(theme) {
theme = themes.includes(theme) ? theme : "system"; theme = themes.includes(theme) ? theme : "system";
themeToggleButtons.forEach((btn) => btn.parentElement.dataset.theme = theme ); themeToggleButtons.forEach((btn) => btn.parentElement.dataset.theme = theme );
themeToggleOptions.forEach((option) => {
option.setAttribute('aria-checked', option.dataset.item === theme ? 'true' : 'false');
});
localStorage.setItem("color-theme", theme); localStorage.setItem("color-theme", theme);
} }
@@ -36,7 +39,16 @@
toggler.addEventListener("click", function (e) { toggler.addEventListener("click", function (e) {
e.preventDefault(); e.preventDefault();
toggler.dataset.state = toggler.dataset.state === 'open' ? 'closed' : 'open';
toggleMenu(toggler); toggleMenu(toggler);
const isOpen = toggler.dataset.state === 'open';
toggler.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
// Focus first menuitem when opening
if (isOpen) {
const firstItem = toggler.nextElementSibling.querySelector('button[role=menuitemradio]');
if (firstItem) firstItem.focus();
}
}); });
}); });
@@ -47,11 +59,50 @@
if (e.target.closest('.hextra-theme-toggle') === null) { if (e.target.closest('.hextra-theme-toggle') === null) {
themeToggleButtons.forEach((toggler) => { themeToggleButtons.forEach((toggler) => {
toggler.dataset.state = 'closed'; toggler.dataset.state = 'closed';
toggler.setAttribute('aria-expanded', 'false');
toggler.nextElementSibling.classList.add('hx:hidden'); toggler.nextElementSibling.classList.add('hx:hidden');
}); });
} }
}); });
// Keyboard navigation for the theme menu
document.querySelectorAll('.hextra-theme-toggle-options[role=menu]').forEach(function (menu) {
menu.addEventListener('keydown', function (e) {
const items = Array.from(menu.querySelectorAll('button[role=menuitemradio]'));
const currentIndex = items.indexOf(document.activeElement);
let newIndex;
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
newIndex = (currentIndex + 1) % items.length;
items[newIndex].focus();
break;
case 'ArrowUp':
e.preventDefault();
newIndex = (currentIndex - 1 + items.length) % items.length;
items[newIndex].focus();
break;
case 'Home':
e.preventDefault();
items[0].focus();
break;
case 'End':
e.preventDefault();
items[items.length - 1].focus();
break;
case 'Escape':
e.preventDefault();
var toggler = menu.previousElementSibling;
toggler.dataset.state = 'closed';
toggler.setAttribute('aria-expanded', 'false');
menu.classList.add('hx:hidden');
toggler.focus();
break;
}
});
});
// Listen for system theme changes // Listen for system theme changes
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => { window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
if (localStorage.getItem("color-theme") === "system") { if (localStorage.getItem("color-theme") === "system") {

View File

@@ -47,10 +47,12 @@ document.addEventListener("DOMContentLoaded", function () {
// Remove active class from previous link // Remove active class from previous link
if (currentActiveLink) { if (currentActiveLink) {
currentActiveLink.classList.remove("hextra-toc-active"); currentActiveLink.classList.remove("hextra-toc-active");
currentActiveLink.removeAttribute("aria-current");
} }
// Add active class to current link // Add active class to current link
targetLink.classList.add("hextra-toc-active"); targetLink.classList.add("hextra-toc-active");
targetLink.setAttribute("aria-current", "location");
currentActiveLink = targetLink; currentActiveLink = targetLink;
} }
}, },
@@ -74,8 +76,10 @@ document.addEventListener("DOMContentLoaded", function () {
if (currentActiveLink) { if (currentActiveLink) {
currentActiveLink.classList.remove("hextra-toc-active"); currentActiveLink.classList.remove("hextra-toc-active");
currentActiveLink.removeAttribute("aria-current");
} }
targetLink.classList.add("hextra-toc-active"); targetLink.classList.add("hextra-toc-active");
targetLink.setAttribute("aria-current", "location");
currentActiveLink = targetLink; currentActiveLink = targetLink;
// Re-enable observer after scroll settles // Re-enable observer after scroll settles

View File

@@ -21,6 +21,7 @@ document.addEventListener("DOMContentLoaded", function () {
(function () { (function () {
const searchDataURL = '{{ $searchData.RelPermalink }}'; const searchDataURL = '{{ $searchData.RelPermalink }}';
const resultsFoundTemplate = '{{ (T "resultsFound") | default "%d results found" }}';
const inputElements = document.querySelectorAll('.hextra-search-input'); const inputElements = document.querySelectorAll('.hextra-search-input');
for (const el of inputElements) { for (const el of inputElements) {
@@ -389,6 +390,12 @@ document.addEventListener("DOMContentLoaded", function () {
if (!results.length) { if (!results.length) {
resultsElement.innerHTML = `<span class="hextra-search-no-result">{{ $noResultsFound | safeHTML }}</span>`; resultsElement.innerHTML = `<span class="hextra-search-no-result">{{ $noResultsFound | safeHTML }}</span>`;
// Announce no results to screen readers
const wrapper = resultsElement.closest('.hextra-search-wrapper');
const statusEl = wrapper ? wrapper.querySelector('.hextra-search-status') : null;
if (statusEl) {
statusEl.textContent = '{{ $noResultsFound | safeHTML }}';
}
return; return;
} }
@@ -472,5 +479,14 @@ document.addEventListener("DOMContentLoaded", function () {
} }
resultsElement.appendChild(fragment); resultsElement.appendChild(fragment);
resultsElement.dataset.count = results.length; resultsElement.dataset.count = results.length;
// Announce results count to screen readers
const wrapper = resultsElement.closest('.hextra-search-wrapper');
const statusEl = wrapper ? wrapper.querySelector('.hextra-search-status') : null;
if (statusEl) {
statusEl.textContent = results.length > 0
? resultsFoundTemplate.replace('%d', results.length.toString())
: '{{ $noResultsFound | safeHTML }}';
}
} }
})(); })();

View File

@@ -14,7 +14,7 @@ prev: /docs
می‌توانید با استفاده از مخزن قالب فوق به سرعت شروع به کار کنید. می‌توانید با استفاده از مخزن قالب فوق به سرعت شروع به کار کنید.
<img src="https://docs.github.com/assets/cb-77734/mw-1440/images/help/repository/use-this-template-button.webp" width="500"> <img src="https://docs.github.com/assets/cb-77734/mw-1440/images/help/repository/use-this-template-button.webp" width="500" alt="صفحه مخزن GitHub که دکمه Use this template را نشان می‌دهد">
ما یک [گردش کار GitHub Actions](https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow) ارائه کرده‌ایم که می‌تواند به صورت خودکار سایت شما را ساخته و در GitHub Pages مستقر کند و به صورت رایگان میزبانی کند. ما یک [گردش کار GitHub Actions](https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow) ارائه کرده‌ایم که می‌تواند به صورت خودکار سایت شما را ساخته و در GitHub Pages مستقر کند و به صورت رایگان میزبانی کند.
برای گزینه‌های بیشتر، [استقرار سایت](../guide/deploy-site) را بررسی کنید. برای گزینه‌های بیشتر، [استقرار سایت](../guide/deploy-site) را بررسی کنید.

View File

@@ -14,7 +14,7 @@ prev: /docs
上記のテンプレートリポジトリを使用して、すぐに始めることができます。 上記のテンプレートリポジトリを使用して、すぐに始めることができます。
<img src="https://docs.github.com/assets/cb-77734/mw-1440/images/help/repository/use-this-template-button.webp" width="500"> <img src="https://docs.github.com/assets/cb-77734/mw-1440/images/help/repository/use-this-template-button.webp" width="500" alt="Use this template ボタンが表示された GitHub リポジトリページ">
[GitHub Actions ワークフロー](https://docs.github.com/ja/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow)を提供しており、サイトを自動的にビルドして GitHub Pages にデプロイし、無料でホストすることができます。 [GitHub Actions ワークフロー](https://docs.github.com/ja/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow)を提供しており、サイトを自動的にビルドして GitHub Pages にデプロイし、無料でホストすることができます。
その他のオプションについては、[サイトのデプロイ](../guide/deploy-site)を確認してください。 その他のオプションについては、[サイトのデプロイ](../guide/deploy-site)を確認してください。

View File

@@ -14,7 +14,7 @@ prev: /docs
You could quickly get started by using the above template repository. You could quickly get started by using the above template repository.
<img src="https://docs.github.com/assets/cb-77734/mw-1440/images/help/repository/use-this-template-button.webp" width="500"> <img src="https://docs.github.com/assets/cb-77734/mw-1440/images/help/repository/use-this-template-button.webp" width="500" alt="GitHub repository page showing the Use this template button">
We have provided a [GitHub Actions workflow](https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow) which can help automatically build and deploy your site to GitHub Pages, and host it for free. We have provided a [GitHub Actions workflow](https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow) which can help automatically build and deploy your site to GitHub Pages, and host it for free.
For more options, check out [Deploy Site](../guide/deploy-site). For more options, check out [Deploy Site](../guide/deploy-site).

View File

@@ -14,7 +14,7 @@ prev: /docs
您可以通过使用上述模板仓库快速开始。 您可以通过使用上述模板仓库快速开始。
<img src="https://docs.github.com/assets/cb-77734/mw-1440/images/help/repository/use-this-template-button.webp" width="500"> <img src="https://docs.github.com/assets/cb-77734/mw-1440/images/help/repository/use-this-template-button.webp" width="500" alt="显示“Use this template”按钮的 GitHub 仓库页面">
我们提供了一个[GitHub Actions工作流](https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow)可以帮助自动构建并将您的站点部署到GitHub Pages并免费托管。 我们提供了一个[GitHub Actions工作流](https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow)可以帮助自动构建并将您的站点部署到GitHub Pages并免费托管。
更多选项,请查看[部署站点](../guide/deploy-site)。 更多选项,请查看[部署站点](../guide/deploy-site)。

View File

@@ -9,6 +9,8 @@ next: /docs/guide/deploy-site
این شورتکدها کمتر پایدار در نظر گرفته می‌شوند و ممکن است هر زمان تغییر کنند. این شورتکدها کمتر پایدار در نظر گرفته می‌شوند و ممکن است هر زمان تغییر کنند.
{{< /callout >}} {{< /callout >}}
## نشان
### أمثلة ### أمثلة
{{< badge "default" >}}&nbsp; {{< badge "default" >}}&nbsp;

View File

@@ -9,6 +9,8 @@ next: /docs/guide/deploy-site
これらのショートコードは安定性が低く、いつでも変更される可能性があります。 これらのショートコードは安定性が低く、いつでも変更される可能性があります。
{{< /callout >}} {{< /callout >}}
## バッジ
### 例 ### 例
{{< badge "default" >}}&nbsp; {{< badge "default" >}}&nbsp;

View File

@@ -9,6 +9,8 @@ next: /docs/guide/deploy-site
这些短代码稳定性较低,可能随时变更。 这些短代码稳定性较低,可能随时变更。
{{< /callout >}} {{< /callout >}}
## 徽章
### 示例 ### 示例
{{< badge "default" >}}&nbsp; {{< badge "default" >}}&nbsp;

47
docs/hugo_stats.json generated
View File

@@ -30,6 +30,7 @@
"h5", "h5",
"h6", "h6",
"head", "head",
"header",
"hr", "hr",
"html", "html",
"iframe", "iframe",
@@ -147,10 +148,13 @@
"hextra-scrollbar", "hextra-scrollbar",
"hextra-search-input", "hextra-search-input",
"hextra-search-results", "hextra-search-results",
"hextra-search-status",
"hextra-search-wrapper", "hextra-search-wrapper",
"hextra-sidebar-active-item", "hextra-sidebar-active-item",
"hextra-sidebar-children",
"hextra-sidebar-collapsible-button", "hextra-sidebar-collapsible-button",
"hextra-sidebar-container", "hextra-sidebar-container",
"hextra-sidebar-item",
"hextra-steps", "hextra-steps",
"hextra-success-icon", "hextra-success-icon",
"hextra-tabs-panel", "hextra-tabs-panel",
@@ -160,10 +164,12 @@
"hextra-toc", "hextra-toc",
"hide-tail", "hide-tail",
"highlight", "highlight",
"hover:hx:no-underline",
"hx:-mb-0.5", "hx:-mb-0.5",
"hx:-ml-2", "hx:-ml-2",
"hx:-mr-2", "hx:-mr-2",
"hx:-mt-20", "hx:-mt-20",
"hx:-translate-y-1/2",
"hx:absolute", "hx:absolute",
"hx:active:bg-gray-400/20", "hx:active:bg-gray-400/20",
"hx:active:opacity-50", "hx:active:opacity-50",
@@ -212,6 +218,7 @@
"hx:bg-yellow-50", "hx:bg-yellow-50",
"hx:block", "hx:block",
"hx:border", "hx:border",
"hx:border-0",
"hx:border-amber-200", "hx:border-amber-200",
"hx:border-b", "hx:border-b",
"hx:border-b-2", "hx:border-b-2",
@@ -294,9 +301,9 @@
"hx:dark:border-white/10", "hx:dark:border-white/10",
"hx:dark:border-yellow-200/30", "hx:dark:border-yellow-200/30",
"hx:dark:contrast-more:border-neutral-400", "hx:dark:contrast-more:border-neutral-400",
"hx:dark:focus:bg-dark", "hx:dark:focus-visible:bg-dark",
"hx:dark:focus:ring-primary-800",
"hx:dark:from-gray-100", "hx:dark:from-gray-100",
"hx:dark:group-hover:text-gray-50",
"hx:dark:hidden", "hx:dark:hidden",
"hx:dark:hover:bg-gray-100/5", "hx:dark:hover:bg-gray-100/5",
"hx:dark:hover:bg-neutral-700", "hx:dark:hover:bg-neutral-700",
@@ -358,11 +365,19 @@
"hx:flex", "hx:flex",
"hx:flex-col", "hx:flex-col",
"hx:flex-wrap", "hx:flex-wrap",
"hx:focus:bg-white", "hx:focus-visible:bg-primary-500",
"hx:focus:hextra-focus", "hx:focus-visible:bg-white",
"hx:focus:outline-hidden", "hx:focus-visible:fixed",
"hx:focus:ring-4", "hx:focus-visible:font-medium",
"hx:focus:ring-primary-300", "hx:focus-visible:left-2",
"hx:focus-visible:not-sr-only",
"hx:focus-visible:px-4",
"hx:focus-visible:py-2",
"hx:focus-visible:rounded-md",
"hx:focus-visible:text-sm",
"hx:focus-visible:text-white",
"hx:focus-visible:top-2",
"hx:focus-visible:z-50",
"hx:font-bold", "hx:font-bold",
"hx:font-extrabold", "hx:font-extrabold",
"hx:font-medium", "hx:font-medium",
@@ -383,10 +398,13 @@
"hx:group", "hx:group",
"hx:group-[.copied]/copybtn:block", "hx:group-[.copied]/copybtn:block",
"hx:group-[.copied]/copybtn:hidden", "hx:group-[.copied]/copybtn:hidden",
"hx:group-data-[active=true]:dark:text-primary-600",
"hx:group-data-[active=true]:text-primary-800",
"hx:group-data-[theme=dark]:hidden", "hx:group-data-[theme=dark]:hidden",
"hx:group-data-[theme=light]:hidden", "hx:group-data-[theme=light]:hidden",
"hx:group-data-[theme=system]:hidden", "hx:group-data-[theme=system]:hidden",
"hx:group-hover/code:opacity-100", "hx:group-hover/code:opacity-100",
"hx:group-hover:text-gray-900",
"hx:group-hover:underline", "hx:group-hover:underline",
"hx:group-open:before:rotate-90", "hx:group-open:before:rotate-90",
"hx:group/code", "hx:group/code",
@@ -403,6 +421,8 @@
"hx:h-[18px]", "hx:h-[18px]",
"hx:h-full", "hx:h-full",
"hx:h-px", "hx:h-px",
"hx:hextra-focus-visible",
"hx:hextra-focus-visible-inset",
"hx:hidden", "hx:hidden",
"hx:hover:bg-gray-100", "hx:hover:bg-gray-100",
"hx:hover:bg-gray-800/5", "hx:hover:bg-gray-800/5",
@@ -462,13 +482,17 @@
"hx:ltr:pl-5", "hx:ltr:pl-5",
"hx:ltr:pl-6", "hx:ltr:pl-6",
"hx:ltr:pl-8", "hx:ltr:pl-8",
"hx:ltr:pr-0", "hx:ltr:pr-1",
"hx:ltr:pr-2", "hx:ltr:pr-2",
"hx:ltr:pr-4", "hx:ltr:pr-4",
"hx:ltr:pr-8",
"hx:ltr:pr-9", "hx:ltr:pr-9",
"hx:ltr:right-1.5", "hx:ltr:right-1.5",
"hx:ltr:right-2",
"hx:ltr:right-3", "hx:ltr:right-3",
"hx:ltr:rotate-180", "hx:ltr:rotate-180",
"hx:ltr:rounded-l-lg",
"hx:ltr:rounded-r-lg",
"hx:ltr:text-right", "hx:ltr:text-right",
"hx:m-[11px]", "hx:m-[11px]",
"hx:max-h-(--menu-height)", "hx:max-h-(--menu-height)",
@@ -554,6 +578,7 @@
"hx:overflow-y-hidden", "hx:overflow-y-hidden",
"hx:overscroll-contain", "hx:overscroll-contain",
"hx:overscroll-x-contain", "hx:overscroll-x-contain",
"hx:p-0",
"hx:p-0.5", "hx:p-0.5",
"hx:p-1", "hx:p-1",
"hx:p-1.5", "hx:p-1.5",
@@ -608,14 +633,17 @@
"hx:rtl:before:rotate-180", "hx:rtl:before:rotate-180",
"hx:rtl:border-r", "hx:rtl:border-r",
"hx:rtl:left-1.5", "hx:rtl:left-1.5",
"hx:rtl:left-2",
"hx:rtl:left-3", "hx:rtl:left-3",
"hx:rtl:md:right-auto", "hx:rtl:md:right-auto",
"hx:rtl:ml-auto", "hx:rtl:ml-auto",
"hx:rtl:mr-1", "hx:rtl:mr-1",
"hx:rtl:mr-3", "hx:rtl:mr-3",
"hx:rtl:mr-auto", "hx:rtl:mr-auto",
"hx:rtl:pl-1",
"hx:rtl:pl-2", "hx:rtl:pl-2",
"hx:rtl:pl-4", "hx:rtl:pl-4",
"hx:rtl:pl-8",
"hx:rtl:pl-9", "hx:rtl:pl-9",
"hx:rtl:pr-12", "hx:rtl:pr-12",
"hx:rtl:pr-16", "hx:rtl:pr-16",
@@ -625,6 +653,8 @@
"hx:rtl:pr-6", "hx:rtl:pr-6",
"hx:rtl:pr-8", "hx:rtl:pr-8",
"hx:rtl:rotate-270", "hx:rtl:rotate-270",
"hx:rtl:rounded-l-lg",
"hx:rtl:rounded-r-lg",
"hx:rtl:text-left", "hx:rtl:text-left",
"hx:scroll-my-6", "hx:scroll-my-6",
"hx:scroll-py-6", "hx:scroll-py-6",
@@ -688,6 +718,7 @@
"hx:text-yellow-900", "hx:text-yellow-900",
"hx:to-gray-600", "hx:to-gray-600",
"hx:top-0", "hx:top-0",
"hx:top-1/2",
"hx:top-16", "hx:top-16",
"hx:top-8", "hx:top-8",
"hx:top-[40%]", "hx:top-[40%]",

View File

@@ -1,15 +1,43 @@
# Accessibility labels (screen reader only)
closeBanner: "Zavřít banner"
menu: "Menu"
mermaidDiagram: "Diagram"
pdfViewer: "Prohlížeč PDF"
permalinkLabel: "Trvalý odkaz na tuto sekci"
playbackTime: "Doba přehrávání"
searchResults: "Výsledky vyhledávání"
skipToContent: "Přejít na obsah"
tableOfContents: "Obsah"
terminalRecording: "Záznam terminálu"
togglePageContextMenu: "Přepnout kontextové menu stránky"
toggleSection: "Přepnout sekci"
# Accessibility live-region/status text
resultsFound: "%d výsledků nalezeno"
# User-facing UI text
archives: "Archiv"
backToTop: "Zpět nahoru" backToTop: "Zpět nahoru"
changeLanguage: "Změnit jazyk" changeLanguage: "Změnit jazyk"
changeTheme: "Změnit vzhled" changeTheme: "Změnit vzhled"
copy: "Kopírovat"
copied: "Zkopírováno!"
copyAsMarkdown: "Kopírovat jako Markdown"
copyPage: "Kopírovat stránku"
copyCode: "Zkopírovat kód" copyCode: "Zkopírovat kód"
copyright: "© 2025 Hextra Project." copyright: "© 2025 Hextra Project."
dark: "Tmavý" dark: "Tmavý"
editThisPage: "Upravit tuto stránku na GitHubu →" editThisPage: "Upravit tuto stránku na GitHubu →"
lastUpdated: "Naposledy změněno" lastUpdated: "Naposledy změněno"
light: "Světlý" light: "Světlý"
next: "Další"
noResultsFound: "Nebylo nic nalezeno." noResultsFound: "Nebylo nic nalezeno."
onThisPage: "Na této stránce" onThisPage: "Na této stránce"
tags: "Tagy" more: "Více"
poweredBy: "Powered by Hextra" poweredBy: "Powered by Hextra"
previous: "Předchozí"
readMore: "Přečíst víc →" readMore: "Přečíst víc →"
searchPlaceholder: "Hledat..." searchPlaceholder: "Hledat..."
system: "Systém"
tags: "Tagy"
viewAsMarkdown: "Zobrazit jako Markdown"

View File

@@ -1,15 +1,43 @@
# Accessibility labels (screen reader only)
closeBanner: "Banner schließen"
menu: "Menü"
mermaidDiagram: "Diagramm"
pdfViewer: "PDF-Betrachter"
permalinkLabel: "Permalink für diesen Abschnitt"
playbackTime: "Wiedergabezeit"
searchResults: "Suchergebnisse"
skipToContent: "Zum Inhalt springen"
tableOfContents: "Inhaltsverzeichnis"
terminalRecording: "Terminalaufzeichnung"
togglePageContextMenu: "Seitenkontextmenü umschalten"
toggleSection: "Abschnitt umschalten"
# Accessibility live-region/status text
resultsFound: "%d Ergebnisse gefunden"
# User-facing UI text
archives: "Archiv"
backToTop: "Nach oben" backToTop: "Nach oben"
changeLanguage: "Sprache ändern" changeLanguage: "Sprache ändern"
changeTheme: "Darstellung ändern" changeTheme: "Darstellung ändern"
copy: "Kopieren"
copied: "Kopiert!"
copyAsMarkdown: "Als Markdown kopieren"
copyPage: "Seite kopieren"
copyCode: "Code kopieren" copyCode: "Code kopieren"
copyright: "© 2025 Hextra Project." copyright: "© 2025 Hextra Project."
dark: "Dunkel" dark: "Dunkel"
editThisPage: "Diese Seite auf GitHub bearbeiten →" editThisPage: "Diese Seite auf GitHub bearbeiten →"
lastUpdated: "Zuletzt aktualisiert am" lastUpdated: "Zuletzt aktualisiert am"
light: "Hell" light: "Hell"
next: "Weiter"
noResultsFound: "Keine Ergebnisse gefunden." noResultsFound: "Keine Ergebnisse gefunden."
onThisPage: "Auf dieser Seite" onThisPage: "Auf dieser Seite"
tags: "Schlagwörter" more: "Mehr"
poweredBy: "Unterstützt durch Hextra" poweredBy: "Unterstützt durch Hextra"
previous: "Zurück"
readMore: "Mehr lesen →" readMore: "Mehr lesen →"
searchPlaceholder: "Suchen..." searchPlaceholder: "Suchen..."
system: "System"
tags: "Schlagwörter"
viewAsMarkdown: "Als Markdown anzeigen"

View File

@@ -1,3 +1,21 @@
# Accessibility labels (screen reader only)
closeBanner: "Close banner"
menu: "Menu"
mermaidDiagram: "Diagram"
pdfViewer: "PDF viewer"
permalinkLabel: "Permalink for this section"
playbackTime: "Playback time"
searchResults: "Search results"
skipToContent: "Skip to content"
tableOfContents: "Table of contents"
terminalRecording: "Terminal recording"
togglePageContextMenu: "Toggle page context menu"
toggleSection: "Toggle section"
# Accessibility live-region/status text
resultsFound: "%d results found"
# User-facing UI text
archives: "Archives" archives: "Archives"
backToTop: "Scroll to top" backToTop: "Scroll to top"
changeLanguage: "Change language" changeLanguage: "Change language"

View File

@@ -1,6 +1,29 @@
# Accessibility labels (screen reader only)
closeBanner: "Cerrar banner"
menu: "Menú"
mermaidDiagram: "Diagrama"
pdfViewer: "Visor de PDF"
permalinkLabel: "Enlace permanente a esta sección"
playbackTime: "Tiempo de reproducción"
searchResults: "Resultados de búsqueda"
skipToContent: "Saltar al contenido"
tableOfContents: "Tabla de contenidos"
terminalRecording: "Grabación de terminal"
togglePageContextMenu: "Alternar menú contextual de la página"
toggleSection: "Alternar sección"
# Accessibility live-region/status text
resultsFound: "%d resultados encontrados"
# User-facing UI text
archives: "Archivos"
backToTop: "Subir al inicio" backToTop: "Subir al inicio"
changeLanguage: "Cambiar idioma" changeLanguage: "Cambiar idioma"
changeTheme: "Cambiar tema" changeTheme: "Cambiar tema"
copy: "Copiar"
copied: "¡Copiado!"
copyAsMarkdown: "Copiar como Markdown"
copyPage: "Copiar página"
copyCode: "Copiar código" copyCode: "Copiar código"
copyright: "© 2025 Proyecto Hextra." copyright: "© 2025 Proyecto Hextra."
dark: "Oscuro" dark: "Oscuro"
@@ -10,9 +33,11 @@ light: "Claro"
next: "Siguiente" next: "Siguiente"
noResultsFound: "No hubo resultados." noResultsFound: "No hubo resultados."
onThisPage: "En esta página" onThisPage: "En esta página"
more: "Más"
poweredBy: "Con tecnología de Hextra" poweredBy: "Con tecnología de Hextra"
previous: "Anterior" previous: "Anterior"
readMore: "Leer más →" readMore: "Leer más →"
searchPlaceholder: "Buscar..." searchPlaceholder: "Buscar..."
system: "Sistema" system: "Sistema"
tags: "Etiquetas" tags: "Etiquetas"
viewAsMarkdown: "Ver como Markdown"

View File

@@ -1,22 +1,43 @@
# Accessibility labels (screen reader only)
closeBanner: "بستن بنر"
menu: "منو"
mermaidDiagram: "نمودار"
pdfViewer: "نمایشگر PDF"
permalinkLabel: "پیوند ثابت به این بخش"
playbackTime: "زمان پخش"
searchResults: "نتایج جستجو"
skipToContent: "رفتن به محتوا"
tableOfContents: "فهرست مطالب"
terminalRecording: "ضبط ترمینال"
togglePageContextMenu: "تغییر وضعیت منوی زمینه صفحه"
toggleSection: "تغییر وضعیت بخش"
# Accessibility live-region/status text
resultsFound: "%d نتیجه یافت شد"
# User-facing UI text
archives: "آرشیو" archives: "آرشیو"
backToTop: "به بالا بروید" backToTop: "به بالا بروید"
changeLanguage: "تغییر زبان" changeLanguage: "تغییر زبان"
changeTheme: "تغییر تم" changeTheme: "تغییر تم"
copy: "کپی"
copied: "کپی شد!"
copyAsMarkdown: "کپی به عنوان Markdown" copyAsMarkdown: "کپی به عنوان Markdown"
copyCode: "کپی کد"
copyPage: "کپی صفحه" copyPage: "کپی صفحه"
copyCode: "کپی کد"
copyright: "© ۲۰۲۴ پروژه هگزترا." copyright: "© ۲۰۲۴ پروژه هگزترا."
dark: "تیره" dark: "تیره"
editThisPage: "ویرایش این صفحه در گیت‌هاب ←" editThisPage: "ویرایش این صفحه در گیت‌هاب ←"
lastUpdated: "آخرین به‌روزرسانی در" lastUpdated: "آخرین به‌روزرسانی در"
light: "روشن" light: "روشن"
next: "بعدی"
noResultsFound: "هیچ نتیجه‌ای پیدا نشد." noResultsFound: "هیچ نتیجه‌ای پیدا نشد."
onThisPage: "در این صفحه" onThisPage: "در این صفحه"
more: "بیشتر" more: "بیشتر"
tags: "برچسب‌ها"
viewAsMarkdown: "مشاهده به عنوان Markdown"
poweredBy: "طراحی شده توسط هگزترا" poweredBy: "طراحی شده توسط هگزترا"
previous: "قبلی"
readMore: "ادامه مطلب ←" readMore: "ادامه مطلب ←"
searchPlaceholder: "جستجو..." searchPlaceholder: "جستجو..."
previous: "قبلی" system: "سیستم"
next: عدی" tags: رچسب‌ها"
viewAsMarkdown: "مشاهده به عنوان Markdown"

View File

@@ -1,6 +1,29 @@
# Accessibility labels (screen reader only)
closeBanner: "Fermer la bannière"
menu: "Menu"
mermaidDiagram: "Diagramme"
pdfViewer: "Lecteur PDF"
permalinkLabel: "Lien permanent vers cette section"
playbackTime: "Temps de lecture"
searchResults: "Résultats de recherche"
skipToContent: "Aller au contenu"
tableOfContents: "Table des matières"
terminalRecording: "Enregistrement du terminal"
togglePageContextMenu: "Basculer le menu contextuel de la page"
toggleSection: "Basculer la section"
# Accessibility live-region/status text
resultsFound: "%d résultats trouvés"
# User-facing UI text
archives: "Archives"
backToTop: "Revenir en haut" backToTop: "Revenir en haut"
changeLanguage: "Changer la langue" changeLanguage: "Changer la langue"
changeTheme: "Thème d'affichage" changeTheme: "Thème d'affichage"
copy: "Copier"
copied: "Copié !"
copyAsMarkdown: "Copier en Markdown"
copyPage: "Copier la page"
copyCode: "Copier le code" copyCode: "Copier le code"
copyright: "© 2025 Hextra Project." copyright: "© 2025 Hextra Project."
dark: "Sombre" dark: "Sombre"
@@ -10,9 +33,11 @@ light: "Clair"
next: "Suivant" next: "Suivant"
noResultsFound: "Pas de résultats trouvés" noResultsFound: "Pas de résultats trouvés"
onThisPage: "Sur cette page" onThisPage: "Sur cette page"
more: "Plus"
poweredBy: "Propulsé par Hextra" poweredBy: "Propulsé par Hextra"
previous: "Précdent" previous: "Précédent"
readMore: "Lire plus →" readMore: "Lire plus →"
searchPlaceholder: "Rechercher..." searchPlaceholder: "Rechercher..."
system: "Système" system: "Système"
tags: "Étiquettes" tags: "Étiquettes"
viewAsMarkdown: "Voir en Markdown"

View File

@@ -1,15 +1,43 @@
# Accessibility labels (screen reader only)
closeBanner: "סגור באנר"
menu: "תפריט"
mermaidDiagram: "תרשים"
pdfViewer: "מציג PDF"
permalinkLabel: "קישור קבוע לפסקה זו"
playbackTime: "זמן ניגון"
searchResults: "תוצאות חיפוש"
skipToContent: "דלג לתוכן"
tableOfContents: "תוכן עניינים"
terminalRecording: "הקלטת מסוף"
togglePageContextMenu: "הצג/הסתר תפריט ההקשר של הדף"
toggleSection: "הצג/הסתר מקטע"
# Accessibility live-region/status text
resultsFound: "%d תוצאות נמצאו"
# User-facing UI text
archives: "ארכיון"
backToTop: "גלול למעלה" backToTop: "גלול למעלה"
changeLanguage: "שנה שפה" changeLanguage: "שנה שפה"
changeTheme: "שנה ערכת צבעים" changeTheme: "שנה ערכת צבעים"
copy: "העתק"
copied: "הועתק!"
copyAsMarkdown: "העתק כ-Markdown"
copyPage: "העתק עמוד"
copyCode: "העתק קוד" copyCode: "העתק קוד"
copyright: "© 2025 פרוייקט Hextra" copyright: "© 2025 פרוייקט Hextra"
dark: "כהה" dark: "כהה"
editThisPage: "← ערוך עמוד זה בגיטהאב" editThisPage: "← ערוך עמוד זה בגיטהאב"
lastUpdated: "עודכן לאחרונה ב" lastUpdated: "עודכן לאחרונה ב"
light: "בהיר" light: "בהיר"
next: "הבא"
noResultsFound: "לא נמצאו תוצאות." noResultsFound: "לא נמצאו תוצאות."
onThisPage: "בעמוד זה" onThisPage: "בעמוד זה"
tags: "תגיות" more: "עוד"
poweredBy: "Hextra מופעל על-ידי" poweredBy: "Hextra מופעל על-ידי"
previous: "הקודם"
readMore: "← קרא עוד" readMore: "← קרא עוד"
searchPlaceholder: "חיפוש..." searchPlaceholder: "חיפוש..."
system: "מערכת"
tags: "תגיות"
viewAsMarkdown: "הצג כ-Markdown"

View File

@@ -1,3 +1,22 @@
# Accessibility labels (screen reader only)
closeBanner: "Chiudi banner"
menu: "Menu"
mermaidDiagram: "Diagramma"
pdfViewer: "Visualizzatore PDF"
permalinkLabel: "Link permanente a questa sezione"
playbackTime: "Tempo di riproduzione"
searchResults: "Risultati della ricerca"
skipToContent: "Vai al contenuto"
tableOfContents: "Indice dei contenuti"
terminalRecording: "Registrazione del terminale"
togglePageContextMenu: "Attiva/disattiva il menu contestuale della pagina"
toggleSection: "Attiva/disattiva sezione"
# Accessibility live-region/status text
resultsFound: "%d risultati trovati"
# User-facing UI text
archives: "Archivi"
backToTop: "Torna all'inizio" backToTop: "Torna all'inizio"
changeLanguage: "Cambia lingua" changeLanguage: "Cambia lingua"
changeTheme: "Cambia tema" changeTheme: "Cambia tema"
@@ -6,6 +25,7 @@ copied: "Copiato!"
copyAsMarkdown: "Copia come Markdown" copyAsMarkdown: "Copia come Markdown"
copyPage: "Copia pagina" copyPage: "Copia pagina"
copyCode: "Copia codice" copyCode: "Copia codice"
copyright: "© 2025 Hextra Project."
dark: "Scuro" dark: "Scuro"
editThisPage: "Modifica questa pagina su GitHub →" editThisPage: "Modifica questa pagina su GitHub →"
lastUpdated: "Ultimo aggiornamento il" lastUpdated: "Ultimo aggiornamento il"
@@ -13,6 +33,7 @@ light: "Chiaro"
next: "Successivo" next: "Successivo"
noResultsFound: "Nessun risultato trovato." noResultsFound: "Nessun risultato trovato."
onThisPage: "In questa pagina" onThisPage: "In questa pagina"
more: "Altro"
poweredBy: "Realizzato da Hextra" poweredBy: "Realizzato da Hextra"
previous: "Precedente" previous: "Precedente"
readMore: "Per saperne di più →" readMore: "Per saperne di più →"

View File

@@ -1,22 +1,43 @@
# Accessibility labels (screen reader only)
closeBanner: "バナーを閉じる"
menu: "メニュー"
mermaidDiagram: "図"
pdfViewer: "PDFビューアー"
permalinkLabel: "このセクションへのパーマリンク"
playbackTime: "再生時間"
searchResults: "検索結果"
skipToContent: "コンテンツにスキップ"
tableOfContents: "目次"
terminalRecording: "ターミナル録画"
togglePageContextMenu: "ページコンテキストメニューの切り替え"
toggleSection: "セクションの切り替え"
# Accessibility live-region/status text
resultsFound: "%d件の結果が見つかりました"
# User-facing UI text
archives: "アーカイブ" archives: "アーカイブ"
backToTop: "トップにスクロール" backToTop: "トップにスクロール"
changeLanguage: "言語を変更" changeLanguage: "言語を変更"
changeTheme: "テーマを変更" changeTheme: "テーマを変更"
copy: "コピー"
copied: "コピーしました!"
copyAsMarkdown: "Markdownとしてコピー" copyAsMarkdown: "Markdownとしてコピー"
copyCode: "コードをコピー"
copyPage: "ページをコピー" copyPage: "ページをコピー"
copyCode: "コードをコピー"
copyright: "© 2025 Hextra プロジェクト。" copyright: "© 2025 Hextra プロジェクト。"
dark: "ダーク" dark: "ダーク"
editThisPage: "このページをGitHubで編集 →" editThisPage: "このページをGitHubで編集 →"
lastUpdated: "最終更新日" lastUpdated: "最終更新日"
light: "ライト" light: "ライト"
next: "次へ"
noResultsFound: "結果が見つかりませんでした。" noResultsFound: "結果が見つかりませんでした。"
onThisPage: "このページの内容" onThisPage: "このページの内容"
more: "その他" more: "その他"
tags: "タグ"
viewAsMarkdown: "Markdownとして表示"
poweredBy: "提供元 Hextra" poweredBy: "提供元 Hextra"
previous: "前へ"
readMore: "もっと読む →" readMore: "もっと読む →"
searchPlaceholder: "検索..." searchPlaceholder: "検索..."
previous: "前へ" system: "システム"
next: "次へ" tags: "タグ"
viewAsMarkdown: "Markdownとして表示"

View File

@@ -1,14 +1,43 @@
# Accessibility labels (screen reader only)
closeBanner: "배너 닫기"
menu: "메뉴"
mermaidDiagram: "다이어그램"
pdfViewer: "PDF 뷰어"
permalinkLabel: "이 섹션에 대한 고유 링크"
playbackTime: "재생 시간"
searchResults: "검색 결과"
skipToContent: "본문으로 건너뛰기"
tableOfContents: "목차"
terminalRecording: "터미널 녹화"
togglePageContextMenu: "페이지 컨텍스트 메뉴 전환"
toggleSection: "섹션 전환"
# Accessibility live-region/status text
resultsFound: "%d개의 결과를 찾았습니다"
# User-facing UI text
archives: "아카이브"
backToTop: "맨위로 스크롤" backToTop: "맨위로 스크롤"
changeLanguage: "언어 변경" changeLanguage: "언어 변경"
changeTheme: "테마 변경" changeTheme: "테마 변경"
copy: "복사"
copied: "복사됨!"
copyAsMarkdown: "Markdown으로 복사"
copyPage: "페이지 복사"
copyCode: "코드 복사"
copyright: "© 2025 Hextra Project." copyright: "© 2025 Hextra Project."
dark: "어두운 테마" dark: "어두운 테마"
editThisPage: "GitHub에서 편집하기 →" editThisPage: "GitHub에서 편집하기 →"
lastUpdated: "마지막 수정 일자" lastUpdated: "마지막 수정 일자"
light: "밝은 테마" light: "밝은 테마"
next: "다음"
noResultsFound: "결과 없음" noResultsFound: "결과 없음"
onThisPage: "페이지 목차" onThisPage: "페이지 목차"
tags: "태그" more: "더보기"
poweredBy: "Hextra로 제작됨" poweredBy: "Hextra로 제작됨"
previous: "이전"
readMore: "더보기 →" readMore: "더보기 →"
searchPlaceholder: "검색..." searchPlaceholder: "검색..."
system: "시스템"
tags: "태그"
viewAsMarkdown: "Markdown으로 보기"

View File

@@ -1,14 +1,43 @@
# Accessibility labels (screen reader only)
closeBanner: "Lukk banner"
menu: "Meny"
mermaidDiagram: "Diagram"
pdfViewer: "PDF-visning"
permalinkLabel: "Permanent lenke til denne seksjonen"
playbackTime: "Avspillingstid"
searchResults: "Søkeresultater"
skipToContent: "Hopp til innhold"
tableOfContents: "Innholdsfortegnelse"
terminalRecording: "Terminalopptak"
togglePageContextMenu: "Veksle sidekontekstmeny"
toggleSection: "Veksle seksjon"
# Accessibility live-region/status text
resultsFound: "%d resultater funnet"
# User-facing UI text
archives: "Arkiv"
backToTop: "Gå til toppen" backToTop: "Gå til toppen"
changeLanguage: "Endre språk" changeLanguage: "Endre språk"
changeTheme: "Endre tema" changeTheme: "Endre tema"
copy: "Kopier"
copied: "Kopiert!"
copyAsMarkdown: "Kopier som Markdown"
copyPage: "Kopier side"
copyCode: "Kopier kode"
copyright: "© 2025 Hextra-prosjektet." copyright: "© 2025 Hextra-prosjektet."
dark: "Mørk" dark: "Mørk"
editThisPage: "Rediger denne siden på GitHub →" editThisPage: "Rediger denne siden på GitHub →"
lastUpdated: "Sist oppdatert" lastUpdated: "Sist oppdatert"
light: "Lys" light: "Lys"
next: "Neste"
noResultsFound: "Fant ingen treff." noResultsFound: "Fant ingen treff."
onThisPage: "På denne siden" onThisPage: "På denne siden"
tags: "Stikkord" more: "Mer"
poweredBy: "Powered by Hextra" poweredBy: "Powered by Hextra"
previous: "Forrige"
readMore: "Les mer →" readMore: "Les mer →"
searchPlaceholder: "Søk..." searchPlaceholder: "Søk..."
system: "System"
tags: "Stikkord"
viewAsMarkdown: "Vis som Markdown"

View File

@@ -1,15 +1,43 @@
# Accessibility labels (screen reader only)
closeBanner: "Banner sluiten"
menu: "Menu"
mermaidDiagram: "Diagram"
pdfViewer: "PDF-weergave"
permalinkLabel: "Permanente link naar deze sectie"
playbackTime: "Afspeeltijd"
searchResults: "Zoekresultaten"
skipToContent: "Ga naar inhoud"
tableOfContents: "Inhoudsopgave"
terminalRecording: "Terminalopname"
togglePageContextMenu: "Paginacontextmenu wisselen"
toggleSection: "Sectie wisselen"
# Accessibility live-region/status text
resultsFound: "%d resultaten gevonden"
# User-facing UI text
archives: "Archief"
backToTop: "Terug naar boven" backToTop: "Terug naar boven"
changeLanguage: "Taal veranderen" changeLanguage: "Taal veranderen"
changeTheme: "Thema aanpassen" changeTheme: "Thema aanpassen"
copy: "Kopiëren"
copied: "Gekopieerd!"
copyAsMarkdown: "Kopieer als Markdown"
copyPage: "Pagina kopiëren"
copyCode: "Kopieer code" copyCode: "Kopieer code"
copyright: "© 2025 Hextra Project." copyright: "© 2025 Hextra Project."
dark: "Donker" dark: "Donker"
editThisPage: "Bewerk deze pagina op GitHub →" editThisPage: "Bewerk deze pagina op GitHub →"
lastUpdated: "Laatst bijgewerkt op" lastUpdated: "Laatst bijgewerkt op"
light: "Licht" light: "Licht"
next: "Volgende"
noResultsFound: "Geen resultaten gevonden." noResultsFound: "Geen resultaten gevonden."
onThisPage: "Op deze pagina" onThisPage: "Op deze pagina"
tags: "Labels" more: "Meer"
poweredBy: "Mogelijk gemaakt door Hextra" poweredBy: "Mogelijk gemaakt door Hextra"
previous: "Vorige"
readMore: "Lees meer →" readMore: "Lees meer →"
searchPlaceholder: "Zoeken..." searchPlaceholder: "Zoeken..."
system: "Systeem"
tags: "Labels"
viewAsMarkdown: "Bekijk als Markdown"

View File

@@ -1,14 +1,43 @@
# Accessibility labels (screen reader only)
closeBanner: "Lukk banner"
menu: "Meny"
mermaidDiagram: "Diagram"
pdfViewer: "PDF-vising"
permalinkLabel: "Permanent lenkje til denne seksjonen"
playbackTime: "Avspelingstid"
searchResults: "Søkjeresultat"
skipToContent: "Hopp til innhald"
tableOfContents: "Innhaldsliste"
terminalRecording: "Terminalopptak"
togglePageContextMenu: "Veksle sidekontekstmeny"
toggleSection: "Veksle seksjon"
# Accessibility live-region/status text
resultsFound: "%d resultat funne"
# User-facing UI text
archives: "Arkiv"
backToTop: "Gå til toppen" backToTop: "Gå til toppen"
changeLanguage: "Endre språk" changeLanguage: "Endre språk"
changeTheme: "Endre tema" changeTheme: "Endre tema"
copy: "Kopier"
copied: "Kopiert!"
copyAsMarkdown: "Kopier som Markdown"
copyPage: "Kopier side"
copyCode: "Kopier kode"
copyright: "© 2025 Hextra-prosjektet." copyright: "© 2025 Hextra-prosjektet."
dark: "Mørk" dark: "Mørk"
editThisPage: "Rediger denne sida på GitHub →" editThisPage: "Rediger denne sida på GitHub →"
lastUpdated: "Sist oppdatert" lastUpdated: "Sist oppdatert"
light: "Ljos" light: "Ljos"
next: "Neste"
noResultsFound: "Fann ingen treff." noResultsFound: "Fann ingen treff."
onThisPage: "På denne sida" onThisPage: "På denne sida"
tags: "Stikkord" more: "Meir"
poweredBy: "Powered by Hextra" poweredBy: "Powered by Hextra"
previous: "Førre"
readMore: "Les meir →" readMore: "Les meir →"
searchPlaceholder: "Søk..." searchPlaceholder: "Søk..."
system: "System"
tags: "Stikkord"
viewAsMarkdown: "Vis som Markdown"

View File

@@ -1,14 +1,43 @@
# Accessibility labels (screen reader only)
closeBanner: "Fechar banner"
menu: "Menu"
mermaidDiagram: "Diagrama"
pdfViewer: "Visualizador de PDF"
permalinkLabel: "Link permanente para esta secção"
playbackTime: "Tempo de reprodução"
searchResults: "Resultados da pesquisa"
skipToContent: "Saltar para o conteúdo"
tableOfContents: "Índice"
terminalRecording: "Gravação de terminal"
togglePageContextMenu: "Alternar menu de contexto da página"
toggleSection: "Alternar secção"
# Accessibility live-region/status text
resultsFound: "%d resultados encontrados"
# User-facing UI text
archives: "Arquivo"
backToTop: "Voltar ao topo" backToTop: "Voltar ao topo"
changeLanguage: "Mudar a língua" changeLanguage: "Mudar a língua"
changeTheme: "Mudar tema" changeTheme: "Mudar tema"
copy: "Copiar"
copied: "Copiado!"
copyAsMarkdown: "Copiar como Markdown"
copyPage: "Copiar página"
copyCode: "Copiar código"
copyright: "© 2025 Projecto Hextra." copyright: "© 2025 Projecto Hextra."
dark: "Escuro" dark: "Escuro"
editThisPage: "Edita esta página no GitHub →" editThisPage: "Edita esta página no GitHub →"
lastUpdated: "Última modificação" lastUpdated: "Última modificação"
light: "Claro" light: "Claro"
next: "Seguinte"
noResultsFound: "Nenhum resultado encontrado" noResultsFound: "Nenhum resultado encontrado"
onThisPage: "Nesta página" onThisPage: "Nesta página"
tags: "Etiquetas" more: "Mais"
poweredBy: "Com a tecnologia de Hextra" poweredBy: "Com a tecnologia de Hextra"
previous: "Anterior"
readMore: "Ler mais →" readMore: "Ler mais →"
searchPlaceholder: "Procurar..." searchPlaceholder: "Procurar..."
system: "Sistema"
tags: "Etiquetas"
viewAsMarkdown: "Ver como Markdown"

View File

@@ -1,15 +1,43 @@
# Accessibility labels (screen reader only)
closeBanner: "Închide bannerul"
menu: "Meniu"
mermaidDiagram: "Diagramă"
pdfViewer: "Vizualizator PDF"
permalinkLabel: "Link permanent către această secțiune"
playbackTime: "Timp de redare"
searchResults: "Rezultatele căutării"
skipToContent: "Salt la conținut"
tableOfContents: "Cuprins"
terminalRecording: "Înregistrare terminal"
togglePageContextMenu: "Comutare meniu contextual pagină"
toggleSection: "Comutare secțiune"
# Accessibility live-region/status text
resultsFound: "%d rezultate găsite"
# User-facing UI text
archives: "Arhivă"
backToTop: "Înapoi sus" backToTop: "Înapoi sus"
changeLanguage: "Schimbă limba" changeLanguage: "Schimbă limba"
changeTheme: "Schimbă tema" changeTheme: "Schimbă tema"
copy: "Copiază"
copied: "Copiat!"
copyAsMarkdown: "Copiază ca Markdown"
copyPage: "Copiază pagina"
copyCode: "Copiază codul" copyCode: "Copiază codul"
copyright: "© 2025 Hextra Project." copyright: "© 2025 Hextra Project."
dark: "Întuneric" dark: "Întuneric"
editThisPage: "Editați această pagină pe GitHub ←" editThisPage: "Editați această pagină pe GitHub ←"
lastUpdated: "Ultima actualizare la" lastUpdated: "Ultima actualizare la"
light: "Lumină" light: "Lumină"
next: "Următor"
noResultsFound: "Nici un rezultat găsit." noResultsFound: "Nici un rezultat găsit."
onThisPage: "Pe această pagină" onThisPage: "Pe această pagină"
tags: "Etichete" more: "Mai mult"
poweredBy: "Susținut de Hextra" poweredBy: "Susținut de Hextra"
previous: "Anterior"
readMore: "Citește mai mult ←" readMore: "Citește mai mult ←"
searchPlaceholder: "Caută..." searchPlaceholder: "Caută..."
system: "Sistem"
tags: "Etichete"
viewAsMarkdown: "Vizualizează ca Markdown"

View File

@@ -1,15 +1,43 @@
backToTop: 'Прокрутить к началу' # Accessibility labels (screen reader only)
changeLanguage: 'Изменить язык' closeBanner: "Закрыть баннер"
changeTheme: 'Изменить тему' menu: "Меню"
copyCode: 'Скопировать код' mermaidDiagram: "Диаграмма"
copyright: '2025 Проект Hextra.' pdfViewer: "Просмотр PDF"
dark: 'Темная' permalinkLabel: "Постоянная ссылка на этот раздел"
editThisPage: 'Отредактировать страницу на GitHub →' playbackTime: "Время воспроизведения"
lastUpdated: 'Последнее обновление' searchResults: "Результаты поиска"
light: 'Светлая' skipToContent: "Перейти к содержимому"
noResultsFound: 'Ничего не найдено.' tableOfContents: "Содержание"
onThisPage: 'На этой странице' terminalRecording: "Запись терминала"
tags: 'Теги' togglePageContextMenu: "Переключить контекстное меню страницы"
poweredBy: 'При поддержке Hextra' toggleSection: "Переключить раздел"
readMore: 'Читать далее →'
searchPlaceholder: 'Поиск...' # Accessibility live-region/status text
resultsFound: "%d результатов найдено"
# User-facing UI text
archives: "Архив"
backToTop: "Прокрутить к началу"
changeLanguage: "Изменить язык"
changeTheme: "Изменить тему"
copy: "Копировать"
copied: "Скопировано!"
copyAsMarkdown: "Копировать как Markdown"
copyPage: "Копировать страницу"
copyCode: "Скопировать код"
copyright: "2025 Проект Hextra."
dark: "Темная"
editThisPage: "Отредактировать страницу на GitHub →"
lastUpdated: "Последнее обновление"
light: "Светлая"
next: "Далее"
noResultsFound: "Ничего не найдено."
onThisPage: "На этой странице"
more: "Ещё"
poweredBy: "При поддержке Hextra"
previous: "Назад"
readMore: "Читать далее →"
searchPlaceholder: "Поиск..."
system: "Система"
tags: "Теги"
viewAsMarkdown: "Просмотреть как Markdown"

View File

@@ -1,14 +1,43 @@
# Accessibility labels (screen reader only)
closeBanner: "Funga bango"
menu: "Menyu"
mermaidDiagram: "Mchoro"
pdfViewer: "Kitazamaji cha PDF"
permalinkLabel: "Kiungo cha kudumu kwa sehemu hii"
playbackTime: "Muda wa kucheza"
searchResults: "Matokeo ya utafutaji"
skipToContent: "Ruka hadi maudhui"
tableOfContents: "Jedwali la yaliyomo"
terminalRecording: "Rekodi ya terminal"
togglePageContextMenu: "Badili menyu ya muktadha wa ukurasa"
toggleSection: "Badili sehemu"
# Accessibility live-region/status text
resultsFound: "Matokeo %d yamepatikana"
# User-facing UI text
archives: "Kumbukumbu"
backToTop: "Tembeza hadi juu" backToTop: "Tembeza hadi juu"
changeLanguage: "Badilisha lugha" changeLanguage: "Badilisha lugha"
changeTheme: "Badilisha mandhari" changeTheme: "Badilisha mandhari"
copy: "Nakili"
copied: "Imenakiliwa!"
copyAsMarkdown: "Nakili kama Markdown"
copyPage: "Nakili ukurasa"
copyCode: "Nakili msimbo"
copyright: "© 2025 Hextra Project." copyright: "© 2025 Hextra Project."
dark: "Meusi" dark: "Meusi"
editThisPage: "Hariri ukurasa huu kwenye GitHub →" editThisPage: "Hariri ukurasa huu kwenye GitHub →"
lastUpdated: "Ilisasishwa mwisho" lastUpdated: "Ilisasishwa mwisho"
light: "Meupe" light: "Meupe"
next: "Ifuatayo"
noResultsFound: "Hakuna matokeo yalipopatikana." noResultsFound: "Hakuna matokeo yalipopatikana."
onThisPage: "Kwenye ukurasa huu" onThisPage: "Kwenye ukurasa huu"
tags: "Lebo" more: "Zaidi"
poweredBy: "Inaendeshwa na Hextra" poweredBy: "Inaendeshwa na Hextra"
previous: "Iliyotangulia"
readMore: "Soma zaidi →" readMore: "Soma zaidi →"
searchPlaceholder: "Tafuta..." searchPlaceholder: "Tafuta..."
system: "Mfumo"
tags: "Lebo"
viewAsMarkdown: "Tazama kama Markdown"

View File

@@ -1,15 +1,43 @@
# Accessibility labels (screen reader only)
closeBanner: "Закрити банер"
menu: "Меню"
mermaidDiagram: "Діаграма"
pdfViewer: "Перегляд PDF"
permalinkLabel: "Постійне посилання на цей розділ"
playbackTime: "Час відтворення"
searchResults: "Результати пошуку"
skipToContent: "Перейти до вмісту"
tableOfContents: "Зміст"
terminalRecording: "Запис терміналу"
togglePageContextMenu: "Перемкнути контекстне меню сторінки"
toggleSection: "Перемкнути розділ"
# Accessibility live-region/status text
resultsFound: "%d результатів знайдено"
# User-facing UI text
archives: "Архів"
backToTop: "Прокрутити до початку" backToTop: "Прокрутити до початку"
changeLanguage: "Змінити мову" changeLanguage: "Змінити мову"
changeTheme: "Змінити тему" changeTheme: "Змінити тему"
copy: "Копіювати"
copied: "Скопійовано!"
copyAsMarkdown: "Копіювати як Markdown"
copyPage: "Копіювати сторінку"
copyCode: "Скопіювати код" copyCode: "Скопіювати код"
copyright: "2025 Проєкт Hextra." copyright: "2025 Проєкт Hextra."
dark: "Темна" dark: "Темна"
editThisPage: "Редагувати цю сторінку на GitHub →" editThisPage: "Редагувати цю сторінку на GitHub →"
lastUpdated: "Востаннє оновлено" lastUpdated: "Востаннє оновлено"
light: "Світла" light: "Світла"
next: "Далі"
noResultsFound: "Не знайдено результатів" noResultsFound: "Не знайдено результатів"
onThisPage: "На цій сторінці" onThisPage: "На цій сторінці"
tags: "Теги" more: "Ще"
poweredBy: "Працює завдяки Hextra" poweredBy: "Працює завдяки Hextra"
previous: "Назад"
readMore: "Читати більше →" readMore: "Читати більше →"
searchPlaceholder: "Пошук..." searchPlaceholder: "Пошук..."
system: "Система"
tags: "Теги"
viewAsMarkdown: "Переглянути як Markdown"

View File

@@ -1,14 +1,43 @@
# Accessibility labels (screen reader only)
closeBanner: "Đóng biểu ngữ"
menu: "Menu"
mermaidDiagram: "Sơ đồ"
pdfViewer: "Trình xem PDF"
permalinkLabel: "Liên kết cố định đến phần này"
playbackTime: "Thời gian phát"
searchResults: "Kết quả tìm kiếm"
skipToContent: "Chuyển đến nội dung"
tableOfContents: "Mục lục"
terminalRecording: "Bản ghi terminal"
togglePageContextMenu: "Bật/tắt menu ngữ cảnh trang"
toggleSection: "Bật/tắt mục"
# Accessibility live-region/status text
resultsFound: "%d kết quả được tìm thấy"
# User-facing UI text
archives: "Lưu trữ"
backToTop: "Lướt lên đầu trang" backToTop: "Lướt lên đầu trang"
changeLanguage: "Đổi ngôn ngữ" changeLanguage: "Đổi ngôn ngữ"
changeTheme: "Đổi chủ đề" changeTheme: "Đổi chủ đề"
copy: "Sao chép"
copied: "Đã sao chép!"
copyAsMarkdown: "Sao chép dạng Markdown"
copyPage: "Sao chép trang"
copyCode: "Sao chép mã"
copyright: "© 2025 Hextra Project." copyright: "© 2025 Hextra Project."
dark: "Tối" dark: "Tối"
editThisPage: "Sửa trang này trên GitHub →" editThisPage: "Sửa trang này trên GitHub →"
lastUpdated: "Lần cuối cập nhật lúc" lastUpdated: "Lần cuối cập nhật lúc"
light: "Sáng" light: "Sáng"
next: "Tiếp"
noResultsFound: "Không tìm thấy kết quả." noResultsFound: "Không tìm thấy kết quả."
onThisPage: "Ở trang này" onThisPage: "Ở trang này"
tags: "Th" more: "Thêm"
poweredBy: "Chạy bởi Hextra" poweredBy: "Chạy bởi Hextra"
previous: "Trước"
readMore: "Đọc thêm →" readMore: "Đọc thêm →"
searchPlaceholder: "Tìm kiếm..." searchPlaceholder: "Tìm kiếm..."
system: "Hệ thống"
tags: "Thẻ"
viewAsMarkdown: "Xem dạng Markdown"

View File

@@ -1,23 +1,43 @@
# Accessibility labels (screen reader only)
closeBanner: "关闭横幅"
menu: "菜单"
mermaidDiagram: "图表"
pdfViewer: "PDF 查看器"
permalinkLabel: "此章节的永久链接"
playbackTime: "播放时间"
searchResults: "搜索结果"
skipToContent: "跳至内容"
tableOfContents: "目录"
terminalRecording: "终端录像"
togglePageContextMenu: "切换页面上下文菜单"
toggleSection: "切换章节"
# Accessibility live-region/status text
resultsFound: "找到 %d 个结果"
# User-facing UI text
archives: "归档" archives: "归档"
backToTop: "返回顶部" backToTop: "返回顶部"
changeLanguage: "切换语言" changeLanguage: "切换语言"
changeTheme: "切换主题" changeTheme: "切换主题"
copy: "复制"
copied: "已复制!"
copyAsMarkdown: "复制为 Markdown" copyAsMarkdown: "复制为 Markdown"
copyCode: "复制代码"
copyPage: "复制页面" copyPage: "复制页面"
copyCode: "复制代码"
copyright: "© 2025 Hextra Project." copyright: "© 2025 Hextra Project."
dark: "深色" dark: "深色"
editThisPage: "在 GitHub 上编辑此页 →" editThisPage: "在 GitHub 上编辑此页 →"
lastUpdated: "最后更新于" lastUpdated: "最后更新于"
light: "浅色" light: "浅色"
next: "下一页"
noResultsFound: "无结果" noResultsFound: "无结果"
onThisPage: "此页上" onThisPage: "此页上"
more: "更多" more: "更多"
tags: "标签"
viewAsMarkdown: "以 Markdown 查看"
poweredBy: "由 Hextra 驱动" poweredBy: "由 Hextra 驱动"
previous: "上一页"
readMore: "更多 →" readMore: "更多 →"
searchPlaceholder: "搜索文档..." searchPlaceholder: "搜索文档..."
previous: "上一页"
next: "下一页"
system: "跟随系统" system: "跟随系统"
tags: "标签"
viewAsMarkdown: "以 Markdown 查看"

View File

@@ -1,14 +1,43 @@
# Accessibility labels (screen reader only)
closeBanner: "關閉橫幅"
menu: "選單"
mermaidDiagram: "圖表"
pdfViewer: "PDF 檢視器"
permalinkLabel: "此章節的永久連結"
playbackTime: "播放時間"
searchResults: "搜尋結果"
skipToContent: "跳至內容"
tableOfContents: "目錄"
terminalRecording: "終端機錄影"
togglePageContextMenu: "切換頁面內容選單"
toggleSection: "切換章節"
# Accessibility live-region/status text
resultsFound: "找到 %d 個結果"
# User-facing UI text
archives: "歸檔"
backToTop: "返回頂部" backToTop: "返回頂部"
changeLanguage: "切換語言" changeLanguage: "切換語言"
changeTheme: "切換主題" changeTheme: "切換主題"
copy: "複製"
copied: "已複製!"
copyAsMarkdown: "複製為 Markdown"
copyPage: "複製頁面"
copyCode: "複製程式碼"
copyright: "© 2025 Hextra Project." copyright: "© 2025 Hextra Project."
dark: "深色" dark: "深色"
editThisPage: "在 GitHub 上編輯此頁 →" editThisPage: "在 GitHub 上編輯此頁 →"
lastUpdated: "最後更新於" lastUpdated: "最後更新於"
light: "淺色" light: "淺色"
next: "下一頁"
noResultsFound: "無結果" noResultsFound: "無結果"
onThisPage: "此頁上" onThisPage: "此頁上"
tags: "標籤" more: "更多"
poweredBy: "由 Hextra 驅動" poweredBy: "由 Hextra 驅動"
previous: "上一頁"
readMore: "更多 →" readMore: "更多 →"
searchPlaceholder: "搜尋文檔..." searchPlaceholder: "搜尋文檔..."
system: "系統"
tags: "標籤"
viewAsMarkdown: "以 Markdown 檢視"

View File

@@ -1,9 +1,9 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="{{ .Site.Language.Lang | default "en" }}" dir="{{ .Site.Language.LanguageDirection | default "ltr" }}">
<body <body
style='font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; height:100vh; text-align:center; display:flex; flex-direction:column; align-items:center; justify-content:center' style='font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; height:100vh; text-align:center; display:flex; flex-direction:column; align-items:center; justify-content:center'
> >
<div> <main id="content">
<style> <style>
body { body {
color: #000; color: #000;
@@ -35,6 +35,6 @@
<div style="display: inline-block; text-align: left"> <div style="display: inline-block; text-align: left">
<h2 style="font-size: 14px; font-weight: 400; line-height: 49px; margin: 0">This page could not be found.</h2> <h2 style="font-size: 14px; font-weight: 400; line-height: 49px; margin: 0">This page could not be found.</h2>
</div> </div>
</div> </main>
</body> </body>
</html> </html>

View File

@@ -1,4 +1,6 @@
<pre class="mermaid hx:mt-6"> <div role="img" aria-label="{{ (T "mermaidDiagram") | default "Diagram" }}">
<pre class="mermaid hx:mt-6">
{{ .Inner | htmlEscape | safeHTML }} {{ .Inner | htmlEscape | safeHTML }}
</pre> </pre>
</div>
{{- .Page.Store.Set "hasMermaid" true -}} {{- .Page.Store.Set "hasMermaid" true -}}

View File

@@ -2,7 +2,7 @@
{{- .Text | safeHTML -}} {{- .Text | safeHTML -}}
{{- if gt .Level 1 -}} {{- if gt .Level 1 -}}
<span class="hx:absolute hx:-mt-20" id="{{ .Anchor | safeURL }}"></span> <span class="hx:absolute hx:-mt-20" id="{{ .Anchor | safeURL }}"></span>
<a href="#{{ .Anchor | safeURL }}" class="subheading-anchor" aria-label="Permalink for this section"></a> <a href="#{{ .Anchor | safeURL }}" class="subheading-anchor" aria-label="{{ (T "permalinkLabel") | default "Permalink for this section" }}"></a>
{{- end -}} {{- end -}}
</h{{ .Level }}> </h{{ .Level }}>
{{- /* Drop trailing newlines */ -}} {{- /* Drop trailing newlines */ -}}

View File

@@ -22,7 +22,7 @@
> >
{{- .Text | safeHTML -}} {{- .Text | safeHTML -}}
{{- if and .Page.Site.Params.externalLinkDecoration $isExternal -}} {{- if and .Page.Site.Params.externalLinkDecoration $isExternal -}}
{{- partial "utils/icon.html" (dict "name" "arrow-up-right" "attributes" `class="hx:inline hx:rtl:rotate-270 hx:align-baseline" height="1em"`) -}} {{- partial "utils/icon.html" (dict "name" "arrow-up-right" "attributes" `class="hx:inline hx:rtl:rotate-270 hx:align-baseline" height="1em" aria-hidden="true"`) -}}
{{- end -}} {{- end -}}
</a> </a>
{{- end -}} {{- end -}}

View File

@@ -10,6 +10,7 @@
{{- end -}} {{- end -}}
<button <button
class="hextra-banner-close-button hx:cursor-pointer hx:absolute hx:right-0 hx:text-white hx:font-bold hx:leading-none hx:hover:opacity-75 hx:transition hx:w-10 hx:h-10 hx:-mr-2 hx:md:mr-0 hx:flex hx:items-center hx:justify-center" class="hextra-banner-close-button hx:cursor-pointer hx:absolute hx:right-0 hx:text-white hx:font-bold hx:leading-none hx:hover:opacity-75 hx:transition hx:w-10 hx:h-10 hx:-mr-2 hx:md:mr-0 hx:flex hx:items-center hx:justify-center"
aria-label="{{ (T "closeBanner") | default "Close banner" }}"
> >
{{- partial "utils/icon.html" (dict "name" "x" "attributes" "height=16") -}} {{- partial "utils/icon.html" (dict "name" "x" "attributes" "height=16") -}}
</button> </button>

View File

@@ -5,7 +5,7 @@
{{- range $page.Ancestors.Reverse }} {{- range $page.Ancestors.Reverse }}
{{- if not .IsHome }} {{- if not .IsHome }}
<div class="hx:whitespace-nowrap hx:transition-colors hx:min-w-[24px] hx:overflow-hidden hx:text-ellipsis hx:hover:text-gray-900 hx:dark:hover:text-gray-100"> <div class="hx:whitespace-nowrap hx:transition-colors hx:min-w-[24px] hx:overflow-hidden hx:text-ellipsis hx:hover:text-gray-900 hx:dark:hover:text-gray-100">
<a href="{{ .RelPermalink }}">{{- partial "utils/title" . -}}</a> <a href="{{ .RelPermalink }}" class="hx:inline-block hx:rounded-sm hx:hextra-focus-visible-inset">{{- partial "utils/title" . -}}</a>
</div> </div>
{{- partial "utils/icon.html" (dict "name" "chevron-right" "attributes" "class=\"hx:w-3.5 hx:shrink-0 hx:rtl:-rotate-180\"") -}} {{- partial "utils/icon.html" (dict "name" "chevron-right" "attributes" "class=\"hx:w-3.5 hx:shrink-0 hx:rtl:-rotate-180\"") -}}
{{ end -}} {{ end -}}

View File

@@ -8,6 +8,8 @@
<button <button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="{{ $copyCode }}" title="{{ $copyCode }}"
aria-label="{{ $copyCode }}"
data-copied-label="{{ (T "copied") | default "Copied!" }}"
> >
<div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> <div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>

View File

@@ -71,10 +71,14 @@
// Listen for system theme changes // Listen for system theme changes
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", setGiscusTheme); window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", setGiscusTheme);
// Update giscus theme when theme switcher is clicked // Update giscus theme when theme switcher is clicked.
const themeToggleOptions = document.querySelectorAll(".hextra-theme-toggle-options p"); const themeToggleOptions = document.querySelectorAll(".hextra-theme-toggle-options [data-item]");
if (themeToggleOptions) { if (themeToggleOptions) {
themeToggleOptions.forEach(toggle => toggle.addEventListener('click', setGiscusTheme)); themeToggleOptions.forEach(toggle => {
toggle.addEventListener('click', () => {
setTimeout(setGiscusTheme, 0);
});
});
} }
}); });
</script> </script>

View File

@@ -9,9 +9,9 @@
{{- $pageURL := $.Permalink -}} {{- $pageURL := $.Permalink -}}
{{- $pageTitle := $.Title -}} {{- $pageTitle := $.Title -}}
<div class="hextra-page-context-menu hx:relative hx:inline-flex hx:shrink-0"> <div class="hextra-page-context-menu hx:relative hx:inline-flex hx:shrink-0">
<div class="hx:inline-flex hx:overflow-hidden hx:rounded-lg hx:border hx:border-gray-200 hx:transition-colors hx:hover:border-gray-300 hx:dark:border-neutral-800 hx:dark:hover:border-neutral-700"> <div class="hx:inline-flex hx:rounded-lg hx:border hx:border-gray-200 hx:transition-colors hx:hover:border-gray-300 hx:dark:border-neutral-800 hx:dark:hover:border-neutral-700">
<button <button
class="hextra-page-context-menu-copy hx:group/copybtn hx:inline-flex hx:cursor-pointer hx:items-center hx:gap-1.5 hx:bg-transparent hx:px-2.5 hx:py-1 hx:text-sm hx:font-medium hx:text-gray-700 hx:transition-colors hx:hover:bg-slate-50 hx:dark:text-gray-300 hx:dark:hover:bg-neutral-900" class="hextra-page-context-menu-copy hx:group/copybtn hx:inline-flex hx:cursor-pointer hx:items-center hx:gap-1.5 hx:bg-transparent hx:px-2.5 hx:py-1 hx:text-sm hx:font-medium hx:text-gray-700 hx:transition-colors hx:hover:bg-slate-50 hx:dark:text-gray-300 hx:dark:hover:bg-neutral-900 hx:ltr:rounded-l-lg hx:rtl:rounded-r-lg hx:hextra-focus-visible-inset"
data-url="{{ $markdownURL }}" data-url="{{ $markdownURL }}"
title="{{ i18n "copyAsMarkdown" }}" title="{{ i18n "copyAsMarkdown" }}"
aria-label="{{ i18n "copyAsMarkdown" }}" aria-label="{{ i18n "copyAsMarkdown" }}"
@@ -25,19 +25,22 @@
<span>{{ i18n "copyPage" }}</span> <span>{{ i18n "copyPage" }}</span>
</button> </button>
<button <button
class="hextra-page-context-menu-toggle hx:inline-flex hx:cursor-pointer hx:items-center hx:justify-center hx:bg-transparent hx:px-1.5 hx:py-1 hx:text-gray-500 hx:transition-colors hx:hover:bg-slate-50 hx:hover:text-gray-700 hx:dark:text-gray-400 hx:dark:hover:bg-neutral-900 hx:dark:hover:text-gray-300" class="hextra-page-context-menu-toggle hx:inline-flex hx:cursor-pointer hx:items-center hx:justify-center hx:bg-transparent hx:px-1.5 hx:py-1 hx:text-gray-500 hx:transition-colors hx:hover:bg-slate-50 hx:hover:text-gray-700 hx:dark:text-gray-400 hx:dark:hover:bg-neutral-900 hx:dark:hover:text-gray-300 hx:ltr:rounded-r-lg hx:rtl:rounded-l-lg hx:hextra-focus-visible-inset"
data-state="closed" data-state="closed"
aria-label="Toggle page context menu" aria-label="{{ (T "togglePageContextMenu") | default "Toggle page context menu" }}"
aria-expanded="false"
aria-haspopup="menu"
> >
<div class="hx:size-4 hx:transition-transform hx:duration-200" data-chevron> <div class="hx:size-4 hx:transition-transform hx:duration-200" data-chevron>
{{- partial "utils/icon.html" (dict "name" "chevron-down" "attributes" "height=16 width=16") -}} {{- partial "utils/icon.html" (dict "name" "chevron-down" "attributes" "height=16 width=16") -}}
</div> </div>
</button> </button>
</div> </div>
<ul class="hextra-page-context-menu-dropdown not-prose hx:hidden hx:absolute hx:top-full hx:left-0 hx:sm:left-auto hx:sm:right-0 hx:mt-1 hx:z-20 hx:max-h-64 hx:overflow-auto hx:rounded-lg hx:border hx:border-gray-200 hx:bg-white hx:p-1 hx:text-sm hx:shadow-lg hx:dark:border-neutral-700 hx:dark:bg-neutral-900"> <ul class="hextra-page-context-menu-dropdown not-prose hx:hidden hx:absolute hx:top-full hx:left-0 hx:sm:left-auto hx:sm:right-0 hx:mt-1 hx:z-20 hx:max-h-64 hx:overflow-auto hx:rounded-lg hx:border hx:border-gray-200 hx:bg-white hx:p-1 hx:text-sm hx:shadow-lg hx:dark:border-neutral-700 hx:dark:bg-neutral-900" role="menu">
<li> <li role="none">
<button <button
data-action="copy" data-action="copy"
role="menuitem"
class="hx:flex hx:w-full hx:cursor-pointer hx:select-none hx:items-center hx:gap-2 hx:whitespace-nowrap hx:rounded-sm hx:px-2 hx:py-1.5 hx:text-sm hx:text-gray-700 hx:outline-none hx:transition-colors hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:text-gray-300 hx:dark:hover:bg-neutral-800 hx:dark:hover:text-gray-100" class="hx:flex hx:w-full hx:cursor-pointer hx:select-none hx:items-center hx:gap-2 hx:whitespace-nowrap hx:rounded-sm hx:px-2 hx:py-1.5 hx:text-sm hx:text-gray-700 hx:outline-none hx:transition-colors hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:text-gray-300 hx:dark:hover:bg-neutral-800 hx:dark:hover:text-gray-100"
> >
<div class="hx:size-4 hx:shrink-0 hx:text-gray-500 hx:dark:text-gray-400"> <div class="hx:size-4 hx:shrink-0 hx:text-gray-500 hx:dark:text-gray-400">
@@ -46,10 +49,11 @@
{{ i18n "copyAsMarkdown" }} {{ i18n "copyAsMarkdown" }}
</button> </button>
</li> </li>
<li> <li role="none">
<button <button
data-action="view" data-action="view"
data-url="{{ $markdownURL }}" data-url="{{ $markdownURL }}"
role="menuitem"
class="hx:flex hx:w-full hx:cursor-pointer hx:select-none hx:items-center hx:gap-2 hx:whitespace-nowrap hx:rounded-sm hx:px-2 hx:py-1.5 hx:text-sm hx:text-gray-700 hx:outline-none hx:transition-colors hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:text-gray-300 hx:dark:hover:bg-neutral-800 hx:dark:hover:text-gray-100" class="hx:flex hx:w-full hx:cursor-pointer hx:select-none hx:items-center hx:gap-2 hx:whitespace-nowrap hx:rounded-sm hx:px-2 hx:py-1.5 hx:text-sm hx:text-gray-700 hx:outline-none hx:transition-colors hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:text-gray-300 hx:dark:hover:bg-neutral-800 hx:dark:hover:text-gray-100"
> >
<div class="hx:size-4 hx:shrink-0 hx:text-gray-500 hx:dark:text-gray-400"> <div class="hx:size-4 hx:shrink-0 hx:text-gray-500 hx:dark:text-gray-400">
@@ -62,11 +66,12 @@
<li class="hx:my-1 hx:h-px hx:bg-gray-200 hx:dark:bg-neutral-700" role="separator"></li> <li class="hx:my-1 hx:h-px hx:bg-gray-200 hx:dark:bg-neutral-700" role="separator"></li>
{{- range $customLinks -}} {{- range $customLinks -}}
{{- $linkURL := partial "utils/template-url.html" (dict "template" .url "values" (dict "url" $pageURL "title" $pageTitle "markdown_url" $markdownURL)) -}} {{- $linkURL := partial "utils/template-url.html" (dict "template" .url "values" (dict "url" $pageURL "title" $pageTitle "markdown_url" $markdownURL)) -}}
<li> <li role="none">
<a <a
href="{{ $linkURL }}" href="{{ $linkURL }}"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
role="menuitem"
class="hx:flex hx:w-full hx:cursor-pointer hx:select-none hx:items-center hx:gap-2 hx:whitespace-nowrap hx:rounded-sm hx:px-2 hx:py-1.5 hx:text-sm hx:text-gray-700 hx:outline-none hx:transition-colors hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:text-gray-300 hx:dark:hover:bg-neutral-800 hx:dark:hover:text-gray-100" class="hx:flex hx:w-full hx:cursor-pointer hx:select-none hx:items-center hx:gap-2 hx:whitespace-nowrap hx:rounded-sm hx:px-2 hx:py-1.5 hx:text-sm hx:text-gray-700 hx:outline-none hx:transition-colors hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:text-gray-300 hx:dark:hover:bg-neutral-800 hx:dark:hover:text-gray-100"
> >
{{- with .icon -}} {{- with .icon -}}

View File

@@ -19,6 +19,8 @@
class="hextra-language-switcher hx:cursor-pointer hx:rounded-md hx:text-left hx:font-medium {{ $class }} hx:grow" class="hextra-language-switcher hx:cursor-pointer hx:rounded-md hx:text-left hx:font-medium {{ $class }} hx:grow"
type="button" type="button"
aria-label="{{ $changeLanguage }}" aria-label="{{ $changeLanguage }}"
aria-expanded="false"
aria-haspopup="menu"
> >
<div class="hx:flex hx:items-center hx:gap-2 hx:capitalize"> <div class="hx:flex hx:items-center hx:gap-2 hx:capitalize">
{{- partial "utils/icon" (dict "name" $iconName "attributes" (printf "height=%d" $iconHeight)) -}} {{- partial "utils/icon" (dict "name" $iconName "attributes" (printf "height=%d" $iconHeight)) -}}
@@ -28,12 +30,14 @@
<ul <ul
class="hextra-language-options hx:hidden hx:z-20 hx:max-h-64 hx:overflow-auto hx:rounded-lg hx:border hx:border-gray-200 hx:bg-white hx:p-1 hx:text-sm hx:shadow-lg hx:dark:border-neutral-700 hx:dark:bg-neutral-900" class="hextra-language-options hx:hidden hx:z-20 hx:max-h-64 hx:overflow-auto hx:rounded-lg hx:border hx:border-gray-200 hx:bg-white hx:p-1 hx:text-sm hx:shadow-lg hx:dark:border-neutral-700 hx:dark:bg-neutral-900"
style="position: fixed; inset: auto auto 0px 0px; margin: 0px; min-width: 100px;" style="position: fixed; inset: auto auto 0px 0px; margin: 0px; min-width: 100px;"
role="menu"
> >
{{ range site.Languages }} {{ range site.Languages }}
{{ $link := partial "utils/lang-link" (dict "lang" .Lang "context" $page) }} {{ $link := partial "utils/lang-link" (dict "lang" .Lang "context" $page) }}
<li class="hx:flex hx:flex-col"> <li role="none" class="hx:flex hx:flex-col">
<a <a
href="{{ $link }}" href="{{ $link }}"
role="menuitem"
class="hx:text-gray-700 hx:dark:text-gray-300 hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:hover:bg-neutral-800 hx:dark:hover:text-gray-100 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:rounded-sm hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9" class="hx:text-gray-700 hx:dark:text-gray-300 hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:hover:bg-neutral-800 hx:dark:hover:text-gray-100 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:rounded-sm hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9"
> >
{{- .LanguageName -}} {{- .LanguageName -}}

View File

@@ -24,6 +24,8 @@
class="hextra-nav-menu-toggle hx:cursor-pointer hx:text-sm hx:contrast-more:text-gray-700 hx:contrast-more:dark:text-gray-100 hx:relative hx:-ml-2 hx:whitespace-nowrap hx:p-2 hx:flex hx:items-center hx:gap-1 {{ $activeClass }}" class="hextra-nav-menu-toggle hx:cursor-pointer hx:text-sm hx:contrast-more:text-gray-700 hx:contrast-more:dark:text-gray-100 hx:relative hx:-ml-2 hx:whitespace-nowrap hx:p-2 hx:flex hx:items-center hx:gap-1 {{ $activeClass }}"
type="button" type="button"
aria-label="{{ or (T $item.Identifier) $item.Name | safeHTML }}" aria-label="{{ or (T $item.Identifier) $item.Name | safeHTML }}"
aria-expanded="false"
aria-haspopup="menu"
> >
{{- if $icon -}} {{- if $icon -}}
<span class="hx:inline-flex hx:items-center"> <span class="hx:inline-flex hx:items-center">
@@ -38,6 +40,7 @@
<ul <ul
class="hextra-nav-menu-items hx:hidden hx:z-20 hx:max-h-64 hx:overflow-auto hx:rounded-lg hx:border hx:border-gray-200 hx:bg-white hx:p-1 hx:text-sm hx:shadow-lg hx:dark:border-neutral-700 hx:dark:bg-neutral-900" class="hextra-nav-menu-items hx:hidden hx:z-20 hx:max-h-64 hx:overflow-auto hx:rounded-lg hx:border hx:border-gray-200 hx:bg-white hx:p-1 hx:text-sm hx:shadow-lg hx:dark:border-neutral-700 hx:dark:bg-neutral-900"
style="min-width: 100px;" style="min-width: 100px;"
role="menu"
> >
{{ range $item.Children }} {{ range $item.Children }}
{{- $link := .URL -}} {{- $link := .URL -}}
@@ -47,10 +50,11 @@
{{- $link = relLangURL (strings.TrimPrefix "/" .) -}} {{- $link = relLangURL (strings.TrimPrefix "/" .) -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
<li class="hextra-nav-menu-item hx:flex hx:flex-col"> <li role="none" class="hextra-nav-menu-item hx:flex hx:flex-col">
<a <a
href="{{ $link }}" href="{{ $link }}"
{{ if $external }}target="_blank" rel="noreferrer"{{ end }} {{ if $external }}target="_blank" rel="noreferrer"{{ end }}
role="menuitem"
class="hx:text-gray-600 hx:hover:text-gray-800 hx:dark:text-gray-400 hx:dark:hover:text-gray-200 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:rounded-sm hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9 hx:flex hx:items-center hx:gap-1 hx:hover:bg-gray-100 hx:dark:hover:bg-neutral-800" class="hx:text-gray-600 hx:hover:text-gray-800 hx:dark:text-gray-400 hx:dark:hover:text-gray-200 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:rounded-sm hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9 hx:flex hx:items-center hx:gap-1 hx:hover:bg-gray-100 hx:dark:hover:bg-neutral-800"
> >
{{- if and (eq .Params.type "link") .Params.icon -}} {{- if and (eq .Params.type "link") .Params.icon -}}

View File

@@ -11,6 +11,6 @@
<img class="hx:mr-2 hx:hidden hx:dark:block" src="{{ $logoDarkPath | relURL }}" alt="{{ cond $displayTitle `Dark Logo` .Site.Title }}" height="{{ $logoHeight }}" width="{{ $logoWidth }}" /> <img class="hx:mr-2 hx:hidden hx:dark:block" src="{{ $logoDarkPath | relURL }}" alt="{{ cond $displayTitle `Dark Logo` .Site.Title }}" height="{{ $logoHeight }}" width="{{ $logoWidth }}" />
{{- end }} {{- end }}
{{- if $displayTitle }} {{- if $displayTitle }}
<span class="hx:mr-2 hx:font-extrabold hx:inline hx:select-none" title="{{ .Site.Title }}">{{- .Site.Title -}}</span> <span class="hx:mr-2 hx:font-extrabold hx:inline hx:select-none">{{- .Site.Title -}}</span>
{{- end }} {{- end }}
</a> </a>

View File

@@ -21,7 +21,7 @@
{{- $currentPage := . -}} {{- $currentPage := . -}}
{{- range .Site.Menus.main -}} {{- range .Site.Menus.main -}}
{{- if eq .Params.type "search" -}} {{- if eq .Params.type "search" -}}
{{- partial "search.html" (dict "params" .Params) -}} {{- partial "search.html" (dict "params" .Params "location" "navbar") -}}
{{- else -}} {{- else -}}
{{- $link := .URL -}} {{- $link := .URL -}}
{{- $external := strings.HasPrefix $link "http" -}} {{- $external := strings.HasPrefix $link "http" -}}
@@ -52,7 +52,7 @@
{{- end -}} {{- end -}}
<button type="button" aria-label="Menu" class="hextra-hamburger-menu hx:cursor-pointer hx:-mr-2 hx:rounded-sm hx:p-2 hx:active:bg-gray-400/20 hx:md:hidden"> <button type="button" aria-label="{{ (T "menu") | default "Menu" }}" aria-expanded="false" class="hextra-hamburger-menu hx:cursor-pointer hx:-mr-2 hx:rounded-sm hx:p-2 hx:active:bg-gray-400/20 hx:md:hidden hx:hextra-focus-visible-inset">
{{- partial "utils/icon.html" (dict "name" "hamburger-menu" "attributes" (printf "height=%d" $iconHeight)) -}} {{- partial "utils/icon.html" (dict "name" "hamburger-menu" "attributes" (printf "height=%d" $iconHeight)) -}}
</button> </button>
</nav> </nav>

View File

@@ -76,8 +76,21 @@
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
<script> <script data-playback-time="{{ (T "playbackTime") | default "Playback time" }}">
const playbackTimeLabel =
document.currentScript?.getAttribute("data-playback-time") || "Playback time";
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
const observers = [];
const applyTimerA11y = (container) => {
container.querySelectorAll(".ap-timer[role='textbox']").forEach((timer) => {
if (!timer.getAttribute("aria-label")) {
timer.setAttribute("aria-label", playbackTimeLabel);
}
});
};
// Fix play button position issue // Fix play button position issue
const style = document.createElement("style"); const style = document.createElement("style");
style.textContent = ` style.textContent = `
@@ -108,7 +121,20 @@
controls: true, // Always show user controls (bottom control bar) controls: true, // Always show user controls (bottom control bar)
idleTimeLimit: 2, // Limit terminal inactivity to 2 seconds (compress pauses longer than 2s) idleTimeLimit: 2, // Limit terminal inactivity to 2 seconds (compress pauses longer than 2s)
}); });
applyTimerA11y(el);
const observer = new MutationObserver(() => applyTimerA11y(el));
observer.observe(el, { childList: true, subtree: true });
observers.push(observer);
} }
}); });
// Prevent lingering observers when navigating away.
window.addEventListener(
"pagehide",
() => {
observers.forEach((observer) => observer.disconnect());
},
{ once: true },
);
}); });
</script> </script>

View File

@@ -5,8 +5,10 @@
<div class="hx:relative hx:flex hx:items-center hx:text-gray-900 hx:contrast-more:text-gray-800 hx:dark:text-gray-300 hx:contrast-more:dark:text-gray-300"> <div class="hx:relative hx:flex hx:items-center hx:text-gray-900 hx:contrast-more:text-gray-800 hx:dark:text-gray-300 hx:contrast-more:dark:text-gray-300">
<input <input
placeholder="{{ $placeholder }}" placeholder="{{ $placeholder }}"
class="hextra-search-input hx:focus:hextra-focus hx:block hx:w-full hx:appearance-none hx:rounded-lg hx:px-3 hx:py-2 hx:transition-colors hx:text-base hx:leading-tight hx:md:text-sm hx:bg-black/[.05] hx:dark:bg-gray-50/10 hx:focus:bg-white hx:dark:focus:bg-dark hx:placeholder:text-gray-500 hx:dark:placeholder:text-gray-400 hx:contrast-more:border hx:contrast-more:border-current" aria-label="{{ $placeholder }}"
class="hextra-search-input hx:hextra-focus-visible hx:block hx:w-full hx:appearance-none hx:rounded-lg hx:px-3 hx:py-2 hx:transition-colors hx:text-base hx:leading-tight hx:md:text-sm hx:bg-black/[.05] hx:dark:bg-gray-50/10 hx:focus-visible:bg-white hx:dark:focus-visible:bg-dark hx:placeholder:text-gray-500 hx:dark:placeholder:text-gray-400 hx:contrast-more:border hx:contrast-more:border-current"
type="search" type="search"
autocomplete="off"
value="" value=""
spellcheck="false" spellcheck="false"
/> />
@@ -20,7 +22,9 @@
<div> <div>
<ul <ul
class="hextra-search-results hextra-scrollbar hx:hidden hx:border hx:border-gray-200 hx:bg-white hx:text-gray-100 hx:dark:border-neutral-800 hx:dark:bg-neutral-900 hx:absolute hx:top-full hx:z-20 hx:mt-2 hx:overflow-auto hx:overscroll-contain hx:rounded-xl hx:py-2.5 hx:shadow-xl hx:max-h-[min(calc(50vh-11rem-env(safe-area-inset-bottom)),400px)] hx:md:max-h-[min(calc(100vh-5rem-env(safe-area-inset-bottom)),400px)] hx:inset-x-0 hx:ltr:md:left-auto hx:rtl:md:right-auto hx:contrast-more:border hx:contrast-more:border-gray-900 hx:contrast-more:dark:border-gray-50 hx:w-screen hx:min-h-[100px] hx:max-w-[min(calc(100vw-2rem),calc(100%+20rem))]" class="hextra-search-results hextra-scrollbar hx:hidden hx:border hx:border-gray-200 hx:bg-white hx:text-gray-100 hx:dark:border-neutral-800 hx:dark:bg-neutral-900 hx:absolute hx:top-full hx:z-20 hx:mt-2 hx:overflow-auto hx:overscroll-contain hx:rounded-xl hx:py-2.5 hx:shadow-xl hx:max-h-[min(calc(50vh-11rem-env(safe-area-inset-bottom)),400px)] hx:md:max-h-[min(calc(100vh-5rem-env(safe-area-inset-bottom)),400px)] hx:inset-x-0 hx:ltr:md:left-auto hx:rtl:md:right-auto hx:contrast-more:border hx:contrast-more:border-gray-900 hx:contrast-more:dark:border-gray-50 hx:w-screen hx:min-h-[100px] hx:max-w-[min(calc(100vw-2rem),calc(100%+20rem))]"
aria-label="{{ (T "searchResults") | default "Search results" }}"
style="transition: max-height 0.2s ease 0s;" style="transition: max-height 0.2s ease 0s;"
></ul> ></ul>
<div class="hextra-search-status hx:sr-only" aria-live="polite" role="status"></div>
</div> </div>
</div> </div>

View File

@@ -32,9 +32,9 @@ The `tabs` parameter is a list of dict with the following keys:
{{- /* Keep HTML on single lines to avoid `>` being parsed as blockquote when nested in steps (#876) */ -}} {{- /* Keep HTML on single lines to avoid `>` being parsed as blockquote when nested in steps (#876) */ -}}
<div class="hextra-scrollbar hx:overflow-x-auto hx:overflow-y-hidden hx:overscroll-x-contain"> <div class="hextra-scrollbar hx:overflow-x-auto hx:overflow-y-hidden hx:overscroll-x-contain">
<div class="hx:mt-4 hx:flex hx:w-max hx:min-w-full hx:border-b hx:border-gray-200 hx:pb-px hx:dark:border-neutral-800"{{- if $enableSync }} data-tab-group="{{ delimit $dataTabGroup `,` }}"{{- end }}> <div class="hx:mt-4 hx:flex hx:w-max hx:min-w-full hx:border-b hx:border-gray-200 hx:pb-px hx:dark:border-neutral-800" role="tablist"{{- if $enableSync }} data-tab-group="{{ delimit $dataTabGroup `,` }}"{{- end }}>
{{- range $i, $item := $tabs -}} {{- range $i, $item := $tabs -}}
<button class="hextra-tabs-toggle hx:cursor-pointer hx:data-[state=selected]:border-primary-500 hx:data-[state=selected]:text-primary-600 hx:data-[state=selected]:dark:border-primary-500 hx:data-[state=selected]:dark:text-primary-600 hx:mr-2 hx:rounded-t hx:p-2 hx:font-medium hx:leading-5 hx:transition-colors hx:-mb-0.5 hx:select-none hx:border-b-2 hx:border-transparent hx:text-gray-600 hx:hover:border-gray-200 hx:hover:text-black hx:dark:text-gray-200 hx:dark:hover:border-neutral-800 hx:dark:hover:text-white" role="tab" type="button" aria-controls="tabs-panel-{{ $globalID }}-{{ $item.id }}"{{- if eq $i $selectedIndex }} aria-selected="true" tabindex="0" data-state="selected"{{- end }}> <button class="hextra-tabs-toggle hx:cursor-pointer hx:data-[state=selected]:border-primary-500 hx:data-[state=selected]:text-primary-600 hx:data-[state=selected]:dark:border-primary-500 hx:data-[state=selected]:dark:text-primary-600 hx:mr-2 hx:rounded-t hx:p-2 hx:font-medium hx:leading-5 hx:transition-colors hx:-mb-0.5 hx:select-none hx:border-b-2 hx:border-transparent hx:text-gray-600 hx:hover:border-gray-200 hx:hover:text-black hx:dark:text-gray-200 hx:dark:hover:border-neutral-800 hx:dark:hover:text-white hx:hextra-focus-visible-inset" id="tabs-tab-{{ $globalID }}-{{ $item.id }}" role="tab" type="button" aria-controls="tabs-panel-{{ $globalID }}-{{ $item.id }}" aria-selected="{{ if eq $i $selectedIndex }}true{{ else }}false{{ end }}" tabindex="{{ if eq $i $selectedIndex }}0{{ else }}-1{{ end }}"{{- if eq $i $selectedIndex }} data-state="selected"{{- end }}>
{{- $item.name -}} {{- $item.name -}}
</button> </button>
{{- end -}} {{- end -}}
@@ -42,7 +42,7 @@ The `tabs` parameter is a list of dict with the following keys:
</div> </div>
<div> <div>
{{- range $i, $item := $tabs -}} {{- range $i, $item := $tabs -}}
<div class="hextra-tabs-panel hx:rounded-sm hx:pt-6 hx:hidden hx:data-[state=selected]:block" id="tabs-panel-{{ $globalID }}-{{ $item.id }}" role="tabpanel"{{- if eq $i $selectedIndex }} tabindex="0" data-state="selected"{{- end }}> <div class="hextra-tabs-panel hx:rounded-sm hx:pt-6 hx:hidden hx:data-[state=selected]:block" id="tabs-panel-{{ $globalID }}-{{ $item.id }}" role="tabpanel" aria-labelledby="tabs-tab-{{ $globalID }}-{{ $item.id }}" aria-hidden="{{ if eq $i $selectedIndex }}false{{ else }}true{{ end }}"{{- if eq $i $selectedIndex }} tabindex="0" data-state="selected"{{- end }}>
{{- $item.content | markdownify -}} {{- $item.content | markdownify -}}
</div> </div>
{{- end -}} {{- end -}}

View File

@@ -24,7 +24,7 @@
{{- if (site.Params.search.enable | default true) -}} {{- if (site.Params.search.enable | default true) -}}
<!-- Search bar on small screen --> <!-- Search bar on small screen -->
<div class="hx:px-4 hx:pt-4 hx:md:hidden"> <div class="hx:px-4 hx:pt-4 hx:md:hidden">
{{ partial "search.html" }} {{ partial "search.html" (dict "location" "sidebar") }}
</div> </div>
{{- end -}} {{- end -}}
<div class="hextra-scrollbar hx:overflow-y-auto hx:overflow-x-hidden hx:p-4 hx:grow hx:md:h-[calc(100vh-var(--navbar-height)-var(--menu-height))]"> <div class="hextra-scrollbar hx:overflow-y-auto hx:overflow-x-hidden hx:p-4 hx:grow hx:md:h-[calc(100vh-var(--navbar-height)-var(--menu-height))]">
@@ -91,7 +91,7 @@
{{- $shouldOpen := or (.Params.sidebar.open) (.IsAncestor $page) $active | default true }} {{- $shouldOpen := or (.Params.sidebar.open) (.IsAncestor $page) $active | default true }}
<li class="{{ if $shouldOpen }}open{{ end }}"> <li class="{{ if $shouldOpen }}open{{ end }}">
{{- $linkTitle := partial "utils/title" . -}} {{- $linkTitle := partial "utils/title" . -}}
{{- template "sidebar-item-link" dict "context" . "active" $active "title" $linkTitle "link" .RelPermalink -}} {{- template "sidebar-item-link" dict "context" . "active" $active "open" $shouldOpen "title" $linkTitle "link" .RelPermalink -}}
{{- if and $toc $active (ne .Params.toc false) -}} {{- if and $toc $active (ne .Params.toc false) -}}
{{- template "sidebar-toc" dict "page" . -}} {{- template "sidebar-toc" dict "page" . -}}
{{- end -}} {{- end -}}
@@ -100,14 +100,14 @@
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- else -}} {{- else -}}
<div class="hx:ltr:pr-0 hx:overflow-hidden"> <div class="hextra-sidebar-children hx:ltr:pr-1 hx:rtl:pl-1 hx:overflow-hidden">
<ul class='hx:relative hx:flex hx:flex-col hx:gap-1 hx:before:absolute hx:before:inset-y-1 hx:before:w-px hx:before:bg-gray-200 hx:before:content-[""] hx:ltr:ml-3 hx:ltr:pl-3 hx:ltr:before:left-0 hx:rtl:mr-3 hx:rtl:pr-3 hx:rtl:before:right-0 hx:dark:before:bg-neutral-800'> <ul class='hx:relative hx:flex hx:flex-col hx:gap-1 hx:before:absolute hx:before:inset-y-1 hx:before:w-px hx:before:bg-gray-200 hx:before:content-[""] hx:ltr:ml-3 hx:ltr:pl-3 hx:ltr:before:left-0 hx:rtl:mr-3 hx:rtl:pr-3 hx:rtl:before:right-0 hx:dark:before:bg-neutral-800'>
{{- range $items.ByWeight }} {{- range $items.ByWeight }}
{{- $active := eq $pageURL .RelPermalink -}} {{- $active := eq $pageURL .RelPermalink -}}
{{- $shouldOpen := or (.Params.sidebar.open) (.IsAncestor $page) $active | default true }} {{- $shouldOpen := or (.Params.sidebar.open) (.IsAncestor $page) $active | default true }}
{{- $linkTitle := partial "utils/title" . -}} {{- $linkTitle := partial "utils/title" . -}}
<li class="hx:flex hx:flex-col {{ if $shouldOpen }}open{{ end }}"> <li class="hx:flex hx:flex-col {{ if $shouldOpen }}open{{ end }}">
{{- template "sidebar-item-link" dict "context" . "active" $active "title" $linkTitle "link" .RelPermalink -}} {{- template "sidebar-item-link" dict "context" . "active" $active "open" $shouldOpen "title" $linkTitle "link" .RelPermalink -}}
{{- if and $toc $active (ne .Params.toc false) -}} {{- if and $toc $active (ne .Params.toc false) -}}
{{ template "sidebar-toc" dict "page" . }} {{ template "sidebar-toc" dict "page" . }}
{{- end }} {{- end }}
@@ -156,7 +156,7 @@
{{- $link = relLangURL (strings.TrimPrefix "/" .) -}} {{- $link = relLangURL (strings.TrimPrefix "/" .) -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
<li>{{ template "sidebar-item-link" dict "active" false "title" $name "link" $link }}</li> <li>{{ template "sidebar-item-link" dict "active" false "open" false "title" $name "link" $link }}</li>
{{ end }} {{ end }}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
@@ -164,8 +164,13 @@
{{- define "sidebar-item-link" -}} {{- define "sidebar-item-link" -}}
{{- $external := strings.HasPrefix .link "http" -}} {{- $external := strings.HasPrefix .link "http" -}}
{{- $open := .open | default true -}} {{- $open := .open | default true -}}
{{- $hasChildren := false -}}
{{- with .context }}{{ if or .RegularPages .Sections }}{{ $hasChildren = true }}{{ end }}{{ end -}}
<div class="hextra-sidebar-item hx:group hx:relative hx:flex hx:items-center" data-active="{{ if .active }}true{{ else }}false{{ end }}">
<a <a
class="hx:flex hx:items-center hx:justify-between hx:gap-2 hx:cursor-pointer hx:rounded-sm hx:px-2 hx:py-1.5 hx:text-sm hx:transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word] class="hx:flex hx:items-center hx:justify-between hx:gap-2 hx:grow hx:cursor-pointer hx:rounded-sm hx:px-2 hx:py-1.5 hx:text-sm hx:transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none]
{{- if $hasChildren }} hx:ltr:pr-8 hx:rtl:pl-8{{- end }}
hx:hextra-focus-visible-inset
{{- if .active }} {{- if .active }}
hextra-sidebar-active-item hx:bg-primary-100 hx:font-semibold hx:text-primary-800 hx:contrast-more:border hx:contrast-more:border-primary-500 hx:dark:bg-primary-400/10 hx:dark:text-primary-600 hx:contrast-more:dark:border-primary-500 hextra-sidebar-active-item hx:bg-primary-100 hx:font-semibold hx:text-primary-800 hx:contrast-more:border hx:contrast-more:border-primary-500 hx:dark:bg-primary-400/10 hx:dark:text-primary-600 hx:contrast-more:dark:border-primary-500
{{- else }} {{- else }}
@@ -174,17 +179,16 @@
href="{{ .link }}" href="{{ .link }}"
{{ if $external }}target="_blank" rel="noreferrer"{{ end }} {{ if $external }}target="_blank" rel="noreferrer"{{ end }}
> >
{{- .title -}} <span class="hx:min-w-0 [word-break:break-word]">{{- .title -}}</span>
{{- with .context }}
{{- if or .RegularPages .Sections }}
<span class="hextra-sidebar-collapsible-button">
{{- template "sidebar-collapsible-button" -}}
</span>
{{- end }}
{{ end -}}
</a> </a>
{{- if $hasChildren }}
<button type="button" class="hextra-sidebar-collapsible-button hx:absolute hx:top-1/2 hx:-translate-y-1/2 hx:ltr:right-2 hx:rtl:left-2 hx:shrink-0 hx:cursor-pointer hx:p-0 hx:text-gray-500 hx:dark:text-neutral-400 hx:group-hover:text-gray-900 hx:dark:group-hover:text-gray-50 hx:group-data-[active=true]:text-primary-800 hx:group-data-[active=true]:dark:text-primary-600 hx:hextra-focus-visible-inset" aria-label="{{ (T "toggleSection") | default "Toggle section" }}" aria-expanded="{{ if $open }}true{{ else }}false{{ end }}">
{{- template "sidebar-collapsible-button" -}}
</button>
{{- end }}
</div>
{{- end -}} {{- end -}}
{{- define "sidebar-collapsible-button" -}} {{- define "sidebar-collapsible-button" -}}
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" class="hx:h-[18px] hx:min-w-[18px] hx:rounded-xs hx:p-0.5 hx:hover:bg-gray-800/5 hx:dark:hover:bg-gray-100/5"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" class="hx:origin-center hx:transition-transform hx:rtl:-rotate-180"></path></svg> <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true" focusable="false" class="hx:h-[18px] hx:min-w-[18px] hx:rounded-xs hx:p-0.5 hx:hover:bg-gray-800/5 hx:dark:hover:bg-gray-100/5"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" class="hx:origin-center hx:transition-transform hx:rtl:-rotate-180"></path></svg>
{{- end -}} {{- end -}}

View File

@@ -16,6 +16,8 @@
class="hextra-theme-toggle hx:cursor-pointer hx:rounded-md hx:text-left hx:font-medium {{ $class }} hx:grow" class="hextra-theme-toggle hx:cursor-pointer hx:rounded-md hx:text-left hx:font-medium {{ $class }} hx:grow"
type="button" type="button"
aria-label="{{ $changeTheme }}" aria-label="{{ $changeTheme }}"
aria-expanded="false"
aria-haspopup="menu"
> >
<div class="hx:flex hx:items-center hx:gap-2 hx:capitalize"> <div class="hx:flex hx:items-center hx:gap-2 hx:capitalize">
{{- partial "utils/icon.html" (dict "name" "sun" "attributes" (printf `height=%d class="hx:group-data-[theme=dark]:hidden hx:group-data-[theme=system]:hidden"` $iconHeight)) -}} {{- partial "utils/icon.html" (dict "name" "sun" "attributes" (printf `height=%d class="hx:group-data-[theme=dark]:hidden hx:group-data-[theme=system]:hidden"` $iconHeight)) -}}
@@ -30,39 +32,52 @@
class="hextra-theme-toggle-options hx:hidden hx:z-20 hx:max-h-64 hx:overflow-auto hx:rounded-lg hx:border hx:border-gray-200 hx:bg-white hx:p-1 hx:text-sm hx:shadow-lg hx:dark:border-neutral-700 hx:dark:bg-neutral-900" class="hextra-theme-toggle-options hx:hidden hx:z-20 hx:max-h-64 hx:overflow-auto hx:rounded-lg hx:border hx:border-gray-200 hx:bg-white hx:p-1 hx:text-sm hx:shadow-lg hx:dark:border-neutral-700 hx:dark:bg-neutral-900"
style="position: fixed; inset: auto auto 0px 0px; margin: 0px; min-width: 100px;" style="position: fixed; inset: auto auto 0px 0px; margin: 0px; min-width: 100px;"
data-theme="light" data-theme="light"
role="menu"
> >
<li class="hx:flex hx:flex-col"> <li role="none" class="hx:flex hx:flex-col">
<p <button
type="button"
role="menuitemradio"
aria-checked="true"
tabindex="-1"
data-item="light" data-item="light"
class="hx:text-gray-700 hx:dark:text-gray-300 hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:hover:bg-neutral-800 hx:dark:hover:text-gray-100 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:rounded-sm hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9" class="hx:text-gray-700 hx:dark:text-gray-300 hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:hover:bg-neutral-800 hx:dark:hover:text-gray-100 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:rounded-sm hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9 hx:text-left hx:w-full hx:bg-transparent hx:border-0"
> >
{{ $light }} {{ $light }}
<span class="hx:absolute hx:inset-y-0 hx:flex hx:items-center hx:ltr:right-3 hx:rtl:left-3 hx:group-data-[theme=dark]:hidden hx:group-data-[theme=system]:hidden"> <span class="hx:absolute hx:inset-y-0 hx:flex hx:items-center hx:ltr:right-3 hx:rtl:left-3 hx:group-data-[theme=dark]:hidden hx:group-data-[theme=system]:hidden">
{{- partial "utils/icon" (dict "name" "check" "attributes" "height=1em width=1em") -}} {{- partial "utils/icon" (dict "name" "check" "attributes" "height=1em width=1em") -}}
</span> </span>
</p> </button>
</li> </li>
<li class="hx:flex hx:flex-col"> <li role="none" class="hx:flex hx:flex-col">
<p <button
type="button"
role="menuitemradio"
aria-checked="false"
tabindex="-1"
data-item="dark" data-item="dark"
class="hx:text-gray-700 hx:dark:text-gray-300 hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:hover:bg-neutral-800 hx:dark:hover:text-gray-100 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:rounded-sm hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9" class="hx:text-gray-700 hx:dark:text-gray-300 hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:hover:bg-neutral-800 hx:dark:hover:text-gray-100 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:rounded-sm hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9 hx:text-left hx:w-full hx:bg-transparent hx:border-0"
> >
{{ $dark }} {{ $dark }}
<span class="hx:absolute hx:inset-y-0 hx:flex hx:items-center hx:ltr:right-3 hx:rtl:left-3 hx:group-data-[theme=light]:hidden hx:group-data-[theme=system]:hidden"> <span class="hx:absolute hx:inset-y-0 hx:flex hx:items-center hx:ltr:right-3 hx:rtl:left-3 hx:group-data-[theme=light]:hidden hx:group-data-[theme=system]:hidden">
{{- partial "utils/icon" (dict "name" "check" "attributes" "height=1em width=1em") -}} {{- partial "utils/icon" (dict "name" "check" "attributes" "height=1em width=1em") -}}
</span> </span>
</p> </button>
</li> </li>
<li class="hx:flex hx:flex-col"> <li role="none" class="hx:flex hx:flex-col">
<p <button
type="button"
role="menuitemradio"
aria-checked="false"
tabindex="-1"
data-item="system" data-item="system"
class="hx:text-gray-700 hx:dark:text-gray-300 hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:hover:bg-neutral-800 hx:dark:hover:text-gray-100 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:rounded-sm hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9" class="hx:text-gray-700 hx:dark:text-gray-300 hx:hover:bg-gray-100 hx:hover:text-gray-900 hx:dark:hover:bg-neutral-800 hx:dark:hover:text-gray-100 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:rounded-sm hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9 hx:text-left hx:w-full hx:bg-transparent hx:border-0"
> >
{{ $system }} {{ $system }}
<span class="hx:absolute hx:inset-y-0 hx:flex hx:items-center hx:ltr:right-3 hx:rtl:left-3 hx:group-data-[theme=dark]:hidden hx:group-data-[theme=light]:hidden"> <span class="hx:absolute hx:inset-y-0 hx:flex hx:items-center hx:ltr:right-3 hx:rtl:left-3 hx:group-data-[theme=dark]:hidden hx:group-data-[theme=light]:hidden">
{{- partial "utils/icon" (dict "name" "check" "attributes" "height=1em width=1em") -}} {{- partial "utils/icon" (dict "name" "check" "attributes" "height=1em width=1em") -}}
</span> </span>
</p> </button>
</li> </li>
</ul> </ul>
</div> </div>

View File

@@ -6,7 +6,7 @@
{{- $editThisPage := (T "editThisPage") | default "Edit this page"}} {{- $editThisPage := (T "editThisPage") | default "Edit this page"}}
{{- $backToTop := (T "backToTop") | default "Scroll to top" -}} {{- $backToTop := (T "backToTop") | default "Scroll to top" -}}
<nav class="hextra-toc hx:order-last hx:hidden hx:w-64 hx:shrink-0 hx:xl:block hx:print:hidden hx:px-4" aria-label="table of contents"> <nav class="hextra-toc hx:order-last hx:hidden hx:w-64 hx:shrink-0 hx:xl:block hx:print:hidden hx:px-4" aria-label="{{ (T "tableOfContents") | default "Table of contents" }}">
{{- if $toc }} {{- if $toc }}
<div class="hextra-scrollbar hx:sticky hx:top-16 hx:overflow-y-auto hx:pr-4 hx:pt-6 hx:text-sm [hyphens:auto] hx:max-h-[calc(100vh-var(--navbar-height)-env(safe-area-inset-bottom))] hx:ltr:-mr-4 hx:rtl:-ml-4"> <div class="hextra-scrollbar hx:sticky hx:top-16 hx:overflow-y-auto hx:pr-4 hx:pt-6 hx:text-sm [hyphens:auto] hx:max-h-[calc(100vh-var(--navbar-height)-env(safe-area-inset-bottom))] hx:ltr:-mr-4 hx:rtl:-ml-4">
{{- with .Fragments.Headings -}} {{- with .Fragments.Headings -}}
@@ -48,14 +48,14 @@
{{- $editURL = urls.JoinPath $editURL $sourceDir $path -}} {{- $editURL = urls.JoinPath $editURL $sourceDir $path -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
<a class="hx:text-xs hx:font-medium hx:text-gray-500 hx:hover:text-gray-900 hx:dark:text-gray-400 hx:dark:hover:text-gray-100 hx:contrast-more:text-gray-800 hx:contrast-more:dark:text-gray-50" href="{{ $editURL }}" target="_blank" rel="noreferrer">{{ $editThisPage }}</a> <a class="hx:inline-block hx:rounded-sm hx:text-xs hx:font-medium hx:text-gray-500 hx:hover:text-gray-900 hx:dark:text-gray-400 hx:dark:hover:text-gray-100 hx:contrast-more:text-gray-800 hx:contrast-more:dark:text-gray-50 hx:hextra-focus-visible-inset" href="{{ $editURL }}" target="_blank" rel="noreferrer">{{ $editThisPage }}</a>
{{- end -}} {{- end -}}
{{/* Scroll To Top */}} {{/* Scroll To Top */}}
<button aria-hidden="true" id="backToTop" onClick="scrollUp();" class="hx:cursor-pointer hx:transition-all hx:duration-75 hx:opacity-0 hx:text-xs hx:font-medium hx:text-gray-500 hx:hover:text-gray-900 hx:dark:text-gray-400 hx:dark:hover:text-gray-100 hx:contrast-more:text-gray-800 hx:contrast-more:dark:text-gray-50"> <button id="backToTop" tabindex="-1" onClick="scrollUp();" class="hx:cursor-pointer hx:transition-all hx:duration-75 hx:opacity-0 hx:text-xs hx:font-medium hx:text-gray-500 hx:hover:text-gray-900 hx:dark:text-gray-400 hx:dark:hover:text-gray-100 hx:contrast-more:text-gray-800 hx:contrast-more:dark:text-gray-50">
<span> <span>
{{- $backToTop -}} {{- $backToTop -}}
</span> </span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="hx:inline hx:ltr:ml-1 hx:rtl:mr-1 hx:h-3.5 hx:w-3.5 hx:rounded-full hx:border hx:border-gray-500 hx:hover:border-gray-900 hx:dark:border-gray-400 hx:dark:hover:border-gray-100 hx:contrast-more:border-gray-800 hx:contrast-more:dark:border-gray-50"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" focusable="false" class="hx:inline hx:ltr:ml-1 hx:rtl:mr-1 hx:h-3.5 hx:w-3.5 hx:rounded-full hx:border hx:border-gray-500 hx:hover:border-gray-900 hx:dark:border-gray-400 hx:dark:hover:border-gray-100 hx:contrast-more:border-gray-800 hx:contrast-more:dark:border-gray-50">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 15.75l7.5-7.5 7.5 7.5" /> <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 15.75l7.5-7.5 7.5 7.5" />
</svg> </svg>
</button> </button>
@@ -78,7 +78,7 @@
{{- range $headings }} {{- range $headings }}
{{- if .Title }} {{- if .Title }}
<li class="hx:my-2 hx:scroll-my-6 hx:scroll-py-6"> <li class="hx:my-2 hx:scroll-my-6 hx:scroll-py-6">
<a class="{{ $class }} hx:inline-block hx:text-gray-500 hx:hover:text-gray-900 hx:dark:text-gray-400 hx:dark:hover:text-gray-300 hx:contrast-more:text-gray-900 hx:contrast-more:underline hx:contrast-more:dark:text-gray-50 hx:w-full hx:wrap-break-word" href="#{{ anchorize .ID }}"> <a class="{{ $class }} hx:inline-block hx:rounded-sm hx:text-gray-500 hx:hover:text-gray-900 hx:dark:text-gray-400 hx:dark:hover:text-gray-300 hx:contrast-more:text-gray-900 hx:contrast-more:underline hx:contrast-more:dark:text-gray-50 hx:w-full hx:wrap-break-word hx:hextra-focus-visible-inset" href="#{{ anchorize .ID }}">
{{- .Title | safeHTML | plainify | htmlUnescape }} {{- .Title | safeHTML | plainify | htmlUnescape }}
</a> </a>
</li> </li>

View File

@@ -76,6 +76,8 @@
{{- .Page.Store.Set "hasAsciinema" true -}} {{- .Page.Store.Set "hasAsciinema" true -}}
<div class="asciinema-player" <div class="asciinema-player"
role="region"
aria-label="{{ (T "terminalRecording") | default "Terminal recording" }}"
data-cast-file="{{ $castFile }}" data-cast-file="{{ $castFile }}"
data-theme="{{ $theme }}" data-theme="{{ $theme }}"
data-speed="{{ $speed }}" data-speed="{{ $speed }}"

View File

@@ -24,7 +24,7 @@ or
{{- $border := not (eq (.Get "border") false) | default true }} {{- $border := not (eq (.Get "border") false) | default true }}
{{- if $link -}} {{- if $link -}}
<a href="{{ $link }}" title="{{ $content | plainify }}" target="_blank"> <a href="{{ $link }}" title="{{ $content | plainify }}" target="_blank" class="not-prose hx:inline-flex hx:align-middle hx:no-underline hover:hx:no-underline">
{{- partial "shortcodes/badge.html" (dict {{- partial "shortcodes/badge.html" (dict
"content" $content "content" $content
"color" $color "color" $color

View File

@@ -6,6 +6,8 @@ A file tree container.
<div class="hextra-filetree hx:mt-6 hx:select-none hx:text-sm hx:text-gray-800 hx:dark:text-gray-300 not-prose"> <div class="hextra-filetree hx:mt-6 hx:select-none hx:text-sm hx:text-gray-800 hx:dark:text-gray-300 not-prose">
<div class="hx:inline-block hx:rounded-lg hx:px-4 hx:py-2 hx:border hx:border-gray-200 hx:dark:border-neutral-800"> <div class="hx:inline-block hx:rounded-lg hx:px-4 hx:py-2 hx:border hx:border-gray-200 hx:dark:border-neutral-800">
<ul class="hx:flex hx:flex-col">
{{- .InnerDeindent -}} {{- .InnerDeindent -}}
</ul>
</div> </div>
</div> </div>

View File

@@ -11,7 +11,7 @@ A folder in a file tree.
{{- $state := .Get "state" | default "open" }} {{- $state := .Get "state" | default "open" }}
<li class="hx:group hx:flex hx:list-none hx:flex-col"> <li class="hx:group hx:flex hx:list-none hx:flex-col">
<button class="hextra-filetree-folder hx:inline-flex hx:cursor-pointer hx:items-center hx:py-1 hx:hover:opacity-60"> <button class="hextra-filetree-folder hx:inline-flex hx:cursor-pointer hx:items-center hx:py-1 hx:hover:opacity-60" aria-expanded="{{ if eq $state "open" }}true{{ else }}false{{ end }}">
<span data-state="{{ $state }}" class="hx:data-[state=open]:hidden"> <span data-state="{{ $state }}" class="hx:data-[state=open]:hidden">
{{- partial "utils/icon" (dict "name" "folder" "attributes" "width=1em") -}} {{- partial "utils/icon" (dict "name" "folder" "attributes" "width=1em") -}}
</span> </span>

View File

@@ -17,7 +17,7 @@ A shortcode for rendering a button with a link.
<a <a
href="{{ $href }}" href="{{ $href }}"
class="not-prose hx:font-medium hx:cursor-pointer hx:px-6 hx:py-3 hx:rounded-full hx:text-center hx:text-white hx:inline-block hx:bg-primary-600 hx:hover:bg-primary-700 hx:focus:outline-hidden hx:focus:ring-4 hx:focus:ring-primary-300 hx:dark:bg-primary-600 hx:dark:hover:bg-primary-700 hx:dark:focus:ring-primary-800 hx:transition-all hx:ease-in hx:duration-200" class="not-prose hx:font-medium hx:cursor-pointer hx:px-6 hx:py-3 hx:rounded-full hx:text-center hx:text-white hx:inline-block hx:bg-primary-600 hx:hover:bg-primary-700 hx:hextra-focus-visible hx:dark:bg-primary-600 hx:dark:hover:bg-primary-700 hx:transition-all hx:ease-in hx:duration-200"
{{ with $style }}style="{{ . | safeCSS }}"{{ end }} {{ with $style }}style="{{ . | safeCSS }}"{{ end }}
{{ if $external }}target="_blank" rel="noreferrer"{{ end -}} {{ if $external }}target="_blank" rel="noreferrer"{{ end -}}
> >

View File

@@ -49,7 +49,7 @@ Render Jupyter Notebook
{{- $outputs := index $cell "outputs" -}} {{- $outputs := index $cell "outputs" -}}
{{- with $outputs -}} {{- with $outputs -}}
<div class="hextra-jupyter-code-cell-outputs-container"> <div class="hextra-jupyter-code-cell-outputs-container">
<div class="hextra-jupyter-code-cell-outputs"> <div class="hextra-jupyter-code-cell-outputs" tabindex="0">
{{- range $output := . -}} {{- range $output := . -}}
{{- if eq (index $output "output_type") "display_data" -}} {{- if eq (index $output "output_type") "display_data" -}}
{{- $data := index $output "data" -}} {{- $data := index $output "data" -}}

View File

@@ -11,5 +11,5 @@ Shortcode to include a PDF file in a page.
<div class="hextra-pdf"> <div class="hextra-pdf">
<iframe src="{{ $url | safeURL }}" width="100%" style="min-height: 32rem;" frameborder="0"></iframe> <iframe src="{{ $url | safeURL }}" width="100%" style="min-height: 32rem;" frameborder="0" title="{{ (T "pdfViewer") | default "PDF viewer" }}"></iframe>
</div> </div>

View File

@@ -7,7 +7,7 @@
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}
{{ partial "toc.html" (dict "Params" (dict "toc" false)) }} {{ partial "toc.html" (dict "Params" (dict "toc" false)) }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12"> <main id="content" class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12">
<br class="hx:mt-1.5 hx:text-sm" /> <br class="hx:mt-1.5 hx:text-sm" />
{{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }} {{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
<div class="hx:mb-16"></div> <div class="hx:mb-16"></div>

View File

@@ -2,6 +2,9 @@
<html lang="{{ .Site.Language.Lang }}" dir="{{ .Site.Language.LanguageDirection | default `ltr` }}"> <html lang="{{ .Site.Language.Lang }}" dir="{{ .Site.Language.LanguageDirection | default `ltr` }}">
{{- partial "head.html" . -}} {{- partial "head.html" . -}}
<body> <body>
<a href="#content" class="hx:sr-only hx:focus-visible:not-sr-only hx:focus-visible:fixed hx:focus-visible:z-50 hx:focus-visible:top-2 hx:focus-visible:left-2 hx:focus-visible:bg-primary-500 hx:focus-visible:text-white hx:focus-visible:px-4 hx:focus-visible:py-2 hx:focus-visible:rounded-md hx:focus-visible:text-sm hx:focus-visible:font-medium">
{{- (T "skipToContent") | default "Skip to content" -}}
</a>
{{- partial "banner.html" . -}} {{- partial "banner.html" . -}}
{{- partial "navbar.html" . -}} {{- partial "navbar.html" . -}}
{{- block "main" . }}{{ end -}} {{- block "main" . }}{{ end -}}

View File

@@ -3,7 +3,7 @@
<div class="hx:mx-auto hx:flex hextra-max-page-width"> <div class="hx:mx-auto hx:flex hextra-max-page-width">
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12"> <main id="content" class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12">
{{ partial "breadcrumb.html" (dict "page" . "enable" false) }} {{ partial "breadcrumb.html" (dict "page" . "enable" false) }}
<br class="hx:mt-1.5 hx:text-sm" /> <br class="hx:mt-1.5 hx:text-sm" />
{{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }} {{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
@@ -21,7 +21,7 @@
{{ end }} {{ end }}
<p class="hx:opacity-80 hx:mt-4 hx:leading-7">{{- partial "utils/page-description" . -}}</p> <p class="hx:opacity-80 hx:mt-4 hx:leading-7">{{- partial "utils/page-description" . -}}</p>
<p class="hx:opacity-80 hx:mt-1 hx:leading-7"> <p class="hx:opacity-80 hx:mt-1 hx:leading-7">
<a class="hx:text-[color:hsl(var(--primary-hue),100%,50%)] hx:underline hx:underline-offset-2 hx:decoration-from-font" href="{{ .RelPermalink }}"> <a class="hx:text-[color:hsl(var(--primary-hue),100%,50%)] hx:underline hx:underline-offset-2 hx:decoration-from-font" href="{{ .RelPermalink }}" aria-label="{{ printf "%s %s" $readMore .Title }}">
{{- $readMore -}} {{- $readMore -}}
</a> </a>
</p> </p>

View File

@@ -3,7 +3,7 @@
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}
{{ partial "toc.html" . }} {{ partial "toc.html" . }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12"> <main id="content" class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12">
{{ partial "breadcrumb.html" (dict "page" . "enable" true) }} {{ partial "breadcrumb.html" (dict "page" . "enable" true) }}
{{ if .Title }} {{ if .Title }}
<div class="hx:flex hx:flex-col hx:sm:flex-row hx:items-start hx:sm:items-center hx:sm:justify-between hx:gap-4 hx:mt-2"> <div class="hx:flex hx:flex-col hx:sm:flex-row hx:items-start hx:sm:items-center hx:sm:justify-between hx:gap-4 hx:mt-2">

View File

@@ -3,7 +3,7 @@
{{ partial "sidebar.html" (dict "context" .) }} {{ partial "sidebar.html" (dict "context" .) }}
{{ partial "toc.html" . }} {{ partial "toc.html" . }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12"> <main id="content" class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12">
{{ partial "breadcrumb.html" (dict "page" . "enable" true) }} {{ partial "breadcrumb.html" (dict "page" . "enable" true) }}
<div class="content"> <div class="content">
{{ if .Title }} {{ if .Title }}

View File

@@ -3,7 +3,7 @@
{{ partial "sidebar.html" (dict "context" .) }} {{ partial "sidebar.html" (dict "context" .) }}
{{ partial "toc.html" . }} {{ partial "toc.html" . }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12"> <main id="content" class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12">
{{ partial "breadcrumb.html" (dict "page" . "enable" true) }} {{ partial "breadcrumb.html" (dict "page" . "enable" true) }}
<div class="content"> <div class="content">
{{ if .Title }} {{ if .Title }}

View File

@@ -3,7 +3,7 @@
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}
{{ partial "toc.html" (dict "Params" (dict "toc" false)) }} {{ partial "toc.html" (dict "Params" (dict "toc" false)) }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12"> <main id="content" class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12">
{{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }} {{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
<div class="content"> <div class="content">
{{- with (index .Site.Data .Site.Language.Lang "termbase") -}} {{- with (index .Site.Data .Site.Language.Lang "termbase") -}}

View File

@@ -1,10 +1,10 @@
{{ define "main" }} {{ define "main" }}
<div class='hx:mx-auto hx:flex hextra-max-page-width'> <div class='hx:mx-auto hx:flex hextra-max-page-width'>
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true) }} {{ partial "sidebar.html" (dict "context" . "disableSidebar" true) }}
<div class="hx:w-full hx:break-words hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:pb-8 hx:pt-8 hx:md:pt-12 hx:pl-[max(env(safe-area-inset-left),1.5rem)] hx:pr-[max(env(safe-area-inset-left),1.5rem)]"> <main id="content" class="hx:w-full hx:break-words hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:pb-8 hx:pt-8 hx:md:pt-12 hx:pl-[max(env(safe-area-inset-left),1.5rem)] hx:pr-[max(env(safe-area-inset-left),1.5rem)]">
<div class="hx:flex hx:flex-col hx:items-start"> <div class="hx:flex hx:flex-col hx:items-start">
{{ .Content }} {{ .Content }}
</div> </div>
</div> </main>
</div> </div>
{{ end }} {{ end }}

View File

@@ -3,7 +3,7 @@
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}
{{ partial "toc.html" . }} {{ partial "toc.html" . }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12"> <main id="content" class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12">
{{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }} {{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
<div class="content"> <div class="content">
{{ .Content }} {{ .Content }}

View File

@@ -3,7 +3,7 @@
{{ partial "sidebar.html" (dict "context" .) }} {{ partial "sidebar.html" (dict "context" .) }}
{{ partial "toc.html" . }} {{ partial "toc.html" . }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12"> <main id="content" class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12">
{{ partial "breadcrumb.html" (dict "page" . "enable" false) }} {{ partial "breadcrumb.html" (dict "page" . "enable" false) }}
<div class="content"> <div class="content">
{{ if .Title }}<h1>{{ .Title }}</h1>{{ end }} {{ if .Title }}<h1>{{ .Title }}</h1>{{ end }}

View File

@@ -3,7 +3,7 @@
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}
{{ partial "toc.html" . }} {{ partial "toc.html" . }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12"> <main id="content" class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12">
{{ partial "breadcrumb.html" (dict "page" . "enable" false) }} {{ partial "breadcrumb.html" (dict "page" . "enable" false) }}
<br class="hx:mt-1.5 hx:text-sm" /> <br class="hx:mt-1.5 hx:text-sm" />
{{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }} {{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}

View File

@@ -3,7 +3,7 @@
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}
{{ partial "toc.html" (dict "Params" (dict "toc" false)) }} {{ partial "toc.html" (dict "Params" (dict "toc" false)) }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12"> <main id="content" class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12">
<br class="hx:mt-1.5 hx:text-sm" /> <br class="hx:mt-1.5 hx:text-sm" />
{{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }} {{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
<div class="hx:mb-16"></div> <div class="hx:mb-16"></div>

View File

@@ -3,7 +3,7 @@
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}
{{ partial "toc.html" (dict "Params" (dict "toc" false)) }} {{ partial "toc.html" (dict "Params" (dict "toc" false)) }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]"> <article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12"> <main id="content" class="hx:w-full hx:min-w-0 hextra-max-content-width hx:px-6 hx:pt-4 hx:md:px-12">
<br class="hx:mt-1.5 hx:text-sm" /> <br class="hx:mt-1.5 hx:text-sm" />
{{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }} {{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
<div class="hx:mb-16"></div> <div class="hx:mb-16"></div>

View File

@@ -1,12 +1,12 @@
{{ define "main" }} {{ define "main" }}
<div class="hx:mx-auto hx:flex hx:max-w-[90rem]"> <div class="hx:mx-auto hx:flex hx:max-w-[90rem]">
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" false) }} {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" false) }}
<article class="hx:w-full hx:break-words hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:pt-4 hx:pb-8 hx:pl-[max(env(safe-area-inset-left),1.5rem)] hx:pr-[max(env(safe-area-inset-left),1.5rem)]"> <main id="content" class="hx:w-full hx:break-words hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:pt-4 hx:pb-8 hx:pl-[max(env(safe-area-inset-left),1.5rem)] hx:pr-[max(env(safe-area-inset-left),1.5rem)]">
<br class="hx:mt-1.5 hx:text-sm" /> <br class="hx:mt-1.5 hx:text-sm" />
{{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }} {{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
<div class="content"> <div class="content">
{{ .Content }} {{ .Content }}
</div> </div>
</article> </main>
</div> </div>
{{ end }} {{ end }}

1097
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,13 +3,18 @@
"dev:theme": "hugo server --logLevel=debug --config=hugo.yaml,../dev.toml --environment=theme --source=docs --themesDir=../.. -D -F --port 1313", "dev:theme": "hugo server --logLevel=debug --config=hugo.yaml,../dev.toml --environment=theme --source=docs --themesDir=../.. -D -F --port 1313",
"dev": "hugo server --source=docs --themesDir=../.. --disableFastRender -D --port 1313", "dev": "hugo server --source=docs --themesDir=../.. --disableFastRender -D --port 1313",
"build:css": "npx postcss --config postcss.config.mjs --env production assets/css/styles.css -o assets/css/compiled/main.css", "build:css": "npx postcss --config postcss.config.mjs --env production assets/css/styles.css -o assets/css/compiled/main.css",
"build": "hugo --gc --minify --themesDir=../.. --source=docs" "build": "hugo --gc --minify --themesDir=../.. --source=docs",
"test": "npx playwright test",
"test:a11y": "npx playwright test tests/accessibility.spec.ts"
}, },
"devDependencies": { "devDependencies": {
"@axe-core/playwright": "^4.10.1",
"@playwright/test": "^1.49.1",
"@tailwindcss/postcss": "^4.1.18", "@tailwindcss/postcss": "^4.1.18",
"postcss-cli": "^11.0.1", "postcss-cli": "^11.0.1",
"prettier": "^3.8.0", "prettier": "^3.8.0",
"prettier-plugin-go-template": "^0.0.15", "prettier-plugin-go-template": "^0.0.15",
"serve": "^14.2.5",
"tailwindcss": "^4.1.18" "tailwindcss": "^4.1.18"
} }
} }

21
playwright.config.ts Normal file
View File

@@ -0,0 +1,21 @@
import { defineConfig } from "@playwright/test";
const baseURL = process.env.BASE_URL || "http://localhost:1313";
export default defineConfig({
testDir: "./tests",
timeout: 60_000,
retries: 0,
reporter: [["list"], ["html"]],
use: {
baseURL,
},
webServer: process.env.BASE_URL
? undefined
: {
command: "npx serve docs/public -l tcp://localhost:1313 --no-clipboard",
url: baseURL,
reuseExistingServer: !process.env.CI,
timeout: 30_000,
},
});

View File

@@ -0,0 +1,92 @@
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
const WCAG_TAGS = ["wcag2a", "wcag2aa", "wcag22aa"];
// TODO: Re-enable once known baseline issues are resolved and tracked.
const DISABLED_RULES = ["color-contrast", "target-size"];
type Violation = Awaited<
ReturnType<InstanceType<typeof AxeBuilder>["analyze"]>
>["violations"][number];
function decodeXmlEntities(value: string): string {
return value
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'");
}
function parseLocUrlsFromSitemap(xml: string): string[] {
const locRegex = /<loc>\s*([^<]+?)\s*<\/loc>/gi;
const urls: string[] = [];
let match: RegExpExecArray | null;
while ((match = locRegex.exec(xml)) !== null) {
urls.push(decodeXmlEntities(match[1]));
}
return urls;
}
async function getEnglishPages(baseURL: string): Promise<string[]> {
const sitemapUrl = `${baseURL}/en/sitemap.xml`;
const response = await fetch(sitemapUrl);
if (!response.ok) {
throw new Error(
`Failed to fetch sitemap (${response.status} ${response.statusText}) at ${sitemapUrl}`,
);
}
const xml = await response.text();
const pages = parseLocUrlsFromSitemap(xml)
.map((url) => {
try {
return new URL(url).pathname;
} catch {
return url;
}
});
if (pages.length === 0) {
throw new Error(`Sitemap at ${sitemapUrl} returned no URLs.`);
}
return pages;
}
function formatViolation(v: Violation): string {
return `${v.id} (${v.impact}) — ${v.nodes.length} element(s)\n ${v.help}\n ${v.helpUrl}`;
}
test("all English pages pass axe-core WCAG AA", async ({ page, baseURL }) => {
const pages = await getEnglishPages(baseURL!);
const failures: string[] = [];
for (const path of pages) {
await test.step(path, async () => {
await page.goto(path, { waitUntil: "load" });
const results = await new AxeBuilder({ page })
.withTags(WCAG_TAGS)
.disableRules(DISABLED_RULES)
.analyze();
if (results.violations.length === 0) {
return;
}
failures.push(
`--- ${path} ---\n${results.violations
.map(formatViolation)
.join("\n\n")}`,
);
});
}
expect(
failures,
`Accessibility violations found:\n\n${failures.join("\n\n")}`,
).toHaveLength(0);
});