diff --git a/README.md b/README.md
index c814cf6..755ee9c 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,63 @@ laptop.
---
+## A quick look
+
+A 30-second tour of the UI, lab, and tutor. Click any image to enlarge.
+
+
+
+
+
+
+
+ Land. Two paths — beginner or quick reference. The "Ask tutor" button is always one tap away.
+ |
+
+
+
+
+ Browse. 46 sections, filterable, grouped by theme. Read in order or jump straight to a topic.
+ |
+
+
+
+
+
+
+ Read. Each section explains the why first, then the syntax. Switch between Teaching and Quick reference modes.
+ |
+
+
+
+
+ Run. Edit the snippet, press Run, see real stdout/stderr and exit code — actually executed, not faked.
+ |
+
+
+
+
+
+
+ Evaluate. The tutor sees your code and what it actually printed, gives a verdict, a next step, and links to official docs.
+ |
+
+
+
+
+ Ask. A floating chat panel for free-form questions — your code and lesson context come along for the ride.
+ |
+
+
+
+> The screenshots above are produced by
+> [`scripts/capture-screenshots.js`](scripts/capture-screenshots.js) with
+> deterministic UI fixtures so they stay reproducible without Ollama running.
+> Real model output will read differently — for the look of the UI, they're
+> faithful. See [`docs/assets/screenshots/README.md`](docs/assets/screenshots/README.md).
+
+---
+
## How it works
```mermaid
diff --git a/docs/assets/screenshots/01-home.png b/docs/assets/screenshots/01-home.png
new file mode 100644
index 0000000..4acc8f2
Binary files /dev/null and b/docs/assets/screenshots/01-home.png differ
diff --git a/docs/assets/screenshots/02-lesson-browser.png b/docs/assets/screenshots/02-lesson-browser.png
new file mode 100644
index 0000000..8d5fb59
Binary files /dev/null and b/docs/assets/screenshots/02-lesson-browser.png differ
diff --git a/docs/assets/screenshots/03-section-view.png b/docs/assets/screenshots/03-section-view.png
new file mode 100644
index 0000000..7a03c86
Binary files /dev/null and b/docs/assets/screenshots/03-section-view.png differ
diff --git a/docs/assets/screenshots/04-code-lab-run.png b/docs/assets/screenshots/04-code-lab-run.png
new file mode 100644
index 0000000..ab7c9d6
Binary files /dev/null and b/docs/assets/screenshots/04-code-lab-run.png differ
diff --git a/docs/assets/screenshots/05-evaluate-feedback.png b/docs/assets/screenshots/05-evaluate-feedback.png
new file mode 100644
index 0000000..c3b21b6
Binary files /dev/null and b/docs/assets/screenshots/05-evaluate-feedback.png differ
diff --git a/docs/assets/screenshots/06-tutor-chat.png b/docs/assets/screenshots/06-tutor-chat.png
new file mode 100644
index 0000000..c07c146
Binary files /dev/null and b/docs/assets/screenshots/06-tutor-chat.png differ
diff --git a/docs/assets/screenshots/README.md b/docs/assets/screenshots/README.md
new file mode 100644
index 0000000..267eea7
--- /dev/null
+++ b/docs/assets/screenshots/README.md
@@ -0,0 +1,32 @@
+# Walkthrough screenshots
+
+These images are linked from the project [README](../../../README.md) to give
+visitors a quick visual tour of the Python Tutor.
+
+| File | What it shows |
+| ---------------------------- | -------------------------------------------------------------- |
+| `01-home.png` | Landing page — two learning paths and the "Ask tutor" FAB. |
+| `02-lesson-browser.png` | The 46-section beginner browser with search. |
+| `03-section-view.png` | A lesson opened in the **Teaching** reading mode. |
+| `04-code-lab-run.png` | The inline code lab after pressing **Run** (stdout panel). |
+| `05-evaluate-feedback.png` | Tutor evaluation: assessment, feedback, next step, references. |
+| `06-tutor-chat.png` | Floating chat panel mid-conversation. |
+
+## How they're generated
+
+The shots are captured by [`scripts/capture-screenshots.js`](../../../scripts/capture-screenshots.js)
+using Playwright. The script serves `frontend/` on a local port and **mocks**
+`/api/health`, `/api/run`, `/api/evaluate`, and `/api/chat` so the UI renders
+its happy-path states without requiring Ollama to be installed or running.
+
+The mocked model responses are **deterministic fixtures** chosen to illustrate
+the UI — they are *not* real Gemma output. If you want screenshots of real
+model output, start the backend (`./run.sh`) and capture them manually.
+
+To regenerate:
+
+```bash
+npm i --no-save playwright
+npx playwright install chromium
+node scripts/capture-screenshots.js
+```
diff --git a/scripts/capture-screenshots.js b/scripts/capture-screenshots.js
new file mode 100644
index 0000000..a539747
--- /dev/null
+++ b/scripts/capture-screenshots.js
@@ -0,0 +1,241 @@
+#!/usr/bin/env node
+/**
+ * scripts/capture-screenshots.js
+ *
+ * Re-generates the README walkthrough screenshots under
+ * docs/assets/screenshots/. Run from the repo root:
+ *
+ * npm i --no-save playwright # if not already
+ * npx playwright install chromium # one-time browser download
+ * node scripts/capture-screenshots.js
+ *
+ * The script serves the static frontend on a local port and mocks
+ * /api/health, /api/run, /api/evaluate, /api/chat so the UI shows
+ * realistic states without requiring Ollama to be running. The
+ * mocked responses are deterministic and do not represent real
+ * model output — they exist purely to make the UI screenshots
+ * reproducible. If you want screenshots of *real* model output,
+ * start the backend (./run.sh) and point a browser at it manually.
+ */
+'use strict';
+
+const path = require('path');
+const http = require('http');
+const fs = require('fs');
+
+let chromium;
+try {
+ ({ chromium } = require('playwright'));
+} catch (_) {
+ console.error('playwright is not installed. Run: npm i --no-save playwright && npx playwright install chromium');
+ process.exit(1);
+}
+
+const REPO = path.resolve(__dirname, '..');
+const FRONTEND = path.join(REPO, 'frontend');
+const OUT = path.join(REPO, 'docs/assets/screenshots');
+
+const MIME = {
+ '.html': 'text/html; charset=utf-8',
+ '.js': 'application/javascript; charset=utf-8',
+ '.css': 'text/css; charset=utf-8',
+ '.json': 'application/json; charset=utf-8',
+ '.svg': 'image/svg+xml',
+ '.png': 'image/png',
+ '.ico': 'image/x-icon',
+};
+
+function serveStatic(root, port) {
+ const server = http.createServer((req, res) => {
+ let urlPath = req.url.split('?')[0];
+ if (urlPath === '/' || urlPath === '') urlPath = '/index.html';
+ const fp = path.normalize(path.join(root, urlPath));
+ if (!fp.startsWith(root)) { res.statusCode = 403; return res.end('forbidden'); }
+ fs.stat(fp, (err, stat) => {
+ if (err || !stat.isFile()) { res.statusCode = 404; return res.end('not found'); }
+ const ext = path.extname(fp).toLowerCase();
+ res.setHeader('content-type', MIME[ext] || 'application/octet-stream');
+ res.setHeader('cache-control', 'no-store');
+ fs.createReadStream(fp).pipe(res);
+ });
+ });
+ return new Promise((resolve) => server.listen(port, '127.0.0.1', () => resolve(server)));
+}
+
+function mockApi(route) {
+ const url = route.request().url();
+ const u = new URL(url);
+ if (u.pathname === '/api/health') {
+ return route.fulfill({
+ status: 200, contentType: 'application/json',
+ body: JSON.stringify({
+ ok: true,
+ ollama: { ok: true, model: 'gemma3:4b', host: 'http://localhost:11434' },
+ version: '0.1.0',
+ }),
+ });
+ }
+ if (u.pathname === '/api/run') {
+ return route.fulfill({
+ status: 200, contentType: 'application/json',
+ body: JSON.stringify({
+ stdout: "Hello, tutor!\nx = 42\ntype(x) =