# Step 2 — Tailwind v4 design system, theme toggle, reader pages Worktree: `/home/thallada/workspace/the-daily-epub-v2b` (branch `v2-frontend`). Read `00-shared.md` first, especially the design language — it is the spec. You own the design system and the **reader-facing** templates. Do not restyle `src/web/templates/dashboard/*` (step 3 does that, on top of your system); they may look plain until then, which is expected. Do not implement the sidebar table of contents (step 4). ## 1. Tailwind toolchain (no Node at build or run time) - `package.json` at the repo root with `devDependencies` `tailwindcss` and `@tailwindcss/cli` pinned to `4.3.3`, scripts `css` (`tailwindcss -i src/web/tailwind.css -o src/web/static/app.css --minify`), `css:watch`, and `css:check` (build to a temp file and `diff` against the committed `app.css`, non-zero on drift). `package-lock.json` committed; `node_modules/` and `dev/` added to `.gitignore`. - The compiled `src/web/static/app.css` is **committed** and embedded with `include_str!` exactly as today, so `cargo build` and the server never need Node. Document the workflow (edit templates/`tailwind.css` → `npm run css` → commit both) in `README.md` and in `docs/runbooks/web-dashboard-rollout.md`. - Fonts: `src/web/static/fonts/Newsreader.woff2`, `Newsreader-italic.woff2` and `OFL.txt` are already in your worktree. Serve them from `web::static_asset` (`include_bytes!`, `font/woff2`, same ETag/304 logic, long cache) — extend the route so `/static/fonts/Newsreader.woff2` or `/static/Newsreader.woff2` works (pick one, keep `safe` matching: a fixed match arm per file, no path joins). `@font-face` in `tailwind.css` with `font-weight: 200 800; font-stretch: normal; font-display: swap;` and the `unicode-range` for latin from Google's CSS (`U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD`). Add a `` for the regular face in the layout. ## 2. Theme toggle (item 2) - `src/web/static/theme.js` (new, served like `app.js`, `include_str!`): a handful of lines run **synchronously in ``** before paint: read `localStorage.theme` (`"light"`/`"dark"`, anything else = system), set `document.documentElement.dataset.theme` accordingly (remove the attribute for system). Wrap storage access in try/catch. No inline script (CSP). - The toggle button in the layout's "ears" row: `