792 lines
30 KiB
CSS
792 lines
30 KiB
CSS
@import "tailwindcss";
|
|
@import "fumadocs-ui/css/neutral.css";
|
|
@import "fumadocs-ui/css/preset.css";
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Theme tokens */
|
|
/* - Fonts: Inter (UI/body) + Instrument Serif (display). */
|
|
/* - Layout width. */
|
|
/* - Zero out every Tailwind radius token → square corners everywhere. */
|
|
/* - Light-mode color tokens (full Fumadocs set). */
|
|
/* ------------------------------------------------------------------ */
|
|
@theme {
|
|
--font-sans: var(--font-sans), "Geist", ui-sans-serif, system-ui, sans-serif;
|
|
--font-heading: var(--font-heading), "Space Grotesk", "Inter", ui-sans-serif,
|
|
system-ui, sans-serif;
|
|
|
|
--fd-layout-width: 90rem;
|
|
--site-shell-max-width: 1400px;
|
|
--site-shell-pad-x: 32px;
|
|
--site-shell-pad-x-mobile: 20px;
|
|
|
|
--radius-xs: 0px;
|
|
--radius-sm: 0px;
|
|
--radius-md: 0px;
|
|
--radius-lg: 0px;
|
|
--radius-xl: 0px;
|
|
--radius-2xl: 0px;
|
|
--radius-3xl: 0px;
|
|
--radius-4xl: 0px;
|
|
|
|
/* Foundation — warm off-white */
|
|
--color-fd-background: hsl(40, 25%, 97%);
|
|
--color-bg-inverse: #000000;
|
|
--color-fd-foreground: hsl(0, 0%, 9%);
|
|
--color-fd-muted: hsl(40, 15%, 94%);
|
|
--color-fd-muted-foreground: hsl(0, 0%, 52%);
|
|
--color-fd-popover: hsl(0, 0%, 100%);
|
|
--color-fd-popover-foreground: hsl(0, 0%, 12%);
|
|
--color-fd-card: hsl(0, 0%, 100%);
|
|
--color-fd-card-foreground: hsl(0, 0%, 9%);
|
|
--color-fd-border: hsla(35, 12%, 65%, 0.4);
|
|
--color-fd-secondary: hsl(40, 15%, 92%);
|
|
--color-fd-secondary-foreground: hsl(0, 0%, 9%);
|
|
--color-fd-accent: hsla(35, 12%, 80%, 0.45);
|
|
--color-fd-accent-foreground: hsl(0, 0%, 9%);
|
|
|
|
/* Primary = neutral foreground. Violet is gone entirely. */
|
|
--color-fd-primary: hsl(0, 0%, 9%);
|
|
--color-fd-primary-foreground: hsl(0, 0%, 100%);
|
|
--color-fd-ring: hsl(0, 0%, 9%);
|
|
|
|
/* Prose body copy — in between foreground (9%) and muted (52%). */
|
|
--color-prose: hsl(0, 0%, 30%);
|
|
|
|
/* Prose surface — slightly whiter than the page off-white so the
|
|
* MDX article reads as a distinct "paper" on top of the canvas. */
|
|
--color-prose-bg: hsl(40, 30%, 99%);
|
|
}
|
|
|
|
/* Dark mode — neutral foundation, no accent color */
|
|
.dark {
|
|
--color-fd-background: hsl(0, 0%, 7%);
|
|
--color-bg-inverse: #ffffff;
|
|
--color-fd-foreground: hsl(0, 0%, 92%);
|
|
--color-fd-muted: hsl(0, 0%, 13%);
|
|
--color-fd-muted-foreground: hsla(0, 0%, 78%, 0.8);
|
|
--color-fd-popover: hsl(0, 0%, 11%);
|
|
--color-fd-popover-foreground: hsl(0, 0%, 87%);
|
|
--color-fd-card: hsl(0, 0%, 10%);
|
|
--color-fd-card-foreground: hsl(0, 0%, 98%);
|
|
--color-fd-border: hsla(0, 0%, 40%, 0.2);
|
|
--color-fd-secondary: hsl(0, 0%, 13%);
|
|
--color-fd-secondary-foreground: hsl(0, 0%, 92%);
|
|
--color-fd-accent: hsla(0, 0%, 41%, 0.3);
|
|
--color-fd-accent-foreground: hsl(0, 0%, 90%);
|
|
|
|
--color-fd-primary: hsl(0, 0%, 92%);
|
|
--color-fd-primary-foreground: hsl(0, 0%, 9%);
|
|
--color-fd-ring: hsl(0, 0%, 92%);
|
|
|
|
/* Prose body copy — in between foreground (92%) and muted (78%). */
|
|
--color-prose: hsl(0, 0%, 84%);
|
|
|
|
/* Prose surface — one notch brighter than the page background so the
|
|
* article still reads as a lifted "paper" in dark mode. */
|
|
--color-prose-bg: hsl(0, 0%, 12%);
|
|
}
|
|
|
|
:root {
|
|
--color-bg-inverse: #000000;
|
|
}
|
|
|
|
.dark {
|
|
--color-bg-inverse: #ffffff;
|
|
}
|
|
|
|
/* MDX content — cap each child block and center it inside the page
|
|
* column. Notebook's container uses flex-col + `*:max-w` which
|
|
* left-aligns by default; `margin-inline: auto` centers it. */
|
|
:is(#nd-page, .docs-page-surface) > * {
|
|
max-width: 650px;
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
width: 100%;
|
|
}
|
|
|
|
/* (fix: remove or correct the invalid selector) */
|
|
/* The selector `* >` is invalid and will throw a CSS error.
|
|
If the intention is to add a border for debugging, use a valid selector like `*` or a more specific one. */
|
|
|
|
/* * {
|
|
border: 1px solid red !important;
|
|
} */
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Reusable utility: engineering grid background */
|
|
/* */
|
|
/* Apply the `.bg-grid` class to any element to paint a layered grid */
|
|
/* behind its content — large cells framing smaller subcells, like */
|
|
/* engineering graph paper. Override the locally-scoped CSS variables */
|
|
/* to change cell size / line color / strength per instance, e.g.: */
|
|
/* */
|
|
/* <section class="bg-grid" style="--grid-cell: 120px">...</section> */
|
|
/* */
|
|
/* Defaults are deliberately strong-ish so the utility looks right */
|
|
/* when used on standalone blocks (hero, cards). The MDX article */
|
|
/* column turns the strength way down below — see `#nd-page`. */
|
|
/* ------------------------------------------------------------------ */
|
|
.bg-grid {
|
|
--grid-cell: 80px;
|
|
--grid-subcell: 16px;
|
|
--grid-line: var(--color-fd-border);
|
|
--grid-subline: color-mix(in oklab, var(--color-fd-border) 40%, transparent);
|
|
--grid-line-width: 1px;
|
|
|
|
background-image: linear-gradient(
|
|
to right,
|
|
var(--grid-line) var(--grid-line-width),
|
|
transparent var(--grid-line-width)
|
|
),
|
|
linear-gradient(
|
|
to bottom,
|
|
var(--grid-line) var(--grid-line-width),
|
|
transparent var(--grid-line-width)
|
|
),
|
|
linear-gradient(
|
|
to right,
|
|
var(--grid-subline) var(--grid-line-width),
|
|
transparent var(--grid-line-width)
|
|
),
|
|
linear-gradient(
|
|
to bottom,
|
|
var(--grid-subline) var(--grid-line-width),
|
|
transparent var(--grid-line-width)
|
|
);
|
|
background-size: var(--grid-cell) var(--grid-cell),
|
|
var(--grid-cell) var(--grid-cell), var(--grid-subcell) var(--grid-subcell),
|
|
var(--grid-subcell) var(--grid-subcell);
|
|
background-position: 0 0;
|
|
}
|
|
|
|
/* Reusable prose "paper" surface.
|
|
*
|
|
* The grid sits on a ::before layer (not the element directly) so we can
|
|
* inset it away from container edges. The inset leaves a clean gutter of
|
|
* prose-bg between the grid and vertical borders, avoiding doubled lines. */
|
|
#nd-page,
|
|
.paper-grid-surface {
|
|
position: relative;
|
|
isolation: isolate;
|
|
background-color: var(--color-prose-bg);
|
|
}
|
|
|
|
#nd-page::before,
|
|
.paper-grid-surface::before {
|
|
content: "";
|
|
position: absolute;
|
|
/* Gutter on left/right only — tops/bottoms don't collide with anything,
|
|
* so letting the grid run edge-to-edge vertically is fine. */
|
|
inset: 0 2px;
|
|
z-index: -1;
|
|
pointer-events: none;
|
|
|
|
/* Note: --color-fd-border is already translucent (~0.4 alpha), so the
|
|
* color-mix percentage multiplies into that. Anything under ~15%
|
|
* falls below the visible threshold on the off-white surface. */
|
|
--grid-line: color-mix(in oklab, var(--color-fd-border) 20%, transparent);
|
|
--grid-subline: color-mix(in oklab, var(--color-fd-border) 10%, transparent);
|
|
|
|
background-image: linear-gradient(
|
|
to right,
|
|
var(--grid-line) 1px,
|
|
transparent 1px
|
|
),
|
|
linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px),
|
|
linear-gradient(to right, var(--grid-subline) 1px, transparent 1px),
|
|
linear-gradient(to bottom, var(--grid-subline) 1px, transparent 1px);
|
|
background-size: 80px 80px, 80px 80px, 16px 16px, 16px 16px;
|
|
background-position: 0 0;
|
|
}
|
|
|
|
/* Sidebar — smaller font + tight row padding.
|
|
* Fumadocs items render as <a>/<button> with `p-2` (0.5rem) and the
|
|
* aside is `text-sm` (14px). We drop to ~13px, zero the viewport's
|
|
* inline padding so section separator rules span the full width,
|
|
* and let each item / heading own its own inline padding. */
|
|
#nd-sidebar,
|
|
#nd-sidebar-mobile {
|
|
font-size: 13px;
|
|
|
|
/* Fumadocs' banner wrapper (`> div:first-child`) previously sat
|
|
* empty (tabs are hidden on desktop, nav title lives in the top
|
|
* nav) and took up `p-4 pb-2` of dead space. It now hosts the
|
|
* search trigger (see `SidebarSearch` + `sidebar.banner` in
|
|
* `lib/section.tsx`). A hairline underneath separates it from
|
|
* the first section heading ("Getting Started"), mirroring the
|
|
* `p ~ p` rule that already divides subsequent sections.
|
|
*
|
|
* Padding is forced to a uniform 0.5rem (overriding Fumadocs'
|
|
* `p-4 pb-2`) so the search trigger sits tight against the
|
|
* sidebar's vertical rules on both sides and hugs the divider
|
|
* above/below — matches the visual density of the section
|
|
* headings directly below, which also use 0.5rem horizontal
|
|
* padding. */
|
|
> div:first-child {
|
|
padding: 0.5rem;
|
|
border-bottom: 1px solid var(--color-fd-border);
|
|
}
|
|
|
|
[data-radix-scroll-area-viewport] {
|
|
padding: 0.5rem 0 0.25rem;
|
|
}
|
|
|
|
a,
|
|
button {
|
|
padding-top: 0.125rem;
|
|
padding-bottom: 0.125rem;
|
|
padding-right: 0.5rem;
|
|
}
|
|
|
|
/* Section headings (rendered as <p>) — tighter top/bottom rhythm.
|
|
* All sections get 1rem of top breathing room above their text
|
|
* (first via padding; subsequent via margin + padding with the
|
|
* separator line in between). */
|
|
p {
|
|
margin-top: 0;
|
|
margin-bottom: 0.5rem;
|
|
padding-top: 1rem;
|
|
padding-left: 0.5rem;
|
|
padding-right: 0.5rem;
|
|
border-top: none;
|
|
font-size: 13px;
|
|
}
|
|
|
|
p:not(:first-child) {
|
|
margin-top: 0.5rem;
|
|
padding-top: 0.5rem;
|
|
border-top: 1px solid var(--color-fd-border);
|
|
}
|
|
|
|
/* Section-heading icons (Lucide SVGs rendered inside the <p>).
|
|
* Match the 13px heading text, use a slightly thinner stroke so
|
|
* they don't feel heavier than the label next to them. */
|
|
p svg {
|
|
width: 13px;
|
|
height: 13px;
|
|
stroke-width: 1.75;
|
|
}
|
|
}
|
|
|
|
/* Mobile sidebar drawer.
|
|
*
|
|
* Fumadocs renders the drawer as `fixed ... inset-y-0`, which starts it
|
|
* at the top of the viewport. Our custom notebook header is also sticky
|
|
* and defines `--fd-header-height`, so anchor the drawer below that
|
|
* header instead of letting its search field and first section slide
|
|
* underneath the nav bar. */
|
|
#nd-sidebar-mobile {
|
|
top: var(--fd-docs-row-2);
|
|
}
|
|
|
|
/* TOC — shrink to match sidebar's 13px, make the "On this page"
|
|
* heading use the dark foreground color, and add a little inline
|
|
* padding so items don't hug the left border.
|
|
*
|
|
* The heading rule also applies to any sibling heading we render into
|
|
* `tableOfContent.footer` (e.g. Contributors) via `data-toc-heading`,
|
|
* so custom footer content stays visually aligned with fumadocs' own
|
|
* `#toc-title`. */
|
|
#nd-toc {
|
|
font-size: 13px;
|
|
padding-top: 1rem;
|
|
padding-right: 0;
|
|
/* No horizontal padding on the container itself — each direct child
|
|
* owns its own inline padding. That way full-bleed children (e.g. the
|
|
* tocFooter with its `border-top` separator) can span edge-to-edge,
|
|
* while normal sections (title, scroll area) still get the 0.75rem
|
|
* indent via the rule below. Opt out with `data-toc-full-bleed`. */
|
|
> :not([data-toc-full-bleed]) {
|
|
padding: 0.75rem;
|
|
padding-top: 0.5rem;
|
|
}
|
|
|
|
#toc-title,
|
|
[data-toc-heading] {
|
|
color: var(--color-fd-foreground);
|
|
font-size: 13px;
|
|
}
|
|
|
|
a {
|
|
font-size: 13px;
|
|
}
|
|
}
|
|
|
|
/* Vertical rules framing the three columns + the top nav.
|
|
*
|
|
* Note: #nd-sidebar's <aside> is `w-full` of a wider grid track and
|
|
* uses `items-end` to align the visible 268px nav to the right edge.
|
|
* So borders on the aside itself land in the wrong place — we target
|
|
* its direct child (the actual 268px column) instead. */
|
|
#nd-sidebar > * {
|
|
border-inline-start: 1px solid var(--color-fd-border);
|
|
}
|
|
|
|
#nd-sidebar-mobile > *,
|
|
#nd-toc {
|
|
border-inline: 1px solid var(--color-fd-border);
|
|
}
|
|
|
|
/* The seam between sidebar and prose should belong to the main docs
|
|
* surface, not the collapsible sidebar rail. That keeps the vertical
|
|
* divider visible even when the sidebar is collapsed away. */
|
|
#nd-page {
|
|
border-inline-start: 1px solid var(--color-fd-border);
|
|
}
|
|
|
|
/* ==================================================================
|
|
* Blueprint callout — one treatment, many hooks.
|
|
*
|
|
* A diagonal-hatched "poché" fill framed by four outward-facing L
|
|
* brackets at the corners — camera-viewfinder / crop-mark style.
|
|
* Reads as a labelled region on an engineering schematic: the 45°
|
|
* stripes contrast the orthogonal MDX grid background, and the
|
|
* corner marks reference construction drawings where the box is
|
|
* implied by its corners rather than drawn as a closed rectangle.
|
|
*
|
|
* One source of truth so every hook picks up the same look:
|
|
* - top-nav text items (#nd-subnav a/button[data-active])
|
|
* - prose inline link hover (.prose a:hover)
|
|
* - ad-hoc use (.fd-blueprint-callout)
|
|
*
|
|
* Per-context tweaks (outward gap, arm length, ink/rule strength)
|
|
* go via the `--fd-callout-*` custom properties below — override
|
|
* them in the context's own block without redeclaring the whole
|
|
* rule. That's the only way to keep the treatment genuinely
|
|
* consistent across contexts.
|
|
* ================================================================== */
|
|
/* NB: selector list is intentionally NOT wrapped in `:where()` — we
|
|
* need each hook to keep its own specificity so it can beat the
|
|
* base prose-link rule further down the file. `:where()` zeros
|
|
* specificity, which would let `color: LinkText` / underline from
|
|
* `.prose a:not([data-card]…)` win on hover. */
|
|
.fd-blueprint-callout,
|
|
/* Top-nav text items — same callout for "I'm here" (active),
|
|
* "I'm thinking about going here" (hover), and the moment-of-click
|
|
* pressed state (`:active`). `[data-active]` (attribute presence,
|
|
* no value check) scopes this to opted-in affordances: NavLinks
|
|
* always carry it with "true"/"false", and the GitHub button opts
|
|
* in with "false". The icon-only utility buttons (theme switch,
|
|
* sidebar toggle) don't carry the attribute and keep their
|
|
* Fumadocs hover styles. */
|
|
#nd-subnav :where(a, button)[data-active="true"],
|
|
#nd-subnav :where(a, button)[data-active]:hover,
|
|
#nd-subnav :where(a, button)[data-active]:active,
|
|
:where(a, button)[data-callout]:hover,
|
|
:where(a, button)[data-callout]:focus-visible,
|
|
/* Fumadocs Cards — on hover, the same callout treatment as
|
|
* everywhere else. Cards are block-level `<a>`s with their own
|
|
* border + rounded background, so the hatch tints the card body
|
|
* and the corner marks frame it from outside. Kept out of the
|
|
* prose-link selector above (via `:not([data-card], [data-card] *)`)
|
|
* so card descendants don't also pick up link styling. */
|
|
.prose [data-card]:hover,
|
|
.prose a:not([data-card], [data-card] *, [data-button], [data-button] *):hover {
|
|
/* Tokens — local `--_*` copies with `--fd-callout-*` fallbacks so
|
|
* each context can override just the knob it cares about.
|
|
*
|
|
* --fd-callout-offset outward gap from host edge to corner
|
|
* marks (px). 0 = marks hug the edge.
|
|
* --fd-callout-arm length of each L arm (px).
|
|
* --fd-callout-ink hatch stripe colour (translucent).
|
|
* --fd-callout-rule corner-mark stroke colour. */
|
|
--_callout-offset: var(--fd-callout-offset, 4px);
|
|
--_callout-arm: var(--fd-callout-arm, 6px);
|
|
--_callout-ink: var(
|
|
--fd-callout-ink,
|
|
color-mix(in oklch, var(--color-fd-primary) 10%, transparent)
|
|
);
|
|
--_callout-rule: var(
|
|
--fd-callout-rule,
|
|
color-mix(in oklch, var(--color-fd-primary) 55%, transparent)
|
|
);
|
|
|
|
/* `position: relative` anchors the `::before` that paints the
|
|
* corner marks. On inline hosts (prose links) this is harmless —
|
|
* inline elements accept `position: relative` without becoming
|
|
* block-level, and the pseudo-element positions against the
|
|
* first line box. Multi-line wrapped inline links therefore
|
|
* show marks around the first line only; acceptable trade-off
|
|
* vs. adding per-line SVG which would be heavier. */
|
|
position: relative;
|
|
color: var(--fd-callout-color, var(--color-fd-foreground));
|
|
text-decoration-line: none;
|
|
background-image: repeating-linear-gradient(
|
|
-45deg,
|
|
var(--_callout-ink) 0 1px,
|
|
transparent 1px 6px
|
|
);
|
|
border-radius: 0;
|
|
/* When an inline link wraps across lines, repeat the hatch per
|
|
* line-box instead of drawing one awkward rectangle around the
|
|
* multi-line bounding box. */
|
|
-webkit-box-decoration-break: clone;
|
|
box-decoration-break: clone;
|
|
}
|
|
|
|
/* Corner marks — four outward-facing L brackets, one per corner.
|
|
*
|
|
* Implementation: a single ::before pseudo extended `--_callout-
|
|
* offset` pixels beyond the host on all four sides, painted with
|
|
* 8 `linear-gradient` layers (one horizontal + one vertical arm
|
|
* per corner). No extra markup required on the host, no SVG
|
|
* asset, no JS. Pseudo is inert (`pointer-events: none`) so it
|
|
* never intercepts clicks on the real link/button underneath.
|
|
*
|
|
* Why 8 gradients instead of 4 SVGs: `background-position` lets
|
|
* us anchor each stroke to a named corner (`top left`, `top
|
|
* right`, …) so the marks stay pinned regardless of host size.
|
|
* A single SVG would need `background-size: 100% 100%` and
|
|
* viewBox math to keep arm length from scaling with width. */
|
|
.fd-blueprint-callout::before,
|
|
#nd-subnav :where(a, button)[data-active="true"]::before,
|
|
#nd-subnav :where(a, button)[data-active]:hover::before,
|
|
#nd-subnav :where(a, button)[data-active]:active::before,
|
|
:where(a, button)[data-callout]:hover::before,
|
|
:where(a, button)[data-callout]:focus-visible::before,
|
|
.prose [data-card]:hover::before,
|
|
.prose
|
|
a:not(
|
|
[data-card],
|
|
[data-card] *,
|
|
[data-button],
|
|
[data-button] *
|
|
):hover::before {
|
|
content: "";
|
|
position: absolute;
|
|
inset: calc(-1 * var(--_callout-offset));
|
|
pointer-events: none;
|
|
background:
|
|
/* top-left: horizontal arm, then vertical arm */ linear-gradient(
|
|
var(--_callout-rule),
|
|
var(--_callout-rule)
|
|
)
|
|
top left / var(--_callout-arm) 1px no-repeat,
|
|
linear-gradient(var(--_callout-rule), var(--_callout-rule)) top left / 1px
|
|
var(--_callout-arm) no-repeat,
|
|
/* top-right */ linear-gradient(var(--_callout-rule), var(--_callout-rule))
|
|
top right / var(--_callout-arm) 1px no-repeat,
|
|
linear-gradient(var(--_callout-rule), var(--_callout-rule)) top right / 1px
|
|
var(--_callout-arm) no-repeat,
|
|
/* bottom-left */
|
|
linear-gradient(var(--_callout-rule), var(--_callout-rule)) bottom left /
|
|
var(--_callout-arm) 1px no-repeat,
|
|
linear-gradient(var(--_callout-rule), var(--_callout-rule)) bottom left /
|
|
1px var(--_callout-arm) no-repeat,
|
|
/* bottom-right */
|
|
linear-gradient(var(--_callout-rule), var(--_callout-rule)) bottom right /
|
|
var(--_callout-arm) 1px no-repeat,
|
|
linear-gradient(var(--_callout-rule), var(--_callout-rule)) bottom right /
|
|
1px var(--_callout-arm) no-repeat;
|
|
}
|
|
|
|
#nd-subnav {
|
|
/* Only a top rule — the left/right edges sit at the viewport's
|
|
* vertical gutters (no visible border below them), and the bottom
|
|
* rule lives on `[data-header-body]` itself. Adding `border-left`
|
|
* here shifts the entire header grid 1px right under border-box
|
|
* sizing, knocking the col 1 / col 2 seam out of alignment with
|
|
* the sidebar's right border below. */
|
|
border-top: 1px solid var(--color-fd-border);
|
|
|
|
/* Keep the nav above the sidebar (z-20) and page chrome, but below
|
|
* popovers/menus (z-50) so dropdown content can layer over it. */
|
|
z-index: 40;
|
|
background-color: var(--color-fd-background);
|
|
|
|
/* `[data-header-body]` padding is intentionally handled inline by
|
|
* our NavHeader component — the body is a 3-column grid whose
|
|
* seams must line up pixel-for-pixel with the sidebar right border
|
|
* and the TOC left border below. Any container padding here would
|
|
* shift the whole grid and break the alignment. Per-cell padding
|
|
* (see `NavHeader/index.tsx`) is the right place to tune inset. */
|
|
|
|
/* Active-state weight bump for links / buttons in the top nav.
|
|
* The visual (hatch + corner marks) comes from the shared
|
|
* blueprint callout at the top of the file; here we only bump
|
|
* `font-weight` so the active label reads heavier than its
|
|
* hovered-but-not-active siblings. `px-2` padding on the link
|
|
* absorbs any glyph-metric shift, so the row doesn't reflow.
|
|
*
|
|
* Scoped to `#nd-subnav` on purpose: sidebar keeps its own pill
|
|
* active state from Fumadocs since nav links (jump) and sidebar
|
|
* items (drill-down) play different navigational roles. */
|
|
:where(a, button)[data-active="true"] {
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Square corners on everything inside the right-hand utility cell
|
|
* (GitHub button, theme switch, sidebar toggle). Fumadocs'
|
|
* ThemeSwitch renders a pill wrapper + inner buttons, each with
|
|
* their own `rounded-full` classes that a single parent
|
|
* `className` prop can't reach. Flattening the whole subtree
|
|
* here is both simpler than case-by-case overrides and
|
|
* future-proof against upstream tweaks.
|
|
*
|
|
* Scoped to `[data-header-body] > *:last-child` specifically so
|
|
* it doesn't leak to col 1 (logo) or col 2 (nav links) and
|
|
* doesn't fight with the blueprint callout's own `border-radius`
|
|
* in the middle cell. Specificity is (1,1,1), comfortably above
|
|
* Tailwind's single-class `.rounded-full` (0,1,0) — no
|
|
* `!important` required. */
|
|
[data-header-body] > *:last-child,
|
|
[data-header-body] > *:last-child *,
|
|
[data-header-body] > *:last-child *::before,
|
|
[data-header-body] > *:last-child *::after {
|
|
border-radius: 0 !important;
|
|
}
|
|
}
|
|
|
|
/* MDX prose — use muted foreground for paragraphs/lists so the
|
|
* reading copy feels airier. Headings, links, and inline code each
|
|
* get their own color overrides below. */
|
|
.prose {
|
|
color: var(--color-prose);
|
|
font-weight: 300;
|
|
font-size: 15px;
|
|
}
|
|
|
|
/* Lists — squares instead of discs/circles at every nesting level. */
|
|
.prose :where(ul, ul ul, ul ul ul) {
|
|
list-style-type: square;
|
|
}
|
|
|
|
/* Headings — Geist Mono for a light/technical notebook feel.
|
|
* Kept dark (foreground) so they pop against the muted body copy.
|
|
* Scoped to #nd-page so it covers both <DocsTitle> (rendered outside
|
|
* .prose) and any headings written in MDX (inside .prose). */
|
|
:is(#nd-page, .docs-page-surface) :where(h1, h2, h3, h4, h5, h6) {
|
|
font-family: var(--font-heading);
|
|
color: var(--color-fd-foreground);
|
|
letter-spacing: -0.01em;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Body links — native browser blue, always regular weight (even when
|
|
* the author wraps them in **bold**). `LinkText` is the CSS system
|
|
* color that resolves to the UA's default link blue and adapts in
|
|
* dark mode.
|
|
*
|
|
* `:not([data-card], [data-card] *)` excludes Fumadocs Cards (and
|
|
* their descendants) so card-as-link doesn't pick up link styling —
|
|
* Fumadocs' own card theme classes win there. */
|
|
.prose a:not([data-card], [data-card] *, [data-button], [data-button] *) {
|
|
color: LinkText;
|
|
text-decoration-line: underline;
|
|
text-decoration-color: currentColor;
|
|
text-decoration-thickness: 1px;
|
|
text-underline-offset: 3px;
|
|
}
|
|
|
|
/* Hover state — visual comes from the shared blueprint callout
|
|
* at the top of the file. No per-context overrides needed here:
|
|
* prose links are inline with no padding, so the default 1px
|
|
* `--fd-callout-offset` is exactly the breathing room we want
|
|
* around glyphs. Left as a comment so the next reader knows
|
|
* where to look (and doesn't re-add a duplicate rule). */
|
|
|
|
/* <details>/<summary> — ensure pointer cursor on hover. Tailwind
|
|
* Preflight doesn't set this, and some browsers default to text. */
|
|
summary {
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Headings are auto-wrapped in <a href="#anchor"> by Fumadocs — we
|
|
* don't want those to turn blue. Reset them back to inheriting. */
|
|
.prose :where(h1, h2, h3, h4, h5, h6) a {
|
|
color: inherit;
|
|
text-decoration: none;
|
|
}
|
|
|
|
/* …and don't let the blueprint callout fire on heading anchors.
|
|
* The shared `.prose a:not([data-card]…):hover` rule at the top of
|
|
* the file would otherwise paint hatch + corner marks around every
|
|
* heading when the cursor passes over it, which reads as noise
|
|
* rather than affordance (users don't typically click heading
|
|
* self-links, they copy the URL via the hash icon).
|
|
*
|
|
* `:not([data-card])` on the anchor raises specificity to (0,3,1),
|
|
* matching the shared hover rule, so source order (this rule
|
|
* comes later) lets the reset win. Without it the heading
|
|
* carve-out is (0,2,1) and the callout leaks through.
|
|
*
|
|
* The `::before` reset neutralises the pseudo-element that paints
|
|
* the four corner marks — `background: none` clears all 8 gradient
|
|
* layers at once; `content: none` makes the pseudo not generate
|
|
* at all so it can't catch layout either. */
|
|
.prose :where(h1, h2, h3, h4, h5, h6) a:not([data-card]):hover {
|
|
color: inherit;
|
|
background-image: none;
|
|
}
|
|
.prose :where(h1, h2, h3, h4, h5, h6) a:not([data-card]):hover::before {
|
|
content: none;
|
|
}
|
|
|
|
/* Force regular weight on links regardless of wrapping <strong>/<b>:
|
|
* [**bold text**](url) → <a><strong>...</strong></a> (strong inside a)
|
|
* **[link](url)** → <strong><a>...</a></strong> (a inside strong)
|
|
*
|
|
* Same carve-out: skip cards so their title/body retain designed weights.
|
|
*/
|
|
.prose a:not([data-card], [data-card] *, [data-button], [data-button] *),
|
|
.prose a:not([data-card], [data-card] *, [data-button], [data-button] *) *,
|
|
.prose
|
|
:is(strong, b)
|
|
a:not([data-card], [data-card] *, [data-button], [data-button] *) {
|
|
font-weight: 400 !important;
|
|
}
|
|
|
|
/* ...but don't force 400 on heading links — headings should stay bold. */
|
|
.prose :where(h1, h2, h3, h4, h5, h6) a,
|
|
.prose :where(h1, h2, h3, h4, h5, h6) a * {
|
|
font-weight: inherit !important;
|
|
}
|
|
|
|
#docs-announcement,
|
|
#docs-announcement * {
|
|
font-size: 13px;
|
|
font-weight: 400;
|
|
}
|
|
|
|
#docs-announcement a {
|
|
display: inline;
|
|
margin-inline: 0.2em;
|
|
color: LinkText;
|
|
text-decoration-line: underline;
|
|
text-decoration-color: currentColor;
|
|
text-underline-offset: 3px;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Fumadocs Cards — tweak default sizing to feel more like our notebook
|
|
* aesthetic: icons + title a hair bigger, description/body a hair
|
|
* smaller. The link-bleed carve-outs above already keep card text
|
|
* black and un-underlined. */
|
|
[data-card] svg {
|
|
width: 18px;
|
|
height: 18px;
|
|
}
|
|
|
|
[data-card] h3 {
|
|
font-size: 15px;
|
|
}
|
|
|
|
[data-card] p,
|
|
[data-card] > div:last-child,
|
|
[data-card] > div:last-child * {
|
|
font-size: 12px;
|
|
}
|
|
|
|
/* Blueprint callout tuning for cards. Default 4px offset already
|
|
* matches what cards want (standardised across every hook — the
|
|
* framing marks should always sit ~4px off the component edge,
|
|
* regardless of whether the component is a nav pill or a big
|
|
* block card), so we only override the tokens that actually need
|
|
* to differ on cards:
|
|
*
|
|
* --fd-callout-arm: 12px longer arms so the L shapes
|
|
* read as deliberate framing
|
|
* rather than tiny dots against
|
|
* the card's larger rectangle.
|
|
* --fd-callout-ink: transparent kills the diagonal hatch; on
|
|
* a card-sized surface the 6px
|
|
* stripe cycle reads as dense
|
|
* wallpaper and competes with
|
|
* the title/body copy. The card
|
|
* keeps its plain background
|
|
* (plus Fumadocs' own accent
|
|
* tint on hover) and only the
|
|
* corner marks signal "you're
|
|
* here." */
|
|
.prose [data-card]:hover {
|
|
--fd-callout-arm: 12px;
|
|
--fd-callout-ink: transparent;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Fumadocs Steps — the <Steps>/<Step> components ship without any CSS,
|
|
* so we own the styling: numbered square badges (no border-radius),
|
|
* a subtle left rail, and consistent vertical rhythm between steps. */
|
|
.fd-steps {
|
|
counter-reset: fd-step;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1.5em;
|
|
margin-block: 1.5em;
|
|
padding-left: 28px;
|
|
border-left: 1px solid var(--color-fd-border);
|
|
}
|
|
|
|
.fd-step {
|
|
counter-increment: fd-step;
|
|
position: relative;
|
|
|
|
&::before {
|
|
content: counter(fd-step);
|
|
position: absolute;
|
|
top: 0;
|
|
left: calc(-28px - 12px);
|
|
width: 24px;
|
|
height: 24px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: var(--color-fd-card);
|
|
border: 1px solid var(--color-fd-border);
|
|
color: var(--color-fd-foreground);
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
line-height: 1;
|
|
border-radius: 0;
|
|
}
|
|
|
|
> :first-child {
|
|
margin-top: 0;
|
|
}
|
|
> :last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Fumadocs Tabs — wrap the tab list onto multiple rows when items
|
|
* would otherwise overflow horizontally. Fumadocs ships the tablist
|
|
* with `flex ... overflow-x-auto`, which turns extra tabs into a
|
|
* horizontal scroll strip (awkward on landing pages with 5+ tabs).
|
|
* The `.overflow-x-auto` suffix scopes the override specifically to
|
|
* Fumadocs' tablist so non-Fumadocs tablists elsewhere are unaffected.
|
|
* `gap-3.5` (0.875rem) already supplies the row-gap once wrapped. */
|
|
[role="tablist"].overflow-x-auto {
|
|
flex-wrap: wrap;
|
|
overflow-x: visible;
|
|
/* Tabs already carry `py-2` internally — an extra 14px of row-gap
|
|
* on top of that makes the wrapped rows feel disconnected. Zero it
|
|
* out so the rows stack tightly. */
|
|
row-gap: 0;
|
|
}
|
|
|
|
html {
|
|
scrollbar-gutter: stable;
|
|
}
|
|
|
|
html > body[data-scroll-locked] {
|
|
margin-right: 0px !important;
|
|
--removed-body-scroll-bar-size: 0px !important;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Pause CSS animations when an element (or any of its descendants) */
|
|
/* is wrapped in <PauseOffscreen> and scrolled out of view. */
|
|
/* */
|
|
/* Used by the home-page animated sections (SOTACards, JudgeCards, */
|
|
/* VibeCodingLoop, hero banner) so 30+ infinite SVG animations stop */
|
|
/* burning GPU when they aren't visible. See */
|
|
/* `src/components/PauseOffscreen/index.tsx` for the wrapper. */
|
|
/* ------------------------------------------------------------------ */
|
|
[data-paused="true"],
|
|
[data-paused="true"] * {
|
|
animation-play-state: paused !important;
|
|
}
|