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 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); 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 f13e6a64a..603a6df49 100644 --- a/components/grid-wallet-demo/src/styles/breakpoints.scss +++ b/components/grid-wallet-demo/src/styles/breakpoints.scss @@ -1,5 +1,10 @@ -/** App + API side-by-side inside the right column (large desktop). */ -$breakpoint-layout-wide: 1800px; +/** 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; diff --git a/mintlify/docs.json b/mintlify/docs.json index cdfb7a9e7..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/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 new file mode 100644 index 000000000..a48c758c1 --- /dev/null +++ b/mintlify/sidebar-toggle.js @@ -0,0 +1,243 @@ +// Collapsible + resizable docs sidebar (desktop). +// +// 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'; + // 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; + 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 rail = null; + + function isDesktop() { + return window.innerWidth >= DESKTOP_MIN; + } + + function isDemo() { + 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); + } catch (e) { + return null; + } + } + + function setPref(value) { + try { + localStorage.setItem(KEY, value); + } catch (e) { + /* 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'); + } + + // 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() { + if (isDemo()) return true; + return getPref() === '1'; + } + + function isCollapsed() { + return document.documentElement.classList.contains('ls-nav-collapsed'); + } + + function updateRail() { + if (!rail) return; + var collapsed = isCollapsed(); + 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); + updateRail(); + } + + function removeRail() { + if (rail && rail.parentNode) rail.parentNode.removeChild(rail); + rail = null; + } + + function ensureRail() { + if (!isDesktop() || !hasVisibleSidebar()) { + removeRail(); + 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); + }); + + el.addEventListener('click', function () { + if (Date.now() - dragEndAt < 300) return; // swallow the click after a drag + var next = !isCollapsed(); + setPref(next ? '1' : '0'); + // 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'); + document.documentElement.getBoundingClientRect(); // force reflow to arm the transition + applyState(next); + clearTimeout(animTimer); + animTimer = setTimeout(function () { + document.documentElement.classList.remove('ls-nav-animating'); + }, 320); + }); + } + + 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(shouldCollapse()); + ensureRail(); + requestAnimationFrame(function () { + root.classList.remove('ls-nav-snap'); + }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', sync); + } else { + sync(); + } + + // 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() { + 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)) { + scheduleEnsureRail(); + } + }); + 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; + ensureRail(); + }); + }); +})(); diff --git a/mintlify/style.css b/mintlify/style.css index 9ec1c9a96..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; } @@ -780,9 +784,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 { @@ -4233,3 +4237,286 @@ html.dark:has(#wallet-demo-container) #navbar button[class*="h-14"][class*="text color: var(--ls-gray-700); } +/* =========================================== + Collapsible sidebar (desktop) + 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-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 */ +} + +/* 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; +} + +/* 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: 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); +} + +/* 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 { + 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; +} + +/* 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: 112px; /* below navbar: 64px bar + 48px tabs */ + bottom: 0; + left: calc(var(--ls-sidebar-width, 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; + -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); +} + +/* 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-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; + position: relative; + z-index: 1; /* keep the toggle above the edge highlight line */ + 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); + /* 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; +} + +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: 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; + 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: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, + 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-collapsed .ls-nav-rail:hover .ls-nav-rail-btn { + color: var(--ls-gray-050); +} + +.ls-nav-rail-btn::before { + content: ""; + width: 20px; + height: 20px; + background-color: currentColor; + -webkit-mask: url("/images/icons/IconSidebarLeftArrow.svg") center / 20px 20px + no-repeat; + mask: url("/images/icons/IconSidebarLeftArrow.svg") center / 20px 20px + no-repeat; +} + +html.ls-nav-collapsed .ls-nav-rail-btn::before { + -webkit-mask-image: url("/images/icons/IconSidebarRightArrow.svg"); + 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; +} + +/* 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 { + display: none !important; + } +} + +@media (prefers-reduced-motion: reduce) { + /* 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; + } +} +