@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,693 @@
1
+ /* Module page — same technical-editorial vocabulary as the home page, but the
2
+ hero leans on a sticky side card with a graph-paper texture, and the body is
3
+ a dense lessons list (status icon + tags + duration) instead of module rows.
4
+ Local --mp-* scale matches HomePage so the two pages read as one document. */
5
+
6
+ .page {
7
+ --mp-space-7: 32px;
8
+ --mp-space-8: 40px;
9
+ --mp-space-9: 56px;
10
+ --mp-space-10: 72px;
11
+ --mp-space-11: 96px;
12
+
13
+ --mp-fs-micro: 11px;
14
+ --mp-fs-meta: 12px;
15
+ --mp-fs-small: 13px;
16
+ --mp-fs-body: 15px;
17
+ --mp-fs-lead: 18px;
18
+ --mp-fs-h3: 22px;
19
+ --mp-fs-h2: 28px;
20
+ --mp-fs-display: 72px;
21
+
22
+ --mp-r-1: 2px;
23
+ --mp-r-2: 4px;
24
+ --mp-r-3: 8px;
25
+
26
+ padding: var(--mp-space-9) var(--mp-space-7) var(--mp-space-11);
27
+ max-width: 1280px;
28
+ width: 100%;
29
+ margin: 0 auto;
30
+ font-size: var(--mp-fs-body);
31
+ line-height: 1.55;
32
+ }
33
+
34
+ /* ---------- Hero ---------- */
35
+
36
+ .hero {
37
+ display: grid;
38
+ grid-template-columns: 1fr 320px;
39
+ gap: var(--mp-space-9);
40
+ align-items: start;
41
+ padding-bottom: var(--mp-space-8);
42
+ border-bottom: 1px solid var(--bg-stroke);
43
+ }
44
+
45
+ .heroText {
46
+ min-width: 0;
47
+ }
48
+
49
+ .eyebrow {
50
+ display: flex;
51
+ align-items: center;
52
+ gap: var(--space-3);
53
+ font-family: var(--font-mono), ui-monospace, monospace;
54
+ font-size: var(--mp-fs-meta);
55
+ color: var(--content-secondary);
56
+ text-transform: uppercase;
57
+ letter-spacing: 0.06em;
58
+ margin-bottom: var(--space-5);
59
+ flex-wrap: wrap;
60
+ }
61
+
62
+ /* The "01 / 09" chip — printed-page weight, anchors the module in the course. */
63
+ .eyebrowNum {
64
+ font-size: 14px;
65
+ color: var(--content-primary);
66
+ font-weight: 600;
67
+ padding: 2px 8px;
68
+ background: var(--bg-subtle);
69
+ border: 1px solid var(--bg-stroke);
70
+ border-radius: var(--mp-r-1);
71
+ }
72
+
73
+ .eyebrowOf {
74
+ color: var(--content-tertiary);
75
+ }
76
+
77
+ .eyebrowDot {
78
+ color: var(--bg-stroke-strong);
79
+ }
80
+
81
+ .title {
82
+ font-size: var(--mp-fs-display);
83
+ line-height: 1;
84
+ letter-spacing: -0.03em;
85
+ font-weight: 700;
86
+ margin: 0 0 var(--space-5);
87
+ color: var(--content-primary);
88
+ text-wrap: balance;
89
+ }
90
+
91
+ .desc {
92
+ font-size: var(--mp-fs-lead);
93
+ line-height: 1.5;
94
+ color: var(--content-secondary);
95
+ margin: 0 0 var(--mp-space-7);
96
+ max-width: 620px;
97
+ text-wrap: pretty;
98
+ }
99
+
100
+ .ctaRow {
101
+ display: flex;
102
+ gap: var(--space-3);
103
+ flex-wrap: wrap;
104
+ align-items: center;
105
+ }
106
+
107
+ /* ---------- Buttons (shared shape with the home page) ---------- */
108
+
109
+ .btn {
110
+ display: inline-flex;
111
+ align-items: center;
112
+ gap: var(--space-3);
113
+ padding: 12px var(--space-5);
114
+ border-radius: var(--mp-r-2);
115
+ font-size: var(--mp-fs-body);
116
+ font-weight: 600;
117
+ border: 1px solid transparent;
118
+ transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
119
+ white-space: nowrap;
120
+ text-decoration: none;
121
+ font-family: inherit;
122
+ cursor: pointer;
123
+ }
124
+
125
+ .btnPrimary {
126
+ background: var(--accent-main);
127
+ color: var(--content-inverse);
128
+ border-color: var(--accent-main);
129
+ }
130
+
131
+ .btnPrimary:hover {
132
+ background: var(--accent-main-hover);
133
+ border-color: var(--accent-main-hover);
134
+ color: var(--content-inverse);
135
+ }
136
+
137
+ .btnSecondary {
138
+ background: var(--bg-surface);
139
+ color: var(--content-primary);
140
+ border-color: var(--bg-stroke-strong);
141
+ }
142
+
143
+ .btnSecondary:hover {
144
+ border-color: var(--accent-main);
145
+ }
146
+
147
+ .btnGhost {
148
+ background: transparent;
149
+ color: var(--content-secondary);
150
+ padding: 12px var(--space-3);
151
+ }
152
+
153
+ .btnGhost:hover {
154
+ color: var(--content-primary);
155
+ }
156
+
157
+ .btnArrow {
158
+ font-family: var(--font-mono), ui-monospace, monospace;
159
+ font-weight: 400;
160
+ transition: transform 150ms ease;
161
+ }
162
+
163
+ .btn:hover .btnArrow {
164
+ transform: translateX(3px);
165
+ }
166
+
167
+ .nextHint {
168
+ margin-top: var(--space-5);
169
+ font-size: var(--mp-fs-small);
170
+ color: var(--content-tertiary);
171
+ font-family: var(--font-mono), ui-monospace, monospace;
172
+ }
173
+
174
+ .nextHintArrow {
175
+ color: var(--content-tertiary);
176
+ }
177
+
178
+ .nextHintModule {
179
+ color: var(--content-secondary);
180
+ }
181
+
182
+ .nextHintSep {
183
+ color: var(--content-tertiary);
184
+ }
185
+
186
+ .nextHintLesson {
187
+ color: var(--content-primary);
188
+ }
189
+
190
+ /* ---------- Side card (sticky readout) ---------- */
191
+
192
+ .sideCard {
193
+ background: var(--bg-surface);
194
+ border: 1px solid var(--bg-stroke);
195
+ border-radius: var(--mp-r-3);
196
+ padding: var(--space-6);
197
+ position: sticky;
198
+ top: var(--mp-space-7);
199
+ }
200
+
201
+ .sideRow {
202
+ display: flex;
203
+ justify-content: space-between;
204
+ align-items: baseline;
205
+ margin-bottom: var(--space-3);
206
+ }
207
+
208
+ .sideLabel {
209
+ font-family: var(--font-mono), ui-monospace, monospace;
210
+ font-size: var(--mp-fs-meta);
211
+ color: var(--content-tertiary);
212
+ text-transform: uppercase;
213
+ letter-spacing: 0.06em;
214
+ }
215
+
216
+ .sideVal {
217
+ font-family: var(--font-mono), ui-monospace, monospace;
218
+ font-size: var(--mp-fs-small);
219
+ color: var(--content-primary);
220
+ font-weight: 600;
221
+ }
222
+
223
+ .sideValDone {
224
+ color: var(--accent-success);
225
+ }
226
+
227
+ .sideBar {
228
+ height: 6px;
229
+ background: var(--bg-subtle);
230
+ border-radius: 999px;
231
+ overflow: hidden;
232
+ }
233
+
234
+ .sideFill {
235
+ display: block;
236
+ height: 100%;
237
+ background: var(--accent-main);
238
+ border-radius: 999px;
239
+ transition: width 400ms ease;
240
+ }
241
+
242
+ .sideFillDone {
243
+ background: var(--accent-success);
244
+ }
245
+
246
+ .sidePct {
247
+ font-family: var(--font-mono), ui-monospace, monospace;
248
+ font-size: 32px;
249
+ font-weight: 600;
250
+ letter-spacing: -0.02em;
251
+ margin-top: var(--space-3);
252
+ color: var(--content-primary);
253
+ line-height: 1;
254
+ }
255
+
256
+ .sideDivider {
257
+ height: 1px;
258
+ background: var(--bg-stroke);
259
+ margin: var(--space-5) 0;
260
+ }
261
+
262
+ .sideMeta {
263
+ margin: 0;
264
+ display: flex;
265
+ flex-direction: column;
266
+ gap: var(--space-3);
267
+ }
268
+
269
+ .sideMeta > div {
270
+ display: flex;
271
+ justify-content: space-between;
272
+ align-items: baseline;
273
+ margin: 0;
274
+ }
275
+
276
+ .sideMetaLabel {
277
+ font-family: var(--font-mono), ui-monospace, monospace;
278
+ font-size: var(--mp-fs-meta);
279
+ color: var(--content-tertiary);
280
+ text-transform: uppercase;
281
+ letter-spacing: 0.06em;
282
+ margin: 0;
283
+ }
284
+
285
+ .sideMetaValue {
286
+ margin: 0;
287
+ font-size: var(--mp-fs-small);
288
+ font-weight: 600;
289
+ color: var(--content-primary);
290
+ }
291
+
292
+ /* ---------- Section heading ---------- */
293
+
294
+ .sectionHead {
295
+ display: flex;
296
+ justify-content: space-between;
297
+ align-items: flex-end;
298
+ padding: var(--mp-space-9) 0 var(--space-6);
299
+ gap: var(--mp-space-7);
300
+ }
301
+
302
+ .sectionEyebrow {
303
+ font-family: var(--font-mono), ui-monospace, monospace;
304
+ font-size: var(--mp-fs-meta);
305
+ color: var(--content-tertiary);
306
+ text-transform: uppercase;
307
+ letter-spacing: 0.08em;
308
+ margin-bottom: var(--space-2);
309
+ }
310
+
311
+ .sectionTitle {
312
+ font-size: var(--mp-fs-h2);
313
+ letter-spacing: -0.02em;
314
+ margin: 0;
315
+ font-weight: 600;
316
+ }
317
+
318
+ .sectionTools {
319
+ display: flex;
320
+ align-items: center;
321
+ gap: var(--space-2);
322
+ font-family: var(--font-mono), ui-monospace, monospace;
323
+ font-size: var(--mp-fs-small);
324
+ color: var(--content-tertiary);
325
+ }
326
+
327
+ /* ---------- Lessons list ---------- */
328
+
329
+ .lessons {
330
+ list-style: none;
331
+ margin: 0;
332
+ padding: 0;
333
+ display: flex;
334
+ flex-direction: column;
335
+ border-top: 1px solid var(--bg-stroke);
336
+ }
337
+
338
+ .lessonItem {
339
+ margin: 0;
340
+ }
341
+
342
+ .lessonRow {
343
+ display: grid;
344
+ grid-template-columns: 56px 32px 1fr auto auto 32px;
345
+ align-items: center;
346
+ gap: var(--space-4);
347
+ padding: var(--space-5) var(--space-3);
348
+ margin: 0 calc(var(--space-3) * -1);
349
+ border-bottom: 1px solid var(--bg-stroke);
350
+ text-decoration: none;
351
+ color: inherit;
352
+ transition: background-color 120ms ease;
353
+ }
354
+
355
+ .lessonRow:hover {
356
+ background: var(--bg-subtle);
357
+ }
358
+
359
+ .lessonRow:hover .lessonArrow {
360
+ color: var(--content-primary);
361
+ transform: translateX(4px);
362
+ }
363
+
364
+ .lessonNum {
365
+ font-family: var(--font-mono), ui-monospace, monospace;
366
+ font-size: var(--mp-fs-small);
367
+ color: var(--content-tertiary);
368
+ letter-spacing: 0.04em;
369
+ padding-left: var(--space-2);
370
+ }
371
+
372
+ .lessonStatus {
373
+ display: grid;
374
+ place-items: center;
375
+ }
376
+
377
+ .lessonCheck {
378
+ color: var(--accent-success);
379
+ font-size: 18px;
380
+ font-weight: 700;
381
+ line-height: 1;
382
+ }
383
+
384
+ .lessonCircle {
385
+ width: 14px;
386
+ height: 14px;
387
+ border: 1.5px solid var(--bg-stroke-strong);
388
+ border-radius: 50%;
389
+ }
390
+
391
+ /* Pulsing "next" marker — solid inner dot inside an accent ring. */
392
+ .lessonDot {
393
+ width: 14px;
394
+ height: 14px;
395
+ border: 1.5px solid var(--accent-main);
396
+ border-radius: 50%;
397
+ background: radial-gradient(circle, var(--accent-main) 40%, transparent 50%);
398
+ }
399
+
400
+ .lessonText {
401
+ display: flex;
402
+ flex-direction: column;
403
+ gap: 2px;
404
+ min-width: 0;
405
+ }
406
+
407
+ .lessonTitle {
408
+ font-size: var(--mp-fs-lead);
409
+ font-weight: 500;
410
+ letter-spacing: -0.005em;
411
+ color: var(--content-primary);
412
+ line-height: 1.3;
413
+ }
414
+
415
+ .lessonHint {
416
+ font-family: var(--font-mono), ui-monospace, monospace;
417
+ font-size: var(--mp-fs-meta);
418
+ color: var(--accent-main);
419
+ text-transform: uppercase;
420
+ letter-spacing: 0.06em;
421
+ }
422
+
423
+ /* Gate-paint managed: all four status glyphs are rendered in DOM at all
424
+ times; the inline gate-paint script flips data-completed / data-next /
425
+ data-locked synchronously before first paint (and applyGatePainting
426
+ re-applies them on progress changes). CSS picks exactly one glyph by
427
+ the row's current attribute state. */
428
+
429
+ .lessonStatus .statusDefault {
430
+ display: inline-flex;
431
+ }
432
+
433
+ .lessonStatus .statusNext,
434
+ .lessonStatus .statusDone,
435
+ .lessonStatus .statusLocked {
436
+ display: none;
437
+ }
438
+
439
+ .lessonRow[data-completed='true'] .statusDefault {
440
+ display: none;
441
+ }
442
+ .lessonRow[data-completed='true'] .statusDone {
443
+ display: inline-flex;
444
+ }
445
+
446
+ .lessonRow[data-next='true'] .statusDefault,
447
+ .lessonRow[data-next='true'] .statusDone {
448
+ display: none;
449
+ }
450
+ .lessonRow[data-next='true'] .statusNext {
451
+ display: inline-flex;
452
+ }
453
+
454
+ /* Locked beats every other state. */
455
+ .lessonRow[data-locked='true'] .statusDefault,
456
+ .lessonRow[data-locked='true'] .statusNext,
457
+ .lessonRow[data-locked='true'] .statusDone {
458
+ display: none;
459
+ }
460
+ .lessonRow[data-locked='true'] .statusLocked {
461
+ display: grid;
462
+ }
463
+
464
+ .lessonLockSlot {
465
+ display: grid;
466
+ place-items: center;
467
+ color: var(--content-tertiary);
468
+ }
469
+
470
+ /* "Done" row dimming + strikethrough. */
471
+ .lessonRow[data-completed='true'] .lessonNum,
472
+ .lessonRow[data-completed='true'] .lessonTitle {
473
+ color: var(--content-tertiary);
474
+ }
475
+ .lessonRow[data-completed='true'] .lessonTitle {
476
+ text-decoration: line-through;
477
+ text-decoration-color: var(--content-tertiary);
478
+ }
479
+
480
+ /* "Next" row: soft accent background + hint reveal. Hint is always rendered
481
+ so SSR markup stays consistent across all rows; CSS toggles its visibility
482
+ based on data-next. */
483
+ .lessonHint {
484
+ display: none;
485
+ }
486
+ .lessonRow[data-next='true']:not([data-locked='true']) {
487
+ background: var(--accent-main-soft);
488
+ }
489
+ .lessonRow[data-next='true']:not([data-locked='true']):hover {
490
+ background: var(--accent-main-soft);
491
+ }
492
+ .lessonRow[data-next='true']:not([data-locked='true']) .lessonHint {
493
+ display: inline;
494
+ }
495
+
496
+ /* Locked row styling. */
497
+ .lessonRow[data-locked='true'] {
498
+ cursor: not-allowed;
499
+ color: var(--content-tertiary);
500
+ }
501
+ .lessonRow[data-locked='true']:hover {
502
+ background: transparent;
503
+ }
504
+ .lessonRow[data-locked='true']:hover .lessonArrow {
505
+ color: var(--content-tertiary);
506
+ transform: none;
507
+ }
508
+ .lessonRow[data-locked='true'] .lessonTitle,
509
+ .lessonRow[data-locked='true'] .lessonNum,
510
+ .lessonRow[data-locked='true'] .lessonDuration,
511
+ .lessonRow[data-locked='true'] .lessonDurUnit {
512
+ color: var(--content-tertiary);
513
+ }
514
+ .lessonRow[data-locked='true'] .lessonArrow {
515
+ visibility: hidden;
516
+ }
517
+
518
+ /* CTA variants. Gate-paint sets data-cta-state on the ctaRow; CSS leaves
519
+ exactly one variant visible. */
520
+ .ctaRow [data-cta-variant] {
521
+ display: none;
522
+ }
523
+ .ctaRow[data-cta-state='not-started'] [data-cta-variant='not-started'],
524
+ .ctaRow[data-cta-state='in-progress'] [data-cta-variant='in-progress'],
525
+ .ctaRow[data-cta-state='complete'] [data-cta-variant='complete'] {
526
+ display: inline-flex;
527
+ }
528
+
529
+ /* Side card: bar / number colour follows success palette once everything
530
+ in the module is done. */
531
+ .sideCard[data-progress-state='complete'] .sideFill {
532
+ background: var(--accent-success);
533
+ }
534
+ .sideCard[data-progress-state='complete'] .sideVal {
535
+ color: var(--accent-success);
536
+ }
537
+
538
+ .lessonTags {
539
+ display: flex;
540
+ gap: var(--space-2);
541
+ }
542
+
543
+ .lessonTag {
544
+ font-family: var(--font-mono), ui-monospace, monospace;
545
+ font-size: var(--mp-fs-meta);
546
+ color: var(--content-tertiary);
547
+ padding: 2px 6px;
548
+ border: 1px solid var(--bg-stroke);
549
+ border-radius: var(--mp-r-1);
550
+ background: var(--bg-surface);
551
+ }
552
+
553
+ .lessonDuration {
554
+ font-family: var(--font-mono), ui-monospace, monospace;
555
+ font-size: var(--mp-fs-small);
556
+ color: var(--content-secondary);
557
+ font-weight: 600;
558
+ text-align: right;
559
+ min-width: 48px;
560
+ }
561
+
562
+ .lessonDurUnit {
563
+ font-weight: 400;
564
+ color: var(--content-tertiary);
565
+ }
566
+
567
+ .lessonArrow {
568
+ font-family: var(--font-mono), ui-monospace, monospace;
569
+ font-size: 18px;
570
+ color: var(--content-tertiary);
571
+ text-align: right;
572
+ padding-right: var(--space-2);
573
+ transition: transform 150ms ease, color 150ms ease;
574
+ }
575
+
576
+ /* ---------- Module nav ---------- */
577
+
578
+ .moduleNav {
579
+ display: grid;
580
+ grid-template-columns: 1fr 1fr;
581
+ gap: var(--space-4);
582
+ margin-top: var(--mp-space-9);
583
+ }
584
+
585
+ .navCard {
586
+ display: grid;
587
+ grid-template-rows: auto auto;
588
+ gap: 2px;
589
+ padding: var(--space-3) var(--space-4);
590
+ border: 1px solid var(--bg-stroke);
591
+ border-radius: var(--mp-r-3);
592
+ background: var(--bg-surface);
593
+ text-decoration: none;
594
+ color: inherit;
595
+ transition: background-color 120ms ease, border-color 120ms ease;
596
+ }
597
+
598
+ .navCard:hover {
599
+ border-color: var(--accent-main);
600
+ background: var(--bg-subtle);
601
+ }
602
+
603
+ .navCardNext {
604
+ text-align: right;
605
+ }
606
+
607
+ .navCardDisabled {
608
+ opacity: 0;
609
+ pointer-events: none;
610
+ }
611
+
612
+ .navLabel {
613
+ font-family: var(--font-mono), ui-monospace, monospace;
614
+ font-size: 11px;
615
+ color: var(--content-tertiary);
616
+ text-transform: uppercase;
617
+ letter-spacing: 0.06em;
618
+ }
619
+
620
+ .navTitle {
621
+ font-size: 15px;
622
+ font-weight: 600;
623
+ letter-spacing: -0.01em;
624
+ color: var(--content-primary);
625
+ }
626
+
627
+ /* ---------- Responsive ---------- */
628
+
629
+ @media (max-width: 1100px) {
630
+ .hero {
631
+ grid-template-columns: 1fr;
632
+ gap: var(--mp-space-7);
633
+ }
634
+ .sideCard {
635
+ position: static;
636
+ }
637
+ .title {
638
+ font-size: 56px;
639
+ }
640
+ .lessonRow {
641
+ grid-template-columns: 48px 28px 1fr auto 28px;
642
+ }
643
+ .lessonTags {
644
+ display: none;
645
+ }
646
+ }
647
+
648
+ @media (max-width: 720px) {
649
+ .page {
650
+ padding: var(--space-6) var(--space-5) var(--mp-space-9);
651
+ }
652
+ .title {
653
+ font-size: 40px;
654
+ }
655
+ .desc {
656
+ font-size: var(--mp-fs-body);
657
+ }
658
+ .ctaRow {
659
+ flex-direction: column;
660
+ align-items: stretch;
661
+ }
662
+ .ctaRow .btn {
663
+ justify-content: space-between;
664
+ width: 100%;
665
+ }
666
+ .btnGhost {
667
+ display: none;
668
+ }
669
+ .lessonRow {
670
+ grid-template-columns: 36px 24px 1fr auto;
671
+ gap: var(--space-3);
672
+ padding: var(--space-3) var(--space-2);
673
+ margin: 0 calc(var(--space-2) * -1);
674
+ }
675
+ .lessonTitle {
676
+ font-size: var(--mp-fs-body);
677
+ }
678
+ .lessonArrow {
679
+ display: none;
680
+ }
681
+ .moduleNav {
682
+ grid-template-columns: 1fr;
683
+ gap: var(--space-2);
684
+ }
685
+ .navTitle {
686
+ font-size: var(--mp-fs-body);
687
+ }
688
+ .sectionHead {
689
+ flex-direction: column;
690
+ align-items: flex-start;
691
+ gap: var(--space-2);
692
+ }
693
+ }