thibaud frere commited on
Commit
4f3bb9d
·
1 Parent(s): a2df917

add code output

Browse files
app/.astro/astro/content.d.ts CHANGED
@@ -1,238 +0,0 @@
1
- declare module 'astro:content' {
2
- interface Render {
3
- '.mdx': Promise<{
4
- Content: import('astro').MarkdownInstance<{}>['Content'];
5
- headings: import('astro').MarkdownHeading[];
6
- remarkPluginFrontmatter: Record<string, any>;
7
- components: import('astro').MDXInstance<{}>['components'];
8
- }>;
9
- }
10
- }
11
-
12
- declare module 'astro:content' {
13
- interface RenderResult {
14
- Content: import('astro/runtime/server/index.js').AstroComponentFactory;
15
- headings: import('astro').MarkdownHeading[];
16
- remarkPluginFrontmatter: Record<string, any>;
17
- }
18
- interface Render {
19
- '.md': Promise<RenderResult>;
20
- }
21
-
22
- export interface RenderedContent {
23
- html: string;
24
- metadata?: {
25
- imagePaths: Array<string>;
26
- [key: string]: unknown;
27
- };
28
- }
29
- }
30
-
31
- declare module 'astro:content' {
32
- type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
33
-
34
- export type CollectionKey = keyof AnyEntryMap;
35
- export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
36
-
37
- export type ContentCollectionKey = keyof ContentEntryMap;
38
- export type DataCollectionKey = keyof DataEntryMap;
39
-
40
- type AllValuesOf<T> = T extends any ? T[keyof T] : never;
41
- type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
42
- ContentEntryMap[C]
43
- >['slug'];
44
-
45
- /** @deprecated Use `getEntry` instead. */
46
- export function getEntryBySlug<
47
- C extends keyof ContentEntryMap,
48
- E extends ValidContentEntrySlug<C> | (string & {}),
49
- >(
50
- collection: C,
51
- // Note that this has to accept a regular string too, for SSR
52
- entrySlug: E,
53
- ): E extends ValidContentEntrySlug<C>
54
- ? Promise<CollectionEntry<C>>
55
- : Promise<CollectionEntry<C> | undefined>;
56
-
57
- /** @deprecated Use `getEntry` instead. */
58
- export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
59
- collection: C,
60
- entryId: E,
61
- ): Promise<CollectionEntry<C>>;
62
-
63
- export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
64
- collection: C,
65
- filter?: (entry: CollectionEntry<C>) => entry is E,
66
- ): Promise<E[]>;
67
- export function getCollection<C extends keyof AnyEntryMap>(
68
- collection: C,
69
- filter?: (entry: CollectionEntry<C>) => unknown,
70
- ): Promise<CollectionEntry<C>[]>;
71
-
72
- export function getEntry<
73
- C extends keyof ContentEntryMap,
74
- E extends ValidContentEntrySlug<C> | (string & {}),
75
- >(entry: {
76
- collection: C;
77
- slug: E;
78
- }): E extends ValidContentEntrySlug<C>
79
- ? Promise<CollectionEntry<C>>
80
- : Promise<CollectionEntry<C> | undefined>;
81
- export function getEntry<
82
- C extends keyof DataEntryMap,
83
- E extends keyof DataEntryMap[C] | (string & {}),
84
- >(entry: {
85
- collection: C;
86
- id: E;
87
- }): E extends keyof DataEntryMap[C]
88
- ? Promise<DataEntryMap[C][E]>
89
- : Promise<CollectionEntry<C> | undefined>;
90
- export function getEntry<
91
- C extends keyof ContentEntryMap,
92
- E extends ValidContentEntrySlug<C> | (string & {}),
93
- >(
94
- collection: C,
95
- slug: E,
96
- ): E extends ValidContentEntrySlug<C>
97
- ? Promise<CollectionEntry<C>>
98
- : Promise<CollectionEntry<C> | undefined>;
99
- export function getEntry<
100
- C extends keyof DataEntryMap,
101
- E extends keyof DataEntryMap[C] | (string & {}),
102
- >(
103
- collection: C,
104
- id: E,
105
- ): E extends keyof DataEntryMap[C]
106
- ? Promise<DataEntryMap[C][E]>
107
- : Promise<CollectionEntry<C> | undefined>;
108
-
109
- /** Resolve an array of entry references from the same collection */
110
- export function getEntries<C extends keyof ContentEntryMap>(
111
- entries: {
112
- collection: C;
113
- slug: ValidContentEntrySlug<C>;
114
- }[],
115
- ): Promise<CollectionEntry<C>[]>;
116
- export function getEntries<C extends keyof DataEntryMap>(
117
- entries: {
118
- collection: C;
119
- id: keyof DataEntryMap[C];
120
- }[],
121
- ): Promise<CollectionEntry<C>[]>;
122
-
123
- export function render<C extends keyof AnyEntryMap>(
124
- entry: AnyEntryMap[C][string],
125
- ): Promise<RenderResult>;
126
-
127
- export function reference<C extends keyof AnyEntryMap>(
128
- collection: C,
129
- ): import('astro/zod').ZodEffects<
130
- import('astro/zod').ZodString,
131
- C extends keyof ContentEntryMap
132
- ? {
133
- collection: C;
134
- slug: ValidContentEntrySlug<C>;
135
- }
136
- : {
137
- collection: C;
138
- id: keyof DataEntryMap[C];
139
- }
140
- >;
141
- // Allow generic `string` to avoid excessive type errors in the config
142
- // if `dev` is not running to update as you edit.
143
- // Invalid collection names will be caught at build time.
144
- export function reference<C extends string>(
145
- collection: C,
146
- ): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
147
-
148
- type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
149
- type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
150
- ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
151
- >;
152
-
153
- type ContentEntryMap = {
154
- "chapters": {
155
- "best-pratices.mdx": {
156
- id: "best-pratices.mdx";
157
- slug: "best-pratices";
158
- body: string;
159
- collection: "chapters";
160
- data: any
161
- } & { render(): Render[".mdx"] };
162
- "components.mdx": {
163
- id: "components.mdx";
164
- slug: "components";
165
- body: string;
166
- collection: "chapters";
167
- data: any
168
- } & { render(): Render[".mdx"] };
169
- "debug-components.mdx": {
170
- id: "debug-components.mdx";
171
- slug: "debug-components";
172
- body: string;
173
- collection: "chapters";
174
- data: any
175
- } & { render(): Render[".mdx"] };
176
- "getting-started.mdx": {
177
- id: "getting-started.mdx";
178
- slug: "getting-started";
179
- body: string;
180
- collection: "chapters";
181
- data: any
182
- } & { render(): Render[".mdx"] };
183
- "greetings.mdx": {
184
- id: "greetings.mdx";
185
- slug: "greetings";
186
- body: string;
187
- collection: "chapters";
188
- data: any
189
- } & { render(): Render[".mdx"] };
190
- "introduction.mdx": {
191
- id: "introduction.mdx";
192
- slug: "introduction";
193
- body: string;
194
- collection: "chapters";
195
- data: any
196
- } & { render(): Render[".mdx"] };
197
- "markdown.mdx": {
198
- id: "markdown.mdx";
199
- slug: "markdown";
200
- body: string;
201
- collection: "chapters";
202
- data: any
203
- } & { render(): Render[".mdx"] };
204
- "writing-your-content.mdx": {
205
- id: "writing-your-content.mdx";
206
- slug: "writing-your-content";
207
- body: string;
208
- collection: "chapters";
209
- data: any
210
- } & { render(): Render[".mdx"] };
211
- };
212
- "embeds": {
213
- "vibe-code-d3-embeds-directives.md": {
214
- id: "vibe-code-d3-embeds-directives.md";
215
- slug: "vibe-code-d3-embeds-directives";
216
- body: string;
217
- collection: "embeds";
218
- data: any
219
- } & { render(): Render[".md"] };
220
- };
221
-
222
- };
223
-
224
- type DataEntryMap = {
225
- "assets": {
226
- "data/mnist-variant-model": {
227
- id: "data/mnist-variant-model";
228
- collection: "assets";
229
- data: any
230
- };
231
- };
232
-
233
- };
234
-
235
- type AnyEntryMap = ContentEntryMap & DataEntryMap;
236
-
237
- export type ContentConfig = never;
238
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/astro.config.mjs CHANGED
@@ -11,8 +11,11 @@ import rehypeCitation from 'rehype-citation';
11
  import rehypeCodeCopy from './plugins/rehype/code-copy.mjs';
