{"id":43512490,"url":"https://github.com/jmilne22/vibe-learn","last_synced_at":"2026-02-06T16:02:55.764Z","repository":{"id":335526141,"uuid":"1146144692","full_name":"jmilne22/vibe-learn","owner":"jmilne22","description":"Self-hosted static course platform — markdown lessons, exercises, flashcards, spaced repetition, 10 themes, zero backend","archived":false,"fork":false,"pushed_at":"2026-02-03T22:25:31.000Z","size":1631,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-04T23:57:46.601Z","etag":null,"topics":["courses","education","exercises","flashcards","javascript","learning-platform","markdown","no-backend","offline-first","self-hosted","spaced-repetition","srs","static-site"],"latest_commit_sha":null,"homepage":"https://vibe-learn.ai/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jmilne22.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-30T17:21:57.000Z","updated_at":"2026-02-03T22:25:35.000Z","dependencies_parsed_at":"2026-02-04T14:00:52.169Z","dependency_job_id":null,"html_url":"https://github.com/jmilne22/vibe-learn","commit_stats":null,"previous_names":["jmilne22/vibe-learn"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jmilne22/vibe-learn","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmilne22%2Fvibe-learn","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmilne22%2Fvibe-learn/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmilne22%2Fvibe-learn/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmilne22%2Fvibe-learn/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jmilne22","download_url":"https://codeload.github.com/jmilne22/vibe-learn/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmilne22%2Fvibe-learn/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29124793,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-05T14:05:12.718Z","status":"ssl_error","status_checked_at":"2026-02-05T14:03:53.078Z","response_time":65,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["courses","education","exercises","flashcards","javascript","learning-platform","markdown","no-backend","offline-first","self-hosted","spaced-repetition","srs","static-site"],"created_at":"2026-02-03T13:13:07.764Z","updated_at":"2026-02-05T15:00:29.404Z","avatar_url":"https://github.com/jmilne22.png","language":"JavaScript","readme":"# vibe-learn\n\nA 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.\n\n**Fork it, replace the content, build, deploy.** The engine is generic. The courses are just data.\n\n\u003e **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.\n\u003e\n\u003e **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.\n\n## Platform features\n\n### Content authoring\n\n| What you write | What you get |\n|---|---|\n| Markdown lessons | Syntax-highlighted pages with side-by-side code comparisons and callout blocks |\n| Exercise YAML (warmups + challenges) | Multiple variants per exercise, shuffle, 5 difficulty modes, concept filters |\n| Flashcard YAML | Flip/rate sessions with keyboard shortcuts, filtered by module |\n| Algorithm YAML | Session-based drills with progression, blind mode, and pattern recognition |\n| Challenge YAML | Open-ended problems with requirements, acceptance criteria checkboxes, company tags |\n\n### Learning engine\n\n- **Spaced repetition (SM-2)** — exercises and flashcards are scheduled based on self-ratings\n- **Daily practice** — review due items, drill weak spots, discover new exercises, or mix modes\n- **Thinking timer** — configurable countdown that locks hints/solutions so you attempt the problem first\n- **Self-rating** — \"Got it\", \"Struggled\", or \"Needed solution\" feeds the SRS scheduler\n- **Hints and solutions** — progressive disclosure with annotations (tips, gotchas, idioms, complexity)\n\n### Tracking and analytics\n\n- **Analytics dashboard** — weak concept report, module strength rankings, per-concept breakdown, top 10 weakest exercises\n- **Streak tracking** and GitHub-style activity heatmap\n- **Personal notes** on any exercise, exportable as markdown\n- **Data export/import** — all progress stored in localStorage, downloadable as JSON\n\n### Platform\n\n- **Plugin system** — features are self-contained plugins, auto-discovered at build time\n- **Multi-course** — one install hosts unlimited courses, each with isolated progress\n- **10 themes** — gruvbox, solarized, everforest, OLED dark, persona 5, factorio, and more\n- **Pomodoro timer** — session presets (25/5, 50/10, 90/20) with break reminders\n- **Offline support** via service worker\n- **Zero backend** — pure static site, host on GitHub Pages, Netlify, S3, a USB stick\n\n## Quick start\n\n```bash\ngit clone \u003cthis-repo\u003e\ncd vibe-learn\nnpm install        # installs marked, highlight.js, js-yaml\nnpm run build      # builds all courses + landing page into dist/\n```\n\nBuild a single course:\n\n```bash\nnpm run build:go     # builds only the Go course into dist/go/\nnpm run build:rust   # builds only the Rust course into dist/rust/\n```\n\nServe locally:\n\n```bash\ncd dist \u0026\u0026 python3 -m http.server 8000\n```\n\nOpen `http://localhost:8000` for the course picker, or `http://localhost:8000/go/` for a specific course.\n\n## Making your own course\n\n### 1. Create a course directory\n\nCreate `courses/\u003cyour-slug\u003e/course.yaml`:\n\n```yaml\ncourse:\n  name: Your Course\n  slug: your-course\n  description: What this course covers.\n  storagePrefix: your-course\n\ntracks:\n  - title: Basics\n    modules: [0, 1, 2]\n\nmodules:\n  - title: Getting Started\n    description: First steps.\n    hasExercises: false\n\nprojects: []\nannotationTypes: {}\n```\n\nMost module fields are auto-derived at build time from the array index:\n- `id` — array index (override with explicit `id` if modules aren't zero-indexed)\n- `num` — string of `id`\n- `file` — `\"module\" + id` (maps to `courses/\u003cslug\u003e/content/lessons/module\u003cid\u003e.md`)\n- `hasExercises` — defaults to `true` (opt out with `hasExercises: false`)\n- Track `id` — array index + 1\n\nOnly `title` is required per module. `storagePrefix` namespaces localStorage keys — change it per course so progress doesn't collide.\n\n### 2. Write lessons in markdown\n\nCreate `courses/\u003cyour-slug\u003e/content/lessons/module0.md`:\n\n```markdown\n## Hello World\n\nHere's how to get started.\n\n\\`\\`\\`python\nprint(\"hello\")\n\\`\\`\\`\n\n\u003e **Tip:** This is a callout block.\n```\n\n### 3. Add exercises (optional)\n\nCreate `courses/\u003cyour-slug\u003e/content/exercises/module0-variants.yaml` and set `hasExercises: true` in course.yaml (or just omit it — `true` is the default):\n\n```yaml\nconceptLinks:\n  Variables: \"#lesson-variables\"\nsharedContent: {}\nwarmups:\n  - id: w1\n    title: Declare a variable\n    concepts:\n      - Variables\n    difficulty: easy\n    variants:\n      - instructions: Declare an integer variable x with value 5.\n        starterCode: \"// your code here\"\n        solution: \"x := 5\"\n        tests:\n          - x == 5\nchallenges: []\n```\n\n### 4. Add flashcards (optional)\n\nCreate `courses/\u003cyour-slug\u003e/content/flashcards/flashcards.yaml`:\n\n```yaml\n\"1\":\n  - topic: Variables\n    q: What is a variable?\n    a: A named storage location in memory.\n```\n\nKeys are module IDs (as strings). Each module gets an array of cards.\n\n### 5. Add real-world challenges (optional)\n\nCreate `courses/\u003cyour-slug\u003e/content/real-world-challenges/real-world-challenges.yaml`:\n\n```yaml\nchallenges:\n  - id: my-challenge\n    title: \"Challenge Title\"\n    difficulty: 3\n    companies: [Acme, Contoso]\n    concepts: [HTTP, JSON]\n    source: \"Where this problem comes from\"\n    requirements: |\n      Build a thing that does stuff.\n\n      - **Feature 1** — description\n      - **Feature 2** — description\n    acceptanceCriteria:\n      - \"It does X\"\n      - \"It handles Y\"\n    hints:\n      - title: \"Getting started\"\n        content: |\n          Think about...\n    extensions:\n      - \"Stretch goal 1\"\n```\n\nAcceptance criteria render as interactive checkboxes. Progress is saved to localStorage.\n\n### 6. Build and deploy\n\n```bash\nnpm run build              # builds all courses\nnode build.js your-slug    # builds just your course\n```\n\nThe sample course (`courses/sample/`) demonstrates every feature. The build auto-discovers courses by scanning `courses/` for directories containing a `course.yaml`.\n\n## Included courses\n\n| Course | Modules | Status |\n|--------|---------|--------|\n| **Go** | 19 modules + 4 projects | Complete |\n| **C** | 18 modules + 4 projects | Scaffold (content coming soon) |\n| **Kubernetes \u0026 Helm** | 22 modules + 8 projects | Hidden — content written, exercises not reviewed |\n| **Rust** | 18 modules + 2 projects | Scaffold (content coming soon) |\n\n## Deploy to GitHub Pages\n\n1. Push to a GitHub repo\n2. Go to **Settings \u003e Pages**, set source to **GitHub Actions**\n3. Create `.github/workflows/deploy.yml`:\n\n```yaml\nname: Deploy\non:\n  push:\n    branches: [main]\njobs:\n  build-deploy:\n    runs-on: ubuntu-latest\n    permissions:\n      pages: write\n      id-token: write\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n      - run: npm install \u0026\u0026 npm run build\n      - uses: actions/upload-pages-artifact@v3\n        with:\n          path: dist\n      - id: deployment\n        uses: actions/deploy-pages@v4\n```\n\n## Themes\n\n10 themes, all generic (no course-specific content):\n\n| Theme | Style |\n|-------|-------|\n| Default (dark) | Built into style.css |\n| Light | Clean light mode |\n| Gruvbox Dark | Warm retro |\n| Gruvbox Light | Warm retro light |\n| Solarized Dark | Ethan Schoonover's classic |\n| Solarized Light | Solarized light variant |\n| Everforest Dark | Soft green tones |\n| Everforest Light | Soft green light |\n| OLED Dark | True black background |\n| Persona 5 | Red/black stylized |\n| Factorio Dark | Industrial amber |\n\n## Project structure\n\n```\nvibe-learn/\n├── build.js                 # Multi-course build script\n├── package.json             # marked, highlight.js, js-yaml, marked-highlight\n│\n├── courses/                 # One directory per course\n│   └── \u003cslug\u003e/\n│       ├── course.yaml      # Course manifest (tracks, modules, projects)\n│       └── content/\n│           ├── lessons/     # Markdown files (one per module/project)\n│           ├── exercises/   # Exercise variants (YAML, per module)\n│           ├── flashcards/  # Flashcard decks (YAML)\n│           ├── algorithms/  # Algorithm problem sets (YAML)\n│           ├── real-world-challenges/  # Challenge definitions (YAML)\n│           └── assets/      # Static assets (favicon, images)\n│\n├── engine/                  # Shared platform code (don't edit for content)\n│   ├── js/                  # Runtime JavaScript\n│   ├── css/                 # Shared styles\n│   ├── themes/              # Color themes\n│   ├── templates/           # HTML page templates\n│   ├── partials/            # Reusable HTML fragments (head, nav, rating guide, etc.)\n│   └── plugins/             # Feature plugins (self-contained)\n│       ├── flashcards/\n│       ├── daily-practice/\n│       ├── analytics/\n│       ├── algorithms/\n│       └── real-world-challenges/\n│\n└── dist/                    # Build output (gitignored)\n    ├── index.html           # Landing page (course picker)\n    └── \u003cslug\u003e/              # Full course site per course\n```\n\n## Architecture\n\n### Build pipeline\n\n```\n courses/\u003cslug\u003e/                engine/\n ├─ course.yaml                 ├─ templates/\n ├─ content/                    │  ├─ landing.html\n │  ├─ lessons/*.md             │  ├─ index.html\n │  ├─ exercises/               │  ├─ module.html\n │  │  └─ *-variants.yaml      │  └─ project.html\n │  ├─ flashcards/              ├─ partials/\n │  │  └─ flashcards.yaml      │  ├─ head.html\n │  ├─ algorithms/              │  ├─ dashboard-nav.html\n │  │  └─ algorithms.yaml      │  ├─ rating-guide.html\n │  ├─ real-world-challenges/   │  ├─ session-nav.html\n │  │  └─ *.yaml                │  └─ session-complete.html\n │  └─ assets/*                 ├─ js/*.js\n        │                       ├─ css/style.css\n        │                       ├─ themes/*.css\n        │                       └─ plugins/\n        │                          ├─ flashcards/\n        │                          ├─ daily-practice/\n        │                          ├─ analytics/\n        │                          ├─ algorithms/\n        │                          └─ real-world-challenges/\n        │                                   │\n        ▼                                   ▼\n ┌─────────────────────────────────────────────────┐\n │                    build.js                      │\n │         marked · highlight.js · js-yaml          │\n │                                                  │\n │  1. Discover courses (scan courses/)             │\n │  2. Discover plugins (scan engine/plugins/)      │\n │  3. Filter active plugins per course             │\n │  4. Parse markdown + YAML content                │\n │  5. Render HTML with syntax highlighting         │\n │  6. Build plugin data (transforms, templates)    │\n │  7. Compile exercise YAML → JS data files        │\n │  8. Bundle engine JS, CSS, themes, assets        │\n │  9. Generate service worker manifest             │\n └──────────────────────┬──────────────────────────┘\n                        ▼\n              dist/\u003cslug\u003e/\n              ├─ index.html\n              ├─ module*.html\n              ├─ project*.html\n              ├─ \u003cplugin\u003e.html\n              ├─ course-data.js\n              ├─ concept-index.js\n              ├─ data/module*-variants.js\n              ├─ flashcard-data.js\n              ├─ algorithm-data.js\n              ├─ *.js (engine + plugins)\n              ├─ style.css + themes/\n              └─ sw.js\n```\n\n### Runtime modules\n\n```\n ┌─────────────────────────────────────────────────────────────┐\n │                     RUNTIME (browser)                       │\n │                                                             │\n │  ┌─ Exercise Engine ──────────────────────────────────────┐ │\n │  │  module-loader.js  loads data/module*-variants.js      │ │\n │  │  exercise-core.js  shuffle, variants, difficulty modes │ │\n │  │  course.js         renders exercises on module pages   │ │\n │  │  exercise-renderer.js  hints, solutions, annotations   │ │\n │  └────────────────────────────┬───────────────────────────┘ │\n │                               │ rates                       │\n │                               ▼                             │\n │  ┌─ Practice \u0026 Review ───────────────────────────────────┐  │\n │  │                                                       │  │\n │  │  session-engine.js   shared session lifecycle         │  │\n │  │       ▲         ▲                                     │  │\n │  │       │         │                                     │  │\n │  │  daily-      algorithms.js   flashcard-engine.js      │  │\n │  │  practice.js  (arena sessions,  (flip/rate,           │  │\n │  │  (review,      progression,      SRS-due,             │  │\n │  │   discover,    blind mode,       random)              │  │\n │  │   weak,        pattern drills)                        │  │\n │  │   mixed)                                              │  │\n │  │       │         │                  │                  │  │\n │  │       └─────────┼──────────────────┘                  │  │\n │  │                 ▼                                     │  │\n │  │           srs.js (SM-2)                               │  │\n │  └────────────┬──────────────────────────────────────────┘  │\n │               │                                             │\n │  ┌─ UI Layer ─┼──────────────────────────────────────────┐  │\n │  │            │                                          │  │\n │  │  sidebar.js       navigation + plugin links           │  │\n │  │  dashboard.js     stats, resume, module progress      │  │\n │  │  analytics.js     weak concepts, strength rankings    │  │\n │  │  theme.js         10 themes + pomodoro timer          │  │\n │  │  streaks.js       streak tracking + activity heatmap  │  │\n │  │  progress.js      per-exercise state tracking         │  │\n │  │  data-backup.js   export/import all data              │  │\n │  │  real-world-challenges.js  challenge cards + status   │  │\n │  └────────────┼──────────────────────────────────────────┘  │\n │               ▼                                             │\n │  ┌─ localStorage ────────────────────────────────────────┐  │\n │  │  All keys prefixed with course storagePrefix          │  │\n │  │                                                       │  │\n │  │  \u003cprefix\u003e-progress          module completion flags   │  │\n │  │  \u003cprefix\u003e-exercise-progress per-exercise state        │  │\n │  │  \u003cprefix\u003e-srs               SM-2 scheduler data      │  │\n │  │  \u003cprefix\u003e-streaks           current + longest streak  │  │\n │  │  \u003cprefix\u003e-activity          daily counts (heatmap)    │  │\n │  │  \u003cprefix\u003e-personal-notes    user notes per exercise   │  │\n │  │  \u003cprefix\u003e-real-world-challenges  status + criteria    │  │\n │  │  \u003cprefix\u003e-algo-pattern-stats     drill accuracy       │  │\n │  │  \u003cprefix\u003e-last-module       resume button target      │  │\n │  │  theme                      selected theme (shared)   │  │\n │  └───────────────────────────────────────────────────────┘  │\n └─────────────────────────────────────────────────────────────┘\n```\n\n### Course discovery\n\nThe 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.\n\nEach `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\"`).\n\nA 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.\n\n### Plugin system\n\nFeature pages (flashcards, daily practice, analytics, real-world challenges) are self-contained plugins in `engine/plugins/\u003cname\u003e/`. Each plugin is a directory containing:\n\n```\nengine/plugins/\u003cname\u003e/\n├── manifest.json     # Plugin configuration (name, label, sidebar info, content pattern)\n├── \u003cname\u003e.html       # Page template\n├── \u003cname\u003e.js         # Runtime JavaScript\n└── build.js          # Optional: build-time data transform\n```\n\n**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.\n\nFor 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.\n\nPlugins that depend on exercises (daily-practice, analytics) use `contentPattern: \"exercises/\"` and activate for any course that has an exercises directory.\n\n**Adding a plugin:**\n\n1. Create `engine/plugins/\u003cname\u003e/manifest.json`:\n\n```json\n{\n  \"name\": \"my-plugin\",\n  \"label\": \"My Plugin\",\n  \"shortLabel\": \"MP\",\n  \"sidebarColor\": \"var(--cyan)\",\n  \"sidebarOrder\": 5,\n  \"template\": \"my-plugin.html\",\n  \"scripts\": [\"my-plugin.js\"],\n  \"dataFile\": \"my-plugin-data.js\",\n  \"dataGlobal\": \"MyPluginData\",\n  \"contentPattern\": \"my-plugin/my-plugin.yaml\",\n  \"backupKey\": \"my-plugin\",\n  \"dashboardScripts\": []\n}\n```\n\n2. Create the HTML template and JS file in the same directory\n3. Optionally create a `build.js` that exports `{ transform(data, context) }` for build-time data processing (e.g., rendering markdown to HTML)\n4. Add content at `courses/\u003cslug\u003e/content/my-plugin/my-plugin.yaml` for each course that should have the feature\n5. Run `npm run build` — the plugin is auto-discovered and built\n\nThe sidebar, dashboard nav pills, data backup, and `CourseConfig.plugins` array are all driven by active plugin manifests — no hardcoded feature lists to update.\n\n### Build steps\n\n`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.\n\n1. **Discover courses** — scans `courses/` for directories containing a `course.yaml`\n2. **Discover plugins** — scans `engine/plugins/` for directories containing a `manifest.json`, sorted by `sidebarOrder`\n3. **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)\n4. **Parse content** — reads markdown lessons, YAML exercises (`module*-variants.yaml`), and YAML flashcards (`flashcards.yaml`)\n5. **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\n6. **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\n7. **Process partials** — loads reusable HTML fragments from `engine/partials/` (head, dashboard-nav, rating-guide, session-nav, session-complete). Plugin templates use `{{\u003e partial-name}}` directives to include shared UI blocks, with placeholders like `{{PREFIX}}` replaced per-plugin\n8. **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\n9. **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\n10. **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)\n11. **Bundle** — copies engine JS files, CSS, theme files, and course assets into `dist/\u003cslug\u003e/`\n12. **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\n13. **Landing page** — generates `dist/index.html` with a card for each non-hidden course\n\nThe output is plain HTML/CSS/JS. Host it anywhere — GitHub Pages, Netlify, S3, a USB stick, `python3 -m http.server`.\n\n### Runtime module reference\n\nAll 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:\n\n**Build-generated data (per course):**\n\n| File | What it does |\n|------|-------------|\n| `course-data.js` | Sets `window.CourseConfig` — module metadata, tracks, sidebar page order, annotation types, active plugins list, which modules have exercises |\n| `data/module*-variants.js` | Sets `window.moduleData` — exercise variants (warmups/challenges), concept links, shared content for that module |\n| `flashcard-data.js` | Sets `window.FlashcardData` — flashcard decks keyed by module ID (generated by flashcards plugin) |\n| `algorithm-data.js` | Sets `window.AlgorithmData` — algorithm categories, problems, and variants with solutions and test cases (generated by algorithms plugin) |\n| `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 |\n| `real-world-challenge-data.js` | Sets `window.RealWorldChallengeData` — challenge definitions with rendered requirements HTML (generated by real-world-challenges plugin) |\n\n**Engine JS (copied from `engine/js/`):**\n\n| File | What it does |\n|------|-------------|\n| `course-config.js` | Reads `window.CourseConfig` and exposes `CourseConfigHelper` — provides `storageKey(suffix)` for namespaced localStorage keys, module lookups, and course metadata access |\n| `module-loader.js` | On lesson pages, dynamically loads `data/module\u003cN\u003e-variants.js` via script injection, then fires a `moduleDataLoaded` custom event |\n| `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 |\n| `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 |\n| `exercise-renderer.js` | Shared rendering utilities — hints (progressive disclosure), solutions (syntax-highlighted with annotations), difficulty stars, test cases, and personal notes textarea |\n| `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` |\n| `progress.js` | Tracks per-exercise state in localStorage: attempted/completed, hints used, solution viewed, self-rating (got it / struggled / needed solution), last attempted timestamp |\n| `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 |\n| `sidebar.js` | Navigation sidebar. Reads `CourseConfig.plugins` to dynamically render plugin page links alongside module/project links. Collapsible on desktop, overlay on mobile |\n| `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 |\n| `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 |\n| `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 |\n| `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 |\n\n**Plugin JS (copied from `engine/plugins/\u003cname\u003e/`):**\n\n| File | What it does |\n|------|-------------|\n| `flashcard-engine.js` | Flashcard session manager. Builds decks filtered by module, supports random and SRS-due ordering, handles flip animation and rate interaction |\n| `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` |\n| `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` |\n| `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` |\n| `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) |\n\n### Exercise flow\n\n```\nUser opens module page\n  -\u003e module-loader.js injects \u003cscript\u003e for data/module\u003cN\u003e-variants.js\n  -\u003e script sets window.moduleData, loader fires \"moduleDataLoaded\" event\n  -\u003e course.js picks up the event, renders warmups then challenges\n     -\u003e each exercise: prompt, difficulty stars, concept link to lesson section\n     -\u003e thinking timer (configurable) locks hints/solution for N seconds\n     -\u003e user expands hints (progressive: think about it -\u003e hint -\u003e pattern)\n     -\u003e user expands solution (syntax-highlighted code + annotations)\n     -\u003e user clicks self-rating: Got it / Struggled / Needed solution\n        -\u003e progress.js records exercise state\n        -\u003e srs.js computes next review date via SM-2\n        -\u003e streaks.js increments today's activity count\n```\n\n### Spaced repetition (SM-2)\n\nEvery exercise and flashcard gets an SRS record with: `easeFactor` (1.3–5.0), `interval` (days until next review), `repetitions`, and `nextReview` date.\n\nSelf-ratings map to SM-2 quality scores:\n- **Got it** (no hints) → quality 5\n- **Struggled** (no hints) → quality 4\n- **Used hints** (not solution) → quality 3\n- **Viewed solution** → quality 2\n- **Needed solution** (self-reported) → quality 1\n\nItems with quality \u003c 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.\n\n### localStorage schema\n\nAll keys are prefixed with the course's `storagePrefix` (e.g., `go-course-progress`). This is how multiple courses coexist without data collisions.\n\n| Key suffix | Contents |\n|------------|----------|\n| `progress` | Module completion flags and last-studied timestamps |\n| `exercise-progress` | Per-exercise: status, hints used, solution viewed, self-rating, last attempted |\n| `srs` | Per-item SM-2 state: easeFactor, interval, repetitions, nextReview |\n| `streaks` | Current streak, longest streak, last active date |\n| `activity` | Daily activity counts keyed by `YYYY-MM-DD` |\n| `personal-notes` | User notes per exercise (freeform text) |\n| `real-world-challenges` | Per-challenge: status, startedAt, completedAt, checkedCriteria (acceptance criteria checkboxes) |\n| `algo-pattern-stats` | Pattern recognition drill lifetime stats: total attempts, correct count, per-category breakdown |\n| `last-module` | Last visited module ID (for the resume button) |\n| `theme` | Selected theme name (shared across courses) |\n\n### Service worker\n\nGenerated 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.\n\n## Contributing\n\nSince 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.\n\n## Disclaimer\n\nThis 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.\n\n## License\n\nDo whatever you want with it.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmilne22%2Fvibe-learn","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjmilne22%2Fvibe-learn","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmilne22%2Fvibe-learn/lists"}