radiant-docs 0.1.16 → 0.1.20

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "radiant-docs",
3
- "version": "0.1.16",
3
+ "version": "0.1.20",
4
4
  "description": "CLI tool for previewing Radiant documentation locally",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,8 +10,8 @@
10
10
  "bundle-template": "node scripts/bundle-template.js",
11
11
  "build": "tsup src/index.ts --format esm --clean && npm run bundle-template",
12
12
  "dev": "tsup src/index.ts --format esm --watch",
13
- "prepublishOnly": "npm run build && npm version patch",
14
- "publish": "npm publish"
13
+ "prepublishOnly": "npm run build",
14
+ "release": "npm version patch && npm publish"
15
15
  },
16
16
  "dependencies": {
17
17
  "chokidar": "^4.0.3",
@@ -340,61 +340,60 @@ const renderedCodeLinesHtml = normalizedTokenLines
340
340
  </div>
341
341
  </div>
342
342
  </div>
343
- </div>
344
-
345
- <script is:inline>
346
- (() => {
347
- const script = document.currentScript;
348
- if (!(script instanceof HTMLScriptElement)) return;
349
-
350
- const root = script.previousElementSibling;
351
- if (!(root instanceof HTMLElement)) return;
352
-
353
- const copyButtons = root.querySelectorAll("[data-rd-copy-trigger='true']");
354
- if (copyButtons.length === 0) return;
355
-
356
- const setCopiedState = (button, copied) => {
357
- const copyIcon = button.querySelector("[data-rd-copy-icon]");
358
- const checkIcon = button.querySelector("[data-rd-copy-check]");
343
+ <script is:inline>
344
+ (() => {
345
+ const script = document.currentScript;
346
+ if (!(script instanceof HTMLScriptElement)) return;
359
347
 
360
- if (!copyIcon || !checkIcon) return;
348
+ const root = script.closest("[data-rd-code-block-root='true']");
349
+ if (!(root instanceof HTMLElement)) return;
361
350
 
362
- if (copied) {
363
- copyIcon.classList.add("scale-50", "opacity-0", "-rotate-6");
364
- copyIcon.classList.remove("scale-100", "opacity-100", "rotate-0");
351
+ const copyButtons = root.querySelectorAll("[data-rd-copy-trigger='true']");
352
+ if (copyButtons.length === 0) return;
365
353
 
366
- checkIcon.classList.remove("scale-50", "opacity-0", "rotate-6");
367
- checkIcon.classList.add("scale-110", "opacity-100", "rotate-0");
368
- return;
369
- }
370
-
371
- copyIcon.classList.remove("scale-50", "opacity-0", "-rotate-6");
372
- copyIcon.classList.add("scale-100", "opacity-100", "rotate-0");
354
+ const setCopiedState = (button, copied) => {
355
+ const copyIcon = button.querySelector("[data-rd-copy-icon]");
356
+ const checkIcon = button.querySelector("[data-rd-copy-check]");
373
357
 
374
- checkIcon.classList.remove("scale-110", "opacity-100", "rotate-0");
375
- checkIcon.classList.add("scale-50", "opacity-0", "rotate-6");
376
- };
358
+ if (!copyIcon || !checkIcon) return;
377
359
 
378
- copyButtons.forEach((button) => {
379
- let timeoutId = null;
380
- button.addEventListener("click", async () => {
381
- const encodedCopyValue =
382
- button.getAttribute("data-rd-copy-content") ?? "";
383
- const copyValue = decodeURIComponent(encodedCopyValue);
360
+ if (copied) {
361
+ copyIcon.classList.add("scale-50", "opacity-0", "-rotate-6");
362
+ copyIcon.classList.remove("scale-100", "opacity-100", "rotate-0");
384
363
 
385
- try {
386
- await navigator.clipboard.writeText(copyValue);
387
- setCopiedState(button, true);
364
+ checkIcon.classList.remove("scale-50", "opacity-0", "rotate-6");
365
+ checkIcon.classList.add("scale-110", "opacity-100", "rotate-0");
366
+ return;
367
+ }
388
368
 
389
- if (timeoutId) window.clearTimeout(timeoutId);
390
- timeoutId = window.setTimeout(() => {
369
+ copyIcon.classList.remove("scale-50", "opacity-0", "-rotate-6");
370
+ copyIcon.classList.add("scale-100", "opacity-100", "rotate-0");
371
+
372
+ checkIcon.classList.remove("scale-110", "opacity-100", "rotate-0");
373
+ checkIcon.classList.add("scale-50", "opacity-0", "rotate-6");
374
+ };
375
+
376
+ copyButtons.forEach((button) => {
377
+ let timeoutId = null;
378
+ button.addEventListener("click", async () => {
379
+ const encodedCopyValue =
380
+ button.getAttribute("data-rd-copy-content") ?? "";
381
+ const copyValue = decodeURIComponent(encodedCopyValue);
382
+
383
+ try {
384
+ await navigator.clipboard.writeText(copyValue);
385
+ setCopiedState(button, true);
386
+
387
+ if (timeoutId) window.clearTimeout(timeoutId);
388
+ timeoutId = window.setTimeout(() => {
389
+ setCopiedState(button, false);
390
+ timeoutId = null;
391
+ }, 1200);
392
+ } catch {
391
393
  setCopiedState(button, false);
392
- timeoutId = null;
393
- }, 1200);
394
- } catch {
395
- setCopiedState(button, false);
396
- }
394
+ }
395
+ });
397
396
  });
398
- });
399
- })();
400
- </script>
397
+ })();
398
+ </script>
399
+ </div>
@@ -100,19 +100,53 @@ const isInitiallyExpanded = shouldShowAllCode || totalLineCount <= visibleLines;
100
100
  </div>
101
101
 
102
102
  <style>
103
- .rd-component-preview :global(.group\/prose-code) {
103
+ .rd-component-preview :global([data-rd-preview-heading="true"]) {
104
+ color: var(--tw-prose-headings, currentColor);
105
+ font-weight: 600;
106
+ }
107
+
108
+ .rd-component-preview :global([data-rd-preview-heading-level="2"]) {
109
+ font-size: 1.5em;
110
+ line-height: 1.3333333;
111
+ margin-top: 2em;
112
+ margin-bottom: 1em;
113
+ }
114
+
115
+ .rd-component-preview :global([data-rd-preview-heading-level="3"]) {
116
+ font-size: 1.25em;
117
+ line-height: 1.6;
118
+ margin-top: 1.6em;
119
+ margin-bottom: 0.6em;
120
+ }
121
+
122
+ .rd-component-preview :global([data-rd-preview-heading-level="4"]) {
123
+ font-size: 1em;
124
+ line-height: 1.5;
125
+ margin-top: 1.5em;
126
+ margin-bottom: 0.5em;
127
+ }
128
+
129
+ .rd-component-preview :global([data-rd-preview-heading-level="5"]),
130
+ .rd-component-preview :global([data-rd-preview-heading-level="6"]) {
131
+ font-size: 0.875em;
132
+ line-height: 1.5714286;
133
+ margin-top: 1.4285714em;
134
+ margin-bottom: 0.5714286em;
135
+ }
136
+
137
+ .rd-component-preview__code :global(.group\/prose-code) {
104
138
  margin-top: 0 !important;
105
139
  margin-bottom: 0 !important;
106
140
  }
107
141
 
108
- .rd-component-preview :global(.group\/prose-code > div) {
142
+ .rd-component-preview__code :global(.group\/prose-code > div) {
109
143
  background-color: var(--color-neutral-50) !important;
110
144
  border-top-left-radius: 0 !important;
111
145
  border-top-right-radius: 0 !important;
112
146
  }
113
147
 
114
- .rd-component-preview :global(.group\/prose-code pre),
115
- .rd-component-preview :global(.group\/prose-code code) {
148
+ .rd-component-preview__code :global(.group\/prose-code pre),
149
+ .rd-component-preview__code :global(.group\/prose-code code) {
116
150
  background-color: var(--color-neutral-50) !important;
117
151
  }
118
152
 
@@ -56,6 +56,17 @@ type MdxJsxTextElementNode = {
56
56
  children?: unknown[];
57
57
  };
58
58
 
59
+ type HeadingNode = {
60
+ type: "heading";
61
+ depth?: number;
62
+ children?: unknown[];
63
+ };
64
+
65
+ type NodeWithChildren = {
66
+ type?: string;
67
+ children?: unknown[];
68
+ };
69
+
59
70
  const COMPONENT_PREVIEW_NAME = "ComponentPreview";
60
71
  const COMPONENT_PREVIEW_BLOCK_NAME = "ComponentPreviewBlock";
61
72
  const COMPONENT_PREVIEW_LANGUAGES = new Set(["jsx", "tsx", "mdx"]);
@@ -192,44 +203,83 @@ function readBooleanAttribute(
192
203
  return parseBooleanAttributeValue(attribute.value);
193
204
  }
194
205
 
195
- function parseComponentPreviewChildren(rawCode: string): unknown[] {
196
- const parsedTree = fromMarkdown(rawCode, {
197
- extensions: [gfm(), mdxjs()],
198
- mdastExtensions: [gfmFromMarkdown(), mdxFromMarkdown()],
199
- }) as Root;
206
+ function normalizePreviewHeadingDepth(depth: number | undefined): number {
207
+ if (depth === 1) return 2;
208
+ if (depth === 2 || depth === 3 || depth === 4 || depth === 5 || depth === 6) {
209
+ return depth;
210
+ }
211
+ return 2;
212
+ }
200
213
 
201
- const children = Array.isArray(parsedTree.children) ? parsedTree.children : [];
214
+ function transformPreviewChildren(children: unknown[] | undefined): unknown[] {
215
+ if (!Array.isArray(children)) return [];
216
+ return children.map((child) => transformPreviewNode(child));
217
+ }
202
218
 
219
+ function transformPreviewNode(node: unknown): unknown {
203
220
  // Promote a single inline MDX JSX node into flow-level JSX so the preview
204
221
  // renders block components (e.g. <Callout />) as expected.
205
- return children.map((node) => {
206
- const paragraphNode = node as ParagraphNode;
207
- if (
208
- paragraphNode.type !== "paragraph" ||
209
- !Array.isArray(paragraphNode.children) ||
210
- paragraphNode.children.length !== 1
211
- ) {
212
- return node;
213
- }
222
+ const headingNode = node as HeadingNode;
223
+ if (headingNode.type === "heading") {
224
+ const depth = normalizePreviewHeadingDepth(headingNode.depth);
225
+ return {
226
+ type: "mdxJsxFlowElement",
227
+ name: "div",
228
+ attributes: [
229
+ createAttribute("data-rd-preview-heading", "true"),
230
+ createAttribute("data-rd-preview-heading-level", String(depth)),
231
+ createAttribute("role", "heading"),
232
+ createAttribute("aria-level", String(depth)),
233
+ ],
234
+ children: transformPreviewChildren(headingNode.children),
235
+ };
236
+ }
214
237
 
238
+ const paragraphNode = node as ParagraphNode;
239
+ if (
240
+ paragraphNode.type === "paragraph" &&
241
+ Array.isArray(paragraphNode.children) &&
242
+ paragraphNode.children.length === 1
243
+ ) {
215
244
  const onlyChild = paragraphNode.children[0] as MdxJsxTextElementNode;
216
245
  if (
217
- onlyChild.type !== "mdxJsxTextElement" ||
218
- typeof onlyChild.name !== "string" ||
219
- onlyChild.name.trim().length === 0
246
+ onlyChild.type === "mdxJsxTextElement" &&
247
+ typeof onlyChild.name === "string" &&
248
+ onlyChild.name.trim().length > 0
220
249
  ) {
221
- return node;
250
+ return {
251
+ type: "mdxJsxFlowElement",
252
+ name: onlyChild.name,
253
+ attributes: Array.isArray(onlyChild.attributes)
254
+ ? onlyChild.attributes
255
+ : [],
256
+ children: transformPreviewChildren(onlyChild.children),
257
+ };
222
258
  }
259
+ }
223
260
 
261
+ const nodeWithChildren = node as NodeWithChildren;
262
+ if (Array.isArray(nodeWithChildren.children)) {
224
263
  return {
225
- type: "mdxJsxFlowElement",
226
- name: onlyChild.name,
227
- attributes: Array.isArray(onlyChild.attributes)
228
- ? onlyChild.attributes
229
- : [],
230
- children: Array.isArray(onlyChild.children) ? onlyChild.children : [],
264
+ ...nodeWithChildren,
265
+ children: transformPreviewChildren(nodeWithChildren.children),
231
266
  };
232
- });
267
+ }
268
+
269
+ return node;
270
+ }
271
+
272
+ function parseComponentPreviewChildren(rawCode: string): unknown[] {
273
+ const parsedTree = fromMarkdown(rawCode, {
274
+ extensions: [gfm(), mdxjs()],
275
+ mdastExtensions: [gfmFromMarkdown(), mdxFromMarkdown()],
276
+ }) as Root;
277
+
278
+ transformCodeBlockNodes(parsedTree);
279
+
280
+ return transformPreviewChildren(
281
+ Array.isArray(parsedTree.children) ? parsedTree.children : [],
282
+ );
233
283
  }
234
284
 
235
285
  function getNearestMdxJsxFlowElementName(
@@ -274,135 +324,139 @@ function buildDefaultCodeGroupFileName(
274
324
  return `file-name-${codeIndexInGroup + 1}.${extension}`;
275
325
  }
276
326
 
277
- export const remarkCodeBlockComponent: Plugin<[], Root> = () => {
278
- return (tree) => {
279
- visitParents(tree, "code", (node, ancestors) => {
280
- const codeNode = node as CodeNode;
281
- const parent = ancestors[ancestors.length - 1] as ParentNode | undefined;
282
- const siblings = parent?.children;
283
- if (!siblings) return;
327
+ function transformCodeBlockNodes(tree: Root): void {
328
+ visitParents(tree, "code", (node, ancestors) => {
329
+ const codeNode = node as CodeNode;
330
+ const parent = ancestors[ancestors.length - 1] as ParentNode | undefined;
331
+ const siblings = parent?.children;
332
+ if (!siblings) return;
333
+
334
+ const currentIndex = siblings.indexOf(node);
335
+ if (currentIndex < 0) return;
336
+
337
+ const nearestMdxFlowElementName = getNearestMdxJsxFlowElementName(
338
+ ancestors,
339
+ );
340
+ const isInsideCodeGroup = nearestMdxFlowElementName === "CodeGroup";
341
+ const isInsideComponentPreview =
342
+ nearestMdxFlowElementName === COMPONENT_PREVIEW_NAME;
343
+ const componentPreviewNode = isInsideComponentPreview
344
+ ? getNearestMdxJsxFlowElement(ancestors, COMPONENT_PREVIEW_NAME)
345
+ : null;
346
+ const isInsideInlineMdx = isInsideMdxJsxTextElement(ancestors);
347
+ if (isInsideInlineMdx) return;
348
+
349
+ const meta = readMetaString(codeNode.meta);
350
+ const parsedMeta = parseParsedCodeMeta(meta);
351
+ const language =
352
+ typeof codeNode.lang === "string" && codeNode.lang.trim().length > 0
353
+ ? codeNode.lang.trim()
354
+ : "plaintext";
355
+ const normalizedLanguage = language.trim().toLowerCase();
356
+ const rawCode = typeof codeNode.value === "string" ? codeNode.value : "";
357
+ const codeIndexInGroup = isInsideCodeGroup
358
+ ? siblings
359
+ .slice(0, currentIndex)
360
+ .filter(
361
+ (sibling) => (sibling as { type?: string }).type === "code",
362
+ ).length
363
+ : 0;
364
+ const fileName =
365
+ parsedMeta.filename.length > 0
366
+ ? parsedMeta.filename
367
+ : isInsideCodeGroup
368
+ ? buildDefaultCodeGroupFileName(language, codeIndexInGroup)
369
+ : "";
370
+ const showFilename = isInsideCodeGroup || fileName.length > 0;
371
+
372
+ const attributes: MdxJsxAttributeNode[] = [
373
+ createAttribute("language", language),
374
+ createAttribute("raw", rawCode),
375
+ createAttribute("showFilename", showFilename ? "true" : "false"),
376
+ createAttribute(
377
+ "showLineNumbers",
378
+ parsedMeta.showLineNumbers ? "true" : "false",
379
+ ),
380
+ ];
381
+
382
+ if (parsedMeta.hideLanguageIcon) {
383
+ attributes.push(createAttribute("hideLanguageIcon", "true"));
384
+ }
284
385
 
285
- const currentIndex = siblings.indexOf(node);
286
- if (currentIndex < 0) return;
386
+ if (showFilename) {
387
+ attributes.push(createAttribute("filename", fileName));
388
+ }
287
389
 
288
- const nearestMdxFlowElementName = getNearestMdxJsxFlowElementName(
289
- ancestors,
290
- );
291
- const isInsideCodeGroup = nearestMdxFlowElementName === "CodeGroup";
292
- const isInsideComponentPreview =
293
- nearestMdxFlowElementName === COMPONENT_PREVIEW_NAME;
294
- const componentPreviewNode = isInsideComponentPreview
295
- ? getNearestMdxJsxFlowElement(ancestors, COMPONENT_PREVIEW_NAME)
296
- : null;
297
- const isInsideInlineMdx = isInsideMdxJsxTextElement(ancestors);
298
- if (isInsideInlineMdx) return;
299
-
300
- const meta = readMetaString(codeNode.meta);
301
- const parsedMeta = parseParsedCodeMeta(meta);
302
- const language =
303
- typeof codeNode.lang === "string" && codeNode.lang.trim().length > 0
304
- ? codeNode.lang.trim()
305
- : "plaintext";
306
- const normalizedLanguage = language.trim().toLowerCase();
307
- const rawCode = typeof codeNode.value === "string" ? codeNode.value : "";
308
- const codeIndexInGroup = isInsideCodeGroup
309
- ? siblings
310
- .slice(0, currentIndex)
311
- .filter(
312
- (sibling) => (sibling as { type?: string }).type === "code",
313
- ).length
314
- : 0;
315
- const fileName =
316
- parsedMeta.filename.length > 0
317
- ? parsedMeta.filename
318
- : isInsideCodeGroup
319
- ? buildDefaultCodeGroupFileName(language, codeIndexInGroup)
320
- : "";
321
- const showFilename = isInsideCodeGroup || fileName.length > 0;
322
-
323
- const attributes: MdxJsxAttributeNode[] = [
324
- createAttribute("language", language),
325
- createAttribute("raw", rawCode),
326
- createAttribute("showFilename", showFilename ? "true" : "false"),
327
- createAttribute(
328
- "showLineNumbers",
329
- parsedMeta.showLineNumbers ? "true" : "false",
330
- ),
331
- ];
332
-
333
- if (parsedMeta.hideLanguageIcon) {
334
- attributes.push(createAttribute("hideLanguageIcon", "true"));
335
- }
390
+ if (isInsideCodeGroup) {
391
+ attributes.push(createAttribute("inCodeGroup", "true"));
392
+ }
336
393
 
337
- if (showFilename) {
338
- attributes.push(createAttribute("filename", fileName));
339
- }
394
+ if (parsedMeta.highlightedLines.length > 0) {
395
+ attributes.push(
396
+ createAttribute("highlightedLines", parsedMeta.highlightedLines),
397
+ );
398
+ }
340
399
 
341
- if (isInsideCodeGroup) {
342
- attributes.push(createAttribute("inCodeGroup", "true"));
343
- }
400
+ if (parsedMeta.collapsedLines.length > 0) {
401
+ attributes.push(
402
+ createAttribute("collapsedLines", parsedMeta.collapsedLines),
403
+ );
404
+ }
344
405
 
345
- if (parsedMeta.highlightedLines.length > 0) {
346
- attributes.push(
347
- createAttribute("highlightedLines", parsedMeta.highlightedLines),
406
+ if (isInsideComponentPreview) {
407
+ if (!COMPONENT_PREVIEW_LANGUAGES.has(normalizedLanguage)) {
408
+ throw new Error(
409
+ `[USER_ERROR]: <${COMPONENT_PREVIEW_NAME}>: Fenced code blocks must use jsx, tsx, or mdx language (received "${language}")`,
348
410
  );
349
411
  }
350
412
 
351
- if (parsedMeta.collapsedLines.length > 0) {
352
- attributes.push(
353
- createAttribute("collapsedLines", parsedMeta.collapsedLines),
413
+ let previewChildren: unknown[] = [];
414
+ try {
415
+ previewChildren = parseComponentPreviewChildren(rawCode);
416
+ } catch (error) {
417
+ const reason =
418
+ error instanceof Error && error.message.trim().length > 0
419
+ ? ` -> ${error.message}`
420
+ : "";
421
+ throw new Error(
422
+ `[USER_ERROR]: <${COMPONENT_PREVIEW_NAME}>: Failed to parse fenced code block as MDX content${reason}`,
354
423
  );
355
424
  }
356
425
 
357
- if (isInsideComponentPreview) {
358
- if (!COMPONENT_PREVIEW_LANGUAGES.has(normalizedLanguage)) {
359
- throw new Error(
360
- `[USER_ERROR]: <${COMPONENT_PREVIEW_NAME}>: Fenced code blocks must use jsx, tsx, or mdx language (received "${language}")`,
361
- );
362
- }
363
-
364
- let previewChildren: unknown[] = [];
365
- try {
366
- previewChildren = parseComponentPreviewChildren(rawCode);
367
- } catch (error) {
368
- const reason =
369
- error instanceof Error && error.message.trim().length > 0
370
- ? ` -> ${error.message}`
371
- : "";
372
- throw new Error(
373
- `[USER_ERROR]: <${COMPONENT_PREVIEW_NAME}>: Failed to parse fenced code block as MDX content${reason}`,
374
- );
375
- }
376
-
377
- const previewAttributes = attributes.filter(
378
- (attribute) => attribute.name !== "inCodeGroup",
379
- );
380
- const showAllCode = readBooleanAttribute(
381
- componentPreviewNode?.attributes,
382
- "showAllCode",
383
- );
384
- if (showAllCode) {
385
- previewAttributes.push(createAttribute("showAllCode", "true"));
386
- }
387
- const previewNode: MdxJsxFlowElementNode = {
388
- type: "mdxJsxFlowElement",
389
- name: COMPONENT_PREVIEW_BLOCK_NAME,
390
- attributes: previewAttributes,
391
- children: previewChildren,
392
- };
393
- siblings[currentIndex] = previewNode;
394
- return;
426
+ const previewAttributes = attributes.filter(
427
+ (attribute) => attribute.name !== "inCodeGroup",
428
+ );
429
+ const showAllCode = readBooleanAttribute(
430
+ componentPreviewNode?.attributes,
431
+ "showAllCode",
432
+ );
433
+ if (showAllCode) {
434
+ previewAttributes.push(createAttribute("showAllCode", "true"));
395
435
  }
396
-
397
- const replacementNode: MdxJsxFlowElementNode = {
436
+ const previewNode: MdxJsxFlowElementNode = {
398
437
  type: "mdxJsxFlowElement",
399
- name: INTERNAL_CODE_BLOCK_NAME,
400
- attributes,
401
- children: [],
438
+ name: COMPONENT_PREVIEW_BLOCK_NAME,
439
+ attributes: previewAttributes,
440
+ children: previewChildren,
402
441
  };
442
+ siblings[currentIndex] = previewNode;
443
+ return;
444
+ }
403
445
 
404
- siblings[currentIndex] = replacementNode;
405
- });
446
+ const replacementNode: MdxJsxFlowElementNode = {
447
+ type: "mdxJsxFlowElement",
448
+ name: INTERNAL_CODE_BLOCK_NAME,
449
+ attributes,
450
+ children: [],
451
+ };
452
+
453
+ siblings[currentIndex] = replacementNode;
454
+ });
455
+ }
456
+
457
+ export const remarkCodeBlockComponent: Plugin<[], Root> = () => {
458
+ return (tree) => {
459
+ transformCodeBlockNodes(tree);
406
460
  };
407
461
  };
408
462
 
@@ -145,6 +145,15 @@
145
145
  @apply px-1 py-px bg-neutral-100/90 text-neutral-600 rounded-md font-mono font-medium border border-neutral-200/80 after:hidden before:hidden;
146
146
  }
147
147
 
148
+ /* Keep nested blockquote content flush with the quote container edges */
149
+ :is(.prose, .prose-rules) blockquote > :first-child {
150
+ margin-top: 0;
151
+ }
152
+
153
+ :is(.prose, .prose-rules) blockquote > :last-child {
154
+ margin-bottom: 0;
155
+ }
156
+
148
157
  /* <ol> numbers */
149
158
  .prose :where(ol > li)::marker {
150
159
  @apply font-medium;