@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,131 @@
1
+ .header {
2
+ position: sticky;
3
+ top: 0;
4
+ z-index: var(--z-header);
5
+ background-color: color-mix(in srgb, var(--bg-default) 90%, transparent);
6
+ backdrop-filter: saturate(180%) blur(10px);
7
+ border-bottom: 1px solid var(--bg-stroke);
8
+ min-height: 56px;
9
+ }
10
+
11
+ /* Constrain header content to the same max-width as LessonPageLayout so the
12
+ breadcrumbs and lesson nav sit directly above the article column rather
13
+ than stretching across the full viewport. */
14
+ .inner {
15
+ display: flex;
16
+ align-items: center;
17
+ gap: var(--space-4);
18
+ padding: var(--space-4) var(--space-8);
19
+ max-width: 1280px;
20
+ width: 100%;
21
+ margin: 0 auto;
22
+ min-height: 56px;
23
+ }
24
+
25
+ .breadcrumbs {
26
+ display: flex;
27
+ align-items: center;
28
+ gap: var(--space-2);
29
+ flex: 1 1 auto;
30
+ min-width: 0;
31
+ font-size: var(--font-size-sm);
32
+ color: var(--content-secondary);
33
+ overflow: hidden;
34
+ text-overflow: ellipsis;
35
+ white-space: nowrap;
36
+ }
37
+
38
+ .breadcrumbRoot {
39
+ color: var(--content-primary);
40
+ font-weight: var(--font-weight-semibold);
41
+ }
42
+
43
+ .breadcrumbLink {
44
+ color: var(--content-secondary);
45
+ text-decoration: none;
46
+ transition: color 120ms ease;
47
+ border-radius: var(--radius-sm);
48
+ }
49
+
50
+ .breadcrumbLink:hover {
51
+ color: var(--content-link-hover);
52
+ }
53
+
54
+ .breadcrumbLink:focus-visible {
55
+ outline: 2px solid var(--accent-main);
56
+ outline-offset: 2px;
57
+ }
58
+
59
+ .breadcrumbCurrent {
60
+ color: var(--content-primary);
61
+ font-weight: var(--font-weight-medium);
62
+ overflow: hidden;
63
+ text-overflow: ellipsis;
64
+ white-space: nowrap;
65
+ }
66
+
67
+ .breadcrumbSeparator {
68
+ color: var(--content-tertiary);
69
+ flex: 0 0 auto;
70
+ }
71
+
72
+ .actions {
73
+ display: flex;
74
+ align-items: center;
75
+ gap: var(--space-1);
76
+ flex: 0 0 auto;
77
+ }
78
+
79
+ .actions > :first-child:not(.navButton):not(.navButtonDisabled) {
80
+ margin-right: var(--space-3);
81
+ }
82
+
83
+ .navButton,
84
+ .navButtonDisabled {
85
+ width: 36px;
86
+ height: 36px;
87
+ display: grid;
88
+ place-items: center;
89
+ border-radius: var(--radius-md);
90
+ border: none;
91
+ background: transparent;
92
+ color: var(--content-secondary);
93
+ text-decoration: none;
94
+ cursor: pointer;
95
+ transition:
96
+ background-color 120ms ease,
97
+ color 120ms ease;
98
+ }
99
+
100
+ .navButton:hover {
101
+ background-color: var(--bg-subtle);
102
+ color: var(--content-primary);
103
+ }
104
+
105
+ .navButton:focus-visible {
106
+ outline: 2px solid var(--accent-main);
107
+ outline-offset: 2px;
108
+ }
109
+
110
+ .navButtonDisabled {
111
+ color: var(--content-tertiary);
112
+ cursor: default;
113
+ opacity: 0.4;
114
+ }
115
+
116
+ @media (max-width: 720px) {
117
+ .inner {
118
+ padding: var(--space-3) var(--space-5);
119
+ }
120
+ }
121
+
122
+ /* Mobile reader mode: the sticky header is replaced by two FABs on the
123
+ sidebar layer (settings + program), so the breadcrumb/progress/lesson-nav
124
+ chrome is hidden entirely. Everything it carried — breadcrumbs context,
125
+ progress percent, prev/next — lives inside the program overlay and
126
+ inside LessonNav at the article footer. */
127
+ @media (max-width: 1023px) {
128
+ .header {
129
+ display: none;
130
+ }
131
+ }
@@ -0,0 +1,26 @@
1
+ import type { ReactNode } from 'react';
2
+ import { DEFAULT_BRAND_NAME } from '@/lib/course';
3
+ import { getDict } from '@/lib/i18n';
4
+ import { type Lang } from '@/lib/lang';
5
+ import styles from './Header.module.css';
6
+
7
+ type HeaderProps = {
8
+ lang: Lang;
9
+ breadcrumbs?: ReactNode;
10
+ actions?: ReactNode;
11
+ brandRoot?: string;
12
+ };
13
+
14
+ export function Header({ lang, breadcrumbs, actions, brandRoot = DEFAULT_BRAND_NAME }: HeaderProps) {
15
+ const t = getDict(lang);
16
+ return (
17
+ <header className={styles.header}>
18
+ <div className={styles.inner}>
19
+ <div className={styles.breadcrumbs} aria-label={t.breadcrumbsLabel}>
20
+ {breadcrumbs ?? <span className={styles.breadcrumbRoot}>{brandRoot}</span>}
21
+ </div>
22
+ <div className={styles.actions}>{actions}</div>
23
+ </div>
24
+ </header>
25
+ );
26
+ }
@@ -0,0 +1,118 @@
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+ import { useGate } from '@/components/GateProvider';
5
+ import { useProgressMode } from '@/components/ProgressModeProvider';
6
+ import type { FlatLessonEntry } from '@/lib/course';
7
+ import { lessonKey, markCompletedAndAdvance } from '@/lib/progress';
8
+ import { useLang, useT } from '@/lib/use-i18n';
9
+ import styles from './Header.module.css';
10
+
11
+ type HeaderLessonNavProps = {
12
+ prev: FlatLessonEntry | null;
13
+ next: FlatLessonEntry | null;
14
+ currentModuleId: string;
15
+ currentSlug: string;
16
+ };
17
+
18
+ export function HeaderLessonNav({
19
+ prev,
20
+ next,
21
+ currentModuleId,
22
+ currentSlug,
23
+ }: HeaderLessonNavProps) {
24
+ const gate = useGate();
25
+ const { disabled } = useProgressMode();
26
+ const t = useT();
27
+ const lang = useLang();
28
+ const handleNextClick = () => {
29
+ // Free-reading mode: the chevron is visible and clickable (CSS hides only
30
+ // the progress widgets, not this nav), so guard the write explicitly — in
31
+ // free-reading mode it only navigates and never records progress.
32
+ if (disabled) return;
33
+ // Mirror LessonNav: advance the sticky pointer before navigating so the
34
+ // gate considers the next lesson reachable. Without this, clicking the
35
+ // header chevron from a fresh-state lesson lands on the locked interstitial.
36
+ markCompletedAndAdvance(gate.course, lessonKey(currentModuleId, currentSlug));
37
+ };
38
+
39
+ return (
40
+ <>
41
+ {prev ? (
42
+ <Link
43
+ href={`/${lang}/${prev.moduleId}/${prev.lesson.slug}`}
44
+ className={styles.navButton}
45
+ title={`← ${prev.lesson.title}`}
46
+ aria-label={`${t.prevLessonAria}: ${prev.lesson.title}`}
47
+ >
48
+ <ChevronLeft />
49
+ </Link>
50
+ ) : (
51
+ <span
52
+ className={styles.navButtonDisabled}
53
+ aria-hidden="true"
54
+ title={t.firstLessonTitle}
55
+ >
56
+ <ChevronLeft />
57
+ </span>
58
+ )}
59
+ {next ? (
60
+ <Link
61
+ href={`/${lang}/${next.moduleId}/${next.lesson.slug}`}
62
+ className={styles.navButton}
63
+ title={`${next.lesson.title} →`}
64
+ aria-label={`${t.nextLessonAria}: ${next.lesson.title}`}
65
+ onClick={handleNextClick}
66
+ >
67
+ <ChevronRight />
68
+ </Link>
69
+ ) : (
70
+ <span
71
+ className={styles.navButtonDisabled}
72
+ aria-hidden="true"
73
+ title={t.lastLessonTitle}
74
+ >
75
+ <ChevronRight />
76
+ </span>
77
+ )}
78
+ </>
79
+ );
80
+ }
81
+
82
+ function ChevronLeft() {
83
+ return (
84
+ <svg
85
+ width="20"
86
+ height="20"
87
+ viewBox="0 0 24 24"
88
+ fill="none"
89
+ stroke="currentColor"
90
+ strokeWidth="1.75"
91
+ strokeLinecap="round"
92
+ strokeLinejoin="round"
93
+ aria-hidden="true"
94
+ focusable="false"
95
+ >
96
+ <path d="M15 6 9 12l6 6" />
97
+ </svg>
98
+ );
99
+ }
100
+
101
+ function ChevronRight() {
102
+ return (
103
+ <svg
104
+ width="20"
105
+ height="20"
106
+ viewBox="0 0 24 24"
107
+ fill="none"
108
+ stroke="currentColor"
109
+ strokeWidth="1.75"
110
+ strokeLinecap="round"
111
+ strokeLinejoin="round"
112
+ aria-hidden="true"
113
+ focusable="false"
114
+ >
115
+ <path d="m9 6 6 6-6 6" />
116
+ </svg>
117
+ );
118
+ }
@@ -0,0 +1 @@
1
+ export { Header } from './Header';