diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml index 41991bf..11583f6 100644 --- a/.github/workflows/mkdocs.yml +++ b/.github/workflows/mkdocs.yml @@ -27,9 +27,22 @@ jobs: with: python-version: '3.12' - run: pip install mkdocs-material mkdocs-static-i18n - # --strict fails the build on broken internal links. The docs use - # relative .md cross-links, so this is the gate that keeps them honest. - - run: mkdocs build --strict + # Build without --strict so the known i18n + navigation.instant warning + # does not abort. Still fail on any other WARNING (link/config issues). + # Language alternates are rewritten at runtime (language-selector-label.js). + - name: Build docs + run: | + set -o pipefail + mkdocs build 2>&1 | tee /tmp/mkdocs-build.log + # Allow only the known i18n ↔ navigation.instant incompatibility warning + # (exact message from mkdocs-static-i18n). Any other WARNING fails CI. + allowed='mkdocs_static_i18n: mkdocs-material language switcher contextual link is not compatible with theme.features = navigation.instant' + unexpected=$(grep -E '^WARNING' /tmp/mkdocs-build.log | grep -vF "$allowed" || true) + if [ -n "$unexpected" ]; then + echo "::error::Unexpected mkdocs WARNING(s):" + echo "$unexpected" + exit 1 + fi # tar --dereference is what preserves the hidden .well-known directory. # Both deploy jobs consume this one artifact, so the two origins cannot drift. - name: Package site diff --git a/docs/dz-icon.png b/docs/dz-icon.png deleted file mode 100644 index 8170361..0000000 Binary files a/docs/dz-icon.png and /dev/null differ diff --git a/docs/favicon.ico b/docs/favicon.ico index 9734d2b..7aab9fc 100644 Binary files a/docs/favicon.ico and b/docs/favicon.ico differ diff --git a/docs/fonts/RecklessStandardM-Bold.woff2 b/docs/fonts/RecklessStandardM-Bold.woff2 new file mode 100644 index 0000000..f10550a Binary files /dev/null and b/docs/fonts/RecklessStandardM-Bold.woff2 differ diff --git a/docs/fonts/RecklessStandardM-Medium.woff2 b/docs/fonts/RecklessStandardM-Medium.woff2 new file mode 100644 index 0000000..86daacf Binary files /dev/null and b/docs/fonts/RecklessStandardM-Medium.woff2 differ diff --git a/docs/fonts/RecklessStandardM-Regular.woff2 b/docs/fonts/RecklessStandardM-Regular.woff2 new file mode 100644 index 0000000..b095d7d Binary files /dev/null and b/docs/fonts/RecklessStandardM-Regular.woff2 differ diff --git a/docs/fonts/RecklessStandardM-SemiBold.woff2 b/docs/fonts/RecklessStandardM-SemiBold.woff2 new file mode 100644 index 0000000..0d919ad Binary files /dev/null and b/docs/fonts/RecklessStandardM-SemiBold.woff2 differ diff --git a/docs/fonts/SuisseIntl-Bold.ttf b/docs/fonts/SuisseIntl-Bold.ttf new file mode 100644 index 0000000..ba18636 Binary files /dev/null and b/docs/fonts/SuisseIntl-Bold.ttf differ diff --git a/docs/fonts/SuisseIntl-Light.ttf b/docs/fonts/SuisseIntl-Light.ttf new file mode 100644 index 0000000..b0c437f Binary files /dev/null and b/docs/fonts/SuisseIntl-Light.ttf differ diff --git a/docs/fonts/SuisseIntl-Medium.ttf b/docs/fonts/SuisseIntl-Medium.ttf new file mode 100644 index 0000000..f3f900f Binary files /dev/null and b/docs/fonts/SuisseIntl-Medium.ttf differ diff --git a/docs/fonts/SuisseIntl-Mono.ttf b/docs/fonts/SuisseIntl-Mono.ttf new file mode 100644 index 0000000..772326e Binary files /dev/null and b/docs/fonts/SuisseIntl-Mono.ttf differ diff --git a/docs/fonts/SuisseIntl-Regular.ttf b/docs/fonts/SuisseIntl-Regular.ttf new file mode 100644 index 0000000..514ac50 Binary files /dev/null and b/docs/fonts/SuisseIntl-Regular.ttf differ diff --git a/docs/fonts/SuisseIntl-SemiBold.ttf b/docs/fonts/SuisseIntl-SemiBold.ttf new file mode 100644 index 0000000..428d8f2 Binary files /dev/null and b/docs/fonts/SuisseIntl-SemiBold.ttf differ diff --git a/docs/javascripts/header-controls-order.js b/docs/javascripts/header-controls-order.js new file mode 100644 index 0000000..54fe959 --- /dev/null +++ b/docs/javascripts/header-controls-order.js @@ -0,0 +1,147 @@ +// Keep theme palette in the header (instant nav replaces the drawer container). +// On mobile, a drawer button clicks the header toggle instead of relocating the form. +(function () { + 'use strict'; + + var MOBILE_MQ = '(max-width: 40em)'; + + function syncPaletteIcon(form) { + if (!form) { + form = document.querySelector('[data-md-component="palette"]'); + } + if (!form) return; + + // Compact theme button owns the visible UI — keep Material labels hidden. + if (form.querySelector('.dz2-theme-btn') || form.querySelector('.dz2-mode-switch')) { + var allLabels = form.querySelectorAll('label.md-header__button'); + for (var k = 0; k < allLabels.length; k++) { + allLabels[k].setAttribute('hidden', ''); + } + return; + } + + var inputs = form.querySelectorAll('.md-option'); + if (!inputs.length) return; + + var checked = form.querySelector('.md-option:checked'); + if (!checked) { + var scheme = document.body.getAttribute('data-md-color-scheme') || 'slate'; + for (var i = 0; i < inputs.length; i++) { + if (inputs[i].getAttribute('data-md-color-scheme') === scheme) { + checked = inputs[i]; + inputs[i].checked = true; + break; + } + } + if (!checked) { + checked = inputs[0]; + checked.checked = true; + } + } + + for (var j = 0; j < inputs.length; j++) { + var input = inputs[j]; + var label = input.nextElementSibling; + while (label && label.tagName !== 'LABEL') { + label = label.nextElementSibling; + } + if (!label) continue; + var shouldHide = input !== checked; + var isHidden = label.hasAttribute('hidden'); + if (shouldHide && !isHidden) { + label.setAttribute('hidden', ''); + } else if (!shouldHide && isHidden) { + label.removeAttribute('hidden'); + } + } + } + + function placePalette() { + var palette = document.querySelector('[data-md-component="palette"]'); + var header = document.querySelector('.md-header__inner'); + if (!palette || !header) return; + // Always keep the form in the header so instant navigation cannot destroy it. + if (palette.parentElement !== header || header.lastElementChild !== palette) { + header.appendChild(palette); + } + } + + function syncDrawerThemeBtn() { + var drawerBtn = document.getElementById('drawer-theme-btn'); + var real = document.querySelector('.dz2-theme-btn'); + if (!drawerBtn || !real) return; + drawerBtn.setAttribute('aria-label', real.getAttribute('aria-label') || 'Toggle theme'); + drawerBtn.setAttribute('data-next-mode', real.getAttribute('data-next-mode') || ''); + var icon = real.querySelector('svg'); + if (icon) { + drawerBtn.innerHTML = icon.outerHTML; + } + } + + function refresh() { + placePalette(); + syncPaletteIcon(); + syncDrawerThemeBtn(); + } + + function boot() { + refresh(); + var form = document.querySelector('[data-md-component="palette"]'); + if (form && !form.dataset.paletteSyncBound) { + form.dataset.paletteSyncBound = '1'; + form.addEventListener('change', function () { + syncPaletteIcon(form); + syncDrawerThemeBtn(); + }); + } + + if (!window.__dzPaletteMqBound) { + window.__dzPaletteMqBound = true; + window.matchMedia(MOBILE_MQ).addEventListener('change', refresh); + + // Opening search from the drawer should close the drawer first + document.addEventListener('click', function (event) { + var trigger = event.target.closest && event.target.closest('label[for="__search"]'); + if (!trigger || !trigger.closest('.mobile-drawer-controls')) return; + var drawer = document.getElementById('__drawer'); + if (drawer) drawer.checked = false; + }); + + // Drawer theme control proxies the header toggle (form stays in header). + document.addEventListener('click', function (event) { + var drawerBtn = event.target.closest && event.target.closest('#drawer-theme-btn'); + if (!drawerBtn) return; + event.preventDefault(); + var real = document.querySelector('.dz2-theme-btn'); + if (real) real.click(); + }); + } + + var header = document.querySelector('.md-header'); + if (header && !header.dataset.paletteOrderBound) { + header.dataset.paletteOrderBound = '1'; + new MutationObserver(function () { + refresh(); + }).observe(header, { childList: true, subtree: true }); + } + + new MutationObserver(syncDrawerThemeBtn).observe(document.body, { + attributes: true, + attributeFilter: ['data-md-color-scheme', 'data-md-color-primary'] + }); + } + + function onReady(fn) { + if (typeof document$ !== 'undefined' && document$.subscribe) { + document$.subscribe(fn); + return; + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', fn); + } else { + fn(); + } + } + + onReady(boot); +})(); diff --git a/docs/javascripts/language-selector-label.js b/docs/javascripts/language-selector-label.js index 581f1de..c043fae 100644 --- a/docs/javascripts/language-selector-label.js +++ b/docs/javascripts/language-selector-label.js @@ -1,5 +1,9 @@ -// Display the currently selected language name next to the language selector icon -(function() { +// Language control: label + path-aware alternate links for navigation.instant. +// +// mkdocs-static-i18n warns that Material's contextual language hrefs go stale +// under instant navigation. We rewrite every alternate link from the current +// pathname so locale switches always target the page you are on. +(function () { 'use strict'; var LOCALE_SEGMENTS = ['zh', 'ja', 'ko', 'pt', 'es', 'fr', 'it']; @@ -14,39 +18,100 @@ return 'en'; } - function getCurrentLanguageName() { - var locale = getCurrentLocale(); - var selector = document.querySelector('.md-select .md-select__list'); - if (!selector) return null; - var links = selector.querySelectorAll('.md-select__link'); + function stripLocale(pathname) { + var segments = (pathname || '/').split('/').filter(Boolean); + if (segments.length && LOCALE_SEGMENTS.indexOf(segments[0]) !== -1) { + segments.shift(); + } + return segments; + } + + // Build a same-page URL in targetLocale (en has no prefix). + // Preserve query + hash so deep links like /architecture/#topology survive. + function localizePath(pathname, targetLocale) { + var segments = stripLocale(pathname); + var tail = segments.length ? segments.join('/') + '/' : ''; + var base; + if (!targetLocale || targetLocale === 'en') { + base = '/' + tail; + } else { + base = '/' + targetLocale + '/' + tail; + } + return base + window.location.search + window.location.hash; + } + + function markActive(links, locale) { + var activeName = null; for (var i = 0; i < links.length; i++) { var link = links[i]; var hreflang = link.getAttribute('hreflang') || ''; + link.setAttribute('href', localizePath(window.location.pathname, hreflang)); if (hreflang === locale) { - return link.textContent.trim(); + link.classList.add('is-active'); + link.setAttribute('aria-current', 'page'); + activeName = link.textContent.trim(); + } else { + link.classList.remove('is-active'); + link.removeAttribute('aria-current'); } } - return null; + return activeName; + } + + function bindLocaleClicks(root) { + if (!root || root.dataset.langNavBound === '1') return; + root.dataset.langNavBound = '1'; + root.addEventListener('click', function (event) { + var link = event.target.closest && event.target.closest('a[hreflang]'); + if (!link || !root.contains(link)) return; + var hreflang = link.getAttribute('hreflang') || 'en'; + // Full navigation on locale change (search index / chrome differ per language). + event.preventDefault(); + window.location.assign(localizePath(window.location.pathname, hreflang)); + }); } function init() { - // Find the language selector button via its parent .md-select container - // rather than aria-label, which gets translated on non-English pages - var selector = document.querySelector('.md-select'); - if (!selector) return; - var btn = selector.querySelector('button'); - if (!btn) return; - var name = getCurrentLanguageName(); - if (!name) return; - var label = document.createElement('span'); - label.className = 'md-header__language-label'; - label.textContent = name; - btn.appendChild(label); + var locale = getCurrentLocale(); + + var selector = document.querySelector('.md-header .md-select'); + if (selector) { + var btn = selector.querySelector('button'); + var label = selector.querySelector('.md-header__language-label'); + var activeName = markActive(selector.querySelectorAll('.md-select__link'), locale); + + if (label && activeName) { + label.textContent = activeName; + } + if (btn && activeName) { + btn.setAttribute('title', activeName); + btn.setAttribute('aria-label', 'Language: ' + activeName); + } + bindLocaleClicks(selector); + } + + var drawerLang = document.querySelector('.mobile-drawer-lang'); + if (drawerLang) { + var drawerLabel = drawerLang.querySelector('.mobile-drawer-lang__label'); + var drawerActive = markActive(drawerLang.querySelectorAll('.mobile-drawer-lang__list a'), locale); + if (drawerLabel && drawerActive) { + drawerLabel.textContent = drawerActive; + } + bindLocaleClicks(drawerLang); + } } - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', init); - } else { - init(); + function onReady(fn) { + if (typeof document$ !== 'undefined' && document$.subscribe) { + document$.subscribe(fn); + return; + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', fn); + } else { + fn(); + } } + + onReady(init); })(); diff --git a/docs/javascripts/nav-accordion.js b/docs/javascripts/nav-accordion.js new file mode 100644 index 0000000..9e30b21 --- /dev/null +++ b/docs/javascripts/nav-accordion.js @@ -0,0 +1,67 @@ +// Accordion primary nav: opening a nested section closes sibling sections +// at the same level (so Solana + Other tenants can't both stay expanded). +(function() { + 'use strict'; + + function siblingToggles(toggle) { + var item = toggle.closest('.md-nav__item--nested'); + if (!item) return []; + var list = item.parentElement; + if (!list || !list.classList.contains('md-nav__list')) return []; + var result = []; + var children = list.children; + for (var i = 0; i < children.length; i++) { + var child = children[i]; + if (!child.classList || !child.classList.contains('md-nav__item--nested')) continue; + var kids = child.children; + for (var j = 0; j < kids.length; j++) { + if (kids[j].classList && kids[j].classList.contains('md-nav__toggle') && kids[j] !== toggle) { + result.push(kids[j]); + break; + } + } + } + return result; + } + + function closeSiblings(toggle) { + siblingToggles(toggle).forEach(function(other) { + other.checked = false; + other.classList.remove('md-toggle--indeterminate'); + // Material uses indeterminate for "contains active page"; clear it so + // the section visually collapses when another section is opened. + try { other.indeterminate = false; } catch (e) {} + }); + } + + function onToggleChange(e) { + var toggle = e.target; + if (!toggle || !toggle.classList || !toggle.classList.contains('md-nav__toggle')) return; + if (!toggle.checked) return; + closeSiblings(toggle); + } + + function bind(root) { + if (!root || root.getAttribute('data-nav-accordion') === '1') return; + root.setAttribute('data-nav-accordion', '1'); + root.addEventListener('change', onToggleChange); + } + + function init() { + document.querySelectorAll('.md-sidebar--primary').forEach(bind); + } + + function onReady(fn) { + if (typeof document$ !== 'undefined' && document$.subscribe) { + document$.subscribe(fn); + return; + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', fn); + } else { + fn(); + } + } + + onReady(init); +})(); diff --git a/docs/javascripts/page-toolbar.js b/docs/javascripts/page-toolbar.js index 7211886..158b74b 100644 --- a/docs/javascripts/page-toolbar.js +++ b/docs/javascripts/page-toolbar.js @@ -7,23 +7,34 @@ toolbar.className = 'page-toolbar'; toolbar.innerHTML = '' + - '' + - 'Copy Page' + + '' + + 'Copy Page' + '' + '|' + '' + - '' + - 'View Markdown' + + '' + + 'View Markdown' + '' + '|' + '' + - '' + - 'Ask in ChatGPT' + + '' + + 'Ask in ChatGPT' + '' + '|' + '' + '' + - 'Ask in Claude' + + 'Ask in Claude' + ''; return toolbar; } @@ -81,10 +92,22 @@ function showCopyFeedback(buttonId) { var btn = document.getElementById(buttonId); if (btn) { - var originalText = btn.textContent; - btn.textContent = 'Copied!'; + var label = btn.querySelector('.page-toolbar-link__text'); + var originalText = label ? label.textContent : btn.textContent; + if (label) { + label.textContent = 'Copied!'; + } else { + btn.textContent = 'Copied!'; + } btn.classList.add('copied'); - setTimeout(function() { btn.textContent = originalText; btn.classList.remove('copied'); }, 2000); + setTimeout(function() { + if (label) { + label.textContent = originalText; + } else { + btn.textContent = originalText; + } + btn.classList.remove('copied'); + }, 2000); } } @@ -109,6 +132,9 @@ } function addToolbar() { + var existing = document.querySelector('.page-toolbar-footer'); + if (existing) existing.remove(); + var footer = document.createElement('footer'); footer.className = 'page-toolbar-footer'; footer.appendChild(createToolbar()); @@ -120,9 +146,17 @@ document.getElementById('ask-claude-btn').addEventListener('click', function(e) { e.preventDefault(); askInClaude(); }); } - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', addToolbar); - } else { - addToolbar(); + function onReady(fn) { + if (typeof document$ !== 'undefined' && document$.subscribe) { + document$.subscribe(fn); + return; + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', fn); + } else { + fn(); + } } + + onReady(addToolbar); })(); diff --git a/docs/javascripts/search-language-filter.js b/docs/javascripts/search-language-filter.js index 0b74792..2735cb2 100644 --- a/docs/javascripts/search-language-filter.js +++ b/docs/javascripts/search-language-filter.js @@ -15,7 +15,9 @@ return LOCALE_SEGMENTS.indexOf(first) !== -1 ? first : 'en'; } - var currentLocale = localeOfPath(window.location.pathname); + function currentLocale() { + return localeOfPath(window.location.pathname); + } function localeOfHref(href) { if (!href) return 'en'; @@ -40,6 +42,7 @@ function filterResults(resultRoot) { var list = resultRoot.querySelector('.md-search-result__list'); if (!list) return; + var locale = currentLocale(); var items = list.querySelectorAll('.md-search-result__item'); var visible = 0; for (var i = 0; i < items.length; i++) { @@ -47,7 +50,7 @@ var link = item.querySelector('a.md-search-result__link'); // Use the resolved .href so the URL is absolute regardless of whether // Material emitted a root- or page-relative attribute. - var keep = !!link && localeOfHref(link.href) === currentLocale; + var keep = !!link && localeOfHref(link.href) === locale; item.hidden = !keep; item.style.display = keep ? '' : 'none'; if (keep) visible++; @@ -57,7 +60,8 @@ function init() { var resultRoot = document.querySelector('[data-md-component="search-result"]'); - if (!resultRoot) return; + if (!resultRoot || resultRoot.dataset.langFilterBound === '1') return; + resultRoot.dataset.langFilterBound = '1'; var scheduled = false; var observer = new MutationObserver(function() { @@ -73,9 +77,17 @@ observer.observe(resultRoot, { childList: true, subtree: true }); } - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', init); - } else { - init(); + function onReady(fn) { + if (typeof document$ !== 'undefined' && document$.subscribe) { + document$.subscribe(fn); + return; + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', fn); + } else { + fn(); + } } + + onReady(init); })(); diff --git a/docs/javascripts/theme-mode-switch.js b/docs/javascripts/theme-mode-switch.js new file mode 100644 index 0000000..28c1f6e --- /dev/null +++ b/docs/javascripts/theme-mode-switch.js @@ -0,0 +1,158 @@ +// DoubleZero CompactThemeToggle (doublezero.xyz) — single button, not a switch. +(function () { + 'use strict'; + + var LIGHT_SCHEME = 'default'; + var DARK_SCHEME = 'slate'; + + // Glyph for the mode you will switch TO (matches marketing CompactThemeToggle). + var ICON_LIGHT = + ''; + + var ICON_DARK = + ''; + + function currentScheme() { + return document.body.getAttribute('data-md-color-scheme') || DARK_SCHEME; + } + + function nextMode() { + return currentScheme() === LIGHT_SCHEME ? 'dark' : 'light'; + } + + function findPaletteInput(scheme) { + var form = document.querySelector('[data-md-component="palette"]'); + if (!form) return null; + var inputs = form.querySelectorAll('input[data-md-color-scheme]'); + for (var i = 0; i < inputs.length; i++) { + if (inputs[i].getAttribute('data-md-color-scheme') === scheme) { + return inputs[i]; + } + } + return null; + } + + function applyScheme(scheme) { + var input = findPaletteInput(scheme); + if (!input || input.checked) { + syncButton(); + return; + } + var label = document.querySelector('label[for="' + input.id + '"]'); + if (label) { + label.click(); + } else { + input.checked = true; + input.dispatchEvent(new Event('change', { bubbles: true })); + } + syncButton(); + } + + function cycleTheme() { + applyScheme(nextMode() === 'light' ? LIGHT_SCHEME : DARK_SCHEME); + } + + function isApplePlatform() { + var platform = navigator.platform || ''; + var ua = navigator.userAgent || ''; + return /Mac|iPhone|iPad|iPod/i.test(platform) || /Mac OS X/.test(ua); + } + + function shortcutKbdHtml() { + if (isApplePlatform()) { + return '+Shift+L'; + } + return 'Ctrl+Shift+L'; + } + + function syncButton() { + var btn = document.querySelector('.dz2-theme-btn'); + if (!btn) return; + var mode = nextMode(); + var label = mode === 'dark' ? 'Switch to dark mode' : 'Switch to light mode'; + btn.setAttribute('aria-label', label); + btn.setAttribute('data-next-mode', mode); + btn.innerHTML = + (mode === 'dark' ? ICON_DARK : ICON_LIGHT) + + '' + + '' + + label + + '' + + shortcutKbdHtml() + + ''; + } + + function buildButton() { + var btn = document.createElement('button'); + btn.type = 'button'; + btn.className = 'dz2-theme-btn'; + btn.addEventListener('click', cycleTheme); + return btn; + } + + function hideMaterialLabels(form) { + if (!form) return; + form.classList.add('dz2-palette-host'); + var labels = form.querySelectorAll('label.md-header__button'); + for (var i = 0; i < labels.length; i++) { + labels[i].setAttribute('hidden', ''); + labels[i].setAttribute('aria-hidden', 'true'); + } + } + + function mount() { + var form = document.querySelector('[data-md-component="palette"]'); + if (!form) return; + + hideMaterialLabels(form); + + // Remove accidental ModeSwitch if a previous load left it around + var oldSwitch = form.querySelector('.dz2-mode-switch'); + if (oldSwitch) oldSwitch.remove(); + + var existing = form.querySelector('.dz2-theme-btn'); + if (!existing) { + form.appendChild(buildButton()); + } + syncButton(); + } + + function boot() { + mount(); + + var form = document.querySelector('[data-md-component="palette"]'); + if (form) { + form.addEventListener('change', syncButton); + } + + new MutationObserver(syncButton).observe(document.body, { + attributes: true, + attributeFilter: ['data-md-color-scheme', 'data-md-color-primary'] + }); + + document.addEventListener('keydown', function (event) { + if (!(event.ctrlKey || event.metaKey) || !event.shiftKey) return; + if (event.key !== 'l' && event.key !== 'L') return; + var tag = (event.target && event.target.tagName) || ''; + if (tag === 'INPUT' || tag === 'TEXTAREA' || (event.target && event.target.isContentEditable)) { + return; + } + event.preventDefault(); + cycleTheme(); + }); + + if (typeof document$ !== 'undefined' && document$.subscribe) { + document$.subscribe(mount); + } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', boot); + } else { + boot(); + } +})(); diff --git a/docs/javascripts/toc-page-top.js b/docs/javascripts/toc-page-top.js new file mode 100644 index 0000000..868d490 --- /dev/null +++ b/docs/javascripts/toc-page-top.js @@ -0,0 +1,16 @@ +// "On this page" TOC title → scroll to top of the current page +(function () { + 'use strict'; + + function scrollToPageTop(event) { + var link = event.target.closest('.toc-page-top'); + if (!link) return; + event.preventDefault(); + window.scrollTo({ top: 0, behavior: 'smooth' }); + if (history.replaceState) { + history.replaceState(null, '', window.location.pathname + window.location.search); + } + } + + document.addEventListener('click', scrollToPageTop); +})(); diff --git a/docs/logo-light.svg b/docs/logo-light.svg new file mode 100644 index 0000000..65ce84d --- /dev/null +++ b/docs/logo-light.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/docs/logo.png b/docs/logo.png deleted file mode 100644 index 9ec90fa..0000000 Binary files a/docs/logo.png and /dev/null differ diff --git a/docs/logo.svg b/docs/logo.svg new file mode 100644 index 0000000..d278ba1 --- /dev/null +++ b/docs/logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index ed9b948..6a4c4f3 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -1,101 +1,1991 @@ - :root > * { - --md-accent-fg-color: #16B5F4; - } +/* DoubleZero brand identity (Media Kit 2026 + doublezero.xyz). + Display: Reckless · Body: Suisse Intl · Mono: Suisse Intl Mono + Palette: black/white primary; spectrum accents only as thin fiber edges. +*/ +@font-face { + font-family: "Reckless"; + src: url("../fonts/RecklessStandardM-Regular.woff2") format("woff2"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Reckless"; + src: url("../fonts/RecklessStandardM-Medium.woff2") format("woff2"); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Reckless"; + src: url("../fonts/RecklessStandardM-SemiBold.woff2") format("woff2"); + font-weight: 600; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Reckless"; + src: url("../fonts/RecklessStandardM-Bold.woff2") format("woff2"); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Suisse Intl"; + src: url("../fonts/SuisseIntl-Light.ttf") format("truetype"); + font-weight: 300; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Suisse Intl"; + src: url("../fonts/SuisseIntl-Regular.ttf") format("truetype"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Suisse Intl"; + src: url("../fonts/SuisseIntl-Medium.ttf") format("truetype"); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Suisse Intl"; + src: url("../fonts/SuisseIntl-SemiBold.ttf") format("truetype"); + font-weight: 600; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Suisse Intl"; + src: url("../fonts/SuisseIntl-Bold.ttf") format("truetype"); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Suisse Intl Mono"; + src: url("../fonts/SuisseIntl-Mono.ttf") format("truetype"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +:root { + /* doublezero.xyz + logo spectrum */ + --dz-black: #08090a; + --dz-white: #fafafa; + --dz-grey: #62666d; + --dz-grey-light: #e2e2e2; + --dz-green: #21b906; + --dz-green-soft: color-mix(in srgb, #21b906 10%, #fafafa); + --dz-teal: #07c8c9; + --dz-blue: #1b63ff; + --dz-yellow: #ffdc78; + --dz-red: #d92308; + --dz-orange: #ff5a1f; + --dz-fiber: linear-gradient( + 90deg, + var(--dz-teal) 0%, + var(--dz-blue) 28%, + #9b7bff 48%, + var(--dz-yellow) 68%, + var(--dz-orange) 85%, + var(--dz-red) 100% + ); + --dz-font-display: "Reckless", "Times New Roman", Georgia, serif; + --dz-font-body: "Suisse Intl", "Helvetica Neue", Helvetica, Arial, sans-serif; + --dz-font-mono: "Suisse Intl Mono", ui-monospace, "SF Mono", Menlo, monospace; + --dz-surface: #08090a; + --dz-surface-raised: #121314; + --dz-surface-code: #18191b; + --dz-text: #e2e2e2; + --dz-text-strong: #fafafa; + --dz-text-muted: #62666d; + /* Header chrome matches doublezero.xyz: same surface as page + hairline */ + --dz-header-bg: var(--dz-surface); + --dz-header-border: #1f1f20; + /* doublezero.xyz: 64px mobile, 67px desktop (≥ xl / 1280px) */ + --dz-header-height: 64px; + --md-text-font: "Suisse Intl"; + --md-code-font: "Suisse Intl Mono"; +} + +@media screen and (min-width: 1280px) { + :root { + --dz-header-height: 67px; + } +} + +:root > * { + --md-accent-fg-color: var(--dz-grey-light); + --md-accent-fg-color--transparent: color-mix(in srgb, var(--dz-grey-light) 12%, transparent); + --md-typeset-a-color: var(--dz-grey-light); +} + +/* Light mode: clean paper + dark type (doublezero.xyz .light) */ +[data-md-color-scheme="default"] { + --md-default-bg-color: #fafafa; + --md-default-bg-color--light: #fafafa; + --md-default-bg-color--lighter: #f0f0f0; + --md-default-bg-color--lightest: #e2e2e2; + --md-default-fg-color: rgba(8, 9, 10, 0.92); + --md-default-fg-color--light: rgba(8, 9, 10, 0.72); + --md-default-fg-color--lighter: rgba(8, 9, 10, 0.54); + --md-default-fg-color--lightest: rgba(8, 9, 10, 0.14); + --md-typeset-color: rgba(8, 9, 10, 0.9); + --md-typeset-a-color: #08090a; + --md-code-bg-color: #efefef; + --md-code-fg-color: #08090a; + /* Keep Material primary in sync so the header never falls back to grey */ + --md-primary-fg-color: #fafafa; + --md-primary-fg-color--light: #fafafa; + --md-primary-fg-color--dark: #fafafa; + --md-primary-bg-color: #08090a; + --md-accent-fg-color: #08090a; + --dz-header-bg: #fafafa; + --dz-header-border: #e2e2e2; + --dz-green-soft: color-mix(in srgb, #21b906 8%, #fafafa); +} + +/* Dark mode: DoubleZero black canvas (doublezero.xyz .dark) */ +[data-md-color-scheme="slate"] { + --md-default-bg-color: var(--dz-surface); + --md-default-bg-color--light: #0c0d0e; + --md-default-bg-color--lighter: var(--dz-surface-raised); + --md-default-bg-color--lightest: #222426; + --md-default-fg-color: var(--dz-text-strong); + --md-default-fg-color--light: var(--dz-grey-light); + --md-default-fg-color--lighter: var(--dz-grey); + --md-default-fg-color--lightest: rgba(255, 255, 255, 0.12); + --md-typeset-color: var(--dz-text); + --md-typeset-a-color: var(--dz-grey-light); + --md-code-bg-color: var(--dz-surface-code); + --md-code-fg-color: #f0f0f0; + --md-footer-bg-color: #000000; + --md-footer-bg-color--dark: #000000; + --md-primary-fg-color: #08090a; + --md-primary-fg-color--light: #08090a; + --md-primary-fg-color--dark: #08090a; + --md-primary-bg-color: #fafafa; + --md-accent-fg-color: var(--dz-white); + --dz-header-bg: #08090a; + --dz-header-border: #1f1f20; + --dz-green-soft: color-mix(in srgb, #21b906 14%, #08090a); +} + +body { + font-family: var(--dz-font-body); + -webkit-font-smoothing: antialiased; +} + +code, +pre, +.md-typeset code, +.md-typeset pre, +.md-code__content, +.dz-btn-rect, +.runbook-assist-btn { + font-family: var(--dz-font-mono); +} + +/* Suisse body · Reckless headings only */ +.md-typeset { + font-size: 0.84rem; + line-height: 1.75; + font-family: var(--dz-font-body); + font-weight: 400; + color: var(--md-typeset-color); +} + +.md-typeset p, +.md-typeset li, +.md-typeset td, +.md-typeset th { + font-family: var(--dz-font-body); +} + +.md-typeset h1, +.md-typeset h2, +.md-typeset h3, +.md-typeset h4 { + font-family: var(--dz-font-display); + letter-spacing: -0.02em; + line-height: 1.2; + color: var(--md-default-fg-color); + font-weight: 500; +} + +.md-typeset > h1 { + font-size: 2rem; + font-weight: 500; + margin-bottom: 0.75em; +} + +.md-typeset h2 { + font-weight: 500; + margin-top: 1.75em; + padding-bottom: 0.35em; + border-bottom: 1px solid var(--md-default-fg-color--lightest); +} + +/* Header: same surface as page + hairline (doublezero.xyz header chrome) */ +.md-header, +.md-header--shadow { + background: var(--dz-header-bg) !important; + color: var(--dz-text-strong); + border-bottom: 1px solid var(--dz-header-border); + box-shadow: none !important; + height: var(--dz-header-height); + box-sizing: border-box; +} + +.md-header__inner { + height: 100%; + min-height: var(--dz-header-height); + box-sizing: border-box; + display: flex; + align-items: center; +} + +.md-header__title { + height: auto; + line-height: 1.2; +} + +[data-md-color-scheme="default"] .md-header, +[data-md-color-scheme="default"] .md-header--shadow { + color: var(--dz-black); +} + +.md-header .md-header__button, +.md-header .md-header__topic, +.md-header .md-source, +.md-header .md-tabs__link { + color: inherit; +} + +[data-md-color-scheme="slate"] .md-header .md-header__button:hover, +[data-md-color-scheme="slate"] .md-header .md-header__button:focus { + opacity: 1; + color: var(--dz-white); +} + +[data-md-color-scheme="default"] .md-header .md-header__button:hover, +[data-md-color-scheme="default"] .md-header .md-header__button:focus { + opacity: 1; + color: var(--dz-black); +} + +/* Links + nav accents */ +.md-typeset a { + color: var(--md-typeset-a-color); + text-decoration: underline; + text-decoration-thickness: 1px; + text-underline-offset: 0.18em; + transition: color 0.15s ease, text-decoration-color 0.15s ease; +} + +.md-typeset a:hover { + color: var(--dz-orange); +} + +[data-md-color-scheme="default"] .md-typeset a:hover { + color: var(--dz-orange); +} + +.md-nav__link:hover, +.md-nav__link:focus, +.md-nav__item .md-nav__link--active, +.md-nav__link--active { + color: var(--md-default-fg-color); +} + +.md-nav__link--active { + transition: color 0.15s ease; +} + +/* Orange square bullets (brand lists) */ +.md-typeset ul:not(.task-list):not(.md-nav__list) { + list-style: none; + padding-left: 0; +} + +.md-typeset ul:not(.task-list):not(.md-nav__list) > li { + position: relative; + padding-left: 1.15em; + margin-left: 0.15em; +} + +.md-typeset ul:not(.task-list):not(.md-nav__list) > li::before { + content: ""; + position: absolute; + left: 0; + top: 0.55em; + width: 0.38em; + height: 0.38em; + background: var(--dz-orange); +} + +/* Admonitions: sharp thin rectangles */ +.md-typeset .admonition, +.md-typeset details { + border-radius: 0 !important; + border-width: 1px !important; + box-shadow: none !important; +} + +/* Informational cards (info / note / tip) — brand green */ +.md-typeset .admonition.info, +.md-typeset details.info, +.md-typeset .admonition.note, +.md-typeset details.note, +.md-typeset .admonition.tip, +.md-typeset details.tip { + border-color: var(--dz-green); +} + +.md-typeset .info > .admonition-title, +.md-typeset .info > summary, +.md-typeset .note > .admonition-title, +.md-typeset .note > summary, +.md-typeset .tip > .admonition-title, +.md-typeset .tip > summary { + background-color: var(--dz-green-soft); + border-radius: 0; + color: var(--md-default-fg-color); +} + +.md-typeset .info > .admonition-title::before, +.md-typeset .info > summary::before, +.md-typeset .note > .admonition-title::before, +.md-typeset .note > summary::before, +.md-typeset .tip > .admonition-title::before, +.md-typeset .tip > summary::before { + background-color: var(--dz-green); +} + +.md-typeset .admonition.warning, +.md-typeset details.warning { + border-color: var(--dz-orange); +} + +.md-typeset .warning > .admonition-title, +.md-typeset .warning > summary { + background-color: color-mix(in srgb, var(--dz-orange) 10%, transparent); +} + +.md-typeset .warning > .admonition-title::before, +.md-typeset .warning > summary::before { + background-color: var(--dz-orange); +} + +/* Green pill badge (brand) */ +.dz-badge-green { + display: inline-flex; + align-items: center; + gap: 0.45rem; + padding: 0.35rem 0.85rem; + border: 1px solid var(--dz-green); + border-radius: 999px; + background: var(--dz-green-soft); + color: var(--dz-green); + font-family: var(--dz-font-mono); + font-size: 0.68rem; + font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; + text-decoration: none !important; + line-height: 1; +} + +.dz-badge-green::before { + content: ""; + width: 0.4em; + height: 0.4em; + background: var(--dz-green); + flex-shrink: 0; +} + +/* CTA links — growing underline on hover (same recipe as Support / doublezero.xyz) */ +.dz-cta-rainbow, +a.dz-cta-rainbow { + display: inline-flex; + align-items: center; + gap: 0.35rem; + color: var(--md-default-fg-color) !important; + -webkit-text-fill-color: var(--md-default-fg-color); + background-color: transparent; + background-image: linear-gradient(currentColor, currentColor); + background-position: left 100%; + background-repeat: no-repeat; + background-size: 0 1px; + font-family: var(--dz-font-mono); + font-size: 0.78rem; + font-weight: 500; + letter-spacing: 0.01em; + text-decoration: none !important; + line-height: 1.3; + transition: background-size 200ms cubic-bezier(0.4, 0, 0.2, 1); +} + +.dz-cta-rainbow::after { + content: "→"; + color: var(--md-default-fg-color); + -webkit-text-fill-color: var(--md-default-fg-color); + font-weight: 400; +} + +.dz-cta-rainbow:hover, +.dz-cta-rainbow:focus-visible, +a.dz-cta-rainbow:hover, +a.dz-cta-rainbow:focus-visible { + background-size: 100% 1px; + color: var(--md-default-fg-color) !important; + -webkit-text-fill-color: var(--md-default-fg-color); +} + +/* Side nav + TOC: growing underline instead of rainbow text */ +.md-sidebar .md-nav__link .md-ellipsis, +.md-sidebar .toc-page-top, +.dz-rainbow-hover { + display: inline-block; + max-width: 100%; + vertical-align: bottom; + background-image: linear-gradient(currentColor, currentColor); + background-position: left 100%; + background-repeat: no-repeat; + background-size: 0 1px; + transition: background-size 200ms cubic-bezier(0.4, 0, 0.2, 1), color 0.15s ease; +} + +.md-sidebar .md-nav__link:hover .md-ellipsis, +.md-sidebar .md-nav__link:focus-visible .md-ellipsis, +.md-sidebar .toc-page-top:hover, +.md-sidebar .toc-page-top:focus-visible, +.dz-rainbow-hover:hover, +.dz-rainbow-hover:focus-visible { + background-size: 100% 1px; + background-color: transparent !important; + text-shadow: none !important; + color: var(--md-default-fg-color); + -webkit-text-fill-color: var(--md-default-fg-color); +} + +@media (prefers-reduced-motion: reduce) { + .dz-cta-rainbow, + a.dz-cta-rainbow, + .md-sidebar .md-nav__link .md-ellipsis, + .md-sidebar .toc-page-top, + .dz-rainbow-hover { + transition: none; + } +} + +/* Sharp mono rectangle buttons */ +.dz-btn-rect, +.md-typeset .md-button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.35rem; + padding: 0.55rem 0.95rem; + border: 1px solid var(--md-default-fg-color) !important; + border-radius: 0 !important; + background: var(--md-default-fg-color); + color: var(--md-default-bg-color) !important; + font-family: var(--dz-font-mono); + font-size: 0.72rem; + font-weight: 500; + letter-spacing: 0.02em; + text-decoration: none !important; + line-height: 1.2; + box-shadow: none !important; +} + +.dz-btn-rect:hover, +.md-typeset .md-button:hover { + background: transparent; + color: var(--md-default-fg-color) !important; +} + +.dz-btn-rect--ghost, +.md-typeset .md-button--primary { + background: transparent; + color: var(--md-default-fg-color) !important; +} + +.dz-btn-rect--ghost:hover, +.md-typeset .md-button--primary:hover { + background: var(--md-default-fg-color); + color: var(--md-default-bg-color) !important; +} + +/* Wider layout: push sidebars toward the viewport edges and give content more room */ +.md-grid { + max-width: none; +} + +.md-main__inner.md-grid { + max-width: none; + margin-inline: 0.75rem; +} + +/* Header width: match doublezero.xyz (full-bleed + max-md:px-3 / min-md:px-8) */ +.md-header__inner.md-grid { + max-width: none; + width: 100%; + box-sizing: border-box; + margin-inline: 0; + padding-inline: 12px; /* max-md:px-3 */ + padding-block: 0; +} + +@media screen and (min-width: 768px) { + .md-header__inner.md-grid { + padding-inline: 32px; /* min-md:px-8 */ + } +} + +@media screen and (min-width: 76.25em) { + .md-main__inner { + padding-left: 0.25rem; + padding-right: 0.25rem; + } + + .md-content { + max-width: none; + } + + .md-sidebar--primary { + width: 14rem; + } + + .md-sidebar--secondary { + width: 13.5rem; + } + + .md-content__inner { + margin: 0 1.25rem; + max-width: none; + } +} + +/* Header control order: logo / language (left) → title → search → support → theme */ +.md-header__inner { + display: flex; + align-items: center; +} + +.md-header__button.md-logo { + order: 1 !important; +} + +.md-header__button[for="__drawer"] { + order: 2 !important; +} + +.md-header__option:has(.md-select) { + order: 3 !important; + display: inline-flex; + align-items: center; + align-self: center; + margin-inline-start: 0.85rem; + height: 100%; +} + +.md-header__title { + order: 10 !important; + flex: 1 1 auto; +} + +.md-header__button[for="__search"] { + order: 50 !important; +} + +.md-search { + order: 51 !important; +} + +.header-support-btn { + order: 80 !important; +} + +.md-header__option[data-md-component="palette"] { + order: 999 !important; +} + +.md-header__source { + order: 1000 !important; +} + +/* Theme toggle: DoubleZero compact button (from doublezero.xyz CompactThemeToggle) */ +.md-header__option[data-md-component="palette"].dz2-palette-host { + display: inline-flex; + align-items: center; + margin: 0.15rem 0.25rem; +} + +.md-header__option[data-md-component="palette"].dz2-palette-host > input.md-option, +.md-header__option[data-md-component="palette"].dz2-palette-host > label.md-header__button { + position: absolute !important; + width: 1px !important; + height: 1px !important; + padding: 0 !important; + margin: -1px !important; + overflow: hidden !important; + clip: rect(0, 0, 0, 0) !important; + white-space: nowrap !important; + border: 0 !important; +} + +.dz2-theme-btn { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + width: 2.1rem; + height: 2.1rem; + margin: 0; + padding: 0; + border: 0; + border-radius: 9999px; + background: color-mix(in srgb, var(--dz-white) 14%, transparent); + color: var(--dz-white); + cursor: pointer; + transition: background-color 0.15s ease, color 0.15s ease; +} + +.dz2-theme-btn:hover, +.dz2-theme-btn:focus-visible { + background: color-mix(in srgb, var(--dz-white) 28%, transparent); + color: var(--dz-white); + outline: none; +} + +[data-md-color-scheme="default"] .dz2-theme-btn { + background: color-mix(in srgb, var(--dz-black) 10%, transparent); + color: var(--dz-black); +} + +[data-md-color-scheme="default"] .dz2-theme-btn:hover, +[data-md-color-scheme="default"] .dz2-theme-btn:focus-visible { + background: var(--dz-black); + color: var(--dz-white); +} + +.dz2-theme-btn svg { + display: block; + width: 1rem; + height: 1rem; +} + +/* Hover tooltip like doublezero.xyz — right-align so it isn't clipped at viewport edge */ +.dz2-theme-btn__tip { + position: absolute; + top: calc(100% + 0.45rem); + right: 0; + left: auto; + transform: none; + z-index: 40; + display: inline-flex; + align-items: center; + gap: 0.4rem; + padding: 0.35rem 0.55rem; + border-radius: 0.35rem; + background: #111213; + color: #f5f6f6; + font-family: var(--dz-font-mono); + font-size: 0.65rem; + font-weight: 500; + letter-spacing: 0.02em; + white-space: nowrap; + pointer-events: none; + opacity: 0; + visibility: hidden; + transition: opacity 0.12s ease, visibility 0.12s ease; +} + +.md-header, +.md-header__inner, +.md-header__option[data-md-component="palette"], +.md-header__option[data-md-component="palette"].dz2-palette-host { + overflow: visible; +} + +.dz2-theme-btn:hover .dz2-theme-btn__tip, +.dz2-theme-btn:focus-visible .dz2-theme-btn__tip { + opacity: 1; + visibility: visible; +} + +/* Drawer / phone: tip has no room — hide it (aria-label still covers a11y) */ +.mobile-drawer-controls .dz2-theme-btn__tip { + display: none !important; +} + +@media screen and (max-width: 40em) { + .dz2-theme-btn__tip { + display: none !important; + } +} + +.dz2-theme-btn__kbd { + display: inline-flex; + align-items: center; + gap: 0.2rem; + color: color-mix(in srgb, #f5f6f6 75%, transparent); +} + +.dz2-theme-btn__kbd kbd { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 1.15rem; + padding: 0.1rem 0.28rem; + border: 1px solid color-mix(in srgb, #f5f6f6 18%, transparent); + border-radius: 0.25rem; + background: color-mix(in srgb, #f5f6f6 10%, transparent); + font: inherit; + line-height: 1.2; +} + +@media (prefers-reduced-motion: reduce) { + .dz2-theme-btn, + .dz2-theme-btn__tip { + transition: none; + } +} + +/* Legacy Material palette icon button styles (kept inert) */ +.md-header__option[data-md-component="palette"] > .md-header__button[hidden] { + display: none !important; +} + +/* Support header button */ +/* Support header button — inverted vs theme; underline grows L→R on hover + (same recipe as doublezero.xyz .header-cta-link / .dz2-footer-link) */ +.header-support-btn { + display: inline-flex !important; + align-items: center; + justify-content: center; + height: 2.1rem; + margin: 0.15rem 0.35rem; + padding: 0 0.85rem !important; + border: 1px solid var(--dz-white); + border-radius: 0; + background: var(--dz-white); + color: var(--dz-black) !important; + opacity: 1 !important; + font-family: var(--dz-font-mono); + font-size: 0.7rem; + font-weight: 500; + letter-spacing: 0.04em; + text-transform: uppercase; + text-decoration: none !important; + line-height: 1; +} + +.header-support-btn, +.header-support-btn:hover, +.header-support-btn:focus, +.header-support-btn:focus-visible { + color: var(--dz-black) !important; + background: var(--dz-white); + border-color: var(--dz-white); + text-decoration: none !important; + outline: none !important; +} + +.header-support-btn__text { + position: relative; + white-space: nowrap; +} + +.header-support-btn__text::after { + content: ""; + position: absolute; + left: 0; + bottom: -2px; + height: 1px; + width: 0; + background: currentColor; + transition: width 200ms cubic-bezier(0.4, 0, 0.2, 1); +} + +.header-support-btn:hover .header-support-btn__text::after, +.header-support-btn:focus .header-support-btn__text::after, +.header-support-btn:focus-visible .header-support-btn__text::after { + width: 100%; +} + +/* Light theme: inverted — dark button */ +[data-md-color-scheme="default"] .header-support-btn, +[data-md-color-scheme="default"] .header-support-btn:hover, +[data-md-color-scheme="default"] .header-support-btn:focus, +[data-md-color-scheme="default"] .header-support-btn:focus-visible { + color: var(--dz-white) !important; + background: var(--dz-black); + border-color: var(--dz-black); + text-decoration: none !important; +} + +@media (prefers-reduced-motion: reduce) { + .header-support-btn__text::after { + transition: none; + } +} + +/* Language selector: discrete text control (name + chevron) */ +.md-header__option .md-select { + position: relative; + display: inline-flex; + align-items: center; + height: 100%; +} + +.md-header__option .md-select > .md-header__language-btn { + display: inline-flex; + align-items: center; + justify-content: center; + align-self: center; + gap: 0.25rem; + width: auto !important; + min-width: 0; + height: auto !important; + min-height: 0; + margin: 0 !important; + padding: 0 !important; + border: 0 !important; + border-radius: 0; + background: transparent !important; + color: color-mix(in srgb, var(--dz-white) 72%, transparent) !important; + opacity: 1 !important; + font-family: var(--dz-font-mono); + font-size: 0.62rem; + font-weight: 500; + letter-spacing: 0.03em; + text-transform: none; + line-height: 1; + cursor: pointer; + box-shadow: none !important; + transition: color 0.15s ease; +} + +.md-header__option .md-select > .md-header__language-btn:hover, +.md-header__option .md-select > .md-header__language-btn:focus { + color: var(--dz-white) !important; + background: transparent !important; + outline: none !important; + box-shadow: none !important; +} + +[data-md-color-scheme="default"] .md-header__option .md-select > .md-header__language-btn { + color: color-mix(in srgb, var(--dz-black) 62%, transparent) !important; +} + +[data-md-color-scheme="default"] .md-header__option .md-select > .md-header__language-btn:hover, +[data-md-color-scheme="default"] .md-header__option .md-select > .md-header__language-btn:focus { + color: var(--dz-black) !important; + background: transparent !important; +} + +.md-header__language-label { + white-space: nowrap; +} + +.md-header__language-chevron { + font-size: 0.5rem; + line-height: 1; + opacity: 0.75; +} + +.md-header__option .md-select > .md-header__button svg { + display: none; +} + +.md-select__inner { + min-width: 0; + width: max-content; + max-width: 10rem; + top: calc(100% + 0.15rem); + margin-top: 0; + padding: 0.2rem; + border: 1px solid color-mix(in srgb, var(--md-default-fg-color) 16%, transparent); + border-radius: 0.45rem; + background-color: var(--md-default-bg-color); + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35); + overflow-y: auto; + /* Closed dropdown must not capture hover in the space below the icon */ + pointer-events: none; +} + +/* Invisible bridge so you can move from icon → menu without a dead gap */ +.md-select__inner::before { + content: ""; + position: absolute; + left: 0; + right: 0; + top: -0.45rem; + height: 0.45rem; +} + +.md-select:focus-within .md-select__inner, +.md-select:hover .md-select__inner { + max-height: min(20rem, 70vh); + opacity: 1; + transform: translate3d(-50%, 0, 0); + pointer-events: auto; +} + +.md-select__list { + margin: 0; + padding: 0; +} + +.md-select__item { + margin: 0; +} + +.md-select__link { + display: block; + margin: 0; + padding: 0.28rem 0.55rem; + border-radius: 0.28rem; + color: var(--md-default-fg-color); + font-size: 0.72rem; + line-height: 1.25; + white-space: nowrap; + text-decoration: none; + transition: background-color 0.12s ease, color 0.12s ease; +} + +.md-select__link:hover, +.md-select__link:focus { + background-color: color-mix(in srgb, var(--md-accent-fg-color) 14%, transparent); + color: var(--md-accent-fg-color); +} + +.md-select__link.is-active { + color: var(--md-accent-fg-color); + font-weight: 600; + background-color: color-mix(in srgb, var(--md-accent-fg-color) 10%, transparent); + padding-inline-end: 1.35rem; +} + +.md-select__link.is-active::after { + content: "✓"; + float: right; + margin-inline-start: 0.45rem; + opacity: 0.9; +} + +/* 50px buffer at bottom of content when fully scrolled, before footer */ +.md-content__inner { + padding-bottom: 50px; +} + +/* Page toolbar styles - text-based links like Pyth docs */ +.page-toolbar-footer { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: var(--md-default-bg-color); + border-top: 1px solid var(--md-default-fg-color--lightest); + z-index: 100; + padding: 0; +} + +.page-toolbar { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.75rem 1rem; + font-size: 0.875rem; + color: var(--md-default-fg-color); + max-width: 100%; + margin: 0 auto; +} + +.page-toolbar-link { + display: inline-flex; + align-items: center; + gap: 0.375rem; + color: var(--md-default-fg-color); + -webkit-text-fill-color: var(--md-default-fg-color); + text-decoration: none; + cursor: pointer; +} + +.page-toolbar-link__text { + display: inline-block; +} + +/* Fiber spectrum sweep — same geometry/timing as doublezero.xyz nav links */ +@keyframes dz-fiber-sweep { + from { + background-position-x: 100%; + } + to { + background-position-x: 0%; + } +} + +.page-toolbar-link:hover .page-toolbar-link__text, +.page-toolbar-link:focus-visible .page-toolbar-link__text { + background-image: linear-gradient( + 90deg, + var(--md-default-fg-color) 0%, + var(--md-default-fg-color) 26%, + var(--dz-teal) 29%, + var(--dz-blue) 43%, + var(--dz-yellow) 57%, + var(--dz-red) 71%, + var(--md-default-fg-color) 74%, + var(--md-default-fg-color) 100% + ); + background-size: 400% 100%; + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + color: transparent; + animation: dz-fiber-sweep 700ms ease-out forwards; +} + +@media (prefers-reduced-motion: reduce) { + .page-toolbar-link:hover .page-toolbar-link__text, + .page-toolbar-link:focus-visible .page-toolbar-link__text { + background: none; + background-clip: border-box; + -webkit-background-clip: border-box; + -webkit-text-fill-color: var(--md-default-fg-color); + color: var(--md-default-fg-color); + animation: none; + } +} + +.page-toolbar-link:hover, +.page-toolbar-link:focus-visible { + text-decoration: none; + opacity: 1; +} + +.page-toolbar-link.copied, +.page-toolbar-link.copied .page-toolbar-link__text { + color: var(--dz-green); + -webkit-text-fill-color: var(--dz-green); + font-weight: 500; + background: none; + animation: none; +} + +.page-toolbar-icon { + width: 16px; + height: 16px; + flex-shrink: 0; + vertical-align: middle; + opacity: 0.9; + color: currentColor; +} + +.page-toolbar-link svg.page-toolbar-icon { + display: block; + overflow: visible; +} + +[data-md-color-scheme="slate"] .page-toolbar-favicon { + filter: brightness(0) invert(1); +} + +.page-toolbar-separator { + color: var(--md-default-fg-color--light); + user-select: none; +} + +@media screen and (max-width: 768px) { + .page-toolbar { + flex-wrap: wrap; + } +} - /* Current language name next to the language selector icon */ - button[aria-label="Select language"] { - display: inline-flex; +/* Runbook assist — copy left, actions right on one row */ +.runbook-assist--compact { + display: flex; + flex-wrap: nowrap; + align-items: center; + gap: 0.65rem 1rem; + margin: 0.25rem 0 1.15rem; + padding: 0.7rem 0; + border: none; + border-bottom: 1px solid var(--md-default-fg-color--lightest); + border-radius: 0; + background: transparent; +} + +.runbook-assist--compact .runbook-assist-mark { + flex: 0 0 auto; + width: 0.45rem; + height: 0.45rem; + background: var(--dz-green); + align-self: center; +} + +.runbook-assist--compact .runbook-assist-icon { + display: none; +} + +.runbook-assist--compact .runbook-assist-copy { + flex: 1 1 auto; + margin: 0 !important; + padding: 0 !important; + font-size: 0.8rem; + line-height: 1.45; + color: var(--md-default-fg-color--light); + font-family: var(--dz-font-body); + min-width: 0; +} + +.runbook-assist--compact .runbook-assist-copy strong { + color: var(--md-default-fg-color); + font-weight: 600; +} + +.runbook-assist--compact .runbook-assist-actions { + display: flex; + flex-wrap: nowrap; + align-items: center; + gap: 0.65rem 0.85rem; + margin: 0 0 0 auto !important; + padding: 0 !important; + flex: 0 0 auto; +} + +.runbook-assist-btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.5rem 0.85rem; + border-radius: 0; + border: 1px solid var(--md-default-fg-color); + background: var(--md-default-fg-color); + color: var(--md-default-bg-color) !important; + font-family: var(--dz-font-mono); + font-size: 0.7rem; + font-weight: 500; + letter-spacing: 0.02em; + text-decoration: none !important; + cursor: pointer; + line-height: 1.2; + white-space: nowrap; +} + +.runbook-assist-btn:hover { + background: transparent; + color: var(--md-default-fg-color) !important; +} + +.runbook-assist-btn.copied { + background: var(--dz-green); + border-color: var(--dz-green); + color: #fff !important; +} + +/* Open runbook: always looks like a link; underline grows on hover */ +a.runbook-assist-btn.dz-cta-rainbow { + padding: 0; + border: none; + background-color: transparent; + background-image: linear-gradient(currentColor, currentColor); + background-position: left 100%; + background-repeat: no-repeat; + background-size: 0 1px; + color: var(--dz-green) !important; + -webkit-text-fill-color: var(--dz-green); + text-decoration: none !important; +} + +a.runbook-assist-btn.dz-cta-rainbow:hover, +a.runbook-assist-btn.dz-cta-rainbow:focus-visible { + background-size: 100% 1px; + color: var(--dz-green) !important; + -webkit-text-fill-color: var(--dz-green); + opacity: 1; +} + +a.runbook-assist-btn.dz-cta-rainbow::after { + content: "→"; + margin-left: 0.35rem; + color: var(--dz-green); + -webkit-text-fill-color: var(--dz-green); + text-decoration: none; +} + +a.runbook-assist-btn.dz-cta-rainbow:hover::after, +a.runbook-assist-btn.dz-cta-rainbow:focus-visible::after { + color: var(--dz-green); + -webkit-text-fill-color: var(--dz-green); +} + +.runbook-assist-btn-secondary { + background: transparent; + color: var(--md-default-fg-color) !important; +} + +.runbook-assist-btn-secondary:hover { + background: var(--md-default-fg-color); + color: var(--md-default-bg-color) !important; +} + +button.runbook-assist-btn { + font-family: var(--dz-font-mono); +} + +/* Narrow: stack copy, keep actions on one row aligned end */ +@media screen and (max-width: 40em) { + .runbook-assist--compact { + flex-wrap: wrap; + } + + .runbook-assist--compact .runbook-assist-copy { + flex: 1 1 calc(100% - 1.5rem); + } + + .runbook-assist--compact .runbook-assist-actions { + flex: 1 1 100%; + justify-content: flex-end; + margin-left: 0 !important; + padding-left: 1.1rem !important; + } +} + +/* Legacy full admonition assist (if any remain) */ +.runbook-assist.admonition { + margin: 0.75rem 0 1.25rem; + padding: 0.85rem 1rem 1rem !important; +} + +.runbook-assist.admonition .admonition-title { + margin-top: 0; +} + +.runbook-assist.admonition > p { + margin-left: 0; + margin-right: 0; +} + +/* Header: DoubleZero wordmark only (swap dark/light by scheme) + Flush to header padding like doublezero.xyz — no Material button chrome. */ +.md-header__button.md-logo { + display: inline-flex; + align-items: center; + align-self: center; + margin: 0 !important; + margin-inline-end: 0.75rem !important; + padding: 0 !important; + height: auto; + line-height: 0; +} + +.md-header__button.md-logo img, +.md-nav__button.md-logo img { + height: 24px; /* doublezero.xyz desktop: h-6 */ + width: auto; + display: block; +} + +.header-logo-wordmark--light { + display: none !important; +} + +[data-md-color-scheme="default"] .header-logo-wordmark--dark { + display: none !important; +} + +[data-md-color-scheme="default"] .header-logo-wordmark--light { + display: inline-block !important; +} + +/* Keep title as flex spacer so controls stay right; hide redundant brand text */ +.md-header__title { + flex: 1 1 auto; + visibility: hidden; + pointer-events: none; +} + +/* Right TOC: "On this page" title → scroll to top */ +.md-sidebar--secondary .md-nav__title { + display: flex; + align-items: center; + gap: 0.4rem; + padding: 0 0.6rem 0.75rem; + font-size: 0.75rem; + font-weight: 700; + text-transform: none; + color: var(--md-default-fg-color); + box-shadow: none; + background: transparent; + opacity: 1; +} + +.md-sidebar--secondary .md-nav__title .md-nav__icon { + margin: 0; + width: 0.9rem; + height: 0.9rem; + color: var(--md-default-fg-color); + fill: currentColor; + opacity: 1; +} + +.md-sidebar--secondary .toc-page-top { + color: var(--md-default-fg-color); + font-weight: 700; + text-decoration: none; +} + +/* Scrollspy TOC — active section tracks as you scroll (Solana / Helius style) */ +.md-sidebar--secondary .md-nav--secondary > .md-nav__list { + position: relative; + margin: 0; + padding: 0; + border-left: 1px solid var(--md-default-fg-color--lightest); +} + +.md-sidebar--secondary .md-nav--secondary .md-nav__item { + padding: 0; +} + +.md-sidebar--secondary .md-nav--secondary .md-nav__link { + margin-top: 0; + padding: 0.4rem 0.75rem; + color: var(--md-default-fg-color--light); + font-size: 0.7rem; + font-weight: 400; + line-height: 1.35; + border-left: 2px solid transparent; + margin-left: -1px; + border-radius: 0; + transition: color 0.15s ease, border-color 0.15s ease, text-shadow 0.15s ease; +} + +.md-sidebar--secondary .md-nav--secondary .md-nav__link:hover, +.md-sidebar--secondary .md-nav--secondary .md-nav__link:focus { + background-color: transparent; + font-weight: 400; +} + +/* Active: color + rail — no layout shift */ +.md-sidebar--secondary .md-nav--secondary .md-nav__link--active, +.md-sidebar--secondary .md-nav--secondary .md-nav__link--active:focus, +.md-sidebar--secondary .md-nav--secondary .md-nav__item--active > .md-nav__link { + color: var(--md-default-fg-color); + -webkit-text-fill-color: var(--md-default-fg-color); + font-weight: 400 !important; + border-left-color: var(--md-default-fg-color); + background-color: transparent; + text-shadow: 0.4px 0 0 currentColor, -0.4px 0 0 currentColor; +} + +.md-sidebar--secondary .md-nav--secondary .md-nav__link--active:hover, +.md-sidebar--secondary .md-nav--secondary .md-nav__link--active:focus-visible { + text-shadow: none !important; +} + +/* Primary nav: stable metrics; underline via shared hover rule above */ +.md-sidebar--primary .md-nav__link { + font-weight: 400 !important; + transition: color 0.15s ease; +} + +.md-sidebar--primary .md-nav__link--active, +.md-sidebar--primary .md-nav__item--active > .md-nav__link { + font-weight: 400 !important; + text-shadow: 0.35px 0 0 currentColor, -0.35px 0 0 currentColor; + color: var(--md-default-fg-color); +} + +.md-sidebar--primary .md-nav__link--active:hover, +.md-sidebar--primary .md-nav__link--active:focus-visible, +.md-sidebar--primary .md-nav__item--active > .md-nav__link:hover, +.md-sidebar--primary .md-nav__item--active > .md-nav__link:focus-visible { + text-shadow: none !important; +} + +/* Nested TOC levels stay indented under the scrollspy rail */ +.md-sidebar--secondary .md-nav--secondary .md-nav .md-nav__list { + border-left: none; + margin: 0; + padding: 0; +} + +.md-sidebar--secondary .md-nav--secondary .md-nav .md-nav__link { + padding-left: 1.25rem; +} + +/* Extra air between a nested subtitle and the next top-level section (e.g. 3→4) */ +.md-sidebar--secondary .md-nav--secondary > .md-nav__list > .md-nav__item > .md-nav { + margin-bottom: 0.2rem; +} + +.md-sidebar--secondary .md-nav--secondary > .md-nav__list > .md-nav__item:has(> .md-nav) + .md-nav__item > .md-nav__link { + padding-top: 0.45rem; +} + +/* Keep scrollspy / in-page anchors aligned under the sticky header */ +.md-typeset :is(h1, h2, h3, h4, h5, h6) { + scroll-margin-top: 3.5rem; +} + +/* Last-updated line at the bottom of each page */ +.md-source-file .page-updated { + color: var(--md-default-fg-color--light); + font-size: 0.75rem; +} + +/* -------------------------------------------------------------------------- + Search: circular header button → centered modal (Helius / QuickNode style) + -------------------------------------------------------------------------- */ + +/* Always show a round magnifying-glass control in the header */ +.md-header__button[for="__search"] { + display: inline-flex !important; + align-items: center; + justify-content: center; + width: 2.1rem; + height: 2.1rem; + margin: 0.15rem 0.25rem; + padding: 0; + border: none; + border-radius: 50%; + box-sizing: border-box; + background-color: color-mix(in srgb, currentColor 18%, transparent); + outline: none !important; + box-shadow: none !important; + transition: background-color 0.15s ease; +} + +.md-header__button[for="__search"]:hover, +.md-header__button[for="__search"]:focus { + border: none; + background-color: color-mix(in srgb, currentColor 48%, transparent); + outline: none !important; + box-shadow: none !important; +} + +.md-header__button[for="__search"] svg { + width: 1.05rem; + height: 1.05rem; +} + +/* Desktop+: don't embed the long search field in the header — use a modal */ +@media screen and (min-width: 60em) { + .md-search { + position: static; + padding: 0; + } + + .md-search__overlay { + border-radius: 0 !important; + background-color: rgba(0, 0, 0, 0.55) !important; + } + + .md-search__inner { + position: fixed !important; + top: 4.75rem !important; + left: 50% !important; + float: none !important; + z-index: 5; + width: min(42rem, calc(100vw - 2.5rem)) !important; + height: auto !important; + max-height: none !important; + padding: 0 !important; + margin: 0 !important; + /* Must be visible: results are position:absolute and were being clipped */ + overflow: visible !important; + opacity: 0 !important; + visibility: hidden; + pointer-events: none; + transform: translateX(-50%) translateY(-0.25rem) !important; + border-radius: 0.75rem; + background-color: transparent; + box-shadow: none; + transition: + opacity 0.18s ease, + transform 0.18s ease, + visibility 0.18s !important; + } + + [data-md-toggle="search"]:checked ~ .md-header .md-search__inner { + opacity: 1 !important; + visibility: visible; + pointer-events: auto; + transform: translateX(-50%) translateY(0) !important; + width: min(42rem, calc(100vw - 2.5rem)) !important; + } + + .md-search__form { + display: flex !important; + align-items: center !important; + height: 3rem !important; + border-radius: 0.75rem !important; + background-color: var(--md-default-bg-color) !important; + box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45) !important; + } + + /* Open search: one cohesive card (form top + output bottom, both rounded) */ + [data-md-toggle="search"]:checked ~ .md-header .md-search__form { + border-radius: 0.75rem 0.75rem 0 0 !important; + box-shadow: none !important; + } + + .md-search__form:hover { + background-color: var(--md-default-bg-color) !important; + } + + .md-search__input { + font-size: 0.95rem !important; + line-height: 3rem !important; + color: var(--md-default-fg-color) !important; + /* Room for the back/close icon on the left + comfortable gap before typed text */ + padding-inline-start: 3rem !important; + } + + .md-search__input::placeholder { + color: var(--md-default-fg-color--light) !important; + line-height: 3rem; + } + + .md-search__icon[for="__search"] { + top: 50% !important; + inset-inline-start: 0.75rem !important; + display: inline-flex !important; align-items: center; - gap: 0.35rem; + justify-content: center; + width: 1.4rem !important; + height: 1.4rem !important; + margin: 0; + transform: translateY(-50%); + color: var(--md-default-fg-color--light) !important; + pointer-events: auto; } - .md-header__language-label { - font-size: 0.8rem; - font-weight: 500; - white-space: nowrap; + + .md-search__icon[for="__search"] svg { + width: 1.2rem; + height: 1.2rem; + display: block; } - /* 50px buffer at bottom of content when fully scrolled, before footer */ - .md-content__inner { - padding-bottom: 50px; + .md-search__options { + top: 50% !important; + transform: translateY(-50%); + display: flex; + align-items: center; + } + + /* Prefer close/back affordance inside the modal */ + .md-search__icon[for="__search"] svg:first-child { + display: none; + } + + .md-search__icon[for="__search"] svg:last-child { + display: block; } - /* Page toolbar styles - text-based links like Pyth docs */ - .page-toolbar-footer { - position: fixed; - bottom: 0; + .md-search__output { + position: absolute !important; + top: 3rem !important; left: 0; right: 0; - background: var(--md-default-bg-color, #1e1e1e); - border-top: 1px solid rgba(255, 255, 255, 0.1); - z-index: 100; + width: 100% !important; + border-radius: 0 0 0.75rem 0.75rem !important; + opacity: 0; + overflow: hidden !important; + background-color: var(--md-default-bg-color); + box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45); + } + + [data-md-toggle="search"]:checked ~ .md-header .md-search__output { + opacity: 1 !important; + } + + .md-search__scrollwrap { + width: 100% !important; + max-width: none !important; + max-height: 0; + border-radius: 0 0 0.75rem 0.75rem; + overflow: hidden; + } + + [data-md-toggle="search"]:checked ~ .md-header .md-search__scrollwrap { + max-height: min(55vh, calc(100vh - 8rem)) !important; + min-height: 2.75rem; + overflow-x: hidden !important; + overflow-y: auto !important; + overscroll-behavior: contain; + -webkit-overflow-scrolling: touch; + } + + /* Leave room for the result icon (Material places it absolutely on the left) */ + .md-search-result__meta { + padding-inline-start: 1rem !important; + } + + .md-search-result__article, + .md-search-result__more > summary > div, + .md-search-result__link { + padding-inline-start: 2.5rem !important; + } + + .md-search-result__icon { + display: block; + inset-inline-start: 0.55rem; + } + + /* Keep shared card radius when results are showing */ + [data-md-toggle="search"]:checked ~ .md-header .md-search__form:has(+ .md-search__output .md-search-result__item) { + border-radius: 0.75rem 0.75rem 0 0 !important; + box-shadow: none !important; + } + + [data-md-toggle="search"]:checked ~ .md-header .md-search__form:has(+ .md-search__output .md-search-result__item) + .md-search__output { + box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45); + } +} + +/* Lock page scroll while search is open (desktop + mobile) */ +html:has([data-md-toggle="search"]:checked), +html:has([data-md-toggle="search"]:checked) body { + overflow: hidden !important; +} + +/* Smaller screens: keep Material’s modal, but float a centered card */ +@media screen and (max-width: 59.9375em) { + [data-md-toggle="search"]:checked ~ .md-header .md-search__inner { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + padding: 5.5rem 1rem 1rem; + background: transparent; + } + + [data-md-toggle="search"]:checked ~ .md-header .md-search__form, + [data-md-toggle="search"]:checked ~ .md-header .md-search__output { + width: min(40rem, 100%); + border-radius: 0.75rem; + overflow: hidden; + box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45); + } + + [data-md-toggle="search"]:checked ~ .md-header .md-search__form { + border-radius: 0.75rem 0.75rem 0 0; + } + + [data-md-toggle="search"]:checked ~ .md-header .md-search__output { + position: relative; + top: 0; + bottom: auto; + max-height: min(55vh, calc(100vh - 8rem)); + border-radius: 0 0 0.75rem 0.75rem; + overflow-y: auto !important; + overscroll-behavior: contain; + } + + [data-md-toggle="search"]:checked ~ .md-header .md-search__scrollwrap { + max-height: none !important; + overflow: visible !important; + } +} + +/* -------------------------------------------------------------------------- + Header / drawer: keep controls visible until they would crush (~40em). + Material still shows the nav hamburger below ~76em — just don't steal + search/lang/support/theme until phone widths. + -------------------------------------------------------------------------- */ +.mobile-drawer-controls { + display: none; +} + +/* Tablet: nav drawer toggle appears beside logo — keep logo from covering it */ +@media screen and (max-width: 76.1875em) { + .md-header__button.md-logo { + flex: 0 1 auto; + max-width: calc(100% - 3.25rem); + min-width: 0; + overflow: hidden; + } + + .md-header__button.md-logo img { + max-height: 24px; + } + + .md-header__button[for="__drawer"] { + flex-shrink: 0; + position: relative; + z-index: 2; + } +} + +/* Narrow phones: match doublezero.xyz mobile wordmark (h-5 = 20px) */ +@media screen and (max-width: 40em) { + .md-header__button.md-logo { + max-width: calc(100% - 3rem); + } + + .md-header__button.md-logo .header-logo-wordmark, + .md-header__button.md-logo img { + height: 20px; + max-width: min(8.75rem, calc(100vw - 6.25rem)); + object-fit: contain; + object-position: left center; + } +} + +@media screen and (max-width: 22.5em) { + .md-header__button.md-logo .header-logo-wordmark, + .md-header__button.md-logo img { + height: 18px; + max-width: calc(100vw - 5.75rem); + } +} + +/* Phone: move chrome controls into the sandwich drawer */ +@media screen and (max-width: 40em) { + .md-header__inner > .md-header__option, + .md-header__inner > .header-support-btn, + .md-header__inner > .md-header__button[for="__search"] { + display: none !important; + } + + /* Search UI stays in DOM for the modal, but takes no header space */ + .md-header__inner > .md-search { + position: absolute; + width: 0; + height: 0; + margin: 0; padding: 0; + overflow: visible; + pointer-events: none; } - .page-toolbar { + .md-header__inner > .md-search .md-search__inner { + pointer-events: auto; + } + + /* Logo left, hamburger right */ + .md-header__button.md-logo { + order: 1; + flex: 1 1 auto; + max-width: calc(100% - 3rem); + min-width: 0; + overflow: hidden; + } + + .md-header__title { + display: none; + } + + .md-header__button[for="__drawer"] { + order: 2; + flex: 0 0 auto; + width: 2.4rem; + margin-inline-start: 0.35rem; + } + + /* Drawer control panel — Search / Support + compact language/theme row */ + .mobile-drawer-controls { display: flex; + flex-direction: column; + gap: 0.45rem; + margin: 0.35rem 0.8rem 0.75rem; + padding: 0 0 0.85rem; + border-bottom: 1px solid color-mix(in srgb, var(--md-default-fg-color) 12%, transparent); + border-radius: 0; + background: transparent; + } + + .mobile-drawer-controls__btn { + display: inline-flex; align-items: center; - justify-content: center; - gap: 0.5rem; - padding: 0.75rem 1rem; - font-size: 0.875rem; - color: #ffffff; - max-width: 100%; - margin: 0 auto; + gap: 0.55rem; + width: 100%; + box-sizing: border-box; + margin: 0; + padding: 0.55rem 0.7rem; + border: 1px solid color-mix(in srgb, var(--md-default-fg-color) 14%, transparent); + border-radius: 0; + background: transparent; + color: var(--md-default-fg-color); + font-family: var(--dz-font-mono); + font-size: 0.75rem; + font-weight: 500; + letter-spacing: 0.02em; + text-decoration: none !important; + cursor: pointer; + line-height: 1.2; + } + + .mobile-drawer-controls__btn svg { + width: 1.1rem; + height: 1.1rem; + flex-shrink: 0; } - .page-toolbar-link { + .mobile-drawer-controls__btn:hover, + .mobile-drawer-controls__btn:focus { + border-color: var(--md-default-fg-color); + color: var(--md-default-fg-color); + } + + .mobile-drawer-controls__meta { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 0.75rem; + padding: 0.15rem 0.15rem 0; + } + + .mobile-drawer-lang { + position: relative; + flex: 1 1 auto; + min-width: 0; + } + + .mobile-drawer-lang__summary { display: inline-flex; align-items: center; - gap: 0.375rem; - color: #ffffff; - text-decoration: none; + gap: 0.25rem; + list-style: none; cursor: pointer; - transition: opacity 0.2s ease; + color: color-mix(in srgb, var(--md-default-fg-color) 62%, transparent); + font-family: var(--dz-font-mono); + font-size: 0.68rem; + font-weight: 500; + letter-spacing: 0.02em; + line-height: 1; + user-select: none; + } + + .mobile-drawer-lang__summary::-webkit-details-marker { + display: none; + } + + .mobile-drawer-lang__summary::marker { + content: ""; + } + + .mobile-drawer-lang[open] .mobile-drawer-lang__summary, + .mobile-drawer-lang__summary:hover { + color: var(--md-default-fg-color); } - .page-toolbar-link:hover { - text-decoration: underline; + .mobile-drawer-lang__chevron { + font-size: 0.6rem; opacity: 0.8; } - .page-toolbar-link.copied { - color: #ffffff; - font-weight: 500; + /* In-flow list so it expands and pushes nav down — never overlays menu items */ + .mobile-drawer-lang__list { + list-style: none; + position: static; + margin: 0.45rem 0 0; + padding: 0.25rem 0; + width: 100%; + max-width: 12rem; + border: 1px solid color-mix(in srgb, var(--md-default-fg-color) 14%, transparent); + background: var(--md-default-bg-color); + box-shadow: none; } - .page-toolbar-icon { - width: 16px; - height: 16px; - flex-shrink: 0; - vertical-align: middle; - opacity: 0.9; + .mobile-drawer-lang__list a { + display: block; + padding: 0.4rem 0.65rem; + color: var(--md-default-fg-color); + font-family: var(--dz-font-mono); + font-size: 0.7rem; + text-decoration: none; + line-height: 1.2; } - .page-toolbar-favicon { - filter: brightness(0) invert(1); + .mobile-drawer-lang__list a:hover, + .mobile-drawer-lang__list a.is-active, + .mobile-drawer-lang__list a[aria-current="page"] { + background: color-mix(in srgb, var(--md-default-fg-color) 8%, transparent); } - .page-toolbar-separator { - color: rgba(255, 255, 255, 0.5); - user-select: none; + /* Keep palette form in the header DOM for instant nav; hide it visually on + mobile and expose #drawer-theme-btn as the click proxy instead. */ + .md-header__inner > [data-md-component="palette"] { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + } + + .mobile-drawer-theme-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + margin: 0; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + color: var(--md-default-fg-color); + cursor: pointer; + flex-shrink: 0; } - /* Responsive design for smaller screens */ - @media screen and (max-width: 768px) { - .page-toolbar { - flex-wrap: wrap; - } + .mobile-drawer-theme-btn svg { + width: 1rem; + height: 1rem; + display: block; } -/* Header shows two logos (Malbec + DoubleZero); lay them out side by side. - The logo partial is also reused by the mobile nav drawer — keep the DZ icon - out of there. */ -.md-header__button.md-logo { + .mobile-drawer-theme-btn:hover, + .mobile-drawer-theme-btn:focus-visible { + opacity: 0.85; + } +} + +/* TOC title: close control (label) + scroll-to-top link as siblings */ +.md-nav--secondary > .md-nav__title { display: flex; align-items: center; - gap: 0.4rem; + gap: 0.35rem; } -.md-nav__button.md-logo img[alt="DoubleZero"] { - display: none; +.md-nav--secondary > .md-nav__title > .toc-page-top { + color: inherit; + text-decoration: none; +} + +.md-nav--secondary > .md-nav__title > .toc-page-top:hover, +.md-nav--secondary > .md-nav__title > .toc-page-top:focus-visible { + text-decoration: underline; +} + +.page-toolbar-link:hover .page-toolbar-icon, +.page-toolbar-link:focus-visible .page-toolbar-icon { + opacity: 1; + filter: none; } diff --git a/docs/support.es.md b/docs/support.es.md new file mode 100644 index 0000000..1acf72b --- /dev/null +++ b/docs/support.es.md @@ -0,0 +1,26 @@ +--- +description: Obtén ayuda con DoubleZero — consulta el estado en vivo de los enlaces y abre un ticket de soporte en Discord. +--- + +# Soporte + +¿Necesitas ayuda para conectarte o para operar en DoubleZero? Comienza con el estado en vivo y luego contacta al equipo si aún necesitas asistencia. + +## Estado de los enlaces de red + +Consulta el estado actual de los enlaces y la conectividad en el portal de datos de DoubleZero: + +**[Panel de estado de enlaces](https://data.doublezero.xyz/status/links)** + +Usa esta página para confirmar si los problemas que estás experimentando son a nivel de red o locales de tu configuración antes de realizar cambios en la configuración. + +## Contacta al equipo + +Si el estado se ve saludable (o no explica tu problema), abre un ticket con el equipo de DoubleZero en Discord: + +1. Únete a **[discord.gg/doublezerotech](https://discord.gg/doublezerotech)** +2. Abre un **ticket de soporte** y describe el problema +3. Incluye lo que puedas: entorno (mainnet / testnet), tipo de nodo, **IP del cliente**, comandos recientes y cualquier salida de error + +!!! tip "Antes de abrir un ticket" + La guía de [Solución de problemas](troubleshooting.md) cubre los fallos de conexión más comunes. Revisarla primero a menudo te permite resolver el problema más rápido. \ No newline at end of file diff --git a/docs/support.fr.md b/docs/support.fr.md new file mode 100644 index 0000000..a6c276d --- /dev/null +++ b/docs/support.fr.md @@ -0,0 +1,26 @@ +--- +description: Obtenez de l'aide avec DoubleZero — vérifiez l'état des liens en direct et ouvrez un ticket de support sur Discord. +--- + +# Support + +Besoin d'aide pour vous connecter ou opérer sur DoubleZero ? Commencez par vérifier l'état en direct, puis contactez l'équipe si vous avez encore besoin d'assistance. + +## État des liens réseau + +Vérifiez la santé actuelle des liens et la connectivité sur le portail de données DoubleZero : + +**[Tableau de bord de l'état des liens](https://data.doublezero.xyz/status/links)** + +Utilisez cette page pour confirmer si les problèmes que vous rencontrez sont à l'échelle du réseau ou locaux à votre configuration avant de modifier la configuration. + +## Contacter l'équipe + +Si l'état semble normal (ou n'explique pas votre problème), ouvrez un ticket auprès de l'équipe DoubleZero sur Discord : + +1. Rejoignez **[discord.gg/doublezerotech](https://discord.gg/doublezerotech)** +2. Ouvrez un **ticket de support** et décrivez le problème +3. Incluez ce que vous pouvez : environnement (mainnet / testnet), type de nœud, **IP du client**, commandes récentes et toute sortie d'erreur + +!!! tip "Avant d'ouvrir un ticket" + Le guide de [Dépannage](troubleshooting.md) couvre les échecs de connexion les plus courants. Le consulter en premier vous permet souvent de résoudre votre problème plus rapidement. \ No newline at end of file diff --git a/docs/support.it.md b/docs/support.it.md new file mode 100644 index 0000000..a92b7f1 --- /dev/null +++ b/docs/support.it.md @@ -0,0 +1,26 @@ +--- +description: Ottieni assistenza con DoubleZero — controlla lo stato dei link in tempo reale e apri un ticket di supporto su Discord. +--- + +# Supporto + +Hai bisogno di aiuto per connetterti o operare su DoubleZero? Inizia controllando lo stato in tempo reale, poi contatta il team se hai ancora bisogno di assistenza. + +## Stato dei link di rete + +Controlla lo stato attuale dei link e la connettività sul portale dati di DoubleZero: + +**[Dashboard dello stato dei link](https://data.doublezero.xyz/status/links)** + +Usa questa pagina per verificare se i problemi che stai riscontrando sono diffusi a livello di rete o limitati alla tua configurazione, prima di modificare le impostazioni. + +## Contatta il team + +Se lo stato risulta regolare (o non spiega il tuo problema), apri un ticket con il team di DoubleZero su Discord: + +1. Unisciti a **[discord.gg/doublezerotech](https://discord.gg/doublezerotech)** +2. Apri un **ticket di supporto** e descrivi il problema +3. Includi tutto ciò che puoi: ambiente (mainnet / testnet), tipo di nodo, **client IP**, comandi recenti e qualsiasi output di errore + +!!! tip "Prima di aprire un ticket" + La guida [Risoluzione dei problemi](troubleshooting.md) copre i problemi di connessione più comuni. Consultarla per prima cosa spesso ti permette di risolvere più velocemente. \ No newline at end of file diff --git a/docs/support.ja.md b/docs/support.ja.md new file mode 100644 index 0000000..39542e8 --- /dev/null +++ b/docs/support.ja.md @@ -0,0 +1,26 @@ +--- +description: DoubleZero のサポート — ライブリンクステータスの確認と Discord サポートチケットの作成。 +--- + +# サポート + +DoubleZero への接続や運用でお困りですか?まずはライブステータスを確認し、それでも解決しない場合はチームにご連絡ください。 + +## ネットワークリンクステータス + +DoubleZero データポータルで現在のリンクの健全性と接続状況を確認できます: + +**[リンクステータスダッシュボード](https://data.doublezero.xyz/status/links)** + +設定を変更する前に、このページを使用して、発生している問題がネットワーク全体のものか、お使いの環境固有のものかを確認してください。 + +## チームへの連絡 + +ステータスが正常な場合(または問題の原因が説明されていない場合)は、Discord で DoubleZero チームにチケットを作成してください: + +1. **[discord.gg/doublezerotech](https://discord.gg/doublezerotech)** に参加する +2. **サポートチケット**を作成し、問題の内容を記述する +3. 可能な限り以下の情報を含めてください:環境(mainnet / testnet)、ノードタイプ、**クライアント IP**、最近実行したコマンド、エラー出力 + +!!! tip "チケットを作成する前に" + [トラブルシューティング](troubleshooting.md)ガイドでは、最も一般的な接続障害について説明しています。先にそちらを確認することで、より早く問題を解決できることがよくあります。 \ No newline at end of file diff --git a/docs/support.ko.md b/docs/support.ko.md new file mode 100644 index 0000000..4a0c014 --- /dev/null +++ b/docs/support.ko.md @@ -0,0 +1,26 @@ +--- +description: DoubleZero에 대한 도움을 받으세요 — 실시간 링크 상태를 확인하고 Discord 지원 티켓을 열 수 있습니다. +--- + +# 지원 + +DoubleZero에 연결하거나 운영하는 데 도움이 필요하신가요? 먼저 실시간 상태를 확인한 후, 여전히 도움이 필요하면 팀에 문의하세요. + +## 네트워크 링크 상태 + +DoubleZero 데이터 포털에서 현재 링크 상태 및 연결성을 확인하세요: + +**[링크 상태 대시보드](https://data.doublezero.xyz/status/links)** + +구성을 변경하기 전에 이 페이지를 사용하여 현재 겪고 있는 문제가 네트워크 전체의 문제인지 또는 로컬 설정에 국한된 문제인지 확인하세요. + +## 팀에 문의하기 + +상태가 정상으로 보이거나 문제를 설명하지 못하는 경우, Discord에서 DoubleZero 팀에 티켓을 열어주세요: + +1. **[discord.gg/doublezerotech](https://discord.gg/doublezerotech)**에 참여하세요 +2. **지원 티켓**을 열고 문제를 설명하세요 +3. 가능한 정보를 포함하세요: 환경 (mainnet / testnet), 노드 유형, **클라이언트 IP**, 최근 명령어, 그리고 모든 오류 출력 + +!!! tip "티켓을 열기 전에" + [문제 해결](troubleshooting.md) 가이드에서 가장 일반적인 연결 실패 사례를 다루고 있습니다. 먼저 확인하면 더 빠르게 문제를 해결할 수 있는 경우가 많습니다. \ No newline at end of file diff --git a/docs/support.md b/docs/support.md new file mode 100644 index 0000000..bcdd3d0 --- /dev/null +++ b/docs/support.md @@ -0,0 +1,26 @@ +--- +description: Get help with DoubleZero — check live link status and open a Discord support ticket. +--- + +# Support + +Need help connecting to or operating on DoubleZero? Start with live status, then reach the team if you still need a hand. + +## Network link status + +Check current link health and connectivity on the DoubleZero data portal: + +**[Link status dashboard](https://data.doublezero.xyz/status/links)** + +Use this page to confirm whether issues you are seeing are network-wide or local to your setup before changing configuration. + +## Contact the team + +If status looks healthy (or does not explain your issue), open a ticket with the DoubleZero team on Discord: + +1. Join **[discord.gg/doublezerotech](https://discord.gg/doublezerotech)** +2. Open a **support ticket** and describe the problem +3. Include what you can: environment (mainnet / testnet), node type, **client IP**, recent commands, and any error output + +!!! tip "Before you open a ticket" + The [Troubleshooting](troubleshooting.md) guide covers the most common connection failures. Checking that first often gets you unblocked faster. diff --git a/docs/support.pt.md b/docs/support.pt.md new file mode 100644 index 0000000..681a5d2 --- /dev/null +++ b/docs/support.pt.md @@ -0,0 +1,26 @@ +--- +description: Obtenha ajuda com o DoubleZero — verifique o status dos links em tempo real e abra um ticket de suporte no Discord. +--- + +# Suporte + +Precisa de ajuda para se conectar ou operar no DoubleZero? Comece verificando o status em tempo real e, em seguida, entre em contato com a equipe se ainda precisar de assistência. + +## Status dos links da rede + +Verifique a saúde e a conectividade atual dos links no portal de dados do DoubleZero: + +**[Painel de status dos links](https://data.doublezero.xyz/status/links)** + +Use esta página para confirmar se os problemas que você está enfrentando são generalizados na rede ou locais à sua configuração antes de alterar qualquer configuração. + +## Entre em contato com a equipe + +Se o status parece saudável (ou não explica o seu problema), abra um ticket com a equipe do DoubleZero no Discord: + +1. Entre em **[discord.gg/doublezerotech](https://discord.gg/doublezerotech)** +2. Abra um **ticket de suporte** e descreva o problema +3. Inclua o que puder: ambiente (mainnet / testnet), tipo de nó, **IP do cliente**, comandos recentes e qualquer saída de erro + +!!! tip "Antes de abrir um ticket" + O guia de [Solução de problemas](troubleshooting.md) cobre as falhas de conexão mais comuns. Verificá-lo primeiro geralmente resolve seu problema mais rapidamente. \ No newline at end of file diff --git a/docs/support.zh.md b/docs/support.zh.md new file mode 100644 index 0000000..eca8004 --- /dev/null +++ b/docs/support.zh.md @@ -0,0 +1,26 @@ +--- +description: 获取 DoubleZero 帮助 — 查看实时链路状态并在 Discord 上提交支持工单。 +--- + +# 支持 + +在连接或使用 DoubleZero 时需要帮助?请先查看实时状态,如果仍需协助,请联系团队。 + +## 网络链路状态 + +在 DoubleZero 数据门户上查看当前链路健康状况和连接性: + +**[链路状态仪表板](https://data.doublezero.xyz/status/links)** + +在更改配置之前,请使用此页面确认您遇到的问题是全网范围的还是仅限于您的本地环境。 + +## 联系团队 + +如果状态显示正常(或无法解释您的问题),请在 Discord 上向 DoubleZero 团队提交工单: + +1. 加入 **[discord.gg/doublezerotech](https://discord.gg/doublezerotech)** +2. 提交一个**支持工单**并描述问题 +3. 尽可能提供以下信息:环境(mainnet / testnet)、节点类型、**客户端 IP**、最近执行的命令以及任何错误输出 + +!!! tip "在提交工单之前" + [故障排除](troubleshooting.md)指南涵盖了最常见的连接故障。先查看该指南通常能更快地帮助您解决问题。 \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index d5495b7..9fc9210 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,20 +7,33 @@ site_description: >- theme: name: material custom_dir: overrides - logo: logo.png + logo: logo.svg favicon: favicon.ico - # Use a language-style globe icon for the language selector - icon: - alternate: fontawesome/solid/language palette: - scheme: slate - primary: grey - accent: custom + # Dark is the default (first entry). Toggle appears in the header. + - scheme: slate + primary: black + accent: custom + toggle: + icon: material/weather-night + name: Switch to light mode + - scheme: default + primary: white + accent: custom + toggle: + icon: material/weather-sunny + name: Switch to dark mode + # Self-hosted Reckless + Suisse Intl (DoubleZero brand kit) via extra.css + font: false features: - content.code.copy - navigation.indexes - - toc.integrate - - header.autohide + - navigation.tracking + # Instant nav: language switcher uses JS path rewriting (see + # language-selector-label.js). i18n still warns about Material's built-in + # contextual alternates; CI allows that one known warning. + - navigation.instant + - navigation.instant.progress plugins: - search # NOTE: llms.txt / llms-full.txt are generated by hooks/emit_markdown.py. @@ -85,6 +98,7 @@ nav: - Decommissioning: contribute-decommission.md - Architecture: architecture.md - Glossary: glossary.md + - Support: support.md markdown_extensions: - admonition - pymdownx.details @@ -104,6 +118,8 @@ markdown_extensions: - pymdownx.blocks.caption - pymdownx.arithmatex: generic: true + - toc: + title: On this page extra_css: - stylesheets/extra.css - stylesheets/wizard.css @@ -111,6 +127,10 @@ extra_javascript: - javascripts/cloudsmith-versions.js - javascripts/connection-wizard.js - javascripts/page-toolbar.js + - javascripts/nav-accordion.js + - javascripts/toc-page-top.js + - javascripts/header-controls-order.js + - javascripts/theme-mode-switch.js - javascripts/language-selector-label.js - javascripts/search-language-filter.js - javascripts/mathjax.js diff --git a/overrides/partials/alternate.html b/overrides/partials/alternate.html new file mode 100644 index 0000000..0328625 --- /dev/null +++ b/overrides/partials/alternate.html @@ -0,0 +1,30 @@ +{#- + This file was automatically generated - do not edit. + Forked from mkdocs-material; re-sync on theme upgrades. + Language selector: current language name + chevron (opens menu). +-#} +{%- set lang_ns = namespace(current_name="English") -%} +{%- for alt in config.extra.alternate -%} + {%- if alt.lang == config.theme.language -%} + {%- set lang_ns.current_name = alt.name -%} + {%- endif -%} +{%- endfor -%} +
+
+ +
+ +
+
+
diff --git a/overrides/partials/header.html b/overrides/partials/header.html new file mode 100644 index 0000000..b6c9c2f --- /dev/null +++ b/overrides/partials/header.html @@ -0,0 +1,80 @@ +{#- + This file was automatically generated - do not edit. + Forked from mkdocs-material; re-sync on theme upgrades. + Support CTA resolves via page object for correct relative links. +-#} +{% set class = "md-header" %} +{% if "navigation.tabs.sticky" in features %} + {% set class = class ~ " md-header--shadow md-header--lifted" %} +{% elif "navigation.tabs" not in features %} + {% set class = class ~ " md-header--shadow" %} +{% endif %} +
+ + {% if "navigation.tabs.sticky" in features %} + {% if "navigation.tabs" in features %} + {% include "partials/tabs.html" %} + {% endif %} + {% endif %} +
diff --git a/overrides/partials/logo.html b/overrides/partials/logo.html index df7d779..05eaf84 100644 --- a/overrides/partials/logo.html +++ b/overrides/partials/logo.html @@ -1,2 +1,2 @@ -DoubleZero -{{ config.site_name }} +{{ config.site_name }} + diff --git a/overrides/partials/nav.html b/overrides/partials/nav.html new file mode 100644 index 0000000..c98dc1c --- /dev/null +++ b/overrides/partials/nav.html @@ -0,0 +1,85 @@ +{#- + This file was automatically generated - do not edit. + Forked from mkdocs-material; re-sync on theme upgrades. +-#} +{% import "partials/nav-item.html" as item with context %} +{% set class = "md-nav md-nav--primary" %} +{% if "navigation.tabs" in features %} + {% set class = class ~ " md-nav--lifted" %} +{% endif %} +{% if "toc.integrate" in features %} + {% set class = class ~ " md-nav--integrated" %} +{% endif %} + diff --git a/overrides/partials/toc.html b/overrides/partials/toc.html new file mode 100644 index 0000000..d7cff06 --- /dev/null +++ b/overrides/partials/toc.html @@ -0,0 +1,28 @@ +{#- + This file was automatically generated - do not edit. + Forked from mkdocs-material; re-sync on theme upgrades. +-#} +{% set title = lang.t("toc") %} +{% if config.mdx_configs.toc and config.mdx_configs.toc.title %} + {% set title = config.mdx_configs.toc.title %} +{% endif %} +