@brandon_m_behring/book-scaffold-astro 3.0.0-alpha.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 (84) hide show
  1. package/CLAUDE.md +179 -0
  2. package/bin/book-scaffold.mjs +61 -0
  3. package/components/CaseStudy.astro +36 -0
  4. package/components/ChapterHeader.astro +61 -0
  5. package/components/ChapterNav.astro +29 -0
  6. package/components/ChapterTOC.astro +33 -0
  7. package/components/Citation.astro +94 -0
  8. package/components/Cite.astro +71 -0
  9. package/components/CodeBlock.astro +115 -0
  10. package/components/CodeRef.astro +49 -0
  11. package/components/ConceptBox.astro +26 -0
  12. package/components/Convergence.astro +41 -0
  13. package/components/CounterBox.astro +15 -0
  14. package/components/Divergence.astro +32 -0
  15. package/components/DynConnect.astro +15 -0
  16. package/components/ExampleBox.astro +15 -0
  17. package/components/Figure.astro +35 -0
  18. package/components/InsightBox.astro +15 -0
  19. package/components/KeyIdea.astro +21 -0
  20. package/components/MarginNote.astro +37 -0
  21. package/components/NoteBox.astro +15 -0
  22. package/components/OpenQuestion.astro +15 -0
  23. package/components/PaperBox.astro +15 -0
  24. package/components/PatternTimeline.astro +133 -0
  25. package/components/Recovery.astro +34 -0
  26. package/components/ResultBox.astro +15 -0
  27. package/components/Sidebar.astro +268 -0
  28. package/components/Sidenote.astro +26 -0
  29. package/components/SkillBox.astro +24 -0
  30. package/components/SourceArchive.astro +285 -0
  31. package/components/StatusBadge.astro +51 -0
  32. package/components/Tag.astro +60 -0
  33. package/components/Theorem.astro +65 -0
  34. package/components/TipBox.astro +15 -0
  35. package/components/ToolFilter.tsx +160 -0
  36. package/components/TryThis.astro +23 -0
  37. package/components/VersionSelector.tsx +85 -0
  38. package/components/WarnBox.astro +15 -0
  39. package/components/WeekRef.astro +51 -0
  40. package/components/XRef.astro +40 -0
  41. package/dist/index.d.ts +135 -0
  42. package/dist/index.mjs +369 -0
  43. package/dist/lib/katex-macros.d.ts +26 -0
  44. package/dist/lib/katex-macros.mjs +98 -0
  45. package/dist/schemas.d.ts +17 -0
  46. package/dist/schemas.mjs +160 -0
  47. package/dist/types-Cz-pwE1N.d.ts +61 -0
  48. package/examples/chapter-template-academic.mdx +100 -0
  49. package/examples/chapter-template-tools.mdx +90 -0
  50. package/layouts/Base.astro +250 -0
  51. package/layouts/Chapter.astro +37 -0
  52. package/package.json +137 -0
  53. package/pages/chapters.astro +371 -0
  54. package/pages/convergence.astro +96 -0
  55. package/pages/print.astro +39 -0
  56. package/pages/references.astro +160 -0
  57. package/pages/search.astro +87 -0
  58. package/pedagogy/kf-chapter-shape.md +96 -0
  59. package/pedagogy/source-tiers.md +121 -0
  60. package/pedagogy/volatility-classes.md +110 -0
  61. package/recipes/00-getting-started.md +77 -0
  62. package/recipes/01-add-math.md +71 -0
  63. package/recipes/02-bibliography-pipeline.md +82 -0
  64. package/recipes/03-asset-pipelines.md +84 -0
  65. package/recipes/04-component-library.md +118 -0
  66. package/recipes/05-deploy-cloudflare.md +74 -0
  67. package/recipes/06-mobile-first-layout.md +73 -0
  68. package/recipes/07-chapter-shapes.md +84 -0
  69. package/recipes/08-decisions-ledger.md +110 -0
  70. package/recipes/09-validation.md +106 -0
  71. package/recipes/10-custom-domain.md +72 -0
  72. package/recipes/README.md +43 -0
  73. package/scripts/build-bib.mjs +99 -0
  74. package/scripts/build-figures.mjs +179 -0
  75. package/scripts/render-notebooks.mjs +223 -0
  76. package/scripts/validate.mjs +179 -0
  77. package/styles/callouts.css +303 -0
  78. package/styles/chapter.css +209 -0
  79. package/styles/convergence.css +349 -0
  80. package/styles/layout.css +156 -0
  81. package/styles/print.css +203 -0
  82. package/styles/tokens.css +194 -0
  83. package/styles/tool-filter.css +135 -0
  84. package/styles/typography.css +147 -0
