# D41. A web target beside SCORM 1.2: one build, a module for a plain web server (14 Oct 2026)

The first public version needs courses shown on the marketing site (free navigation, nothing kept), an online review with comments, and publication on Elabora's servers. All three are the same thing to the core: a module served as static files, without an LMS. One build chain keeps two targets; the hosting (Cloudflare R2 and D1, a separate content domain, EU jurisdiction), accounts and the review's hooks come later and build on this.
- **The target.** `elabora build --target web` (MCP `elabora_build` `target: "web"`, the engine's build request `target`, `@elabora/node` `build({ target: 'web', web })`) writes `dist/<course>-<version>-web/`, a folder to serve as it is, and its ZIP (both by default). Everything the SCORM package holds but `imsmanifest.xml`; no LMS profile, suspend data budget, package limit or forbidden extension applies (`validateProject` takes `target: 'web'`), file names stay portable (they are URLs). Still deterministic (D8): the same course and settings give the same bytes. `--profile` is refused with it, the web settings without it. The report and the proof dossier say `target: "web"` and the settings; the pending human gate is a trial at the published address instead of the LMS smoke test.
- **Progress: `local` (default) or `none`.** The player takes a third adapter (`packages/player/src/web.ts`) when the course data says `delivery.target: "web"`, and then never looks for an LMS: `local` keeps the lesson location, status, score and suspend data in `localStorage` under `elabora:web:<course id>` (the same encoding and permanent keys as in an LMS, D4, so a learner resumes across updates), never the interactions or the session time (an LMS's journal); `none` reads and writes nothing, the narration's preferences included, and a reload starts again. Neither shows a banner nor Quit (no platform to go back to). SCORM packages and the preview's standalone adapter (sessionStorage, its banner) are unchanged.
- **Navigation: `authored` (default) or `free`.** `free` is applied by the compiler, in the course data, so the player has no second set of rules: `navigation.mode: "free"` (every screen reachable from the outline), `lockUntilComplete: false` (Next, Continue and narrated steps wait for nothing: no activity, visited controls or narration to finish first), the outline shown, and the narrations' `seek: "seen"` becomes `free` (`none`, a timeline without a bar, stays). Events, visited controls and conditions still work as written; they no longer hold the learner back.
- **Defaults: a publication.** `local` + `authored`: a module built for the web without a word is the course as designed, and a learner who comes back finds where they left. The locks are pedagogical choices (an assessment after its practice, a narration heard before going on), so opening them is a decision someone takes, never a default; losing progress silently is worse than keeping it in the learner's own browser (nothing leaves it; no account, no cookie, no server). A showcase says `--progress none --navigation free` (nothing kept on a marketing site, a visitor goes where they like); a review link will say `--navigation free --noindex`, its progress to choose with the review.
- **The page.** `index.html` gets the course's `title` and `description` (`<meta name="description">`, Open Graph `og:title`, `og:description`, `og:type`, a Twitter card), `--noindex` (`<meta name="robots" content="noindex">`), `--url` (the absolute address of the module: `<link rel="canonical">`, `og:url`; an address without a file name is the module's folder), and `--image` (a PNG, JPEG or WebP copied as `share.<ext>`, `og:image` absolute with `--url`). **The course has no thumbnail**: the app draws one per project in its data folder (`<data>/thumbnail.png`, machine-local, never in the course), so the build takes the image it is given rather than invent one; `E_WEB_IMAGE`, `E_WEB_URL`, `W_WEB_IMAGE_WITHOUT_URL` (sharing previews only read absolute addresses).
- **What hosting must give.** One origin per publisher at least (localStorage is per origin: two courses with the same id on one origin share their progress; a course's pages could read another's), HTTPS, and the folder served as it is; `X-Robots-Tag: noindex` on a whole review publication is the host's (the free objects' frames are pages of their own).
*Revisit if:* the review needs progress kept per reviewer, or publication needs progress on the server (accounts): a fourth adapter beside `local`, the course data unchanged; or the format gets a sharing image or a short description of its own (course.json `share`).
*Amended on 14 Oct 2026 (the privacy policy's drafts):* with `--progress local` the learner is told, quietly and in the module's language, that the progress stays in this browser, and may clear it: a last line of the outline (and of the page an `exit` leaves), « Votre progression reste dans ce navigateur. » and « Effacer ma progression », which asks once (« Effacer » / « Annuler »), then ends the visit without saving it, removes `elabora:web:<course id>` and starts the module again (labels `progressKept`, `clearProgress`, `clearProgressConfirm`, `clearProgressYes`, `cancel`, in the seven languages). It is in the player, as the adapter it resets is: `player.js` and `player.css` change for every build, and nothing else does; a module with `showMenu: false` says it only when the visit ends.
*Amended on 29 Oct 2026 (the founder's policy for handles, elabora-cloud C33):* the progress kept with `local` is dated at each save (`savedAt`, milliseconds, beside the kept elements) and forgotten a year after the last one: the adapter removes `elabora:web:<course id>` and starts afresh (`WEB_PROGRESS_KEPT_MS`). Each visit saves, so each visit renews it. Elabora's hosting enforces the same year for everything an origin keeps, whatever player runs there, with a cookie of its own and `Clear-Site-Data` (C33); the player's rule holds on any other server too. A module on Elabora's hosting may meet a new address when its author changes their handle: a new origin, so learners start again there (no redirect, the founder's decision).
