@karimsa/mdxserve 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +233 -2
  3. package/client/App.tsx +7 -0
  4. package/client/CodeBlock.tsx +395 -0
  5. package/client/CrossFade.tsx +72 -0
  6. package/client/DocContext.ts +14 -0
  7. package/client/DocView.tsx +107 -0
  8. package/client/ErrorBox.tsx +23 -0
  9. package/client/Heading.tsx +31 -0
  10. package/client/HomeEmptyState.tsx +101 -0
  11. package/client/HomeView.tsx +78 -0
  12. package/client/ListingView.tsx +663 -0
  13. package/client/MdSection.tsx +234 -0
  14. package/client/MdSectionEditor.tsx +233 -0
  15. package/client/Mermaid.tsx +435 -0
  16. package/client/RenderErrorBoundary.tsx +40 -0
  17. package/client/Table.tsx +14 -0
  18. package/client/TaskCheckbox.tsx +38 -0
  19. package/client/api.ts +138 -0
  20. package/client/app.css +372 -0
  21. package/client/builtins/Badge.tsx +109 -0
  22. package/client/builtins/Button.tsx +111 -0
  23. package/client/builtins/Callout.tsx +97 -0
  24. package/client/builtins/Card.tsx +111 -0
  25. package/client/builtins/Chart.tsx +875 -0
  26. package/client/builtins/Diff.tsx +722 -0
  27. package/client/builtins/Dropdown.tsx +417 -0
  28. package/client/builtins/FileTree.tsx +87 -0
  29. package/client/builtins/Kbd.tsx +18 -0
  30. package/client/builtins/Screenshot.tsx +209 -0
  31. package/client/builtins/Sparkline.tsx +63 -0
  32. package/client/builtins/Tabs.tsx +169 -0
  33. package/client/builtins/Tooltip.tsx +52 -0
  34. package/client/builtins/chart-data.ts +133 -0
  35. package/client/builtins/index.ts +167 -0
  36. package/client/design/base/editor.css +151 -0
  37. package/client/design/base/prose.css +143 -0
  38. package/client/design/base/reset.css +79 -0
  39. package/client/design/tokens/colors.css +188 -0
  40. package/client/design/tokens/elevation.css +42 -0
  41. package/client/design/tokens/fonts.css +6 -0
  42. package/client/design/tokens/motion.css +76 -0
  43. package/client/design/tokens/spacing.css +34 -0
  44. package/client/design/tokens/typography.css +56 -0
  45. package/client/doc-module-cache.ts +17 -0
  46. package/client/editor-link.ts +27 -0
  47. package/client/entry.tsx +51 -0
  48. package/client/export-doc.ts +80 -0
  49. package/client/export-save.ts +96 -0
  50. package/client/favicon.svg +1 -0
  51. package/client/file-system-access.d.ts +29 -0
  52. package/client/format.ts +17 -0
  53. package/client/hooks.ts +34 -0
  54. package/client/lucide-icons.d.ts +9 -0
  55. package/client/mdx-components-base.ts +32 -0
  56. package/client/mdx-components.ts +18 -0
  57. package/client/mermaid-chart.ts +109 -0
  58. package/client/mermaid-direction.ts +73 -0
  59. package/client/motion.ts +104 -0
  60. package/client/platform.ts +16 -0
  61. package/client/route-path.ts +15 -0
  62. package/client/router.ts +452 -0
  63. package/client/shell/AppShell.tsx +401 -0
  64. package/client/shell/Footer.tsx +33 -0
  65. package/client/shell/NotFoundView.tsx +22 -0
  66. package/client/shell/Sidebar.tsx +169 -0
  67. package/client/shell/StandaloneShell.tsx +65 -0
  68. package/client/shell/TocRail.tsx +53 -0
  69. package/client/shell/TopBar.tsx +117 -0
  70. package/client/shell/use-doc-width.ts +61 -0
  71. package/client/shell/useToc.ts +77 -0
  72. package/client/ssr-entry.tsx +22 -0
  73. package/client/standalone-entry.tsx +51 -0
  74. package/client/state.ts +90 -0
  75. package/client/theme.ts +65 -0
  76. package/client/ui/Breadcrumb.tsx +49 -0
  77. package/client/ui/ConfirmDeleteDialog.tsx +113 -0
  78. package/client/ui/ExpandModal.tsx +342 -0
  79. package/client/ui/Icon.tsx +114 -0
  80. package/client/ui/IconButton.tsx +63 -0
  81. package/client/ui/Kbd.tsx +17 -0
  82. package/client/ui/PageNav.tsx +77 -0
  83. package/client/ui/ResizeHandle.tsx +201 -0
  84. package/client/ui/SearchDialog.tsx +187 -0
  85. package/client/ui/Tag.tsx +44 -0
  86. package/client/ui/Toast.tsx +189 -0
  87. package/client/ui/TocList.tsx +71 -0
  88. package/client/ui/icon-set.ts +102 -0
  89. package/client/ui/toast-count.ts +28 -0
  90. package/dist/cli.js +5091 -0
  91. package/dist/registry.json +703 -0
  92. package/dist/render-worker.js +145 -0
  93. package/package.json +115 -5
  94. package/skills/mdxserve/SKILL.md +178 -0
