@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.
- package/LICENSE +21 -0
- package/README.md +232 -0
- package/assets/fonts/jetbrains-mono/JetBrainsMono-Bold.woff2 +0 -0
- package/assets/fonts/jetbrains-mono/JetBrainsMono-BoldItalic.woff2 +0 -0
- package/assets/fonts/jetbrains-mono/JetBrainsMono-Italic.woff2 +0 -0
- package/assets/fonts/jetbrains-mono/JetBrainsMono-Medium.woff2 +0 -0
- package/assets/fonts/jetbrains-mono/JetBrainsMono-Regular.woff2 +0 -0
- package/assets/fonts/jetbrains-mono/JetBrainsMono-SemiBold.woff2 +0 -0
- package/package.json +92 -0
- package/scripts/check-course-coverage.mts +32 -0
- package/scripts/fix-static-image-extensions.mjs +78 -0
- package/scripts/generate-readme-toc.mts +32 -0
- package/scripts/resolve-course-paths.mjs +28 -0
- package/scripts/sync-images.mjs +88 -0
- package/src/components/AppShell/AppShell.module.css +40 -0
- package/src/components/AppShell/AppShell.tsx +135 -0
- package/src/components/AppShell/index.ts +1 -0
- package/src/components/Callout/Callout.module.css +68 -0
- package/src/components/Callout/Callout.tsx +83 -0
- package/src/components/Callout/index.ts +1 -0
- package/src/components/CodeBlock/CodeBlock.module.css +68 -0
- package/src/components/CodeBlock/CodeBlock.tsx +65 -0
- package/src/components/CodeBlock/index.ts +1 -0
- package/src/components/GateProvider/GateProvider.tsx +207 -0
- package/src/components/GateProvider/index.ts +1 -0
- package/src/components/Header/Breadcrumbs.tsx +50 -0
- package/src/components/Header/Header.module.css +131 -0
- package/src/components/Header/Header.tsx +26 -0
- package/src/components/Header/HeaderLessonNav.tsx +118 -0
- package/src/components/Header/index.ts +1 -0
- package/src/components/HomePage/HomePage.module.css +538 -0
- package/src/components/HomePage/HomePage.tsx +295 -0
- package/src/components/HomePage/index.ts +1 -0
- package/src/components/LessonAwareLink/LessonAwareLink.module.css +12 -0
- package/src/components/LessonAwareLink/LessonAwareLink.tsx +86 -0
- package/src/components/LessonAwareLink/index.ts +1 -0
- package/src/components/LessonLayout/LessonLayout.module.css +35 -0
- package/src/components/LessonLayout/LessonLayout.tsx +18 -0
- package/src/components/LessonLayout/index.ts +1 -0
- package/src/components/LessonLockedInterstitial/LessonLockedInterstitial.module.css +367 -0
- package/src/components/LessonLockedInterstitial/LessonLockedInterstitial.tsx +256 -0
- package/src/components/LessonLockedInterstitial/index.ts +1 -0
- package/src/components/LessonNav/LessonNav.module.css +84 -0
- package/src/components/LessonNav/LessonNav.tsx +64 -0
- package/src/components/LessonNav/index.ts +1 -0
- package/src/components/LessonPageLayout/LessonPageLayout.module.css +118 -0
- package/src/components/LessonPageLayout/LessonPageLayout.tsx +46 -0
- package/src/components/LessonPageLayout/index.ts +1 -0
- package/src/components/LessonSideMeta/LessonSideMeta.module.css +68 -0
- package/src/components/LessonSideMeta/LessonSideMeta.tsx +87 -0
- package/src/components/LessonSideMeta/index.ts +1 -0
- package/src/components/ModulePage/ModulePage.module.css +693 -0
- package/src/components/ModulePage/ModulePage.tsx +301 -0
- package/src/components/ModulePage/index.ts +1 -0
- package/src/components/ProgramDrawer/LockIcon.tsx +19 -0
- package/src/components/ProgramDrawer/ProgramDrawer.module.css +563 -0
- package/src/components/ProgramDrawer/ProgramDrawer.tsx +481 -0
- package/src/components/ProgramDrawer/index.ts +1 -0
- package/src/components/ProgressBar/ProgressBar.module.css +46 -0
- package/src/components/ProgressBar/ProgressBar.tsx +45 -0
- package/src/components/ProgressBar/index.ts +1 -0
- package/src/components/ProgressModeProvider/ProgressModeProvider.tsx +87 -0
- package/src/components/ProgressModeProvider/index.ts +1 -0
- package/src/components/ReadingPrefsProvider/ReadingPrefsProvider.tsx +100 -0
- package/src/components/ReadingPrefsProvider/index.ts +1 -0
- package/src/components/ReadingProgress/ReadingProgress.module.css +19 -0
- package/src/components/ReadingProgress/ReadingProgress.tsx +53 -0
- package/src/components/ReadingProgress/index.ts +1 -0
- package/src/components/SettingsToggle/SettingsToggle.module.css +888 -0
- package/src/components/SettingsToggle/SettingsToggle.tsx +688 -0
- package/src/components/SettingsToggle/index.ts +1 -0
- package/src/components/Sidebar/Sidebar.module.css +157 -0
- package/src/components/Sidebar/Sidebar.tsx +63 -0
- package/src/components/Sidebar/icons/GitHubIcon.tsx +17 -0
- package/src/components/Sidebar/icons/HomeIcon.tsx +22 -0
- package/src/components/Sidebar/icons/LanguageIcon.tsx +24 -0
- package/src/components/Sidebar/icons/ProgramIcon.tsx +23 -0
- package/src/components/Sidebar/icons/SettingsIcon.tsx +26 -0
- package/src/components/Sidebar/icons/ThemeIcon.tsx +22 -0
- package/src/components/Sidebar/icons/index.ts +6 -0
- package/src/components/Sidebar/index.ts +1 -0
- package/src/components/ThemeProvider/ThemeProvider.tsx +68 -0
- package/src/components/ThemeProvider/index.ts +1 -0
- package/src/components/Toc/Toc.module.css +78 -0
- package/src/components/Toc/Toc.tsx +92 -0
- package/src/components/Toc/index.ts +1 -0
- package/src/components/TranslationBanner/TranslationBanner.module.css +32 -0
- package/src/components/TranslationBanner/TranslationBanner.tsx +40 -0
- package/src/components/TranslationBanner/index.ts +1 -0
- package/src/config.d.mts +12 -0
- package/src/config.mjs +110 -0
- package/src/index.ts +62 -0
- package/src/layout/lang.tsx +44 -0
- package/src/layout/root.tsx +223 -0
- package/src/lib/course-loader.ts +33 -0
- package/src/lib/course.ts +429 -0
- package/src/lib/coverage.ts +141 -0
- package/src/lib/description.ts +43 -0
- package/src/lib/extract-toc.ts +59 -0
- package/src/lib/format.ts +55 -0
- package/src/lib/frontier-link.ts +37 -0
- package/src/lib/gate-init-script.ts +40 -0
- package/src/lib/gate-mark-script.ts +324 -0
- package/src/lib/i18n.ts +474 -0
- package/src/lib/lang.ts +90 -0
- package/src/lib/lesson-gate.ts +79 -0
- package/src/lib/lesson.ts +66 -0
- package/src/lib/markdown-components.tsx +51 -0
- package/src/lib/markdown.ts +180 -0
- package/src/lib/mdx-plugins/rehype-callout.ts +80 -0
- package/src/lib/mdx-plugins/remark-lesson-images.ts +109 -0
- package/src/lib/mdx-plugins/remark-link-rewrite.ts +231 -0
- package/src/lib/paths.ts +36 -0
- package/src/lib/program-drawer.ts +8 -0
- package/src/lib/progress-mode.ts +69 -0
- package/src/lib/progress.ts +182 -0
- package/src/lib/reading-prefs.ts +127 -0
- package/src/lib/readme-toc.ts +69 -0
- package/src/lib/site-url.ts +33 -0
- package/src/lib/sitemap.ts +112 -0
- package/src/lib/slug.ts +15 -0
- package/src/lib/theme.ts +78 -0
- package/src/lib/use-i18n.ts +25 -0
- package/src/og/icon.tsx +40 -0
- package/src/og/opengraph-image.tsx +126 -0
- package/src/pages/home.tsx +66 -0
- package/src/pages/lesson.tsx +260 -0
- package/src/pages/module.tsx +80 -0
- package/src/pages/not-found-lang.tsx +51 -0
- package/src/pages/not-found-root.tsx +48 -0
- package/src/pages/root.tsx +44 -0
- package/src/seo/robots.ts +16 -0
- package/src/seo/sitemap.ts +10 -0
- package/src/styles/globals.css +139 -0
- package/src/styles/markdown.css +265 -0
- package/src/styles/reset.css +89 -0
- 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';
|