Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,20 @@ Core engine, React component, and public website shipped.
- [x] More supported tools: Windsurf, Replit, Linear, Figma AI, v0 (13 total)
- [x] More roles: Sales, Customer Success, Finance (11 total)
- [x] `generateLessonContent(lesson)` — returns full lesson body, exercises, and rubric
- [x] Lesson prerequisite graph — sequential `prerequisiteIds[]` on each lesson; ProgressTracker locks accordingly
- [ ] `progress` field in `LearningPath` to track completed lessons
- [ ] Lesson prerequisite graph — reorder-aware sequencing

### React package
- [x] `<ProgressTracker />` component — persists lesson completion state to localStorage
- [ ] `<LessonDetail />` — renders full lesson body returned by `generateLessonContent()`
- [ ] `light` theme variant (in addition to `warm`, `midnight`, `technical`)
- [ ] Headless mode: all components accept `renderItem` render-prop overrides
- [x] `<ProgressTracker />` component — persists lesson completion state to localStorage; prerequisite-aware lesson locking
- [x] `<LessonDetail />` — renders full lesson body, exercises, and rubric from `generateLessonContent()`
- [x] `light` theme variant (in addition to `warm`, `midnight`, `technical`)
- [x] Headless mode: `renderItem` render-prop on `LearningPath` and `ProgressTracker`

### Web
- [x] `/changelog` page — versioned release notes
- [x] `/compare/[slug]` pages — Claude vs ChatGPT, Cursor vs Copilot, Windsurf vs Cursor, and more
- [x] `/guides/[slug]` — Next.js integration, theming, CLI, generateLessonContent guides
- [ ] Per-role `opengraph-image` for `/roles/[slug]` pages
- [x] `opengraph-image` for `/roles/[slug]` and `/tools/[slug]` pages

### Developer experience
- [x] `@learnkit-ai/cli` — `npx @learnkit-ai/cli generate` outputs a JSON learning path
Expand Down
121 changes: 121 additions & 0 deletions apps/web/src/app/roles/[slug]/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { ImageResponse } from 'next/og';
import { ROLES } from '@/lib/seo-data';

export const runtime = 'edge';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';

export default async function Image({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const role = ROLES.find((r) => r.slug === slug);
if (!role) return new Response('Not found', { status: 404 });

return new ImageResponse(
(
<div
style={{
width: 1200,
height: 630,
background: '#F7F4EF',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
padding: '72px 80px',
fontFamily: 'Georgia, serif',
}}
>
{/* Top: brand */}
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 10,
fontFamily: 'system-ui, sans-serif',
fontSize: 16,
fontWeight: 600,
color: '#6B7280',
letterSpacing: '0.06em',
textTransform: 'uppercase',
}}
>
LearnKit AI
</div>

{/* Middle: role name + tagline */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
<div
style={{
fontSize: 14,
fontFamily: 'system-ui, sans-serif',
fontWeight: 500,
color: '#C8472A',
letterSpacing: '0.1em',
textTransform: 'uppercase',
}}
>
30-day AI learning path
</div>
<div
style={{
fontSize: 72,
fontWeight: 400,
color: '#1A2547',
lineHeight: 1,
letterSpacing: '-0.03em',
}}
>
AI training for
<br />
<span style={{ fontStyle: 'italic', color: '#C8472A' }}>{role.name}s</span>.
</div>
<div
style={{
fontSize: 22,
fontFamily: 'system-ui, sans-serif',
color: '#4A557A',
lineHeight: 1.4,
maxWidth: 800,
marginTop: 8,
}}
>
{role.blurb}
</div>
</div>

{/* Bottom: stats + tools */}
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<div
style={{
display: 'flex',
gap: 32,
fontFamily: 'system-ui, sans-serif',
fontSize: 15,
color: '#6B7280',
letterSpacing: '0.04em',
}}
>
<span>4 weeks</span>
<span>12 lessons</span>
<span>Apache-2.0</span>
</div>
<div
style={{
fontFamily: 'system-ui, sans-serif',
fontSize: 15,
color: '#9CA3AF',
}}
>
learnkit-ai.com
</div>
</div>
</div>
),
{ ...size },
);
}
144 changes: 144 additions & 0 deletions apps/web/src/app/tools/[slug]/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { ImageResponse } from 'next/og';
import { TOOLS } from '@/lib/seo-data';

export const runtime = 'edge';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';

export default async function Image({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const tool = TOOLS.find((t) => t.slug === slug);
if (!tool) return new Response('Not found', { status: 404 });

return new ImageResponse(
(
<div
style={{
width: 1200,
height: 630,
background: '#1A2547',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
padding: '72px 80px',
fontFamily: 'Georgia, serif',
}}
>
{/* Top: brand */}
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 10,
fontFamily: 'system-ui, sans-serif',
fontSize: 16,
fontWeight: 600,
color: 'rgba(244,239,227,0.5)',
letterSpacing: '0.06em',
textTransform: 'uppercase',
}}
>
LearnKit AI
</div>

{/* Middle: tool name + tagline */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 16,
}}
>
<div
style={{
width: 64,
height: 64,
borderRadius: 12,
background: tool.color,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 28,
color: '#fff',
fontFamily: 'system-ui, sans-serif',
fontWeight: 700,
}}
>
{tool.name[0]}
</div>
<div
style={{
fontFamily: 'system-ui, sans-serif',
fontSize: 15,
fontWeight: 600,
color: 'rgba(244,239,227,0.5)',
letterSpacing: '0.06em',
textTransform: 'uppercase',
}}
>
{tool.vendor}
</div>
</div>
<div
style={{
fontSize: 68,
fontWeight: 400,
color: '#F4EFE3',
lineHeight: 1.05,
letterSpacing: '-0.03em',
}}
>
{tool.tagline}.
</div>
<div
style={{
fontSize: 22,
fontFamily: 'system-ui, sans-serif',
color: 'rgba(244,239,227,0.65)',
lineHeight: 1.4,
maxWidth: 800,
marginTop: 8,
}}
>
{tool.blurb}
</div>
</div>

{/* Bottom: stats */}
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<div
style={{
display: 'flex',
gap: 32,
fontFamily: 'system-ui, sans-serif',
fontSize: 15,
color: 'rgba(244,239,227,0.4)',
letterSpacing: '0.04em',
}}
>
<span>{tool.modules} modules</span>
<span>~{tool.hours}h</span>
<span>Apache-2.0</span>
</div>
<div
style={{
fontFamily: 'system-ui, sans-serif',
fontSize: 15,
color: 'rgba(244,239,227,0.3)',
}}
>
learnkit-ai.com
</div>
</div>
</div>
),
{ ...size },
);
}
10 changes: 9 additions & 1 deletion apps/web/src/components/demo/DemoFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const ROLES = [
'Founder',
'Operations',
'Researcher',
'Sales',
'Customer Success',
'Finance',
];

const ALL_TOOLS = [
Expand All @@ -30,6 +33,11 @@ const ALL_TOOLS = [
'Notion AI',
'Perplexity',
'Gemini',
'Windsurf',
'Replit',
'Linear',
'Figma AI',
'v0',
];

const GOAL_SAMPLES = [
Expand Down Expand Up @@ -558,7 +566,7 @@ export function DemoFlow() {
</>
}
>
<LessonPreview role={role} tools={tools} onBack={() => setStep(4)} />
<LessonPreview role={role} tools={tools} goal={goal} level={level} onBack={() => setStep(4)} />
</StepShell>
)}
</div>
Expand Down
Loading
Loading