@dsbasko/cookbook-engine 0.1.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.

Potentially problematic release.


This version of @dsbasko/cookbook-engine might be problematic. Click here for more details.

Files changed (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +232 -0
  3. package/assets/fonts/jetbrains-mono/JetBrainsMono-Bold.woff2 +0 -0
  4. package/assets/fonts/jetbrains-mono/JetBrainsMono-BoldItalic.woff2 +0 -0
  5. package/assets/fonts/jetbrains-mono/JetBrainsMono-Italic.woff2 +0 -0
  6. package/assets/fonts/jetbrains-mono/JetBrainsMono-Medium.woff2 +0 -0
  7. package/assets/fonts/jetbrains-mono/JetBrainsMono-Regular.woff2 +0 -0
  8. package/assets/fonts/jetbrains-mono/JetBrainsMono-SemiBold.woff2 +0 -0
  9. package/package.json +92 -0
  10. package/scripts/check-course-coverage.mts +32 -0
  11. package/scripts/fix-static-image-extensions.mjs +78 -0
  12. package/scripts/generate-readme-toc.mts +32 -0
  13. package/scripts/resolve-course-paths.mjs +28 -0
  14. package/scripts/sync-images.mjs +88 -0
  15. package/src/components/AppShell/AppShell.module.css +40 -0
  16. package/src/components/AppShell/AppShell.tsx +135 -0
  17. package/src/components/AppShell/index.ts +1 -0
  18. package/src/components/Callout/Callout.module.css +68 -0
  19. package/src/components/Callout/Callout.tsx +83 -0
  20. package/src/components/Callout/index.ts +1 -0
  21. package/src/components/CodeBlock/CodeBlock.module.css +68 -0
  22. package/src/components/CodeBlock/CodeBlock.tsx +65 -0
  23. package/src/components/CodeBlock/index.ts +1 -0
  24. package/src/components/GateProvider/GateProvider.tsx +207 -0
  25. package/src/components/GateProvider/index.ts +1 -0
  26. package/src/components/Header/Breadcrumbs.tsx +50 -0
  27. package/src/components/Header/Header.module.css +131 -0
  28. package/src/components/Header/Header.tsx +26 -0
  29. package/src/components/Header/HeaderLessonNav.tsx +118 -0
  30. package/src/components/Header/index.ts +1 -0
  31. package/src/components/HomePage/HomePage.module.css +538 -0
  32. package/src/components/HomePage/HomePage.tsx +295 -0
  33. package/src/components/HomePage/index.ts +1 -0
  34. package/src/components/LessonAwareLink/LessonAwareLink.module.css +12 -0
  35. package/src/components/LessonAwareLink/LessonAwareLink.tsx +86 -0
  36. package/src/components/LessonAwareLink/index.ts +1 -0
  37. package/src/components/LessonLayout/LessonLayout.module.css +35 -0
  38. package/src/components/LessonLayout/LessonLayout.tsx +18 -0
  39. package/src/components/LessonLayout/index.ts +1 -0
  40. package/src/components/LessonLockedInterstitial/LessonLockedInterstitial.module.css +367 -0
  41. package/src/components/LessonLockedInterstitial/LessonLockedInterstitial.tsx +256 -0
  42. package/src/components/LessonLockedInterstitial/index.ts +1 -0
  43. package/src/components/LessonNav/LessonNav.module.css +84 -0
  44. package/src/components/LessonNav/LessonNav.tsx +64 -0
  45. package/src/components/LessonNav/index.ts +1 -0
  46. package/src/components/LessonPageLayout/LessonPageLayout.module.css +118 -0
  47. package/src/components/LessonPageLayout/LessonPageLayout.tsx +46 -0
  48. package/src/components/LessonPageLayout/index.ts +1 -0
  49. package/src/components/LessonSideMeta/LessonSideMeta.module.css +68 -0
  50. package/src/components/LessonSideMeta/LessonSideMeta.tsx +87 -0
  51. package/src/components/LessonSideMeta/index.ts +1 -0
  52. package/src/components/ModulePage/ModulePage.module.css +693 -0
  53. package/src/components/ModulePage/ModulePage.tsx +301 -0
  54. package/src/components/ModulePage/index.ts +1 -0
  55. package/src/components/ProgramDrawer/LockIcon.tsx +19 -0
  56. package/src/components/ProgramDrawer/ProgramDrawer.module.css +563 -0
  57. package/src/components/ProgramDrawer/ProgramDrawer.tsx +481 -0
  58. package/src/components/ProgramDrawer/index.ts +1 -0
  59. package/src/components/ProgressBar/ProgressBar.module.css +46 -0
  60. package/src/components/ProgressBar/ProgressBar.tsx +45 -0
  61. package/src/components/ProgressBar/index.ts +1 -0
  62. package/src/components/ProgressModeProvider/ProgressModeProvider.tsx +87 -0
  63. package/src/components/ProgressModeProvider/index.ts +1 -0
  64. package/src/components/ReadingPrefsProvider/ReadingPrefsProvider.tsx +100 -0
  65. package/src/components/ReadingPrefsProvider/index.ts +1 -0
  66. package/src/components/ReadingProgress/ReadingProgress.module.css +19 -0
  67. package/src/components/ReadingProgress/ReadingProgress.tsx +53 -0
  68. package/src/components/ReadingProgress/index.ts +1 -0
  69. package/src/components/SettingsToggle/SettingsToggle.module.css +888 -0
  70. package/src/components/SettingsToggle/SettingsToggle.tsx +688 -0
  71. package/src/components/SettingsToggle/index.ts +1 -0
  72. package/src/components/Sidebar/Sidebar.module.css +157 -0
  73. package/src/components/Sidebar/Sidebar.tsx +63 -0
  74. package/src/components/Sidebar/icons/GitHubIcon.tsx +17 -0
  75. package/src/components/Sidebar/icons/HomeIcon.tsx +22 -0
  76. package/src/components/Sidebar/icons/LanguageIcon.tsx +24 -0
  77. package/src/components/Sidebar/icons/ProgramIcon.tsx +23 -0
  78. package/src/components/Sidebar/icons/SettingsIcon.tsx +26 -0
  79. package/src/components/Sidebar/icons/ThemeIcon.tsx +22 -0
  80. package/src/components/Sidebar/icons/index.ts +6 -0
  81. package/src/components/Sidebar/index.ts +1 -0
  82. package/src/components/ThemeProvider/ThemeProvider.tsx +68 -0
  83. package/src/components/ThemeProvider/index.ts +1 -0
  84. package/src/components/Toc/Toc.module.css +78 -0
  85. package/src/components/Toc/Toc.tsx +92 -0
  86. package/src/components/Toc/index.ts +1 -0
  87. package/src/components/TranslationBanner/TranslationBanner.module.css +32 -0
  88. package/src/components/TranslationBanner/TranslationBanner.tsx +40 -0
  89. package/src/components/TranslationBanner/index.ts +1 -0
  90. package/src/config.d.mts +12 -0
  91. package/src/config.mjs +110 -0
  92. package/src/index.ts +62 -0
  93. package/src/layout/lang.tsx +44 -0
  94. package/src/layout/root.tsx +223 -0
  95. package/src/lib/course-loader.ts +33 -0
  96. package/src/lib/course.ts +429 -0
  97. package/src/lib/coverage.ts +141 -0
  98. package/src/lib/description.ts +43 -0
  99. package/src/lib/extract-toc.ts +59 -0
  100. package/src/lib/format.ts +55 -0
  101. package/src/lib/frontier-link.ts +37 -0
  102. package/src/lib/gate-init-script.ts +40 -0
  103. package/src/lib/gate-mark-script.ts +324 -0
  104. package/src/lib/i18n.ts +474 -0
  105. package/src/lib/lang.ts +90 -0
  106. package/src/lib/lesson-gate.ts +79 -0
  107. package/src/lib/lesson.ts +66 -0
  108. package/src/lib/markdown-components.tsx +51 -0
  109. package/src/lib/markdown.ts +180 -0
  110. package/src/lib/mdx-plugins/rehype-callout.ts +80 -0
  111. package/src/lib/mdx-plugins/remark-lesson-images.ts +109 -0
  112. package/src/lib/mdx-plugins/remark-link-rewrite.ts +231 -0
  113. package/src/lib/paths.ts +36 -0
  114. package/src/lib/program-drawer.ts +8 -0
  115. package/src/lib/progress-mode.ts +69 -0
  116. package/src/lib/progress.ts +182 -0
  117. package/src/lib/reading-prefs.ts +127 -0
  118. package/src/lib/readme-toc.ts +69 -0
  119. package/src/lib/site-url.ts +33 -0
  120. package/src/lib/sitemap.ts +112 -0
  121. package/src/lib/slug.ts +15 -0
  122. package/src/lib/theme.ts +78 -0
  123. package/src/lib/use-i18n.ts +25 -0
  124. package/src/og/icon.tsx +40 -0
  125. package/src/og/opengraph-image.tsx +126 -0
  126. package/src/pages/home.tsx +66 -0
  127. package/src/pages/lesson.tsx +260 -0
  128. package/src/pages/module.tsx +80 -0
  129. package/src/pages/not-found-lang.tsx +51 -0
  130. package/src/pages/not-found-root.tsx +48 -0
  131. package/src/pages/root.tsx +44 -0
  132. package/src/seo/robots.ts +16 -0
  133. package/src/seo/sitemap.ts +10 -0
  134. package/src/styles/globals.css +139 -0
  135. package/src/styles/markdown.css +265 -0
  136. package/src/styles/reset.css +89 -0
  137. package/src/styles/tokens.css +270 -0
@@ -0,0 +1,265 @@
1
+ /* Markdown rendering styles for lesson pages.
2
+ * Scope: `.markdown` wrapper around the rendered article body.
3
+ * Code blocks come from rehype-pretty-code wrapped in our CodeBlock component:
4
+ * <figure data-rehype-pretty-code-figure><header>…</header><div><pre data-theme="github-light night-owl"><code>…</code></pre></div></figure>
5
+ * The figure chrome (border, radius, header) is owned by CodeBlock.module.css.
6
+ * This file styles the inner pre/code and maps shiki dual-theme CSS vars
7
+ * (--shiki-light / --shiki-dark) onto `color` based on the root `data-theme`.
8
+ */
9
+
10
+ .markdown {
11
+ font-family: var(--prose-font-active), Georgia, serif;
12
+ font-size: var(--prose-font-size);
13
+ line-height: var(--line-height-relaxed);
14
+ color: var(--content-primary);
15
+ word-wrap: break-word;
16
+ }
17
+
18
+ .markdown > * + * {
19
+ margin-top: var(--space-5);
20
+ }
21
+
22
+ .markdown h2 {
23
+ font-size: 1.75em;
24
+ line-height: var(--line-height-snug);
25
+ font-weight: var(--font-weight-bold);
26
+ letter-spacing: -0.02em;
27
+ margin-top: var(--space-10);
28
+ margin-bottom: var(--space-5);
29
+ scroll-margin-top: var(--space-12);
30
+ }
31
+
32
+ .markdown h3 {
33
+ font-size: 1.375em;
34
+ line-height: var(--line-height-snug);
35
+ font-weight: var(--font-weight-semibold);
36
+ letter-spacing: -0.015em;
37
+ margin-top: var(--space-8);
38
+ margin-bottom: var(--space-3);
39
+ scroll-margin-top: var(--space-12);
40
+ }
41
+
42
+ .markdown h4 {
43
+ font-size: 1.125em;
44
+ font-weight: var(--font-weight-semibold);
45
+ margin-top: var(--space-6);
46
+ margin-bottom: var(--space-2);
47
+ }
48
+
49
+ .markdown p {
50
+ margin: 0 0 20px 0;
51
+ text-wrap: pretty;
52
+ }
53
+
54
+ .markdown strong {
55
+ font-weight: var(--font-weight-semibold);
56
+ color: var(--content-primary);
57
+ }
58
+
59
+ .markdown a.heading-anchor {
60
+ color: inherit;
61
+ text-decoration: none;
62
+ }
63
+
64
+ .markdown a.heading-anchor:hover {
65
+ color: var(--content-link);
66
+ }
67
+
68
+ /* Inline prose links: same color as body text, semibold, dotted underline.
69
+ * Identity in all three themes — the link reads as text first, then as a
70
+ * link via weight and underline (mirrors Substack / iA Writer style). */
71
+ .markdown a:not(.heading-anchor) {
72
+ color: var(--content-primary);
73
+ font-weight: var(--font-weight-semibold);
74
+ text-decoration: underline dotted;
75
+ text-decoration-color: var(--content-tertiary);
76
+ text-underline-offset: 3px;
77
+ text-decoration-thickness: 1px;
78
+ }
79
+
80
+ .markdown a:not(.heading-anchor):hover {
81
+ color: var(--content-primary);
82
+ text-decoration-color: var(--content-primary);
83
+ }
84
+
85
+ .markdown a[data-external]::after {
86
+ content: ' ↗';
87
+ font-size: 0.8em;
88
+ opacity: 0.7;
89
+ }
90
+
91
+ .markdown ul,
92
+ .markdown ol {
93
+ padding-left: var(--space-6);
94
+ margin: 0 0 20px 0;
95
+ }
96
+
97
+ .markdown ol {
98
+ list-style: decimal;
99
+ }
100
+
101
+ .markdown ul {
102
+ list-style: none;
103
+ padding-left: 0;
104
+ }
105
+
106
+ .markdown ul > li {
107
+ position: relative;
108
+ padding-left: var(--space-5);
109
+ }
110
+
111
+ .markdown ul > li::before {
112
+ content: '—';
113
+ position: absolute;
114
+ left: 0;
115
+ top: 0;
116
+ color: var(--content-tertiary);
117
+ font-family: var(--font-mono), ui-monospace, monospace;
118
+ font-size: 0.95em;
119
+ line-height: inherit;
120
+ }
121
+
122
+ .markdown ul ul,
123
+ .markdown ol ol,
124
+ .markdown ul ol,
125
+ .markdown ol ul {
126
+ margin-top: var(--space-2);
127
+ padding-left: var(--space-6);
128
+ }
129
+
130
+ .markdown ul ul {
131
+ list-style: none;
132
+ }
133
+
134
+ .markdown ol ol {
135
+ list-style: lower-alpha;
136
+ }
137
+
138
+ .markdown li + li {
139
+ /* margin-top: var(--space-2); */
140
+ }
141
+
142
+ .markdown li > p {
143
+ margin: 0;
144
+ }
145
+
146
+ .markdown li > p + p {
147
+ margin-top: var(--space-2);
148
+ }
149
+
150
+ .markdown blockquote {
151
+ margin: 0;
152
+ padding: var(--space-3) var(--space-4);
153
+ border-left: 3px solid var(--bg-stroke-strong);
154
+ background-color: var(--bg-subtle);
155
+ color: var(--content-secondary);
156
+ border-radius: 0 var(--radius-md) var(--radius-md) 0;
157
+ }
158
+
159
+ .markdown blockquote > * + * {
160
+ margin-top: var(--space-2);
161
+ }
162
+
163
+ .markdown :not(pre) > code {
164
+ font-family: var(--code-font-active), ui-monospace, monospace;
165
+ font-size: 0.86em;
166
+ padding: 1px 6px;
167
+ background-color: var(--bg-subtle);
168
+ border: 1px solid var(--bg-stroke);
169
+ border-radius: var(--radius-sm);
170
+ color: var(--content-primary);
171
+ white-space: nowrap;
172
+ }
173
+
174
+ .markdown table {
175
+ width: 100%;
176
+ border-collapse: collapse;
177
+ font-family: inherit;
178
+ font-size: 0.875em;
179
+ display: block;
180
+ overflow-x: auto;
181
+ margin: 0 0 20px 0;
182
+ }
183
+
184
+ .markdown th,
185
+ .markdown td {
186
+ padding: var(--space-2) var(--space-3);
187
+ border: 1px solid var(--bg-stroke);
188
+ text-align: left;
189
+ vertical-align: top;
190
+ }
191
+
192
+ .markdown th {
193
+ background-color: var(--bg-subtle);
194
+ font-weight: var(--font-weight-semibold);
195
+ }
196
+
197
+ .markdown tr:nth-child(even) td {
198
+ background-color: var(--bg-subtle);
199
+ }
200
+
201
+ .markdown img {
202
+ max-width: 100%;
203
+ height: auto;
204
+ display: block;
205
+ border-radius: var(--radius-md);
206
+ border: 1px solid var(--bg-stroke);
207
+ }
208
+
209
+ .markdown hr {
210
+ border: 0;
211
+ border-top: 1px solid var(--bg-stroke);
212
+ margin: var(--space-8) 0;
213
+ }
214
+
215
+ /* rehype-pretty-code inner pre/code (wrapper styled by CodeBlock.module.css). */
216
+ .markdown figure[data-rehype-pretty-code-figure] {
217
+ margin-top: var(--space-5);
218
+ margin-bottom: var(--space-6);
219
+ }
220
+
221
+ .markdown figure[data-rehype-pretty-code-figure] pre {
222
+ margin: 0;
223
+ padding: var(--space-5);
224
+ overflow-x: auto;
225
+ font-size: var(--code-font-size);
226
+ line-height: 1.6;
227
+ background: transparent;
228
+ tab-size: 2;
229
+ }
230
+
231
+ .markdown figure[data-rehype-pretty-code-figure] code {
232
+ display: block;
233
+ font-family: var(--code-font-active), ui-monospace, monospace;
234
+ background: transparent;
235
+ font-variant-ligatures: none;
236
+ }
237
+
238
+ /* Fira Code is built around its ligature set (`->`, `=>`, `!=`, `===` …) —
239
+ * disabling them removes half its identity. Opt in only when Fira Code is the
240
+ * active code font; JetBrains Mono stays ligature-free per the rule above. */
241
+ [data-code-font='fira'] .markdown figure[data-rehype-pretty-code-figure] code {
242
+ font-variant-ligatures: contextual;
243
+ }
244
+
245
+ .markdown figure[data-rehype-pretty-code-figure] code [data-line] {
246
+ display: block;
247
+ padding: 0;
248
+ min-height: 1.6em;
249
+ }
250
+
251
+ /* Dual-theme via CSS variables stamped on token spans by rehype-pretty-code.
252
+ * Light theme uses --shiki-light; dark theme uses --shiki-dark. */
253
+ .markdown figure[data-rehype-pretty-code-figure] code span {
254
+ color: var(--shiki-light);
255
+ font-style: var(--shiki-light-font-style);
256
+ font-weight: var(--shiki-light-font-weight);
257
+ text-decoration: var(--shiki-light-text-decoration);
258
+ }
259
+
260
+ [data-theme='dark'] .markdown figure[data-rehype-pretty-code-figure] code span {
261
+ color: var(--shiki-dark);
262
+ font-style: var(--shiki-dark-font-style);
263
+ font-weight: var(--shiki-dark-font-weight);
264
+ text-decoration: var(--shiki-dark-text-decoration);
265
+ }
@@ -0,0 +1,89 @@
1
+ /* Modern CSS reset (adapted from Andy Bell). */
2
+
3
+ *,
4
+ *::before,
5
+ *::after {
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ html {
10
+ -webkit-text-size-adjust: 100%;
11
+ -moz-tab-size: 4;
12
+ tab-size: 4;
13
+ }
14
+
15
+ body,
16
+ h1,
17
+ h2,
18
+ h3,
19
+ h4,
20
+ h5,
21
+ h6,
22
+ p,
23
+ figure,
24
+ blockquote,
25
+ dl,
26
+ dd,
27
+ ol,
28
+ ul {
29
+ margin: 0;
30
+ padding: 0;
31
+ }
32
+
33
+ ul[role='list'],
34
+ ol[role='list'] {
35
+ list-style: none;
36
+ }
37
+
38
+ html:focus-within {
39
+ scroll-behavior: smooth;
40
+ }
41
+
42
+ body {
43
+ min-height: 100vh;
44
+ min-height: 100dvh;
45
+ text-rendering: optimizeLegibility;
46
+ line-height: var(--line-height-normal);
47
+ -webkit-font-smoothing: antialiased;
48
+ -moz-osx-font-smoothing: grayscale;
49
+ }
50
+
51
+ a:not([class]) {
52
+ text-decoration-skip-ink: auto;
53
+ }
54
+
55
+ img,
56
+ picture,
57
+ svg {
58
+ max-width: 100%;
59
+ display: block;
60
+ }
61
+
62
+ input,
63
+ button,
64
+ textarea,
65
+ select {
66
+ font: inherit;
67
+ color: inherit;
68
+ }
69
+
70
+ button {
71
+ background: none;
72
+ border: 0;
73
+ padding: 0;
74
+ cursor: pointer;
75
+ }
76
+
77
+ @media (prefers-reduced-motion: reduce) {
78
+ html:focus-within {
79
+ scroll-behavior: auto;
80
+ }
81
+ *,
82
+ *::before,
83
+ *::after {
84
+ animation-duration: 0.01ms !important;
85
+ animation-iteration-count: 1 !important;
86
+ transition-duration: 0.01ms !important;
87
+ scroll-behavior: auto !important;
88
+ }
89
+ }
@@ -0,0 +1,270 @@
1
+ /* Design tokens — cookbook-engine
2
+ * Light: warm paper palette (cream + warm grays).
3
+ * Dark: deep ink with the same blue accent for continuity.
4
+ *
5
+ * Naming (BEM-friendly): --<category>-<role>.
6
+ * bg-* — surface fills
7
+ * content-* — text/icon foregrounds
8
+ * accent-* — semantic accents (action, success, notice, critical)
9
+ * font-* — font-family stacks (set in app/layout.tsx)
10
+ * space-* — spacing scale
11
+ * radius-* — corner radii
12
+ * shadow-* — elevation shadows
13
+ * z-* — z-index scale
14
+ *
15
+ * Accent: each theme declares a single `--accent-base`; `--accent-main` reads
16
+ * from it so a course can re-skin the action colour by overriding one token.
17
+ * `buildBrandAccentCss()` (lib/course.ts) injects a higher-specificity
18
+ * `:root[data-theme=…]` block from `course.yaml.brand.accent` that wins this
19
+ * cascade and re-derives the hover/soft shades via color-mix.
20
+ */
21
+
22
+ :root {
23
+ /* Spacing scale (4px grid) */
24
+ --space-1: 4px;
25
+ --space-2: 8px;
26
+ --space-3: 12px;
27
+ --space-4: 16px;
28
+ --space-5: 20px;
29
+ --space-6: 24px;
30
+ --space-8: 32px;
31
+ --space-10: 40px;
32
+ --space-12: 48px;
33
+ --space-16: 64px;
34
+ --space-20: 80px;
35
+
36
+ /* Type scale */
37
+ --font-size-xs: 12px;
38
+ --font-size-sm: 14px;
39
+ --font-size-md: 16px;
40
+ --font-size-lg: 18px;
41
+ --font-size-xl: 22px;
42
+ --font-size-2xl: 28px;
43
+ --font-size-3xl: 36px;
44
+ --font-size-4xl: 44px;
45
+
46
+ --line-height-tight: 1.2;
47
+ --line-height-snug: 1.4;
48
+ --line-height-normal: 1.6;
49
+ --line-height-relaxed: 1.75;
50
+
51
+ --font-weight-regular: 400;
52
+ --font-weight-medium: 500;
53
+ --font-weight-semibold: 600;
54
+ --font-weight-bold: 700;
55
+
56
+ /* Radii */
57
+ --radius-sm: 4px;
58
+ --radius-md: 8px;
59
+ --radius-lg: 12px;
60
+ --radius-xl: 16px;
61
+ --radius-pill: 999px;
62
+
63
+ /* Layout */
64
+ --layout-sidebar-width: 64px;
65
+ --layout-content-max: 720px;
66
+ --layout-toc-width: 280px;
67
+
68
+ /* Z-index */
69
+ --z-drawer: 100;
70
+ --z-overlay: 90;
71
+ --z-header: 80;
72
+ --z-popover: 110;
73
+
74
+ /* Reading preferences — overridden by data-* on <html>. Default = step 1 (16px prose, 14px code). */
75
+ --prose-font-size: 16px;
76
+ --code-font-size: 14px;
77
+ --prose-font-active: var(--font-serif);
78
+ --code-font-active: var(--font-mono);
79
+ --prose-max-width: 900px;
80
+ }
81
+
82
+ /* Six-step scale matching design reference: 14 / 16 / 18 / 20 / 22 / 24 px.
83
+ * Article max-width scales with the same step so the line-length budget
84
+ * (~52–58 characters) stays comfortable as prose grows. */
85
+ [data-prose-size='0'] {
86
+ --prose-font-size: 14px;
87
+ --prose-max-width: 820px;
88
+ }
89
+ [data-prose-size='1'] {
90
+ --prose-font-size: 16px;
91
+ --prose-max-width: 900px;
92
+ }
93
+ [data-prose-size='2'] {
94
+ --prose-font-size: 18px;
95
+ --prose-max-width: 980px;
96
+ }
97
+ [data-prose-size='3'] {
98
+ --prose-font-size: 20px;
99
+ --prose-max-width: 1060px;
100
+ }
101
+ [data-prose-size='4'] {
102
+ --prose-font-size: 22px;
103
+ --prose-max-width: 1140px;
104
+ }
105
+ [data-prose-size='5'] {
106
+ --prose-font-size: 24px;
107
+ --prose-max-width: 1220px;
108
+ }
109
+
110
+ /* Mobile: shift the prose scale down one step so the default (step 1)
111
+ * renders at 15px, matching pre-feature mobile lesson rendering. */
112
+ @media (max-width: 720px) {
113
+ :root {
114
+ --prose-font-size: 15px;
115
+ }
116
+ [data-prose-size='0'] {
117
+ --prose-font-size: 13px;
118
+ }
119
+ [data-prose-size='1'] {
120
+ --prose-font-size: 15px;
121
+ }
122
+ [data-prose-size='2'] {
123
+ --prose-font-size: 16px;
124
+ }
125
+ [data-prose-size='3'] {
126
+ --prose-font-size: 18px;
127
+ }
128
+ [data-prose-size='4'] {
129
+ --prose-font-size: 20px;
130
+ }
131
+ [data-prose-size='5'] {
132
+ --prose-font-size: 22px;
133
+ }
134
+ }
135
+
136
+ [data-code-size='0'] {
137
+ --code-font-size: 14px;
138
+ }
139
+ [data-code-size='1'] {
140
+ --code-font-size: 16px;
141
+ }
142
+ [data-code-size='2'] {
143
+ --code-font-size: 18px;
144
+ }
145
+ [data-code-size='3'] {
146
+ --code-font-size: 20px;
147
+ }
148
+ [data-code-size='4'] {
149
+ --code-font-size: 22px;
150
+ }
151
+ [data-code-size='5'] {
152
+ --code-font-size: 24px;
153
+ }
154
+
155
+ [data-prose-font='serif'] {
156
+ --prose-font-active: var(--font-serif);
157
+ }
158
+ [data-prose-font='sans'] {
159
+ --prose-font-active: var(--font-prose-inter);
160
+ }
161
+ [data-prose-font='slab'] {
162
+ --prose-font-active: var(--font-prose-slab);
163
+ }
164
+
165
+ [data-code-font='jetbrains'] {
166
+ --code-font-active: var(--font-mono);
167
+ }
168
+ [data-code-font='fira'] {
169
+ --code-font-active: var(--font-code-fira);
170
+ }
171
+
172
+ /* Light theme — neo-academic cream + deep moss accent. */
173
+ [data-theme='light'] {
174
+ --bg-default: #faf7f2;
175
+ --bg-surface: #ffffff;
176
+ --bg-subtle: #f3efe7;
177
+ --bg-muted: #ece7dc;
178
+ --bg-stroke: #e6e0d3;
179
+ --bg-stroke-strong: #d4cdba;
180
+
181
+ --content-primary: #1a1a1a;
182
+ --content-secondary: #5b5750;
183
+ --content-tertiary: #8a857b;
184
+ --content-inverse: #faf7f2;
185
+ --content-link: #2e3b2e;
186
+ --content-link-hover: #243024;
187
+
188
+ --accent-base: #2e3b2e;
189
+ --accent-main: var(--accent-base);
190
+ --accent-main-hover: #243024;
191
+ --accent-main-soft: rgba(46, 59, 46, 0.1);
192
+ --accent-success: #1f8a5b;
193
+ --accent-success-soft: rgba(31, 138, 91, 0.12);
194
+ --accent-notice: #c1862a;
195
+ --accent-notice-soft: rgba(193, 134, 42, 0.14);
196
+ --accent-critical: #b53120;
197
+ --accent-critical-soft: rgba(181, 49, 32, 0.12);
198
+
199
+ --shadow-sm: 0 1px 2px rgba(35, 25, 10, 0.04), 0 0 0 1px rgba(35, 25, 10, 0.04);
200
+ --shadow-md: 0 4px 16px rgba(35, 25, 10, 0.06), 0 1px 3px rgba(35, 25, 10, 0.04);
201
+ --shadow-lg: 0 12px 32px rgba(35, 25, 10, 0.12);
202
+ }
203
+
204
+ /* Paper theme — warm sepia for long-form reading.
205
+ * Bg/fg pair #f3eedc / #2f2a24 ≈ 12.2:1 (above WCAG AAA for body text).
206
+ * Accent shifts from moss to muted brown to reinforce the paper identity. */
207
+ [data-theme='paper'] {
208
+ color-scheme: light;
209
+
210
+ --bg-default: #f3eedc;
211
+ --bg-surface: #fbf7ea;
212
+ --bg-subtle: #ede5cf;
213
+ --bg-muted: #e8ddc5;
214
+ --bg-stroke: #e5dcc8;
215
+ --bg-stroke-strong: #d8ccb3;
216
+
217
+ --content-primary: #2f2a24;
218
+ --content-secondary: #5b5148;
219
+ --content-tertiary: #6b5e52;
220
+ --content-inverse: #fbf7ea;
221
+ --content-link: #1f5c57;
222
+ --content-link-hover: #174842;
223
+
224
+ --accent-base: #7e6149;
225
+ --accent-main: var(--accent-base);
226
+ --accent-main-hover: #65503c;
227
+ --accent-main-soft: rgba(126, 97, 73, 0.12);
228
+ --accent-success: #2e7855;
229
+ --accent-success-soft: rgba(46, 120, 85, 0.14);
230
+ --accent-notice: #a86420;
231
+ --accent-notice-soft: rgba(168, 100, 32, 0.16);
232
+ --accent-critical: #9f3a2a;
233
+ --accent-critical-soft: rgba(159, 58, 42, 0.14);
234
+
235
+ --shadow-sm: 0 1px 2px rgba(47, 42, 36, 0.06), 0 0 0 1px rgba(47, 42, 36, 0.05);
236
+ --shadow-md: 0 4px 16px rgba(47, 42, 36, 0.08), 0 1px 3px rgba(47, 42, 36, 0.05);
237
+ --shadow-lg: 0 12px 32px rgba(47, 42, 36, 0.14);
238
+ }
239
+
240
+ /* Dark theme — deep ink with sage accent. */
241
+ [data-theme='dark'] {
242
+ --bg-default: #16140f;
243
+ --bg-surface: #1d1b16;
244
+ --bg-subtle: #100e0a;
245
+ --bg-muted: #25221b;
246
+ --bg-stroke: #2a2620;
247
+ --bg-stroke-strong: #3a342a;
248
+
249
+ --content-primary: #f0ece2;
250
+ --content-secondary: #a8a294;
251
+ --content-tertiary: #6e695e;
252
+ --content-inverse: #16140f;
253
+ --content-link: #b8c7b1;
254
+ --content-link-hover: #cdd9c7;
255
+
256
+ --accent-base: #b8c7b1;
257
+ --accent-main: var(--accent-base);
258
+ --accent-main-hover: #cdd9c7;
259
+ --accent-main-soft: rgba(184, 199, 177, 0.12);
260
+ --accent-success: #4dbf8c;
261
+ --accent-success-soft: rgba(77, 191, 140, 0.14);
262
+ --accent-notice: #e0a449;
263
+ --accent-notice-soft: rgba(224, 164, 73, 0.16);
264
+ --accent-critical: #e25c48;
265
+ --accent-critical-soft: rgba(226, 92, 72, 0.14);
266
+
267
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.04);
268
+ --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4), 0 1px 3px rgba(0, 0, 0, 0.3);
269
+ --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.6);
270
+ }