From bbc87f8b38a83ac375ace38e3bce6d61961e165c Mon Sep 17 00:00:00 2001 From: Pat Capulong Date: Sun, 21 Jun 2026 02:13:35 -0700 Subject: [PATCH 01/15] Add collapsible docs sidebar with an edge chevron tab A chevron tab on the right edge of #sidebar-content toggles html.ls-nav-collapsed and the sidebar wipes closed with an ease-out-snappy curve. The choice persists in localStorage and the Global Accounts playground defaults collapsed (set pre-paint in docs.json head.raw to avoid a flash). Desktop only; adds mintlify/sidebar-toggle.js plus chevron icons rendered as theme-aware masks. The state class is ls-nav-collapsed (not "sidebar-collapsed") to avoid the existing [class*="sidebar-collapse"] hide rule matching . Co-authored-by: Cursor --- mintlify/docs.json | 2 +- .../images/icons/IconChevronLeftMedium.svg | 3 + .../images/icons/IconChevronRightMedium.svg | 3 + mintlify/sidebar-toggle.js | 143 ++++++++++++++++++ mintlify/style.css | 125 +++++++++++++++ 5 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 mintlify/images/icons/IconChevronLeftMedium.svg create mode 100644 mintlify/images/icons/IconChevronRightMedium.svg create mode 100644 mintlify/sidebar-toggle.js diff --git a/mintlify/docs.json b/mintlify/docs.json index cdfb7a9e7..6e80f397c 100644 --- a/mintlify/docs.json +++ b/mintlify/docs.json @@ -446,7 +446,7 @@ }, "footer": {}, "head": { - "raw": "", + "raw": "", "links": [ { "rel": "preload", diff --git a/mintlify/images/icons/IconChevronLeftMedium.svg b/mintlify/images/icons/IconChevronLeftMedium.svg new file mode 100644 index 000000000..9cafc052c --- /dev/null +++ b/mintlify/images/icons/IconChevronLeftMedium.svg @@ -0,0 +1,3 @@ + + + diff --git a/mintlify/images/icons/IconChevronRightMedium.svg b/mintlify/images/icons/IconChevronRightMedium.svg new file mode 100644 index 000000000..8207536f2 --- /dev/null +++ b/mintlify/images/icons/IconChevronRightMedium.svg @@ -0,0 +1,3 @@ + + + diff --git a/mintlify/sidebar-toggle.js b/mintlify/sidebar-toggle.js new file mode 100644 index 000000000..dc2f397b3 --- /dev/null +++ b/mintlify/sidebar-toggle.js @@ -0,0 +1,143 @@ +// Collapsible docs sidebar (desktop). +// +// Injects a small chevron "tab" on the right edge of #sidebar-content. Clicking +// it toggles html.ls-nav-collapsed (the CSS in style.css animates the +// collapse with an ease-out-snappy curve). The choice is remembered in +// localStorage and applies on every page; the Global Accounts playground +// defaults to collapsed until the visitor sets a preference. +// +// First-paint state is set by an inline script in docs.json head.raw so there's +// no flash; this file handles the interactive tab + SPA route changes. + +(function () { + var DESKTOP_MIN = 1024; + var KEY = 'ls-nav-collapsed'; + var DEMO_PATHS = ['/global-accounts/demo', '/global-accounts/demo/']; + var SIDEBAR_WIDTH = 280; // matches #sidebar-content width in style.css + + var tab = null; + + function isDesktop() { + return window.innerWidth >= DESKTOP_MIN; + } + + function isDemo() { + return DEMO_PATHS.indexOf(location.pathname) !== -1; + } + + function getPref() { + try { + return localStorage.getItem(KEY); + } catch (e) { + return null; + } + } + + function setPref(value) { + try { + localStorage.setItem(KEY, value); + } catch (e) { + /* private mode / blocked storage — toggle still works for the session */ + } + } + + // Remembered preference wins everywhere; otherwise the playground defaults to + // collapsed and every other page defaults to expanded. + function shouldCollapse() { + var pref = getPref(); + if (pref === '1') return true; + if (pref === '0') return false; + return isDemo(); + } + + function isCollapsed() { + return document.documentElement.classList.contains('ls-nav-collapsed'); + } + + // The sidebar's expanded right edge. Computed from its left offset + the fixed + // width so it's correct even while collapsed (width 0). Drives the tab's + // resting position via --ls-sidebar-edge. + function measureEdge() { + var sidebar = document.getElementById('sidebar-content'); + if (!sidebar) return; + var left = sidebar.getBoundingClientRect().left; + var edge = Math.round(left + SIDEBAR_WIDTH); + document.documentElement.style.setProperty('--ls-sidebar-edge', edge + 'px'); + } + + function updateTab() { + if (!tab) return; + var collapsed = isCollapsed(); + tab.setAttribute('aria-label', collapsed ? 'Show navigation' : 'Hide navigation'); + tab.setAttribute('aria-expanded', collapsed ? 'false' : 'true'); + } + + function applyState(collapsed) { + document.documentElement.classList.toggle('ls-nav-collapsed', collapsed); + updateTab(); + } + + function removeTab() { + if (tab && tab.parentNode) tab.parentNode.removeChild(tab); + tab = null; + } + + function ensureTab() { + if (!isDesktop() || !document.getElementById('sidebar-content')) { + removeTab(); + return; + } + if (tab && document.body.contains(tab)) return; + + tab = document.createElement('button'); + tab.type = 'button'; + tab.className = 'ls-nav-tab'; + tab.innerHTML = ''; + tab.addEventListener('click', function () { + var next = !isCollapsed(); + setPref(next ? '1' : '0'); + measureEdge(); + applyState(next); + }); + document.body.appendChild(tab); + measureEdge(); + updateTab(); + } + + // Re-apply the default when no preference is stored (keeps a stored choice), + // then make sure the tab exists. + function sync() { + applyState(shouldCollapse()); + ensureTab(); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', sync); + } else { + sync(); + } + + // SPA navigation: Mintlify swaps content without a full reload. + var lastPath = location.pathname; + var observer = new MutationObserver(function () { + if (location.pathname !== lastPath) { + lastPath = location.pathname; + sync(); + } else if (!tab || !document.body.contains(tab)) { + ensureTab(); + } + }); + observer.observe(document.body, { childList: true, subtree: true }); + window.addEventListener('popstate', sync); + + var rafPending = false; + window.addEventListener('resize', function () { + if (rafPending) return; + rafPending = true; + requestAnimationFrame(function () { + rafPending = false; + ensureTab(); + measureEdge(); + }); + }); +})(); diff --git a/mintlify/style.css b/mintlify/style.css index 9ec1c9a96..dd82354dd 100644 --- a/mintlify/style.css +++ b/mintlify/style.css @@ -4233,3 +4233,128 @@ html.dark:has(#wallet-demo-container) #navbar button[class*="h-14"][class*="text color: var(--ls-gray-700); } +/* =========================================== + Collapsible sidebar (desktop) + A chevron tab (injected by sidebar-toggle.js) rides the sidebar's right edge. + Clicking it toggles html.ls-nav-collapsed; the sidebar wipes closed with + (class name avoids the substring "sidebar-collapse" so the hide-controls block + above doesn't match and display:none the whole page) + an ease-out-snappy curve. State + the playground default are handled in JS; + first paint is set by the inline script in docs.json head.raw. + =========================================== */ + +:root { + --ls-sidebar-ease: cubic-bezier(0.19, 1, 0.22, 1); /* easeOutSnappy */ + --ls-sidebar-dur: 260ms; + --ls-nav-tab-w: 24px; + --ls-nav-tab-h: 80px; +} + +/* Animate the column width. The inner nav is pinned to the expanded width and + clipped (overflow-x) so it wipes cleanly instead of squishing/reflowing. */ +#sidebar-content { + overflow-x: hidden !important; + transition: width var(--ls-sidebar-dur) var(--ls-sidebar-ease), + min-width var(--ls-sidebar-dur) var(--ls-sidebar-ease), + max-width var(--ls-sidebar-dur) var(--ls-sidebar-ease) !important; +} + +/* Pin children to the expanded width so they clip (not squish/reflow) as the + column animates closed. Width is the cross axis here (#sidebar-content is a + flex COLUMN), so this must NOT touch `flex` — the nav keeps its flex-1 growth + and the footer row stays pinned to the bottom. */ +#sidebar-content > * { + width: 280px !important; + box-sizing: border-box !important; +} + +html.ls-nav-collapsed #sidebar-content { + width: 0 !important; + min-width: 0 !important; + max-width: 0 !important; + border-right-color: transparent !important; +} + +/* The tab. Fixed, vertically centered below the navbar (64px bar + 48px tabs). + Its right edge aligns to the sidebar's right edge when expanded; it slides to + the screen's left edge when collapsed (left animates with the same curve). */ +.ls-nav-tab { + position: fixed; + top: calc(50dvh + 56px); + /* nudged 0.5px under the sidebar's right border so the tab (higher z-index, + same fill, no left border) paints over the divider where it sits — reads as + one continuous surface with the sidebar. */ + left: calc(var(--ls-sidebar-edge, 280px) - 0.5px); + transform: translateY(-50%); + z-index: 30; + display: flex; + align-items: center; + justify-content: center; + width: var(--ls-nav-tab-w); + height: var(--ls-nav-tab-h); + margin: 0; + padding: 0; + border: 0.5px solid var(--ls-black-10); + border-left: none; + border-radius: 0 var(--ls-radius-md) var(--ls-radius-md) 0; + background-color: var(--ls-gray-100); + color: var(--ls-gray-500); + cursor: pointer; + -webkit-appearance: none; + appearance: none; + box-shadow: 1px 0 3px rgba(0, 0, 0, 0.04); + transition: left var(--ls-sidebar-dur) var(--ls-sidebar-ease), + background-color 150ms var(--ls-sidebar-ease), + color 150ms var(--ls-sidebar-ease); +} + +html.dark .ls-nav-tab { + border-color: var(--ls-white-06); + background-color: var(--ls-gray-975); + color: var(--ls-gray-600); + box-shadow: 1px 0 3px rgba(0, 0, 0, 0.24); +} + +html.ls-nav-collapsed .ls-nav-tab { + left: 0; +} + +.ls-nav-tab:hover { + background-color: var(--ls-gray-050); + color: var(--ls-gray-950); +} + +html.dark .ls-nav-tab:hover { + background-color: var(--ls-gray-900); + color: var(--ls-gray-050); +} + +.ls-nav-tab-icon { + width: 18px; + height: 18px; + background-color: currentColor; + -webkit-mask: url("/images/icons/IconChevronLeftMedium.svg") center / 18px 18px + no-repeat; + mask: url("/images/icons/IconChevronLeftMedium.svg") center / 18px 18px + no-repeat; +} + +html.ls-nav-collapsed .ls-nav-tab-icon { + -webkit-mask-image: url("/images/icons/IconChevronRightMedium.svg"); + mask-image: url("/images/icons/IconChevronRightMedium.svg"); +} + +/* Desktop only — mobile uses the hamburger nav (no #sidebar-content). */ +@media (max-width: 1023px) { + .ls-nav-tab { + display: none !important; + } +} + +@media (prefers-reduced-motion: reduce) { + #sidebar-content, + .ls-nav-tab { + transition: none !important; + } +} + From 5650374da4cfb554006a4a5bed10600751c8cd12 Mon Sep 17 00:00:00 2001 From: Pat Capulong Date: Sun, 21 Jun 2026 02:13:42 -0700 Subject: [PATCH 02/15] Keep wallet demo app + API side by side at docs widths Lower $breakpoint-layout-wide 1800px -> 1200px so the embedded playground stays in the 3-column (Configure | App | API) layout at typical docs widths now that the docs sidebar can collapse. Co-authored-by: Cursor --- components/grid-wallet-demo/src/styles/breakpoints.scss | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/grid-wallet-demo/src/styles/breakpoints.scss b/components/grid-wallet-demo/src/styles/breakpoints.scss index f13e6a64a..6c10df0b5 100644 --- a/components/grid-wallet-demo/src/styles/breakpoints.scss +++ b/components/grid-wallet-demo/src/styles/breakpoints.scss @@ -1,5 +1,7 @@ -/** App + API side-by-side inside the right column (large desktop). */ -$breakpoint-layout-wide: 1800px; +/** App + API side-by-side inside the right column. Lowered from 1800px so the + embedded playground keeps app + API side by side at typical docs widths (the + docs sidebar is now collapsible, freeing horizontal space). Tune to taste. */ +$breakpoint-layout-wide: 1200px; /** Configure stacks above app + API (phone). */ $breakpoint-layout-mobile: 767px; From f29af5b5322fde2d05407a76cf83f23b7c4f0cd4 Mon Sep 17 00:00:00 2001 From: Pat Capulong Date: Sun, 21 Jun 2026 02:43:50 -0700 Subject: [PATCH 03/15] Replace the sidebar chevron tab with a panel rail Collapsed now shows a 48px left rail with a bare panel icon that reserves real layout space: the sidebar collapses to the rail width (not 0), so the playground iframe starts after it instead of being overlaid. Expanded shows the toggle only on hover at the edge (col-resize cursor); collapsed uses a pointer cursor. Swaps the chevron glyphs for IconSidebarLeftArrow/RightArrow panel icons (1.5px stroke, matching the nav icons). Co-authored-by: Cursor --- .../images/icons/IconChevronLeftMedium.svg | 3 - .../images/icons/IconChevronRightMedium.svg | 3 - .../images/icons/IconSidebarLeftArrow.svg | 5 + .../images/icons/IconSidebarRightArrow.svg | 5 + mintlify/sidebar-toggle.js | 11 +- mintlify/style.css | 158 ++++++++++++------ 6 files changed, 123 insertions(+), 62 deletions(-) delete mode 100644 mintlify/images/icons/IconChevronLeftMedium.svg delete mode 100644 mintlify/images/icons/IconChevronRightMedium.svg create mode 100644 mintlify/images/icons/IconSidebarLeftArrow.svg create mode 100644 mintlify/images/icons/IconSidebarRightArrow.svg diff --git a/mintlify/images/icons/IconChevronLeftMedium.svg b/mintlify/images/icons/IconChevronLeftMedium.svg deleted file mode 100644 index 9cafc052c..000000000 --- a/mintlify/images/icons/IconChevronLeftMedium.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/mintlify/images/icons/IconChevronRightMedium.svg b/mintlify/images/icons/IconChevronRightMedium.svg deleted file mode 100644 index 8207536f2..000000000 --- a/mintlify/images/icons/IconChevronRightMedium.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/mintlify/images/icons/IconSidebarLeftArrow.svg b/mintlify/images/icons/IconSidebarLeftArrow.svg new file mode 100644 index 000000000..cc171afec --- /dev/null +++ b/mintlify/images/icons/IconSidebarLeftArrow.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/mintlify/images/icons/IconSidebarRightArrow.svg b/mintlify/images/icons/IconSidebarRightArrow.svg new file mode 100644 index 000000000..66faa8fdd --- /dev/null +++ b/mintlify/images/icons/IconSidebarRightArrow.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/mintlify/sidebar-toggle.js b/mintlify/sidebar-toggle.js index dc2f397b3..81d6bce8d 100644 --- a/mintlify/sidebar-toggle.js +++ b/mintlify/sidebar-toggle.js @@ -1,8 +1,9 @@ // Collapsible docs sidebar (desktop). // -// Injects a small chevron "tab" on the right edge of #sidebar-content. Clicking -// it toggles html.ls-nav-collapsed (the CSS in style.css animates the -// collapse with an ease-out-snappy curve). The choice is remembered in +// Injects a full-height "rail" on the right edge of #sidebar-content; hovering +// it reveals a centered panel-toggle button. Clicking toggles +// html.ls-nav-collapsed (the CSS in style.css animates the collapse with an +// ease-out-snappy curve). The choice is remembered in // localStorage and applies on every page; the Global Accounts playground // defaults to collapsed until the visitor sets a preference. // @@ -91,8 +92,8 @@ tab = document.createElement('button'); tab.type = 'button'; - tab.className = 'ls-nav-tab'; - tab.innerHTML = ''; + tab.className = 'ls-nav-rail'; + tab.innerHTML = ''; tab.addEventListener('click', function () { var next = !isCollapsed(); setPref(next ? '1' : '0'); diff --git a/mintlify/style.css b/mintlify/style.css index dd82354dd..81088c2e9 100644 --- a/mintlify/style.css +++ b/mintlify/style.css @@ -4235,19 +4235,20 @@ html.dark:has(#wallet-demo-container) #navbar button[class*="h-14"][class*="text /* =========================================== Collapsible sidebar (desktop) - A chevron tab (injected by sidebar-toggle.js) rides the sidebar's right edge. - Clicking it toggles html.ls-nav-collapsed; the sidebar wipes closed with - (class name avoids the substring "sidebar-collapse" so the hide-controls block - above doesn't match and display:none the whole page) - an ease-out-snappy curve. State + the playground default are handled in JS; + A full-height "rail" (injected by sidebar-toggle.js) sits on the sidebar's + right edge; hovering it reveals a centered panel-toggle button. Clicking + toggles html.ls-nav-collapsed and the sidebar wipes closed with an + ease-out-snappy curve. State + the playground default are handled in JS; first paint is set by the inline script in docs.json head.raw. + (The class is ls-nav-collapsed, not "sidebar-collapsed", so the broad + [class*="sidebar-collapse"] hide rule above doesn't match .) =========================================== */ :root { --ls-sidebar-ease: cubic-bezier(0.19, 1, 0.22, 1); /* easeOutSnappy */ --ls-sidebar-dur: 260ms; - --ls-nav-tab-w: 24px; - --ls-nav-tab-h: 80px; + --ls-nav-rail-hit: 24px; /* expanded: invisible hover/click zone at the edge */ + --ls-nav-rail-w: 48px; /* collapsed: visible left rail */ } /* Animate the column width. The inner nav is pinned to the expanded width and @@ -4266,94 +4267,149 @@ html.dark:has(#wallet-demo-container) #navbar button[class*="h-14"][class*="text #sidebar-content > * { width: 280px !important; box-sizing: border-box !important; + transition: opacity var(--ls-sidebar-dur) var(--ls-sidebar-ease); } +/* Collapse to the rail's width (not 0) so the rail reserves real layout space — + content/iframe starts after it instead of being overlaid. */ html.ls-nav-collapsed #sidebar-content { - width: 0 !important; - min-width: 0 !important; - max-width: 0 !important; + width: var(--ls-nav-rail-w) !important; + min-width: var(--ls-nav-rail-w) !important; + max-width: var(--ls-nav-rail-w) !important; border-right-color: transparent !important; } -/* The tab. Fixed, vertically centered below the navbar (64px bar + 48px tabs). - Its right edge aligns to the sidebar's right edge when expanded; it slides to - the screen's left edge when collapsed (left animates with the same curve). */ -.ls-nav-tab { +/* Hide the (clipped) nav + footer in the collapsed slot so only the rail shows. */ +html.ls-nav-collapsed #sidebar-content > * { + opacity: 0 !important; + pointer-events: none !important; +} + +/* EXPANDED: an invisible hover/click zone straddling the sidebar's right edge — + no visible rail here, just the hover-revealed button (below). */ +.ls-nav-rail { position: fixed; - top: calc(50dvh + 56px); - /* nudged 0.5px under the sidebar's right border so the tab (higher z-index, - same fill, no left border) paints over the divider where it sits — reads as - one continuous surface with the sidebar. */ - left: calc(var(--ls-sidebar-edge, 280px) - 0.5px); - transform: translateY(-50%); + top: 112px; /* below navbar: 64px bar + 48px tabs */ + bottom: 0; + left: calc(var(--ls-sidebar-edge, 280px) - (var(--ls-nav-rail-hit) / 2)); + width: var(--ls-nav-rail-hit); z-index: 30; + margin: 0; + padding: 0; + border: none; + background: transparent; + cursor: col-resize; /* match the demo's ColumnResizeHandle */ display: flex; align-items: center; justify-content: center; - width: var(--ls-nav-tab-w); - height: var(--ls-nav-tab-h); - margin: 0; - padding: 0; - border: 0.5px solid var(--ls-black-10); - border-left: none; - border-radius: 0 var(--ls-radius-md) var(--ls-radius-md) 0; - background-color: var(--ls-gray-100); - color: var(--ls-gray-500); - cursor: pointer; -webkit-appearance: none; appearance: none; - box-shadow: 1px 0 3px rgba(0, 0, 0, 0.04); transition: left var(--ls-sidebar-dur) var(--ls-sidebar-ease), - background-color 150ms var(--ls-sidebar-ease), - color 150ms var(--ls-sidebar-ease); + width var(--ls-sidebar-dur) var(--ls-sidebar-ease), + background-color var(--ls-sidebar-dur) var(--ls-sidebar-ease); +} + +/* COLLAPSED: a visible left rail, full height, flush at the screen edge. */ +html.ls-nav-collapsed .ls-nav-rail { + left: 0; + width: var(--ls-nav-rail-w); + background-color: var(--ls-gray-100); + border-right: 0.5px solid var(--ls-black-10); + cursor: pointer; /* collapsed: it's a button to reopen, not a resize handle */ } -html.dark .ls-nav-tab { +html.dark.ls-nav-collapsed .ls-nav-rail { + background-color: var(--ls-gray-975); + border-right-color: var(--ls-white-06); +} + +/* EXPANDED toggle: a styled button, hidden until you hover the edge. */ +.ls-nav-rail-btn { + display: grid; + place-items: center; + flex: none; + width: 30px; + height: 30px; + border-radius: var(--ls-radius-md); + border: 0.5px solid var(--ls-black-10); + background-color: var(--ls-gray-100); + color: var(--ls-gray-500); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); + opacity: 0; + transform: scale(0.92); + pointer-events: none; /* the rail handles the click */ + transition: opacity 150ms var(--ls-sidebar-ease), + transform 150ms var(--ls-sidebar-ease), background-color 150ms ease, + color 150ms ease; +} + +html.dark .ls-nav-rail-btn { border-color: var(--ls-white-06); background-color: var(--ls-gray-975); color: var(--ls-gray-600); - box-shadow: 1px 0 3px rgba(0, 0, 0, 0.24); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); } -html.ls-nav-collapsed .ls-nav-tab { - left: 0; +.ls-nav-rail:hover .ls-nav-rail-btn { + opacity: 1; + transform: scale(1); + color: var(--ls-gray-950); } -.ls-nav-tab:hover { - background-color: var(--ls-gray-050); +html.dark .ls-nav-rail:hover .ls-nav-rail-btn { + color: var(--ls-gray-050); +} + +/* COLLAPSED toggle: just the icon — no fill/border/shadow — always visible, + centered in the rail. */ +html.ls-nav-collapsed .ls-nav-rail-btn { + opacity: 1; + transform: none; + border: none; + background: transparent; + box-shadow: none; + color: var(--ls-gray-500); +} + +html.dark.ls-nav-collapsed .ls-nav-rail-btn { + color: var(--ls-gray-600); +} + +html.ls-nav-collapsed .ls-nav-rail:hover .ls-nav-rail-btn { color: var(--ls-gray-950); } -html.dark .ls-nav-tab:hover { - background-color: var(--ls-gray-900); +html.dark.ls-nav-collapsed .ls-nav-rail:hover .ls-nav-rail-btn { color: var(--ls-gray-050); } -.ls-nav-tab-icon { - width: 18px; - height: 18px; +.ls-nav-rail-btn::before { + content: ""; + width: 20px; + height: 20px; background-color: currentColor; - -webkit-mask: url("/images/icons/IconChevronLeftMedium.svg") center / 18px 18px + -webkit-mask: url("/images/icons/IconSidebarLeftArrow.svg") center / 20px 20px no-repeat; - mask: url("/images/icons/IconChevronLeftMedium.svg") center / 18px 18px + mask: url("/images/icons/IconSidebarLeftArrow.svg") center / 20px 20px no-repeat; } -html.ls-nav-collapsed .ls-nav-tab-icon { - -webkit-mask-image: url("/images/icons/IconChevronRightMedium.svg"); - mask-image: url("/images/icons/IconChevronRightMedium.svg"); +html.ls-nav-collapsed .ls-nav-rail-btn::before { + -webkit-mask-image: url("/images/icons/IconSidebarRightArrow.svg"); + mask-image: url("/images/icons/IconSidebarRightArrow.svg"); } /* Desktop only — mobile uses the hamburger nav (no #sidebar-content). */ @media (max-width: 1023px) { - .ls-nav-tab { + .ls-nav-rail { display: none !important; } } @media (prefers-reduced-motion: reduce) { #sidebar-content, - .ls-nav-tab { + .ls-nav-rail, + .ls-nav-rail-btn { transition: none !important; } } From 8253bed48b5c57a2c91143ff334516b5a5192726 Mon Sep 17 00:00:00 2001 From: Pat Capulong Date: Sun, 21 Jun 2026 03:20:19 -0700 Subject: [PATCH 04/15] Make the sidebar rail drag-to-resize and refine its hover states - Drag the rail to resize the sidebar (280-420px, session-only, resets to 280 on reload); dragging left past the threshold collapses live. Click still toggles collapse. - Edge hover reveals the toggle in its resting state; hovering the button itself is its own active state. - 2px edge highlight on hover/drag drawn as an overlay so it never shifts layout, and suppressed during the open/close animation so it doesn't flash. - col-resize cursor on the edge (matches the demo's ColumnResizeHandle), pointer when collapsed; the playground iframe ignores pointer events mid- drag so the resize keeps tracking over it. Co-authored-by: Cursor --- mintlify/sidebar-toggle.js | 164 +++++++++++++++++++++++++------------ mintlify/style.css | 82 +++++++++++++++++-- 2 files changed, 187 insertions(+), 59 deletions(-) diff --git a/mintlify/sidebar-toggle.js b/mintlify/sidebar-toggle.js index 81d6bce8d..a94544ab4 100644 --- a/mintlify/sidebar-toggle.js +++ b/mintlify/sidebar-toggle.js @@ -1,22 +1,24 @@ -// Collapsible docs sidebar (desktop). +// Collapsible + resizable docs sidebar (desktop). // -// Injects a full-height "rail" on the right edge of #sidebar-content; hovering -// it reveals a centered panel-toggle button. Clicking toggles -// html.ls-nav-collapsed (the CSS in style.css animates the collapse with an -// ease-out-snappy curve). The choice is remembered in -// localStorage and applies on every page; the Global Accounts playground -// defaults to collapsed until the visitor sets a preference. -// -// First-paint state is set by an inline script in docs.json head.raw so there's -// no flash; this file handles the interactive tab + SPA route changes. +// A rail on the right edge of #sidebar-content: +// - drag it to resize the sidebar (clamped MIN..MAX); release below the snap +// threshold to collapse. +// - click it (no drag) to toggle collapse. +// Collapsed shows a slim visible rail with a bare icon (click/pointer to +// reopen); expanded shows the toggle on hover at the edge (col-resize). Width + +// collapsed state persist in localStorage and are restored pre-paint by the +// inline script in docs.json head.raw, so there's no flash. (function () { var DESKTOP_MIN = 1024; var KEY = 'ls-nav-collapsed'; var DEMO_PATHS = ['/global-accounts/demo', '/global-accounts/demo/']; - var SIDEBAR_WIDTH = 280; // matches #sidebar-content width in style.css + var MIN_WIDTH = 280; // the original sidebar width — only resizes wider + var MAX_WIDTH = 420; + var SNAP_COLLAPSE = 240; // drag left past this x -> collapse + var DRAG_THRESHOLD = 4; // px of movement before a press counts as a drag - var tab = null; + var rail = null; function isDesktop() { return window.innerWidth >= DESKTOP_MIN; @@ -38,10 +40,20 @@ try { localStorage.setItem(KEY, value); } catch (e) { - /* private mode / blocked storage — toggle still works for the session */ + /* private mode — toggle still works for the session */ } } + function clampWidth(w) { + return Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, Math.round(w))); + } + + // Width is session-only — not persisted, so a refresh resets to the 280px + // default (the CSS var fallback) with no post-paint resize jump. + function applyWidth(w) { + document.documentElement.style.setProperty('--ls-sidebar-width', w + 'px'); + } + // Remembered preference wins everywhere; otherwise the playground defaults to // collapsed and every other page defaults to expanded. function shouldCollapse() { @@ -55,61 +67,112 @@ return document.documentElement.classList.contains('ls-nav-collapsed'); } - // The sidebar's expanded right edge. Computed from its left offset + the fixed - // width so it's correct even while collapsed (width 0). Drives the tab's - // resting position via --ls-sidebar-edge. - function measureEdge() { - var sidebar = document.getElementById('sidebar-content'); - if (!sidebar) return; - var left = sidebar.getBoundingClientRect().left; - var edge = Math.round(left + SIDEBAR_WIDTH); - document.documentElement.style.setProperty('--ls-sidebar-edge', edge + 'px'); - } - - function updateTab() { - if (!tab) return; + function updateRail() { + if (!rail) return; var collapsed = isCollapsed(); - tab.setAttribute('aria-label', collapsed ? 'Show navigation' : 'Hide navigation'); - tab.setAttribute('aria-expanded', collapsed ? 'false' : 'true'); + rail.setAttribute('aria-label', collapsed ? 'Show navigation' : 'Hide navigation'); + rail.setAttribute('aria-expanded', collapsed ? 'false' : 'true'); } function applyState(collapsed) { document.documentElement.classList.toggle('ls-nav-collapsed', collapsed); - updateTab(); + updateRail(); } - function removeTab() { - if (tab && tab.parentNode) tab.parentNode.removeChild(tab); - tab = null; + function removeRail() { + if (rail && rail.parentNode) rail.parentNode.removeChild(rail); + rail = null; } - function ensureTab() { + function ensureRail() { if (!isDesktop() || !document.getElementById('sidebar-content')) { - removeTab(); + removeRail(); return; } - if (tab && document.body.contains(tab)) return; + if (rail && document.body.contains(rail)) return; + + rail = document.createElement('button'); + rail.type = 'button'; + rail.className = 'ls-nav-rail'; + rail.innerHTML = ''; + attachInteractions(rail); + document.body.appendChild(rail); + updateRail(); + } + + // Drag = resize (expanded only); plain click = toggle collapse (either state, + // mouse or keyboard). + function attachInteractions(el) { + var startX = 0; + var moved = false; + var dragging = false; + var dragEndAt = 0; + var animTimer = 0; + + function onMove(e) { + if (!moved && Math.abs(e.clientX - startX) > DRAG_THRESHOLD) { + moved = true; + dragging = true; + document.documentElement.classList.add('ls-nav-dragging'); + document.body.style.userSelect = 'none'; + } + if (!dragging) return; + // Live: crossing the snap threshold collapses immediately (no release + // needed); dragging back out reopens and resumes resizing. + if (e.clientX < SNAP_COLLAPSE) { + if (!isCollapsed()) applyState(true); + } else { + if (isCollapsed()) applyState(false); + applyWidth(clampWidth(e.clientX)); + } + } + + function onUp(e) { + document.removeEventListener('mousemove', onMove, true); + document.removeEventListener('mouseup', onUp, true); + if (!moved) return; // a click — handled by the click listener + dragEndAt = Date.now(); + document.documentElement.classList.remove('ls-nav-dragging'); + document.body.style.userSelect = ''; + // State was already applied live during the drag — just persist it. + if (isCollapsed()) { + setPref('1'); + } else { + applyWidth(clampWidth(e.clientX)); + setPref('0'); + } + } + + // Resize only from the expanded edge; the collapsed rail is click-only. + el.addEventListener('mousedown', function (e) { + if (e.button !== 0 || isCollapsed()) return; + e.preventDefault(); + startX = e.clientX; + moved = false; + dragging = false; + document.addEventListener('mousemove', onMove, true); + document.addEventListener('mouseup', onUp, true); + }); - tab = document.createElement('button'); - tab.type = 'button'; - tab.className = 'ls-nav-rail'; - tab.innerHTML = ''; - tab.addEventListener('click', function () { + el.addEventListener('click', function () { + if (Date.now() - dragEndAt < 300) return; // swallow the click after a drag var next = !isCollapsed(); setPref(next ? '1' : '0'); - measureEdge(); applyState(next); + // Suppress the hover reveal during the open/close animation so the button + // and edge highlight don't flash from the cursor sitting over the rail + // mid-transition; they reveal together on a real hover once settled. + document.documentElement.classList.add('ls-nav-animating'); + clearTimeout(animTimer); + animTimer = setTimeout(function () { + document.documentElement.classList.remove('ls-nav-animating'); + }, 320); }); - document.body.appendChild(tab); - measureEdge(); - updateTab(); } - // Re-apply the default when no preference is stored (keeps a stored choice), - // then make sure the tab exists. function sync() { applyState(shouldCollapse()); - ensureTab(); + ensureRail(); } if (document.readyState === 'loading') { @@ -124,8 +187,8 @@ if (location.pathname !== lastPath) { lastPath = location.pathname; sync(); - } else if (!tab || !document.body.contains(tab)) { - ensureTab(); + } else if (!rail || !document.body.contains(rail)) { + ensureRail(); } }); observer.observe(document.body, { childList: true, subtree: true }); @@ -137,8 +200,7 @@ rafPending = true; requestAnimationFrame(function () { rafPending = false; - ensureTab(); - measureEdge(); + ensureRail(); }); }); })(); diff --git a/mintlify/style.css b/mintlify/style.css index 81088c2e9..ece86a3e1 100644 --- a/mintlify/style.css +++ b/mintlify/style.css @@ -780,9 +780,9 @@ button[class*="text-primary"] * { #sidebar-content { background-color: var(--ls-gray-100) !important; border-right: 0.5px solid var(--ls-black-10) !important; - width: 280px !important; - min-width: 280px !important; - max-width: 280px !important; + width: var(--ls-sidebar-width, 280px) !important; + min-width: var(--ls-sidebar-width, 280px) !important; + max-width: var(--ls-sidebar-width, 280px) !important; } html.dark #sidebar-content { @@ -4247,6 +4247,7 @@ html.dark:has(#wallet-demo-container) #navbar button[class*="h-14"][class*="text :root { --ls-sidebar-ease: cubic-bezier(0.19, 1, 0.22, 1); /* easeOutSnappy */ --ls-sidebar-dur: 260ms; + --ls-sidebar-width: 280px; /* resizable: drag the rail to change */ --ls-nav-rail-hit: 24px; /* expanded: invisible hover/click zone at the edge */ --ls-nav-rail-w: 48px; /* collapsed: visible left rail */ } @@ -4265,11 +4266,26 @@ html.dark:has(#wallet-demo-container) #navbar button[class*="h-14"][class*="text flex COLUMN), so this must NOT touch `flex` — the nav keeps its flex-1 growth and the footer row stays pinned to the bottom. */ #sidebar-content > * { - width: 280px !important; + width: var(--ls-sidebar-width, 280px) !important; box-sizing: border-box !important; transition: opacity var(--ls-sidebar-dur) var(--ls-sidebar-ease); } +/* During an active drag, kill transitions so width tracks the cursor 1:1. */ +html.ls-nav-dragging #sidebar-content, +html.ls-nav-dragging #sidebar-content > *, +html.ls-nav-dragging .ls-nav-rail { + transition: none !important; +} + +/* While dragging, let pointer events pass through the playground iframe so the + drag keeps tracking when the cursor moves over it (otherwise the iframe + swallows mousemove/mouseup and the resize stalls on the demo page). */ +html.ls-nav-dragging #wallet-demo-host, +html.ls-nav-dragging #wallet-demo-iframe { + pointer-events: none !important; +} + /* Collapse to the rail's width (not 0) so the rail reserves real layout space — content/iframe starts after it instead of being overlaid. */ html.ls-nav-collapsed #sidebar-content { @@ -4291,7 +4307,7 @@ html.ls-nav-collapsed #sidebar-content > * { position: fixed; top: 112px; /* below navbar: 64px bar + 48px tabs */ bottom: 0; - left: calc(var(--ls-sidebar-edge, 280px) - (var(--ls-nav-rail-hit) / 2)); + left: calc(var(--ls-sidebar-width, 280px) - (var(--ls-nav-rail-hit) / 2)); width: var(--ls-nav-rail-hit); z-index: 30; margin: 0; @@ -4328,6 +4344,8 @@ html.dark.ls-nav-collapsed .ls-nav-rail { display: grid; place-items: center; flex: none; + position: relative; + z-index: 1; /* keep the toggle above the edge highlight line */ width: 30px; height: 30px; border-radius: var(--ls-radius-md); @@ -4337,7 +4355,9 @@ html.dark.ls-nav-collapsed .ls-nav-rail { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); opacity: 0; transform: scale(0.92); - pointer-events: none; /* the rail handles the click */ + /* auto (not none) so the button itself is hoverable; clicks/drags still + bubble up to the rail, which owns the handlers. */ + pointer-events: auto; transition: opacity 150ms var(--ls-sidebar-ease), transform 150ms var(--ls-sidebar-ease), background-color 150ms ease, color 150ms ease; @@ -4350,14 +4370,29 @@ html.dark .ls-nav-rail-btn { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); } -.ls-nav-rail:hover .ls-nav-rail-btn { +/* Edge hover: reveal the button in its resting state (colors stay at base). */ +html:not(.ls-nav-collapsed) .ls-nav-rail:hover .ls-nav-rail-btn { opacity: 1; transform: scale(1); +} + +/* Button hover: the button's own active state. */ +html:not(.ls-nav-collapsed) .ls-nav-rail-btn:hover { color: var(--ls-gray-950); + background-color: var(--ls-gray-050); } -html.dark .ls-nav-rail:hover .ls-nav-rail-btn { +html.dark:not(.ls-nav-collapsed) .ls-nav-rail-btn:hover { color: var(--ls-gray-050); + background-color: var(--ls-gray-950); +} + +/* While the sidebar animates open from a click, suppress the hover reveal so a + cursor sitting over the rail mid-transition doesn't flash the button or edge + highlight — they reveal together on a genuine hover once it settles. */ +html.ls-nav-animating:not(.ls-nav-collapsed) .ls-nav-rail-btn, +html.ls-nav-animating:not(.ls-nav-collapsed) .ls-nav-rail::after { + opacity: 0 !important; } /* COLLAPSED toggle: just the icon — no fill/border/shadow — always visible, @@ -4399,6 +4434,37 @@ html.ls-nav-collapsed .ls-nav-rail-btn::before { mask-image: url("/images/icons/IconSidebarRightArrow.svg"); } +/* Hover/drag edge highlight: a 2px line at the divider, drawn as an overlay so + it never shifts layout (the real border stays 0.5px). Expanded only. */ +.ls-nav-rail::after { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 50%; + width: 2px; + transform: translateX(-50%); + background-color: var(--ls-gray-300); + opacity: 0; + pointer-events: none; + transition: opacity 150ms var(--ls-sidebar-ease); +} + +html.dark .ls-nav-rail::after { + background-color: var(--ls-gray-700); +} + +/* Delay the hover reveal so a brief hover — e.g. the cursor sitting over the + rail the instant it slides back out on expand — doesn't flash the highlight. */ +.ls-nav-rail:hover::after, +html.ls-nav-dragging .ls-nav-rail::after { + opacity: 1; +} + +html.ls-nav-collapsed .ls-nav-rail::after { + display: none; +} + /* Desktop only — mobile uses the hamburger nav (no #sidebar-content). */ @media (max-width: 1023px) { .ls-nav-rail { From fd363ff81a335370aee22ded0fe9280c4146fcad Mon Sep 17 00:00:00 2001 From: Pat Capulong Date: Sun, 21 Jun 2026 03:23:07 -0700 Subject: [PATCH 05/15] Cap the empty API panel height in the laptop stacked layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The empty (skeleton) API panel was only capped to one screenful at the mobile breakpoint; in the laptop stacked range it grew to the full intrinsic skeleton height — a tall empty void below the app. Apply the same one-screen cap across the whole stacked range (below $breakpoint-layout-wide). Co-authored-by: Cursor --- .../src/components/ApiPanel/ApiPanelEmpty.module.scss | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/components/grid-wallet-demo/src/components/ApiPanel/ApiPanelEmpty.module.scss b/components/grid-wallet-demo/src/components/ApiPanel/ApiPanelEmpty.module.scss index 1bf43b2f2..a84b608e7 100644 --- a/components/grid-wallet-demo/src/components/ApiPanel/ApiPanelEmpty.module.scss +++ b/components/grid-wallet-demo/src/components/ApiPanel/ApiPanelEmpty.module.scss @@ -121,9 +121,10 @@ font-feature-settings: 'salt' 1; } -/* Mobile (Playground): one screenful with the centered message, not the full - intrinsic skeleton height — so the API section isn't a tall empty void. */ -@media (max-width: $breakpoint-layout-mobile) { +/* Stacked layouts (mobile + laptop): cap to one screenful with the centered + message, not the full intrinsic skeleton height — so the API section isn't a + tall empty void below the app. */ +@media (max-width: ($breakpoint-layout-wide - 1px)) { .root { flex: none; height: calc(100dvh - 52px); From 2c51b7f796bdf7535c39acef72dc7994a97857d6 Mon Sep 17 00:00:00 2001 From: Pat Capulong Date: Sun, 21 Jun 2026 03:46:40 -0700 Subject: [PATCH 06/15] Hide the sidebar rail on custom-layout pages Custom-layout pages (frontmatter mode: "custom", e.g. the flow builder) keep #sidebar-content in the DOM but render it hidden, so the old "does #sidebar-content exist?" check still injected the rail. Gate injection on a sidebar that actually renders (getClientRects), and re-validate on each SPA mutation batch so the rail is dropped the moment a page turns out to have no sidebar. A :has(.is-custom) CSS rule hides it instantly as a no-flash guard during navigation. Co-authored-by: Cursor --- mintlify/sidebar-toggle.js | 30 ++++++++++++++++++++++++++---- mintlify/style.css | 8 ++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/mintlify/sidebar-toggle.js b/mintlify/sidebar-toggle.js index a94544ab4..4a1ac2bf3 100644 --- a/mintlify/sidebar-toggle.js +++ b/mintlify/sidebar-toggle.js @@ -28,6 +28,15 @@ return DEMO_PATHS.indexOf(location.pathname) !== -1; } + // #sidebar-content is in the DOM on every docs page, but custom-layout pages + // (frontmatter mode: "custom", e.g. the flow builder) keep it and hide it + // (display:none). getClientRects() is empty for a non-rendered element, so this + // is true only when there's a real sidebar to toggle — no sidebar, no rail. + function hasVisibleSidebar() { + var sc = document.getElementById('sidebar-content'); + return !!sc && sc.getClientRects().length > 0; + } + function getPref() { try { return localStorage.getItem(KEY); @@ -85,7 +94,7 @@ } function ensureRail() { - if (!isDesktop() || !document.getElementById('sidebar-content')) { + if (!isDesktop() || !hasVisibleSidebar()) { removeRail(); return; } @@ -181,14 +190,27 @@ sync(); } - // SPA navigation: Mintlify swaps content without a full reload. + // SPA navigation: Mintlify swaps content without a full reload. Re-validate on + // each mutation batch (rAF-coalesced) so the rail is re-added if Mintlify wipes + // it, and removed the instant a page turns out to have no sidebar (e.g. the + // custom-layout flow builder, whose sidebar renders hidden a tick after the + // path changes). var lastPath = location.pathname; + var ensureScheduled = false; + function scheduleEnsureRail() { + if (ensureScheduled) return; + ensureScheduled = true; + requestAnimationFrame(function () { + ensureScheduled = false; + ensureRail(); + }); + } var observer = new MutationObserver(function () { if (location.pathname !== lastPath) { lastPath = location.pathname; sync(); - } else if (!rail || !document.body.contains(rail)) { - ensureRail(); + } else { + scheduleEnsureRail(); } }); observer.observe(document.body, { childList: true, subtree: true }); diff --git a/mintlify/style.css b/mintlify/style.css index ece86a3e1..2d78ca377 100644 --- a/mintlify/style.css +++ b/mintlify/style.css @@ -4465,6 +4465,14 @@ html.ls-nav-collapsed .ls-nav-rail::after { display: none; } +/* Custom-layout pages (frontmatter mode: "custom", e.g. the flow builder) have + no docs sidebar — Mintlify tags them with .is-custom — so there's nothing to + toggle. The JS won't inject the rail there either; this is the no-flash guard + during SPA navigation. */ +html:has(.is-custom) .ls-nav-rail { + display: none !important; +} + /* Desktop only — mobile uses the hamburger nav (no #sidebar-content). */ @media (max-width: 1023px) { .ls-nav-rail { From 6dbaabe94876e19cd1476dabb5821b661c5d9c81 Mon Sep 17 00:00:00 2001 From: Pat Capulong Date: Sun, 21 Jun 2026 03:46:53 -0700 Subject: [PATCH 07/15] Polish the playground resize edge, min width, and stacking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Resize handle hover highlight is now a full-height line over the divider matching the docs sidebar edge (2px, gray-300/700), drawn above the sticky panel headers so it isn't clipped by the API calls header — replaces the floating grabber pill. - Min API column width is the configure width (475) so dragging never shrinks it below its default/snap width. - Stack app + API at 1440px (was 1200) so the phone isn't squeezed tiny in the 3-column layout before it stacks. Co-authored-by: Cursor --- .../ColumnResizeHandle.module.scss | 22 +++++++++++++------ .../src/hooks/useColumnResize.ts | 4 +++- .../src/styles/breakpoints.scss | 11 ++++++---- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/components/grid-wallet-demo/src/components/ColumnResizeHandle/ColumnResizeHandle.module.scss b/components/grid-wallet-demo/src/components/ColumnResizeHandle/ColumnResizeHandle.module.scss index 322d3d8a6..8dd6e157d 100644 --- a/components/grid-wallet-demo/src/components/ColumnResizeHandle/ColumnResizeHandle.module.scss +++ b/components/grid-wallet-demo/src/components/ColumnResizeHandle/ColumnResizeHandle.module.scss @@ -8,7 +8,9 @@ margin-right: -6px; cursor: col-resize; position: relative; - z-index: 1; + // Above the panel headers (sticky, z-index 3) so the divider + hover highlight + // run the full height instead of being clipped by the "API calls" header. + z-index: 4; &::before { content: ''; @@ -20,18 +22,24 @@ border-left: var(--stroke-xs) solid var(--border-primary); } + // Hover highlight: a full-height line over the divider, matching the Mintlify + // docs sidebar resize edge exactly (2px; gray-300 light / gray-700 dark — the + // same values as the docs' --ls-gray-300 / --ls-gray-700). &::after { content: ''; position: absolute; - top: 50%; + top: 0; + bottom: 0; left: 50%; - transform: translate(-50%, -50%); - width: 3px; - height: 32px; - border-radius: var(--corner-radius-2xs); - background: var(--border-secondary); + transform: translateX(-50%); + width: 2px; + background: var(--color-gray-300); opacity: 0; transition: opacity 150ms ease; + + :global([data-theme='dark']) & { + background: var(--color-gray-700); + } } &:hover::after, diff --git a/components/grid-wallet-demo/src/hooks/useColumnResize.ts b/components/grid-wallet-demo/src/hooks/useColumnResize.ts index 2b0f53e86..32dcdc39d 100644 --- a/components/grid-wallet-demo/src/hooks/useColumnResize.ts +++ b/components/grid-wallet-demo/src/hooks/useColumnResize.ts @@ -4,7 +4,9 @@ import { useCallback, useLayoutEffect, useRef, useState } from 'react'; const CONFIGURE_WIDTH = 475; const MIN_APP = 320; -const MIN_API = 320; +// Never resize the API column below its default/snap width (= the configure +// column width); dragging only widens it from there. +const MIN_API = CONFIGURE_WIDTH; const SNAP_THRESHOLD = 28; /** Default + snap target — matches configure column width. */ diff --git a/components/grid-wallet-demo/src/styles/breakpoints.scss b/components/grid-wallet-demo/src/styles/breakpoints.scss index 6c10df0b5..603a6df49 100644 --- a/components/grid-wallet-demo/src/styles/breakpoints.scss +++ b/components/grid-wallet-demo/src/styles/breakpoints.scss @@ -1,7 +1,10 @@ -/** App + API side-by-side inside the right column. Lowered from 1800px so the - embedded playground keeps app + API side by side at typical docs widths (the - docs sidebar is now collapsible, freeing horizontal space). Tune to taste. */ -$breakpoint-layout-wide: 1200px; +/** App + API side-by-side inside the right column. Above this the layout is 3 + columns: Configure (475) | App (phone) | API (475 default), so the App column + is roughly (viewport − 950). The phone needs ~466px (434 shell + 16px inset + each side) to render full size, i.e. ~1416px of viewport; below that the App + column squeezes the phone tiny. Stack at 1440px so the phone never shrinks + before App + API stack vertically (each full-width). Tune to taste. */ +$breakpoint-layout-wide: 1440px; /** Configure stacks above app + API (phone). */ $breakpoint-layout-mobile: 767px; From e39fe5476b7d4f506b374aba6ac10cfe38b081a4 Mon Sep 17 00:00:00 2001 From: Pat Capulong Date: Sun, 21 Jun 2026 11:44:15 -0700 Subject: [PATCH 08/15] Only animate the sidebar collapse on a deliberate toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The collapse transition was always on, so a collapsed sidebar mounting on a freshly-loaded page (e.g. arriving from the sidebar-less flow builder) faded its nav content in from expanded over 260ms — a flash of nav content inside the rail. Gate the width/opacity transitions behind .ls-nav-animating, which the click handler sets (with a reflow) only for a real toggle; navigation and first paint now render the final collapsed state instantly. sync() clears the flag so a leftover toggle never bleeds into a navigation. Co-authored-by: Cursor --- mintlify/sidebar-toggle.js | 15 +++++++++++---- mintlify/style.css | 20 +++++++++++++++----- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/mintlify/sidebar-toggle.js b/mintlify/sidebar-toggle.js index 4a1ac2bf3..9e80846e6 100644 --- a/mintlify/sidebar-toggle.js +++ b/mintlify/sidebar-toggle.js @@ -167,11 +167,14 @@ if (Date.now() - dragEndAt < 300) return; // swallow the click after a drag var next = !isCollapsed(); setPref(next ? '1' : '0'); - applyState(next); - // Suppress the hover reveal during the open/close animation so the button - // and edge highlight don't flash from the cursor sitting over the rail - // mid-transition; they reveal together on a real hover once settled. + // ls-nav-animating turns the collapse transition on for this deliberate + // toggle (it's off by default so navigation never animates) and suppresses + // the hover reveal so the button/edge don't flash from the cursor sitting + // over the rail mid-transition. Add it + force a reflow before applyState + // so the width/opacity change animates from the current value, not snaps. document.documentElement.classList.add('ls-nav-animating'); + void document.documentElement.offsetWidth; + applyState(next); clearTimeout(animTimer); animTimer = setTimeout(function () { document.documentElement.classList.remove('ls-nav-animating'); @@ -180,6 +183,10 @@ } function sync() { + // Navigation/first paint must never animate (only deliberate toggles do — + // see the click handler). Clearing any leftover flag keeps a collapsed + // sidebar from fading in on a freshly-loaded page. + document.documentElement.classList.remove('ls-nav-animating'); applyState(shouldCollapse()); ensureRail(); } diff --git a/mintlify/style.css b/mintlify/style.css index 2d78ca377..6d1dcc66f 100644 --- a/mintlify/style.css +++ b/mintlify/style.css @@ -4252,13 +4252,10 @@ html.dark:has(#wallet-demo-container) #navbar button[class*="h-14"][class*="text --ls-nav-rail-w: 48px; /* collapsed: visible left rail */ } -/* Animate the column width. The inner nav is pinned to the expanded width and - clipped (overflow-x) so it wipes cleanly instead of squishing/reflowing. */ +/* The inner nav is pinned to the expanded width and clipped (overflow-x) so it + wipes cleanly instead of squishing/reflowing. */ #sidebar-content { overflow-x: hidden !important; - transition: width var(--ls-sidebar-dur) var(--ls-sidebar-ease), - min-width var(--ls-sidebar-dur) var(--ls-sidebar-ease), - max-width var(--ls-sidebar-dur) var(--ls-sidebar-ease) !important; } /* Pin children to the expanded width so they clip (not squish/reflow) as the @@ -4268,6 +4265,19 @@ html.dark:has(#wallet-demo-container) #navbar button[class*="h-14"][class*="text #sidebar-content > * { width: var(--ls-sidebar-width, 280px) !important; box-sizing: border-box !important; +} + +/* Animate the wipe ONLY on a deliberate toggle (ls-nav-animating is set by the + click handler). Off by default, so navigation/first paint render the final + state instantly — a collapsed sidebar landing on a new page never fades in + from expanded, which is what flashed nav content inside the rail. */ +html.ls-nav-animating #sidebar-content { + transition: width var(--ls-sidebar-dur) var(--ls-sidebar-ease), + min-width var(--ls-sidebar-dur) var(--ls-sidebar-ease), + max-width var(--ls-sidebar-dur) var(--ls-sidebar-ease) !important; +} + +html.ls-nav-animating #sidebar-content > * { transition: opacity var(--ls-sidebar-dur) var(--ls-sidebar-ease); } From 00b8b2b6a8b90cdf29f7336484fc7449daf32c1b Mon Sep 17 00:00:00 2001 From: Pat Capulong Date: Sun, 21 Jun 2026 11:50:10 -0700 Subject: [PATCH 09/15] Always start the playground with the sidebar collapsed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The playground (global-accounts/demo) needs the horizontal space, so it should be collapsed regardless of the saved preference — previously a stored "expanded" preference kept it open. shouldCollapse() now returns true for the demo before consulting the pref, and the pre-paint script in docs.json collapses on the demo path regardless of the stored value so it's collapsed on first paint too. Every other page still follows the saved preference. Co-authored-by: Cursor --- mintlify/docs.json | 2 +- mintlify/sidebar-toggle.js | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/mintlify/docs.json b/mintlify/docs.json index 6e80f397c..478528995 100644 --- a/mintlify/docs.json +++ b/mintlify/docs.json @@ -446,7 +446,7 @@ }, "footer": {}, "head": { - "raw": "", + "raw": "", "links": [ { "rel": "preload", diff --git a/mintlify/sidebar-toggle.js b/mintlify/sidebar-toggle.js index 9e80846e6..90188cf3c 100644 --- a/mintlify/sidebar-toggle.js +++ b/mintlify/sidebar-toggle.js @@ -63,13 +63,12 @@ document.documentElement.style.setProperty('--ls-sidebar-width', w + 'px'); } - // Remembered preference wins everywhere; otherwise the playground defaults to - // collapsed and every other page defaults to expanded. + // The playground (demo) always starts collapsed — it needs the horizontal + // space — regardless of the saved preference. Every other page follows the + // remembered preference (default expanded). function shouldCollapse() { - var pref = getPref(); - if (pref === '1') return true; - if (pref === '0') return false; - return isDemo(); + if (isDemo()) return true; + return getPref() === '1'; } function isCollapsed() { From 83249b818467cdef612b0b3efab25de4dca20213 Mon Sep 17 00:00:00 2001 From: Pat Capulong Date: Sun, 21 Jun 2026 11:50:10 -0700 Subject: [PATCH 10/15] Snap the sidebar rail instantly on navigation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rail kept an always-on transition (left/width/background-color), so arriving on a page with a different collapse state — e.g. the always-collapsed playground from a page with the sidebar open — slid the rail across instead of showing it already in place. Gate the rail transition behind .ls-nav-animating like the column, so it only animates on a deliberate toggle, never on navigation. Co-authored-by: Cursor --- mintlify/style.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mintlify/style.css b/mintlify/style.css index 6d1dcc66f..3dea68f87 100644 --- a/mintlify/style.css +++ b/mintlify/style.css @@ -4330,6 +4330,13 @@ html.ls-nav-collapsed #sidebar-content > * { justify-content: center; -webkit-appearance: none; appearance: none; +} + +/* Like the column, the rail only animates its slide on a deliberate toggle + (ls-nav-animating). On navigation it snaps straight to the collapsed/expanded + position instead of sliding across — so landing on the playground from a page + with the sidebar open collapses instantly, with no stray animation. */ +html.ls-nav-animating .ls-nav-rail { transition: left var(--ls-sidebar-dur) var(--ls-sidebar-ease), width var(--ls-sidebar-dur) var(--ls-sidebar-ease), background-color var(--ls-sidebar-dur) var(--ls-sidebar-ease); From 69492e8c1d9113f797c539521065d0a500c8e2e4 Mon Sep 17 00:00:00 2001 From: Pat Capulong Date: Sun, 21 Jun 2026 12:26:18 -0700 Subject: [PATCH 11/15] Address review: reduced-motion, rail snap, lighter observer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Respect prefers-reduced-motion: the collapse transitions are gated behind .ls-nav-animating, which out-specified the old reduced-motion override, so it still animated. Point the override at those selectors — reduced-motion users get an instant toggle again. - Snap the rail button (no opacity fade) during navigation so its icon doesn't ghost in/out between pages; the hover reveal keeps its transition. - MutationObserver only re-adds the rail when it's actually missing instead of reading layout every frame; custom-layout pages stay covered by sync() on navigation plus the :has(.is-custom) CSS rule. - Lint-clean reflow: getBoundingClientRect() instead of `void offsetWidth`. - Note that the demo path is mirrored in the docs.json pre-paint script (it must run inline before this file, so the two are kept in sync by hand). Co-authored-by: Cursor --- mintlify/sidebar-toggle.js | 29 +++++++++++++++++++---------- mintlify/style.css | 17 ++++++++++++++--- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/mintlify/sidebar-toggle.js b/mintlify/sidebar-toggle.js index 90188cf3c..a48c758c1 100644 --- a/mintlify/sidebar-toggle.js +++ b/mintlify/sidebar-toggle.js @@ -12,6 +12,9 @@ (function () { var DESKTOP_MIN = 1024; var KEY = 'ls-nav-collapsed'; + // NOTE: keep in sync with the demo-path check in docs.json head.raw — the + // pre-paint script collapses the playground before this runs (no flash). The + // path lives in both because the pre-paint must run inline, before this file. var DEMO_PATHS = ['/global-accounts/demo', '/global-accounts/demo/']; var MIN_WIDTH = 280; // the original sidebar width — only resizes wider var MAX_WIDTH = 420; @@ -172,7 +175,7 @@ // over the rail mid-transition. Add it + force a reflow before applyState // so the width/opacity change animates from the current value, not snaps. document.documentElement.classList.add('ls-nav-animating'); - void document.documentElement.offsetWidth; + document.documentElement.getBoundingClientRect(); // force reflow to arm the transition applyState(next); clearTimeout(animTimer); animTimer = setTimeout(function () { @@ -182,12 +185,18 @@ } function sync() { + var root = document.documentElement; // Navigation/first paint must never animate (only deliberate toggles do — - // see the click handler). Clearing any leftover flag keeps a collapsed - // sidebar from fading in on a freshly-loaded page. - document.documentElement.classList.remove('ls-nav-animating'); + // see the click handler). Clear the animate flag, and snap the rail button + // for this navigation-driven state change so its icon doesn't ghost in/out + // between pages; restore its transition next frame so hover reveals animate. + root.classList.remove('ls-nav-animating'); + root.classList.add('ls-nav-snap'); applyState(shouldCollapse()); ensureRail(); + requestAnimationFrame(function () { + root.classList.remove('ls-nav-snap'); + }); } if (document.readyState === 'loading') { @@ -196,11 +205,11 @@ sync(); } - // SPA navigation: Mintlify swaps content without a full reload. Re-validate on - // each mutation batch (rAF-coalesced) so the rail is re-added if Mintlify wipes - // it, and removed the instant a page turns out to have no sidebar (e.g. the - // custom-layout flow builder, whose sidebar renders hidden a tick after the - // path changes). + // SPA navigation: Mintlify swaps content without a full reload. On a path + // change, re-sync. Otherwise only re-add the rail if Mintlify wiped it — a + // cheap guard so we don't read layout every frame. Custom-layout pages are + // handled by sync() on navigation plus the CSS :has(.is-custom) rule, so the + // rail never lingers visibly even without per-frame polling here. var lastPath = location.pathname; var ensureScheduled = false; function scheduleEnsureRail() { @@ -215,7 +224,7 @@ if (location.pathname !== lastPath) { lastPath = location.pathname; sync(); - } else { + } else if (!rail || !document.body.contains(rail)) { scheduleEnsureRail(); } }); diff --git a/mintlify/style.css b/mintlify/style.css index 3dea68f87..4e498b17b 100644 --- a/mintlify/style.css +++ b/mintlify/style.css @@ -4387,6 +4387,13 @@ html.dark .ls-nav-rail-btn { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); } +/* Navigation snaps the button (no opacity fade) so its icon doesn't ghost in/out + between pages as the collapse state changes; sync() adds this for the state + change and drops it next frame, leaving the hover reveal still animated. */ +html.ls-nav-snap .ls-nav-rail-btn { + transition: none !important; +} + /* Edge hover: reveal the button in its resting state (colors stay at base). */ html:not(.ls-nav-collapsed) .ls-nav-rail:hover .ls-nav-rail-btn { opacity: 1; @@ -4498,9 +4505,13 @@ html:has(.is-custom) .ls-nav-rail { } @media (prefers-reduced-motion: reduce) { - #sidebar-content, - .ls-nav-rail, - .ls-nav-rail-btn { + /* Match the gated selectors' specificity (html.ls-nav-animating …) so these + actually win and the toggle is instant for reduced-motion users. */ + html.ls-nav-animating #sidebar-content, + html.ls-nav-animating #sidebar-content > *, + html.ls-nav-animating .ls-nav-rail, + .ls-nav-rail-btn, + .ls-nav-rail::after { transition: none !important; } } From 61a433061ed025cf650e6969e4ea846440fb7a80 Mon Sep 17 00:00:00 2001 From: Pat Capulong Date: Sun, 21 Jun 2026 12:29:25 -0700 Subject: [PATCH 12/15] Ignore the pat-notes.md scratch file Local-only personal notes; keeps it out of git (and stops review tooling from trying to read it). Co-authored-by: Cursor --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e802a4b96..2635e08ae 100644 --- a/.gitignore +++ b/.gitignore @@ -50,8 +50,9 @@ figma-*.md # Cursor IDE rules (personal config) .cursor/rules/ -# Personal todo files +# Personal todo + notes files TODO-*.md +pat-notes.md # Icon build script (local tool, requires license key) scripts/export-icons.js From ca2d841830618c47ea0b59ce97dc2fa1f046829c Mon Sep 17 00:00:00 2001 From: Pat Capulong Date: Sun, 21 Jun 2026 14:38:09 -0700 Subject: [PATCH 13/15] Animate the sidebar wipe-open when leaving the playground MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Leaving the playground (collapsed -> expanded) was snapping the sidebar open, which read as a hard "pop": the nav appeared before the page content settled, and the swapped-in nav items showed instantly (brand-new elements don't transition). Make that one navigation animate — wipe the column open (ls-nav-animating) and fade the whole content container in via a keyframe (ls-nav-revealing, which plays regardless of element freshness). Entering the playground and page-to-page navigation still snap instantly. Co-authored-by: Cursor --- mintlify/sidebar-toggle.js | 42 ++++++++++++++++++++++++++++++-------- mintlify/style.css | 17 +++++++++++++++ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/mintlify/sidebar-toggle.js b/mintlify/sidebar-toggle.js index a48c758c1..5f539a800 100644 --- a/mintlify/sidebar-toggle.js +++ b/mintlify/sidebar-toggle.js @@ -22,6 +22,7 @@ var DRAG_THRESHOLD = 4; // px of movement before a press counts as a drag var rail = null; + var navAnimTimer = 0; function isDesktop() { return window.innerWidth >= DESKTOP_MIN; @@ -184,21 +185,46 @@ }); } - function sync() { + // Apply a navigation-driven state change without animating: snap the rail + // button (no opacity fade) so its icon doesn't ghost between pages, then + // restore its transition next frame so hover reveals still animate. + function snapState(collapsed) { var root = document.documentElement; - // Navigation/first paint must never animate (only deliberate toggles do — - // see the click handler). Clear the animate flag, and snap the rail button - // for this navigation-driven state change so its icon doesn't ghost in/out - // between pages; restore its transition next frame so hover reveals animate. - root.classList.remove('ls-nav-animating'); root.classList.add('ls-nav-snap'); - applyState(shouldCollapse()); - ensureRail(); + applyState(collapsed); requestAnimationFrame(function () { root.classList.remove('ls-nav-snap'); }); } + function sync() { + var root = document.documentElement; + var collapsed = shouldCollapse(); + // Leaving the playground (collapsed -> expanded) is the ONE navigation that + // animates: wipe the sidebar open so it reads as a smooth transition rather + // than a hard "pop" as the new page swaps in. Everything else (entering the + // playground, page-to-page) snaps instantly, per earlier feedback. + if (isCollapsed() && !collapsed) { + // The nav was just swapped to the new page's items (fresh elements), so the + // child opacity transition won't fire — fade the whole content in via a + // keyframe (ls-nav-revealing) alongside the width wipe (ls-nav-animating). + root.classList.add('ls-nav-animating'); + root.classList.add('ls-nav-revealing'); + root.getBoundingClientRect(); // reflow so the width wipe animates + applyState(false); + clearTimeout(navAnimTimer); + navAnimTimer = setTimeout(function () { + root.classList.remove('ls-nav-animating'); + root.classList.remove('ls-nav-revealing'); + }, 360); + } else { + root.classList.remove('ls-nav-animating'); + root.classList.remove('ls-nav-revealing'); + snapState(collapsed); + } + ensureRail(); + } + if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', sync); } else { diff --git a/mintlify/style.css b/mintlify/style.css index 4e498b17b..0ae4804fe 100644 --- a/mintlify/style.css +++ b/mintlify/style.css @@ -4281,6 +4281,23 @@ html.ls-nav-animating #sidebar-content > * { transition: opacity var(--ls-sidebar-dur) var(--ls-sidebar-ease); } +/* Leaving the playground swaps in the new page's nav — brand-new child elements + that wouldn't transition on their own — so fade the whole content container in + (a keyframe animation plays regardless of element freshness) alongside the + width wipe. */ +@keyframes ls-nav-reveal { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +html.ls-nav-revealing #sidebar-content { + animation: ls-nav-reveal var(--ls-sidebar-dur) var(--ls-sidebar-ease); +} + /* During an active drag, kill transitions so width tracks the cursor 1:1. */ html.ls-nav-dragging #sidebar-content, html.ls-nav-dragging #sidebar-content > *, From b4b94eaa6252a305343c0ff2831ac94e69866149 Mon Sep 17 00:00:00 2001 From: Pat Capulong Date: Sun, 21 Jun 2026 14:41:40 -0700 Subject: [PATCH 14/15] Revert "Animate the sidebar wipe-open when leaving the playground" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit ca2d841. The wipe-open + content fade flashed white on staging — the content container fading from opacity 0 reveals the page background behind it — and didn't read as smooth. Back to the instant snap; the brief "pop" when leaving the playground is acceptable. Co-authored-by: Cursor --- mintlify/sidebar-toggle.js | 42 ++++++++------------------------------ mintlify/style.css | 17 --------------- 2 files changed, 8 insertions(+), 51 deletions(-) diff --git a/mintlify/sidebar-toggle.js b/mintlify/sidebar-toggle.js index 5f539a800..a48c758c1 100644 --- a/mintlify/sidebar-toggle.js +++ b/mintlify/sidebar-toggle.js @@ -22,7 +22,6 @@ var DRAG_THRESHOLD = 4; // px of movement before a press counts as a drag var rail = null; - var navAnimTimer = 0; function isDesktop() { return window.innerWidth >= DESKTOP_MIN; @@ -185,46 +184,21 @@ }); } - // Apply a navigation-driven state change without animating: snap the rail - // button (no opacity fade) so its icon doesn't ghost between pages, then - // restore its transition next frame so hover reveals still animate. - function snapState(collapsed) { + function sync() { var root = document.documentElement; + // Navigation/first paint must never animate (only deliberate toggles do — + // see the click handler). Clear the animate flag, and snap the rail button + // for this navigation-driven state change so its icon doesn't ghost in/out + // between pages; restore its transition next frame so hover reveals animate. + root.classList.remove('ls-nav-animating'); root.classList.add('ls-nav-snap'); - applyState(collapsed); + applyState(shouldCollapse()); + ensureRail(); requestAnimationFrame(function () { root.classList.remove('ls-nav-snap'); }); } - function sync() { - var root = document.documentElement; - var collapsed = shouldCollapse(); - // Leaving the playground (collapsed -> expanded) is the ONE navigation that - // animates: wipe the sidebar open so it reads as a smooth transition rather - // than a hard "pop" as the new page swaps in. Everything else (entering the - // playground, page-to-page) snaps instantly, per earlier feedback. - if (isCollapsed() && !collapsed) { - // The nav was just swapped to the new page's items (fresh elements), so the - // child opacity transition won't fire — fade the whole content in via a - // keyframe (ls-nav-revealing) alongside the width wipe (ls-nav-animating). - root.classList.add('ls-nav-animating'); - root.classList.add('ls-nav-revealing'); - root.getBoundingClientRect(); // reflow so the width wipe animates - applyState(false); - clearTimeout(navAnimTimer); - navAnimTimer = setTimeout(function () { - root.classList.remove('ls-nav-animating'); - root.classList.remove('ls-nav-revealing'); - }, 360); - } else { - root.classList.remove('ls-nav-animating'); - root.classList.remove('ls-nav-revealing'); - snapState(collapsed); - } - ensureRail(); - } - if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', sync); } else { diff --git a/mintlify/style.css b/mintlify/style.css index 0ae4804fe..4e498b17b 100644 --- a/mintlify/style.css +++ b/mintlify/style.css @@ -4281,23 +4281,6 @@ html.ls-nav-animating #sidebar-content > * { transition: opacity var(--ls-sidebar-dur) var(--ls-sidebar-ease); } -/* Leaving the playground swaps in the new page's nav — brand-new child elements - that wouldn't transition on their own — so fade the whole content container in - (a keyframe animation plays regardless of element freshness) alongside the - width wipe. */ -@keyframes ls-nav-reveal { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -html.ls-nav-revealing #sidebar-content { - animation: ls-nav-reveal var(--ls-sidebar-dur) var(--ls-sidebar-ease); -} - /* During an active drag, kill transitions so width tracks the cursor 1:1. */ html.ls-nav-dragging #sidebar-content, html.ls-nav-dragging #sidebar-content > *, From d4651f4ca10db05e1211d0704bfbbddd732cd18c Mon Sep 17 00:00:00 2001 From: Pat Capulong Date: Sun, 21 Jun 2026 14:50:03 -0700 Subject: [PATCH 15/15] Keep navbar link text at medium weight on the hosted build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hosted Mintlify build wraps each navbar link's text in a and applies a body-weight rule to descendants, so the text rendered at 400 even though the anchor itself was 500 (the local CLI build, 4.2.284, doesn't add that rule — which is why this only showed on staging). Target the inner elements too so GitHub / Book a live demo / Dashboard stay medium (500). Co-authored-by: Cursor --- mintlify/style.css | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mintlify/style.css b/mintlify/style.css index 4e498b17b..8b3fbe9ac 100644 --- a/mintlify/style.css +++ b/mintlify/style.css @@ -277,9 +277,13 @@ h1#page-title, text-shadow: none !important; } -/* Navbar links: Medium weight (500) — avoids font flash during SPA navigation - since Medium is preloaded via next/font/local in docs.json */ +/* Navbar links (GitHub, Book a live demo, Dashboard): Medium weight (500). The + hosted Mintlify build wraps the link text in a and applies a body-weight + rule to descendants, which drops the text back to 400 even though the anchor is + 500 — so target the inner elements too, not just the anchor. (Medium is + preloaded via next/font/local in docs.json to avoid a flash on SPA nav.) */ .navbar-link a, +.navbar-link a *, #navbar a.link:not(.nav-tabs-item) { font-weight: 500 !important; }