From cd23c619df8e4bb458344a4b00911c2dc3dcb40b Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Sat, 17 Oct 2026 20:38:18 -0400 Subject: [PATCH] build(docs): generate Blume site artifacts --- apps/docs/src/generated/Ask.astro | 15 + apps/docs/src/generated/app.css | 1443 ++++++ .../component-slots/mdx-DependencySpine.astro | 11 + apps/docs/src/generated/components.ts | 9 + apps/docs/src/generated/consent-client.ts | 3 + apps/docs/src/generated/content-assets.json | 1 + apps/docs/src/generated/data.json | 4383 +++++++++++++++++ apps/docs/src/generated/examples.css | 154 + apps/docs/src/generated/examples.ts | 3 + apps/docs/src/generated/features.ts | 11 + apps/docs/src/generated/includes.json | 1 + apps/docs/src/generated/mcp-data.json | 1 + apps/docs/src/generated/openapi.json | 1 + apps/docs/src/generated/raw-markdown.json | 1 + apps/docs/src/generated/search-client.ts | 7 + apps/docs/src/generated/search.json | 1 + apps/docs/src/pages/404.astro | 176 + apps/docs/src/pages/404.json.ts | 52 + apps/docs/src/pages/404.md.ts | 57 + apps/docs/src/pages/[...slug].astro | 492 ++ apps/docs/src/pages/[...slug].md.ts | 24 + apps/docs/src/pages/[...slug].mdx.ts | 24 + .../src/pages/api/docs/navigation.json.ts | 9 + apps/docs/src/pages/api/docs/pages.json.ts | 9 + .../pages/api/docs/pages/[...route].json.ts | 13 + .../docs/src/pages/blume-assets/[...asset].ts | 120 + apps/docs/src/pages/blume-search.json.ts | 10 + apps/docs/src/pages/og/[...slug].png.ts | 96 + apps/docs/src/pages/openapi.json.ts | 803 +++ 29 files changed, 7930 insertions(+) create mode 100644 apps/docs/src/generated/Ask.astro create mode 100644 apps/docs/src/generated/app.css create mode 100644 apps/docs/src/generated/component-slots/mdx-DependencySpine.astro create mode 100644 apps/docs/src/generated/components.ts create mode 100644 apps/docs/src/generated/consent-client.ts create mode 100644 apps/docs/src/generated/content-assets.json create mode 100644 apps/docs/src/generated/data.json create mode 100644 apps/docs/src/generated/examples.css create mode 100644 apps/docs/src/generated/examples.ts create mode 100644 apps/docs/src/generated/features.ts create mode 100644 apps/docs/src/generated/includes.json create mode 100644 apps/docs/src/generated/mcp-data.json create mode 100644 apps/docs/src/generated/openapi.json create mode 100644 apps/docs/src/generated/raw-markdown.json create mode 100644 apps/docs/src/generated/search-client.ts create mode 100644 apps/docs/src/generated/search.json create mode 100644 apps/docs/src/pages/404.astro create mode 100644 apps/docs/src/pages/404.json.ts create mode 100644 apps/docs/src/pages/404.md.ts create mode 100644 apps/docs/src/pages/[...slug].astro create mode 100644 apps/docs/src/pages/[...slug].md.ts create mode 100644 apps/docs/src/pages/[...slug].mdx.ts create mode 100644 apps/docs/src/pages/api/docs/navigation.json.ts create mode 100644 apps/docs/src/pages/api/docs/pages.json.ts create mode 100644 apps/docs/src/pages/api/docs/pages/[...route].json.ts create mode 100644 apps/docs/src/pages/blume-assets/[...asset].ts create mode 100644 apps/docs/src/pages/blume-search.json.ts create mode 100644 apps/docs/src/pages/og/[...slug].png.ts create mode 100644 apps/docs/src/pages/openapi.json.ts diff --git a/apps/docs/src/generated/Ask.astro b/apps/docs/src/generated/Ask.astro new file mode 100644 index 0000000..30142ae --- /dev/null +++ b/apps/docs/src/generated/Ask.astro @@ -0,0 +1,15 @@ +--- +// Generated by Blume. Do not edit. +import Assistant from "blume/components/islands/Assistant.astro"; +import data from "blume:data"; + +const { strings } = Astro.props; +--- + + diff --git a/apps/docs/src/generated/app.css b/apps/docs/src/generated/app.css new file mode 100644 index 0000000..777ab40 --- /dev/null +++ b/apps/docs/src/generated/app.css @@ -0,0 +1,1443 @@ +/* Generated by Blume. Do not edit. */ +@import "tailwindcss"; +@plugin "@tailwindcss/typography"; + +/* Scan Blume's components and the user's project for utility classes. */ +@source "../../../../../../../../../../tmp/bunx-1000-blume@latest/node_modules/blume/src/**/*.{astro,ts,tsx}"; +@source "../../**/*.{astro,jsx,mdx,ts,tsx}"; + +/* Dark mode is driven by data-theme on the element. */ +@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *)); + +:root { + color-scheme: light; +} + +:root[data-theme="dark"] { + color-scheme: dark; +} + +:root { + --blume-background: oklch(1 0 0); + --blume-background-image: none; + --blume-background-image-repeat: no-repeat; + --blume-background-image-size: cover; + --blume-foreground: oklch(0.145 0 0); + --blume-muted: oklch(0.965 0 0); + /* 5.28:1 on the page background. The headroom over 4.5:1 is the point: muted + text is 14px body copy routinely set on a tinted surface (callouts, badges, + panels), and each tint costs a few tenths. */ + --blume-muted-foreground: oklch(0.53 0 0); + --blume-border: oklch(0.88 0.006 260 / 0.72); + --blume-accent: oklch(0.145 0 0); + --blume-accent-foreground: oklch(1 0 0); + --blume-action: var(--blume-accent); + --blume-action-foreground: var(--blume-accent-foreground); + --blume-code-background: oklch(0.99 0 0); + /* Shiki notation transformers: line/word highlight, diff add/remove, and + error/warning lines. */ + --blume-code-highlight: oklch(0.55 0.16 255 / 0.1); + --blume-code-highlight-border: oklch(0.55 0.16 255 / 0.55); + --blume-code-add: oklch(0.72 0.16 150 / 0.16); + --blume-code-add-border: oklch(0.52 0.15 150 / 0.7); + --blume-code-remove: oklch(0.66 0.21 22 / 0.16); + --blume-code-remove-border: oklch(0.55 0.2 22 / 0.7); + --blume-code-error: oklch(0.66 0.21 22 / 0.16); + --blume-code-error-border: oklch(0.55 0.2 22 / 0.7); + --blume-code-warning: oklch(0.8 0.16 80 / 0.2); + --blume-code-warning-border: oklch(0.66 0.15 70 / 0.75); + --blume-code-word: oklch(0.55 0.16 255 / 0.16); + --blume-code-word-border: oklch(0.55 0.16 255 / 0.5); + --blume-radius: 0.75rem; + + /* Max width of the prose content column (article, breadcrumb, TOC, feedback, + pagination, last-updated). Override to re-measure the whole column at once. */ + --blume-content-width: 42rem; + + /* + * Font tokens resolve through optional src variables that Astro's Fonts API + * populates (via theme.fonts). When unset they fall back to the system + * stacks, so the default look is unchanged. The display font defaults to the + * body font. + */ + --blume-font-body: var( + --blume-font-body-src, + ui-sans-serif, + system-ui, + -apple-system, + "Segoe UI", + Roboto, + Helvetica, + Arial, + sans-serif + ); + --blume-font-mono: var( + --blume-font-mono-src, + ui-monospace, + "SF Mono", + "Cascadia Code", + "Source Code Pro", + Menlo, + Consolas, + monospace + ); + --blume-font-display: var(--blume-font-display-src, var(--blume-font-body)); +} + +:root[data-theme="dark"] { + --blume-background: oklch(0.085 0 0); + --blume-background-image: none; + --blume-background-image-repeat: no-repeat; + --blume-background-image-size: cover; + --blume-foreground: oklch(0.96 0 0); + --blume-muted: oklch(0.16 0 0); + --blume-muted-foreground: oklch(0.68 0 0); + --blume-border: oklch(0.24 0 0 / 0.8); + --blume-accent: oklch(0.96 0 0); + --blume-accent-foreground: oklch(0.085 0 0); + --blume-action: var(--blume-accent); + --blume-action-foreground: var(--blume-accent-foreground); + --blume-code-background: oklch(0.12 0 0); + /* Brighter tints read better over the dark code surface. */ + --blume-code-highlight: oklch(0.7 0.14 255 / 0.16); + --blume-code-highlight-border: oklch(0.7 0.14 255 / 0.6); + --blume-code-add: oklch(0.78 0.17 150 / 0.2); + --blume-code-add-border: oklch(0.72 0.16 150 / 0.7); + --blume-code-remove: oklch(0.72 0.21 22 / 0.22); + --blume-code-remove-border: oklch(0.7 0.2 22 / 0.7); + --blume-code-error: oklch(0.72 0.21 22 / 0.22); + --blume-code-error-border: oklch(0.7 0.2 22 / 0.7); + --blume-code-warning: oklch(0.82 0.16 80 / 0.18); + --blume-code-warning-border: oklch(0.82 0.16 80 / 0.7); + --blume-code-word: oklch(0.7 0.14 255 / 0.22); + --blume-code-word-border: oklch(0.7 0.14 255 / 0.55); +} + +@theme inline { + --color-background: var(--blume-background); + --color-foreground: var(--blume-foreground); + --color-muted: var(--blume-muted); + --color-muted-foreground: var(--blume-muted-foreground); + --color-border: var(--blume-border); + --color-accent: var(--blume-accent); + --color-accent-foreground: var(--blume-accent-foreground); + --color-action: var(--blume-action); + --color-action-foreground: var(--blume-action-foreground); + --color-code: var(--blume-code-background); + --radius-blume: var(--blume-radius); + --container-content: var(--blume-content-width); + --font-sans: var(--blume-font-body); + --font-mono: var(--blume-font-mono); + --font-display: var(--blume-font-display); +} + +@layer base { + /* Nothing refuses to shrink below its intrinsic content width. This global + min-width reset defuses the classic flex/grid overflow — a long or + truncating child forcing its container (and the page) past the viewport + edge — so components don't need per-element min-w-0 overrides. */ + * { + min-width: 0; + } + /* Interactive controls get a pointer cursor unless disabled. */ + button:not(:disabled), + [role="button"]:not(:disabled) { + cursor: pointer; + } + html { + scroll-behavior: smooth; + scroll-padding-top: 4.5rem; + text-rendering: optimizeLegibility; + } + /* Modal surfaces coordinate through independent root attributes. The lock + remains until every owner releases its attribute, while !important lets it + temporarily override (and therefore preserve) an authored inline value. + Classic (non-overlay) scrollbars would otherwise vanish while locked and + shift the page and the centered dialog sideways, so keep their gutter. */ + html:where([data-blume-nav-open], [data-blume-search-dialog-open]) { + overflow: hidden !important; + scrollbar-gutter: stable; + } + /* Headings use the display font (defaults to the body font when unset). + The tightened tracking is part of the theme, not the font: display-tuned + families bake it into their metrics, but a text family promoted to + headings (including the Inter default) reads loose without it. -0.05em + was matched visually against Inter Tight, the previous display default. */ + h1, + h2, + h3, + h4, + h5, + h6 { + font-family: var(--font-display); + letter-spacing: -0.05em; + } + :focus-visible { + outline: 2px solid var(--blume-accent); + outline-offset: 2px; + border-radius: 2px; + } + body { + background-attachment: fixed; + background-image: var(--blume-background-image); + background-position: center top; + background-repeat: var(--blume-background-image-repeat); + background-size: var(--blume-background-image-size); + } + @media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } + } +} + +/* Same-origin navigations ride Astro's client router (the layouts render + ClientRouter), which owns the page-transition styling — including its own + prefers-reduced-motion guard — so the theme declares nothing about it. The + cross-document view-transition opt-in that briefly lived here is + deliberately gone: mixing it with the client router double-animates the + full-load navigations the router hands back to the browser. */ + +/* Code reads left-to-right regardless of page direction; only the surrounding + chrome mirrors for RTL. Inline code is isolated so LTR identifiers don't + disturb the bidi flow of right-to-left prose. */ +[dir="rtl"] pre, +[dir="rtl"] .prose pre { + direction: ltr; + text-align: left; +} +[dir="rtl"] :not(pre) > code { + unicode-bidi: isolate; +} + +/* The sidebar's rows. A docs site renders every sidebar row on every page, + so a large site's HTML is mostly this markup — one utility per row kind + instead of the dozen classes each expands to keeps every page (and the + build's HTML output) small. Kept in sync with NavTree.astro: a page row, + a page-mode group's drill-in row (or, when the group has a page, the + link and chevron button it splits into), a collapsible group's summary + and the link inside it, and a flat group's heading and the link inside + it. */ +@utility blume-nav-link { + @apply block rounded-[0.65rem] px-2.5 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground aria-[current=page]:bg-muted aria-[current=page]:font-medium aria-[current=page]:text-foreground; +} +@utility blume-nav-drill { + @apply flex w-full items-center gap-2 rounded-[0.65rem] px-2.5 py-1.5 text-start font-medium text-foreground text-sm transition-colors hover:bg-muted aria-[current]:bg-muted; +} +/* A routed drill row's label and chevron are separate pills, like a panel's + back button and title, so each shows which one the pointer is on. The + chevron's padding matches the drill row's, so chevrons line up. */ +@utility blume-nav-drill-link { + @apply flex min-w-0 flex-1 items-center gap-2 rounded-[0.65rem] px-2.5 py-1.5 font-medium text-foreground text-sm transition-colors hover:bg-muted aria-[current]:bg-muted; +} +@utility blume-nav-drill-toggle { + @apply flex shrink-0 items-center justify-center self-stretch rounded-[0.65rem] px-2.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground; +} +@utility blume-nav-summary { + @apply flex cursor-pointer list-none items-center gap-1.5 rounded-[0.65rem] px-2.5 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground [&::-webkit-details-marker]:hidden; +} +@utility blume-nav-summary-link { + @apply -my-1 flex flex-1 items-center gap-1.5 rounded py-1 transition-colors aria-[current=page]:text-foreground aria-[current=page]:font-semibold; +} +@utility blume-nav-heading { + @apply mb-1 flex items-center gap-1.5 px-2.5 py-1.5 font-medium text-foreground text-sm; +} +/* The linked heading fills the heading's own box with negative margins, so + its hover/active pill is the page row's exactly (same padding and radius) + and an unlinked heading beside it keeps the same text position. */ +@utility blume-nav-heading-link { + @apply -mx-2.5 -my-1.5 flex flex-1 items-center gap-1.5 rounded-[0.65rem] px-2.5 py-1.5 text-foreground transition-colors hover:bg-muted aria-[current=page]:bg-muted; +} + +/* Theme Tailwind Typography (prose) with Blume tokens. */ +.prose { + --tw-prose-body: var(--blume-foreground); + --tw-prose-headings: var(--blume-foreground); + --tw-prose-lead: var(--blume-muted-foreground); + --tw-prose-links: var(--blume-foreground); + --tw-prose-bold: var(--blume-foreground); + --tw-prose-counters: var(--blume-muted-foreground); + --tw-prose-bullets: var(--blume-muted-foreground); + --tw-prose-hr: var(--blume-border); + --tw-prose-quotes: var(--blume-muted-foreground); + --tw-prose-quote-borders: var(--blume-border); + --tw-prose-captions: var(--blume-muted-foreground); + --tw-prose-code: var(--blume-foreground); + --tw-prose-pre-code: var(--blume-foreground); + --tw-prose-pre-bg: var(--blume-code-background); + --tw-prose-th-borders: var(--blume-border); + --tw-prose-td-borders: var(--blume-border); + /* Neutralized: the plugin defaults are near-black text and shadow with no + dark counterpart (Blume never applies prose-invert), and the kbd rule + below draws the badge with Blume tokens instead. */ + --tw-prose-kbd: inherit; + --tw-prose-kbd-shadows: transparent; + color: var(--blume-muted-foreground); + font-size: 0.875rem; + line-height: 1.7; + /* An unbreakable run — an API permission, a broker list, a bare URL used as + its own link text, an OpenAPI operation title of the form + `METHOD /very/long/{path}`, a module-qualified name in a generated + reference — paints past the content column, and nothing between the + paragraph and the viewport clips it, so it lands in the document's scroll + width and drags the page sideways on a phone. Inherited, so headings and + inline code are covered too; the only re-declarations below are the + opt-in code-wrap mode and Twoslash, which want different values. + `break-word` and not `anywhere`: only `anywhere` reduces min-content, + which is what sizes `table-layout: auto` columns, so it would resize + every table on the site. The flip side is that a token in a table cell + still never breaks — the column grows to fit it — so wide tables rely on + the .blume-table-scroll wrapper, not on this. */ + overflow-wrap: break-word; +} + +/* No letter-spacing here: prose headings inherit the base h1-h6 rule's + display tracking, same as headings outside the prose column. */ +.prose :where(h1, h2, h3, h4) { + font-weight: 500; +} + +.prose :where(h1) { + font-size: 3rem; + line-height: 1.1; + margin: 0 0 1rem; +} + +.prose :where(h2) { + font-size: 1.875rem; + line-height: 1.2; + margin-top: 3rem; +} + +.prose :where(h3) { + font-size: 1.25rem; + line-height: 1.35; +} + +.prose :where(h4) { + color: var(--blume-muted-foreground); + font-size: 0.875rem; + line-height: 1.4; +} + +.prose :where(h1, h2, h3, h4, h5, h6) a { + color: inherit; + font-weight: inherit; + text-decoration: none; +} + +/* Auto-generated heading permalinks (markdown.headingAnchors): the whole + heading links to its own id, with a muted “#” revealed on hover or keyboard + focus so the link is discoverable without cluttering the heading at rest. */ +.prose :where(h2, h3, h4, h5, h6) a.blume-heading-anchor::after { + content: "#"; + color: var(--blume-muted-foreground); + margin-inline-start: 0.35em; + opacity: 0; + transition: opacity 0.15s ease; +} +.prose :where(h2, h3, h4, h5, h6) a.blume-heading-anchor:hover::after, +.prose :where(h2, h3, h4, h5, h6) a.blume-heading-anchor:focus-visible::after { + opacity: 1; +} + +/* A [toc]-marked heading exists only for the table of contents: it stays in + the flow as a zero-height, invisible anchor target so its TOC entry (and any + deep link) still has somewhere to scroll to, without rendering on the page. */ +.prose :where(.blume-toc-only) { + border: 0; + height: 0; + margin: 0; + overflow: hidden; + padding: 0; + visibility: hidden; +} + +.prose :where(h2:first-child) { + border-top: 0; + padding-top: 0; +} + +.prose :where(p, ul, ol) { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.prose :where(strong) { + font-weight: 600; +} + +.prose :where(a) { + color: var(--blume-foreground); + font-weight: 500; + text-decoration-line: underline; + text-decoration-style: dotted; + text-decoration-thickness: 1px; + text-underline-offset: 0.2em; +} + +.prose :where(a[data-blume-card]) { + color: inherit; + font-weight: inherit; + text-decoration: none; +} + +/* External Markdown links (markdown.externalLinks) open in a new tab and trail + the same Lucide arrow-up-right the featured sidebar links use, as a mask so it + takes the link's color. A link wrapping only an image (a badge, a logo) + skips the arrow. */ +.prose :where(a[data-blume-external]:not(:has(img, svg)))::after { + background-color: currentColor; + content: ""; + display: inline-block; + height: 0.75em; + margin-inline-start: 0.125em; + -webkit-mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%3E%3Cpath%20d%3D%22M7%207h10v10M7%2017L17%207%22%2F%3E%3C%2Fsvg%3E") center / contain no-repeat; + mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%3E%3Cpath%20d%3D%22M7%207h10v10M7%2017L17%207%22%2F%3E%3C%2Fsvg%3E") center / contain no-repeat; + opacity: 0.6; + width: 0.75em; +} + +.prose :where(hr) { + margin: 2.5rem 0; +} + +.prose :where(pre) { + background: transparent; + border: 1px solid var(--blume-border); + border-radius: var(--blume-radius); + color: var(--blume-foreground); + font-family: var(--font-mono); + font-size: 0.8125rem; + line-height: 1.55; + margin: 1.5rem 0; + overflow-x: auto; + padding: 1rem 0; + position: relative; +} + +.prose :where(pre.astro-code) { + background: transparent !important; + color: var(--shiki-light, var(--blume-foreground)) !important; +} + +:root[data-theme="dark"] .prose :where(pre.astro-code) { + color: var(--shiki-dark, var(--blume-foreground)) !important; +} + +.prose :where(pre.astro-code span) { + background: transparent !important; + color: var(--shiki-light, inherit); + font-style: var(--shiki-light-font-style, inherit); + font-weight: var(--shiki-light-font-weight, inherit); + text-decoration: var(--shiki-light-text-decoration, inherit); +} + +:root[data-theme="dark"] .prose :where(pre.astro-code span) { + color: var(--shiki-dark, inherit); + font-style: var(--shiki-dark-font-style, inherit); + font-weight: var(--shiki-dark-font-weight, inherit); + text-decoration: var(--shiki-dark-text-decoration, inherit); +} + +/* The Diff component (@pierre/diffs) renders into a declarative shadow root and + themes itself with light-dark(). Drive that from Blume's data-theme instead + of the OS, scoped to the diff host: the custom property pierces the shadow + boundary, where the appended host color-scheme rule consumes it. */ +blume-diff { + --blume-diff-color-scheme: light; + display: block; +} + +:root[data-theme="dark"] blume-diff { + --blume-diff-color-scheme: dark; +} + +.prose :where(pre[data-language]) { + padding-top: 3.75rem; +} + +.prose :where(pre[data-language])::before { + align-items: center; + border-bottom: 1px solid var(--blume-border); + color: var(--blume-muted-foreground); + content: attr(data-language); + display: flex; + font-family: var(--font-sans); + font-size: 0.75rem; + font-weight: 500; + height: 2.75rem; + left: 0; + padding: 0 3.25rem 0 1rem; + position: absolute; + right: 0; + top: 0; +} + +.prose :where(pre[data-title])::before { + content: attr(data-title); +} + +/* Language icon (simple-icons) sits at the header's left edge; the label shifts + right to make room. Injected at build time by the language-icon transformer. + The icon is gated on data-language (the header bar), not just data-icon: the + transformer runs whenever icons are on, but a header-less standalone + CodeBlock (no title) never gets data-language, so without this gate the + absolutely-positioned icon would overlap the first code line. Fenced code + and titled blocks always have a header, so the icon shows there. */ +.prose > :where(pre[data-language][data-icon])::after { + content: ""; + height: 0.875rem; + left: 1rem; + -webkit-mask-position: center; + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: contain; + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + pointer-events: none; + position: absolute; + top: 0.875rem; + width: 0.875rem; +} + +.prose > :where(pre[data-language][data-icon])::before { + padding-left: 2.5rem; +} + +/* One mask rule per language the site's Markdown uses (see + markdown/language-icon.ts); the ::after above only gets a color and a mask + from these, so a block whose language has no rule paints nothing. */ +.prose > :where(pre[data-language][data-icon="javascript"])::after { + background-color: var(--blume-muted-foreground); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cpath%20d%3D%22M0%200h24v24H0V0zm22.034%2018.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84%201.515-.66.39.12.75.42.976.9%201.034-.676%201.034-.676%201.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71%201.005-1.14%201.291-.811%203.541.569%204.471%201.365%201.02%203.361%201.244%203.616%202.205.24%201.17-.87%201.545-1.966%201.41-.811-.18-1.26-.586-1.755-1.336l-1.83%201.051c.21.48.45.689.81%201.109%201.74%201.756%206.09%201.666%206.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0%201.938-.009%203.864-.009%205.805%200%201.232.063%202.363-.138%202.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825%201.125c.305.63.75%201.172%201.324%201.517.855.51%202.004.675%203.207.405.783-.226%201.458-.691%201.811-1.411.51-.93.402-2.07.397-3.346.012-2.054%200-4.109%200-6.179l.004-.056z%22%2F%3E%3C%2Fsvg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cpath%20d%3D%22M0%200h24v24H0V0zm22.034%2018.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84%201.515-.66.39.12.75.42.976.9%201.034-.676%201.034-.676%201.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71%201.005-1.14%201.291-.811%203.541.569%204.471%201.365%201.02%203.361%201.244%203.616%202.205.24%201.17-.87%201.545-1.966%201.41-.811-.18-1.26-.586-1.755-1.336l-1.83%201.051c.21.48.45.689.81%201.109%201.74%201.756%206.09%201.666%206.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0%201.938-.009%203.864-.009%205.805%200%201.232.063%202.363-.138%202.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825%201.125c.305.63.75%201.172%201.324%201.517.855.51%202.004.675%203.207.405.783-.226%201.458-.691%201.811-1.411.51-.93.402-2.07.397-3.346.012-2.054%200-4.109%200-6.179l.004-.056z%22%2F%3E%3C%2Fsvg%3E"); +} +.prose > :where(pre[data-language][data-icon="python"])::after { + background-color: var(--blume-muted-foreground); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cpath%20d%3D%22M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02%201.27.05zm-6.3%201.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09%203.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14%201.04.05%201.23-.06%201.23-.16%201.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01%202.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47%2014.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z%22%2F%3E%3C%2Fsvg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cpath%20d%3D%22M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02%201.27.05zm-6.3%201.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09%203.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14%201.04.05%201.23-.06%201.23-.16%201.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01%202.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47%2014.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z%22%2F%3E%3C%2Fsvg%3E"); +} + +.prose :where(pre code) { + background: transparent; + border-radius: 0; + color: inherit; + font-size: inherit; + font-weight: 400; + padding: 0; +} + +/* Long lines scroll inside the code element, not the pre: the pre stays static + so its absolute header bar (::before) and copy button don't drift with the + scroll. The pre's horizontal padding lives here so content still scrolls + edge-to-edge past it. Two contexts opt out: twoslash blocks (popups must + escape any scroll container — see theme/twoslash.ts), and the API request + panel, which owns its code layout and keeps the copy control in the panel + header. Every other component that hosts a code block — Tabs, CodeGroup, + Steps, Callout, Card, Accordion — is real prose content and keeps the inset, + even though its chrome wrapper is not-prose. */ +.prose :where(pre:not(.twoslash, .twoslash pre, blume-panel-tabs *) > code) { + display: block; + /* Tall blocks scroll vertically in place instead of taking the page. Both + axes live on the code element for the same reason: the pre stays static + so the header bar and copy button never drift with the scroll. */ + max-height: 24rem; + overflow: auto; + /* The small bottom inset keeps the horizontal thumb off the last line's + descenders now that scrollbars are visible. The pre gives up the same + amount below (next rule), so the inset adds no height to the block. */ + padding: 0 1.25rem 0.375rem; + /* Thin theme-colored scrollbars, matching the sidebar treatment, so a + height-capped block reads as scrollable instead of simply ending. + Safari before 18.2 supports neither property and falls back to the + platform-default scrollbar — acceptable, since macOS overlays it. */ + scrollbar-color: var(--blume-border) transparent; + scrollbar-width: thin; +} + +/* The scroller's bottom inset comes out of the pre's own block padding: the + text still sits one full inset above the frame's bottom edge, the thumb sits + in the gap, and a one-line command is no taller than it was before the + scroller existed. */ +.prose :where(pre:not(.twoslash, .twoslash pre, blume-panel-tabs *):has(> code)) { + padding-bottom: calc(1rem - 0.375rem); +} + +/* The dark border token is too close to the page background to read as a + scrollbar thumb; derive a brighter one from the muted foreground instead. */ +:root[data-theme="dark"] + .prose + :where(pre:not(.twoslash, .twoslash pre, blume-panel-tabs *) > code) { + scrollbar-color: color-mix(in oklab, var(--blume-muted-foreground) 55%, transparent) + transparent; +} + +/* Word wrap: long lines wrap instead of scrolling, on every block + (markdown.code.wrap, an attribute on ) or on one (```ts wrap, which + Shiki's fence-meta reader sets as data-wrap). Default code keeps + `white-space: pre`. */ +[data-blume-code-wrap] pre, +[data-blume-code-wrap] pre code, +pre[data-wrap], +pre[data-wrap] code { + overflow-wrap: break-word; + white-space: pre-wrap; +} + +/* An expandable block (```ts expandable) starts collapsed to its first lines, + faded out toward the cut, with a toggle under it (added by the layout + script). Opened, it shows in full: no height cap and no inner scroll. The + collapse needs that script to undo it, so a page without scripting shows + the block the ordinary way. */ +@media (scripting: enabled) { + .prose pre[data-expandable]:not([data-expanded]) > code { + max-height: calc(10lh + 0.375rem); + mask-image: linear-gradient(to bottom, #000 calc(100% - 3lh), transparent); + overflow-y: hidden; + } +} + +.prose pre[data-expandable][data-expanded] > code { + max-height: none; +} + +[data-blume-code-expand] { + align-items: center; + color: var(--blume-muted-foreground); + display: flex; + font-family: var(--font-sans); + font-size: 0.75rem; + font-weight: 500; + gap: 0.25rem; + justify-content: center; + padding-top: 0.5rem; + width: 100%; +} + +[data-blume-code-expand]:hover { + color: var(--blume-foreground); +} + +[data-blume-code-expand] svg { + transition: transform 150ms; +} + +[data-blume-code-expand][aria-expanded="true"] svg { + transform: rotate(180deg); +} + +.prose :where(table) { + font-size: 0.8125rem; +} + +/* Scroll wide tables in place; the wrapper is added by the blume:table-wrap hast + plugin. The border+radius frames the scroll viewport so a clipped wide table + reads as scrollable rather than cut off, and carries the table's vertical + rhythm (the inner table's own margin is zeroed so it fills the frame). */ +.blume-table-scroll { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + margin: 1.5rem 0; + border: 1px solid var(--blume-border); + border-radius: var(--blume-radius); +} +.blume-table-scroll > table { + margin: 0; +} +/* Restore inner padding on every cell. Typography zeroes the first/last cell's + inline padding so a borderless table aligns to the prose margin; inside the + framed wrapper that leaves edge text touching the border. The :is() selector + outweighs Typography's :where()-scoped rules so the outer columns get it too. + + The block padding is 0.75rem against a table line-height near 1.7: at 0.5rem a + cell whose content wrapped put MORE space between its own two lines than + between itself and the next row, so a table of wrapping cells read as one + block rather than as rows. The inline padding stays where it was, deliberately + — widening it comes out of column width in a capped article, and on one corpus + that pushed cells fitting on two lines onto three. */ +.blume-table-scroll :is(th, td) { + padding: 0.75rem; +} +/* Keep column labels on one line so a two-word header does not wrap into a + ragged stack; the table just scrolls a little wider instead. Body cells keep + wrapping so a wide table stays a scroll away, not an endless horizontal run. */ +.blume-table-scroll th { + white-space: nowrap; +} + +/* GFM renders cells as directly, which the descendant form alone + never matches (a cell is not its own descendant). */ +.prose :where(td, th) > code, +.prose :where(td, th) :not(pre) > code { + white-space: normal; +} + +blume-tabs pre, +.not-prose > div > pre { + background: var(--blume-code-background); +} + +/* The Component source pane fills its tab's fixed height (set inline to match the + preview tab) and scrolls the inner code element, leaving the pre static so the + copy button — absolute, pinned to the pre — doesn't drift while it scrolls. */ +pre.blume-source { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; +} + +pre.blume-source > code { + flex: 1; + /* The pane's measured height is authoritative (it can exceed the prose + 24rem cap), so undo the generic prose max-height: the code must fill + whatever height the tab was given. */ + max-height: none; + min-height: 0; + overflow: auto; +} + +/* Code blocks inside tabs sit flush; the tab container owns the frame. These + rules are unlayered like the base `pre` styles, so they win over Tailwind's + layered utilities (which a class on the panel cannot). The second selector + covers , where each code block IS the panel (a direct child of the + tab content wrapper) rather than a
 nested inside a . */
