https://github.com/jmilne22/vibe-learn
Self-hosted static course platform — markdown lessons, exercises, flashcards, spaced repetition, 10 themes, zero backend
https://github.com/jmilne22/vibe-learn
courses education exercises flashcards javascript learning-platform markdown no-backend offline-first self-hosted spaced-repetition srs static-site
Last synced: 3 months ago
JSON representation
Self-hosted static course platform — markdown lessons, exercises, flashcards, spaced repetition, 10 themes, zero backend
- Host: GitHub
- URL: https://github.com/jmilne22/vibe-learn
- Owner: jmilne22
- Created: 2026-01-30T17:21:57.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-02-03T22:25:31.000Z (3 months ago)
- Last Synced: 2026-02-04T23:57:46.601Z (3 months ago)
- Topics: courses, education, exercises, flashcards, javascript, learning-platform, markdown, no-backend, offline-first, self-hosted, spaced-repetition, srs, static-site
- Language: JavaScript
- Homepage: https://vibe-learn.ai/
- Size: 1.56 MB
- Stars: 3
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# vibe-learn
A static course platform. Write lessons in markdown, define exercises in YAML, run one build command, and get a full course site with interactive exercises, spaced repetition, daily practice, analytics, and offline support. No backend, no database, no accounts — just static files you can host anywhere.
**Fork it, replace the content, build, deploy.** The engine is generic. The courses are just data.
> **This is a vibe-coded project.** The entire thing — engine, content, exercises, all of it — was generated by AI (Claude) as an experiment. I haven't audited a single line of code. I built it for fun to see how far vibe-coding could go. It seems to work, but don't trust it with anything important.
>
> **Everything is intentionally local.** Zero backend, zero accounts, zero data collection. All progress is stored in your browser's localStorage and never leaves your machine.
## Platform features
### Content authoring
| What you write | What you get |
|---|---|
| Markdown lessons | Syntax-highlighted pages with side-by-side code comparisons and callout blocks |
| Exercise YAML (warmups + challenges) | Multiple variants per exercise, shuffle, 5 difficulty modes, concept filters |
| Flashcard YAML | Flip/rate sessions with keyboard shortcuts, filtered by module |
| Algorithm YAML | Session-based drills with progression, blind mode, and pattern recognition |
| Challenge YAML | Open-ended problems with requirements, acceptance criteria checkboxes, company tags |
### Learning engine
- **Spaced repetition (SM-2)** — exercises and flashcards are scheduled based on self-ratings
- **Daily practice** — review due items, drill weak spots, discover new exercises, or mix modes
- **Thinking timer** — configurable countdown that locks hints/solutions so you attempt the problem first
- **Self-rating** — "Got it", "Struggled", or "Needed solution" feeds the SRS scheduler
- **Hints and solutions** — progressive disclosure with annotations (tips, gotchas, idioms, complexity)
### Tracking and analytics
- **Analytics dashboard** — weak concept report, module strength rankings, per-concept breakdown, top 10 weakest exercises
- **Streak tracking** and GitHub-style activity heatmap
- **Personal notes** on any exercise, exportable as markdown
- **Data export/import** — all progress stored in localStorage, downloadable as JSON
### Platform
- **Plugin system** — features are self-contained plugins, auto-discovered at build time
- **Multi-course** — one install hosts unlimited courses, each with isolated progress
- **10 themes** — gruvbox, solarized, everforest, OLED dark, persona 5, factorio, and more
- **Pomodoro timer** — session presets (25/5, 50/10, 90/20) with break reminders
- **Offline support** via service worker
- **Zero backend** — pure static site, host on GitHub Pages, Netlify, S3, a USB stick
## Quick start
```bash
git clone
cd vibe-learn
npm install # installs marked, highlight.js, js-yaml
npm run build # builds all courses + landing page into dist/
```
Build a single course:
```bash
npm run build:go # builds only the Go course into dist/go/
npm run build:rust # builds only the Rust course into dist/rust/
```
Serve locally:
```bash
cd dist && python3 -m http.server 8000
```
Open `http://localhost:8000` for the course picker, or `http://localhost:8000/go/` for a specific course.
## Making your own course
### 1. Create a course directory
Create `courses//course.yaml`:
```yaml
course:
name: Your Course
slug: your-course
description: What this course covers.
storagePrefix: your-course
tracks:
- title: Basics
modules: [0, 1, 2]
modules:
- title: Getting Started
description: First steps.
hasExercises: false
projects: []
annotationTypes: {}
```
Most module fields are auto-derived at build time from the array index:
- `id` — array index (override with explicit `id` if modules aren't zero-indexed)
- `num` — string of `id`
- `file` — `"module" + id` (maps to `courses//content/lessons/module.md`)
- `hasExercises` — defaults to `true` (opt out with `hasExercises: false`)
- Track `id` — array index + 1
Only `title` is required per module. `storagePrefix` namespaces localStorage keys — change it per course so progress doesn't collide.
### 2. Write lessons in markdown
Create `courses//content/lessons/module0.md`:
```markdown
## Hello World
Here's how to get started.
\`\`\`python
print("hello")
\`\`\`
> **Tip:** This is a callout block.
```
### 3. Add exercises (optional)
Create `courses//content/exercises/module0-variants.yaml` and set `hasExercises: true` in course.yaml (or just omit it — `true` is the default):
```yaml
conceptLinks:
Variables: "#lesson-variables"
sharedContent: {}
warmups:
- id: w1
title: Declare a variable
concepts:
- Variables
difficulty: easy
variants:
- instructions: Declare an integer variable x with value 5.
starterCode: "// your code here"
solution: "x := 5"
tests:
- x == 5
challenges: []
```
### 4. Add flashcards (optional)
Create `courses//content/flashcards/flashcards.yaml`:
```yaml
"1":
- topic: Variables
q: What is a variable?
a: A named storage location in memory.
```
Keys are module IDs (as strings). Each module gets an array of cards.
### 5. Add real-world challenges (optional)
Create `courses//content/real-world-challenges/real-world-challenges.yaml`:
```yaml
challenges:
- id: my-challenge
title: "Challenge Title"
difficulty: 3
companies: [Acme, Contoso]
concepts: [HTTP, JSON]
source: "Where this problem comes from"
requirements: |
Build a thing that does stuff.
- **Feature 1** — description
- **Feature 2** — description
acceptanceCriteria:
- "It does X"
- "It handles Y"
hints:
- title: "Getting started"
content: |
Think about...
extensions:
- "Stretch goal 1"
```
Acceptance criteria render as interactive checkboxes. Progress is saved to localStorage.
### 6. Build and deploy
```bash
npm run build # builds all courses
node build.js your-slug # builds just your course
```
The sample course (`courses/sample/`) demonstrates every feature. The build auto-discovers courses by scanning `courses/` for directories containing a `course.yaml`.
## Included courses
| Course | Modules | Status |
|--------|---------|--------|
| **Go** | 19 modules + 4 projects | Complete |
| **C** | 18 modules + 4 projects | Scaffold (content coming soon) |
| **Kubernetes & Helm** | 22 modules + 8 projects | Hidden — content written, exercises not reviewed |
| **Rust** | 18 modules + 2 projects | Scaffold (content coming soon) |
## Deploy to GitHub Pages
1. Push to a GitHub repo
2. Go to **Settings > Pages**, set source to **GitHub Actions**
3. Create `.github/workflows/deploy.yml`:
```yaml
name: Deploy
on:
push:
branches: [main]
jobs:
build-deploy:
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install && npm run build
- uses: actions/upload-pages-artifact@v3
with:
path: dist
- id: deployment
uses: actions/deploy-pages@v4
```
## Themes
10 themes, all generic (no course-specific content):
| Theme | Style |
|-------|-------|
| Default (dark) | Built into style.css |
| Light | Clean light mode |
| Gruvbox Dark | Warm retro |
| Gruvbox Light | Warm retro light |
| Solarized Dark | Ethan Schoonover's classic |
| Solarized Light | Solarized light variant |
| Everforest Dark | Soft green tones |
| Everforest Light | Soft green light |
| OLED Dark | True black background |
| Persona 5 | Red/black stylized |
| Factorio Dark | Industrial amber |
## Project structure
```
vibe-learn/
├── build.js # Multi-course build script
├── package.json # marked, highlight.js, js-yaml, marked-highlight
│
├── courses/ # One directory per course
│ └── /
│ ├── course.yaml # Course manifest (tracks, modules, projects)
│ └── content/
│ ├── lessons/ # Markdown files (one per module/project)
│ ├── exercises/ # Exercise variants (YAML, per module)
│ ├── flashcards/ # Flashcard decks (YAML)
│ ├── algorithms/ # Algorithm problem sets (YAML)
│ ├── real-world-challenges/ # Challenge definitions (YAML)
│ └── assets/ # Static assets (favicon, images)
│
├── engine/ # Shared platform code (don't edit for content)
│ ├── js/ # Runtime JavaScript
│ ├── css/ # Shared styles
│ ├── themes/ # Color themes
│ ├── templates/ # HTML page templates
│ ├── partials/ # Reusable HTML fragments (head, nav, rating guide, etc.)
│ └── plugins/ # Feature plugins (self-contained)
│ ├── flashcards/
│ ├── daily-practice/
│ ├── analytics/
│ ├── algorithms/
│ └── real-world-challenges/
│
└── dist/ # Build output (gitignored)
├── index.html # Landing page (course picker)
└── / # Full course site per course
```
## Architecture
### Build pipeline
```
courses// engine/
├─ course.yaml ├─ templates/
├─ content/ │ ├─ landing.html
│ ├─ lessons/*.md │ ├─ index.html
│ ├─ exercises/ │ ├─ module.html
│ │ └─ *-variants.yaml │ └─ project.html
│ ├─ flashcards/ ├─ partials/
│ │ └─ flashcards.yaml │ ├─ head.html
│ ├─ algorithms/ │ ├─ dashboard-nav.html
│ │ └─ algorithms.yaml │ ├─ rating-guide.html
│ ├─ real-world-challenges/ │ ├─ session-nav.html
│ │ └─ *.yaml │ └─ session-complete.html
│ └─ assets/* ├─ js/*.js
│ ├─ css/style.css
│ ├─ themes/*.css
│ └─ plugins/
│ ├─ flashcards/
│ ├─ daily-practice/
│ ├─ analytics/
│ ├─ algorithms/
│ └─ real-world-challenges/
│ │
▼ ▼
┌─────────────────────────────────────────────────┐
│ build.js │
│ marked · highlight.js · js-yaml │
│ │
│ 1. Discover courses (scan courses/) │
│ 2. Discover plugins (scan engine/plugins/) │
│ 3. Filter active plugins per course │
│ 4. Parse markdown + YAML content │
│ 5. Render HTML with syntax highlighting │
│ 6. Build plugin data (transforms, templates) │
│ 7. Compile exercise YAML → JS data files │
│ 8. Bundle engine JS, CSS, themes, assets │
│ 9. Generate service worker manifest │
└──────────────────────┬──────────────────────────┘
▼
dist//
├─ index.html
├─ module*.html
├─ project*.html
├─ .html
├─ course-data.js
├─ concept-index.js
├─ data/module*-variants.js
├─ flashcard-data.js
├─ algorithm-data.js
├─ *.js (engine + plugins)
├─ style.css + themes/
└─ sw.js
```
### Runtime modules
```
┌─────────────────────────────────────────────────────────────┐
│ RUNTIME (browser) │
│ │
│ ┌─ Exercise Engine ──────────────────────────────────────┐ │
│ │ module-loader.js loads data/module*-variants.js │ │
│ │ exercise-core.js shuffle, variants, difficulty modes │ │
│ │ course.js renders exercises on module pages │ │
│ │ exercise-renderer.js hints, solutions, annotations │ │
│ └────────────────────────────┬───────────────────────────┘ │
│ │ rates │
│ ▼ │
│ ┌─ Practice & Review ───────────────────────────────────┐ │
│ │ │ │
│ │ session-engine.js shared session lifecycle │ │
│ │ ▲ ▲ │ │
│ │ │ │ │ │
│ │ daily- algorithms.js flashcard-engine.js │ │
│ │ practice.js (arena sessions, (flip/rate, │ │
│ │ (review, progression, SRS-due, │ │
│ │ discover, blind mode, random) │ │
│ │ weak, pattern drills) │ │
│ │ mixed) │ │
│ │ │ │ │ │ │
│ │ └─────────┼──────────────────┘ │ │
│ │ ▼ │ │
│ │ srs.js (SM-2) │ │
│ └────────────┬──────────────────────────────────────────┘ │
│ │ │
│ ┌─ UI Layer ─┼──────────────────────────────────────────┐ │
│ │ │ │ │
│ │ sidebar.js navigation + plugin links │ │
│ │ dashboard.js stats, resume, module progress │ │
│ │ analytics.js weak concepts, strength rankings │ │
│ │ theme.js 10 themes + pomodoro timer │ │
│ │ streaks.js streak tracking + activity heatmap │ │
│ │ progress.js per-exercise state tracking │ │
│ │ data-backup.js export/import all data │ │
│ │ real-world-challenges.js challenge cards + status │ │
│ └────────────┼──────────────────────────────────────────┘ │
│ ▼ │
│ ┌─ localStorage ────────────────────────────────────────┐ │
│ │ All keys prefixed with course storagePrefix │ │
│ │ │ │
│ │ -progress module completion flags │ │
│ │ -exercise-progress per-exercise state │ │
│ │ -srs SM-2 scheduler data │ │
│ │ -streaks current + longest streak │ │
│ │ -activity daily counts (heatmap) │ │
│ │ -personal-notes user notes per exercise │ │
│ │ -real-world-challenges status + criteria │ │
│ │ -algo-pattern-stats drill accuracy │ │
│ │ -last-module resume button target │ │
│ │ theme selected theme (shared) │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Course discovery
The build auto-discovers courses by scanning `courses/` for subdirectories that contain a `course.yaml`. Drop a new folder with a manifest and content, run `npm run build`, and it appears on the site. No registration step, no config file to update.
Each `course.yaml` declares a `storagePrefix` (e.g., `"go-course"`) that namespaces all localStorage keys, so multiple courses coexist in the same browser without collisions. The `course-config.js` runtime helper exposes `storageKey(suffix)` which every other module uses to read/write its data (e.g., `storageKey('srs')` → `"go-course-srs"`).
A course can set `hidden: true` in its `course.yaml` to be excluded from the landing page while still being built and accessible via direct URL.
### Plugin system
Feature pages (flashcards, daily practice, analytics, real-world challenges) are self-contained plugins in `engine/plugins//`. Each plugin is a directory containing:
```
engine/plugins//
├── manifest.json # Plugin configuration (name, label, sidebar info, content pattern)
├── .html # Page template
├── .js # Runtime JavaScript
└── build.js # Optional: build-time data transform
```
**Plugin discovery** — at build time, `build.js` scans `engine/plugins/` for directories with a `manifest.json`. Each manifest declares a `contentPattern` — a path relative to the course's `content/` directory. If the pattern exists for a course, the plugin is active for that course; otherwise it's skipped.
For example, the real-world-challenges plugin has `contentPattern: "real-world-challenges/real-world-challenges.yaml"`. The Go course has this file, so it gets the Real-World Challenges page. A course without that file simply doesn't get the page — no errors, no dead links.
Plugins that depend on exercises (daily-practice, analytics) use `contentPattern: "exercises/"` and activate for any course that has an exercises directory.
**Adding a plugin:**
1. Create `engine/plugins//manifest.json`:
```json
{
"name": "my-plugin",
"label": "My Plugin",
"shortLabel": "MP",
"sidebarColor": "var(--cyan)",
"sidebarOrder": 5,
"template": "my-plugin.html",
"scripts": ["my-plugin.js"],
"dataFile": "my-plugin-data.js",
"dataGlobal": "MyPluginData",
"contentPattern": "my-plugin/my-plugin.yaml",
"backupKey": "my-plugin",
"dashboardScripts": []
}
```
2. Create the HTML template and JS file in the same directory
3. Optionally create a `build.js` that exports `{ transform(data, context) }` for build-time data processing (e.g., rendering markdown to HTML)
4. Add content at `courses//content/my-plugin/my-plugin.yaml` for each course that should have the feature
5. Run `npm run build` — the plugin is auto-discovered and built
The sidebar, dashboard nav pills, data backup, and `CourseConfig.plugins` array are all driven by active plugin manifests — no hardcoded feature lists to update.
### Build steps
`build.js` is the entire build system — a single Node script with four dependencies (marked, marked-highlight, highlight.js, js-yaml). No bundler, no framework, no transpilation. It runs once and produces a fully self-contained static site per course.
1. **Discover courses** — scans `courses/` for directories containing a `course.yaml`
2. **Discover plugins** — scans `engine/plugins/` for directories containing a `manifest.json`, sorted by `sidebarOrder`
3. **Filter active plugins** — for each course, checks which plugins have matching content (e.g., a course with `content/real-world-challenges/real-world-challenges.yaml` gets the real-world-challenges plugin; a course without it doesn't)
4. **Parse content** — reads markdown lessons, YAML exercises (`module*-variants.yaml`), and YAML flashcards (`flashcards.yaml`)
5. **Render markdown** — converts lessons to HTML with syntax-highlighted code blocks via marked + highlight.js. A post-processor detects labeled code blocks (e.g., `*Python*` above a fenced block) and wraps consecutive labeled blocks in side-by-side comparison divs
6. **Compute sidebar order** — builds an ordered page list from modules, with projects interleaved after their `afterModule` position, followed by active plugin pages. This powers both the sidebar navigation and prev/next buttons
7. **Process partials** — loads reusable HTML fragments from `engine/partials/` (head, dashboard-nav, rating-guide, session-nav, session-complete). Plugin templates use `{{> partial-name}}` directives to include shared UI blocks, with placeholders like `{{PREFIX}}` replaced per-plugin
8. **Generate HTML** — injects rendered content into templates from `engine/templates/` (landing, dashboard, module, project) and active plugin templates. Modules with exercises get jump-link boxes injected after the Exercises heading
9. **Build plugins** — for each active plugin: loads YAML content, runs optional build-time transforms (e.g., rendering markdown in challenge requirements), generates data JS files, processes HTML templates with partial expansion, copies plugin JS to dist
10. **Compile data to JS** — converts exercise YAML to `data/module*-variants.js` (sets `window.moduleData`). Generates `concept-index.js` mapping exercise keys to concept names (includes both module exercises and algorithm problems). Also generates `course-data.js` (sets `window.CourseConfig` with all module metadata, tracks, computed sidebar, annotation types, and active plugin list)
11. **Bundle** — copies engine JS files, CSS, theme files, and course assets into `dist//`
12. **Service worker** — generates `sw.js` with a manifest of every file in the build, using a cache-first strategy for instant loads and offline support
13. **Landing page** — generates `dist/index.html` with a card for each non-hidden course
The output is plain HTML/CSS/JS. Host it anywhere — GitHub Pages, Netlify, S3, a USB stick, `python3 -m http.server`.
### Runtime module reference
All JavaScript runs in the browser with no framework. Scripts communicate through `window` globals and localStorage. There are 12 engine JS files plus build-generated data files per course:
**Build-generated data (per course):**
| File | What it does |
|------|-------------|
| `course-data.js` | Sets `window.CourseConfig` — module metadata, tracks, sidebar page order, annotation types, active plugins list, which modules have exercises |
| `data/module*-variants.js` | Sets `window.moduleData` — exercise variants (warmups/challenges), concept links, shared content for that module |
| `flashcard-data.js` | Sets `window.FlashcardData` — flashcard decks keyed by module ID (generated by flashcards plugin) |
| `algorithm-data.js` | Sets `window.AlgorithmData` — algorithm categories, problems, and variants with solutions and test cases (generated by algorithms plugin) |
| `concept-index.js` | Sets `window.ConceptIndex` — maps exercise base keys to concept names (e.g., `m2_challenge_1` → `"Pointer Arithmetic"`, `algo_arrays-hashing_two-sum` → `"Hash Map Complement"`). Used by analytics for per-concept strength breakdowns |
| `real-world-challenge-data.js` | Sets `window.RealWorldChallengeData` — challenge definitions with rendered requirements HTML (generated by real-world-challenges plugin) |
**Engine JS (copied from `engine/js/`):**
| File | What it does |
|------|-------------|
| `course-config.js` | Reads `window.CourseConfig` and exposes `CourseConfigHelper` — provides `storageKey(suffix)` for namespaced localStorage keys, module lookups, and course metadata access |
| `module-loader.js` | On lesson pages, dynamically loads `data/module-variants.js` via script injection, then fires a `moduleDataLoaded` custom event |
| `exercise-core.js` | Shared exercise logic library — pure functions for shuffle/variant selection (5 difficulty modes), UI components (thinking timer, difficulty selector, shuffle button, concept filter), and shared CSS injection. Used by both `course.js` and the algorithms plugin |
| `course.js` | Module exercise renderer. Listens for `moduleDataLoaded`, delegates to `exercise-core.js` for variant selection and UI components, renders prompts/hints/solutions with self-rating buttons |
| `exercise-renderer.js` | Shared rendering utilities — hints (progressive disclosure), solutions (syntax-highlighted with annotations), difficulty stars, test cases, and personal notes textarea |
| `session-engine.js` | Shared session lifecycle for practice plugins. Provides queue building (SRS review/weakest/mixed), session state management (start/next/skip/advance/finish), progress bar rendering, results screen, and config button wiring. Used by both `algorithms.js` and `daily-practice.js` |
| `progress.js` | Tracks per-exercise state in localStorage: attempted/completed, hints used, solution viewed, self-rating (got it / struggled / needed solution), last attempted timestamp |
| `srs.js` | SM-2 spaced repetition scheduler. Exposes `window.SRS` — records quality scores, computes ease factor + interval + next review date. Used by exercises, flashcards, and daily practice |
| `sidebar.js` | Navigation sidebar. Reads `CourseConfig.plugins` to dynamically render plugin page links alongside module/project links. Collapsible on desktop, overlay on mobile |
| `theme.js` | Theme switcher, Pomodoro session timer, and focus mode. Sets `data-theme` on the document and loads theme CSS. Also manages session timer with configurable presets (25/5, 50/10, 90/20), sound notifications, and a floating timer widget. Includes safe localStorage wrappers for environments where storage may be unavailable |
| `streaks.js` | Streak tracking and GitHub-style activity heatmap. Exposes `window.Streaks`. Increments on any exercise/flashcard activity, resets if a day is skipped, renders a 52-week calendar grid |
| `dashboard.js` | Dashboard page logic. Displays modules completed, exercises done, progress %, items due for review, current/longest streaks, and the activity heatmap. Resume button tracks last visited module |
| `data-backup.js` | Export all course localStorage keys as a single JSON file with metadata. Import from a previous export. Reads backup keys from `CourseConfig.plugins`. Nuke button to wipe all data |
**Plugin JS (copied from `engine/plugins//`):**
| File | What it does |
|------|-------------|
| `flashcard-engine.js` | Flashcard session manager. Builds decks filtered by module, supports random and SRS-due ordering, handles flip animation and rate interaction |
| `daily-practice.js` | Builds exercise queues from SRS data across all modules. Four modes: review (due items), discover (random new), weak (lowest ease factor), mixed (due + weak). Dynamically loads module variant data as needed. Delegates session lifecycle to `session-engine.js` |
| `algorithms.js` | Session-based algorithm practice. Builds queues from AlgorithmData across 10 categories with discover/review/weakest/mixed modes. SRS key format: `algo_{categoryId}_{problemId}_{variantId}`. Three differentiating features: **structured progression** (category cards with mastery bars, per-tier unlock gating at 70% thresholds), **blind mode** (hides hints/solutions, self-grade then reveal), and **pattern recognition drills** (multiple-choice concept identification with distractor generation and accuracy tracking). Delegates session lifecycle to `session-engine.js` |
| `analytics.js` | Weak concept report. Groups SRS data by module (including algorithm categories as pseudo-module "Algorithms"), computes average ease factor, classifies strength (strong/good/moderate/weak/too early), shows per-concept breakdown with algorithm concepts from ConceptIndex, lists weakest individual exercises. Formats `algo_` keys for display and links to `algorithms.html` |
| `real-world-challenges.js` | Renders challenge cards with difficulty, company/concept tags, source attribution, collapsible requirements/hints/acceptance criteria (interactive checkboxes), and status tracking (not-started / in-progress / completed) |
### Exercise flow
```
User opens module page
-> module-loader.js injects for data/module<N>-variants.js
-> script sets window.moduleData, loader fires "moduleDataLoaded" event
-> course.js picks up the event, renders warmups then challenges
-> each exercise: prompt, difficulty stars, concept link to lesson section
-> thinking timer (configurable) locks hints/solution for N seconds
-> user expands hints (progressive: think about it -> hint -> pattern)
-> user expands solution (syntax-highlighted code + annotations)
-> user clicks self-rating: Got it / Struggled / Needed solution
-> progress.js records exercise state
-> srs.js computes next review date via SM-2
-> streaks.js increments today's activity count
```
### Spaced repetition (SM-2)
Every exercise and flashcard gets an SRS record with: `easeFactor` (1.3–5.0), `interval` (days until next review), `repetitions`, and `nextReview` date.
Self-ratings map to SM-2 quality scores:
- **Got it** (no hints) → quality 5
- **Struggled** (no hints) → quality 4
- **Used hints** (not solution) → quality 3
- **Viewed solution** → quality 2
- **Needed solution** (self-reported) → quality 1
Items with quality < 3 reset to interval 1. The ease factor adjusts after every rating — items you struggle with come back sooner, items you know well space out further.
### localStorage schema
All keys are prefixed with the course's `storagePrefix` (e.g., `go-course-progress`). This is how multiple courses coexist without data collisions.
| Key suffix | Contents |
|------------|----------|
| `progress` | Module completion flags and last-studied timestamps |
| `exercise-progress` | Per-exercise: status, hints used, solution viewed, self-rating, last attempted |
| `srs` | Per-item SM-2 state: easeFactor, interval, repetitions, nextReview |
| `streaks` | Current streak, longest streak, last active date |
| `activity` | Daily activity counts keyed by `YYYY-MM-DD` |
| `personal-notes` | User notes per exercise (freeform text) |
| `real-world-challenges` | Per-challenge: status, startedAt, completedAt, checkedCriteria (acceptance criteria checkboxes) |
| `algo-pattern-stats` | Pattern recognition drill lifetime stats: total attempts, correct count, per-category breakdown |
| `last-module` | Last visited module ID (for the resume button) |
| `theme` | Selected theme name (shared across courses) |
### Service worker
Generated at build time with a versioned cache name and a manifest of every file in the build output. Uses a cache-first strategy: serves assets instantly from cache, falling back to network on cache miss. Freshness is handled by the cache version — each build produces a new `CACHE_NAME`, so deploying new code triggers a full cache refresh. On activation, deletes all previous cache versions and claims all open tabs.
## Contributing
Since I haven't audited the code, I will most likely not accept pull requests unless it's something simple (typo fix, small bug fix, etc). Nothing personal — I just can't review code I don't fully understand myself. You're welcome to fork it and make a billion dollars though.
## Disclaimer
This is a fun experiment, not production software. The AI probably introduced bugs, bad patterns, or subtle issues I haven't noticed because I haven't read the code. If you learn something from the course content, great. If you find something broken, that's the vibe-coding experience.
## License
Do whatever you want with it.