@@ -0,0 +1,167 @@
1
+ import type { ComponentType } from "react";
2
+ import type { z } from "zod";
3
+ import Callout, { calloutProps } from "./Callout";
4
+ import Button, { buttonProps } from "./Button";
5
+ import Tooltip, { tooltipProps } from "./Tooltip";
6
+ import Tabs, { Tab, tabsProps, tabProps } from "./Tabs";
7
+ import Badge, { badgeProps } from "./Badge";
8
+ import Diff, { diffProps } from "./Diff";
9
+ import Card, { CardGrid, cardProps, cardGridProps } from "./Card";
10
+ import Kbd, { kbdProps } from "./Kbd";
11
+ import FileTree, { fileTreeProps } from "./FileTree";
12
+ import Chart, { chartProps } from "./Chart";
13
+ import Sparkline, { sparklineProps } from "./Sparkline";
14
+ import Dropdown, { Option, dropdownProps, optionProps } from "./Dropdown";
15
+ import Screenshot, { screenshotProps } from "./Screenshot";
16
+
17
+ export interface BuiltinDefinition {
18
+ name: string;
19
+ description: string;
20
+ whenToUse: string;
21
+ props: z.ZodObject<any>;
22
+ Component: ComponentType<any>;
23
+ }
24
+
25
+ export const builtins: BuiltinDefinition[] = [
26
+ {
27
+ name: "Callout",
28
+ description:
29
+ "A boxed aside with an icon, used to draw attention to a note or warning inline with the text.",
30
+ whenToUse:
31
+ 'Use to highlight a tip, note, or warning that should stand out from surrounding prose. Prefer `tone="warn"` for caveats or risks, `tone="danger"` for breaking changes, `tone="tip"` or `tone="ok"` for positive asides, and the default `tone="note"` for general notes; add `title` when the callout needs a short heading. (`type` is a deprecated alias for `tone`.)',
32
+ props: calloutProps,
33
+ Component: Callout,
34
+ },
35
+ {
36
+ name: "Button",
37
+ description:
38
+ "A clickable button or link with primary/secondary/ghost/danger styles and a small tap animation.",
39
+ whenToUse:
40
+ 'Use for a call to action inside MDX/JSX content — a link styled as a button (`href`) or an interactive action (`onClick`, JSX only). Prefer `variant="primary"` for the main action on a page, `secondary` or `ghost` for less prominent actions, and `danger` for destructive ones. `icon`/`iconRight` take a lucide icon name.',
41
+ props: buttonProps,
42
+ Component: Button,
43
+ },
44
+ {
45
+ name: "Tooltip",
46
+ description:
47
+ "Wraps inline content in a hover/focus tooltip (via tippy.js) that shows a short bit of text.",
48
+ whenToUse:
49
+ "Use to add a short explanation to a word, phrase, or another inline component (e.g. a `Badge`) without cluttering the surrounding prose. Keep `content` brief — it's not meant for long-form text.",
50
+ props: tooltipProps,
51
+ Component: Tooltip,
52
+ },
53
+ {
54
+ name: "Tabs",
55
+ description:
56
+ "A tabbed container that switches between panels of content, each declared with a `Tab` child.",
57
+ whenToUse:
58
+ "Use to present a few alternative or sequential views of content (e.g. per-language code samples, per-OS instructions) without showing them all at once. Each direct child should be a `Tab` with a `label`; the panel content cross-fades and the underline animates between tabs.",
59
+ props: tabsProps,
60
+ Component: Tabs,
61
+ },
62
+ {
63
+ name: "Tab",
64
+ description: "A single labeled panel within a `Tabs` container.",
65
+ whenToUse:
66
+ "Only as a direct child of `<Tabs>`. `label` is required; `value` defaults to a slug of `label`.",
67
+ props: tabProps,
68
+ Component: Tab,
69
+ },
70
+ {
71
+ name: "Badge",
72
+ description: "A small inline pill for a status, tag, or label, with tone/variant/dot options.",
73
+ whenToUse:
74
+ 'Use inline with text (or inside a table cell) to call out a short status or category, e.g. a version, state, or tag. Prefer `variant="soft"` (the default) for most uses; `solid` for emphasis, `outline` for a quieter look; set `dot` for a status indicator. Tones: `neutral`, `teal`, `info`, `ok`, `warn`, `danger`, `note`. (`color` is a deprecated alias for `tone`.)',
75
+ props: badgeProps,
76
+ Component: Badge,
77
+ },
78
+ {
79
+ name: "Diff",
80
+ description:
81
+ "Renders the difference between two code snippets with GitHub-style unified or side-by-side views.",
82
+ whenToUse:
83
+ "Use for before/after API changes, config migrations, or 'change this to that' instructions. Prefer a plain fence when only showing the final code.",
84
+ props: diffProps,
85
+ Component: Diff,
86
+ },
87
+ {
88
+ name: "Card",
89
+ description:
90
+ "A bordered content card with an optional icon, title, description, and meta line.",
91
+ whenToUse:
92
+ "Use for a link or highlighted block that needs more visual weight than a plain link — a related page, an external resource, a next step. Set `href` (or `interactive`) to enable the hover lift and trailing arrow. Group several inside `CardGrid` for a responsive layout.",
93
+ props: cardProps,
94
+ Component: Card,
95
+ },
96
+ {
97
+ name: "CardGrid",
98
+ description: "A responsive 2- or 3-column grid for laying out `Card` elements.",
99
+ whenToUse:
100
+ 'Wrap 2 or more `Card` elements to lay them out side by side; set `columns="3"` for a wider grid.',
101
+ props: cardGridProps,
102
+ Component: CardGrid,
103
+ },
104
+ {
105
+ name: "Kbd",
106
+ description: "A small styled key cap for keyboard shortcuts.",
107
+ whenToUse:
108
+ "Use inline for a literal keystroke, e.g. `<Kbd>⌘</Kbd><Kbd>K</Kbd>`. Not for regular inline code — use a backtick fence for that.",
109
+ props: kbdProps,
110
+ Component: Kbd,
111
+ },
112
+ {
113
+ name: "FileTree",
114
+ description: "A collapsible file/folder tree, for showing a project layout.",
115
+ whenToUse:
116
+ "Use to show the shape of a directory a reader needs to navigate, e.g. 'here's what the generated project looks like'. Pass `nodes` as a plain JSON array of `{ name, path?, children?, open? }`; set `activePath` to highlight one row.",
117
+ props: fileTreeProps,
118
+ Component: FileTree,
119
+ },
120
+ {
121
+ name: "Chart",
122
+ description:
123
+ "A bar, line, area or histogram chart rendered as inline SVG, framed like a code block.",
124
+ whenToUse:
125
+ 'For bar, line and area: `data` is `[{ label, <series>: number }, ...]`; `series` lists which keys to plot (defaults to `["value"]`), for small, self-contained datasets (four series or fewer) that are clearer as a shape than a table. Set `orientation="horizontal"` on a bar chart when labels are long or there are more than ~6 categories. For a histogram, set `type="histogram"` and pass raw numbers as `values`; leave `data`/`series` off and optionally cap the bin count with `bins`. Prefer a table once there\'s more data than a chart can read at a glance.',
126
+ props: chartProps,
127
+ Component: Chart,
128
+ },
129
+ {
130
+ name: "Sparkline",
131
+ description: "A tiny inline trend line with no axes or labels.",
132
+ whenToUse:
133
+ "Use inline (e.g. in a sentence or table cell) to show the shape of a trend without a full chart — 'requests over the last hour ' next to a Sparkline. Needs at least two `values`.",
134
+ props: sparklineProps,
135
+ Component: Sparkline,
136
+ },
137
+ {
138
+ name: "Dropdown",
139
+ description:
140
+ "A menu-style picker: a button that opens a popover list of options — a plain form control, or a content switcher when given `Option` children.",
141
+ whenToUse:
142
+ "Pass `options` (strings or `{ value, label }`) for a form-style select, e.g. a settings picker. Pass `Option` children instead — each with a `label` and its own content — to switch between longer or more numerous panels of content; prefer `Tabs` when there are only 2-4 visible alternatives, and `Dropdown` once the list gets longer than fits comfortably as tabs.",
143
+ props: dropdownProps,
144
+ Component: Dropdown,
145
+ },
146
+ {
147
+ name: "Option",
148
+ description: "A single labeled option within a `Dropdown` content switcher.",
149
+ whenToUse:
150
+ "Only as a direct child of `<Dropdown>`. `label` is required; `value` defaults to a slug of `label`.",
151
+ props: optionProps,
152
+ Component: Option,
153
+ },
154
+ {
155
+ name: "Screenshot",
156
+ description:
157
+ "An image framed as a macOS window — traffic-light buttons, an optional title, a raised border — for app and UI screenshots.",
158
+ whenToUse:
159
+ "Use instead of a plain Markdown image when the picture is a screenshot of an app, web page, or terminal, so it reads as a window rather than a bare bitmap. `src` resolves like a Markdown image; add `title` for the window name and `caption` for a note below. The title bar's expand control opens the image full-screen with pan and zoom, so a dense screenshot can stay small inline. Keep plain `![alt](src)` for diagrams, logos, and photos.",
160
+ props: screenshotProps,
161
+ Component: Screenshot,
162
+ },
163
+ ];
164
+
165
+ export const builtinComponents: Record<string, ComponentType<any>> = Object.fromEntries(
166
+ builtins.map((builtin) => [builtin.name, builtin.Component]),
167
+ );
@@ -0,0 +1,151 @@
1
+ /* Base ProseMirror rules for the inline Tiptap editor (client/MdSectionEditor.tsx).
2
+ Hand-written rather than importing prosemirror-view's style/prosemirror.css
3
+ as a dependency — the file is tiny, and this keeps every value on an
4
+ mdxserve design token so dark mode is free. Selectors and defaults are
5
+ copied 1:1 from node_modules/prosemirror-view/style/prosemirror.css and
6
+ node_modules/prosemirror-gapcursor/style/gapcursor.css (read at
7
+ implementation time), with only the hardcoded colors swapped for tokens. */
8
+
9
+ .ProseMirror {
10
+ position: relative;
11
+ white-space: pre-wrap;
12
+ white-space: break-spaces;
13
+ word-wrap: break-word;
14
+ font-variant-ligatures: none;
15
+ outline: none;
16
+ }
17
+
18
+ /* A selected node (an image, a table cell range) gets the accent ring the
19
+ rest of the design uses for interactive emphasis, instead of
20
+ prosemirror-view's hardcoded `#8cf`. */
21
+ .ProseMirror-selectednode {
22
+ outline: 2px solid var(--border-accent);
23
+ }
24
+
25
+ /* The gap cursor (the blinking bar shown between block nodes that can't
26
+ otherwise take a text cursor, e.g. before/after a table). */
27
+ .ProseMirror-gapcursor {
28
+ display: none;
29
+ pointer-events: none;
30
+ position: absolute;
31
+ }
32
+
33
+ .ProseMirror-gapcursor::after {
34
+ content: "";
35
+ display: block;
36
+ position: absolute;
37
+ top: -2px;
38
+ width: 20px;
39
+ border-top: 1px solid var(--text-body);
40
+ animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
41
+ }
42
+
43
+ @keyframes ProseMirror-cursor-blink {
44
+ to {
45
+ visibility: hidden;
46
+ }
47
+ }
48
+
49
+ .ProseMirror-focused .ProseMirror-gapcursor {
50
+ display: block;
51
+ }
52
+
53
+ /* The editing frame. Entering edit mode must not move a single glyph, so the
54
+ section keeps its read-mode box exactly and the glow is a positioned
55
+ pseudo-element that lays out nothing. Its edges are chosen so the frame
56
+ reads as *replacing* the section rules: the top edge sits where this
57
+ section's h2 rule was, the bottom edge where the next section's h2 rule
58
+ is (that rule is 3px wide and the ring covers it). Sections without an h2
59
+ on either side get half the prose gap instead. */
60
+ .mdx-section[data-editing]::before {
61
+ content: "";
62
+ position: absolute;
63
+ pointer-events: none;
64
+ inset: calc(-1 * var(--prose-gap) / 2) calc(-1 * var(--space-4));
65
+ border-radius: var(--radius-md);
66
+ box-shadow: 0 0 0 3px var(--focus-ring);
67
+ /* The glow fades and grows in on entry; --dur-slow is 0ms under
68
+ prefers-reduced-motion, so it simply appears there. */
69
+ animation: mdx-editor-glow-in var(--dur-slow) var(--ease-out);
70
+ }
71
+
72
+ /* This section opens with an h2: its rule is the frame's top edge. */
73
+ .mdx-section[data-editing]:has(.ProseMirror > h2:first-child)::before {
74
+ top: 0;
75
+ }
76
+
77
+ /* The next section opens with an h2: its rule is the frame's bottom edge
78
+ (that h2 carries the heading gap as margin-top, so the rule sits exactly
79
+ one heading gap below this section's last line). */
80
+ .mdx-section[data-editing]:has(+ .mdx-section > h2:first-child)::before {
81
+ bottom: calc(-1 * var(--prose-gap-heading));
82
+ }
83
+
84
+ @keyframes mdx-editor-glow-in {
85
+ from {
86
+ opacity: 0;
87
+ box-shadow: 0 0 0 0 var(--focus-ring);
88
+ }
89
+ }
90
+
91
+ /* The frame is the focus indicator; reset.css's global `:focus-visible` ring
92
+ would otherwise draw a second one hugging the contenteditable. */
93
+ .mdx-section[data-editing] .ProseMirror:focus-visible {
94
+ box-shadow: none;
95
+ }
96
+
97
+ /* In read mode a top-level h2 draws the section divider (border-top +
98
+ padding-top, prose.css). Inside the frame the line collides with the glow,
99
+ but its box has to stay — dropping the padding would shift the heading up —
100
+ so only the colour goes. */
101
+ .mdx-section[data-editing] .ProseMirror > h2,
102
+ /* …and the next section's rule, which the frame's bottom edge stands in for
103
+ (it would otherwise show through under the rounded corners). */
104
+ .mdx-section[data-editing] + .mdx-section > h2:first-child {
105
+ border-top-color: transparent;
106
+ }
107
+
108
+ /* app.css's `.mdx-prose pre { background: transparent }` assumes CodeFrame's
109
+ own card chrome (client/CodeBlock.tsx) draws the background/border — but a
110
+ code block typed straight into the editor has no CodeFrame around it, so
111
+ restore a plain, static version of that chrome here. */
112
+ .mdx-prose .ProseMirror pre {
113
+ background: var(--code-bg);
114
+ border: 1px solid var(--code-border);
115
+ border-radius: var(--radius-md);
116
+ padding: var(--space-4);
117
+ }
118
+
119
+ /* GFM task items inside the editor: TaskList/TaskItem (@tiptap/extension-list)
120
+ render `<ul data-type="taskList"><li data-checked><label><input type="checkbox"></label><div>…</div></li></ul>`
121
+ (the `data-type="taskItem"` attribute exists only in TaskItem's *parse* rule,
122
+ not in its rendered markup), a different shape than the read-mode markup
123
+ styled in app.css's `.mdx-prose li:has(> input[type=checkbox])` rules — mirror
124
+ the same flex-row checkbox alignment so a task list doesn't visibly reflow
125
+ between read and edit mode. */
126
+ .mdx-prose .ProseMirror ul[data-type="taskList"] {
127
+ list-style: none;
128
+ padding-left: 0;
129
+ }
130
+
131
+ .mdx-prose .ProseMirror ul[data-type="taskList"] > li {
132
+ display: flex;
133
+ align-items: baseline;
134
+ }
135
+
136
+ .mdx-prose .ProseMirror ul[data-type="taskList"] > li > label {
137
+ display: inline-flex;
138
+ align-items: center;
139
+ margin: 0 0.5em 0 0;
140
+ user-select: none;
141
+ }
142
+
143
+ .mdx-prose .ProseMirror ul[data-type="taskList"] > li > label input[type="checkbox"] {
144
+ accent-color: var(--teal-550);
145
+ vertical-align: -0.1em;
146
+ }
147
+
148
+ .mdx-prose .ProseMirror ul[data-type="taskList"] > li > div {
149
+ flex: 1 1 auto;
150
+ min-width: 0;
151
+ }
@@ -0,0 +1,143 @@
1
+ /* Element rules carry a `:not(.not-prose *)` guard (added at port time) so builtin
2
+ components, which set `not-prose` on their root, keep their own typography. */
3
+ /* .mdx-prose — the rendered-markdown container. This is the product's core surface:
4
+ every vertical rhythm decision in mdxserve lives here. */
5
+ .mdx-prose {
6
+ font: var(--type-prose);
7
+ color: var(--text-body);
8
+ /* No max-width here: the column width is owned by AppShell's wrapper
9
+ (`max-w-prose` by default, or the user's dragged doc width), so the
10
+ article must fill it or the resize handles would only move the frame. */
11
+ /* Long identifiers in headings/prose (`Foo.get_arrow_schema`) have no break
12
+ opportunities; let them wrap instead of pushing past the column. */
13
+ overflow-wrap: break-word;
14
+ }
15
+ /* `<MdSection>` (client/MdSection.tsx) wraps every editable run of top-level
16
+ markdown nodes but must be visually transparent in read mode — no padding,
17
+ border or overflow, just `position: relative` for the hover pencil — so
18
+ its children's margins keep collapsing through it exactly as if `.mdx-prose`
19
+ were their direct parent. */
20
+ .mdx-section {
21
+ position: relative;
22
+ }
23
+ /* The direct-child rhythm rules below therefore have to reach three levels:
24
+ `.mdx-prose` itself (docs with no sections, or content before the first
25
+ one), `.mdx-section` (the normal wrapped case), and `.mdx-prose .ProseMirror`
26
+ (a section's own edit-mode surface) — the last one is what makes editing
27
+ WYSIWYG, since without it a heading/list typed into the editor would use
28
+ ProseMirror's default spacing instead of mdxserve's. */
29
+ :is(.mdx-prose, .mdx-section, .mdx-prose .ProseMirror) > * + * {
30
+ margin-top: var(--prose-gap);
31
+ }
32
+ .mdx-prose h1:not(:where(.not-prose, .not-prose *)) {
33
+ font: var(--type-h1);
34
+ margin-top: 0;
35
+ }
36
+ .mdx-prose h2:not(:where(.not-prose, .not-prose *)) {
37
+ font: var(--type-h2);
38
+ letter-spacing: var(--tracking-tight);
39
+ }
40
+ .mdx-prose h3:not(:where(.not-prose, .not-prose *)) {
41
+ font: var(--type-h3);
42
+ letter-spacing: var(--tracking-snug);
43
+ }
44
+ .mdx-prose h4:not(:where(.not-prose, .not-prose *)) {
45
+ font: var(--type-h4);
46
+ }
47
+ .mdx-prose h2:not(:where(.not-prose, .not-prose *)),
48
+ .mdx-prose h3:not(:where(.not-prose, .not-prose *)),
49
+ .mdx-prose h4:not(:where(.not-prose, .not-prose *)) {
50
+ position: relative;
51
+ }
52
+ :is(.mdx-prose, .mdx-section, .mdx-prose .ProseMirror) > h2 {
53
+ margin-top: var(--prose-gap-heading);
54
+ padding-top: var(--space-6);
55
+ border-top: 1px solid var(--border-subtle);
56
+ }
57
+ :is(.mdx-prose, .mdx-section, .mdx-prose .ProseMirror) > h3,
58
+ :is(.mdx-prose, .mdx-section, .mdx-prose .ProseMirror) > h4 {
59
+ margin-top: var(--prose-gap-block);
60
+ }
61
+ .mdx-prose strong:not(:where(.not-prose, .not-prose *)) {
62
+ font-weight: var(--weight-bold);
63
+ color: var(--text-heading);
64
+ }
65
+ .mdx-prose em:not(:where(.not-prose, .not-prose *)) {
66
+ font-style: italic;
67
+ }
68
+ .mdx-prose code:not(:where(.not-prose, .not-prose *)) {
69
+ font: var(--type-code-inline);
70
+ background: var(--code-bg-inline);
71
+ color: var(--text-accent);
72
+ padding: 0.12em 0.34em;
73
+ border-radius: var(--radius-xs);
74
+ }
75
+ .mdx-prose a code:not(:where(.not-prose, .not-prose *)) {
76
+ color: inherit;
77
+ }
78
+ .mdx-prose ul:not(:where(.not-prose, .not-prose *)),
79
+ .mdx-prose ol:not(:where(.not-prose, .not-prose *)) {
80
+ margin-bottom: 0;
81
+ padding-left: 1.35em;
82
+ }
83
+ .mdx-prose li + li:not(:where(.not-prose, .not-prose *)) {
84
+ margin-top: var(--space-2);
85
+ }
86
+ .mdx-prose li::marker:not(:where(.not-prose, .not-prose *)) {
87
+ color: var(--text-subtle);
88
+ }
89
+ .mdx-prose blockquote:not(:where(.not-prose, .not-prose *)) {
90
+ margin-bottom: 0;
91
+ padding: var(--space-1) 0 var(--space-1) var(--space-5);
92
+ border-left: 2px solid var(--border-accent);
93
+ color: var(--text-muted);
94
+ font-style: normal;
95
+ }
96
+ .mdx-prose table:not(:where(.not-prose, .not-prose *)) {
97
+ border-collapse: collapse;
98
+ width: 100%;
99
+ font-size: var(--size-md);
100
+ }
101
+ .mdx-prose th:not(:where(.not-prose, .not-prose *)) {
102
+ text-align: left;
103
+ font-weight: var(--weight-semibold);
104
+ color: var(--text-heading);
105
+ border-bottom: 1px solid var(--border-strong);
106
+ padding: var(--space-2-5) var(--space-3);
107
+ }
108
+ .mdx-prose td:not(:where(.not-prose, .not-prose *)) {
109
+ border-bottom: 1px solid var(--border-subtle);
110
+ padding: var(--space-2-5) var(--space-3);
111
+ vertical-align: top;
112
+ }
113
+ .mdx-prose img:not(:where(.not-prose, .not-prose *)) {
114
+ max-width: 100%;
115
+ border-radius: var(--radius-md);
116
+ border: 1px solid var(--border-default);
117
+ }
118
+ .mdx-prose hr:not(:where(.not-prose, .not-prose *)) {
119
+ margin: var(--space-10) 0;
120
+ }
121
+ /* anchor affordance: the ¶-style link appears on heading hover, never in print */
122
+ .mdx-anchor {
123
+ position: absolute;
124
+ left: -1.1em;
125
+ /* Above DocView's ResizeHandle (z-10, absolute inset-y-0 on the same
126
+ container): the glyph hangs in the left gutter where the handle lives,
127
+ and without this the handle takes the hover and the click. */
128
+ z-index: 20;
129
+ /* `top: 0` would sit in an h2's padding-top (the rule above it); anchor
130
+ to the bottom of the box so the glyph shares the heading's last line. */
131
+ bottom: 0;
132
+ opacity: 0;
133
+ color: var(--text-subtle);
134
+ text-decoration: none;
135
+ font-weight: var(--weight-regular);
136
+ transition: opacity var(--dur-fast) var(--ease-standard);
137
+ }
138
+ .mdx-prose h2:hover .mdx-anchor,
139
+ .mdx-prose h3:hover .mdx-anchor,
140
+ .mdx-prose h4:hover .mdx-anchor,
141
+ .mdx-anchor:focus-visible {
142
+ opacity: 1;
143
+ }
@@ -0,0 +1,79 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ }
6
+ html {
7
+ -webkit-text-size-adjust: 100%;
8
+ }
9
+ body {
10
+ margin: 0;
11
+ background: var(--surface-page);
12
+ color: var(--text-body);
13
+ font: var(--type-ui);
14
+ -webkit-font-smoothing: antialiased;
15
+ text-rendering: optimizeLegibility;
16
+ }
17
+ h1,
18
+ h2,
19
+ h3,
20
+ h4,
21
+ h5,
22
+ h6 {
23
+ margin: 0;
24
+ color: var(--text-heading);
25
+ letter-spacing: var(--tracking-tight);
26
+ text-wrap: balance;
27
+ }
28
+ p {
29
+ margin: 0;
30
+ text-wrap: pretty;
31
+ }
32
+ /* Link colour is scoped to prose (port-time change): the design applied it to every <a>,
33
+ but this sheet is unlayered, so that rule beat Tailwind utilities on anchors that are
34
+ buttons, cards and nav rows. UI anchors style themselves. */
35
+ .mdx-prose a:not(:where(.not-prose, .not-prose *)) {
36
+ color: var(--text-link);
37
+ text-decoration: underline;
38
+ text-decoration-color: color-mix(in oklab, var(--text-link) 35%, transparent);
39
+ text-underline-offset: 0.18em;
40
+ transition: var(--transition-color);
41
+ }
42
+ .mdx-prose a:not(:where(.not-prose, .not-prose *)):hover {
43
+ color: var(--text-link-hover);
44
+ text-decoration-color: currentColor;
45
+ }
46
+ code,
47
+ pre,
48
+ kbd,
49
+ samp {
50
+ font-family: var(--font-mono);
51
+ }
52
+ /* (button/input font+color inherit removed at port time — Tailwind preflight already does it,
53
+ and an unlayered copy here would beat utility classes on buttons.) */
54
+ :focus-visible {
55
+ outline: none;
56
+ box-shadow: var(--shadow-focus);
57
+ border-radius: var(--radius-sm);
58
+ }
59
+ ::selection {
60
+ background: color-mix(in oklab, var(--teal-400) 28%, transparent);
61
+ }
62
+ hr {
63
+ border: 0;
64
+ border-top: 1px solid var(--border-default);
65
+ }
66
+ ::-webkit-scrollbar {
67
+ width: 10px;
68
+ height: 10px;
69
+ }
70
+ ::-webkit-scrollbar-thumb {
71
+ background: var(--border-strong);
72
+ border-radius: var(--radius-pill);
73
+ border: 3px solid transparent;
74
+ background-clip: content-box;
75
+ }
76
+ ::-webkit-scrollbar-thumb:hover {
77
+ background: var(--n-400);
78
+ background-clip: content-box;
79
+ }