Files
sim/apps/docs/app/global.css
T
Waleed 3ff91f0439 improvement(docs): clean up leftovers from the code-block alignment PR (#6825)
* improvement(docs): clear leftovers from the reverted revisions

A cleanup pass over the final state. Every finding was residue from an approach
this PR tried and abandoned, or a claim that stopped being true when it did.

- Delete the copy-button svg sizing rule: a later rule sets `display: none` on
  that same element ungated, so sizing it was never observable. Superseded by
  the mask approach.
- Drop the paragraph in page.tsx arguing about a custom Shiki factory. The
  factory was deleted; nothing configures one now.
- Correct shiki-curl-json.ts, which still claimed the grammar "reaches the
  client path too". It does not — that was the justification for choosing a
  grammar over a transformer, so leaving it stated the opposite of the truth.
  Now records where it applies, where it does not, and why not to retry.
- Correct the global.css section header, which claimed the component owns the
  shell while the next rule defines it here.
- Qualify the `--copy-glyph` declarations with `:has(> svg[class*="lucide"])`,
  which the group's own comment asserts of every rule in it.
- Correct `getCode`'s TSDoc: the gutter is a `::before`, and pseudo-element
  content never reaches `textContent`, so line numbers were never what the
  clone guards. It guards transformer-emitted `.nd-copy-ignore` nodes.
- Compose `chipGeometryClass` and emcn's `ChipChevronDown` in the API example
  selector instead of restating their literals.
- Merge the duplicated `div[role="region"]` rule. The tablist pair stays split:
  biome's `noDuplicateProperties` reads a nested `@variant` setting the same
  property as a duplicate and fails the build — recorded so it is not remerged.
- Note that fumadocs ships its own gutter for `lines`-meta fences, which cannot
  be suppressed from here and would paint a second column.

* fix(docs): drop a highlighter registration that can never fire

fumadocs-openapi calls `renderCodeBlock` with a hard-coded `"json"` from both of
its call sites (`request-tabs.js:76`, `response-tabs.js:48`), so the docs
`CodeBlock` it routes through never receives a shell language. The
`getHighlighter('js', { langs: [curlJsonBodyGrammar] })` registering the
shell-scoped JSON-body injection therefore did nothing but await on every API
sample render, and the docblock claiming the grammar covers those samples was
wrong.

- Delete the call and its imports.
- State the grammar's real coverage: prose fences only, via `langs`. Both API
  reference paths are unreachable — samples are JSON, and the cURL usage tabs
  highlight client-side off fumadocs' own factory.
- Correct `code-block.tsx`'s TSDoc, which still said API samples come from
  fumadocs' own renderer. They come through this component; `UsageTab` is the
  renderer that bypasses it.
- Re-home a comment orphaned when two CSS rules merged — it had drifted onto
  the rule below and read as documenting it.
- Drop a `.nd-copy-ignore` claim about transformers emitting those nodes;
  nothing here does, and upstream parity is the reason the clone exists.
2026-08-18 15:28:02 -07:00

2019 lines
70 KiB
CSS

@import "tailwindcss";
@import "fumadocs-ui/css/neutral.css";
@import "fumadocs-ui/css/preset.css";
@import "fumadocs-openapi/css/preset.css";
@source "../../../packages/emcn/src";
@source "../../../packages/workflow-renderer/src";
/* Every @sim/emcn component expresses hover through `hover-hover:` so touch
devices never latch a sticky hover state. The app registers it as a plugin
variant in apps/sim/tailwind.config.ts; docs is CSS-first Tailwind v4 with no
config, so without this declaration the variant compiles to nothing and every
emcn hover state silently no-ops here. */
@custom-variant hover-hover {
@media (hover: hover) and (pointer: fine) {
&:hover {
@slot;
}
}
}
/* Prevent overscroll bounce effect on the page */
html,
body {
overscroll-behavior: none;
}
/* Prevent modals/dialogs from shifting layout via scroll-lock compensation */
html,
body {
padding-right: 0 !important;
margin-right: 0 !important;
}
@theme {
--color-fd-primary: var(--color-fd-foreground);
/* Sim's custom type scale — emcn components (Label, Badge, the shared block
views) use these names, so they must resolve here or text falls back to the
inherited size. Mirrors apps/sim/tailwind.config.ts. */
--text-micro: 10px;
--text-xs: 11px;
--text-caption: 12px;
--text-small: 13px;
--text-base: 15px;
--text-md: 16px;
/* Code-token size for the API reference — a deliberate sixth step, between
--text-caption and --text-small, because the mono face reads small at 12px. */
--text-code: 0.78125rem;
/* The platform's code face. `apps/sim/tailwind.config.ts` lists `var(--font-martian-mono)`
first, but nothing in the app ever defines that variable — `apps/sim/app/layout.tsx`
applies only `season.variable` — so every code surface in the product resolves to the
system stack below. Docs match what the app actually renders rather than the token it
nominally references; loading a webfont here would make docs the odd one out, not the
aligned one. If the app ever wires that font up for real, add the var back in both
places at once. */
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
"Courier New", monospace;
}
/* Pure white light mode background */
:root:not(.dark) {
--color-fd-background: hsl(0, 0%, 100%) !important;
}
/* Match landing page dark background (#1b1b1b) */
.dark {
--color-fd-background: hsl(0, 0%, 10.6%) !important;
--color-fd-card: hsl(0, 0%, 13%) !important;
--color-fd-popover: hsl(0, 0%, 14%) !important;
--color-fd-secondary: hsl(0, 0%, 15.5%) !important;
--color-fd-muted: hsl(0, 0%, 18%) !important;
}
/*
* emcn workspace tokens — mirrored from apps/sim/app/_styles/globals.css so the
* docs chip chrome (components/ui/chip.tsx, dropdown-menu.tsx, badges) renders
* identically to the platform. Keep values in sync with the main app.
*/
:root {
--bg: #fefefe;
--workflow-edge: #e0e0e0;
--surface-1: #fbfbfb;
--surface-2: #ffffff;
--surface-3: #f7f7f7;
--surface-4: #f5f5f5;
--surface-5: #f3f3f3;
--surface-6: #e5e5e5;
--surface-7: #d9d9d9;
--surface-active: #ececec;
--surface-hover: #f2f2f2;
--border: #d8d8d8;
/** Legacy neutral-border aliases. New work should use --border. */
--border-1: var(--border);
--text-primary: #1a1a1a;
--text-secondary: #525252;
--text-tertiary: #5c5c5c;
--text-body: #434343;
--text-muted: #7a7a7a;
--text-subtle: #8c8c8c;
--text-icon: #5a5a5a;
--text-icon-muted: #5c5c5c;
--text-inverse: #ffffff;
--text-error: #ef4444;
--text-muted-inverse: #a0a0a0;
--brand-accent: #33c482;
--brand-accent-hover: #2dac72;
--brand-secondary: #33b4ff;
--surface-inverted: #1b1b1b;
--surface-inverted-hover: #363636;
--border-inverted: #363636;
--border-muted: var(--border);
--badge-success-bg: #bbf7d0;
--badge-success-text: #15803d;
/* Shared-component tokens: referenced by @sim/emcn and @sim/workflow-renderer.
Undefined here, a var() silently falls back to currentColor. Values mirror
apps/sim/app/_styles/globals.css. */
--border-success: #e0e0e0;
--success: #22c55e;
--error: #dc2626;
--warning: #ea580c;
--caution: #f59e0b;
--text-placeholder: #8d8d8d;
--badge-teal-bg: #99f6e4;
--badge-teal-text: #0f766e;
--badge-cyan-bg: #cffafe;
--badge-cyan-text: #0891b2;
--badge-pink-bg: #fbcfe8;
--badge-pink-text: #be185d;
--badge-blue-secondary-bg: #bae6fd;
--badge-blue-secondary-text: #0369a1;
--badge-blue-bg: #bfdbfe;
--badge-blue-text: #1d4ed8;
--badge-purple-bg: #e9d5ff;
--badge-purple-text: #7c3aed;
--badge-amber-bg: #fde68a;
--badge-amber-text: #a16207;
--badge-orange-bg: #fed7aa;
--badge-orange-text: #c2410c;
--badge-error-bg: #fecaca;
--badge-error-text: #dc2626;
--badge-gray-bg: #e7e5e4;
--badge-gray-text: #57534e;
--code-bg: #f5f5f5;
--code-foreground: #1a1a1a;
--code-line-number: #737373;
--selection-bg: #add6ff;
--selection-dark: #264f78;
--highlight-search-active: #f6ad55;
--scrollbar-thumb-color: #c0c0c0;
--shadow-subtle: 0 2px 4px 0 rgba(0, 0, 0, 0.08);
--shadow-medium: 0 4px 12px rgba(0, 0, 0, 0.1);
--shadow-overlay: 0 10px 30px rgba(0, 0, 0, 0.11);
--shadow-card: 0 1px 3px rgba(0, 0, 0, 0.04);
/* z-index scale — poppers sit above the modal so menus stay clickable over
the modal overlay. Mirrors apps/sim/app/_styles/globals.css. */
--z-toast: 150;
--z-modal: 200;
--z-popover: 300;
--z-tooltip: 400;
}
.dark {
--bg: #1b1b1b;
--workflow-edge: #454545;
--surface-1: #1e1e1e;
--surface-2: #232323;
--surface-3: #242424;
--surface-4: #292929;
--surface-5: #363636;
--surface-6: #454545;
--surface-7: #505050;
--surface-active: #2c2c2c;
--surface-hover: #262626;
--border: #444444;
/** Legacy neutral-border aliases. New work should use --border. */
--border-1: var(--border);
--text-primary: #e6e6e6;
--text-secondary: #cccccc;
--text-tertiary: #b3b3b3;
--text-body: #c1c1c1;
--text-muted: #6e6e6e;
--text-subtle: #7d7d7d;
--text-icon: #969696;
--text-icon-muted: #949494;
--text-inverse: #1b1b1b;
--text-error: #ef4444;
--text-muted-inverse: #b3b3b3;
--brand-accent: #33c482;
--brand-accent-hover: #2dac72;
--brand-secondary: #33b4ff;
--surface-inverted: #242424;
--surface-inverted-hover: #363636;
--border-inverted: #3d3d3d;
--border-muted: var(--border);
--badge-success-bg: rgba(34, 197, 94, 0.2);
--badge-success-text: #86efac;
/* Shared-component tokens: referenced by @sim/emcn and @sim/workflow-renderer.
Undefined here, a var() silently falls back to currentColor. Values mirror
apps/sim/app/_styles/globals.css. */
--border-success: #575757;
--success: #22c55e;
--error: #f87171;
--warning: #ff6600;
--caution: #f59e0b;
--text-placeholder: #8d8d8d;
--badge-teal-bg: rgba(20, 184, 166, 0.2);
--badge-teal-text: #5eead4;
--badge-cyan-bg: rgba(14, 165, 233, 0.2);
--badge-cyan-text: #7dd3fc;
--badge-pink-bg: rgba(236, 72, 153, 0.2);
--badge-pink-text: #f9a8d4;
--badge-blue-secondary-bg: rgba(51, 180, 255, 0.2);
--badge-blue-secondary-text: #7dd3fc;
--badge-blue-bg: rgba(59, 130, 246, 0.2);
--badge-blue-text: #93c5fd;
--badge-purple-bg: rgba(168, 85, 247, 0.2);
--badge-purple-text: #d8b4fe;
--badge-amber-bg: rgba(245, 158, 11, 0.2);
--badge-amber-text: #fcd34d;
--badge-orange-bg: rgba(249, 115, 22, 0.2);
--badge-orange-text: #fdba74;
--badge-error-bg: #551a1a;
--badge-error-text: #fca5a5;
--badge-gray-bg: #3a3a3a;
--badge-gray-text: #a8a8a8;
--code-bg: #1f1f1f;
--code-foreground: #eeeeee;
--code-line-number: #a8a8a8;
--selection-bg: #264f78;
--scrollbar-thumb-color: #5a5a5a;
--shadow-overlay: 0 10px 30px rgba(0, 0, 0, 0.3);
}
/* Scrollbars — platform thumb tokens, transparent track. A non-auto
`scrollbar-width`/`scrollbar-color` makes Chromium ignore every
`::-webkit-scrollbar*` rule on the element, so no webkit block here. Hover
shading is not expressible through the standard properties. */
* {
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-thumb-color) transparent;
}
/* Font family utilities */
.font-sans {
font-family: var(--font-geist-sans), ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
/* Platform UI font — Season Sans, used by the chip chrome to match the main app */
.font-season {
font-family: var(--font-season), system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial,
"Noto Sans", sans-serif;
}
:root {
--fd-border-sidebar: transparent !important;
--fd-nav-height: 92px; /* Custom navbar height (52px top + 40px tabs) */
/* Content container width used to center main content */
--spacing-fd-container: 1400px;
/* Edge gutter = leftover space on each side of centered container */
--edge-gutter: max(1rem, calc((100vw - var(--spacing-fd-container)) / 2));
/* How far sidebar/TOC shift inward from the edge gutter */
--sidebar-shift: 50px;
--sidebar-offset: max(0px, calc(var(--edge-gutter) - var(--sidebar-shift)));
--toc-shift: 50px;
--toc-offset: max(0px, calc(var(--edge-gutter) - var(--toc-shift)));
/* Inner padding for navbar and sidebar — single source of truth */
--nav-inset: 44px;
/* Extra gap between sidebar/TOC and the main text content */
--content-gap: 2.25rem;
}
/* Light mode navbar background */
:root:not(.dark) nav {
background-color: hsla(0, 0%, 100%, 0.85) !important;
}
/* Dark mode navbar background */
:root.dark nav {
background-color: hsla(0, 0%, 10.6%, 0.92) !important;
}
/* Floating sidebar appearance - remove background */
[data-sidebar-container],
#nd-sidebar {
background: transparent !important;
border: none !important;
--color-fd-muted: transparent !important;
--color-fd-card: transparent !important;
--color-fd-secondary: transparent !important;
}
aside[data-sidebar],
aside#nd-sidebar {
background: transparent !important;
border: none !important;
}
/* Remove the fade-out mask on sidebar scroll viewport (Fumadocs applies inline maskImage) */
[data-sidebar-placeholder] [data-radix-scroll-area-viewport],
#nd-sidebar [data-radix-scroll-area-viewport],
#nd-sidebar [data-radix-scroll-area-viewport] > div,
aside#nd-sidebar [data-radix-scroll-area-viewport] {
mask: none !important;
mask-image: none !important;
-webkit-mask: none !important;
-webkit-mask-image: none !important;
}
/* Fumadocs v16: Add sidebar placeholder styling for grid area */
[data-sidebar-placeholder] {
background: transparent !important;
}
/* Fumadocs v16: Hide sidebar panel (floating collapse button) */
[data-sidebar-panel] {
display: none !important;
}
/* Mobile only: Reduce gap between navbar and content (custom navbar hidden on mobile) */
@media (max-width: 1023px) {
#nd-docs-layout {
margin-top: -25px;
}
}
/* Hide TOC popover on tablet/medium screens (768px - 1279px) */
/* Keeps it visible on mobile (<768px) for easy navigation */
/* Desktop (>=1280px) already hides it via fumadocs xl:hidden */
@media (min-width: 768px) and (max-width: 1279px) {
#nd-docs-layout {
--fd-toc-popover-height: 0px !important;
}
[data-toc-popover] {
display: none !important;
}
}
/* Desktop only: Apply custom navbar offset, sidebar width and margin offsets */
/* On mobile, let fumadocs handle the layout natively */
@media (min-width: 1024px) {
:root {
--fd-banner-height: 92px !important; /* 52px top + 40px tabs */
}
#nd-docs-layout {
/* Use 100dvh so sidebar height = 100dvh - 92px (banner) = full viewport below navbar */
--fd-docs-height: 100dvh !important;
--fd-sidebar-width: 300px !important;
margin-left: var(--sidebar-offset) !important;
margin-right: var(--toc-offset) !important;
min-height: var(--fd-docs-height) !important;
}
/* Pin the sidebar to the viewport instead of letting fumadocs' `sticky` do it.
A sticky box is bottom-limited by its containing block, and #nd-docs-layout
ends ~660px above the document bottom because the site footer is a sibling
of the layout, not a grid child. So across the whole footer the sidebar gets
pushed upward — and any content-height change while the reader is in that
zone (expanding an FAQ row, say) makes it visibly jump. A fixed box ignores
both the container's end and the document's height, so neither happens.
Safe because the grid columns are explicit (`0px 300px 1fr 268px 0px`), so
removing the placeholder from flow leaves its track intact. `left`/`width`
are restated because a fixed box no longer derives them from its grid cell,
and `height` already comes from fumadocs' own utility classes.
Anchoring to `bottom` rather than `top` is what keeps the footer off it: the
offset is how far the footer currently reaches into the viewport (published
by `FooterOverlapProbe`), so the sidebar keeps its full height and slides up
out of view as the footer arrives, the way it did before it was pinned. With
no footer on screen the offset is 0 and this resolves back to top: 92px. */
[data-sidebar-placeholder] {
position: fixed !important;
left: var(--sidebar-offset);
width: var(--fd-sidebar-width);
top: auto !important;
bottom: var(--docs-footer-overlap, 0px) !important;
}
/* Sidebar divider line — pinned for the same reason, and so it stays glued to
the sidebar's right edge. Being fixed takes it out of #nd-docs-layout's grid
entirely, so it needs no grid placement and cannot skew a content cell; its
position comes from `left`/`top`/`bottom` alone. Unlike the sidebar it is
shortened rather than slid, so it runs from the navbar down to the footer's
top border and the two meet instead of the line stopping short. */
#nd-docs-layout::before {
content: "";
display: block;
position: fixed;
top: 92px; /* below navbar */
bottom: var(--docs-footer-overlap, 0px);
left: calc(var(--sidebar-offset) + var(--fd-sidebar-width));
width: 1px;
background-color: var(--surface-active);
pointer-events: none;
z-index: 21;
}
/* Hide fumadocs nav on desktop - we use custom navbar there */
#nd-docs-layout > header {
display: none !important;
}
}
/* Sidebar spacing — left padding aligns item text with navbar content (item has 8px internal padding) */
[data-sidebar-viewport],
#nd-sidebar > div {
padding: 0 14px 0 calc(var(--nav-inset) - 8px) !important;
background: transparent !important;
}
/* Override Fumadocs p-4 on scroll viewport — must beat Tailwind v4 utility layer specificity */
html #nd-sidebar [data-radix-scroll-area-viewport],
html #nd-sidebar [data-radix-scroll-area-viewport].p-4,
html aside#nd-sidebar [data-radix-scroll-area-viewport] {
padding: 0 !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
/* Top/bottom breathing room inside the scrollable sidebar content — scrollable, not clipped */
html #nd-sidebar [data-radix-scroll-area-viewport] > div {
padding-top: 20px !important;
padding-bottom: 16px !important;
}
/* Override sidebar item styling to match Raindrop */
/* Target Link and button elements in sidebar - override Fumadocs itemVariants */
/* Exclude the small chevron-only toggle buttons */
/* Using html prefix for higher specificity over Tailwind v4 utilities */
/* NEVER set `display` here. This selector scores (1,2,2) and carries
`!important`, so it out-specifies every rule below that hides the language
selector, theme toggle, and search button — a `display` declaration here
forces all of them back into the sidebar. The 30px chip height therefore
comes from line-height + padding (20 + 5 + 5), not from flex centering. */
html #nd-sidebar a:not(:has(span.font-mono)),
html #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
font-size: 0.875rem !important; /* 14px to match navbar items */
line-height: 20px !important;
padding: 5px 0.5rem !important; /* 30px tall overall — the app's chip pill, at its px-2 */
font-weight: 400 !important;
border-radius: 0.5rem !important; /* platform rounded-lg */
font-family: var(--font-geist-sans), ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
}
/* Sidebar text — platform --text-body */
html #nd-sidebar a:not(:has(span.font-mono)),
html #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
color: var(--text-body) !important;
}
/* Chevron icons — platform --text-icon */
#nd-sidebar svg {
display: inline-block !important;
opacity: 1 !important;
color: var(--text-icon) !important;
flex-shrink: 0 !important;
}
/* Ensure the small chevron toggle buttons are visible */
#nd-sidebar button[aria-label*="ollapse"],
#nd-sidebar button[aria-label*="xpand"] {
display: flex !important;
opacity: 1 !important;
padding: 0.25rem !important;
}
/* Add tiny gap between nested items */
#nd-sidebar ul li {
margin-bottom: 0.0625rem !important;
}
#nd-sidebar ul li:last-child {
margin-bottom: 0 !important;
}
/* Section separator headers — match the app sidebar's group label: 12px,
sentence case, normal weight. Only the fumadocs defaults are overridden here;
size and color stay on the component. */
[data-sidebar-viewport] [data-separator] p,
#nd-sidebar [data-separator] p {
font-weight: 400 !important;
text-transform: none !important;
letter-spacing: normal !important;
margin: 0 !important;
padding: 0 !important;
}
/* Active state — aligned with platform --surface-active */
#nd-sidebar a[data-active="true"]:not(:has(span.font-mono)),
#nd-sidebar button[data-active="true"] {
background-image: none !important;
}
html #nd-sidebar a[data-active="true"]:not(:has(span.font-mono)),
html #nd-sidebar button[data-active="true"] {
background-color: var(--surface-active) !important;
color: var(--text-body) !important;
}
/* Hover state — emcn's two-surface row model (see `chipHoverSurfaceClass`).
These rules must carry !important to beat fumadocs' own sidebar styles, which
also means they override the Tailwind hover utilities on the items; keep the
two in step. */
html #nd-sidebar a:not(:has(span.font-mono)):hover:not([data-active="true"]),
html #nd-sidebar button:hover:not([data-active="true"]) {
background-color: var(--surface-hover) !important;
}
/* Hide search, platform, and collapse button from sidebar completely */
[data-sidebar] [data-search],
[data-sidebar] .search-toggle,
#nd-sidebar [data-search],
#nd-sidebar .search-toggle,
[data-sidebar-viewport] [data-search],
[data-sidebar-viewport] button[data-search],
aside[data-sidebar] [role="button"]:has([data-search]),
aside[data-sidebar] > div > button:first-child,
#nd-sidebar > div > button:first-child,
[data-sidebar] a[href*="sim.ai"],
#nd-sidebar a[href*="sim.ai"],
[data-sidebar-viewport] a[href*="sim.ai"],
/* Hide search buttons (but NOT folder chevron buttons) */
aside[data-sidebar] > div:first-child
> button:not([aria-label="Collapse"]):not([aria-label="Expand"]),
#nd-sidebar > div:first-child > button:not([aria-label="Collapse"]):not([aria-label="Expand"]),
/* Hide sidebar collapse button (panel icon) - direct children only */
aside[data-sidebar] > button:first-of-type:not([aria-label="Collapse"]):not([aria-label="Expand"]),
[data-sidebar]
> button[type="button"]:first-of-type:not([aria-label="Collapse"]):not([aria-label="Expand"]),
button[data-collapse]:not([aria-label="Collapse"]):not([aria-label="Expand"]),
[data-sidebar-header] button,
/* Hide theme toggle from sidebar footer */
aside[data-sidebar] [data-theme-toggle],
[data-sidebar-footer],
[data-sidebar] footer,
footer button[aria-label*="heme"],
aside[data-sidebar] > div:last-child:has(button[aria-label*="heme"]),
aside[data-sidebar] button[aria-label*="heme"],
[data-sidebar] button[aria-label*="Theme"],
/* Additional theme toggle selectors */
aside[data-sidebar] > *:last-child
button,
[data-sidebar-viewport] ~ *,
aside[data-sidebar] > div:not([data-sidebar-viewport]),
/* Aggressive theme toggle hiding */
aside[data-sidebar] svg[class*="sun"],
aside[data-sidebar] svg[class*="moon"],
aside[data-sidebar] button[type="button"]:last-child,
aside button:has(svg:only-child),
[data-sidebar] div:has(> button[type="button"]:only-child:last-child),
/* Hide theme toggle and other non-content elements */
aside[data-sidebar] > *:not([data-sidebar-viewport]) {
display: none !important;
}
/* Desktop only: Hide sidebar toggle buttons and nav title/logo (keep visible on mobile) */
@media (min-width: 1025px) {
[data-sidebar-container] > button,
[data-sidebar-container] [data-toggle],
aside[data-sidebar] [data-sidebar-toggle],
button[data-sidebar-toggle],
nav button[data-sidebar-toggle],
button[aria-label="Toggle Sidebar"],
button[aria-label="Collapse Sidebar"],
/* Hide nav title/logo in sidebar on desktop - target all possible locations */
/* Lower specificity selectors first (attribute selectors) */
[data-sidebar-header],
[data-sidebar] [data-title],
aside[data-sidebar] a[href="/"],
aside[data-sidebar] a[href="/"] img,
aside[data-sidebar] > a:first-child,
aside[data-sidebar] > div > a:first-child,
aside[data-sidebar] img[alt="Sim"],
aside[data-sidebar] svg[aria-label="Sim"],
/* Higher specificity selectors (ID selectors) */
#nd-sidebar
a[href="/"],
#nd-sidebar a[href="/"] img,
#nd-sidebar a[href="/"] svg,
#nd-sidebar > a:first-child,
#nd-sidebar > div:first-child > a:first-child,
#nd-sidebar img[alt="Sim"],
#nd-sidebar svg[aria-label="Sim"],
/* Hide theme toggle at bottom of sidebar on desktop */
#nd-sidebar
> footer,
#nd-sidebar footer,
aside#nd-sidebar > *:last-child:not(div),
#nd-sidebar > button:last-child,
#nd-sidebar button[aria-label*="theme" i],
#nd-sidebar button[aria-label*="Theme"],
#nd-sidebar > div:last-child > button {
display: none !important;
}
}
/* Extra aggressive - hide everything after the viewport */
aside[data-sidebar] [data-sidebar-viewport] ~ * {
display: none !important;
}
/* Tighter spacing for sidebar content */
[data-sidebar-viewport] > * {
margin-bottom: 0.0625rem;
}
[data-sidebar-viewport] > *:last-child {
margin-bottom: 0;
}
[data-sidebar-viewport] ul {
margin: 0;
padding: 0;
}
/* Ensure sidebar starts with content immediately */
aside[data-sidebar] > div:first-child {
padding-top: 0;
}
/* Remove all sidebar borders and backgrounds (except the right divider) */
[data-sidebar-container],
aside[data-sidebar],
[data-sidebar],
[data-sidebar] *,
#nd-sidebar,
#nd-sidebar * {
border: none !important;
}
/* Sidebar border is handled via #nd-docs-layout::before pseudo-element (see desktop section below) */
/* Override fumadocs background colors for sidebar */
.dark #nd-sidebar,
.dark [data-sidebar-container],
.dark aside[data-sidebar] {
--color-fd-muted: transparent !important;
--color-fd-secondary: transparent !important;
background: transparent !important;
}
/* Force normal text flow in sidebar */
[data-sidebar],
[data-sidebar] *,
[data-sidebar-viewport],
[data-sidebar-viewport] * {
writing-mode: horizontal-tb !important;
}
code,
pre,
pre code {
font-family: var(--font-mono);
}
/* Inline code chip, matching the one the app renders for markdown
(`apps/sim/lib/content/mdx.tsx`).
Two things here are deliberate rather than oversights. The border is gone because the app's
chip has none, and at inline size a hairline reads as a boxed-in label rather than a
highlight. The color is unset so the chip composites over whatever encloses it — inline
code inside a link picks up the link color instead of sitting on it as a grey island, which
is the behavior the app's chip was written for. */
:not(pre) > code {
padding: 0.125rem 0.375rem;
border-radius: 4px;
font-size: 0.875em;
font-weight: 400;
background-color: var(--surface-5);
}
/* Search dialog — lighter shadow + constrained size */
[data-radix-popper-content-wrapper] > div,
[role="dialog"][data-state] {
box-shadow: var(--shadow-medium) !important;
}
[role="dialog"][data-state] {
max-width: 480px !important;
}
/* Search dialog overlay + panel must cover the sticky navbar — both default to z-50,
and the navbar wins the tie by DOM order, leaving it unblurred above the overlay */
.bg-fd-overlay,
[role="dialog"][data-state] {
z-index: 60 !important;
}
/* Code metrics — the platform's `Code.Viewer` runs 13px on a 21px line box
(`text-small` / `leading-[21px]`); identical snippets should not read larger here. */
pre {
font-size: var(--text-small);
line-height: 21px;
tab-size: 2;
-webkit-overflow-scrolling: touch;
}
pre code {
display: block;
width: fit-content;
min-width: 100%;
}
/* Syntax highlighting adjustments for better readability */
pre code .line {
padding-left: 0;
padding-right: 0;
}
/* Content typography — align heading sizes and weights with platform */
main article h1,
[data-content] h1,
#nd-page h1 {
font-size: 1.5rem !important;
font-weight: 550 !important;
letter-spacing: -0.02em;
color: var(--text-primary);
}
main article h2,
[data-content] h2,
#nd-page h2 {
font-size: 1.25rem !important;
font-weight: 500 !important;
letter-spacing: -0.015em;
color: var(--text-primary);
}
main article h3,
main article h4,
[data-content] h3,
[data-content] h4,
#nd-page h3,
#nd-page h4 {
font-size: var(--text-md) !important;
font-weight: 470 !important;
letter-spacing: -0.01em;
color: var(--text-body);
}
/* Body text — platform --text-secondary */
main article p,
[data-content] p,
[data-docs-page] p {
color: var(--text-secondary);
}
/* Muted text — platform --text-muted */
main article .text-muted-foreground,
[data-content] .text-muted-foreground,
[data-docs-page] .text-muted-foreground {
color: var(--text-muted);
}
/* List items — inherit body text color */
main article li,
[data-content] li,
[data-docs-page] li {
color: var(--text-secondary);
}
/* Custom text highlighting styles */
.text-highlight {
color: var(--color-fd-primary);
}
/* Override marker color for highlighted lists */
.highlight-markers li::marker {
color: var(--color-fd-primary);
}
/* Remove the thin border-left on nested TOC items (keeps main indicator only) */
#nd-toc a[style*="padding-inline-start"] {
border-left: none !important;
}
/* The clerk TOC draws its active range twice: a track segment, plus a dot that
animates along the track to the current heading. The dot is redundant with the
segment and reads as a stray artifact, so hide it and keep the segment. It is
the only node fumadocs gives an inline offset-path, which is what anchors it. */
#nd-toc [style*="offset-path"] {
display: none !important;
}
/* TOC heading — align weight with landing */
#nd-toc h3,
#nd-toc [class*="title"] {
font-weight: 480 !important;
font-size: var(--text-small) !important;
color: var(--text-muted);
}
/* TOC links — softer colors aligned with landing muted text */
#nd-toc a {
font-weight: 430 !important;
font-size: var(--text-small) !important;
color: var(--text-muted);
transition: color 0.2s;
}
#nd-toc a:hover {
color: var(--text-body);
}
#nd-toc a[data-active="true"] {
color: var(--text-primary) !important;
font-weight: 470 !important;
}
/* Add bottom spacing to prevent abrupt page endings */
[data-content] {
padding-top: 1.5rem !important;
padding-bottom: 4rem;
}
/* Alternative fallback for different Fumadocs versions */
main article,
.docs-page main {
padding-top: 1.5rem !important;
padding-bottom: 4rem;
}
/* Main content area - center and constrain like turborepo/raindrop */
/* Note: --sidebar-offset and --toc-offset are now applied at #nd-docs-layout level */
main[data-main] {
max-width: var(--spacing-fd-container, 1400px);
margin-left: auto;
margin-right: auto;
padding-top: 1rem;
padding-left: var(--content-gap);
padding-right: var(--content-gap);
order: 1 !important;
}
/* Adjust for smaller screens */
@media (max-width: 768px) {
main[data-main] {
padding-left: 1rem;
padding-right: 1rem;
}
#nd-page:has(.api-page-header) {
padding-left: 1rem;
padding-right: 1rem;
}
}
/* Ensure docs page content is properly constrained */
[data-docs-page] {
max-width: 1400px;
margin-left: auto;
margin-right: auto;
padding-top: 1.5rem !important;
}
/* Remove any unwanted outlines from video elements */
video {
outline: none !important;
}
/* API Reference Pages — Mintlify-style overrides */
/* OpenAPI pages: span main + TOC grid columns for wide two-column layout.
Use named grid lines from grid-template-areas so this works regardless
of whether the grid has 3 columns (production) or 5 columns (local dev). */
#nd-page:has(.api-page-header) {
grid-column: main-start / toc-end !important;
max-width: 1400px !important;
padding-left: calc(var(--content-gap) + 1rem);
padding-right: calc(var(--content-gap) + 1rem);
}
/* Hide the empty TOC aside on OpenAPI pages so it doesn't overlay content */
#nd-docs-layout:has(#nd-page:has(.api-page-header)) #nd-toc {
display: none;
}
/* Hide the default "Response Body" heading rendered by fumadocs-openapi */
.response-section-wrapper > .response-section-content > h2,
.response-section-wrapper > .response-section-content > h3 {
display: none !important;
}
/* Hide default accordion triggers (status code rows) — we show our own dropdown */
.response-section-wrapper [data-orientation="vertical"] > [data-state] > h3 {
display: none !important;
}
/* Ensure API reference pages use the same font as the rest of the docs.
`.font-mono` is excluded: this selector (id + element) outranks the
`.font-mono` class rule, so without it every code identifier renders sans. */
#nd-page:has(.api-page-header),
#nd-page:has(.api-page-header) h2,
#nd-page:has(.api-page-header) h3,
#nd-page:has(.api-page-header) h4,
#nd-page:has(.api-page-header) p:not(.font-mono),
#nd-page:has(.api-page-header) span:not(.font-mono),
#nd-page:has(.api-page-header) div:not(.font-mono),
#nd-page:has(.api-page-header) label:not(.font-mono),
#nd-page:has(.api-page-header) button:not(.font-mono) {
font-family: var(--font-geist-sans), ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
/* Method badge pills — shared background colors (page + sidebar) */
span.font-mono.font-medium[data-method="get"],
span.font-mono.font-medium[data-method="head"],
span.font-mono.font-medium[data-method="options"] {
background-color: var(--badge-success-bg);
}
span.font-mono.font-medium[data-method="post"] {
background-color: var(--badge-blue-bg);
}
span.font-mono.font-medium[data-method="put"] {
background-color: var(--badge-amber-bg);
}
span.font-mono.font-medium[data-method="patch"] {
background-color: var(--badge-orange-bg);
}
span.font-mono.font-medium[data-method="delete"] {
background-color: var(--badge-error-bg);
}
/* Sidebar links with method badges — match regular item padding. Radius follows the
platform's two-tier convention (documented on emcn's `DropdownMenu`): floating surfaces
take 12px, rows inside one take 8px. These are rows. */
html #nd-sidebar a:has(span.font-mono.font-medium) {
display: flex !important;
align-items: center !important;
gap: 0.375rem;
padding: 0.375rem 0.875rem !important;
font-size: 0.875rem !important;
line-height: 1.4 !important;
font-weight: 450 !important;
border-radius: 0.5rem !important;
}
/* Sidebar method badges — fixed-width for right-aligned labels */
#nd-sidebar a span.font-mono.font-medium {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.625rem;
font-size: var(--text-micro) !important;
line-height: 1 !important;
padding: 0.15625rem 0.25rem;
border-radius: 0.375rem;
flex-shrink: 0;
}
/* Footer navigation method badges — pill styling to match sidebar */
#nd-page span.font-mono.font-medium[data-method] {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: var(--text-micro) !important;
line-height: 1 !important;
padding: 0.15625rem 0.375rem;
border-radius: 0.375rem;
}
/* API page code/card containers — transparent background */
#nd-page:has(.api-page-header) {
--color-fd-card: transparent !important;
}
/* API page code example panels — transparent */
#nd-page:has(.api-page-header) .rounded-xl.border.bg-fd-card {
background-color: transparent !important;
}
#nd-page:has(.api-page-header) .rounded-xl.border.shadow-md.bg-fd-card {
background-color: transparent !important;
box-shadow: none !important;
}
/* Hide "Filter Properties" search bar everywhere — main page and popovers */
input[placeholder="Filter Properties"] {
display: none !important;
}
div:has(> input[placeholder="Filter Properties"]) {
display: none !important;
}
/* Remove top border on first visible property after hidden Filter Properties */
div:has(> input[placeholder="Filter Properties"]) + .text-sm.border-t {
border-top: none !important;
}
/* Hide "TypeScript Definitions" copy panel on API pages */
#nd-page:has(.api-page-header) div.not-prose.rounded-xl.border.p-3.mb-4 {
display: none !important;
}
#nd-page:has(.api-page-header) div.not-prose.rounded-xl.border.p-3:has(> div > p.font-medium) {
display: none !important;
}
/* Hide info tags (Format, Default, etc.) everywhere — main page and popovers */
div.flex.flex-row.gap-2.flex-wrap.not-prose:has(> div.bg-fd-secondary) {
display: none !important;
}
div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs {
display: none !important;
}
/* Method+path bar — cleaner, lighter styling like Gumloop.
Override bg-fd-card CSS variable directly for reliability. */
#nd-page:has(.api-page-header) div.flex.flex-row.items-center.rounded-xl.border.not-prose {
--color-fd-card: var(--surface-3) !important;
background-color: var(--surface-3) !important;
border-color: var(--border-1) !important;
}
/* Method badge inside path bar — cleaner sans-serif, softer colors */
#nd-page:has(.api-page-header)
div.flex.flex-row.items-center.rounded-xl.border.not-prose
span.font-mono.font-medium {
font-family: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif !important;
font-weight: 600 !important;
font-size: var(--text-xs) !important;
letter-spacing: 0.025em;
text-transform: uppercase;
padding: 0.125rem 0.5rem !important;
border-radius: 0.375rem !important;
}
/* Path bar per-method colors (fumadocs renders these, so we match by class) */
/* GET */
#nd-page:has(.api-page-header)
div.flex.flex-row.items-center.rounded-xl.border.not-prose
span.font-mono.font-medium[class*="text-green"] {
color: var(--badge-success-text) !important;
background-color: var(--badge-success-bg) !important;
}
/* POST */
#nd-page:has(.api-page-header)
div.flex.flex-row.items-center.rounded-xl.border.not-prose
span.font-mono.font-medium[class*="text-blue"] {
color: var(--badge-blue-text) !important;
background-color: var(--badge-blue-bg) !important;
}
/* PUT */
#nd-page:has(.api-page-header)
div.flex.flex-row.items-center.rounded-xl.border.not-prose
span.font-mono.font-medium[class*="text-yellow"] {
color: var(--badge-amber-text) !important;
background-color: var(--badge-amber-bg) !important;
}
/* PATCH */
#nd-page:has(.api-page-header)
div.flex.flex-row.items-center.rounded-xl.border.not-prose
span.font-mono.font-medium[class*="text-orange"] {
color: var(--badge-orange-text) !important;
background-color: var(--badge-orange-bg) !important;
}
/* DELETE */
#nd-page:has(.api-page-header)
div.flex.flex-row.items-center.rounded-xl.border.not-prose
span.font-mono.font-medium[class*="text-red"] {
color: var(--badge-error-text) !important;
background-color: var(--badge-error-bg) !important;
}
/* The path inside the method+path bar is a heading, not a chip — strip the inline-code
surface so it reads as the endpoint itself. */
#nd-page:has(.api-page-header) div.flex.flex-row.items-center.rounded-xl.border.not-prose code {
color: var(--text-body) !important;
background: none !important;
padding: 0 !important;
font-size: var(--text-small) !important;
}
/* Response Section — custom dropdown-based rendering (Mintlify style) */
/* Hide divider lines between accordion items */
.response-section-wrapper [data-orientation="vertical"].divide-y > * {
border-top-width: 0 !important;
border-bottom-width: 0 !important;
}
.response-section-wrapper [data-orientation="vertical"].divide-y {
border-top: none !important;
}
/* Remove content type labels inside accordion items (we show one in the header) */
.response-section-wrapper [data-orientation="vertical"] p.not-prose:has(code.text-xs) {
display: none !important;
}
/* Hide the top-level response description (e.g. "Execution was successfully cancelled.")
but NOT field descriptions inside Schema which also use prose-no-margin.
The response description is a direct child of AccordionContent (role=region) with mb-2. */
.response-section-wrapper [data-orientation="vertical"] [role="region"] > .prose-no-margin.mb-2,
.response-section-wrapper
[data-orientation="vertical"]
[role="region"]
> div
> .prose-no-margin.mb-2 {
display: none !important;
}
/* Remove left padding on accordion content so it aligns with Path Parameters */
.response-section-wrapper [data-orientation="vertical"] [role="region"] {
padding-inline-start: 0 !important;
}
/* Response section header */
.response-section-header {
display: flex;
align-items: center;
gap: 1rem;
margin-top: 1.75rem;
margin-bottom: 0.25rem;
}
.response-section-title {
font-size: 1.25rem;
font-weight: 500;
letter-spacing: -0.015em;
margin: 0;
color: var(--text-primary);
font-family: var(--font-geist-sans), ui-sans-serif, system-ui, -apple-system, sans-serif;
}
.response-section-meta {
display: flex;
align-items: center;
gap: 0.75rem;
margin-left: auto;
}
/* Status code dropdown */
.response-section-dropdown-wrapper {
position: relative;
}
/* API-reference metadata face — the status trigger, the content-type label, the
`required` / `header` markers, and the status-code tabs. Defined once; each
consumer below adds only its own colour, content, and order. The `code.text-xs`
label further down needs `!important` to beat fumadocs and stays separate. */
#nd-page:has(.api-page-header) button.response-section-dropdown-trigger,
.response-section-dropdown-trigger,
#nd-page:has(.api-page-header) span.response-section-content-type,
.response-section-content-type,
#nd-page:has(.api-page-header)
.flex.flex-wrap.items-center.gap-3.not-prose:has(span.text-red-400)::after,
#nd-page:has(.api-page-header) div.my-4 > .flex.flex-wrap.items-center.gap-3.not-prose::before,
#nd-page:has(.api-page-header) div.my-4 > .flex.flex-wrap.items-center.gap-3.not-prose::after,
#nd-page:has(.api-page-header) .flex.gap-3\.5.overflow-x-auto.not-prose > button {
font-size: var(--text-code);
line-height: 1.25rem;
font-weight: 400;
font-family: var(--font-mono);
}
/* Status-code trigger — matches the content-type label beside it. */
#nd-page:has(.api-page-header) button.response-section-dropdown-trigger,
.response-section-dropdown-trigger {
display: flex;
align-items: center;
gap: 0.25rem;
height: 1.25rem;
padding: 0 0.25rem;
color: var(--text-secondary);
background: none;
border: none;
cursor: pointer;
border-radius: 0.375rem;
transition: color 0.15s;
}
/* Carries the same id-qualified prefix as the base rule above; without it the
base rule outranks this one and the trigger never changes colour on hover. */
#nd-page:has(.api-page-header) button.response-section-dropdown-trigger:hover,
.response-section-dropdown-trigger:hover {
color: var(--text-primary);
}
.response-section-chevron {
width: 0.75rem;
height: 0.75rem;
transition: transform 0.15s;
}
.response-section-chevron-open {
transform: rotate(180deg);
}
.response-section-dropdown-menu {
position: absolute;
top: calc(100% + 0.25rem);
left: 0;
z-index: 50;
min-width: 5rem;
background-color: var(--bg);
border: 1px solid var(--border);
border-radius: 0.75rem;
box-shadow: var(--shadow-card);
padding: 0.375rem;
overflow: hidden;
}
.response-section-dropdown-item {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 0.375rem 0.5rem;
font-size: 0.875rem;
color: var(--text-body);
background: none;
border: none;
cursor: pointer;
border-radius: 0.5rem;
transition:
background-color 0.1s,
color 0.1s;
font-family: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif;
}
.response-section-dropdown-item:hover {
background-color: var(--surface-active);
color: var(--text-primary);
}
.response-section-dropdown-item-selected {
color: var(--text-primary);
}
.response-section-check {
width: 0.875rem;
height: 0.875rem;
}
/* Content-type label. The Response header renders this class; the Request Body
header renders a fumadocs `code.text-xs`. Keep the two in sync — the same
string at different weights reads as one being lighter than the other. */
#nd-page:has(.api-page-header) span.response-section-content-type,
.response-section-content-type {
color: var(--text-secondary);
background: none;
border: none;
padding: 0;
}
/* Response schema container — remove border to match Path Parameters style */
.response-section-wrapper [data-orientation="vertical"] .border.px-3.py-2.rounded-lg {
border: none !important;
padding: 0 !important;
border-radius: 0 !important;
background-color: transparent;
}
/* Property row — reorder: name (1) → type badge (2) → required badge (3) */
#nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose {
display: flex;
flex-wrap: wrap;
align-items: center;
}
/* Name span — order 1 */
#nd-page:has(.api-page-header)
.flex.flex-wrap.items-center.gap-3.not-prose
> span.font-medium.font-mono.text-fd-primary {
order: 1;
}
/* Type token — order 2. Covers every shape the slot takes: scalar span, union
wrapper, schema-reference button, and the auth row's `::after` label. Reuses
the docs inline-code recipe, so a type reads as code wherever it appears; the
explicit 20px height keeps a union level with a scalar, which its nested
links would otherwise push to 26px. */
#nd-page:has(.api-page-header)
.flex.flex-wrap.items-center.gap-3.not-prose
> span.text-sm.font-mono.text-fd-muted-foreground,
#nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > button,
#nd-page:has(.api-page-header) .flex.flex-wrap.items-center.gap-3.not-prose > span:has(> button),
#nd-page:has(.api-page-header)
div.my-4
> .flex.flex-wrap.items-center.gap-3.not-prose
> span.text-sm.font-mono.text-fd-muted-foreground::after {
order: 2;
display: inline-flex;
align-items: center;
height: 1.25rem;
/* No gap: an `array<T>` slot holds its brackets as bare text nodes, which
become anonymous flex items, so any gap here would prise `array<` and `>`
away from the type they wrap. The union separator spaces itself instead. */
gap: 0;
background-color: var(--surface-4);
border: 1px solid var(--border-1);
color: var(--text-body);
padding: 0 0.3125rem;
border-radius: 0.375rem;
font-size: var(--text-code);
line-height: 1;
font-weight: 400;
font-family: var(--font-mono);
}
/* Everything inside a type token inherits the token's own face, size, and ink.
Applied to every descendant, not just the links: a union's `|` separator is a
classless `span`, so the page-wide `span:not(.font-mono)` rule assigned it the
body sans face and one chip rendered in two faces. Anything fumadocs nests in
here later is covered by the same reset.
Underline is deferred to hover so links don't read heavier than a plain scalar
in the same box. The button that *is* the slot needs its own rule below: it
cannot `inherit`, which would pull the row's 14px sans back in. */
#nd-page:has(.api-page-header)
.flex.flex-wrap.items-center.gap-3.not-prose
> span.text-sm.font-mono.text-fd-muted-foreground
* {
text-decoration: none;
color: inherit;
font-size: inherit;
font-family: inherit;
}
#nd-page:has(.api-page-header)
.flex.flex-wrap.items-center.gap-3.not-prose
> button.text-sm.font-mono.text-fd-muted-foreground {
text-decoration: none;
}
#nd-page:has(.api-page-header)
.flex.flex-wrap.items-center.gap-3.not-prose
> span.text-sm.font-mono.text-fd-muted-foreground
:is(a, button):hover,
#nd-page:has(.api-page-header)
.flex.flex-wrap.items-center.gap-3.not-prose
> button.text-sm.font-mono.text-fd-muted-foreground:hover {
text-decoration: underline;
text-underline-offset: 2px;
}
/* Union separator — dimmed one step, no further: `string | null` started
reading as `string null` on the chip fill. Own margin; the slot has no gap. */
#nd-page:has(.api-page-header)
.flex.flex-wrap.items-center.gap-3.not-prose
> span.text-sm.font-mono.text-fd-muted-foreground
> span {
margin: 0 0.375rem;
color: var(--text-muted);
}
/* Hide the "*" inside the name span — we'll add "required" as a ::after on the flex row */
#nd-page:has(.api-page-header) span.font-medium.font-mono.text-fd-primary > span.text-red-400 {
display: none;
}
/* Required marker — order 3. Error text colour but no fill: eight required
params on one page should not read as eight alarms. */
#nd-page:has(.api-page-header)
.flex.flex-wrap.items-center.gap-3.not-prose:has(span.text-red-400)::after {
content: "required";
order: 3;
display: inline-flex;
align-items: center;
color: var(--badge-error-text);
}
/* Optional "?" indicator — hide it */
#nd-page:has(.api-page-header)
span.font-medium.font-mono.text-fd-primary
> span.text-fd-muted-foreground {
display: none;
}
/* Hide the auth scheme type label (e.g. "apiKey") next to Authorization heading */
#nd-page:has(.api-page-header) .flex.items-start.justify-between.gap-2 > div.not-prose {
display: none !important;
}
/* Auth property — replace "<token>" with "string" badge, add "header" and "required" badges.
Auth properties use my-4 (vs py-4 for regular properties). */
/* Auth property flex row — name: order 1, type: order 2, ::before "header": order 3, ::after "required": order 4 */
#nd-page:has(.api-page-header)
div.my-4
> .flex.flex-wrap.items-center.gap-3.not-prose
> span.font-medium.font-mono.text-fd-primary {
order: 1;
}
/* Auth rows collapse the real `<token>` text to zero and draw the chip in the
`::after` below, so this span is a bare wrapper: it must drop the type-token
box it matches, or the chip renders inside a second, empty bordered box. */
#nd-page:has(.api-page-header)
div.my-4
> .flex.flex-wrap.items-center.gap-3.not-prose
> span.text-sm.font-mono.text-fd-muted-foreground {
order: 2;
font-size: 0;
padding: 0;
background: none;
border: none;
height: auto;
line-height: 0;
}
/* Only the label — the box comes from the shared type-token rule above, which
this pseudo-element is a member of. */
#nd-page:has(.api-page-header)
div.my-4
> .flex.flex-wrap.items-center.gap-3.not-prose
> span.text-sm.font-mono.text-fd-muted-foreground::after {
content: "string";
}
/* "header" location via ::before on the auth flex row — uncontained metadata,
matching the `required` marker rather than the type token. */
#nd-page:has(.api-page-header) div.my-4 > .flex.flex-wrap.items-center.gap-3.not-prose::before {
content: "header";
order: 3;
display: inline-flex;
align-items: center;
color: var(--text-secondary);
}
/* "required" marker via ::after on the auth flex row */
#nd-page:has(.api-page-header) div.my-4 > .flex.flex-wrap.items-center.gap-3.not-prose::after {
content: "required";
order: 4;
display: inline-flex;
align-items: center;
color: var(--badge-error-text);
}
/* Hide the trailing "In: header" line — redundant with the header marker.
Matched by position, not shape: descriptions contain a `code` too (status
codes), so a bare `p:has(> code)` also hid the API-key description. */
#nd-page:has(.api-page-header) div.my-4 .prose-no-margin > p:last-child:has(> code) {
display: none !important;
}
/* Section dividers — bottom border after Authorization and Body sections. */
.api-section-divider {
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--surface-active);
}
/* Property rows — breathing room like Mintlify.
Regular properties use border-t py-4; auth properties use border-t my-4. */
#nd-page:has(.api-page-header) .text-sm.border-t.py-4 {
padding-top: 1.25rem !important;
padding-bottom: 1.25rem !important;
}
#nd-page:has(.api-page-header) .text-sm.border-t.my-4 {
margin-top: 1.25rem !important;
margin-bottom: 1.25rem !important;
padding-top: 1.25rem;
}
/* Divider lines between fields — very subtle like Mintlify */
#nd-page:has(.api-page-header) .text-sm.border-t {
border-color: var(--surface-active);
}
/* Body/Callback "application/json" label — strip inline-code chrome and keep in
sync with `.response-section-content-type`; same string, two headers. */
#nd-page:has(.api-page-header) .flex.gap-2.items-center.justify-between p.not-prose code.text-xs,
#nd-page:has(.api-page-header) .flex.justify-between.gap-2.items-end p.not-prose code.text-xs {
background: none !important;
border: none !important;
padding: 0 !important;
color: var(--text-secondary) !important;
font-size: var(--text-code) !important;
line-height: 1.25rem !important;
font-weight: 400 !important;
font-family: var(--font-mono) !important;
}
/* Section headings (Authorization, Path Parameters, etc.) — consistent top spacing */
#nd-page:has(.api-page-header) .min-w-0.flex-1 h2 {
margin-top: 1.75rem !important;
margin-bottom: 0.25rem !important;
}
/* Example-panel code overflows rather than wraps: a wrapped line restarts at
column zero and misreports the JSON nesting depth. */
/* fumadocs' own lucide glyphs (heading anchor, copy button) ship at stroke-width
2 while emcn strokes at 1.55, so they read heavier than everything near them.
Layout-wide on purpose: one icon weight across the docs. Retired once
createAPIPage is given renderHeading/renderCodeBlock. */
#nd-docs-layout svg[class*="lucide"] {
stroke-width: 1.55;
}
/* Callout/alert — transparent background, no shadow, hide colored bar, add padding */
div.rounded-xl.border.bg-fd-card.shadow-md:has(> [role="none"]) {
background-color: transparent !important;
box-shadow: none !important;
border-color: var(--border-1) !important;
padding-left: 0.875rem !important;
align-items: center !important;
}
div.rounded-xl.border.bg-fd-card.shadow-md > [role="none"] {
display: none !important;
}
/* Files component — transparent background */
div.not-prose.rounded-md.border.bg-fd-card.p-2 {
background-color: transparent !important;
border-color: var(--border-1) !important;
}
/* Callout icon — outline style, text color, vertically centered */
div.rounded-xl.border.bg-fd-card.shadow-md:has(> [role="none"]) > svg {
fill: none !important;
color: var(--color-fd-foreground) !important;
stroke-width: 1.55 !important;
flex-shrink: 0;
width: 1rem !important;
height: 1rem !important;
}
/* API page header — constrain title/copy-page to left content column, not full width.
Only applies on OpenAPI pages (which have the two-column layout). */
@media (min-width: 1280px) {
.api-page-header {
max-width: calc(100% - 400px - 1.5rem);
}
}
/* Footer navigation — constrain to left content column on OpenAPI pages only.
Target pages that contain the two-column layout via :has() selector. */
#nd-page:has(.api-page-header) > div:last-child {
max-width: calc(100% - 400px - 1.5rem);
}
@media (max-width: 1024px) {
#nd-page:has(.api-page-header) > div:last-child {
max-width: 100%;
}
}
/* ─── Content Components — aligned with platform design system ─── */
/* Heading anchor links — no underline, inherit color */
#nd-page h1 a,
#nd-page h2 a,
#nd-page h3 a,
#nd-page h4 a,
#nd-page h5 a,
#nd-page h6 a,
.prose h1 a,
.prose h2 a,
.prose h3 a,
.prose h4 a,
.prose h5 a,
.prose h6 a {
text-decoration: none !important;
color: inherit !important;
border: none !important;
}
/* Tables — clean divider-based style matching chat markdown renderer */
.prose table,
[data-docs-page] table,
[data-content] table,
main article table {
border-collapse: collapse !important;
border-spacing: 0 !important;
background: none !important;
border: none !important;
border-radius: 0 !important;
overflow: visible !important;
font-size: 0.875rem !important;
}
.prose thead th,
[data-docs-page] thead th,
[data-content] thead th,
main article thead th {
background: none !important;
font-weight: 600 !important;
padding: 0.5rem 0.75rem !important;
border-bottom: 1px solid var(--border) !important;
border-left: none !important;
border-right: none !important;
border-top: none !important;
border-inline-start: none !important;
color: var(--text-primary);
text-align: left;
white-space: nowrap;
line-height: 1.5;
}
.prose td,
[data-docs-page] td,
[data-content] td,
main article td {
padding: 0.5rem 0.75rem !important;
border-bottom: 1px solid var(--surface-active) !important;
border-left: none !important;
border-right: none !important;
border-top: none !important;
border-inline-start: none !important;
color: var(--text-secondary);
line-height: 1.5;
}
/* Remove bottom border on last row */
.prose tbody tr:last-child td,
[data-docs-page] tbody tr:last-child td,
[data-content] tbody tr:last-child td,
main article tbody tr:last-child td {
border-bottom: none !important;
}
/* Code blocks — platform field chrome.
Three renderers emit these figures and only two go through components/ui/code-block.tsx, so
the shell and the fumadocs internals that component cannot reach — the title row, the scroll
viewport, the tab strip `<Tabs>` puts above a fence — are all defined here. The component
keeps only the copy control and the prose margin. */
/* Local aliases, not new design values: each names an existing platform token pair whose light
and dark halves differ (`--surface-5`/`--code-bg`, `--text-muted`/`--code-line-number`).
`--code-surface` has three consumers. `--code-gutter` has one, and is an alias anyway because
a `.dark` twin would have to restate the sibling-combinator `:has()` its consumer is matched
by — the expensive form, which re-checks on every line insertion. */
:root {
--code-surface: var(--surface-5);
--code-gutter: var(--text-muted);
}
.dark {
--code-surface: var(--code-bg);
--code-gutter: var(--code-line-number);
}
/* Code block shell — `chipFieldSurfaceClass`'s `rounded-lg` and `--border-1` hairline, over
the `--surface-5` / `--code-bg` fill. (`--surface-5` is `chipFilledFillTokens`' light fill;
the dark half is the code surface emcn's `Code` container paints. The chip field uses
`--surface-4` on dark, which is not a code surface.)
Three renderers emit these figures: the MDX `pre` mapping and fumadocs-openapi's
`renderCodeBlock` both go through `components/ui/code-block.tsx`, but its `UsageTab` renders
`ClientCodeBlock` directly and takes no override. The shell could be a class on the
component for the first two, at the cost of defining it twice — once there and once here for
the third. One definition covering all three is the simpler seam.
Vertical margin is deliberately not set here: prose fences want `my-4` (the component
supplies it) and API samples sit flush inside their panel with `my-0`.
@see packages/emcn/src/components/chip/chip-chrome.ts — `chipFieldSurfaceClass`, the constant
this mirrors. Keep the two in step by hand. */
figure.shiki,
div:has(> [role="tablist"]):has(> div > figure.shiki) {
border-radius: 0.5rem;
border: 1px solid var(--border-1);
background-color: var(--code-surface);
overflow: hidden;
box-shadow: none;
}
/* Title row — the platform's `--surface-4`-over-`--surface-5` pairing, the same split
ChipModal uses between its outer frame and inner panel. */
figure.shiki > div:first-child:has(figcaption),
div:has(> div > figure.shiki) > [role="tablist"] {
height: 34px;
padding-inline: 0.625rem;
background-color: var(--surface-4);
border-bottom: 1px solid var(--border-1);
}
figure.shiki figcaption {
color: var(--text-muted);
font-family: var(--font-mono);
font-size: var(--text-caption);
}
/* fumadocs derives a file-type icon from the title's extension and leaves it at
`currentColor`; the platform paints row icons `--text-icon`. */
figure.shiki > div:first-child:has(figcaption) svg {
color: var(--text-icon);
}
/* Line-number gutter, mirroring the one `Code.Viewer` draws in the app.
Only multi-line fences get one, column and digit together. A line number on a one-line shell
command references nothing, and these docs are mostly one-line commands. Reserving the column
but leaving it blank was tried and is worse: the code picks up a 44px indent with nothing in
it, which reads as a rendering fault rather than as alignment. Single-line fences therefore
keep fumadocs' default padding, so adjacent blocks can start their text 28px apart —
acceptable, because each fence is its own bordered card, and differing padding between cards
is far less noticeable than an unexplained indent inside one.
The column is opened through fumadocs' own `--padding-left`, not by overriding its `.line`
rule: that rule is `.shiki:not(.not-fumadocs-codeblock *) .line`, and `:not()` carries its
argument's specificity, so it lands at (0,3,0) — above anything reasonable to write here.
Declared at every level the variable could be shadowed at, because it is inherited and the
nearest declaration wins: fumadocs sets it on `.shiki`, which is on the `figure` for prose
fences but on the `figure` AND the inner `pre` for API samples.
The `!important` and the viewport selector are belt-and-braces, not strictly required —
fumadocs' declaration is (0,2,0) and these selectors are (0,3,1) and (0,4,1), so they
already win, and nothing declares this property on the viewport. They are kept because the
specificity of `:has()` and `:not()` is easy to miscount in the direction that puts the line
numbers on top of the code. Remove them only alongside a visual check.
*/
figure.shiki:has(.line ~ .line),
figure.shiki:has(.line ~ .line) > div[role="region"],
figure.shiki:has(.line ~ .line) .shiki {
--padding-left: 2.75rem !important;
}
figure.shiki code {
counter-reset: line;
}
/* Gated on the same condition as the digit these anchor: without it, every line of every
single-line fence becomes a positioned containing block for a pseudo-element that never
renders. */
figure.shiki code:has(.line ~ .line) .line {
position: relative;
counter-increment: line;
}
figure.shiki code:has(.line ~ .line) .line::before {
content: counter(line);
position: absolute;
left: 0;
width: 1.75rem;
text-align: right;
color: var(--code-gutter);
font-size: var(--text-xs);
font-variant-numeric: tabular-nums;
user-select: none;
pointer-events: none;
}
/* The code fill is painted here, on the scroll viewport, rather than left to show through from
the figure or the tab group. Those diverge per renderer: fumadocs gives a standalone figure
`bg-fd-card` but an in-tab figure `bg-fd-secondary`, and this app forces
`--color-fd-card: transparent` on API reference pages. The result was measurable — a request
panel showing the page background (#fff light, `--bg` dark) beside a response panel showing
the code surface. The viewport is the one box all three renderers agree on. */
figure.shiki > div[role="region"] {
background-color: var(--code-surface);
/* fumadocs ships 14px of vertical padding, the platform's viewer 8px; 10px splits them and
keeps a single-line fence from looking hollow at the tighter 21px line box. */
padding-block: 10px;
}
/* Shiki emits `--shiki-*-bg` custom properties under `defaultColor: false`; keep the `pre` clear
so nothing paints over the viewport fill. */
figure.shiki pre {
background-color: transparent;
}
/* Untitled blocks float the copy control over the code, so the last column has to clear it:
an 8px offset plus emcn's 20px icon button, with room to breathe. fumadocs reserves 32px,
which the glyphs run into.
`!important` is load-bearing: fumadocs writes this same custom property as an inline style
on the viewport, and an inline declaration outranks any stylesheet rule without it. */
figure.shiki:not(:has(figcaption)),
figure.shiki:not(:has(figcaption)) > div[role="region"],
figure.shiki:not(:has(figcaption)) .shiki {
--padding-right: 2.75rem !important;
}
/* Copy control on the one block this app does not render.
Prose fences and the API reference's request/response samples both render
`components/ui/code-block.tsx`, which uses an emcn `Button`. Only fumadocs-openapi's
`UsageTab` is left — it renders `ClientCodeBlock` directly and takes no override — so these
rules bring its button to the same geometry and color as emcn's `variant='quiet' size='icon'`.
Every rule here is qualified with `:has(> svg[class*="lucide"])`. Without it they also match
the emcn `Button`, whose `aria-label` is identical, and re-declare chrome that component
owns — silently pinning docs to today's `buttonVariants` values.
The container carries an opaque `--code-surface` pad rather than fumadocs' `backdrop-blur-lg`.
The blur went milky over a filled block, but something has to sit behind the button: these
fences scroll horizontally and a long line runs straight under a transparent one. Matching
the block's own fill hides the code cleanly instead of frosting it. */
figure.shiki > div:has(> button[aria-label$="Text"] > svg[class*="lucide"]) {
top: 0.5rem;
right: 0.5rem;
padding-left: 0.25rem;
border-radius: 0.25rem;
background-color: var(--code-surface);
backdrop-filter: none;
}
figure.shiki button[aria-label="Copy Text"]:has(> svg[class*="lucide"]),
figure.shiki button[aria-label="Copied Text"]:has(> svg[class*="lucide"]) {
display: flex;
align-items: center;
justify-content: center;
padding: 0;
border: none;
border-radius: 0.25rem;
width: 20px;
height: 20px;
color: var(--text-icon-muted);
background: none;
box-shadow: none;
transition:
background-color 150ms,
color 150ms;
}
figure.shiki button[aria-label="Copy Text"]:has(> svg[class*="lucide"]) {
@variant hover-hover {
background-color: var(--surface-active);
}
}
figure.shiki button[aria-label="Copied Text"]:has(> svg[class*="lucide"]) {
color: var(--brand-accent);
}
/* One block per API reference page — the request "usage tabs" (cURL / JavaScript / Go / …) —
is rendered by fumadocs-openapi's `UsageTab`, which hardcodes its own `ClientCodeBlock`.
`renderCodeBlock` covers every other sample, and `OperationClientOptions` exposes only
`APIExampleSelector`, so there is no supported prop that reaches this one. Its lucide
clipboard is masked over with emcn's `Duplicate` / `Check` artwork so the copy glyph is the
same everywhere.
Scoped to `svg.lucide-*`: any block that already renders the real emcn icon is untouched,
and if fumadocs ever adds an override for these tabs, these rules simply stop matching.
The paths are copied from `packages/emcn/src/icons/{duplicate,check}.tsx` — a mask cannot
reference a component — so they must be updated together if that artwork changes. */
figure.shiki button[aria-label$="Text"] > svg[class*="lucide"] {
display: none;
}
figure.shiki button[aria-label$="Text"]:has(> svg[class*="lucide"])::before {
content: "";
display: block;
width: 14px;
height: 14px;
background-color: currentColor;
mask-image: var(--copy-glyph);
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
-webkit-mask-image: var(--copy-glyph);
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
-webkit-mask-size: contain;
}
figure.shiki button[aria-label="Copy Text"]:has(> svg[class*="lucide"]) {
--copy-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-1 -2 24 24' fill='none' stroke='%23000' stroke-width='1.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14.25 0.75H2.75C1.64543 0.75 0.75 1.64543 0.75 2.75V14.25'/%3E%3Crect x='5.25' y='5.25' width='14' height='14' rx='2'/%3E%3C/svg%3E");
}
figure.shiki button[aria-label="Copied Text"]:has(> svg[class*="lucide"]) {
--copy-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-1 -2 24 24' fill='none' stroke='%23000' stroke-width='1.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18.25 2.75L7.25 15.75L1.75 10.25'/%3E%3C/svg%3E");
}
/* Tabbed fences (`<Tabs>` around code) — collapse the strip and the figure into one box so
the group reads as a single control rather than a bar stacked on a card.
Every rule in this group carries the full `:has(> div > figure.shiki)` qualifier. Dropping
it on the panel rules would match prose `<Tabs>` too — nine pages use those — and strip the
padding they rely on. */
/* The strip becomes the group's title row; it and a titled fence's caption row are the same
object, so they share the rule above rather than restating its body.
Its scrollbar is hidden, matching what the platform does for a scrolling tab strip
(`overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden` on emcn's `TabStrip`).
fumadocs makes the strip `overflow-x-auto`, and an endpoint with ten status codes overflows it
in the API reference's narrow rail — leaving a scrollbar across the bottom of a 34px header,
which reads as the header being clipped rather than as something scrollable. The code viewport
below keeps its scrollbar: there the overflow is content, and hiding it would hide that a line
continues. */
div:has(> div > figure.shiki) > [role="tablist"] {
align-items: center;
gap: 0.25rem;
padding-inline: 0.375rem;
scrollbar-width: none;
}
div:has(> div > figure.shiki) > [role="tablist"]::-webkit-scrollbar {
display: none;
}
/* Triggers read as chips — a `--surface-active` pad on the selected tab, at `ChipTag`'s 6px
inline-tag corner — rather than fumadocs' underline, which drew a hard black rule the
platform uses nowhere. */
div:has(> div > figure.shiki) > [role="tablist"] button {
height: 22px;
padding-inline: 0.5rem;
border: none;
border-radius: 0.375rem;
color: var(--text-muted);
font-family: var(--font-mono);
font-size: var(--text-caption);
font-weight: 470;
transition:
background-color 150ms,
color 150ms;
}
/* Kept as its own rule rather than nested into the block above: biome's
`noDuplicateProperties` reads a nested `@variant` setting the same property as a duplicate
declaration and fails the build. */
div:has(> div > figure.shiki) > [role="tablist"] button {
@variant hover-hover {
color: var(--text-body);
}
}
/* The API reference's request-sample tabs draw their active indicator as an absolutely
positioned hairline child rather than a border, so the chip pad below would sit under a
second, redundant underline. Matched structurally — it is the only element fumadocs renders
before the trigger's label — rather than by its utility classes, which are upstream's to
change. */
div:has(> div > figure.shiki) > [role="tablist"] button > div:first-child {
display: none;
}
div:has(> div > figure.shiki) > [role="tablist"] button[data-state="active"] {
background-color: var(--surface-active);
color: var(--text-primary);
}
/* The panel is scaffolding, not chrome. fumadocs gives it `p-4`, its own `rounded-xl`, and a
`bg-fd-background` fill that would paint white inside the group's surface; strip all three
so the fence sits flush and the group reads as one box. */
div:has(> [role="tablist"]):has(> div > figure.shiki) > [role="tabpanel"] {
padding: 0;
border-radius: 0;
background-color: transparent;
}
/* A fence inside a panel is already inside the group's box — drop its own shell. Its
viewport padding supplies the inset the panel's `p-4` used to. */
div:has(> [role="tablist"]):has(> div > figure.shiki) > [role="tabpanel"] > figure.shiki {
margin: 0;
border: none;
border-radius: 0;
}
/* Cards container — vertical list with line dividers instead of grid boxes */
.prose .grid:has(> a[data-card]),
#nd-page .grid:has(> a[data-card]) {
display: flex !important;
flex-direction: column !important;
gap: 0 !important;
border-top: 1px solid var(--border) !important;
border-bottom: 1px solid var(--border) !important;
}
/* Card items — line-separated rows with hover bg (scoped to grid container only) */
.grid > a[data-card] {
display: flex !important;
flex-direction: column !important;
gap: 0.125rem !important;
background: none !important;
border: none !important;
border-bottom: 1px solid var(--border) !important;
border-radius: 0 !important;
padding: 0.875rem 1rem !important;
transition: background-color 0.15s !important;
box-shadow: none !important;
}
.grid > a[data-card]:last-child {
border-bottom: none !important;
}
.grid > a[data-card]:hover {
background-color: var(--surface-3) !important;
}
/* Card title */
.grid > a[data-card] h3 {
font-weight: 470 !important;
font-size: 0.875rem !important;
color: var(--text-primary) !important;
}
/* Card description */
.grid > a[data-card] p,
.grid > a[data-card] div {
color: var(--text-muted) !important;
font-size: var(--text-small) !important;
}
/* Steps — lighter styling aligned with platform */
.fd-steps {
border-left-color: var(--surface-active) !important;
}
.fd-step::before {
background-color: var(--surface-4) !important;
color: var(--text-muted) !important;
font-weight: 500 !important;
}
/* Blockquotes — subtle left border, matching platform divider */
.prose blockquote,
[data-docs-page] blockquote,
[data-content] blockquote,
main article blockquote {
border-left: 3px solid var(--border-1) !important;
padding: 0.25rem 0 0.25rem 1rem !important;
color: var(--text-muted) !important;
font-style: italic;
background: none !important;
margin: 1.5rem 0 !important;
}
/* Remove all remaining box-shadows on content components */
.prose > *,
[data-docs-page] figure,
[data-docs-page] table,
[data-docs-page] [data-card] {
box-shadow: none !important;
}
/* Tailwind v4 content sources */
@source '../app/**/*.{js,ts,jsx,tsx,mdx}';
@source '../components/**/*.{js,ts,jsx,tsx,mdx}';
@source '../content/**/*.{js,ts,jsx,tsx,mdx}';
@source '../mdx-components.tsx';
@source '../node_modules/fumadocs-ui/dist/**/*.js';