12
  import rehypeReferencesAndFootnotes from './plugins/rehype/post-citation.mjs';
13
  import remarkIgnoreCitationsInCode from './plugins/remark/ignore-citations-in-code.mjs';
 
 
14
  import rehypeRestoreAtInCode from './plugins/rehype/restore-at-in-code.mjs';
15
  import rehypeWrapTables from './plugins/rehype/wrap-tables.mjs';
 
16
  // Built-in Shiki (dual themes) — no rehype-pretty-code
17
 
18
  // Plugins moved to app/plugins/*
@@ -44,7 +47,9 @@ export default defineConfig({
44
  remarkPlugins: [
45
  remarkIgnoreCitationsInCode,
46
  remarkMath,
47
- [remarkFootnotes, { inlineNotes: true }]
 
 
48
  ],
49
  rehypePlugins: [
50
  rehypeSlug,
@@ -57,6 +62,7 @@ export default defineConfig({
57
  rehypeReferencesAndFootnotes,
58
  rehypeRestoreAtInCode,
59
  rehypeCodeCopy,
 
60
  rehypeWrapTables
61
  ]
62
  }
 
11
  import rehypeCodeCopy from './plugins/rehype/code-copy.mjs';
12
  import rehypeReferencesAndFootnotes from './plugins/rehype/post-citation.mjs';
13
  import remarkIgnoreCitationsInCode from './plugins/remark/ignore-citations-in-code.mjs';
14
+ import remarkDirective from 'remark-directive';
15
+ import remarkOutputContainer from './plugins/remark/output-container.mjs';
16
  import rehypeRestoreAtInCode from './plugins/rehype/restore-at-in-code.mjs';
17
  import rehypeWrapTables from './plugins/rehype/wrap-tables.mjs';
18
+ import rehypeWrapOutput from './plugins/rehype/wrap-outputs.mjs';
19
  // Built-in Shiki (dual themes) — no rehype-pretty-code
20
 
21
  // Plugins moved to app/plugins/*
 
47
  remarkPlugins: [
48
  remarkIgnoreCitationsInCode,
49
  remarkMath,
50
+ [remarkFootnotes, { inlineNotes: true }],
51
+ remarkDirective,
52
+ remarkOutputContainer
53
  ],
54
  rehypePlugins: [
55
  rehypeSlug,
 
62
  rehypeReferencesAndFootnotes,
63
  rehypeRestoreAtInCode,
64
  rehypeCodeCopy,
65
+ rehypeWrapOutput,
66
  rehypeWrapTables
67
  ]
68
  }
app/package-lock.json CHANGED
Binary files a/app/package-lock.json and b/app/package-lock.json differ
 
app/package.json CHANGED
Binary files a/app/package.json and b/app/package.json differ
 
app/plugins/rehype/wrap-outputs.mjs ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Wrap plain-text content inside <section class="code-outputs"> into a <pre>
2
+ export default function rehypeWrapOutput() {
3
+ return (tree) => {
4
+ const isWhitespace = (value) => typeof value === 'string' && !/\S/.test(value);
5
+ const extractText = (node) => {
6
+ if (!node) return '';
7
+ if (node.type === 'text') return String(node.value || '');
8
+ const kids = Array.isArray(node.children) ? node.children : [];
9
+ return kids.map(extractText).join('');
10
+ };
11
+ const visit = (node) => {
12
+ if (!node || typeof node !== 'object') return;
13
+ const children = Array.isArray(node.children) ? node.children : [];
14
+ if (node.type === 'element' && node.tagName === 'section') {
15
+ const className = node.properties?.className || [];
16
+ const classes = Array.isArray(className) ? className : [className].filter(Boolean);
17
+ if (classes.includes('code-output')) {
18
+ const meaningful = children.filter((c) => !(c.type === 'text' && isWhitespace(c.value)));
19
+ if (meaningful.length === 1) {
20
+ const only = meaningful[0];
21
+ const isPlainParagraph = only.type === 'element' && only.tagName === 'p' && (only.children || []).every((c) => c.type === 'text');
22
+ const isPlainText = only.type === 'text';
23
+ if (isPlainParagraph || isPlainText) {
24
+ const text = isPlainText ? String(only.value || '') : extractText(only);
25
+ node.children = [
26
+ { type: 'element', tagName: 'pre', properties: {}, children: [ { type: 'text', value: text } ] }
27
+ ];
28
+ }
29
+ }
30
+ }
31
+ }
32
+ children.forEach(visit);
33
+ };
34
+ visit(tree);
35
+ };
36
+ }
37
+
38
+
app/plugins/remark/output-container.mjs ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Transform `:::output ... :::` into a <section class="code-output"> wrapper
2
+ // Requires remark-directive to be applied before this plugin
3
+
4
+ export default function remarkOutputContainer() {
5
+ return (tree) => {
6
+ const visit = (node) => {
7
+ if (!node || typeof node !== 'object') return;
8
+
9
+ if (node.type === 'containerDirective' && node.name === 'output') {
10
+ node.data = node.data || {};
11
+ node.data.hName = 'section';
12
+ node.data.hProperties = { className: ['code-output'] };
13
+ }
14
+
15
+ const children = Array.isArray(node.children) ? node.children : [];
16
+ for (const child of children) visit(child);
17
+ };
18
+
19
+ visit(tree);
20
+ };
21
+ }
22
+
23
+
app/plugins/remark/outputs-container.mjs ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Transform `:::outputs ... :::` into a <section class="code-outputs"> wrapper
2
+ // Requires remark-directive to be applied before this plugin
3
+
4
+ export default function remarkOutputsContainer() {
5
+ return (tree) => {
6
+ const visit = (node) => {
7
+ if (!node || typeof node !== 'object') return;
8
+
9
+ if (node.type === 'containerDirective' && node.name === 'outputs') {
10
+ node.data = node.data || {};
11
+ node.data.hName = 'section';
12
+ node.data.hProperties = { className: ['code-outputs'] };
13
+ }
14
+
15
+ const children = Array.isArray(node.children) ? node.children : [];
16
+ for (const child of children) visit(child);
17
+ };
18
+
19
+ visit(tree);
20
+ };
21
+ }
22
+
23
+
app/src/components/Accordion.astro CHANGED
@@ -88,6 +88,10 @@ const wrapperClass = ["accordion", className].filter(Boolean).join(" ");
88
  border-color: color-mix(in oklab, var(--border-color), var(--primary-color) 20%);
89
  }
90
 
 
 
 
 
91
  .accordion__summary {
92
  margin: 0;
93
  list-style: none;
 
88
  border-color: color-mix(in oklab, var(--border-color), var(--primary-color) 20%);
89
  }
90
 
91
+ .accordion[open] .accordion__summary {
92
+ border-bottom: 1px solid var(--border-color);
93
+ }
94
+
95
  .accordion__summary {
96
  margin: 0;
97
  list-style: none;
app/src/content/chapters/best-pratices.mdx CHANGED
@@ -60,5 +60,5 @@ Picking the right visualization depends on your goal (compare values, show distr
60
  src={visualPoster}
61
  alt="Visual Vocabulary: choosing the right chart by task"
62
  linkHref="https://raw.githubusercontent.com/Financial-Times/chart-doctor/refs/heads/main/visual-vocabulary/poster.png"
63
- caption={'Credits <a href="https://www.ft.com/" target="_blank" rel="noopener noreferrer">Financial-Times/chart-doctor</a> <br/>A handy reference to select chart types by purpose — click to enlarge.'}
64
  />
 
60
  src={visualPoster}
61
  alt="Visual Vocabulary: choosing the right chart by task"
62
  linkHref="https://raw.githubusercontent.com/Financial-Times/chart-doctor/refs/heads/main/visual-vocabulary/poster.png"
63
+ caption={'Credits <a href="https://www.ft.com/" target="_blank" rel="noopener noreferrer">Financial-Times</a> <br/>A handy reference to select chart types by purpose — click to enlarge.'}
64
  />
app/src/content/chapters/markdown.mdx CHANGED
@@ -44,7 +44,31 @@ $$
44
 
45
  ### Code
46
 
47
- Use fenced code blocks with a language for syntax highlighting (e.g., `python`).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
  ```python
50
  def greet(name: str) -> None:
@@ -52,6 +76,16 @@ def greet(name: str) -> None:
52
 
53
  greet("Astro")
54
  ```
 
 
 
 
 
 
 
 
 
 
55
 
56
  <Accordion title="Code example">
57
  ````mdx
@@ -61,6 +95,15 @@ def greet(name: str) -> None:
61
 
62
  greet("Astro")
63
  ```
 
 
 
 
 
 
 
 
 
64
  ````
65
  </Accordion>
66
 
 
44
 
45
  ### Code
46
 
47
+ Use inline code with backticks \`...\` or \`\`\` fenced code blocks \`\`\` with a language for syntax highlighting (e.g., \`python\`).
48
+
49
+ As an example, here is inline code: `greet("Astro")` and below is a block.
50
+
51
+
52
+ ```python
53
+ def greet(name: str) -> None:
54
+ print(f"Hello, {name}!")
55
+ ```
56
+
57
+ <Accordion title="Code example">
58
+ ````mdx
59
+ `greet("Astro")`
60
+
61
+ ```python
62
+ def greet(name: str) -> None:
63
+ print(f"Hello, {name}!")
64
+ ```
65
+ ````
66
+ </Accordion>
67
+
68
+
69
+ ### Code output
70
+
71
+ If you want to display the output of a code block, you can use the `:::output` directive. If it's directly below the code block, it will adapt to the code block's styling.
72
 
73
  ```python
74
  def greet(name: str) -> None:
 
76
 
77
  greet("Astro")
78
  ```
79
+ :::output
80
+ Hello, Astro!
81
+ :::
82
+
83
+ Or it can also be used at a standalone block.
84
+
85
+ :::output
86
+ Hello i'm a standalone output block.
87
+ :::
88
+
89
 
90
  <Accordion title="Code example">
91
  ````mdx
 
95
 
96
  greet("Astro")
97
  ```
98
+ :::output
99
+ Hello, Astro!
100
+ :::
101
+
102
+ Or you can also use it at a standalone block.
103
+
104
+ :::output
105
+ Hello i'm a standalone outputs block.
106
+ :::
107
  ````
108
  </Accordion>
109
 
app/src/env.d.ts CHANGED
@@ -2,12 +2,6 @@
2
  /// <reference types="astro/client" />
3
  /// <reference types="vite/client" />
4
 
5
- interface ImportMetaEnv {
6
- readonly PUBLIC_TABLE_OF_CONTENT_AUTO_COLLAPSE?: string | boolean;
7
- // Back-compat
8
- readonly PUBLIC_TOC_AUTO_COLLAPSE?: string | boolean;
9
- }
10
-
11
  interface ImportMeta {
12
  readonly env: ImportMetaEnv;
13
  }
 
2
  /// <reference types="astro/client" />
3
  /// <reference types="vite/client" />
4
 
 
 
 
 
 
 
5
  interface ImportMeta {
6
  readonly env: ImportMetaEnv;
7
  }
app/src/pages/index.astro CHANGED
@@ -6,7 +6,6 @@ import Footer from '../components/Footer.astro';
6
  import ThemeToggle from '../components/ThemeToggle.astro';
7
  import Seo from '../components/Seo.astro';
8
  import TableOfContents from '../components/TableOfContents.astro';
9
- import colorPalettesUrl from '../scripts/color-palettes.js?url';
10
  // Default OG image served from public/
11
  const ogDefaultUrl = '/thumb.auto.jpg';
12
  import 'katex/dist/katex.min.css';
@@ -144,7 +143,7 @@ const licence = (articleFM as any)?.licence ?? (articleFM as any)?.license ?? (a
144
  } catch {}
145
  })();
146
  </script>
147
- <script type="module" src={colorPalettesUrl}></script>
148
 
149
  <!-- TO MANAGE PROPERLY -->
150
  <script src="https://cdn.plot.ly/plotly-3.0.0.min.js" charset="utf-8"></script>
 
6
  import ThemeToggle from '../components/ThemeToggle.astro';
7
  import Seo from '../components/Seo.astro';
8
  import TableOfContents from '../components/TableOfContents.astro';
 
9
  // Default OG image served from public/
10
  const ogDefaultUrl = '/thumb.auto.jpg';
11
  import 'katex/dist/katex.min.css';
 
143
  } catch {}
