executable-stories-formatters 0.17.0 → 1.0.1

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 (61) hide show
  1. package/README.md +6 -4
  2. package/dist/adapters.cjs.map +1 -1
  3. package/dist/adapters.d.cts +183 -1
  4. package/dist/adapters.d.ts +183 -1
  5. package/dist/adapters.js.map +1 -1
  6. package/dist/cli.js +2525 -16783
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.cjs +2336 -15957
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +117 -1259
  11. package/dist/index.d.ts +117 -1259
  12. package/dist/index.js +2335 -15950
  13. package/dist/index.js.map +1 -1
  14. package/package.json +15 -11
  15. package/schemas/README.md +0 -1
  16. package/templates/astro-thin/astro.config.mjs +62 -0
  17. package/templates/astro-thin/executable-stories.config.mjs +47 -0
  18. package/templates/astro-thin/gitignore +3 -0
  19. package/templates/astro-thin/package.json +20 -0
  20. package/templates/astro-thin/reports/sample-run.json +133 -0
  21. package/templates/astro-thin/src/content/docs/404.md +20 -0
  22. package/templates/astro-thin/src/content/docs/guides/writing-docs.mdx +7 -0
  23. package/templates/astro-thin/src/content/docs/index.mdx +44 -0
  24. package/templates/astro-thin/src/content.config.ts +26 -0
  25. package/templates/astro-thin/src/styles/stories.css +32 -0
  26. package/templates/astro-thin/tsconfig.json +5 -0
  27. package/dist/index-CXrzCk9p.d.cts +0 -628
  28. package/dist/index-CXrzCk9p.d.ts +0 -628
  29. package/schemas/story-report-v1.json +0 -456
  30. package/templates/astro-starlight/astro.config.mjs +0 -57
  31. package/templates/astro-starlight/gitignore +0 -14
  32. package/templates/astro-starlight/package.json +0 -20
  33. package/templates/astro-starlight/public/stories/assets/.gitkeep +0 -0
  34. package/templates/astro-starlight/public/stories/notes-index.json +0 -4
  35. package/templates/astro-starlight/public/stories/story-report.json +0 -17
  36. package/templates/astro-starlight/src/components/ApiOperations.astro +0 -366
  37. package/templates/astro-starlight/src/components/Checklist.astro +0 -15
  38. package/templates/astro-starlight/src/components/HealthDashboard.astro +0 -171
  39. package/templates/astro-starlight/src/components/PageTitle.astro +0 -53
  40. package/templates/astro-starlight/src/components/VerifiedBy.astro +0 -281
  41. package/templates/astro-starlight/src/components/VerifiedStep.astro +0 -91
  42. package/templates/astro-starlight/src/content/docs/examples/example-adr.mdx +0 -45
  43. package/templates/astro-starlight/src/content/docs/guides/behavior-portal.mdx +0 -41
  44. package/templates/astro-starlight/src/content/docs/guides/writing-docs.mdx +0 -49
  45. package/templates/astro-starlight/src/content/docs/index.mdx +0 -49
  46. package/templates/astro-starlight/src/content/docs/stories/.gitkeep +0 -0
  47. package/templates/astro-starlight/src/content.config.ts +0 -18
  48. package/templates/astro-starlight/src/lib/config.ts +0 -50
  49. package/templates/astro-starlight/src/lib/render-doc-entry.ts +0 -154
  50. package/templates/astro-starlight/src/lib/report-health.ts +0 -61
  51. package/templates/astro-starlight/src/lib/verification.ts +0 -247
  52. package/templates/astro-starlight/src/pages/explorer/explorer.css +0 -729
  53. package/templates/astro-starlight/src/pages/explorer/index.astro +0 -404
  54. package/templates/astro-starlight/src/styles/global.css +0 -293
  55. package/templates/astro-starlight/src/styles/themes/corporate.css +0 -83
  56. package/templates/astro-starlight/src/styles/themes/dashboard.css +0 -76
  57. package/templates/astro-starlight/src/styles/themes/default.css +0 -86
  58. package/templates/astro-starlight/src/styles/themes/minimal.css +0 -87
  59. package/templates/astro-starlight/src/styles/themes/playful.css +0 -77
  60. package/templates/astro-starlight/src/styles/themes/terminal.css +0 -77
  61. package/templates/astro-starlight/tsconfig.json +0 -13
