ASE Academy — Lesson Standard (the full "instructions")

This is the complete, consolidated spec for how every ASE Academy lesson is built, covering all the modifications made across sessions (home + work). Apply ALL of it to every unit/lesson going forward (Unit 2 → 10, then B3–B6). Built and proven on B2 Unit 1 "Shop Safety & Survival" (live at Firestore academy_lessons/B2-1, admin-only while js/academy-config.js coming-soon switch is on).

Two layers: PLAYER features live in simulator.html and apply to EVERY lesson automatically. CONTENT features are baked into the lesson HTML by the build passes in this folder.


0. MASTER LESSON TEMPLATE (the canonical structure + order of every lesson)

Approved by Mario 2026-06-12, grounded in professional lesson-plan frameworks (Madeline Hunter, Gagné's Nine Events, the 5E model). EVERY lesson in EVERY unit follows this. This section is the authoritative build checklist; the numbered sections below (1–5) and the dated notes are the implementation detail. Status tags: [LIVE] = built into the player and/or B2 Unit 1; [TO BUILD] = approved, not yet built.

A. Overview page (X.0) — the lesson's front door [LIVE except where noted]

B. Every teaching page [LIVE]

C. End of every lesson (this exact order)

  1. "The Big Picture" recap card — the LAST content block: a 4–6 bullet plain-English summary (closure before assessment). .bigpicture navy card; authored per lesson, grounded in its goal/objectives/key facts. [LIVE, all 8 B2-1 lessons]
  2. Match the Key Terms — the drag-to-match game; vocabulary check BEFORE the content quiz. Title sits outside its box and joins the sticky-freeze. [LIVE]
  3. Lesson Quiz — a distinct end-of-lesson 10-question assessment. Remediation rule: score under 70% shows a "Review these pages, then retake the quiz" prompt linking back into the lesson. [TO BUILD] (Note: per-page Quick Checks are now also 10q from the same category pool — when building this, confirm with Mario how the end-of-lesson quiz should differ, e.g. draw from ALL the lesson's pages, or hold it until the question bank is tagged per lesson.)
  4. LAB Skill Task — the green .labskill card (a divider sits before it); its title renders outside the box; auto-feeds the end-of-unit "🎓 Skills You Learned" checklist. [LIVE]

Order rationale (don't reshuffle): overview/hook → teach with per-page checks → recap → vocabulary → assessment → hands-on practice. That is the professional-lesson-plan sequence.

1. Module / unit structure

2. Overview page (the first page of every lesson) — CONTENT + PLAYER

3. Every teaching section (each <h3 class="acad-h3">) — CONTENT

3b. Lab Skill Activity (.labskill) — CONTENT, the most important component

Where a lesson teaches a hands-on skill, add a .labskill card (a green "🔧 Lab Skill Activity" action card, distinct from the safety/goal boxes). Each one MUST have: .lab-kicker ("🔧 Lab Skill Activity"), .lab-title (the skill name, also put it in data-skill="..."), .lab-goal ("What you will practice"), a .lab-req row with a .lab-ppe box ("PPE required") + a "You will need" box, numbered .lab-steps (an <ol>), and a .lab-done box ("✅ You did it right when…"). Do NOT reuse the class lab — that collides with the .steps .lab "How to do it" label; the container class is labskill. The end-of-unit Skills page auto-collects every .labskill (by data-skill/.lab-title), so every lab card automatically becomes a tracked skill.

3c. End-of-unit pages (automatic) — PLAYER

After the lessons the player appends, in order: a 📖 Glossary page (every key term A–Z, from P.terms), then any Pre/Post-Test/Results/Practice-Hub the doc defines, then a 🎓 Skills You Learned page as the VERY LAST step (a progress checklist of every .labskill in the unit, each linking back to its lesson). The TOC shows a green ✓ on a lesson only when ALL its pages are visited.

3d. Page numbering, mini-TOC, scrubber — PLAYER (automatic)

4. Every page — PLAYER

5. Player-wide UX (automatic for all lessons) — PLAYER

6. Hard rules (from CLAUDE.md)


Build pipeline (this folder)

Source of truth: u1_enhanced.json ({result:{lessons:[{number,title,innerHtml,keyTerms}]}}). Order of the content passes that produced it (each is a Workflow of one agent per lesson + a small python "apply" that inserts the result without touching approved text):

  1. author → the lesson body (30-sec table, Goal, intro, sections, subheads, steps, safety, quizzes).
  2. popize → stat row, myth-vs-fact, icon list, <mark> highlights.
  3. asetask → the per-section ASE task callouts.
  4. bigterms → one "remember these" callout per lesson.
  5. overview → the 2–3 paragraph lesson overview on the first page.
  6. images → 2–3+ images per section. (The agent prompts are in workflows/; match results to lessons by ARRAY ORDER, the agents' number field is unreliable.)

Rebuild + publish after editing u1_enhanced.json:

python3 functions/scripts/academy-build/assemble_unit1.py    functions/scripts/academy-build/u1_enhanced.json
python3 functions/scripts/academy-build/build_sim_harness.py functions/scripts/academy-build/u1_enhanced.json
cd functions && node scripts/seed-academy-b2-unit1.js
cd .. && npx @11ty/eleventy && firebase deploy --only hosting --project ase-portal-5d37c

build_sim_harness.py lifts the real player JS out of simulator.html; the pop CSS, scroll-reveal, shape-wrap SVGs, and wrap_images/declutter_titles/sds_prefix/fix_checks transforms live there. The .py scripts have hardcoded absolute paths — fix them first on a different machine.

After building/editing a unit's lessons, also refresh the image-generation worklist so it carries the exact prompts the lessons use (real filenames, grouped by the precise lesson section) instead of generic category placeholders:

python3 functions/scripts/academy-build/update-image-helper.py functions/scripts/academy-build/u1_enhanced.json B2 1

That prepends every .imgph/.aiprompt from the source into question-image-helper.html's "ASE Academy" view (idempotent per module+unit via the b2-u1- uid prefix). Mario generates those in Google Flow and saves each under the shown filename. Re-run it for every new unit (pass its source, module, unit number).

Lesson images — drop-folder → Storage → live lesson (2026-06-12)

Real lesson art is wired WITHOUT rebuilding/re-seeding the lesson doc (the live doc has in-place edits not in the source, so never rebuild it). Pipeline:

  1. Mario generates images in Google Flow and drops them in academy-media-incoming/ (repo root, outside the build), named EXACTLY as the Question Image Helper card shows (e.g. b2-u1-l1-osha-inspector.jpg).
  2. node functions/scripts/upload-academy-media.js — uploads every file to Storage academy_media/ (bucket is public-read) AND rewrites the player's media manifest between the ACADEMY_MEDIA_START/END markers in simulator.html (the Set of filenames that actually exist, so no 404s for ungenerated art).
  3. node functions/scripts/wire-academy-images.js [DOC_ID] [SOURCE_JSON] — patches the LIVE Firestore lesson doc IN PLACE, injecting data-file="<filename>" onto each .imgph by matching its caption to the source's save as filename (the build strips the dev .aiprompt, so the live placeholders carry only captions). Idempotent; backs up the doc to academy-build/_doc_backups/ first. Run ONCE per unit doc — after that, every .imgph carries its filename, so future image drops just need steps 2 + deploy.
  4. npx @11ty/eleventy && firebase deploy --only hosting (the manifest lives in simulator.html). PLAYER side (simulator.html, automatic): the lesson renderer reads each .imgph's data-file; if that file is in window.ACADEMY_MEDIA, it loads the real academy_media/ image, else the gray placeholder. Lesson docs are cached in memory only, so a fresh load shows the new art (no cache bust needed).

Lead + thumbnail-strip layout (2026-06-12, final): the consistent rule Mario approved (combines the "one image per topic" and "lead + thumbnails" options). Each TOPIC = a run of consecutive images (one subhead's art). The FIRST image becomes the .lesson-lead — floated, alternating lead-left/lead-right across topics, fixed size (desktop 44% / max 440px). EVERY extra image becomes a .figstrip strip (.lesson-thumb, click-to-enlarge) dropped at the END of that topic (before the next subhead/callout). The strip spans the FULL page width and the images grow evenly to fill it (2 extras = halves, 3 = thirds, 4+ wraps; a lone extra centers at 680px) — Mario's request: no empty space beside the strip. So a topic with 1 image and a topic with 6 read the same — that consistency is the point. Topic boundary = _acTopicEnd (next subhead/heading/callout). Logic is in the image-processing pass of _acSectionToPages; the float/strip CSS is in the injected lesson CSS. (This deliberately shows fewer big images inline; the extras live in the strip — addresses "too many big images / no consistent pattern".)

Per-lesson HERO banners: each lesson overview (.0) shows a wide banner b2-u<unit>-l<lesson>-hero.jpg (display aspect 2/1; falls back to placeholder). The 8 Unit 1 hero prompts are in the Question Image Helper "ASE Academy" tab — add new units' heroes with functions/scripts/academy-build/add-unit1-heroes.py (copy + adjust the LESSONS list for each unit; idempotent).

Previews (open the file)

mockups/academy-b2-simulator-view.html (in-simulator, faithful), mockups/academy-b2-unit1-preview.html (plain), mockups/text-formatting-styles.html (the 10 styles), mockups/academy-animations.html (10 CSS animations Mario can pick), mockups/qz-widget-test.html (quiz widget).