Pages & routing
The app is a single-page app with real-path client-side routes (served by
BrowserRouter, not hash routes). Every page has a genuine URL like /hub/:id,
so the Worker can return a prerendered snapshot to crawlers and serves
index.html for unknown paths so deep links resolve:
Unknown paths redirect to the home page (/).
Every page's header carries a shared nav (a Lesson hub link and an account
control that shows Sign in or the signed-in account menu). Routing is set up
in src/main.jsx (BrowserRouter + AuthProvider, wrapped in a
DisplayNameGate) and the route table is in src/App.jsx.
Home page
The home page (src/pages/HomePage.jsx) has two faces, chosen from the auth state:
- Signed out — a hero whose backdrop is real spelling words drifting upward
(built with tsParticles; see
src/components/FloatingWords.jsx), followed by alternating feature blurbs. The words come from the Worker'sGET /spelling-words.json— an aggregate of every spelling word taught across the published hub lessons, rebuilt at most once every two days and cached in KV (apps/api/src/routes/spelling-words.js). If that fetch fails, a small built-in word list is used instead. Feature illustrations live underapps/web/public/home/(a missing file degrades to a labelled placeholder — see that folder'sREADME.md). - Signed in — a dashboard showing the hub's latest-lessons Atom feed and the
user's own activity feed (both parsed client-side from Atom with
DOMParser, reusing the samefeed.xml/profiles/:id/feed.xmlendpoints the "RSS" links point at), plus a roomier list of the user's notifications.