@@ -1,404 +0,0 @@
1
- ---
2
- import { NOTES_INDEX_URL, REPORT_URL, SOURCE_BASE_URL } from "../../lib/config";
3
- // Share the design tokens (--es-*) and theme palette (--sl-*) with the rest of
4
- // the docs so the explorer reads as one product, not a bolted-on page.
5
- import "../../styles/themes/default.css";
6
- import "../../styles/global.css";
7
- // Explorer styles live in a sibling stylesheet. A page-level CSS import is
8
- // global, which the runtime-built list/detail markup needs (Astro's scoped
9
- // styles never reach innerHTML-injected elements).
10
- import "./explorer.css";
11
-
12
- const reportPath = REPORT_URL;
13
- const notesIndexPath = NOTES_INDEX_URL;
14
- const sourceBaseUrl = SOURCE_BASE_URL;
15
- const baseUrl = import.meta.env.BASE_URL ?? "/";
16
- ---
17
-
18
- <!doctype html>
19
- <html lang="en">
20
- <head>
21
- <meta charset="utf-8" />
22
- <meta name="viewport" content="width=device-width, initial-scale=1" />
23
- <title>Scenario Explorer</title>
24
- <script is:inline>
25
- // Mirror the docs theme: Starlight persists the choice under this key.
26
- try {
27
- const t = localStorage.getItem("starlight-theme");
28
- if (t === "light" || t === "dark") document.documentElement.dataset.theme = t;
29
- } catch {}
30
- </script>
31
- </head>
32
- <body class="es-tool">
33
- <header class="topbar">
34
- <a class="topbar__home" href={baseUrl}>Story Docs</a>
35
- <span class="topbar__sep">/</span>
36
- <span class="topbar__here">Scenario Explorer</span>
37
- </header>
38
- <main class="shell">
39
- <aside class="sidebar" aria-label="Scenario navigation">
40
- <div class="brand">
41
- <h1>Scenario Explorer</h1>
42
- <span class="meta" id="run-meta">Loading…</span>
43
- </div>
44
- <div class="controls">
45
- <input
46
- class="search"
47
- id="search"
48
- type="search"
49
- aria-label="Search scenarios, tags, or file paths"
50
- placeholder="Search scenarios, tags, or file paths"
51
- />
52
- <select class="select" id="status-filter" aria-label="Filter by status">
53
- <option value="all">All statuses</option>
54
- <option value="passed">Passed</option>
55
- <option value="failed">Failed</option>
56
- <option value="skipped">Skipped</option>
57
- <option value="pending">Pending</option>
58
- </select>
59
- </div>
60
- <div class="summary" id="summary" aria-label="Run summary"></div>
61
- <div class="scenario-list" id="scenario-list"></div>
62
- </aside>
63
- <section class="content">
64
- <article class="scenario-card" id="scenario-detail">
65
- <div class="empty">Load <code>story-report.json</code> to browse executable stories.</div>
66
- </article>
67
- </section>
68
- </main>
69
-
70
- <script is:inline define:vars={{ reportPath, notesIndexPath, sourceBaseUrl, baseUrl }}>
71
- window.__EXPLORER_CONFIG__ = { reportPath, notesIndexPath, sourceBaseUrl, baseUrl };
72
- </script>
73
- <script>
74
- // Rendering libraries are bundled with the site (no CDN), so the Explorer
75
- // works offline / air-gapped — the enterprise "Confluence replacement"
76
- // case. Code highlighting, section markdown, and live diagrams all render
77
- // locally.
78
- import { marked } from "marked";
79
- import hljs from "highlight.js/lib/common";
80
- import mermaid from "mermaid";
81
- // Pure, unit-tested doc-entry → HTML renderer.
82
- import { renderDocs, escapeHtml } from "../../lib/render-doc-entry";
83
-
84
- const { reportPath, notesIndexPath, sourceBaseUrl, baseUrl } = window.__EXPLORER_CONFIG__ ?? {
85
- reportPath: "/stories/story-report.json",
86
- notesIndexPath: "/stories/notes-index.json",
87
- sourceBaseUrl: "",
88
- baseUrl: "/",
89
- };
90
-
91
- const libs = { marked, hljs, mermaid };
92
- {
93
- const dark =
94
- document.documentElement.dataset.theme === "dark" ||
95
- (!document.documentElement.dataset.theme &&
96
- matchMedia("(prefers-color-scheme: dark)").matches);
97
- mermaid.initialize({ startOnLoad: false, theme: dark ? "dark" : "default", securityLevel: "loose" });
98
- }
99
-
100
- const state = {
101
- report: null,
102
- scenarios: [],
103
- notesByScenarioId: {},
104
- selectedId: "",
105
- query: "",
106
- status: "all",
107
- };
108
-
109
- const elements = {
110
- meta: document.querySelector("#run-meta"),
111
- search: document.querySelector("#search"),
112
- status: document.querySelector("#status-filter"),
113
- summary: document.querySelector("#summary"),
114
- list: document.querySelector("#scenario-list"),
115
- detail: document.querySelector("#scenario-detail"),
116
- };
117
-
118
- const statusClass = (status) => `status status-${status}`;
119
- const withBase = (value) => {
120
- const base = (baseUrl || "/").replace(/\/$/, "");
121
- const normalized = String(value || "").replace(/^\/+/, "");
122
- return base ? `${base}/${normalized}` : `/${normalized}`;
123
- };
124
-
125
- const flattenReport = (report) =>
126
- (report.features ?? []).flatMap((feature) =>
127
- (feature.scenarios ?? []).map((scenario) => ({
128
- ...scenario,
129
- featureTitle: feature.title,
130
- sourceFile: scenario.sourceFile ?? feature.sourceFile,
131
- })),
132
- );
133
-
134
- const filteredScenarios = () => {
135
- const q = state.query.trim().toLowerCase();
136
- return state.scenarios.filter((scenario) => {
137
- const matchesStatus = state.status === "all" || scenario.status === state.status;
138
- const haystack = [
139
- scenario.title,
140
- scenario.featureTitle,
141
- scenario.sourceFile,
142
- ...(scenario.tags ?? []),
143
- ...(scenario.covers ?? []),
144
- ]
145
- .join(" ")
146
- .toLowerCase();
147
- return matchesStatus && (!q || haystack.includes(q));
148
- });
149
- };
150
-
151
- const renderSummary = () => {
152
- const summary = state.report?.summary ?? {
153
- total: 0,
154
- passed: 0,
155
- failed: 0,
156
- skipped: 0,
157
- pending: 0,
158
- };
159
- elements.summary.innerHTML = ["total", "passed", "failed", "skipped", "pending"]
160
- .map(
161
- (key) => `
162
- <div class="metric metric--${key}">
163
- <strong>${summary[key] ?? 0}</strong>
164
- <span>${key}</span>
165
- </div>
166
- `,
167
- )
168
- .join("");
169
- };
170
-
171
- const renderList = () => {
172
- const scenarios = filteredScenarios();
173
- if (!state.selectedId && scenarios[0]) state.selectedId = scenarios[0].id;
174
-
175
- elements.list.innerHTML = scenarios
176
- .map(
177
- (scenario) => `
178
- <button class="scenario-button" type="button" data-scenario-id="${escapeHtml(
179
- scenario.id,
180
- )}" aria-current="${scenario.id === state.selectedId}">
181
- <span class="row-dot dot-${escapeHtml(scenario.status)}"></span>
182
- <span class="scenario-title">${escapeHtml(scenario.title)}</span>
183
- ${
184
- state.notesByScenarioId[scenario.id]
185
- ? '<span class="scenario-context-flag">Context</span>'
186
- : ""
187
- }
188
- <span class="scenario-source">${escapeHtml(scenario.sourceFile ?? "")}${
189
- scenario.sourceLine ? `:${scenario.sourceLine}` : ""
190
- }</span>
191
- </button>
192
- `,
193
- )
194
- .join("");
195
-
196
- elements.list.querySelectorAll("[data-scenario-id]").forEach((button) => {
197
- button.addEventListener("click", () => {
198
- // pushState so the browser Back button steps through visited
199
- // scenarios instead of ejecting you from the Explorer.
200
- selectScenario(button.dataset.scenarioId, { push: true });
201
- const smooth = !matchMedia("(prefers-reduced-motion: reduce)").matches;
202
- elements.detail.scrollIntoView({ block: "start", behavior: smooth ? "smooth" : "auto" });
203
- });
204
- });
205
- };
206
-
207
- // renderDocs / renderDocEntry are imported from ../../lib/render-doc-entry.
208
- // Run the rendering libraries over the freshly-inserted markup.
209
- const hydrateDocs = async () => {
210
- const root = elements.detail;
211
- if (libs.marked) {
212
- root.querySelectorAll(".doc-section-content[data-md]").forEach((el) => {
213
- try {
214
- el.innerHTML = libs.marked.parse(decodeURIComponent(el.dataset.md));
215
- el.removeAttribute("data-md");
216
- } catch {}
217
- });
218
- }
219
- if (libs.hljs) {
220
- root.querySelectorAll("pre code[class^='language-']").forEach((el) => {
221
- try {
222
- libs.hljs.highlightElement(el);
223
- } catch {}
224
- });
225
- }
226
- if (libs.mermaid) {
227
- const nodes = [...root.querySelectorAll("pre.mermaid:not([data-processed])")];
228
- if (nodes.length) {
229
- try {
230
- await libs.mermaid.run({ nodes });
231
- } catch {}
232
- }
233
- }
234
- // Flag broken images so the layout doesn't show a torn-image icon.
235
- root.querySelectorAll(".doc-media img").forEach((img) => {
236
- img.addEventListener("error", () => {
237
- const wrap = img.closest(".doc-media");
238
- if (wrap)
239
- wrap.outerHTML = `<div class="doc-media doc-media--broken">Image unavailable<code>${escapeHtml(
240
- img.getAttribute("src") ?? "",
241
- )}</code></div>`;
242
- });
243
- });
244
- };
245
-
246
- const sourceMarkup = (scenario) => {
247
- const path = scenario.sourceFile;
248
- if (!path) return "";
249
- const loc = `${escapeHtml(path)}${scenario.sourceLine ? `:${scenario.sourceLine}` : ""}`;
250
- if (sourceBaseUrl) {
251
- const base = sourceBaseUrl.replace(/\/$/, "");
252
- const href = `${base}/${path}${scenario.sourceLine ? `#L${scenario.sourceLine}` : ""}`;
253
- return `<a class="source-link" href="${escapeHtml(
254
- href,
255
- )}" target="_blank" rel="noopener noreferrer">${loc}</a>`;
256
- }
257
- return `<span class="source-text">${loc}</span>`;
258
- };
259
-
260
- const renderDetail = () => {
261
- const scenario =
262
- state.scenarios.find((item) => item.id === state.selectedId) ?? filteredScenarios()[0];
263
-
264
- if (!scenario) {
265
- elements.detail.innerHTML = '<div class="empty">No scenarios match current filters.</div>';
266
- return;
267
- }
268
-
269
- const note = state.notesByScenarioId[scenario.id];
270
- state.selectedId = scenario.id;
271
-
272
- elements.detail.innerHTML = `
273
- <header class="scenario-heading">
274
- <span class="${statusClass(scenario.status)}">${escapeHtml(scenario.status)}</span>
275
- <h2>${escapeHtml(scenario.title)}</h2>
276
- <div class="feature-line">
277
- ${scenario.featureTitle ? `<span>${escapeHtml(scenario.featureTitle)}</span><span class="sep">·</span>` : ""}
278
- ${sourceMarkup(scenario)}
279
- </div>
280
- ${
281
- note
282
- ? `<div class="context-row"><a class="context-link" href="${escapeHtml(
283
- withBase(`notes/${note.slug}/`),
284
- )}">Business context →</a></div>`
285
- : ""
286
- }
287
- ${
288
- (scenario.tags ?? []).length
289
- ? `<div class="tag-row">${scenario.tags
290
- .map((tag) => `<span class="pill">#${escapeHtml(tag)}</span>`)
291
- .join("")}</div>`
292
- : ""
293
- }
294
- ${
295
- (scenario.covers ?? []).length
296
- ? `<div class="covers-row">Covers ${scenario.covers
297
- .map((p) => `<code class="covers-path">${escapeHtml(p)}</code>`)
298
- .join(" ")}</div>`
299
- : ""
300
- }
301
- </header>
302
- <section class="section">
303
- <h3>Steps</h3>
304
- <ol class="steps">
305
- ${(scenario.steps ?? [])
306
- .map(
307
- (step) => `
308
- <li class="step step--${escapeHtml(step.status)}">
309
- <span class="keyword">${escapeHtml(step.keyword)}</span>
310
- <span class="step-text">${escapeHtml(step.text)}</span>
311
- <span class="step-status">${escapeHtml(step.status)}</span>
312
- </li>
313
- `,
314
- )
315
- .join("")}
316
- </ol>
317
- </section>
318
- <section class="section">
319
- <h3>Docs</h3>
320
- <div class="doc-grid">${renderDocs(scenario.docEntries)}</div>
321
- </section>
322
- ${
323
- scenario.errorMessage
324
- ? `<section class="section"><h3>Error</h3><pre class="error">${escapeHtml(
325
- scenario.errorMessage,
326
- )}</pre></section>`
327
- : ""
328
- }
329
- `;
330
- hydrateDocs();
331
- };
332
-
333
- const render = () => {
334
- renderSummary();
335
- renderList();
336
- renderDetail();
337
- };
338
-
339
- // Select a scenario and reflect it in the URL. `push` adds a history entry
340
- // (clicks) so Back works; otherwise we replace (initial load / popstate).
341
- const selectScenario = (id, { push = false } = {}) => {
342
- if (!id) return;
343
- state.selectedId = id;
344
- const hash = `#${encodeURIComponent(id)}`;
345
- if (location.hash !== hash) {
346
- if (push) history.pushState(null, "", hash);
347
- else history.replaceState(null, "", hash);
348
- }
349
- render();
350
- };
351
-
352
- // Apply whatever scenario the URL hash points at (deep-link, Back/Forward,
353
- // or a hash change while already on the page).
354
- const selectFromHash = () => {
355
- const id = decodeURIComponent(location.hash.slice(1));
356
- if (id && state.scenarios.some((s) => s.id === id)) {
357
- state.selectedId = id;
358
- }
359
- render();
360
- };
361
-
362
- window.addEventListener("hashchange", selectFromHash);
363
- window.addEventListener("popstate", selectFromHash);
364
-
365
- elements.search.addEventListener("input", (event) => {
366
- state.query = event.target.value;
367
- state.selectedId = "";
368
- render();
369
- });
370
-
371
- elements.status.addEventListener("change", (event) => {
372
- state.status = event.target.value;
373
- state.selectedId = "";
374
- render();
375
- });
376
-
377
- const loadJson = (url, { allowMissing = false } = {}) =>
378
- fetch(url).then((response) => {
379
- if (!response.ok) {
380
- if (allowMissing && response.status === 404) return null;
381
- throw new Error(`Could not load ${url}`);
382
- }
383
- return response.json();
384
- });
385
-
386
- Promise.all([loadJson(reportPath), loadJson(notesIndexPath, { allowMissing: true })])
387
- .then(([report, notesIndex]) => {
388
- state.report = report;
389
- state.scenarios = flattenReport(report);
390
- state.notesByScenarioId = Object.fromEntries(
391
- (notesIndex?.notes ?? []).map((note) => [note.scenarioId, note]),
392
- );
393
- state.selectedId = decodeURIComponent(location.hash.slice(1));
394
- elements.meta.textContent = `${report.summary.total} scenarios`;
395
- render();
396
- })
397
- .catch((error) => {
398
- elements.meta.textContent = "No report";
399
- elements.detail.innerHTML = `<pre class="error">${escapeHtml(error.message)}</pre>`;
400
- renderSummary();
401
- });
402
- </script>
403
- </body>
404
- </html>
@@ -1,293 +0,0 @@
1
- /*
2
- * Shared component overrides — applies to all themes.
3
- * Color/font variables come from the theme CSS file (themes/*.css).
4
- * Import in astro.config.mjs: customCss: ['./src/styles/global.css', './src/styles/themes/default.css']
5
- */
6
-
7
- /* ============================================================================
8
- Design tokens (--es-*)
9
- ----------------------------------------------------------------------------
10
- The single source of truth for the custom components (VerifiedBy badge,
11
- API operations table, Scenario Explorer). Derived from Starlight's semantic
12
- --sl-color-* / font variables so they adapt to whichever theme is active and
13
- flip correctly between light and dark. Status + HTTP-method hues are fixed
14
- (they carry meaning); their "soft" fills are mixed into the page background
15
- with color-mix, so they read correctly in both modes from one definition.
16
- ============================================================================ */
17
-
18
- :root {
19
- /* Surfaces & structure */
20
- --es-bg: var(--sl-color-black);
21
- --es-surface: var(--sl-color-gray-7);
22
- --es-surface-2: var(--sl-color-gray-6);
23
- --es-line: var(--sl-color-gray-5);
24
- --es-line-strong: var(--sl-color-gray-4);
25
- --es-text: var(--sl-color-white);
26
- --es-text-soft: var(--sl-color-gray-1);
27
- --es-text-muted: var(--sl-color-gray-3);
28
-
29
- --es-accent: var(--sl-color-accent);
30
- --es-accent-high: var(--sl-color-accent-high);
31
- --es-accent-soft: var(--sl-color-accent-low);
32
-
33
- --es-mono: var(--sl-font-mono);
34
- --es-radius: 0.625rem;
35
- --es-radius-sm: 0.375rem;
36
- --es-radius-pill: 999px;
37
- --es-shadow: var(--sl-shadow-lg);
38
- --es-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.06), 0 1px 1px rgb(0 0 0 / 0.04);
39
-
40
- /* Status hues (meaningful, theme-independent) */
41
- --es-passed: hsl(145 63% 38%);
42
- --es-failed: hsl(0 72% 48%);
43
- --es-skipped: hsl(38 92% 40%);
44
- --es-pending: hsl(262 55% 55%);
45
- --es-unverified: hsl(38 92% 40%);
46
-
47
- /* Soft status fills — mix the hue into the current background */
48
- --es-passed-soft: color-mix(in oklab, var(--es-passed) 12%, var(--es-bg));
49
- --es-failed-soft: color-mix(in oklab, var(--es-failed) 12%, var(--es-bg));
50
- --es-skipped-soft: color-mix(in oklab, var(--es-skipped) 12%, var(--es-bg));
51
- --es-pending-soft: color-mix(in oklab, var(--es-pending) 12%, var(--es-bg));
52
-
53
- /* HTTP method hues (API operations) */
54
- --es-method-get: hsl(145 63% 38%);
55
- --es-method-post: hsl(212 80% 48%);
56
- --es-method-put: hsl(33 92% 44%);
57
- --es-method-patch: hsl(280 55% 54%);
58
- --es-method-delete: hsl(0 72% 48%);
59
- --es-method-default: var(--sl-color-gray-3);
60
- }
61
-
62
- /* Brighten status hues a touch in dark mode for legibility on dark fills. */
63
- [data-theme='dark'] {
64
- --es-passed: hsl(145 60% 52%);
65
- --es-failed: hsl(0 78% 64%);
66
- --es-skipped: hsl(38 92% 56%);
67
- --es-pending: hsl(262 70% 72%);
68
- --es-unverified: hsl(38 92% 56%);
69
- --es-method-get: hsl(145 60% 52%);
70
- --es-method-post: hsl(212 85% 66%);
71
- --es-method-put: hsl(38 92% 58%);
72
- --es-method-patch: hsl(280 70% 72%);
73
- --es-method-delete: hsl(0 78% 66%);
74
- }
75
- @media (prefers-color-scheme: dark) {
76
- :root:not([data-theme='light']) {
77
- --es-passed: hsl(145 60% 52%);
78
- --es-failed: hsl(0 78% 64%);
79
- --es-skipped: hsl(38 92% 56%);
80
- --es-pending: hsl(262 70% 72%);
81
- --es-unverified: hsl(38 92% 56%);
82
- --es-method-get: hsl(145 60% 52%);
83
- --es-method-post: hsl(212 85% 66%);
84
- --es-method-put: hsl(38 92% 58%);
85
- --es-method-patch: hsl(280 70% 72%);
86
- --es-method-delete: hsl(0 78% 66%);
87
- }
88
- }
89
-
90
- /* ============================================================================
91
- Typography
92
- ============================================================================ */
93
-
94
- .sl-markdown-content h1,
95
- .sl-markdown-content h2,
96
- .sl-markdown-content h3,
97
- .sl-markdown-content h4 {
98
- letter-spacing: -0.02em;
99
- }
100
-
101
- .sl-markdown-content > p,
102
- .sl-markdown-content > ul,
103
- .sl-markdown-content > ol {
104
- max-width: 72ch;
105
- }
106
-
107
- /* ============================================================================
108
- Header / Nav
109
- ============================================================================ */
110
-
111
- .site-title {
112
- font-weight: 700 !important;
113
- letter-spacing: -0.02em;
114
- }
115
-
116
- .site-title span {
117
- color: var(--sl-color-text) !important;
118
- }
119
-
120
- .site-title img {
121
- width: 1.75rem !important;
122
- height: 1.75rem !important;
123
- }
124
-
125
- header.header {
126
- border-bottom: 1px solid var(--sl-color-hairline) !important;
127
- }
128
-
129
- /* ============================================================================
130
- Sidebar
131
- ============================================================================ */
132
-
133
- nav.sidebar {
134
- border-right: 1px solid var(--sl-color-gray-3);
135
- }
136
-
137
- nav.sidebar a[aria-current='page'] {
138
- border-left: 2px solid var(--sl-color-accent);
139
- background: var(--sl-color-accent-low);
140
- font-weight: 600;
141
- color: var(--sl-color-accent) !important;
142
- }
143
-
144
- nav.sidebar a {
145
- transition: background-color 150ms ease, color 150ms ease;
146
- }
147
-
148
- nav.sidebar a:hover {
149
- background: var(--sl-color-accent-low);
150
- }
151
-
152
- /* ============================================================================
153
- Code blocks — theme-aware via Starlight variables
154
- ============================================================================ */
155
-
156
- .expressive-code .frame {
157
- border-radius: 0.5rem !important;
158
- border: 1px solid var(--sl-color-gray-5) !important;
159
- box-shadow: none !important;
160
- overflow: hidden;
161
- }
162
-
163
- .expressive-code pre {
164
- border-radius: 0 !important;
165
- border: none !important;
166
- padding: 1rem 1.25rem !important;
167
- font-family: var(--sl-font-mono);
168
- font-size: 0.875rem !important;
169
- line-height: 1.7 !important;
170
- }
171
-
172
- .expressive-code .header {
173
- border-bottom: 1px solid var(--sl-color-gray-5) !important;
174
- padding: 8px 14px !important;
175
- }
176
-
177
- .expressive-code .header .title {
178
- font-size: 0.75rem !important;
179
- font-family: var(--sl-font-mono) !important;
180
- }
181
-
182
- /* Inline code */
183
- .sl-markdown-content :not(pre) > code {
184
- background: var(--sl-color-gray-6);
185
- border: 1px solid var(--sl-color-gray-5);
186
- border-radius: 0.25rem;
187
- padding: 0.125em 0.375em;
188
- font-size: 0.85em;
189
- }
190
-
191
- /* ============================================================================
192
- Status badges — match report status colors
193
- Starlight renders <span class="sl-badge success">, so override via the
194
- --sl-badge-*-bg/border/text CSS custom properties it already defines.
195
- ============================================================================ */
196
-
197
- :root {
198
- --sl-badge-success-bg: hsl(145 63% 42%);
199
- --sl-badge-success-border: hsl(145 63% 35%);
200
- --sl-badge-success-text: #fff;
201
-
202
- --sl-badge-danger-bg: hsl(0 72% 51%);
203
- --sl-badge-danger-border: hsl(0 72% 42%);
204
- --sl-badge-danger-text: #fff;
205
-
206
- --sl-badge-caution-bg: hsl(38 92% 50%);
207
- --sl-badge-caution-border: hsl(38 92% 40%);
208
- --sl-badge-caution-text: #fff;
209
-
210
- --sl-badge-note-bg: hsl(262 60% 55%);
211
- --sl-badge-note-border: hsl(262 60% 45%);
212
- --sl-badge-note-text: #fff;
213
-
214
- --sl-badge-tip-bg: hsl(145 63% 42%);
215
- --sl-badge-tip-border: hsl(145 63% 35%);
216
- --sl-badge-tip-text: #fff;
217
- }
218
-
219
- [data-theme='light']:root {
220
- --sl-badge-success-bg: hsl(145 63% 42%);
221
- --sl-badge-danger-bg: hsl(0 72% 51%);
222
- --sl-badge-caution-bg: hsl(38 92% 50%);
223
- --sl-badge-note-bg: hsl(262 60% 55%);
224
- --sl-badge-tip-bg: hsl(145 63% 42%);
225
- }
226
-
227
- /* ============================================================================
228
- Cards / content areas
229
- ============================================================================ */
230
-
231
- .sl-markdown-content details {
232
- border: 1px solid var(--sl-color-gray-3);
233
- border-radius: 0.5rem;
234
- background: var(--sl-color-gray-1);
235
- }
236
-
237
- .sl-markdown-content details[open] > summary {
238
- border-bottom: 1px solid var(--sl-color-gray-3);
239
- }
240
-
241
- .sl-markdown-content summary {
242
- font-weight: 500;
243
- cursor: pointer;
244
- }
245
-
246
- /* ============================================================================
247
- Tables
248
- ============================================================================ */
249
-
250
- .sl-markdown-content table {
251
- border-collapse: collapse;
252
- width: 100%;
253
- }
254
-
255
- .sl-markdown-content th {
256
- background: var(--sl-color-gray-2);
257
- font-weight: 600;
258
- font-size: 0.8125rem;
259
- text-transform: uppercase;
260
- letter-spacing: 0.05em;
261
- }
262
-
263
- .sl-markdown-content th,
264
- .sl-markdown-content td {
265
- border: 1px solid var(--sl-color-gray-3);
266
- padding: 0.5rem 0.75rem;
267
- }
268
-
269
- /* ============================================================================
270
- Links
271
- ============================================================================ */
272
-
273
- .sl-markdown-content a {
274
- color: var(--sl-color-accent);
275
- text-decoration: underline;
276
- text-underline-offset: 2px;
277
- }
278
-
279
- .sl-markdown-content a:hover {
280
- color: var(--sl-color-accent-high);
281
- }
282
-
283
- /* ============================================================================
284
- Search dialog
285
- ============================================================================ */
286
-
287
- [is='search-dialog']::part(result-link):hover {
288
- background: var(--sl-color-accent-low);
289
- }
290
-
291
- [is='search-dialog']::part(result-link)[aria-selected='true'] {
292
- color: var(--sl-color-accent);
293
- }