@@ -0,0 +1,61 @@
1
+ import { AstroIntegration, AstroUserConfig } from 'astro';
2
+
3
+ /**
4
+ * Shared types for @brandon_m_behring/book-scaffold-astro.
5
+ *
6
+ * Public types referenced from PACKAGE_DESIGN.md §4 / §5 / §6. Kept in
7
+ * one place so consumer IntelliSense surfaces a coherent API.
8
+ */
9
+
10
+ type BookProfile = 'academic' | 'tools' | 'minimal';
11
+ declare const BOOK_PROFILES: readonly ["academic", "tools", "minimal"];
12
+ /**
13
+ * Options for `defineBookConfig`. See PACKAGE_DESIGN.md §4.
14
+ *
15
+ * Note on the index signature: `AstroUserConfig` carries generic
16
+ * parameters (`Locales`, `SessionDriverName`, fonts) that can't be
17
+ * threaded cleanly through a wrapper. Instead we type the package-
18
+ * specific fields strictly and allow arbitrary AstroUserConfig keys
19
+ * via the index signature — consumer types will lint clean but lose
20
+ * full IDE autocomplete on non-package fields. Acceptable trade.
21
+ */
22
+ interface BookConfigOptions {
23
+ /** Required. Book's deployed origin (sitemap, canonical, Pagefind). */
24
+ site: string;
25
+ /**
26
+ * Optional. Falls back to `process.env.BOOK_PROFILE`, then `'minimal'`.
27
+ * Explicit param always wins over env.
28
+ */
29
+ profile?: BookProfile;
30
+ /** Optional. Appended to the package-provided integration list. */
31
+ extraIntegrations?: AstroIntegration[];
32
+ /**
33
+ * Optional. CSS basenames to inject in addition to the profile-resolved
34
+ * set. Cross-profile escape hatch (e.g. an academic book using
35
+ * `<Convergence>`). Example: `['convergence.css']`.
36
+ */
37
+ extraStyles?: string[];
38
+ /** Optional. Spread-merged into the package-provided markdown config. */
39
+ markdown?: AstroUserConfig['markdown'];
40
+ /** Escape hatch for any other AstroUserConfig field. */
41
+ [key: string]: unknown;
42
+ }
43
+ /** Options for `defineBookSchemas`. See PACKAGE_DESIGN.md §5. */
44
+ interface BookSchemasOptions {
45
+ profile?: BookProfile;
46
+ /** Defaults to `'./src/content/chapters'`. */
47
+ chaptersBase?: string;
48
+ }
49
+ /** Options for the internal `bookScaffoldIntegration`. See PACKAGE_DESIGN.md §6. */
50
+ interface BookScaffoldIntegrationOptions {
51
+ profile: BookProfile;
52
+ extraStyles?: string[];
53
+ }
54
+ /** Raised when the resolved profile is not one of `BOOK_PROFILES`. */
55
+ declare class BookConfigError extends Error {
56
+ constructor(message: string);
57
+ }
58
+ /** Resolve profile from explicit param → env → default. Throws on invalid. */
59
+ declare function resolveProfile(explicit?: BookProfile): BookProfile;
60
+
61
+ export { BOOK_PROFILES as B, BookConfigError as a, type BookConfigOptions as b, type BookProfile as c, type BookScaffoldIntegrationOptions as d, type BookSchemasOptions as e, resolveProfile as r };
@@ -0,0 +1,100 @@
1
+ ---
2
+ # Frontmatter for BOOK_PROFILE=academic. See src/content.config.ts for
3
+ # the full schema and validation rules.
4
+ week: 1
5
+ part: foundations
6
+ title: "Chapter title — your subject here"
7
+ status: scaffolded
8
+ # Optional fields:
9
+ # description: "Short SEO/meta description, ~140 chars."
10
+ # code_path: experiments/jax/week01/some_module.py
11
+ # tests_path: experiments/jax/week01/test_some_module.py
12
+ # notebook_path: notebooks/week01/companion.ipynb
13
+ # roadmap_lines: [10, 42]
14
+ ---
15
+ import NoteBox from '../../components/callouts/NoteBox.astro';
16
+ import ExampleBox from '../../components/callouts/ExampleBox.astro';
17
+ import InsightBox from '../../components/callouts/InsightBox.astro';
18
+ import OpenQuestion from '../../components/callouts/OpenQuestion.astro';
19
+ import Theorem from '../../components/Theorem.astro';
20
+ import Cite from '../../components/Cite.astro';
21
+ import Figure from '../../components/Figure.astro';
22
+ import Sidenote from '../../components/Sidenote.astro';
23
+
24
+ # {frontmatter.title}
25
+
26
+ ## Overview
27
+
28
+ A half-page motivation: what this chapter covers, why it matters,
29
+ and how it fits into the curriculum. Reader should be able to decide
30
+ after the Overview whether to read further.<Sidenote>Use Sidenote
31
+ for short asides that belong in the right margin on desktop and
32
+ reflow inline on mobile.</Sidenote>
33
+
34
+ <InsightBox>
35
+ The single most important takeaway of this chapter, stated as a
36
+ short principle. Comes before the Theory so a reader who only
37
+ skims gets the heart of the matter.
38
+ </InsightBox>
39
+
40
+ ## Theory
41
+
42
+ The core mathematical content. Definitions, lemmas, propositions,
43
+ theorems. Cite primary sources using <Cite key="example-key2024" />.
44
+
45
+ <Theorem type="definition" id="ch1:def:main">
46
+ A *something* is a mathematical object such that … Provide the
47
+ precise definition with the key properties explicit.
48
+ </Theorem>
49
+
50
+ <Theorem type="theorem" id="ch1:thm:main" label="Main result">
51
+ Under the conditions of Definition <a href="#ch1:def:main">1.1</a>,
52
+ the property $P$ holds with $\|x\| \le c \cdot \|y\|$ for some
53
+ constant $c > 0$.
54
+ </Theorem>
55
+
56
+ <Theorem type="proof">
57
+ Direct calculation. Apply the chain rule, regroup terms, and use
58
+ the boundedness assumption from the hypothesis.
59
+ </Theorem>
60
+
61
+ ## Examples
62
+
63
+ Concrete instances that exercise the theory. At least one worked
64
+ example per theorem.
65
+
66
+ <ExampleBox title="Worked example 1">
67
+ Take $A = \mathrm{diag}(\lambda_1, \ldots, \lambda_n)$ with all
68
+ $|\lambda_i| < 1$. Then the iteration $x_{k+1} = A x_k$ converges
69
+ to 0, with rate $\max_i |\lambda_i|$.
70
+ </ExampleBox>
71
+
72
+ <Figure
73
+ src="/figures/example/placeholder.svg"
74
+ caption="Caption explaining what the reader should see in the figure."
75
+ id="ch1-fig-example"
76
+ />
77
+
78
+ ## Reflections
79
+
80
+ What should the reader walk away knowing? Three to five takeaways.
81
+
82
+ 1. The first key insight, restated in plain language.
83
+ 2. The second key insight, with a connection to a sibling chapter.
84
+ 3. The third key insight, often a caveat or limitation.
85
+
86
+ <OpenQuestion>
87
+ A research gap the chapter exposes. What's not yet known? What
88
+ would extend this result? Pointers to the wish-list / project-ideas
89
+ file for ambitious readers.
90
+ </OpenQuestion>
91
+
92
+ ## Forward-map
93
+
94
+ How this chapter connects to next week. One paragraph; sets up the
95
+ reader's mental model for what comes next.
96
+
97
+ <NoteBox>
98
+ Delete this section when no follow-on chapter exists yet, or replace
99
+ with a "Synthesis" section in the final chapter of a part.
100
+ </NoteBox>
@@ -0,0 +1,90 @@
1
+ ---
2
+ # Frontmatter for BOOK_PROFILE=tools. See src/content.config.ts for
3
+ # the full schema and validation rules.
4
+ title: "Chapter title — your topic here"
5
+ part: 1
6
+ chapter: 1
7
+ volatility: architectural-pattern
8
+ tools_compared: [claude-code, gemini-cli, codex-cli]
9
+ last_verified: 2026-05-18
10
+ sources: []
11
+ # Optional fields:
12
+ # description: "Short SEO/meta description, ~140 chars."
13
+ # draft: false
14
+ # updated: 2026-05-18
15
+ ---
16
+ import SkillBox from '../../components/callouts/SkillBox.astro';
17
+ import CaseStudy from '../../components/callouts/CaseStudy.astro';
18
+ import ConceptBox from '../../components/callouts/ConceptBox.astro';
19
+ import KeyIdea from '../../components/callouts/KeyIdea.astro';
20
+ import TryThis from '../../components/callouts/TryThis.astro';
21
+ import Recovery from '../../components/callouts/Recovery.astro';
22
+ import Convergence from '../../components/callouts/Convergence.astro';
23
+ import Divergence from '../../components/callouts/Divergence.astro';
24
+
25
+ # {frontmatter.title}
26
+
27
+ ## Representation
28
+
29
+ What does the artifact (a pattern, a feature, a capability) look like
30
+ across the tools you're comparing? Define terms precisely.
31
+
32
+ <ConceptBox term="The thing being represented">
33
+ A short, precise definition. Avoid jargon; if jargon is unavoidable,
34
+ link to a chapter where it's defined.
35
+ </ConceptBox>
36
+
37
+ <KeyIdea>
38
+ The single most important framing of this chapter. The reader who
39
+ reads only this box should still leave with the central insight.
40
+ </KeyIdea>
41
+
42
+ ## Operation
43
+
44
+ What do you DO with this artifact? Concrete operations, with
45
+ examples per tool. Show, don't tell.
46
+
47
+ <SkillBox title="The core skill">
48
+ A practical workflow the reader can apply right now. Numbered steps,
49
+ specific commands, no hand-waving.
50
+
51
+ 1. First concrete action
52
+ 2. Second concrete action
53
+ 3. Verify
54
+ </SkillBox>
55
+
56
+ <TryThis title="Hands-on exercise">
57
+ Open your own project and apply the skill above to a specific case.
58
+ What did you observe? What surprised you?
59
+ </TryThis>
60
+
61
+ <CaseStudy date="2026-05">
62
+ A specific, dated incident or example showing the pattern in
63
+ practice. Concrete numbers if possible (durations, file counts,
64
+ token savings, etc.).
65
+ </CaseStudy>
66
+
67
+ <Recovery
68
+ pattern="Common anti-pattern in this space"
69
+ symptom="What the reader experiences when stuck in the anti-pattern."
70
+ >
71
+ The recommended fix. One paragraph.
72
+ </Recovery>
73
+
74
+ ## Evolution
75
+
76
+ What's converging or diverging across the tools you're tracking?
77
+ This is the comparative-tracking heart of the Koller-Friedman shape
78
+ applied to evolving technology.
79
+
80
+ <Convergence tools={['claude-code', 'gemini-cli', 'codex-cli']}>
81
+ All tracked tools now do X. You can safely write practices that
82
+ assume this. Date the convergence so the reader knows when to
83
+ re-check.
84
+ </Convergence>
85
+
86
+ <Divergence axis="The axis of disagreement">
87
+ Tool A picks one tradeoff; Tool B picks the opposite. The space
88
+ hasn't settled. Document both positions plainly so the reader can
89
+ make an informed choice.
90
+ </Divergence>
@@ -0,0 +1,250 @@
1
+ ---
2
+ /**
3
+ * Base.astro — the outermost HTML shell.
4
+ *
5
+ * Every page in the book extends this. Loads:
6
+ * - Variable fonts (Roboto, Source Code Pro) self-hosted via @fontsource-variable
7
+ * - Design tokens, baseline typography, Tufte layout
8
+ * - FOUC-prevention script for dark mode (reads localStorage before paint)
9
+ * - Meta tags for viewport, color-scheme, generator
10
+ * - Optional left chapter-nav Sidebar (showSidebar prop; default true)
11
+ *
12
+ * Props:
13
+ * title — document title (required)
14
+ * description — meta description (optional; used by search and social)
15
+ * lang — html lang attribute (default: en)
16
+ * showSidebar — render the left chapter-navigation sidebar (default: true).
17
+ * Set false for the landing page or any full-bleed surface.
18
+ * Below 1024px the sidebar is auto-hidden via CSS regardless.
19
+ *
20
+ * Tools chrome (ToolFilter, VersionSelector): only mounted when
21
+ * BOOK_PROFILE !== 'academic'. Academic-profile books get no JS islands
22
+ * in the chrome row (search + theme toggle only).
23
+ *
24
+ * Usage:
25
+ * ---
26
+ * import Base from '../layouts/Base.astro';
27
+ * ---
28
+ * <Base title="Chapter 1" description="...">
29
+ * <main>...</main>
30
+ * </Base>
31
+ */
32
+ import '@fontsource-variable/roboto';
33
+ import '@fontsource-variable/source-code-pro';
34
+ // KaTeX stylesheet is always loaded (~60 KB) so that academic-profile
35
+ // books render math without additional setup. Minimal/tools profiles
36
+ // receive the CSS but have no math elements to style — harmless.
37
+ // rehype-katex (the JS pipeline) is profile-gated in astro.config.mjs;
38
+ // only the academic profile produces katex-classed DOM nodes at build.
39
+ import 'katex/dist/katex.min.css';
40
+ import '../styles/tokens.css';
41
+ import '../styles/typography.css';
42
+ import '../styles/layout.css';
43
+ import '../styles/callouts.css';
44
+ import '../styles/chapter.css';
45
+ import '../styles/tool-filter.css';
46
+ import '../styles/convergence.css';
47
+ import '../styles/print.css';
48
+ import VersionSelector from '../components/VersionSelector';
49
+ import ToolFilter from '../components/ToolFilter';
50
+ import Sidebar from '../components/Sidebar.astro';
51
+
52
+ const profile = import.meta.env.BOOK_PROFILE ?? 'minimal';
53
+ const showToolsChrome = profile !== 'academic';
54
+
55
+ interface Props {
56
+ title: string;
57
+ description?: string;
58
+ lang?: string;
59
+ showSidebar?: boolean;
60
+ }
61
+
62
+ const { title, description, lang = 'en', showSidebar = true } = Astro.props;
63
+ ---
64
+
65
+ <!doctype html>
66
+ <html lang={lang}>
67
+ <head>
68
+ <meta charset="utf-8" />
69
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
70
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
71
+ <meta name="color-scheme" content="light dark" />
72
+ <meta name="generator" content={Astro.generator} />
73
+ <title>{title}</title>
74
+ {description && <meta name="description" content={description} />}
75
+ {/* FOUC prevention — apply saved theme before any paint. */}
76
+ <script is:inline>
77
+ (function () {
78
+ try {
79
+ var saved = localStorage.getItem('theme');
80
+ if (saved === 'light' || saved === 'dark') {
81
+ document.documentElement.setAttribute('data-theme', saved);
82
+ }
83
+ } catch (e) { /* localStorage unavailable — fall back to prefers-color-scheme */ }
84
+ })();
85
+ </script>
86
+ <slot name="head" />
87
+ </head>
88
+ <body>
89
+ <div class="chrome-buttons">
90
+ {showToolsChrome && <ToolFilter client:idle />}
91
+ {showToolsChrome && <VersionSelector client:idle />}
92
+ <a
93
+ href="/search/"
94
+ class="chrome-button"
95
+ aria-label="Search the book"
96
+ title="Search"
97
+ >
98
+ <span aria-hidden="true">⌕</span>
99
+ </a>
100
+ <button
101
+ id="theme-toggle"
102
+ class="chrome-button theme-toggle"
103
+ type="button"
104
+ aria-label="Toggle dark mode"
105
+ title="Toggle dark mode"
106
+ >
107
+ <span class="theme-toggle-icon light-icon" aria-hidden="true">☀</span>
108
+ <span class="theme-toggle-icon dark-icon" aria-hidden="true">☾</span>
109
+ </button>
110
+ </div>
111
+ {showSidebar ? (
112
+ <div class="layout-with-sidebar">
113
+ <Sidebar />
114
+ <main class="layout-main"><slot /></main>
115
+ </div>
116
+ ) : (
117
+ <slot />
118
+ )}
119
+ <script is:inline>
120
+ (function () {
121
+ var btn = document.getElementById('theme-toggle');
122
+ if (!btn) return;
123
+ btn.addEventListener('click', function () {
124
+ var current = document.documentElement.getAttribute('data-theme');
125
+ // If no explicit theme, pick the opposite of prefers-color-scheme.
126
+ if (!current) {
127
+ var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
128
+ current = prefersDark ? 'dark' : 'light';
129
+ }
130
+ var next = current === 'dark' ? 'light' : 'dark';
131
+ document.documentElement.setAttribute('data-theme', next);
132
+ try { localStorage.setItem('theme', next); } catch (e) {}
133
+ });
134
+ })();
135
+ </script>
136
+ </body>
137
+ </html>
138
+
139
+ <style is:global>
140
+ /* Floating chrome buttons — fixed top-right corner.
141
+ * Search icon + theme toggle; more buttons can join this row later. */
142
+ .chrome-buttons {
143
+ position: fixed;
144
+ top: var(--space-3);
145
+ right: var(--space-3);
146
+ display: inline-flex;
147
+ gap: var(--space-2);
148
+ z-index: 10;
149
+ }
150
+ .chrome-button {
151
+ width: 2.25rem;
152
+ height: 2.25rem;
153
+ padding: 0;
154
+ display: inline-flex;
155
+ align-items: center;
156
+ justify-content: center;
157
+ background: var(--color-bg-subtle);
158
+ color: var(--color-text);
159
+ border: 1px solid var(--color-border);
160
+ border-radius: 50%;
161
+ cursor: pointer;
162
+ font-size: 1.1rem;
163
+ line-height: 1;
164
+ text-decoration: none;
165
+ transition: background 120ms ease, border-color 120ms ease;
166
+ }
167
+ .chrome-button:hover {
168
+ border-color: var(--color-link);
169
+ text-decoration: none;
170
+ }
171
+
172
+ /* Version selector dropdown */
173
+ .version-selector {
174
+ position: relative;
175
+ display: inline-block;
176
+ }
177
+ .version-selector-trigger {
178
+ font-family: var(--font-code);
179
+ font-weight: 600;
180
+ font-size: 0.9rem;
181
+ padding: 0 0.6em !important;
182
+ }
183
+ .version-selector-menu {
184
+ position: absolute;
185
+ top: calc(100% + var(--space-2));
186
+ right: 0;
187
+ min-width: 12rem;
188
+ margin: 0;
189
+ padding: var(--space-2);
190
+ list-style: none;
191
+ background: var(--color-bg);
192
+ border: 1px solid var(--color-border);
193
+ border-radius: var(--radius-md);
194
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
195
+ }
196
+ .version-selector-menu li {
197
+ margin: 0;
198
+ }
199
+ .version-selector-menu a {
200
+ display: grid;
201
+ grid-template-columns: 1fr auto;
202
+ gap: var(--space-2);
203
+ padding: var(--space-2) var(--space-3);
204
+ text-decoration: none;
205
+ color: var(--color-text);
206
+ border-radius: var(--radius-sm);
207
+ }
208
+ .version-selector-menu a:hover {
209
+ background: var(--color-bg-subtle);
210
+ }
211
+ .version-selector-menu a.version-current {
212
+ color: var(--color-link);
213
+ font-weight: 500;
214
+ }
215
+ .version-selector-menu .version-label {
216
+ font-family: var(--font-body);
217
+ font-size: var(--text-sm);
218
+ }
219
+ .version-selector-menu .version-date {
220
+ font-family: var(--font-code);
221
+ font-size: var(--text-xs);
222
+ color: var(--color-text-muted);
223
+ }
224
+ .theme-toggle .theme-toggle-icon {
225
+ display: none;
226
+ }
227
+ /* Default (light): show moon (click to go dark) */
228
+ .theme-toggle .dark-icon {
229
+ display: inline;
230
+ }
231
+ /* Dark mode (explicit or system): show sun (click to go light) */
232
+ :root[data-theme="dark"] .theme-toggle .dark-icon,
233
+ :root:not([data-theme]) .theme-toggle .dark-icon {
234
+ display: inline;
235
+ }
236
+ :root[data-theme="dark"] .theme-toggle .light-icon {
237
+ display: inline;
238
+ }
239
+ :root[data-theme="dark"] .theme-toggle .dark-icon {
240
+ display: none;
241
+ }
242
+ @media (prefers-color-scheme: dark) {
243
+ :root:not([data-theme="light"]) .theme-toggle .light-icon {
244
+ display: inline;
245
+ }
246
+ :root:not([data-theme="light"]) .theme-toggle .dark-icon {
247
+ display: none;
248
+ }
249
+ }
250
+ </style>
@@ -0,0 +1,37 @@
1
+ ---
2
+ /**
3
+ * Chapter.astro — layout for a single chapter page.
4
+ *
5
+ * Wraps Base.astro with a chapter-specific structure:
6
+ * <article.prose>
7
+ * <ChapterHeader /> ← frontmatter metadata
8
+ * <ChapterTOC /> ← collapsed "On this page"
9
+ * <slot /> ← the rendered MDX body
10
+ * <ChapterNav /> ← prev / next
11
+ * </article.prose>
12
+ *
13
+ * Driven by a CollectionEntry<'chapters'> passed in Astro.props.entry
14
+ * plus the `headings` array Astro's MDX renderer provides.
15
+ */
16
+ import type { CollectionEntry } from 'astro:content';
17
+ import type { MarkdownHeading } from 'astro';
18
+ import Base from './Base.astro';
19
+ import ChapterHeader from '../components/ChapterHeader.astro';
20
+ import ChapterTOC from '../components/ChapterTOC.astro';
21
+ import ChapterNav from '../components/ChapterNav.astro';
22
+
23
+ interface Props {
24
+ entry: CollectionEntry<'chapters'>;
25
+ headings: MarkdownHeading[];
26
+ }
27
+ const { entry, headings } = Astro.props;
28
+ ---
29
+
30
+ <Base title={entry.data.title} description={entry.data.description}>
31
+ <article class="prose">
32
+ <ChapterHeader data={entry.data} />
33
+ <ChapterTOC headings={headings} />
34
+ <slot />
35
+ <ChapterNav currentId={entry.id} />
36
+ </article>
37
+ </Base>
package/package.json ADDED
@@ -0,0 +1,137 @@
1
+ {
2
+ "name": "@brandon_m_behring/book-scaffold-astro",
3
+ "description": "Astro 6 + MDX toolkit for long-form technical books. Profile-aware (academic / tools / minimal); ships Tufte typography, KaTeX, BibTeX citations, Pagefind, Cloudflare Workers deploy. See PACKAGE_DESIGN.md for the API contract.",
4
+ "version": "3.0.0-alpha.0",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Brandon Behring",
8
+ "homepage": "https://github.com/brandon-behring/book-scaffold-astro",
9
+ "bugs": "https://github.com/brandon-behring/book-scaffold-astro/issues",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/brandon-behring/book-scaffold-astro.git",
13
+ "directory": "package"
14
+ },
15
+ "keywords": [
16
+ "astro",
17
+ "astro-component",
18
+ "mdx",
19
+ "book",
20
+ "tufte",
21
+ "katex",
22
+ "bibtex",
23
+ "pagefind",
24
+ "book-scaffold"
25
+ ],
26
+ "engines": {
27
+ "node": ">=22.12.0"
28
+ },
29
+ "main": "./dist/index.mjs",
30
+ "types": "./dist/index.d.ts",
31
+ "bin": {
32
+ "book-scaffold": "./bin/book-scaffold.mjs"
33
+ },
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.ts",
37
+ "import": "./dist/index.mjs"
38
+ },
39
+ "./schemas": {
40
+ "types": "./dist/schemas.d.ts",
41
+ "import": "./dist/schemas.mjs"
42
+ },
43
+ "./components/CaseStudy.astro": "./components/CaseStudy.astro",
44
+ "./components/ChapterHeader.astro": "./components/ChapterHeader.astro",
45
+ "./components/ChapterNav.astro": "./components/ChapterNav.astro",
46
+ "./components/ChapterTOC.astro": "./components/ChapterTOC.astro",
47
+ "./components/Citation.astro": "./components/Citation.astro",
48
+ "./components/Cite.astro": "./components/Cite.astro",
49
+ "./components/CodeBlock.astro": "./components/CodeBlock.astro",
50
+ "./components/CodeRef.astro": "./components/CodeRef.astro",
51
+ "./components/ConceptBox.astro": "./components/ConceptBox.astro",
52
+ "./components/Convergence.astro": "./components/Convergence.astro",
53
+ "./components/CounterBox.astro": "./components/CounterBox.astro",
54
+ "./components/Divergence.astro": "./components/Divergence.astro",
55
+ "./components/DynConnect.astro": "./components/DynConnect.astro",
56
+ "./components/ExampleBox.astro": "./components/ExampleBox.astro",
57
+ "./components/Figure.astro": "./components/Figure.astro",
58
+ "./components/InsightBox.astro": "./components/InsightBox.astro",
59
+ "./components/KeyIdea.astro": "./components/KeyIdea.astro",
60
+ "./components/MarginNote.astro": "./components/MarginNote.astro",
61
+ "./components/NoteBox.astro": "./components/NoteBox.astro",
62
+ "./components/OpenQuestion.astro": "./components/OpenQuestion.astro",
63
+ "./components/PaperBox.astro": "./components/PaperBox.astro",
64
+ "./components/PatternTimeline.astro": "./components/PatternTimeline.astro",
65
+ "./components/Recovery.astro": "./components/Recovery.astro",
66
+ "./components/ResultBox.astro": "./components/ResultBox.astro",
67
+ "./components/Sidebar.astro": "./components/Sidebar.astro",
68
+ "./components/Sidenote.astro": "./components/Sidenote.astro",
69
+ "./components/SkillBox.astro": "./components/SkillBox.astro",
70
+ "./components/SourceArchive.astro": "./components/SourceArchive.astro",
71
+ "./components/StatusBadge.astro": "./components/StatusBadge.astro",
72
+ "./components/Tag.astro": "./components/Tag.astro",
73
+ "./components/Theorem.astro": "./components/Theorem.astro",
74
+ "./components/TipBox.astro": "./components/TipBox.astro",
75
+ "./components/ToolFilter": "./components/ToolFilter.tsx",
76
+ "./components/TryThis.astro": "./components/TryThis.astro",
77
+ "./components/VersionSelector": "./components/VersionSelector.tsx",
78
+ "./components/WarnBox.astro": "./components/WarnBox.astro",
79
+ "./components/WeekRef.astro": "./components/WeekRef.astro",
80
+ "./components/XRef.astro": "./components/XRef.astro",
81
+ "./styles/tokens.css": "./styles/tokens.css",
82
+ "./styles/layout.css": "./styles/layout.css",
83
+ "./styles/callouts.css": "./styles/callouts.css",
84
+ "./styles/chapter.css": "./styles/chapter.css",
85
+ "./styles/typography.css": "./styles/typography.css",
86
+ "./styles/print.css": "./styles/print.css",
87
+ "./styles/convergence.css": "./styles/convergence.css",
88
+ "./styles/tool-filter.css": "./styles/tool-filter.css",
89
+ "./layouts/Base.astro": "./layouts/Base.astro",
90
+ "./layouts/Chapter.astro": "./layouts/Chapter.astro",
91
+ "./lib": {
92
+ "types": "./dist/lib/katex-macros.d.ts",
93
+ "import": "./dist/lib/katex-macros.mjs"
94
+ }
95
+ },
96
+ "files": [
97
+ "dist",
98
+ "components",
99
+ "styles",
100
+ "layouts",
101
+ "pages",
102
+ "scripts",
103
+ "bin",
104
+ "recipes",
105
+ "pedagogy",
106
+ "examples",
107
+ "CLAUDE.md",
108
+ "README.md"
109
+ ],
110
+ "scripts": {
111
+ "build": "tsup",
112
+ "prepublishOnly": "npm run build"
113
+ },
114
+ "peerDependencies": {
115
+ "astro": "^6.1.7",
116
+ "@astrojs/mdx": "^5.0.3",
117
+ "@astrojs/preact": "^5.1.1",
118
+ "preact": "^10.29.1"
119
+ },
120
+ "peerDependenciesMeta": {
121
+ "katex": { "optional": true },
122
+ "rehype-katex": { "optional": true },
123
+ "remark-math": { "optional": true }
124
+ },
125
+ "dependencies": {
126
+ "@citation-js/core": "^0.7.21",
127
+ "@citation-js/plugin-bibtex": "^0.7.21",
128
+ "@fontsource-variable/roboto": "^5.2.10",
129
+ "@fontsource-variable/source-code-pro": "^5.2.7",
130
+ "pagefind": "^1.5.2"
131
+ },
132
+ "devDependencies": {
133
+ "@types/node": "^22.10.0",
134
+ "tsup": "^8.3.5",
135
+ "typescript": "^5.7.0"
136
+ }
137
+ }