+blume-tabs [data-blume-tab-panel] > pre,
+blume-tabs [data-blume-tab-content] > pre {
+  /* Important to beat the astro-code background rule above; the tab container
+     owns the surface, so the code sits directly on it. */
+  background: transparent !important;
+  border: 0;
+  border-radius: 0;
+  margin: 0;
+}
+
+/* These contexts drop the language bar (`::before` is cleared below), so the
+   bar's padding goes with it and the block keeps the plain inset. Unscoped on
+   purpose:  wraps its pre in its own `.prose` below the component
+   chrome, so without this a titled block on a page with no prose column
+   (PageLayout) would keep the 3.75rem bar padding under a cleared bar. */
+blume-tabs pre[data-language],
+.not-prose pre[data-language] {
+  padding-top: 1rem;
+}
+
+/* Where no language bar holds the copy button — a flush block in tabs or a
+   not-prose component, a bar-less block in prose — the docs layout still pins
+   one over the block's top-right corner, so a first line long enough to reach
+   it would end underneath. A code switcher (CodeGroup, ts2js) hosts the button
+   in its tab strip instead and needs nothing here; for every block that keeps
+   an overlay button, give the scrolling code element enough end padding that
+   the line's tail clears the button at the end of its scroll. Keyed on the
+   injected button itself (which the playground's client-created response pre
+   never gets), and `.prose`-scoped to outrank the base `:where(pre code)`
+   inset. Not a vertical inset — that reserved a strip above every one-line
+   command and read as a rendering bug. */
+.prose :is(blume-tabs pre, .not-prose pre, pre:not([data-language])):not(.twoslash, .twoslash pre, blume-panel-tabs *):has(> [data-blume-copy]) > code {
+  padding-inline-end: 3.5rem;
+}
+
+/* With the assistant on, an Ask button sits beside the copy button, so the
+   line's tail clears both. */
+.prose :is(blume-tabs pre, .not-prose pre, pre:not([data-language])):not(.twoslash, .twoslash pre, blume-panel-tabs *):has(> [data-blume-ask]) > code {
+  padding-inline-end: 5.75rem;
+}
+
+blume-tabs pre[data-language]::before,
+.not-prose pre[data-language]::before {
+  content: none;
+}
+
+/* Pre-hydration layout-shift guard. The horizontal trigger row and
+   single-panel visibility are built by the  custom element's JS:
+   until it upgrades, the trigger row is empty and every panel is in normal flow,
+   so the browser paints all panels stacked vertically (e.g. all four
+   package-manager commands at once) before JS hides them. While the element is
+   :not(:defined), show only the first panel and reserve the trigger row's height
+   so nothing jumps when JS takes over. The child selector covers both 
+   panels and  raw code blocks (which only gain
+   [data-blume-tab-panel] from JS). */
+blume-tabs:not(:defined) [data-blume-tab-content] > *:nth-child(n + 2) {
+  display: none;
+}
+
+blume-tabs:not(:defined) [data-blume-tablist] {
+  /* Matches a trigger's height: py-2.5 + text-xs line-height + border-b-2. */
+  min-height: 2.375rem;
+}
+
+blume-tabs:not(:defined)[data-dropdown="true"] [data-blume-tablist] {
+  /* Dropdown variant: p-3 padding around a select (py-2 + text-sm + border). */
+  min-height: 3.875rem;
+}
+
+/* Code blocks inside request/response example panels sit flush; the panel owns
+   the frame. Unlayered (like the tab rules) so they beat the base `pre` styles
+   that a class on the panel cannot. */
+[data-blume-code-panel] pre {
+  background: transparent !important;
+  border: 0;
+  border-radius: 0;
+  margin: 0;
+}
+
+/* Opt-in line numbers (```ts file.ts lineNumbers): a counter-driven gutter that
+   works in prose and flush (tab / API panel) code blocks alike. */
+pre[data-line-numbers] code {
+  counter-reset: line;
+}
+
+pre[data-line-numbers] .line::before {
+  color: var(--blume-muted-foreground);
+  content: counter(line);
+  counter-increment: line;
+  display: inline-block;
+  margin-right: 1.25rem;
+  text-align: right;
+  user-select: none;
+  width: 1.25rem;
+}
+
+/* Shiki notation transformers (on by default). The notation comments are
+   stripped from the output; these style the classes they leave behind. A styled
+   line becomes a full-width inline-block so its background fills the row; plain
+   lines are left untouched, so blocks without notations render as before. */
+.line.highlighted,
+.line.diff {
+  display: inline-block;
+  width: 100%;
+}
+
+/* The backgrounds need !important to beat the `pre.astro-code span` rule above,
+   which forces token spans transparent; these line/word spans are exceptions. */
+.line.highlighted {
+  background-color: var(--blume-code-highlight) !important;
+  box-shadow: inset 2px 0 0 0 var(--blume-code-highlight-border);
+}
+
+.line.diff.add {
+  background-color: var(--blume-code-add) !important;
+  box-shadow: inset 2px 0 0 0 var(--blume-code-add-border);
+}
+
+.line.diff.remove {
+  background-color: var(--blume-code-remove) !important;
+  box-shadow: inset 2px 0 0 0 var(--blume-code-remove-border);
+}
+
+/* Error and warning lines (`// [!code error]`, `// [!code warning]`) are
+   highlighted lines with a level class, tinted red and amber. */
+.line.highlighted.error {
+  background-color: var(--blume-code-error) !important;
+  box-shadow: inset 2px 0 0 0 var(--blume-code-error-border);
+}
+
+.line.highlighted.warning {
+  background-color: var(--blume-code-warning) !important;
+  box-shadow: inset 2px 0 0 0 var(--blume-code-warning-border);
+}
+
+/* Word highlight (`// [!code word:x]`) wraps matches in an inline span. The
+   element selector keeps it ahead of the transparent token-span rule. */
+span.highlighted-word {
+  background-color: var(--blume-code-word) !important;
+  border-radius: 0.25rem;
+  box-shadow: 0 0 0 1px var(--blume-code-word-border);
+  padding: 0.1em 0.2em;
+}
+
+/* Focus (`// [!code focus]`): dim and blur the rest; reveal on hover. */
+pre:has(.line.focused) .line:not(.focused) {
+  filter: blur(0.085rem);
+  opacity: 0.5;
+  transition:
+    filter 0.2s ease,
+    opacity 0.2s ease;
+}
+
+pre:has(.line.focused):hover .line:not(.focused) {
+  filter: none;
+  opacity: 1;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  pre:has(.line.focused) .line:not(.focused) {
+    transition: none;
+  }
+}
+
+@media (max-width: 640px) {
+  .prose :where(h1) {
+    font-size: 2.25rem;
+  }
+
+  .prose :where(h2) {
+    font-size: 1.625rem;
+  }
+}
+
+.prose :not(pre) > code:not(:where([class~="not-prose"] *)) {
+  background: var(--blume-code-background);
+  padding: 0.15em 0.35em;
+  border-radius: 0.3rem;
+  font-family: var(--font-mono);
+  font-weight: 500;
+}
+
+.prose :not(pre) > code::before,
+.prose :not(pre) > code::after {
+  content: none;
+}
+
+/* Keyboard keys () render as the same bordered badge the search dialog
+   uses for its shortcut hints. Typography's own kbd rule is scoped with
+   :not(.not-prose *), so it never reaches a key inside , ,
+   , or any other not-prose component; this rule is deliberately
+   unscoped from that exclusion so a key looks the same everywhere in the
+   content column. Unlayered, so it outranks the plugin's layered rule. */
+.prose kbd {
+  background: var(--blume-muted);
+  border: 1px solid var(--blume-border);
+  border-radius: 0.25rem;
+  box-shadow: none;
+  color: inherit;
+  font-family: var(--font-mono);
+  font-size: 0.85em;
+  font-weight: 500;
+  padding: 0.125rem 0.25rem;
+}
+
+/* Inline code highlighting: Shiki colors the tokens of a `code`{:lang} snippet
+   via the same dual-theme CSS variables as fenced blocks, keeping the inline
+   pill background. Always on — it only fires on the trailing {:lang} marker. */
+.prose code.blume-inline-code span {
+  color: var(--shiki-light);
+}
+
+:root[data-theme="dark"] .prose code.blume-inline-code span {
+  color: var(--shiki-dark);
+}
+
+/* Print / "Save as PDF" (the page-actions Export → PDF runs window.print()).
+   Strip the surrounding chrome so the printout is just the article. Scoped to
+   the chrome itself, never bare `header`/`aside`: content renders those too
+   (every Callout and Panel is an