Now showing · learning-english
Every exported contract that carries JSDoc: the domain's ports and use cases, the adapters behind them, and the hooks, utilities and components the app is built from.
Browse by layer
The domain imports nothing but zod and neverthrow. Everything else reaches it through a port.
Index
Grouped by folder. Paths mirror src/, one folder per entity.
Use case: enroll the learner in a course the catalog serves.
Domain errors emitted by findContinueWatching.
Use case: resolve a stored location into the entities the home needs.
Discriminated error union for the findCourseCatalog use case.
Reduce a lesson to its progress slice.
One lesson as the course overview sees it: its position in the module, its title, its runtime and its artwork, and nothing more.
Domain errors emitted by findLearnerProfile.
Use case: read the learner profile this device holds.
Domain errors emitted by findLessonForView.
The view object the Lesson Page renders. Composed by the use case so the
Server Component does not have to reach for ports directly. modules and
lessons feed the Outline (sidebar); the rest is the page body.
A module together with its lessons, in sequence order.
Discriminated union of domain errors emitted by findNextLessonToRecommend.
Use case: given a course and a current lesson, recommend the next lesson
in the course. Crosses module boundaries — when the current lesson is the
last in its module, returns the first lesson of the next module; returns
null when the learner just finished the last lesson of the last module.
Discriminated union of errors raised by getPlaybackPosition.
Use case: read the persisted playback position for a lesson.
Use case: mark a lesson as complete. Validates the lesson exists and then
writes through the ProgressTracker port. In v1 the tracker is in-memory
and ephemeral; the use case contract is unchanged when persistence arrives.
Discriminated union of errors raised by recordContinueWatching: those of
the enrollment it runs first, plus internal-error when the continue-watching
repository rejects the location.
Use case: record where the learner is, enrolling them in that course.
Discriminated union of errors raised by recordPlaybackPosition.
Use case: persist the playback position for a lesson.
Domain errors emitted by saveLearnerProfile.
Use case: validate and store the learner profile.
Use case: clear a lesson's completion — the learner's own undo of
markLessonComplete. Validates the lesson exists and then writes through
the ProgressTracker port. Un-marking a lesson that was never marked is
not an error: the port's writer is idempotent, so the caller never has to
ask whether the mark is there first.
Port: a clock the domain uses to read the current time.
Port: where the learner was in each course they have opened.
Port: the courses a learner has enrolled in.
Port: read-only access to courses.
Port: the tickets a learner has earned, one per lesson.
Port: an identity generator the domain uses to mint new ids.
Port: the one learner profile this device holds.
The notes view the use case and the UI consume. The Resource is
included so the Lesson Page can still surface the original Markdown
source link in the Resources region.
Port: read-only access to lessons.
Port: read-only access to modules.
Port: persists the playback position per lesson.
The playback position for a single lesson. seconds is non-negative and
finite — NaN and Infinity are rejected at the boundary so adapters
never persist nonsense values.
Port: the module prizes a learner has claimed on the prize counter.
Port: tracks which lessons the learner has completed.
Port: read-only access to resources.
Where the learner was the last time they opened a lesson.
A course's resume location together with when the learner was last there.
Derives every course's standing from the catalog it belongs to.
Whether a course is a rung of the catalog ladder or reference material.
Branded UUID identifiers for course-platform entities.
The letters an initials avatar shows: the first letter of the first word, followed by the first letter of the last word when there is more than one.
A reading lesson — markdown body, no media. The first lesson kind.
A grouping of related Lessons within a Course.
The kind of a Resource — drives the icon in the UI and the filter in
ResourceRepository.listByKind (deferred). Defined as a Zod enum so the
domain rejects unknown values at the boundary.
A URL-safe identifier for a course (kebab-case, ≥ 3 chars).
A string that is either a fully-qualified absolute URL or a site-relative
path beginning with /. Used for Resource.url, VideoLesson.source,
and VideoLesson.poster — v1 ships static assets under /public (see
design.md §D5, §D9) so relative paths are the natural form. The boundary
still rejects garbage (empty strings, schemes without a host, paths
without a leading /).
One email, fully rendered: the sender adds nothing but transport.
Sends email over SMTP with nodemailer.
Rejects keys that could escape their store: empty, absolute, or containing a
.. segment.
Whether a store can mint signed URLs.
The images.remotePatterns entries a location manifest requires.
Parses the text of a content-locations.json into a validated manifest.
Builds the application's BlobStore from the location manifest.
Driven adapter: Google Cloud Storage-backed BlobStore.
Driven adapter: filesystem-backed BlobStore.
Driven adapter: a BlobStore that fans out over several stores.
Driven adapter: S3-backed BlobStore.
The course catalog, read from the tracked manifests under src/content/.
Raised when a course manifest does not describe a servable catalog.
Turns the nested course manifests into the flat catalog the repositories take.
Raised when SHOW_DRAFT_COURSES holds a value that is neither a yes nor a no.
In-memory CourseEnrollmentRepository, for use-case tests and stories.
Driven adapter: in-memory implementation of CourseRepository.
In-memory EarnedTicketRepository, for use-case tests and stories.
Driven adapter: in-memory implementation of LessonRepository.
Driven adapter: in-memory implementation of ModuleRepository.
Driven adapter: in-memory implementation of PlaybackPositionRepository.
In-memory PrizeClaimRepository, for use-case tests and stories.
Driven adapter: in-memory implementation of ProgressTracker.
Driven adapter: in-memory implementation of ResourceRepository.
Builds a CoursePlatformDeps from the generated content seed.
The browser's ContinueWatchingRepository: the learner store's places, one
per course, saved through the continue-watching Server Action.
The browser's LearnerProfileRepository: the learner store's card, saved
through the profile Server Action.
The browser's PlaybackPositionRepository: every position reaches the
learner store at once, and the server at most once every
SERVER_WRITE_INTERVAL_MS per lesson.
The browser's ProgressTracker: reads the learner store, writes through
the completion Server Actions.
Driven adapter: filesystem-backed LessonNotesRepository.
Driven adapter: LessonRepository backed by declared manifest rows whose
content keys are resolved through a BlobStore on every read.
Driven adapter: ResourceRepository backed by declared manifest rows whose
content keys are resolved through a BlobStore on every read.
The single place where a content key becomes a URL.
Builds a Drizzle client over @libsql/client for one database.
The server's composition root for one learner: the Turso ports bound to that learner, composed with the catalog into the use cases the learner actions and the playback beacon run.
Builds every learner-scoped port for one learner — the only place that names the Turso adapters.
Reads everything the client needs about one learner, in one step.
The learner card: one per learner.
ContinueWatchingRepository over the continue_watching table: one row per
learner and course, ordered by updated_at.
CourseEnrollmentRepository over the course_enrollment table, for one
learner. An enrollment is kept for good, so enrolling again changes nothing.
EarnedTicketRepository over the earned_ticket table, for one learner.
LearnerProfileRepository over the learner_profile table: one card per
learner, replaced by every set.
PlaybackPositionRepository over the playback_position table, for one
learner: one row per lesson, replaced on every save.
PrizeClaimRepository over the prize_claim table, for one learner. A
claim is kept for good, so claiming again changes nothing.
ProgressTracker over the lesson_completion table, for one learner.
Values, validation and translated field errors for one account form.
The submission mechanics every account form shares.
Client hook: reports whether the browser's own chrome — Safari's toolbar on an iPhone — is still taking part of the screen while the device is held in landscape.
Client hook: whether this learner can still add the app to their home screen.
Plays short audio clips one at a time, in order, and reports which one is sounding.
Marks a lesson complete once the learner has watched it to the end.
The snapshot the server renders with: always empty, so the first client render agrees with it.
Client hook: reads and writes where the learner was in each course.
A number that climbs from zero to target, for counts that should feel earned.
Client hook: the video the learner continues a course with, decided exactly
as the course overview's continue tile decides it (capability
continue-target).
Reads the learner's enrollments, places, completion marks, positions and claimed prizes once, and sorts the catalog with them.
How far the learner has got through a course, in whole lessons.
The snapshot the server renders with: always empty.
Owns the lesson player's viewport-filling mode: the state, the Escape key,
and the place in the page to come back to.
The snapshot the server renders with: always empty.
Client hook: how far to shrink fixed-size content so it fits its container.
The element the browser is presenting fullscreen, if any.
Client hook: a guide that plays itself, and answers a drag.
Client hook: a sideways drag over an element, as a pair of callbacks.
Client hook: which install route this browser offers, and what it needs to take it.
Client hook: the browser's standing offer to install this app, if it has one.
Client hook: reports whether React has finished hydrating the server-rendered HTML.
Client hook: reports whether the learner store has been seeded with this learner's state.
The learner's tickets, prizes and distinction, as this device's progress stands.
The snapshot the server renders with: always unknown.
Sends the learner to another page once their profile status matches a rule.
Client hook: rewrites a stored theme the app no longer recognises to dark.
The snapshot the server renders with: always empty.
Celebrates the lesson on screen earning its ticket, and hands the learner on to the counter when that ticket was the module's last.
The one rule for "how far is this learner through this lesson", read by every surface that shows progress or completion.
Reads the prize of a single module from this device: which toy it is, its state and how many of its tickets are earned.
Where the learner stands in one module on this device.
Reads the learner's progress through a module as a route.
How many of a module's lessons the learner has finished.
Resolves the onboarding's destinations from the next query parameter.
Re-reads storage and notifies every subscriber.
Persists where the learner is in a lesson, on the cadence the
playback-position capability specifies.
Client hook: reads and writes the playback position for one lesson.
The snapshot the server renders with: always empty.
Sends a device without a learner profile from a course route to the onboarding, remembering the route so the onboarding can return to it.
Reads the stored continue-watching record and resolves it into a panel.
The "offer to resume on the first play" rule, with no player library in it.
Turns a runtime in seconds into the course overview's localized label.
Client hook: which Safari install flow, if any, this learner can be shown.
The snapshot the server renders with: always empty.
Client hook: scrolls a region so the row it marks with
aria-current="page" sits in the middle of its visible area.
Keeps a seek run alive between edge taps and ends it when the taps stop.
Client hook: the seek step the learner chose, and the way to change it.
Client hook: registers the app's service worker, once, after mount.
Reports whether the learner is pressing and holding the video.
Client hook: the theme a theme control shows, and a way to switch to the other one.
The Turnstile challenge as the challenged account forms place it: last in the form, under "Continue with Google" on sign-in and sign-up and under the submit button on forgot-password.
What an account form turns into once there is nothing left to fill in: "check your inbox" after sign-up or a reset request, or "this link no longer works" for a spent reset link. The message is a status region, so it is announced when it replaces the form.
One labelled input of an account form.
One account detail on the Profile page: its label and current value, with a Change button that opens the form for it in place.
The Profile page's sign-in and security settings: the address the learner is registered with, their password, and how they sign in.
The card every account page is built in: sign-in, sign-up, forgot and reset password. A heading, an optional subtitle, the page's content, and a footer for the links between those pages.
The end of every account form's fields: the announced error of the last
refused attempt, and the submit button, which stays disabled until the form
may be sent. A challenged form places its Turnstile challenge on its own,
with AccountChallenge.
Whether the nearest AccountWait has a request in flight.
The Achievements page: the learner's card with the distinction they have earned, and their tickets and prizes across the whole catalog.
The install guide, playing itself: the four steps on a loop, and a drag to move it by hand.
A miniature iPhone showing the iOS surface one install step happens on, with that step's target control picked out.
Adding the course to an iPhone home screen, as iOS 26 Safari requires it.
Available courses: every catalog course as an equal poster, the learner's own courses first.
Picks the learner's avatar: their initials or one of eight illustrations.
The Immersion Cinema wordmark: ENGLISH·COURSE, letter-spaced, with the
middle dot in the gold accent. Links wherever the caller points it
(locale-aware), defaulting to the locale home — the header sends a signed-in
learner to My learning instead. The visible text carries the accessible
name, so no extra aria-label is needed.
The way from My learning to Available courses: the size of the catalog, how much of it the learner has not joined, and one course they could try next.
The Profile page's email form: moves the account to a different address, behind two links.
The Profile page's password form: replaces the account's password without signing the learner out here.
The Immersion Cinema backdrop: a warm radial glow bleeding from the
upper-right over the base --background, plus a subtle top scrim that
evokes the mockup's letterbox bar. Rendered once (in the locale layout)
as a fixed, aria-hidden layer behind all content. The glow is static —
no animation — so it is safe under prefers-reduced-motion.
The backdrop of a cinema hero frame: a video poster fading into the page background from the left and from the bottom, so copy laid over it reads in both themes.
The landing's closing band for a learner who already has a card: a greeting by first name, their learner card with its progress, and the way back in.
The course overview's one way in: the video the learner continues with, as a large artwork tile with its position, title and a single action.
A course at a glance: For — who it suits — and You'll learn — its highlights — in the active locale.
The course page's opening frame: the course's first video's artwork, what kind of course it is, how big it is, and the enroll action.
The course page a learner sees before joining a course: what it teaches,
what is in it, how long it takes, and Enroll (capability
course-detail-page).
The course page's one action: Enroll until the learner has joined the course, then the way into it — Start course, Continue where you left off or Watch again.
The course page's bottom bar on narrow screens: the course's name and size, and the enroll action, kept at the bottom of the viewport while the page scrolls.
The course page's enroll card: the action, how big the course is, and how long it takes at the learner's pace.
Driving adapter: React component that calls the findNextLessonToRecommend
use case and renders the recommendation.
What you'll learn: a course's outcomes as a checklist, in the order the course declares them.
Inline error state for the Course Overview. Reused for the unknown
courseSlug and the internal-error case. The page never leaks the
raw error kind to the user.
The course overview: the video to continue with and the course's progress, then every lesson as a progress-ring tile.
Holds a course page back until the learner store is seeded, showing
PendingCoursePage meanwhile (capability course-detail-page).
Decides what the course route shows: the course page to a learner who has
not joined the course, the progress board to one who has (capabilities
course-detail-page, cinema-course-overview).
One course of Available courses' lobby, drawn as a vertical cinema poster: artwork, its level, its title, who it is for and what it teaches, and the way in.
The course overview's progress board: the continue tile and the course progress tile, which leads to the course page, then one ring tile per lesson.
The course overview's progress tile: the course title as a heading, a large ring filled to the watched share, and how much is left in videos and time.
One section of the course page: a gold eyebrow, a level-two heading, then its content, as a region named by the heading.
The sounds a course teaches, counted in the heading and laid out as two groups of IPA symbols: vowels, then consonants.
The way into a course the learner has joined: Start course, Continue where you left off or Watch again.
The course page's syllabus: every lesson in sequence order as a
disclosure row that opens to its videos.
The last section of the Profile page: deletes the learner's account.
One enrolled course on My learning's Your courses: its share watched, and the video it continues with.
A small uppercase, letter-spaced gold label — the cinema "eyebrow" used
for section chrome and kickers ("Now streaming", "Lesson 3", "Video 1").
The --gold token is contrast safe as text in both themes (design.md §D2).
Onboarding step 3: the one course every new learner is recommended, with a way to start it and a way to see every course.
Requests a password-reset email, behind a Turnstile challenge.
What a learner sees when the root layout itself fails: one line, one retry button, and a report to Sentry.
Client-side providers that wrap the entire app. Add new global providers here (theme, auth session, query client, etc.).
A cinema pill/badge. gold is the accented variant (counts, "featured");
neutral is the quiet variant (metadata chips). Purely presentational.
"Continue with Google": starts the Google OAuth round-trip, which signs the
learner in (creating the account on first use, or linking it to an existing
account with the same address) and returns them to returnPath in the
current locale. A failed round-trip comes back to sign-in.
The row of controls a guide is moved by: back, one per frame, and on.
The locale home: always the editorial landing.
The header chip that gets the course onto the learner's home screen or Dock.
A picture of where the course will come to rest, drawn per platform.
The learner's collection: how many tickets and prizes they hold across the catalog, then the prize counter with every module's prize.
The learner's avatar: a circle showing their chosen illustration, or the initials of their name on the gold accent.
One of the eight learner illustrations, filling its container.
The learner card's name, written in place.
The learner card: who is learning on this device, drawn like a membership
card — wordmark, Learner tag, avatar, name, level, and progress.
The new-visitor home's three questions, each answered in a sentence or two.
Hands the server's learner snapshot to the browser's learner store.
The shell both legal pages share: a title, an opening paragraph, a series of titled passages, and the date the wording last changed.
The "you have taken this lesson" indicator.
Renders a lesson notes Markdown body with the Immersion Cinema typographic hierarchy.
One lesson (module) of the course overview: its artwork, ordinal, a progress ring, a status and how much is left. The whole tile opens the lesson.
One lesson's ticket: a notched strip of cream arcade paper carrying the lesson's sound.
The breadcrumb at the top of the Lesson Page. Three segments:
Course › Module › Lesson. The first two are locale-aware links to
the course and module overview pages; the Lesson is the current
page (no link).
The end-of-lesson block: the "Mark as complete" action and the next lesson as one closing surface.
The lesson's completion control, in both of its states — and in the third one, which says nothing.
A module's lessons in sequence order, each a locale-aware link to the
lesson route. The current lesson is marked with aria-current="page" and
a visual indicator. Each row uses the project's standard minimum touch
target height and a visible focus ring.
The Lesson Page's Notes/Transcript tab pair (design.md §D6/§D7). The
Notes tab renders the readme.md section written in the app's active
locale — one language at a time, never a side-by-side pair — falling back
to English and then Spanish when the notes do not carry that locale. The
Transcript tab is present for visual parity but disabled — no transcript
data exists — so activating it only reveals a localized "not available"
notice, never transcript content.
Inline error state for the Lesson Page. Used when the resolved route is well-formed but the domain can't satisfy it (course not found, module not in course, lesson not in module, or invalid URL params).
The lesson player: a Vidstack <MediaPlayer> with the Default Video Layout.
The player's pointer gestures: a single tap toggles playback with a mouse and brings the control bar in or out with a finger, a double tap in the middle toggles fullscreen, a double tap on an edge starts a seek run — where every further tap on that edge adds a step and an indicator counts them — and a press held on the frame runs the lesson at double speed until it is released. All of them are the YouTube app's conventions, which the learner's thumbs already know.
Builds the full DefaultLayoutTranslations map from the app's own messages.
The "Resume from MM:SS · Restart from beginning" choice, drawn inside the player over the video frame.
What the lesson's video frame shows while the player is still booting.
The composition the Lesson Page renders. Receives the resolved View
from findLessonForView and the Server Action that completes a lesson.
Dispatches on lesson.kind so video and reading lessons render
appropriately (video → PlaybackPositionedVideoPlayer; reading → body).
Renders the course's modules in sequence order, each with its lessons
listed below. lessonsByModuleId is the precomputed map of moduleId →
lessons (sorted by sequence) that the page passes in.
Builds a course of lessonsPerModule.length modules, with the current lesson
placed at currentLessonIndex inside the module at currentModuleIndex.
Responsive shell around the Outline. On screens >= lg it renders as a
permanent sidebar (the design's desktop layout). On smaller screens it
renders as a compact row above the breadcrumb — collapsed by default,
expanded with a tap.
The Course outline sidebar. Renders the course's modules and lessons in
sequence order with the current lesson indicated. The component itself
does not own the responsive shell — the page chooses where the outline
sits (sidebar on desktop, drawer on mobile).
Observes a player command's promise and discards its rejection.
The lesson player with its memory: it remembers where the learner stopped, and offers to take them back there.
Records where the learner is, so the home can offer to bring them back.
A single resource row inside the Resources list. The icon is chosen by
ResourceKind; the title is the link text; the URL is opened in a new tab
so the learner does not navigate away from the lesson.
A flat list of resources for the Lesson Page's right rail. Renders a heading plus one row per resource, or a localized empty-state message when there are none.
The hint drawn over the enlarged video while the browser's own chrome still takes part of the screen, asking for the gesture that reclaims it.
The indicator drawn over the tapped half of the video while a seek run is active: a translucent half-disc, three chevrons pointing the way the video moved, and the seconds the run has seeked so far.
The seek-step setting, as an entry in the player's own settings menu.
Returns the Markdown a learner on locale should read, without its ##
language heading.
The pill drawn over the video while a press-and-hold runs it faster: the rate in force and a pair of chevrons pointing the way it is running.
The buffering indicator the lesson player draws at the centre of the frame: the Default Layout's own ring, with an opaque core.
The play/pause control drawn at the centre of the video on a touch device, for as long as the control bar is in view.
The fallback control that fills the viewport with the video, seated beside Vidstack's own fullscreen button.
How much of one video the learner has watched.
Catalog courses as rows of an ordered table: the levels, or the reference courses that sit outside them.
The one thing a loading shell says out loud.
Language selector that updates the active locale via next-intl's
locale-aware navigation.
Explains how achievements are earned: tickets, prizes and the card's distinction.
The add-to-home-screen guide, in a modal.
Asks the learner to confirm deleting their account.
Welcomes a learner who just enrolled from the course page: the poster of
the video the course starts with, an Enrolled mark, and the way in
(capability enrollment-welcome).
The install prompt: one question, two answers, then the browser takes over.
Tells the learner a prize is waiting for them at the counter, without showing which one.
Celebrates redeeming a module's prize: the module's tickets fly into the prize's silhouette, it shakes and flashes, and the coloured prize appears with its name.
The iPad and macOS install guides, in a modal.
Asks the learner to confirm un-marking a lesson they had completed.
Inline error state for the Module Overview. Used for unknown module, module-not-in-course and invalid params. The page never leaks the raw domain error kind to the user.
The module overview as a route through its videos: a server-rendered header
— back link, Lesson NN · Course eyebrow, title, and how many videos the
module holds with their total runtime — above the learner's route.
A module's prize on the module overview, drawn the way the prize counter draws it.
The module's progress at a glance: a ring with the percentage finished, the count of finished videos, and the watch time left.
One lesson on a module's route.
Seeds the browser with a learner who finished the first finishedCount
videos and is currentFraction of the way through the next one.
The learner's route through a module: the progress panel, one step per lesson, each showing whether it is finished, current or upcoming, and the module's prize at the end of the route.
How many of a module's videos the learner has finished.
My learning: the learner's own page — a greeting, the way back into the course they watched last, and every course they are enrolled in.
The landing's hero: who the course is for, what it promises, and one way in.
A slim "next up" bar, like a mini player, that tells a learner enrolled in nothing where to begin: the recommended course's first video, where it sits, and Start course.
Onboarding step 2: the learner picks an avatar on the same learner card.
Onboarding step 1: the learner types their name into the learner card itself.
Where the learner is in the onboarding, in words and as a segmented bar.
The onboarding step's shape while storage has not said whether this device already holds a profile.
The button an account surface waits on: at rest it is an ordinary Button, and while its request is in flight it swaps its label and turns a SpinnerArc beside it.
What a course route shows before the learner's state has reached the browser: the course title over placeholders, and nothing that depends on whether the learner is enrolled.
Tells the learner about a prize whose announcement the lesson page never got to make.
The circular gold play affordance. Two modes:
The reusable Immersion Cinema "poster". Composes an artwork box (warm radial glow, optional cover image behind a scrim, an oversized sequence number, an optional eyebrow/headline, and a decorative play circle) with an optional meta row (title + badge) beneath it.
A non-interactive summary of a course's modules rendered as a row of
numeric marks separated by a connector. The number of marks equals the
module count of the course and is used on the locale-home card and the
course overview. Accessibility is provided through the aria-label and
a visually hidden text description — the marks themselves are not
interactive on the home card.
The prize counter: one shelf per course holding every module's prize, like the counter where arcade tickets are redeemed.
One prize from the arcade counter, drawn in colour or as a silhouette.
One module's prize on the counter shelf: the toy, its name once claimed, and a hanging tag with the tickets collected — or the control that claims it.
The Profile page's card column: the learner card, which carries the level's progress as a count and a bar, and under it two ticket stubs counting the tickets the learner has earned and the prizes they have claimed, each a link to the Achievements page where those tickets are spent and prizes live.
The Profile page's save bar, for as long as the learner's card has unsaved changes: under the card on a wide screen, docked to the bottom edge of the viewport on a narrow one.
One named section of the Profile page: a heading, an optional supporting line, and whatever the section holds.
The Profile page: the learner's card and progress in a column of their own, beside the sections that edit the card, hold the sign-in and security settings, set the preferences, and delete the account.
A circular progress indicator in the Immersion Cinema gold.
Sets a new password with the token from a reset email.
My learning's way back in: the video to open next in the course the learner watched last, as the course overview's artwork tile.
The Safari install guide, playing itself: one platform's taps on a loop, and a drag to move it by hand.
Whether this frame is a tap the learner performs, or the result of having performed them all.
A miniature Safari window showing the surface one install step happens on, with that step's target control picked out.
Renders the sharing card at 1200×630.
Signs a verified learner in with email and password, behind a Turnstile challenge, or with Google.
Creates an account with a name, an email and a password, behind a Turnstile challenge.
The site's footer: the two legal documents, and nothing else.
The Immersion Cinema top bar: the ENGLISH·COURSE wordmark, a section
eyebrow naming only the current section ("PROFILE") derived from the route,
and the locale chip. The theme is not set here — its only control lives in
the Profile page's Preferences section. Client-side because the section
label reads the pathname; the locale control was already a client component.
The first focusable element on every page. Hidden until it receives keyboard focus, then becomes visible to offer screen-reader and keyboard users a way to jump to the page's main landmark.
The gold arc that turns while a request is in flight.
The landing's primary action: Start course for a new visitor, Continue for a learner who already has a card.
The landing's closing band: the offer restated once more, and the hero's primary action repeated.
Renders one schema.org payload as linked data.
The visual of the theme switch: a pill track with a sun and a moon, and a thumb that slides to the theme in use with a slight overshoot.
Theme switch backed by next-themes.
The notification a lesson page shows when the learner earns a ticket: the ticket dropping out of its slot, the tickets collected towards the module's prize, and that prize's silhouette.
Cloudflare Turnstile, the bot check on the sign-up, sign-in and forgot-password forms.
shadcn/ui Button primitive.
shadcn/ui Dialog root — the state machine behind a modal.
shadcn/ui DropdownMenu root — the state machine behind a menu.
A pulsing block that stands in for content that has not arrived yet.
shadcn/ui TooltipProvider — shares the open delay across the tooltips
inside it.
The home's interactive minimal-pair card: ship /ɪ/ against sheep /i/, each with a recording, a duration bar and a note tying both vowels to words the learner already says.
A thin gold meter with its reading beside it.
Drops the prize announcement the deleted account left on this device, so the next learner here is not told about a prize that no longer exists.
Where the deletion link lands, signed out: confirms the account is gone and forgets what this device was still holding for it.
The <main> landmark every account page renders its card in, centred with
room around it on every viewport.
The metadata every account page declares: its own title and subtitle, and
noindex, follow — an account page is nothing a search result should lead to.
Forgot password: asks for the address a reset link should go to.
Reset password: where the emailed link lands.
Sign-in. next is validated before anything uses it; a learner who is
already signed in goes straight there.
Sign-up. The verification link returns to the validated next, so a
learner who followed a course link lands on that course once verified.
Catch-all for unknown paths inside the [locale] segment. Fires
notFound(), which renders the locale-segment not-found.tsx — the
localized "Page not found" message and a home link.
Achievements' loading shell: the card and heading, then the two counts.
The Achievements page. A thin shell like My learning: it resolves the catalog on the server and hands the learner's collection to the client, which derives it from this device's progress.
Server Action invoked by the home's ContinueWatching component with the
location it read from localStorage.
Projects catalog entries onto what the client pages count progress over: each course with its modules and lesson slices. Lesson bodies stay on the server.
Every catalog course as the course overview sees it — modules, and lessons with their titles, durations and posters — in catalog order.
The course a course route serves, or null when the slug is malformed or
names no course — the page then renders its error state.
The lesson page's loading shell — the one that answers the reported bug.
404 page for the Lesson Page route. Reached when findLessonForView
resolves with course-not-found. Renders the localized "course not
found" message with a locale-aware "Go home" link, matching the spec
scenario for an unknown course.
A lesson's sharing card: the lesson title, its place in the catalog, and — for a Lecture — its runtime.
Per-page metadata. Sets <title> to the resolved lesson's title on
success, or to a localized fallback (HomePage.notFound) on any error
or invalid params.
The module overview's loading shell.
A module's sharing card: the module's own title, over its place in the course.
The course overview's loading shell.
A course's sharing card.
Wraps every course, module and lesson route. The session is validated on the server first; the profile gate then decides after hydration whether this learner needs the onboarding.
The course routes' gate: sends a device without a learner card to the onboarding. Renders nothing, so the server-rendered course stays intact.
The lesson the new-visitor home's primary action opens: the first lesson of the first level course, with its runtime in whole minutes.
Per-locale defaults every route inherits: the origin its relative URLs are absolutized against, and the title template that appends the brand.
The input of every learner action, by action. None carries a learner or
user id: the learner is whoever the session says, never what the client
claims. Kept apart from learner-actions.ts because a "use server"
module may only export async functions.
Records a lesson as complete for the signed-in learner.
My learning's loading shell: the greeting and the panel beneath it.
My learning. A thin shell like the home: it resolves the catalog on the server and hands the learner's page to the client, which reads the profile, the continue-watching record and progress from this device.
The home's loading shell.
The line on the Page Not Found state that names the address the learner asked for, so a mistyped path is something they can see rather than guess.
The not-found page for everything under the [locale] segment.
The home's sharing card: the catalog's own promise, over its real totals.
The locale home route. A thin shell: it resolves the catalog through the
use case and hands HomeView every course with its modules and lesson
progress slices, and the first lesson the closing band's offer is sized by —
mirroring how the course and module routes delegate to CourseOverview and
ModuleOverview.
The layout every personal route segment shares: it validates the session on the server before the page renders, and redirects to sign-in otherwise.
The privacy policy. Public and session-free: an anonymous reader — and Google's OAuth consent screen, which will not publish without fetching it — has to be able to reach it.
The Profile page's loading shell: the card band, then the first section.
The Profile page: the learner card's editor, with the first level's progress, and the account settings the session answers for.
Turns a stored continue-watching location into the panel that describes it,
or null when there is nothing honest to render.
Onboarding step 2: the same learner card, now with the avatar picker.
The onboarding's loading shell: the same shape the step shows before storage answers.
Onboarding step 1. A thin shell: it names the first level for the learner card, hands the step the name the account was created with, and lets the client read the device's profile.
The terms of service. Public and session-free, for the same reason as the privacy policy: they are read before anyone has an account.
Answers a content request with a redirect to wherever the asset actually lives.
Signing endpoint for privately-stored course content.
The page-hide flush of the playback position.
Replaces the root layout when it fails, so it brings its own html and body.
The web app manifest.
The crawling policy, decided by the deployment rather than by configuration.
The URLs a crawler can be served, in every locale, with their alternates.
Every course manifest, in catalog order: the level courses, then reference material.
The shared shape of the account emails, drawn in the Immersion Cinema dark palette: black letterbox bars cropping the cinema gradient into a film frame, and centred between them the wordmark, a heading, one paragraph, one button, the same link printed as text for clients that hide buttons, and a closing line saying what happens if the email is ignored.
The email that approves moving an account to a different address. It goes to the address the account holds today, names the one it would move to, and only asks that new address to confirm once this link is opened.
The email that confirms deleting an account. Following its link, while signed in, deletes the account and everything the learner saved.
The email that tells a learner their password was replaced.
The email that carries a password-reset link. Following it opens the page where the learner chooses a new password.
The email that confirms a new account's address. Following its link verifies the address and signs the learner in.
Centralised URL builders for the catalog and lesson pages. Pages and components import these instead of concatenating URL strings, so future routing changes happen in one place.
Loads the message catalog for the active locale on every Server Component request. Falls back to the default locale when the requested one is not supported (defensive — the middleware should already validate it).
Narrows a route's locale param to a locale the application serves, or
ends the request as a 404.
Centralized routing configuration for next-intl.
The locale an account email must be written in, read from the link it carries.
Translates a Better Auth refusal into the key of a localized message, so no raw server text ever reaches a learner.
The change-email form: the address the account would move to, which has to differ from the one it holds today.
A way the learner can sign in to their account.
The browser's Better Auth client: sign-up, sign-in (email and Google),
password reset, sign-out and session reads, against /api/auth on the
current origin.
Builds the Better Auth instance the application runs on.
The signed-in learner's account identity for this request, or null
without a session.
The signed-in learner's snapshot for this request, or null without a
session.
Sends a learner who is already signed in away from an account page — there is nothing to sign in to.
The server-side session check every personal route runs before rendering.
Fires the confetti burst that celebrates finishing a lesson.
Returns the scroll offset that puts a row in the middle of a scrollable region, clamped to what the region can actually scroll.
Fires the confetti burst the app celebrates with: one fan of gold from each bottom corner of the page.
Picks the video a learner continues with. The single source of truth for that question: every surface that offers to continue calls this function rather than reading the continue-watching record directly.
A course's description, outcomes, audience and highlights in a locale.
Orders every catalog course for Available courses' lobby: the learner's courses first, then every course they have not joined, in catalog order.
Pairs each module with its summary, in module order; a module without a summary is left out.
The site itself and who publishes it.
Sorts the catalog into the course to lead with, the learner's other courses and the courses still available to them. A learner enrolled in nothing is led by the first level course, as a recommendation — never a reference course.
Split a duration in seconds into hours and minutes for display.
Format a playback position as a zero-padded MM:SS clock.
Start listening for the browser's install offer.
Whether the app is already running from the home screen rather than in a browser tab.
Whether the router has a route for this path, judged by its first segment.
The symbol a lesson's ticket shows.
The snapshot of a learner with no progress at all.
Adopts the snapshot the server loaded for this request.
Resolves a document's passages, in the order its key tuple declares.
Where the current lesson sits inside its own module.
The recorded clips the home's vowel-length card plays, keyed by the English word each one speaks.
Answers a request for a path no route serves with a real 404.
The prize a module redeems once every one of its lessons has earned its ticket.
Decides which state a module's progress panel is in, and the lesson its primary action should open.
Splits a runtime into hours and minutes, rounded to the nearest minute.
Tells whether a value may be used as the course route a learner returns to after the onboarding.
Whether a locale-less path belongs to a signed-in learner.
Returns true when the saved positionSeconds is meaningful enough to
resume from (not noise, not "basically finished"). Both bounds must hold.
Whether a player of this width wears the Default Layout's compact chrome.
Reports an error the application caught and recovered from, so it still shows up in Sentry.
Base safe-action client. Use this for any Server Action that needs validation.
Whether a value is a path that can only lead somewhere on this site.
Resolves a stored seek step to one the player can use.
Decides whether Sentry starts, and with what.
Whether Better Auth rate-limits sign-in, sign-up and the email endpoints.
The proxy's session check: sends a request for a personal route that
carries no session cookie to sign-in, with the route as next.
The two Geist weights the sharing card renders in, as ImageResponse wants
them.
The promise a course makes, extracted from its description.
Composes the metadata a single route publishes about itself.
Whether a value may be used as the route a learner returns to after signing in.
The absolute origin the application publishes about itself.
Turns a course's runtime into how long it takes at a number of minutes a day.
Joins a list of class names, dropping falsy values.
Whether a pointer event is a primary-button press or release that landed on the video itself.
The position, in seconds, at which a lesson counts as watched to the end.
Extracts the YouTube video id from a lesson's source, when that source is
a YouTube link.
Starts Sentry in whichever server runtime Next.js is booting.
Next.js 16 uses proxy.ts for middleware (formerly middleware.ts).
Discriminated union of errors raised by
enrollInCourse.