144
  })();
145
  </script>
146
+ <script type="module" src="/src/scripts/color-palettes.js"></script>
147
 
148
  <!-- TO MANAGE PROPERLY -->
149
  <script src="https://cdn.plot.ly/plotly-3.0.0.min.js" charset="utf-8"></script>
app/src/styles/components/_code.css CHANGED
@@ -33,16 +33,17 @@ p code, .note code {
33
  /* Shared sizing & horizontal scroll for code containers */
34
  .astro-code,
35
  section.content-grid pre {
36
- overflow-x: auto;
37
  width: 100%;
38
  max-width: 100%;
39
  box-sizing: border-box;
40
  -webkit-overflow-scrolling: touch;
41
  padding: 0;
42
  margin-bottom: var(--block-spacing-y) !important;
 
 
43
  }
44
 
45
- section.content-grid pre { margin: 0; }
46
 
47
  section.content-grid pre code {
48
  display: inline-block;
@@ -193,3 +194,105 @@ section.content-grid pre code {
193
 
194
  /* In Accordions, keep same bottom-right placement */
195
  .accordion .astro-code::after { right: 0; bottom: 0; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  /* Shared sizing & horizontal scroll for code containers */
34
  .astro-code,
35
  section.content-grid pre {
 
36
  width: 100%;
37
  max-width: 100%;
38
  box-sizing: border-box;
39
  -webkit-overflow-scrolling: touch;
40
  padding: 0;
41
  margin-bottom: var(--block-spacing-y) !important;
42
+ overflow: hidden!important;
43
+ overflow-x: auto;
44
  }
45
 
46
+ section.content-grid pre.astro-code { margin: 0; padding: var(--spacing-1) 0; }
47
 
48
  section.content-grid pre code {
49
  display: inline-block;
 
194
 
195
  /* In Accordions, keep same bottom-right placement */
196
  .accordion .astro-code::after { right: 0; bottom: 0; }
197
+
198
+ /* ============================================================================ */
199
+ /* Results blocks glued to code blocks */
200
+ /* ============================================================================ */
201
+ .code-output {
202
+ position: relative;
203
+ background: oklch(from var(--code-bg) calc(l - 0.005) c h);
204
+ border: 1px solid var(--border-color);
205
+ border-radius: 6px;
206
+ padding: calc(var(--spacing-3) + 6px) var(--spacing-3) var(--spacing-3) var(--spacing-3);
207
+ margin-top: 0;
208
+ margin-bottom: var(--block-spacing-y);
209
+ }
210
+
211
+ /* If immediately following a code container, keep tight visual connection */
212
+ .code-card + .code-output,
213
+ .astro-code + .code-output,
214
+ section.content-grid pre + .code-output {
215
+ margin-top: 0;
216
+ border-top: none;
217
+ border-top-left-radius: 0;
218
+ border-top-right-radius: 0;
219
+ box-shadow: inset 0 8px 12px -12px rgba(0, 0, 0, 0.15);
220
+
221
+ }
222
+
223
+ /* Remove bottom margin on code when immediately followed by results */
224
+ .astro-code:has(+ .code-output) {
225
+ margin-bottom: 0 !important;
226
+ }
227
+ .code-card:has(+ .code-output) .astro-code {
228
+ margin-bottom: 0 !important;
229
+ }
230
+ section.content-grid pre:has(+ .code-output) {
231
+ margin-bottom: 0 !important;
232
+ }
233
+
234
+ /* Remove bottom border radius on code when followed by results */
235
+ .astro-code:has(+ .code-output) {
236
+ border-bottom-left-radius: 0;
237
+ border-bottom-right-radius: 0;
238
+ }
239
+ .code-card:has(+ .code-output) .astro-code {
240
+ border-bottom-left-radius: 0;
241
+ border-bottom-right-radius: 0;
242
+ }
243
+ section.content-grid pre:has(+ .code-output) {
244
+ border-bottom-left-radius: 0;
245
+ border-bottom-right-radius: 0;
246
+ }
247
+
248
+ /* Small top-left label */
249
+ .code-output::before {
250
+ content: "Output";
251
+ position: absolute;
252
+ top: 0;
253
+ right: 0;
254
+ font-size: 10px;
255
+ line-height: 1;
256
+ color: var(--muted-color);
257
+ text-transform: uppercase;
258
+ letter-spacing: 0.04em;
259
+ border-top: none;
260
+ border-right: none;
261
+ border-radius: 0 0 0 6px;
262
+ padding: 10px 10px;
263
+ }
264
+
265
+ /* Very subtle top shadow so results feels under the code */
266
+ .code-output {
267
+ }
268
+
269
+ .code-output > :where(*):first-child {
270
+ margin-top: 0 !important;
271
+ }
272
+
273
+ .code-output > :where(*):last-child {
274
+ margin-bottom: 0 !important;
275
+ }
276
+
277
+ /* ============================================================================ */
278
+ /* Optional filename tag above code blocks */
279
+ /* ============================================================================ */
280
+ .code-filename {
281
+ display: inline-block;
282
+ font-size: 12px;
283
+ line-height: 1;
284
+ color: var(--muted-color);
285
+ background: var(--surface-bg);
286
+ border: 1px solid var(--border-color);
287
+ border-bottom: none;
288
+ border-radius: 6px 6px 0 0;
289
+ padding: 4px 8px;
290
+ margin: 0;
291
+ }
292
+
293
+ .code-filename + .code-card .astro-code,
294
+ .code-filename + .astro-code,
295
+ .code-filename + section.content-grid pre {
296
+ border-top-left-radius: 0;
297
+ border-top-right-radius: 6px;
298
+ }