@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.
- package/CLAUDE.md +179 -0
- package/bin/book-scaffold.mjs +61 -0
- package/components/CaseStudy.astro +36 -0
- package/components/ChapterHeader.astro +61 -0
- package/components/ChapterNav.astro +29 -0
- package/components/ChapterTOC.astro +33 -0
- package/components/Citation.astro +94 -0
- package/components/Cite.astro +71 -0
- package/components/CodeBlock.astro +115 -0
- package/components/CodeRef.astro +49 -0
- package/components/ConceptBox.astro +26 -0
- package/components/Convergence.astro +41 -0
- package/components/CounterBox.astro +15 -0
- package/components/Divergence.astro +32 -0
- package/components/DynConnect.astro +15 -0
- package/components/ExampleBox.astro +15 -0
- package/components/Figure.astro +35 -0
- package/components/InsightBox.astro +15 -0
- package/components/KeyIdea.astro +21 -0
- package/components/MarginNote.astro +37 -0
- package/components/NoteBox.astro +15 -0
- package/components/OpenQuestion.astro +15 -0
- package/components/PaperBox.astro +15 -0
- package/components/PatternTimeline.astro +133 -0
- package/components/Recovery.astro +34 -0
- package/components/ResultBox.astro +15 -0
- package/components/Sidebar.astro +268 -0
- package/components/Sidenote.astro +26 -0
- package/components/SkillBox.astro +24 -0
- package/components/SourceArchive.astro +285 -0
- package/components/StatusBadge.astro +51 -0
- package/components/Tag.astro +60 -0
- package/components/Theorem.astro +65 -0
- package/components/TipBox.astro +15 -0
- package/components/ToolFilter.tsx +160 -0
- package/components/TryThis.astro +23 -0
- package/components/VersionSelector.tsx +85 -0
- package/components/WarnBox.astro +15 -0
- package/components/WeekRef.astro +51 -0
- package/components/XRef.astro +40 -0
- package/dist/index.d.ts +135 -0
- package/dist/index.mjs +369 -0
- package/dist/lib/katex-macros.d.ts +26 -0
- package/dist/lib/katex-macros.mjs +98 -0
- package/dist/schemas.d.ts +17 -0
- package/dist/schemas.mjs +160 -0
- package/dist/types-Cz-pwE1N.d.ts +61 -0
- package/examples/chapter-template-academic.mdx +100 -0
- package/examples/chapter-template-tools.mdx +90 -0
- package/layouts/Base.astro +250 -0
- package/layouts/Chapter.astro +37 -0
- package/package.json +137 -0
- package/pages/chapters.astro +371 -0
- package/pages/convergence.astro +96 -0
- package/pages/print.astro +39 -0
- package/pages/references.astro +160 -0
- package/pages/search.astro +87 -0
- package/pedagogy/kf-chapter-shape.md +96 -0
- package/pedagogy/source-tiers.md +121 -0
- package/pedagogy/volatility-classes.md +110 -0
- package/recipes/00-getting-started.md +77 -0
- package/recipes/01-add-math.md +71 -0
- package/recipes/02-bibliography-pipeline.md +82 -0
- package/recipes/03-asset-pipelines.md +84 -0
- package/recipes/04-component-library.md +118 -0
- package/recipes/05-deploy-cloudflare.md +74 -0
- package/recipes/06-mobile-first-layout.md +73 -0
- package/recipes/07-chapter-shapes.md +84 -0
- package/recipes/08-decisions-ledger.md +110 -0
- package/recipes/09-validation.md +106 -0
- package/recipes/10-custom-domain.md +72 -0
- package/recipes/README.md +43 -0
- package/scripts/build-bib.mjs +99 -0
- package/scripts/build-figures.mjs +179 -0
- package/scripts/render-notebooks.mjs +223 -0
- package/scripts/validate.mjs +179 -0
- package/styles/callouts.css +303 -0
- package/styles/chapter.css +209 -0
- package/styles/convergence.css +349 -0
- package/styles/layout.css +156 -0
- package/styles/print.css +203 -0
- package/styles/tokens.css +194 -0
- package/styles/tool-filter.css +135 -0
- 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
|
+
}
|