@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,888 @@
1
+ .wrapper {
2
+ position: relative;
3
+ display: inline-flex;
4
+ }
5
+
6
+ .trigger {
7
+ width: 36px;
8
+ height: 36px;
9
+ display: grid;
10
+ place-items: center;
11
+ border: 1px solid transparent;
12
+ background: transparent;
13
+ color: var(--content-secondary);
14
+ border-radius: var(--radius-sm);
15
+ cursor: pointer;
16
+ transition:
17
+ color 120ms ease,
18
+ background-color 120ms ease,
19
+ border-color 120ms ease;
20
+ }
21
+
22
+ .trigger svg {
23
+ width: 18px;
24
+ height: 18px;
25
+ }
26
+
27
+ .trigger:hover,
28
+ .trigger[aria-expanded='true'] {
29
+ background-color: var(--bg-subtle);
30
+ color: var(--content-primary);
31
+ border-color: var(--bg-stroke);
32
+ }
33
+
34
+ .trigger:focus-visible {
35
+ outline: 2px solid var(--accent-main);
36
+ outline-offset: 2px;
37
+ }
38
+
39
+ /* Mobile-only scrim — hidden on desktop where the popover is anchored. */
40
+ .overlay {
41
+ display: none;
42
+ }
43
+
44
+ .popover {
45
+ position: absolute;
46
+ bottom: 0;
47
+ left: calc(100% + var(--space-3));
48
+ width: 420px;
49
+ max-width: calc(100vw - var(--layout-sidebar-width) - var(--space-5));
50
+ max-height: calc(100dvh - var(--space-7));
51
+ overflow: hidden;
52
+ background: var(--bg-surface);
53
+ border: 1px solid var(--bg-stroke);
54
+ border-radius: var(--radius-md);
55
+ box-shadow:
56
+ var(--shadow-md),
57
+ 0 24px 60px rgba(35, 25, 10, 0.12);
58
+ display: flex;
59
+ flex-direction: column;
60
+ z-index: var(--z-popover);
61
+ }
62
+
63
+ [data-theme='dark'] .popover {
64
+ box-shadow:
65
+ var(--shadow-md),
66
+ 0 24px 60px rgba(0, 0, 0, 0.5);
67
+ }
68
+
69
+ /* Notch pointing at the trigger icon. */
70
+ .popover::before {
71
+ content: '';
72
+ position: absolute;
73
+ left: -7px;
74
+ bottom: 13px;
75
+ width: 12px;
76
+ height: 12px;
77
+ background: var(--bg-surface);
78
+ border-left: 1px solid var(--bg-stroke);
79
+ border-bottom: 1px solid var(--bg-stroke);
80
+ transform: rotate(45deg);
81
+ }
82
+
83
+ .popover[hidden] {
84
+ display: none;
85
+ }
86
+
87
+ .grabber {
88
+ display: none;
89
+ }
90
+
91
+ /* ─── Header ─────────────────────────────────────────────── */
92
+
93
+ .head {
94
+ display: flex;
95
+ align-items: flex-end;
96
+ justify-content: space-between;
97
+ gap: var(--space-3);
98
+ padding: var(--space-5) var(--space-5) var(--space-4);
99
+ border-bottom: 1px solid var(--bg-stroke);
100
+ flex: 0 0 auto;
101
+ }
102
+
103
+ .headTitles {
104
+ min-width: 0;
105
+ }
106
+
107
+ .eyebrow {
108
+ font-family: var(--font-mono);
109
+ font-size: var(--font-size-xs);
110
+ color: var(--content-tertiary);
111
+ text-transform: uppercase;
112
+ letter-spacing: 0.08em;
113
+ margin-bottom: 2px;
114
+ }
115
+
116
+ .title {
117
+ margin: 0;
118
+ font-size: var(--font-size-xl, 22px);
119
+ font-weight: var(--font-weight-semibold, 600);
120
+ letter-spacing: -0.015em;
121
+ color: var(--content-primary);
122
+ line-height: 1.1;
123
+ }
124
+
125
+ .closeBtn {
126
+ flex: 0 0 auto;
127
+ width: 32px;
128
+ height: 32px;
129
+ display: grid;
130
+ place-items: center;
131
+ border: 1px solid transparent;
132
+ background: transparent;
133
+ color: var(--content-secondary);
134
+ border-radius: var(--radius-sm);
135
+ cursor: pointer;
136
+ transition:
137
+ color 120ms ease,
138
+ background-color 120ms ease;
139
+ }
140
+
141
+ .closeBtn:hover {
142
+ background-color: var(--bg-subtle);
143
+ color: var(--content-primary);
144
+ }
145
+
146
+ .closeBtn:focus-visible {
147
+ outline: 2px solid var(--accent-main);
148
+ outline-offset: 2px;
149
+ }
150
+
151
+ /* ─── Body ───────────────────────────────────────────────── */
152
+
153
+ .body {
154
+ padding: var(--space-4) var(--space-5) var(--space-6);
155
+ overflow-y: auto;
156
+ display: flex;
157
+ flex-direction: column;
158
+ gap: var(--space-4);
159
+ }
160
+
161
+ .divider {
162
+ height: 1px;
163
+ background: var(--bg-stroke);
164
+ margin: var(--space-2) 0 var(--space-1);
165
+ }
166
+
167
+ /* Row = small mono caps label + control. Used for theme/lang and for
168
+ the font/size sub-rows inside text groups. */
169
+ .row {
170
+ display: grid;
171
+ grid-template-columns: 50px 1fr;
172
+ align-items: center;
173
+ gap: var(--space-4);
174
+ }
175
+
176
+ .rowLabel {
177
+ font-family: var(--font-mono);
178
+ font-size: var(--font-size-xs);
179
+ color: var(--content-tertiary);
180
+ text-transform: uppercase;
181
+ letter-spacing: 0.08em;
182
+ }
183
+
184
+ /* ─── Text group (Lecture text / Code text) ──────────────── */
185
+
186
+ .group {
187
+ display: flex;
188
+ flex-direction: column;
189
+ gap: var(--space-4);
190
+ }
191
+
192
+ .groupTitle {
193
+ margin: 0;
194
+ font-family: var(--font-mono);
195
+ font-size: var(--font-size-xs);
196
+ color: var(--content-tertiary);
197
+ text-transform: uppercase;
198
+ letter-spacing: 0.08em;
199
+ font-weight: var(--font-weight-medium, 500);
200
+ }
201
+
202
+ .preview {
203
+ padding: var(--space-3) var(--space-4);
204
+ background: var(--bg-subtle);
205
+ border: 1px solid var(--bg-stroke);
206
+ border-radius: var(--radius-sm);
207
+ color: var(--content-primary);
208
+ line-height: 1.5;
209
+ min-height: 44px;
210
+ display: flex;
211
+ align-items: center;
212
+ text-wrap: balance;
213
+ transition:
214
+ font-family 150ms ease,
215
+ font-size 150ms ease;
216
+ }
217
+
218
+ .previewCode {
219
+ letter-spacing: 0.005em;
220
+ }
221
+
222
+ /* ─── Segmented control ──────────────────────────────────── */
223
+
224
+ .seg {
225
+ display: inline-flex;
226
+ width: 100%;
227
+ background: var(--bg-subtle);
228
+ border: 1px solid var(--bg-stroke);
229
+ border-radius: var(--radius-sm);
230
+ padding: 3px;
231
+ gap: 2px;
232
+ }
233
+
234
+ .segOpt {
235
+ flex: 1 1 0;
236
+ min-width: 0;
237
+ padding: 7px 10px;
238
+ background: transparent;
239
+ border: 0;
240
+ border-radius: 4px;
241
+ color: var(--content-secondary);
242
+ font-size: 13px;
243
+ font-weight: var(--font-weight-medium, 500);
244
+ line-height: 1.4;
245
+ cursor: pointer;
246
+ text-align: center;
247
+ white-space: nowrap;
248
+ overflow: hidden;
249
+ text-overflow: ellipsis;
250
+ transition:
251
+ color 120ms ease,
252
+ background-color 120ms ease,
253
+ box-shadow 120ms ease;
254
+ }
255
+
256
+ .segOpt:hover {
257
+ color: var(--content-primary);
258
+ }
259
+
260
+ .segOpt:focus-visible {
261
+ outline: 2px solid var(--accent-main);
262
+ outline-offset: -2px;
263
+ }
264
+
265
+ .segOpt[data-active='true'] {
266
+ background: var(--bg-surface);
267
+ color: var(--content-primary);
268
+ font-weight: var(--font-weight-semibold, 600);
269
+ box-shadow:
270
+ 0 1px 2px rgba(35, 25, 10, 0.06),
271
+ 0 0 0 1px rgba(35, 25, 10, 0.04);
272
+ }
273
+
274
+ [data-theme='dark'] .segOpt[data-active='true'] {
275
+ box-shadow:
276
+ 0 1px 2px rgba(0, 0, 0, 0.4),
277
+ 0 0 0 1px rgba(255, 255, 255, 0.06);
278
+ }
279
+
280
+ .segMono {
281
+ font-family: var(--font-mono);
282
+ letter-spacing: 0.005em;
283
+ }
284
+
285
+ /* Language pill: small mono code (RU / EN) + label */
286
+ .segLang {
287
+ display: inline-flex;
288
+ align-items: center;
289
+ justify-content: center;
290
+ gap: var(--space-2);
291
+ }
292
+
293
+ .segLangBadge {
294
+ font-family: var(--font-mono);
295
+ font-size: 10px;
296
+ font-weight: var(--font-weight-medium, 500);
297
+ letter-spacing: 0.05em;
298
+ padding: 1px 5px;
299
+ border: 1px solid var(--bg-stroke);
300
+ border-radius: 3px;
301
+ color: var(--content-tertiary);
302
+ background: var(--bg-default);
303
+ }
304
+
305
+ .segOpt[data-active='true'] .segLangBadge {
306
+ color: var(--content-primary);
307
+ border-color: var(--bg-stroke-strong);
308
+ }
309
+
310
+ /* Theme pill: small bordered icon (sun / page / moon) + label */
311
+ .segTheme {
312
+ display: inline-flex;
313
+ align-items: center;
314
+ justify-content: center;
315
+ gap: var(--space-2);
316
+ }
317
+
318
+ .segThemeIcon {
319
+ display: inline-flex;
320
+ align-items: center;
321
+ justify-content: center;
322
+ width: 16px;
323
+ height: 16px;
324
+ color: var(--content-tertiary);
325
+ flex-shrink: 0;
326
+ }
327
+
328
+ .segOpt[data-active='true'] .segThemeIcon {
329
+ color: var(--content-primary);
330
+ }
331
+
332
+ /* ─── Size stepper ───────────────────────────────────────── */
333
+
334
+ .sizeStepper {
335
+ display: grid;
336
+ grid-template-columns: 32px 1fr 32px auto;
337
+ align-items: center;
338
+ gap: var(--space-3);
339
+ }
340
+
341
+ .sizeBtn {
342
+ width: 32px;
343
+ height: 32px;
344
+ display: grid;
345
+ place-items: center;
346
+ padding: 0;
347
+ border: 1px solid var(--bg-stroke);
348
+ border-radius: var(--radius-sm);
349
+ background: var(--bg-surface);
350
+ color: var(--content-secondary);
351
+ cursor: pointer;
352
+ transition:
353
+ color 120ms ease,
354
+ background-color 120ms ease,
355
+ border-color 120ms ease,
356
+ opacity 120ms ease;
357
+ }
358
+
359
+ .sizeBtn:hover:not(:disabled) {
360
+ color: var(--content-primary);
361
+ border-color: var(--bg-stroke-strong);
362
+ }
363
+
364
+ .sizeBtn:focus-visible {
365
+ outline: 2px solid var(--accent-main);
366
+ outline-offset: -2px;
367
+ }
368
+
369
+ .sizeBtn:disabled {
370
+ opacity: 0.4;
371
+ cursor: not-allowed;
372
+ }
373
+
374
+ .sizeBtnGlyphSmall {
375
+ font-family: var(--font-ui, var(--font-sans));
376
+ font-size: 10px;
377
+ font-weight: var(--font-weight-bold, 700);
378
+ line-height: 1;
379
+ }
380
+
381
+ .sizeBtnGlyphLarge {
382
+ font-family: var(--font-ui, var(--font-sans));
383
+ font-size: 16px;
384
+ font-weight: var(--font-weight-bold, 700);
385
+ line-height: 1;
386
+ }
387
+
388
+ .sizeTrack {
389
+ position: relative;
390
+ height: 32px;
391
+ display: flex;
392
+ align-items: center;
393
+ }
394
+
395
+ .sizeRail {
396
+ position: absolute;
397
+ left: 0;
398
+ right: 0;
399
+ top: 50%;
400
+ transform: translateY(-50%);
401
+ height: 4px;
402
+ background: var(--bg-subtle);
403
+ border: 1px solid var(--bg-stroke);
404
+ border-radius: 999px;
405
+ }
406
+
407
+ .sizeRailFill {
408
+ position: absolute;
409
+ left: 0;
410
+ top: 50%;
411
+ transform: translateY(-50%);
412
+ height: 4px;
413
+ background: var(--content-primary);
414
+ border-radius: 999px;
415
+ z-index: 1;
416
+ transition: width 150ms ease;
417
+ }
418
+
419
+ /* Knob is intentionally hidden — the dots are the visible target. */
420
+ .sizeKnob {
421
+ display: none;
422
+ }
423
+
424
+ .sizeTicks {
425
+ position: absolute;
426
+ inset: 0;
427
+ display: flex;
428
+ justify-content: space-between;
429
+ align-items: center;
430
+ z-index: 2;
431
+ }
432
+
433
+ .sizeTick {
434
+ width: 22px;
435
+ height: 32px;
436
+ display: grid;
437
+ place-items: center;
438
+ padding: 0;
439
+ background: transparent;
440
+ border: 0;
441
+ cursor: pointer;
442
+ }
443
+
444
+ .sizeTickDot {
445
+ width: 8px;
446
+ height: 8px;
447
+ background: var(--bg-surface);
448
+ border: 1.5px solid var(--bg-stroke-strong);
449
+ border-radius: 50%;
450
+ transition:
451
+ transform 120ms ease,
452
+ background-color 120ms ease,
453
+ border-color 120ms ease;
454
+ }
455
+
456
+ .sizeTick:hover .sizeTickDot {
457
+ border-color: var(--content-primary);
458
+ }
459
+
460
+ .sizeTick[data-active='true'] .sizeTickDot {
461
+ background: var(--content-primary);
462
+ border-color: var(--content-primary);
463
+ transform: scale(1.25);
464
+ }
465
+
466
+ .sizeTick:focus-visible {
467
+ outline: 2px solid var(--accent-main);
468
+ outline-offset: -4px;
469
+ border-radius: var(--radius-sm);
470
+ }
471
+
472
+ .sizeValue {
473
+ font-family: var(--font-mono);
474
+ font-size: var(--font-size-sm);
475
+ color: var(--content-primary);
476
+ font-weight: var(--font-weight-semibold, 600);
477
+ font-variant-numeric: tabular-nums;
478
+ min-width: 42px;
479
+ text-align: right;
480
+ white-space: nowrap;
481
+ }
482
+
483
+ .sizeUnit {
484
+ color: var(--content-tertiary);
485
+ margin-left: 1px;
486
+ font-weight: var(--font-weight-regular, 400);
487
+ }
488
+
489
+ /* Size control wrapper: desktop renders the stepper, mobile renders a
490
+ segmented size pill. Both are rendered (a11y radiogroup stays consistent
491
+ between breakpoints) and CSS swaps which one is visible. */
492
+ .sizeControl {
493
+ display: contents;
494
+ }
495
+
496
+ .sizeControlMobile {
497
+ display: none;
498
+ }
499
+
500
+ /* ─── Resources / GitHub link (mobile-only via media query below) ──── */
501
+
502
+ .resourcesDivider {
503
+ display: none;
504
+ }
505
+
506
+ .resourcesRow {
507
+ display: none;
508
+ }
509
+
510
+ .resourceLink {
511
+ display: flex;
512
+ align-items: center;
513
+ gap: var(--space-3);
514
+ padding: var(--space-3);
515
+ border: 1px solid var(--bg-stroke);
516
+ border-radius: var(--radius-md);
517
+ background-color: var(--bg-surface);
518
+ color: var(--content-primary);
519
+ text-decoration: none;
520
+ transition:
521
+ background-color 120ms ease,
522
+ border-color 120ms ease;
523
+ }
524
+
525
+ .resourceLink:hover,
526
+ .resourceLink:focus-visible {
527
+ background-color: var(--bg-subtle);
528
+ border-color: var(--bg-stroke-strong);
529
+ }
530
+
531
+ .resourceLink:focus-visible {
532
+ outline: 2px solid var(--accent-main);
533
+ outline-offset: 2px;
534
+ }
535
+
536
+ .resourceIcon {
537
+ display: inline-flex;
538
+ align-items: center;
539
+ justify-content: center;
540
+ width: 32px;
541
+ height: 32px;
542
+ border-radius: var(--radius-sm);
543
+ background-color: var(--content-primary);
544
+ color: var(--bg-default);
545
+ flex: 0 0 auto;
546
+ }
547
+
548
+ .resourceIcon svg {
549
+ width: 18px;
550
+ height: 18px;
551
+ }
552
+
553
+ .resourceText {
554
+ flex: 1;
555
+ min-width: 0;
556
+ display: flex;
557
+ flex-direction: column;
558
+ gap: 2px;
559
+ }
560
+
561
+ .resourceTitle {
562
+ font-size: 13px;
563
+ font-weight: var(--font-weight-semibold);
564
+ color: var(--content-primary);
565
+ }
566
+
567
+ .resourceSub {
568
+ font-family: var(--font-mono);
569
+ font-size: 10px;
570
+ color: var(--content-tertiary);
571
+ }
572
+
573
+ .resourceArrow {
574
+ font-size: 14px;
575
+ color: var(--content-tertiary);
576
+ }
577
+
578
+ /* ─── Free reading (disable progress) ────────────────────── */
579
+
580
+ .freeReadingRow {
581
+ display: flex;
582
+ align-items: center;
583
+ justify-content: space-between;
584
+ gap: var(--space-4);
585
+ }
586
+
587
+ .freeReadingText {
588
+ display: flex;
589
+ flex-direction: column;
590
+ gap: 2px;
591
+ min-width: 0;
592
+ }
593
+
594
+ .freeReadingTitle {
595
+ font-size: 14px;
596
+ font-weight: var(--font-weight-semibold, 600);
597
+ color: var(--content-primary);
598
+ line-height: 1.3;
599
+ }
600
+
601
+ .freeReadingDesc {
602
+ font-size: var(--font-size-sm);
603
+ color: var(--content-tertiary);
604
+ line-height: 1.35;
605
+ }
606
+
607
+ .switch {
608
+ flex: 0 0 auto;
609
+ position: relative;
610
+ width: 42px;
611
+ height: 24px;
612
+ padding: 0;
613
+ border: 1px solid var(--bg-stroke);
614
+ border-radius: 999px;
615
+ background: var(--bg-subtle);
616
+ cursor: pointer;
617
+ transition:
618
+ background-color 140ms ease,
619
+ border-color 140ms ease;
620
+ }
621
+
622
+ .switch[data-active='true'] {
623
+ background: var(--content-primary);
624
+ border-color: var(--content-primary);
625
+ }
626
+
627
+ .switch:focus-visible {
628
+ outline: 2px solid var(--accent-main);
629
+ outline-offset: 2px;
630
+ }
631
+
632
+ .switchThumb {
633
+ position: absolute;
634
+ top: 50%;
635
+ left: 2px;
636
+ width: 18px;
637
+ height: 18px;
638
+ border-radius: 50%;
639
+ background: var(--bg-surface);
640
+ box-shadow: 0 1px 2px rgba(35, 25, 10, 0.2);
641
+ transform: translateY(-50%);
642
+ transition: left 140ms ease;
643
+ }
644
+
645
+ .switch[data-active='true'] .switchThumb {
646
+ left: calc(100% - 20px);
647
+ background: var(--bg-default);
648
+ }
649
+
650
+ .confirm {
651
+ display: flex;
652
+ align-items: center;
653
+ justify-content: space-between;
654
+ gap: var(--space-3);
655
+ padding: var(--space-3) var(--space-4);
656
+ background: var(--bg-subtle);
657
+ border: 1px solid var(--bg-stroke);
658
+ border-radius: var(--radius-sm);
659
+ }
660
+
661
+ .confirmText {
662
+ font-size: var(--font-size-sm);
663
+ color: var(--content-secondary);
664
+ line-height: 1.35;
665
+ min-width: 0;
666
+ }
667
+
668
+ .confirmActions {
669
+ display: inline-flex;
670
+ gap: var(--space-2);
671
+ flex: 0 0 auto;
672
+ }
673
+
674
+ .confirmBtn {
675
+ padding: 6px 12px;
676
+ border: 1px solid var(--bg-stroke);
677
+ border-radius: var(--radius-sm);
678
+ background: var(--bg-surface);
679
+ color: var(--content-secondary);
680
+ font-size: 13px;
681
+ font-weight: var(--font-weight-medium, 500);
682
+ cursor: pointer;
683
+ white-space: nowrap;
684
+ transition:
685
+ color 120ms ease,
686
+ background-color 120ms ease,
687
+ border-color 120ms ease;
688
+ }
689
+
690
+ .confirmBtn:hover {
691
+ color: var(--content-primary);
692
+ border-color: var(--bg-stroke-strong);
693
+ }
694
+
695
+ .confirmBtn:focus-visible {
696
+ outline: 2px solid var(--accent-main);
697
+ outline-offset: 2px;
698
+ }
699
+
700
+ .confirmBtnDanger {
701
+ background: var(--content-primary);
702
+ color: var(--bg-default);
703
+ border-color: var(--content-primary);
704
+ }
705
+
706
+ .confirmBtnDanger:hover {
707
+ color: var(--bg-default);
708
+ opacity: 0.88;
709
+ }
710
+
711
+ /* ─── Responsive ─────────────────────────────────────────── */
712
+
713
+ @media (max-width: 1023px) {
714
+ /* The trigger sits as a standalone FAB at top-left in mobile (no longer
715
+ wrapped by the sidebar pill), so it needs its own surface treatment. */
716
+ .trigger {
717
+ width: 44px;
718
+ height: 44px;
719
+ border-radius: 999px;
720
+ background-color: var(--bg-surface);
721
+ border: 1px solid var(--bg-stroke);
722
+ box-shadow: var(--shadow-sm);
723
+ }
724
+
725
+ .trigger[aria-expanded='true'] {
726
+ background-color: var(--content-primary);
727
+ color: var(--bg-default);
728
+ border-color: var(--content-primary);
729
+ }
730
+
731
+ .overlay {
732
+ display: block;
733
+ position: fixed;
734
+ inset: 0;
735
+ background: rgba(20, 16, 8, 0.32);
736
+ backdrop-filter: blur(2px);
737
+ -webkit-backdrop-filter: blur(2px);
738
+ z-index: calc(var(--z-popover) - 1);
739
+ animation: settingsOverlayIn 180ms ease-out;
740
+ }
741
+
742
+ [data-theme='dark'] .overlay {
743
+ background: rgba(0, 0, 0, 0.55);
744
+ }
745
+
746
+ /* Match the ProgramDrawer mobile chrome: full-viewport surface, no rounded
747
+ corners, no top shadow, slide-in from the left edge. The sheet/grabber
748
+ pattern previously used here is dropped so nav and settings read as
749
+ the same primitive on mobile. */
750
+ .popover {
751
+ position: fixed;
752
+ inset: 0;
753
+ top: 0;
754
+ right: 0;
755
+ bottom: 0;
756
+ left: 0;
757
+ width: 100vw;
758
+ width: 100dvw;
759
+ max-width: 100vw;
760
+ max-height: none;
761
+ height: auto;
762
+ border: 0;
763
+ border-radius: 0;
764
+ box-shadow: none;
765
+ background-color: var(--bg-default);
766
+ animation: settingsDrawerIn 220ms cubic-bezier(0.2, 0.7, 0.2, 1);
767
+ }
768
+
769
+ [data-theme='dark'] .popover {
770
+ box-shadow: none;
771
+ }
772
+
773
+ .popover::before {
774
+ display: none;
775
+ }
776
+
777
+ .grabber {
778
+ display: none;
779
+ }
780
+
781
+ /* Sticky frosted header — mirrors ProgramDrawer.module.css under the same
782
+ breakpoint so both drawers carry the same eyebrow + title chrome. */
783
+ .head {
784
+ position: sticky;
785
+ top: 0;
786
+ z-index: 5;
787
+ padding: max(env(safe-area-inset-top), var(--space-4)) var(--space-5)
788
+ var(--space-4);
789
+ background-color: color-mix(in srgb, var(--bg-default) 94%, transparent);
790
+ backdrop-filter: blur(16px);
791
+ -webkit-backdrop-filter: blur(16px);
792
+ }
793
+
794
+ .body {
795
+ gap: var(--space-5);
796
+ padding-bottom: calc(var(--space-5) + env(safe-area-inset-bottom));
797
+ }
798
+
799
+ /* Design keeps the row label on the LEFT in mobile (mono uppercase, 56px
800
+ column) — the segmented control sits next to it. This matches the
801
+ reference's drawer-settings .mset-row grid: `56px 1fr`. */
802
+ .row {
803
+ grid-template-columns: 56px 1fr;
804
+ gap: var(--space-3);
805
+ align-items: center;
806
+ }
807
+
808
+ .rowLabel {
809
+ font-size: 10px;
810
+ letter-spacing: 0.08em;
811
+ }
812
+
813
+ .segOpt {
814
+ padding: 8px 10px;
815
+ }
816
+
817
+ /* Swap stepper for a horizontal segmented size selector. The 6 options
818
+ (14/16/18/20/22/24) scroll horizontally when they don't fit. */
819
+ .sizeControlDesktop {
820
+ display: none;
821
+ }
822
+
823
+ .sizeControlMobile {
824
+ display: flex;
825
+ }
826
+
827
+ .segSizes {
828
+ overflow-x: auto;
829
+ scroll-snap-type: x mandatory;
830
+ scrollbar-width: none;
831
+ }
832
+
833
+ .segSizes::-webkit-scrollbar {
834
+ display: none;
835
+ }
836
+
837
+ .segSizes .segOpt {
838
+ flex: 1 0 auto;
839
+ min-width: 38px;
840
+ scroll-snap-align: start;
841
+ }
842
+
843
+ /* Surface the GitHub resources card at the bottom of the sheet. */
844
+ .resourcesDivider {
845
+ display: block;
846
+ }
847
+
848
+ .resourcesRow {
849
+ display: grid;
850
+ }
851
+ }
852
+
853
+ @media (max-width: 480px) {
854
+ .seg {
855
+ padding: 3px;
856
+ }
857
+
858
+ .segOpt {
859
+ font-size: var(--font-size-sm);
860
+ }
861
+ }
862
+
863
+ @keyframes settingsOverlayIn {
864
+ from {
865
+ opacity: 0;
866
+ }
867
+ to {
868
+ opacity: 1;
869
+ }
870
+ }
871
+
872
+ @keyframes settingsDrawerIn {
873
+ from {
874
+ transform: translateX(-12px);
875
+ opacity: 0;
876
+ }
877
+ to {
878
+ transform: translateX(0);
879
+ opacity: 1;
880
+ }
881
+ }
882
+
883
+ @media (prefers-reduced-motion: reduce) {
884
+ .overlay,
885
+ .popover {
886
+ animation: none;
887
+ }
888
+ }