ASE Collision Test Prep โ€” Project Brief for Codex

Read this whole file at session start. It is the source of truth for how to work on this codebase. The user is non-technical and works visually โ€” keep responses plain-English, prefer screenshots over code excerpts, and confirm scope before big multi-file changes.


๐Ÿ“Œ RESUME FROM HERE โ€” Active hand-off notes (July 10, 2026 โ€” Tools slides + Lab v2 grading card SHIPPED; Mario's big L12/labs/safety feedback batch IN PROGRESS)

When you start the next session, read this first, then say: "Welcome back. The new Tools slides (Key-Terms-style, all 68 lessons), the redesigned Lab Assignment page with your tap-to-grade Instructor card, the Do-vs-Don't slide type, and the L01/L04 fixes are all live. Next up is your L12 feedback batch (numbered lists, per-substrate cleaning slides + labs, product comparisons, visual defect slides, the be-very-specific pass), then the per-tool labs and the two new early lessons. Continue with the L12 batch?"

Shipped + LIVE this session (2026-07-10, deployed to ase-portal-5d37c)

โœ… L12 BATCH DONE (2026-07-10 PM, deployed): numbered lists (L12 contaminants + sequence via "1." sub-lines โ†’ circled numbers); NEW "Step 1 Up Close: tar/release removal" slide (3M 08984, soak-wipe-one-way, done-check) + ANIMATION PROMPT; solvent-vs-waterborne W&G comparison slide (PPG DX330 named); "Know Your Rust by Sight" 3-card visual (haze/flakes/pits, macro prompts + animation); haze slide made SPECIFIC (maroon Scotch-Brite 7447 โ‰ˆP320-P400 by hand, gray 7448 too fine, DA P180-P320, raking-light + solvent-cloth done-check); wire-wheel grits (36-80 flap disc); steel cleaning lab in L12 + substrate labs distributed to their home lessons: L13 aluminum (dedicated-tools slide + lab), L14 plastic (SEM Plastic Prep lab, water-sheeting check), L15 e-coat (clean+scuff lab, cut-through rule), L17 magnesium/mixed-metal (chemical-only cleaning slide + 3-substrate lab). L04 "three verbs" โ†’ own slide (26B). Common Questions restructured academy-wide: 72 files, 279 Q/A pairs, answers as sub-bullets UNDER questions.

๐Ÿ”ด NEXT UP โ€” remaining feedback batch (tasks #14/#15/#18/#19)

  1. Safety expansion (task #19): F04 += fire extinguisher PASS, fire blankets, fluid-by-fluid spill response (coolant/oil/brake/trans/hydraulic/PS) + labs; F05 += eyewash station how-to (15-min rule) + lab. Then reseed B2-F4/B2-F5.
  2. Phase B (task #14): one lab per MAJOR OPERABLE tool across all lessons (consumables/reference items excluded), grounded in the I-CAR lab exercises (`OCPs/`I-CAR ACADEMY 01/05/07 "Student Lab Exercise" PDFs).
  3. Phase C (task #15): two new early lessons โ€” Panel Cleaning (two-towel, I-CAR EE360) + Pre-Repair Health Scan (Matco Maximus 5.0, every vehicle, before touching it) โ€” each with a lab, inserted early with the early block RENUMBERED (sequential). Use the showcase designs (STRIP IMAGES + interactive explore) in lessons where they fit.

๐Ÿ“Œ (ARCHIVED) Active hand-off notes (July 9, 2026 โ€” ALL B2 IMAGE PROMPTS REVISED to the signature-shop + strict-zone spec)

When you start the next session, read this first, then say: "Welcome back. Every ASE B2 image prompt was rewritten to the new signature-shop + strict-zone rules โ€” office people are locked inside the office, each scene names its exact zone, and strict PPE is baked in. The media tracker is live with all 1,443 revised prompts. Want me to start generating a batch from the new prompts, or something else first?"

What this session did (all LIVE + deployed to ase-portal-5d37c)

Next up


๐Ÿ“Œ (ARCHIVED) Active hand-off notes (July 7โ€“8, 2026 โ€” CURRICULUM COMPLETE (all 67 lessons live) + Academy menu redesign + real-photo image pipeline; plus L01 reference-lesson polish, Media Style Guide, player-UX batch)

When you start the next session, read this first, then say: "Welcome back. The ENTIRE B2 curriculum is live โ€” all 67 lessons (F01โ€“F10, ASE Guide, L01โ€“L56) authored, quizzed, seeded, menu-wired, and deployed. The Academy menu got its redesign (resume strip, icon buttons, time chips), the real-photo image pipeline is proven (L09 has all 21 images live, made FROM Mario's own shop photos, plus the first PDF-book-grounded image in L51), and L01 carries the reference-lesson polish. Want me to run the next image batch, roll the L01 player polish wider, or review something first?"

โšก UPDATE (July 8, 2026 PM โ€” L36 images DONE + grounded-reference pipeline + credit correction)

โœ… CURRICULUM COMPLETE + menu redesign + real-photo pipeline (July 7โ€“8, home-Mac session)

๐Ÿ–ผ๏ธ AI MEDIA PIPELINE โ€” VERIFIED + IN USE (memory: higgsfield-media-automation)

๐ŸŽจ MEDIA STYLE GUIDE โ€” the binding rules for every image/video (functions/scripts/academy-build/MEDIA-STYLE-GUIDE.md, memory: academy-media-style-rules)

Ground each image in the real source material first (look at the `OCPs folders / textbook / I-CAR / customer-car photos, recreate in our DSLR style, colorize B&W references) ยท match the image to the slide's actual content ยท rotate all races + both genders (women required, not tokens) ยท full task-based PPE (glasses+gloves always; respirator for spray/sand/mix/chemical; full suit in booth) ยท documents are REAL only โ€” never AI-generate one; flag the shot, Mario supplies it, then generate the scene with his doc ยท vehicles real/varied/no logos, straight & parallel in the booth; structural panels stay attached (quarters/roof/rockers/rails), only bolt-ons on a paint stand; booth has lit glass side walls ยท defect realism (peel = flat matte patch + thin crisp edge; substrate colors: plasticโ†’black, steelโ†’green e-coat, aluminumโ†’dull silver) ยท show up to 3 real examples when a slide names an example-able thing ยท spray technique shop-accurate (full fan, 50% overlap, 6โ€“8", steady perpendicular) ยท spray guns = modern, with a 3M PPS disposable cup on top โ€” SATA 5500, Iwata SuperNova LPH400, or DeVilbiss DV1 only; an IDLE gun is hung on a wall-mounted gun holder with the air hose DISCONNECTED (never on a bench or plugged in) ยท real, correct automotive panel shapes โ€” fenders, quarters, doors, hoods, bumpers must look like actual car panels, never generic or distorted AI shapes ยท READ the ground-source material for the facts BEFORE writing the image prompt (the real part, tool, defect, procedure step, panel shape) so the prompt is accurate and specific, not guessed ยท video is deferred until the image set is approved.

๐Ÿ“„ L01 = the polished REFERENCE lesson (deck b2-l01 / doc B2-L01, 39 slides, all LIVE)

โ–ถ๏ธ PLAYER FEATURES shipped this session (academy-player.html โ€” apply to EVERY lesson, no reseed)

Still open / next


๐Ÿ“Œ (ARCHIVED) Active hand-off notes (July 3, 2026 โ€” v1.3 Foundation rebuild: F01โ€“F04 LIVE + menu-wired, F05โ€“F10 being authored, big player feature batch)

When you start the next session, read this first, then say: "Welcome back. The v1.3 Foundation unit is COMPLETE: all ten lessons (F01โ€“F10, 405 slides total) are live, hand-reviewed, seeded, and wired into the simulator menu, plus the shared ASE Guide. Unit 1 is rolling too: L01, L02 (with the VIN decoder interactives), and L03 are live and the Surface Preparation menu now shows the new 23-lesson lineup. Next: L04 Film Thickness Reading. Continue?"

The v1.3 rebuild (the main thread)

Player features shipped this week (all LIVE; details in the LOCAL locked-rules memory)

Authoring rules added this week (bake into every new lesson; F04 shows them all)

  1. Sparse bold: max ~1 bold phrase per bullet; group headers may be bold, list items stay normal (Mario: "too much bold").
  2. Split, don't shrink โ€” and PROVE it: after building, run the preview density sweep: open the deck at desktop viewport, click every #tree .row, flag scrollHeight>clientHeight or body li font < full size (31px at tscale 1.2), then split/trim offenders in the txt and rebuild. F04 and F01 both got fixes this way.
  3. Documents on their own viewer slide (above), interactive EXAMPLEs for worked questions, quiz 10Q PASSING 80 grounded in the bank inventories (scratchpad b2f_inventory_*.md etc.), one Technician A/B item per quiz.

โญ QUALITY over QUANTITY (Mario's rule, 2026-07-03)

Said while F05-F10 were being batch-drafted. Every lesson ships at the F01/F04 golden-template bar or it does not ship. Agent drafts are raw material, not finished work: read the whole lesson, fact-check against the bank inventories, enforce every locked rule, rewrite weak slides by hand, run the density sweep, and ship ONE fully-verified lesson at a time. Falling behind schedule is acceptable; shipping a mediocre lesson is not.

Unchanged standing points


๐Ÿ“Œ (ARCHIVED) Active hand-off notes (July 1, 2026 โ€” NEW shared "ASE Certification Guide" lesson built + F01's 2 ASE slides moved into it)

When you start the next session, read this first, then say: "Welcome back. There's now a dedicated, module-neutral ASE Certification Guide lesson (deck ase-guide, 38 slides, live at /academy-player/?deck=ase-guide) that covers what ASE is, all 5 question types with worked examples, test length/timing, how to register, results/pass-fail, and tips, grounded in the official ASE 2026 guide. F01's two ASE-intro slides were removed (F01 is now 43 slides). Want me to wire the guide into all 5 tests' Academy menus, drop in its ~23 images, or move on to F02?"

What this session did (all LIVE + deployed to ase-portal-5d37c)

NEXT UP for the ASE Guide

  1. Wire it into the Academy menu of all 5 tests as the first lesson (needs a Firestore seed per module + an entry in ACADEMY_LESSON_DECKS/ACADEMY_SECTIONS in simulator.html). Not done yet โ€” it's live only on the public ?deck=ase-guide link.
  2. Images (~23): 19 AI scene photos + 4 cheat-sheet panels (ase-cheat-1..4.jpg), dropped in ase-academy/slideshow/media/ase-guide/. 3 are best as REAL screenshots of my.ase.com (ase-account.jpg, ase-register.jpg, ase-schedule.jpg) โ€” marked "SCREENSHOT NEEDED" in the txt.
  3. FINAL audio pass once content is locked: node functions/scripts/generate-academy-audio.js ase-guide --force.

๐Ÿ“Œ (ARCHIVED) Active hand-off notes (June 30, 2026 PM โ€” F01 fully reworked to 40 single-topic slides + REMEMBER banner + sub-bullets + 40/40 no-shrink + new rules)

When you start the next session, read this first, then say: "Welcome back. F01 is fully reworked into 40 single-topic, entry-level slides with the new REMEMBER banner and indented sub-bullets, 40/40 padding with no text-shrinking, and real-brand image prompts. All live + reseeded to B2-F1. Want me to generate the F01 image checklist, have Mario drop the real F01 media, or start F02?"

What we did this session (all LIVE + reseeded to academy_slides/B2-F1; F01 is now 40 slides)

Build loop (unchanged): edit the lesson .txt โ†’ node functions/scripts/build-slideshow-deck.js "<path>" โ†’ reseed node scripts/seed-academy-slides.js b2-f01 --docid B2-F1 (from functions/, for the simulator) โ†’ node node_modules/@11ty/eleventy/cmd.cjs && firebase deploy --only hosting --project ase-portal-5d37c. SKIP audio (final-only step). Verify in the browser at ~1080p height (the /academy-player/?deck=b2-f01 public link reads deck.js directly).

NEXT UP (F01 + beyond)

  1. F01 real media: Mario drops the real photos (~20 image slots, filenames shown on each placeholder), the 4 document images B2-F01-IM1..IM4.jpg, and 3 real TDS (tds-clearcoat/basecoat/primer.jpg) into ase-academy/slideshow/media/b2-f01/, then deploy. (Offer to generate a one-list image checklist of every filename + prompt.)
  2. FINAL audio pass for F01 once content is locked: node functions/scripts/generate-academy-audio.js b2-f01 --force.
  3. Batch the other 65 lessons (F02โ€“F10, L01โ€“L56) applying ALL the new rules + features (single-topic split, REMEMBER banners, sub-bullets, real-brand prompts, 40/40 no-shrink).
  4. Restructure the simulator's in-Academy menu to the 66-lesson map; retire old decks. Veo demo clips for the wired video slots.

๐Ÿ“Œ (ARCHIVED) Active hand-off notes (June 30, 2026 AM โ€” F01 rebuilt/restructured + big no-scroll/consistency player pass + new workflow & authoring rules)

When you start the next session, read this first, then say: "Welcome back. F01 is rebuilt from its v1.1 txt with a documents-overview restructure, the player got a major no-scroll + font-consistency + slide-numbering pass, audio is now a final-only step, and several authoring rules are locked in. Want me to add the per-document deep-dive slides for F01, have Mario drop the real F01 media, or batch the next lessons?"

โš  Re-read the LOCAL memory files first (~/.Codex/.../memory/, this Mac only): academy-player-locked-design-rules.md, no-scrolling-in-slides.md, academy-audio-generate-last.md, academy-slideshow-asset-caching.md, academy-use-correct-slide-type.md, academy-image-prompt-realism.md. They hold every locked rule; don't trade one for another.

๐Ÿ”Š AUDIO IS NOW A FINAL-ONLY STEP (Mario's rule)

STOP running generate-academy-audio.js during iterative building. It's the LAST step, run ONCE per lesson after all content is locked (--force regen also fixes any NN.m4a numbering drift from added/removed slides). The build loop is now: edit the lesson .txt โ†’ node functions/scripts/build-slideshow-deck.js "<path>" โ†’ reseed node scripts/seed-academy-slides.js <deckId> --docid <DOCID> (from functions/, for the simulator) โ†’ node node_modules/@11ty/eleventy/cmd.cjs && firebase deploy --only hosting --project ase-portal-5d37c. SKIP audio. (To-do tracked: "FINAL STEP โ€” generate all Academy audio".)

โš  "I don't see my changes" has THREE causes (all fixed/known now)

  1. The SIMULATOR reads FIRESTORE, not the deck.js file. /simulator/?module=B2#academy โ†’ click a lesson โ†’ the embedded player opens ?lesson=<DOCID>&embed=1 (gated), reading academy_slides/<DOCID>. The doc-id map is ACADEMY_LESSON_DECKS in simulator.html (F1โ†’B2-F1, L01โ†’B2-L01 โ€ฆ). Rebuilding the deck does NOT update Firestore โ€” you MUST reseed: node scripts/seed-academy-slides.js b2-f01 --docid B2-F1. The public /academy-player/?deck=b2-f01 link reads deck.js directly (no reseed). (Simulator also caches the lesson in memory โ†’ a full page reload clears it.)
  2. Browser cache of deck.js / audio. Fixed: firebase.json sets /academy-decks/** + /academy-audio/** to no-cache, must-revalidate, AND the player appends ?cb=<page-load timestamp> (ASSET_BUST) to the deck + audio URLs.
  3. Stale editor/Drive view of the .txt. The lesson .txt sources live OUTSIDE this git repo at โ€ฆ/Sheridan Technical/ai-Codex/ASE B-Series Academy (DATA)/Academy Lessons/B2 Slides/ (e.g. ASE-B2-F01-Foundation โ€ฆ (v1.1).txt). Edits land on disk immediately; a stale open editor shows old content โ€” reload from disk, never overwrite-save the stale buffer.

F01 rebuilt + restructured (all in the v1.1 txt; rebuilt + reseeded to B2-F1; 21 slides)

Builder fixes (build-slideshow-deck.js)

Player pass (academy-player.html โ€” applies to ALL lessons, no reseed needed)

New AUTHORING rules (locked in SLIDESHOW-REWRITE-PROMPT.md + memory)

  1. PROHIBIT SCROLLING โ€” if too long, split into multiple slides.
  2. Teach basic โ†’ advanced.
  3. Documents must be REAL/original (TDS/SDS/SOP/TSB/OEM/EST/RECALL/P-pages) โ€” never fabricate/AI-generate; Mario supplies them, we only wire the DOCUMENT(S): slots.
  4. LEFT = old/wrong/bad, RIGHT = new/right/good on every side-by-side.
  5. Use the CORRECT slide TYPE for the info (comparing 2-4 things = comparison columns, not a content slide).
  6. Overview FIRST, then detail (one framing overview โ†’ a slide per item โ†’ assess).
  7. 40px top AND bottom padding on content slides, and NEVER shrink the body text to fit (Mario's rule, 2026-06-30). Every content slide has a consistent 40px gap above the title and 40px below the last content. The player may shrink the IMAGE to fit, but the body FONT must stay full size. If a slide is so full that the text would have to shrink (the player applies a --tscale < base), that is the signal to split it into two slides โ€” do not let the font shrink and do not let it scroll. Author slides to fit at full font with 40/40 padding.
  8. List items as separate bullets, not a crammed sentence (Mario's rule, 2026-06-30). When a point lists several items or examples (ADAS features, tools, materials, document types, steps), break them into separate bullet points so students remember them more easily โ€” never bury them as "features like A, B, and C" inside one prose sentence. A short fixed "must remember" set (2โ€“6 items) can use the REMEMBER: banner instead. Watch density (more bullets can trigger rule 7's split).

Where media goes (Mario asked)

ase-academy/slideshow/media/<deckId>/ (e.g. media/b2-f01/), served at /academy-media/<deckId>/. Filenames must match the lesson's IMAGE NAME / DOCUMENTS fields (README at ase-academy/slideshow/media/README.md). F01 still needs its real photos + the 4 real document images (B2-F01-IM1..IM4.jpg) + the 3 real TDS (tds-clearcoat/basecoat/primer.jpg) dropped in, then a deploy.

NEXT UP

  1. (Optional) add per-document deep-dive slides for Work Auth, Supplement, OEM in F01.
  2. Mario drops the real F01 media (photos + documents) โ†’ deploy.
  3. The FINAL audio pass for F01 (and each lesson) once content is locked.
  4. Batch the other lessons applying all the new rules.

๐Ÿ“Œ (ARCHIVED) RESUME FROM HERE โ€” Active hand-off notes (June 26, 2026 โ€” B2 re-rewrite: 66 lessons, L26 pilot built + many player/design fixes)

When you start the next session, read this first, then say: "Welcome back. Cowork re-delivered B2 as a 66-lesson renumbered curriculum, and Lesson L26 is fully built and live as the pilot/template (/academy-player/?deck=b2-l26). Want me to build the two remaining slide types (wrongright + comparison), batch the other 65 lessons, or something else?"

โš  FIRST read the LOCAL memory file academy-player-locked-design-rules.md (in ~/.Codex/.../memory/, this Mac only). It is the checklist of EVERY locked player/design rule Mario set, with a note of the layouts he REJECTED. He got frustrated when a later change silently broke an earlier rule โ€” re-read it before editing academy-player.html and never trade one locked rule for another. This AGENTS.md note is the portable summary.

What cowork delivered (the BIG change)

Cowork re-wrote and RENUMBERED the entire B2 unit into 66 lessons: Foundations F01โ€“F10, then L01โ€“L56 (Surface Prep L01โ€“L22, Spray Guns L23โ€“L28, Paint Mixing L29โ€“L44, Solving Problems L45โ€“L46, Finish Defects L47โ€“L53, Final Inspection L54โ€“L55, ADAS L56). This supersedes the old ~36 decks (old numbering is gone; e.g. old L11=spray-gun, new L11=aluminum). Source .txt files: โ€ฆ/ai-Codex/ASE B-Series Academy (DATA)/Academy Lessons/B2 Slides/. The cowork docs there: _HANDOFF-TO-Codex.md (coworkโ†’me), _REWRITE-PROMPT.md (the prompt Mario GIVES cowork โ€” edit this to change cowork's output), _KEYTERMS-AUDIT-FOR-COWORK.md (fix-list).

New cowork v4 slide types + fields

L26 pilot โ€” DONE + LIVE (the template)

Built end-to-end: node functions/scripts/build-slideshow-deck.js "<L26 .txt>" โ†’ node functions/scripts/generate-academy-audio.js b2-l26 --force โ†’ node node_modules/@11ty/eleventy/cmd.cjs && firebase deploy --only hosting --project ase-portal-5d37c. Reviews/keyterms/termsmatch were cowork-authored; I authored the 10-question final quiz into a === QUIZ === block in the L26 source. Public review URL (no auth): /academy-player/?deck=b2-l26. (Gated reseed node scripts/seed-academy-slides.js b2-l26 from functions/ is only needed when wiring into the in-simulator menu โ€” not done yet.)

Player/design rules fixed THIS session (all live; full list in the memory file)

Titles max 2 rows (fitLines + px max-width, stops at exactly 2 rows); key terms 4โ†’2ร—2 / 6โ†’3ร—2 fill (use all space, room for definition, no scroll); logo watermark on keyterms/termsmatch; Quick-Check feedback font = lesson body size; [VIDEO N] Option 4 video layout; labassignment hides the answer key; loading = spray-booth animation; quiz "Review every question" overlay no longer cuts the control bar; transcript = per-slide NARRATION; acronyms SOLID (audio gen spells them); content density = 3โ€“4 short bullets (cowork). Simulator dashboard performance cards stretch to fill.

NEXT UP

  1. Build the two remaining slide types: wrongright + comparison (builder + player), then they're ready for the batch.
  2. Batch the other 65 lessons (build deck โ†’ gen audio โ†’ deploy each), in waves, reviewing as you go.
  3. Restructure the simulator's in-Academy menu (ACADEMY_SECTIONS/units in simulator.html) to the new 66-lesson map, then retire the old decks.
  4. Generate the images (Mario's local pipeline โ†’ academy-media/<deckId>/) and the Veo demo clips (the VIDEO PROMPT/[VIDEO N] slots are wired and waiting).

SEPARATE project (do NOT auto-start)

โ€ฆ/ai-Codex/ASE B-Series Academy (DATA)/CLAUDE_CODE_START_PROMPT.txt is the kickoff for a separate Next.js "ase-lab-system" app (lab grading rubrics, AI feedback, new Firestore collections) โ€” NOT the slideshow. Mario @-referenced it once; not started. The labassignment instructor blocks are designed to eventually feed it.


๐Ÿ“Œ (ARCHIVED) RESUME FROM HERE โ€” Active hand-off notes (June 21, 2026 โ€” Slideshow Academy: B2-L01 fully built + heavy player polish)

When you start the next session, read this first, then say: "Welcome back. The narrated SLIDESHOW Academy for B2 Lesson 1 is fully built and live (interactives + quiz + title images), and the player got a big polish + reliability pass. Want me to wire up the next lesson cowork delivers (F1 or L02), generate the L01 images, or something else?"

This session was ALL on the I-CAR-style narrated SLIDESHOW player (academy-player.html + academy-decks/<id>.deck.js + builder functions/scripts/build-slideshow-deck.js + audio functions/scripts/generate-academy-audio.js + seeder functions/scripts/seed-academy-slides.js). This is SEPARATE from the older scrolling-lesson Academy. Full local detail is in the memory file academy-slideshow-player.md (LOCAL to this Mac only โ€” this note is the portable version).

Division of labor (LOCKED with cowork)

B2-L01 is FULLY BUILT & LIVE (35 slides) โ€” the template for every lesson

Source: โ€ฆ/ai-Codex/ASE B-Series Academy (DATA)/Academy Lessons/B2 Slides/B2-L01-slides.txt. Built with node functions/scripts/build-slideshow-deck.js "<path>", audio with node functions/scripts/generate-academy-audio.js b2-l01 --force, then node node_modules/@11ty/eleventy/cmd.cjs && firebase deploy --only hosting --project ase-portal-5d37c, then reseed the gated copy from functions/: node scripts/seed-academy-slides.js b2-l01. Review publicly at /academy-player/?deck=b2-l01 (no auth). Contains: splash + objectives + 4 sections (each: divider โ†’ content slides โ†’ quick-check โ†’ optional lab โ†’ DYK โ†’ progress review) + 2 summaries (recap + cheat sheet) + quizintro + 10-Q final quiz. Interactives built: a real VIN decoder (vindecoder, all 17 positions + check-digit math + free NHTSA vPIC lookup), a manual VIN drill (vindrill, random valid VIN, student decodes, self-checks), and a 3-tab OEM Procedure Walkthrough (oemwalk: Find it / Read it / Safety) built on the REAL 2010 Honda Fit roof R&R procedure (the $42M-case vehicle, from Mario's PDF).

New slide types + builder fields added this session

Builder now parses, beyond the original 7: lab (YOU NEED / STEPS / DONE WHEN lists), quizintro + a === QUIZ === โ€ฆ === END QUIZ === block (PASSING + --- Q --- questions), and the Codex interactives vindecoder / vindrill / oemwalk (oemwalk fields: FIND IT, READ IT, SAFETY ITEMS, DOC TITLE/META/NOTE, STEPS). Content slides can now carry a SOURCE: field (renders a clickable "Source: hostname" link โ€” used for 49 CFR 565 โ†’ ecfr.gov). cheatsheet/interactive/hotspot still render blank (use summary for a cheat sheet).

Title images on splash + dividers (NEW)

The opening splash AND every section divider now take an IMAGE NAME/IMAGE PROMPT that summarizes the title, rendered full-bleed behind a strong blue overlay so the white title stays readable. Splash stacks the per-lesson image over /images/academy/title-bg.jpg so it falls back gracefully until generated. Image naming pattern (locked with cowork): splash = B2-LXX-TITLE.jpg, divider = B2-LXX-S{n}-DIV.jpg, content = B2-LXX-S{n}-IMG{n}.jpg. Splash/divider images need NO caption; content images get a one-sentence caption. L01's splash prompt (B2-L01-TITLE.jpg) is in place; dividers + the rest still need images generated.

Player polish + reliability fixes this session (all LIVE)

Cowork docs updated (so the rules persist)

functions/scripts/academy-build/SLIDESHOW-LESSON-FORMAT.md and โ€ฆ/B2 Slides/_REWRITE-PROMPT.md: labs are back, title images on splash+dividers, **bold** supported, the interactive markers, content SOURCE: field. Per the "cowork-level changes โ†’ write instructions" rule, any future source/authoring change must update these.

Next up

  1. When cowork delivers the next lesson (suggest F1 or L02 Inspect/Plan/Read) with its markers + image prompts, build its quick-checks, progress reviews, 10-Q quiz, ASE tags, and any VIN/OEM-style interactives. For an OEM walkthrough, get a real OEM procedure PDF from Mario first.
  2. Generate the L01 images (splash B2-L01-TITLE.jpg, the 4 divider โ€ฆ-DIV.jpg, and the content โ€ฆ-IMG{n}.jpg) โ†’ drop in academy-media/b2-l01/ โ†’ they appear automatically (no redeploy needed for images? they're hosting assets, so a deploy IS needed to publish new files; the deck already references them).
  3. Roll the same full pattern out to the rest of B2, then B3-B6.

๐Ÿ“Œ (ARCHIVED) RESUME FROM HERE โ€” Active hand-off notes (June 18, 2026 โ€” Mario switching to Mac Studio)

When you start on the Mac Studio, read this first, then say: "Welcome back on the Mac Studio. B2 Academy Unit 1 is rebuilt and live with the new split-file format, all Spanish removed, real image captions, the slow-load (IndexedDB) fix, and the layout polish. Want me to build the other 4 B2 units (Foundations, 2, 3, 4), keep polishing Unit 1, or something else?"

โš ๏ธ Lesson files were RESTRUCTURED โ€” one header file + one file per section (June 18)

Image fields are now IMAGE PROMPT + IMAGE NAME + IMAGE DESCRIPTION (June 18)

All Spanish removed โ€” Academy is English-only now (June 18)

build-v3-unit.js other changes this session (run node scripts/build-v3-unit.js <unit> from functions/)

simulator.html (player) changes this session โ€” all LIVE, deployed to ase-portal-5d37c

Process + gotchas (IMPORTANT for the Mac Studio)

Next up

  1. Build the other 4 B2 units: node scripts/build-v3-unit.js 0 (Foundations), 2, 3, 4 (from functions/, with serviceAccountKey.json). They parse clean already; only B2-1 is published. When launching all units, update ACADEMY_SECTIONS.B2 / ACADEMY_UNIT_CATS.B2 in simulator.html to the 5-unit structure.
  2. Generate the real lesson images (replace gray placeholders) named per the IMAGE NAME ids, upload via upload-academy-media.js, deploy.
  3. Optional: shorten any still-long key-term definitions (cowork) so the Match-the-Key-Terms game stays tidy; add WHERE THIS IS HEADING / images to any sections missing them.

๐Ÿ“Œ (ARCHIVED) RESUME FROM HERE โ€” Active hand-off notes (June 17, 2026 โ€” Mario continuing at home)

When you start the next session, read this first, then say: "Welcome back. The v3 B2 Academy is the new canonical content, and Unit 1 (Surface Preparation) is built, live (admin-only), and heavily polished as the template. Want me to roll the verified-fact boxes + topic sub-titles + image/paragraph balance out to the rest of Lesson 1 and Lessons 2-9 (via cowork), or build the other 4 units?"

The v3 PILOT curriculum is now the canonical B2 content

The v3 build script (NEW): functions/scripts/build-v3-unit.js

Run from functions/ with serviceAccountKey.json: node scripts/build-v3-unit.js 1 (build+seed Unit 1 โ†’ B2-1; add --dry to preview). Module-aware SRC (B2 Lessons, B3 Lessons, โ€ฆ). It: tolerates the (v3) header + PILOT NOTE; orders lessons by FILENAME number (a stray internal LESSON number can't scramble it); HIDES instructor blocks from students but stores them in section.instructor[] for a future instructor view; places each [IMAGE N] inline with its IMAGE IDEA and drops untagged IMAGE IDEAS (prints a WARNING list of 1:1 violations); renders the violet "Did You Know?" box, the un-boxed "How to do it" (big numbered steps + "What you need" attached on top), Common Mistakes as a sticky sub-title, Tools/Supplies as linked bullet lists, the lesson Key Terms & Reference glossary page, and ## Topic lines inside THE WHY as sticky sub-titles. Backs up the doc to academy-build/_doc_backups/ first. Content changes need NO hosting deploy (the doc is read live); only simulator.html (player) changes need build+deploy.

Player (simulator.html) layout/UX changes this session (all LIVE)

Cowork instructions updated: functions/scripts/academy-build/COWORK-LESSON-INSTRUCTIONS.md

Now documents: the correct field order; the active B2 Lessons/ folder; every image needs 1-2 substantial paragraphs + 1:1 [IMAGE N] โ†” IMAGE IDEAS (target exactly [IMAGE 1/2/3] per section); the DID YOU KNOW field (must be fact-checked + sourced, never invented); and ## Topic sub-titles in THE WHY.

The rollout loop (how to continue at home)

Cowork edits a lesson's THE WHY / images / facts / sub-titles in B2 Lessons/, saves the file back (same name); then run build-v3-unit.js <unit> to rebuild + reseed. Layout/feature work = edit simulator.html, then node node_modules/@11ty/eleventy/cmd.cjs && firebase deploy --only hosting --project ase-portal-5d37c.

Done on Lesson 1 (the template) so far

Full 3-images-per-section pass (cowork); 4 web-verified "Did You Know?" facts (John Eagle ~$42M case, 1981 17-char VIN standard, ALLDATA's 1986 origin, the 2016 scan-position-statement wave) with source links; topic sub-titles demoed on the "The Platforms" page; big-step "How to do it"; Common Mistakes sub-title; the 1.3 lab fixed (platform comparison, no off-topic featheredge); off-topic "featheredge grit"/"approved primer system" terms removed from L01 key terms.

Next up

  1. Roll the verified-fact boxes + topic sub-titles + image/paragraph balance to the rest of Lesson 1 and Lessons 2-9 (cowork passes โ†’ rebuild).
  2. Build the other 4 units (build-v3-unit.js 0, 2, 3, 4) once ready; update ACADEMY_SECTIONS.B2 / ACADEMY_UNIT_CATS.B2 in simulator.html to the 5-unit structure when launching all units.
  3. Verify the sticky breadcrumb scroll + big-window layout feel right; generate the real lesson images (replace gray placeholders).

Env note

Google Drive File Stream was sometimes extremely slow this session (3.5-minute eleventy builds, normally <1s). Deploys can auto-background; confirm with the Hosting URL output. Memory files saved this session are LOCAL to that Mac and do NOT sync: academy-lesson-float-layout, academy-teaching-page-sticky-title, academy-how-to-steps-format โ€” this AGENTS.md note is the portable version.


๐Ÿ“Œ (ARCHIVED) RESUME FROM HERE โ€” Active hand-off notes (June 12, 2026 โ€” Mario going home)

When you start the next session, read this first, then say: "Welcome back. ASE B2 Academy Unit 1 lessons got a big polish pass and the MASTER LESSON TEMPLATE is locked in. Want me to build the end-of-lesson 10-question Lesson Quiz (with the under-70% retake prompt), author Unit 2, or something else?"

The whole standard for academy lessons now lives in functions/scripts/academy-build/ACADEMY-LESSON-STANDARD.md โ€” read it (esp. section 0, the MASTER LESSON TEMPLATE). It captures everything below in detail. All changes this session are LIVE (B2-1 is admin-only via js/academy-config.js coming-soon switch).

What we did this session (all LIVE, B2 Unit 1):

๐Ÿšง Next up on the Academy (priority order):

  1. End-of-lesson 10-question Lesson Quiz (the master-template piece still unbuilt) with a review-and-retake prompt when score < 70% (links back into the lesson).
  2. Estimated-time + prerequisite lines on each overview page (master template, not yet built).
  3. Author Unit 2 ("How Paint Works") and beyond, using the full template + the build scripts.
  4. Generate the remaining lesson images (the ~166 placeholders + 8 heroes + the 2 new SDS/pictogram-chart spots) โ€” drop in academy-media-incoming/, run upload-academy-media.js, deploy.
  5. Tag the question bank per-lesson (future project) so Quick Checks get truly lesson-specific (today they pull from the lesson's ASE category).
  6. Still open from before: wire the lesson "Practice this section" button to its real ASE category (Unit 1 โ†’ F); Mario to pick which CSS animations to embed.

Scripts added this session (all in functions/scripts/, run from functions/ with serviceAccountKey.json):

upload-academy-media.js, wire-academy-images.js, add-bigpicture-recaps.js, enhance-sds-tds-content.js, swap-official-pictograms.js. All back up the live doc to academy-build/_doc_backups/ first and are idempotent. Build/deploy after player edits: node node_modules/@11ty/eleventy/cmd.cjs && firebase deploy --only hosting --project ase-portal-5d37c (the .bin/eleventy symlink keeps getting its exec bit stripped by Drive โ€” use the cmd.cjs form).


๐Ÿ“Œ (ARCHIVED) RESUME FROM HERE โ€” Active hand-off notes (June 10, 2026 โ€” Mario switching to Mac Mini at work)

When you start on the Mac Mini, read this first, then say: "Welcome back from the Mac Mini. ASE B2 Academy Unit 1 is fully built and live (admin-only). Want me to wire the lesson 'Practice' button to its ASE category, pick which CSS animations to embed, or start Unit 2?"

What we built this session (ASE B2 Academy โ€” Unit 1 "Shop Safety & Survival"), all LIVE:

The whole session was building the ASE Academy lesson system, grounded in Mario's `OCPs folder. Structure decision: each module uses a repair-order 10-unit structure (lessons flow how a tech fixes a car, foundation lessons before tasks, safety first) PLUS an ASE-category Aโ€“F exam index โ€” chosen over the old 6-ASE-category layout. B2 = 63 lessons / 10 units. Unit 1 (Safety, 7 lessons) is built as the canonical template and seeded live at Firestore academy_lessons/B2-1 (admin sees it now; students still see "Coming Soon" via js/academy-config.js switch).

Player features added/changed in simulator.html (apply to every academy lesson): ACADEMY_SECTIONS.B2 is now the 10-unit list (Unit 1 code:'1' live); each lesson doc's sections[] = its lessons, paginated by the player. New: TOC sub-item checkmarks as you visit; images alternate L/R (:nth-of-type); the per-page Quick Check is now a compact 5-question mini-quiz pulled from the question DB (window._acPickQuiz + ACADEMY_UNIT_CATS map [Unit 1โ†’F]; embedded in the iframe, rendered by the bridge), with a second-chance attempt, DB explanations, and a 0โ€“100% score; key terms clickable inline (_acWrapTerms upgraded) and listed at page bottom; the 10 "pop" formatting styles are baked in as CSS restyles of existing markup (.objectiveโ†’Goal banner, .instructornoteโ†’pull quote, .safetyโ†’hazard stripe, .steps olโ†’timeline, .am-title underline, drop cap) + content components (.statrow, .mvf, .iconlist, <mark>); dark-mode overrides for all of them; and the per-section .asetask ASE-task callout is restored (floats to page top).

Build pipeline + content saved to the synced project: functions/scripts/academy-build/ โ€” read its README.md AND ACADEMY-LESSON-STANDARD.md (the full consolidated "instructions": every component/feature an academy lesson must have, built across home + work sessions, to apply to every future unit/lesson). Newer features beyond the basics: each lesson's overview page title is the LESSON NAME + a "โฑ 30-Second Overview" badge with a 2-3 paragraph lesson roadmap; per-section ASE task callouts (multi-task = each on its own numbered line); a big-bold animated .bigterms "remember these" callout for any 2+ comparison; scroll-reveal animations (callout boxes + images fade in as they enter view); 2-3+ images per section; and search results show the lesson context + page number. Key files: u1_enhanced.json (the lesson SOURCE), assemble_unit1.py / build_sim_harness.py (rebuild the previews + the Firestore doc; the harness lifts the player JS from simulator.html), seed-academy-b2-unit1.js (in functions/scripts/, pushes the doc to Firestore), and workflows/ (the agent prompts for authoring/popize/asetask). โš ๏ธ those .py scripts have hardcoded absolute paths to this Mac โ€” fix the paths first if the Mac Mini's home/Drive path differs. The deeper notes live in the local auto-memory file memory/ase-academy-curriculum-plan.md (NOT synced โ€” this AGENTS.md note is the portable version).

Previews (open the file, self-contained):

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

๐Ÿšง Open / next up on the Academy:

  1. MASTER LESSON TEMPLATE approved (2026-06-12) โ€” ACADEMY-LESSON-STANDARD.md now opens with section 0: the canonical lesson order (objectives/hook โ†’ teach with per-page Quick Checks โ†’ recap โ†’ key-terms match โ†’ quiz โ†’ lab), built by comparing Mario's layout to professional lesson-plan frameworks (Hunter / Gagnรฉ / 5E). Of the 3 approved additions: (a) "Big Picture" recap card is โœ… BUILT + LIVE for all 8 B2-1 lessons (navy .bigpicture card, grounded recaps appended to each lesson via functions/scripts/add-bigpicture-recaps.js); STILL TO DO: (b) an end-of-lesson 10-question Lesson Quiz with a review-and-retake prompt when score < 70% (links back into the lesson, NOT just a score); (c) estimated time + (when a lesson builds on an earlier one) a one-line prerequisite note on the overview page. Quiz questions pull from the lesson's ASE category for now; tagging the question bank per lesson is an approved future project so quizzes get truly lesson-specific over time. Also LIVE since the template was approved: real lesson images wired from academy-media-incoming/ โ†’ Storage (upload-academy-media.js + wire-academy-images.js), per-lesson hero banners (8 prompts in the Question Image Helper), the consistent lead-image + full-width thumbnail-strip layout, numbered "On this page" TOC, magazine subtitle under each page title, and the double-divider-line fix.
  2. Wire the end-of-lesson "Practice this section" + "Section Test" buttons โ€” they call startDrill('1')/startSimulation('sectionExam','1') but '1' is not a real ASE category; map each unit to its ASE category (Unit 1 โ†’ F).
  3. Mario to pick which of the 10 CSS animations (mockups/academy-animations.html) to embed into lessons.
  4. Author the next units (Unit 2 "How Paint Works", then 3โ€ฆ10) using the same workflow patterns, then B3โ€“B6. Re-run authoring โ†’ popize โ†’ asetask โ†’ build โ†’ seed for each.
  5. Replace the .imgph placeholders with the real generated media (the 289 prompts in the Question Image Helper) once hosted in academy-gated Storage.
  6. Verify the live Unit 1 quiz pulls real B2 category-F questions (falls back to the whole B2 pool if F is thin).

๐Ÿ“Œ (ARCHIVED) RESUME FROM HERE โ€” Active hand-off notes (June 4, 2026)

When you start the next session, read this section first, then say: "Welcome back. Want me to build the simulator side-menu badges, the branded password-reset email, or pick up the Apps Script fix?"

โœ… Shipped & LIVE (2026-06-09):

โœ… Shipped & LIVE this session (2026-06-04):

โณ NEEDS VERIFICATION NEXT SESSION (Mario at work):

โœ… Verified / no action (this session):

๐Ÿšง OPEN / NEXT UP:

  1. Simulator side-menu badges (approved, not built) โ€” Weakness Drills (# categories <70%), a resume indicator on Review (paused practice/section test), Flashcards-left count, new support-reply count (cache it). See the June 3 spec below for the exact rules.

  2. Branded password-reset email โ€” the matching companion to the verification email (Console reset template is still Firebase's plain one).

  3. Apps Script doPost for the Simulator Reports sheet โ€” Mario to paste the code (Sheet โ†’ Extensions โ†’ Apps Script) so column G logs the per-test total (65) from the explicit fields the simulator already POSTs (correctCount/incorrectCount/totalQuestions), not the lifetime leaderboard number.

  4. Tool ID data โ€” populate tools_b2โ€ฆb6 at /tool-uploader (offer to generate starter B2 bulk-JSON).

  5. Newsletter extras not yet chosen โ€” a "welcome to the newsletter" auto-email (MailerLite automation, guided) and an auto plain-text version.

  6. First-visit simulator tutorial (mobile + desktop); support-ticket resolution notifications to students.

  7. Gear price tracker โ€” UNLOCKED (2026-07-09: Associates approved as collisionre03-20 + 3 qualifying sales done). Build PA-API 5.0 (Option C) when Mario asks.

  8. More SEO blog articles (for AdSense).

  9. IP protection guidance (Mario asked 2026-06-07, "later"): (a) Logo โ€” copyright vs trademark? Both apply and they protect different things: copyright protects the logo artwork automatically the moment it's created (registration via copyright.gov ~$45 strengthens enforcement); a trademark protects the logo/name as a brand identifier in commerce (USPTO TEAS ~$250โ€“350/class, or use โ„ข for free now / ยฎ only after registration). For a study-prep brand, the trademark on the name "ASE Collision Test Prep" + logo is the more valuable one โ€” BUT note "ASE" itself is a registered trademark of the National Institute for Automotive Service Excellence, so a mark including "ASE" may face refusal/objection; likely safer to trademark a distinctive non-ASE brand element. Recommend a quick consult with a trademark attorney before filing. (b) Prevent copying of Academy/Simulator questions: no method is 100%, layer defenses โ€” keep paid content server-gated (already doing: Firestore rules + server-side grants, never public URLs); add a Terms-of-Use / IP notice + per-account watermarking (embed the logged-in user's id faintly in content) to deter + trace leaks; disable easy bulk copy (right-click/select where reasonable, though determined users bypass it); rate-limit/throttle question reads per account to stop scraping; consider registering the question bank as a copyrighted compilation. Build a short written plan when picked up.

  10. ASE Academy โ€” Phase 1 buy buttons DONE (2026-06-07), Phase 2 still open. The $99 Simulator vs $298 Simulator+Academy bundle buttons are now LIVE on all five module pages (b2-b6, replacing the old 1/2/3-month tiers) AND on the dashboard module cards, with a "$199 upgrade from your dashboard" note (links /dashboard?upgrade=BX). Server grant grantAcademyOnPayment (functions/index.js, ACADEMY_FULFILL_CATALOG) fulfills the 10 academy price IDs โ€” bundle unlocks simulator+academy 30d, upgrade adds academy only. b2/b3 use a 3-param startCheckout(priceId, months, btnElement, plan); b4/b5/b6 use 2-param startCheckout(priceId, btnElement, plan) (watch this if editing). Phase 2a DONE (2026-06-07): secure vault + viewer LIVE. (1) New Firestore collection academy_lessons/{MOD-CODE} (e.g. B2-A), one doc per lesson holding the full self-contained lesson HTML; firestore.rules gates read to isAdmin() || hasAcademyAccess(resource.data.module) โ€” paid IP, never world-readable. (2) Mario's real B2-A "Surface Preparation" lesson (from ase-academy/ASE B2 - Painting and Refinishing/A. Surface Preparation/) is seeded into academy_lessons/B2-A via functions/scripts/seed-academy-b2a.js (run from functions/ with the local serviceAccountKey.json; re-run to re-seed after edits). (3) Simulator Academy viewer (renderAcademyView + openAcademyLesson/_renderAcademyLessonFrame/academySectionListHtml in simulator.html): paid users see the section list (live vs "coming soon" from ACADEMY_SECTIONS[mod][i].live), click a live lesson โ†’ fetch the gated doc โ†’ render in a sandboxed iframe (sandbox="allow-scripts", style-isolated; injected CSS hides dev-only .aiprompt notes; postMessage bridge auto-sizes height + routes the lesson's in-page [data-sim] button + .html/DEMO links to the parent). Lesson HTML cached in memory only (window.__academyLessonCache), never localStorage. A parent "Practice this section" button โ†’ window.startDrill(code). Phase 2b OPEN: (a) admin authoring/uploader page so Mario publishes/edits lessons without re-running the seed script (today edits need the script); (b) vault + wire the 5 practice DEMO sub-files (Pre/Post Test, Key Terms, Tools&Equipment ID, Student Skills Sheet, Lab Skills Evaluation) โ€” currently their links show a "coming soon" alert; (c) refine B2-A together as the canonical template + replace .imgph placeholders with real images (needs academy-gated Storage hosting); (d) author B2-Bโ€ฆF and B3-B6. Mario should also test one real bundle purchase end-to-end to confirm the unlock. โš ๏ธ ACADEMY "COMING SOON" SWITCH IS ON (2026-06-07). Single source of truth: js/academy-config.js โ†’ window.ACADEMY_COMING_SOON = true, loaded in _includes/layout.html (covers b2-b6 + dashboard) and simulator.html . While TRUE: the $298 Sim+Academy bundle button shows "ACADEMY COMING SOON" (disabled) on b2-b6 + dashboard module cards; buyBundle/upgradeAcademy + the ?upgrade=MOD modal show a coming-soon message; the in-simulator Academy view shows a "Coming Soon" card + "SOON" nav badge to students (ADMINS still get the real lesson viewer). The $99 Simulator stays fully on sale and existing students are untouched. To GO LIVE: (1) finish the site-wide pricing audit (task #12 above), (2) flip that one line to false, (3) redeploy hosting. Reason it's on: Mario is mid-migration to the new $99/$298 pricing and didn't want the Academy bundle purchasable until the Academy is done + all pages show consistent pricing.

  11. Batch email to Mario's old body-shop customer accounts (asked 2026-06-07, "later") โ€” outreach to potential customers. Mario has a list of former body-shop customers he wants to email about asecollisiontestprep.com as a marketing/launch outreach. Build it the RIGHT way, NOT a raw SMTP blast: (1) Import the list into MailerLite (already our ESP, free tier ~1k subscribers / 12k emails-mo) as its OWN segment/group (e.g. "Body Shop Past Customers") so it's separate from the newsletter opt-ins. (2) Compose a one-off campaign (reuse the branded navy/orange template like the newsletter builder) with a clear intro ("you may remember us from [shop]"), value pitch, and CTA to the modules/simulator. (3) CRITICAL compliance (CAN-SPAM): these are people he has a prior business relationship with, but they did NOT opt into THIS list โ€” so the email MUST include a working unsubscribe link (MailerLite adds {$unsubscribe} automatically), a real physical mailing address, a truthful subject line, and clear sender identity. Do NOT mass-import-and-blast cold purchased lists. Honor unsubscribes immediately (MailerLite does this). (4) Deliverability: warm up โ€” send in smaller batches, not all at once, to protect the sending domain reputation; ensure SPF/DKIM/domain are verified in MailerLite (the Resend verified-domain work is separate; MailerLite needs its own domain auth). (5) Track opens/clicks in MailerLite to see who's interested. Decide with Mario: send from MailerLite campaign (recommended, handles unsub + compliance + deliverability) vs a custom Cloud Function (NOT recommended โ€” would have to hand-build unsubscribe, suppression, throttling, and risks the iCloud/domain reputation). Likely a guided MailerLite setup + a drafted campaign.

  12. Site-wide pricing audit โ€” replace OLD monthly tiers with the new strategy (asked 2026-06-07). New strategy = Simulator $99/mo (1 month) + Simulator&Academy bundle $298 + $199 academy upgrade. The OLD 1/2/3-month tiers ($99/$158/$207, "2 Month"/"3 Month", $79/mo, $69/mo) are GONE on b2-b6 (done) but still appear elsewhere. A 2026-06-07 scan found leftovers in: all blog articles (blog/*/index.md โ€” each ends with an .article-pricing 3-tier block per the old AGENTS.md rule 12; ~17 files), _includes/article.html (shared .article-pricing styles/markup), dashboard.html (displayed 2/3-month pricing + MODULE_PRICES; NOTE: keep priceToModuleMap 60d/90d entries so any legacy/in-flight purchases still unlock, but remove the DISPLAYED old tiers), faq.html (pricing answer), index.html, profile.html, _data/previewModules.js (old 60d/90d price IDs). Also update AGENTS.md rule 12 itself (the article-pricing convention) to the new strategy so future articles don't reintroduce the old tiers. Ignore mockups/ (throwaway). Confirm exact new-pricing wording per context with Mario before mass edits (esp. blog CTA blocks: likely "$99 Simulator / $298 Sim+Academy" with the bX#pricing CTA).

  13. Welcome email polish (asked 2026-06-07, "later"): in the welcome email (_welcomeEmailHtml in functions/index.js) (a) replace the social-media word buttons (Instagram/Facebook/TikTok/LinkedIn text) with the actual brand icons (use images/social/{instagram,facebook,tiktok,linkedin}.png like the newsletter builder already does โ€” email-safe PNGs, FontAwesome doesn't render in mail) and center them; add all Mario's socials (match the newsletter set). (b) Replace the text title "ASE Collision Test Prep" at the top with the logo image (images/logo.png, as the verification/reset emails already do in their header). Apply the same logo+icons treatment to the other branded emails for consistency.

  14. Migrate the REST of the transactional emails to Resend (root-cause of the iCloud bounces). sendWelcomeEmail was switched to the Resend-first sendAdminEmail helper on 2026-06-07 after a 550 5.7.1 [CS101] iCloud OUTBOUND rejection. The same iCloud-SMTP vulnerability still affects sendVerificationEmail, adminSendVerificationEmail, sendPasswordResetEmail, and the two test variants (all still use nodemailer โ†’ smtp.mail.me.com). Convert each to sendAdminEmail({from,to,replyTo,subject,text,html}) and add RESEND_API_KEY to their secrets:[...] array. Resend uses the verified domain (proper SPF/DKIM) so it won't hit iCloud's CS101/CS01 outbound filter.

  15. dashboard.html โ€” show question + key-term counts for LOCKED modules (asked 2026-06-07). Locked module cards (B3-B6 when not owned) currently show "0 Questions / 0 Key Terms" because the counts only load for unlocked modules. Surface the REAL per-module totals (from the cached rc_counts_v1 counts, or a small public per-module counts doc) on locked cards too, so a buyer sees how much content they're getting BEFORE purchasing. Must stay $0/cached (don't add live reads per card).

Mario still to do (small):


๐Ÿ“Œ (ARCHIVED) RESUME FROM HERE โ€” Active hand-off notes (June 3, 2026 โ€” Mario going home)

When you start the next session, read this whole section first, then say: "Welcome back. Want me to build the side-menu badges (task #3), the branded logo emails (task #4), or something else?"

โœ… Shipped & LIVE this session (2026-06-03):

๐Ÿ”Ž Verified (no code change):

๐Ÿšง OPEN / NEXT UP (these were tracked in the session task list, which does NOT persist โ€” capture is here):

  1. Side-menu number badges (simulator.html) โ€” approved, NOT built. Add badges matching #flagNavBadge style:
  2. Branded HTML emails with logo (verification + password reset) โ€” approved, NOT built. Firebase's built-in auth templates CANNOT show a logo. Plan: new Cloud Function(s) using admin.auth().generateEmailVerificationLink() / generatePasswordResetLink() to mint links, then send branded HTML (logo /images/logo.png, navy/orange, from support@) via the same nodemailer/SMTP path as sendWelcomeEmail. Update register/verify + forgot-password.html to call these instead of Firebase's built-ins. Requires a FUNCTIONS deploy (node_modules must be hydrated โ€” see env note). Test login/registration carefully.
  3. Password-reset/verification SENDER โ†’ support@ โ€” Mario is doing this himself in Firebase Console โ†’ Authentication โ†’ Templates โ†’ Password reset โ†’ change From noreplyโ†’support, Reply-to support. (Agent was blocked from clicking through auth settings by a safety guard.) Confirm whether he completed it. Also offer the same on the "Email address verification" template. Note: support@asecollisiontestprep.com should be a real inbox.
  4. Database read/write usage tracker on dashboard โ€” ON HOLD (Mario dismissed). If revived: admin-only Cloud Function querying Google Cloud Monitoring (firestore document read_count/write_count/delete_count) โ†’ new "Database Usage" panel on dashboard2 with progress vs free-tier daily limits (50k reads / 20k writes / 20k deletes). $0.

โš ๏ธ ENVIRONMENT NOTE (important):

Google Drive File Stream was EVICTING node_modules to online-only during this session, which broke npx @11ty/eleventy (exit 127, "eleventy: command not found", broken .bin/eleventy symlink) and timed out some file reads. WORKAROUND used for hosting deploys: simulator.html has no layout, so its build output is just front-matter-stripped โ€” built it by hand with tail -n +4 simulator.html > _site/simulator.html then firebase deploy --only hosting. For a normal/full build, ensure node_modules is hydrated (open the folder in Finder / ls -R node_modules >/dev/null to force download), then npx @11ty/eleventy. Functions deploys REQUIRE hydrated node_modules.


๐Ÿ“Œ (ARCHIVED) RESUME FROM HERE โ€” Active hand-off notes (May 29, 2026 evening โ€” switching iMac โ†’ Mac Studio)

Mario is switching from his iMac to his Mac Studio. When you start Codex on the Mac Studio, read this whole section first then say: "Welcome back from the Mac Studio. Want me to keep going on the question uploader (test your B4 paste) or pick up something else?"

Just shipped (live as of 2026-05-29 evening):

Question uploader (Mario's external admin_uploader.html) โ€” updated this session:

Path: /Users/mr.hernandez/Library/CloudStorage/GoogleDrive-mm.hdz@icloud.com/My Drive/Sheridan Technical/ASE_website/Firebase Question Uploader/admin_uploader.html Not deployed โ€” Mario opens this file locally in his browser. Lives outside ASE_Portal/ so it's not part of the Eleventy build.

Three behavior changes this session:

  1. Per-question module routing โ€” parses each question's TEST & CATEGORY: ASE B<n>-<letter> line, groups by detected module, uploads to the correct bundled_questions_b<n> collection. The dropdown is now just the fallback for questions whose category line is missing/unreadable.
  2. 4-option enforcement โ€” any question that doesn't parse out to exactly 4 multiple-choice options gets SKIPPED (not uploaded) and listed in the confirm popup with its UNIQUE ID + how many options it actually had + first 80 chars of the question text. Mario fixes the source .txt and re-pastes.
  3. Pre-upload confirm popup โ€” shows per-module counts, mismatched-module IDs (with destination module), 4-option-violation IDs, and a warning about chunk ID collisions before any write fires.

Mario said: "going home to work from Mac Studio" โ€” meaning he wants to test the new uploader against a real .txt file at home.

๐Ÿšง Still open:

  1. Test the question uploader against a real .txt โ€” Mario will paste his ASE B4 file and we'll see what the popup reports.
  2. Tool ID Firestore data โ€” /tool-uploader is live but tools_b2โ€ฆb6 are empty. First add 4+ tools (each with 3 distractors) to B2 so the Gallery + Quiz light up on the simulator.
  3. Apps Script doPost for ASE Simulator Reports sheet โ€” Mario still needs to share the Apps Script code from Sheet โ†’ Extensions โ†’ Apps Script.
  4. Gear sheet gearType column โ€” populate to enable the cascading filter sub-row.
  5. 404 page โ€” make it funnier with more collision-repair / refinish humor (currently P0404 + "Tow Me Back to the Shop"). On Mario's request.

When Mario opens Codex on the Mac Studio (suggested opener):

"Welcome back from the Mac Studio. The new question uploader and tool uploader both shipped โ€” open /Firebase Question Uploader/admin_uploader.html to paste your ASE B4 file and we'll see what the popup reports. Or want to pick up the 404 page rewrite, populate B2 tools, or something else?"

What we shipped in this session (all live):

Gear page overhaul (gear.html + supporting pages):

Media release form (NEW โ€” /media-release):

Jeopardy game rebuild (/jeopardy):

Classroom (/classroom):

Dashboard.html (student):

Dashboard2 (admin):

Simulator (/simulator):

B2-B6 (/b2 through /b6):

โœ… Confirmed shipped (verified live 2026-05-29):

๐Ÿšง Open issues / pending tasks:

  1. Apps Script doPost for ASE Simulator Reports sheet โ€” Mario still needs to share the Apps Script code (Sheet โ†’ Extensions โ†’ Apps Script) so we can update column mapping to the new explicit fields (correctCount, incorrectCount, totalQuestions). Currently column G shows cumulative lifetime totals (e.g., 4926) instead of per-test totals (65) because the Apps Script is enriching with leaderboards data.

  2. Tool ID Firestore data โ€” code + admin uploader done. New admin page at /tool-uploader (file tool-uploader.html) supports add/edit/delete per ASE module, image upload to Firebase Storage at tool_images/{mod}/, FontAwesome icon fallback, 3-distractor quiz answers, ASE task references, and bulk JSON paste. Each tool doc needs โ‰ฅ3 distractors to appear in the quiz. Action: Mario populates tools_b2โ€ฆb6 collections by adding tools at /tool-uploader.

  3. Gear sheet gearType column โ€” still needs to be populated for the cascading filter sub-row to appear. Existing rows need gearType values (ppe / tools / supplies / equipment / books).

โญ AMAZON ASSOCIATES APPROVED + QUALIFIED (2026-07-09) โ€” store ID collisionre03-20

Amazon Associates account approved; the affiliate tag is collisionre03-20 (SUPERSEDES the old collision-tools-20 referenced elsewhere in this file). โœ… 3 qualifying sales DONE (2026-07-09) โ€” Associates Summary showed 5 ordered / 3 shipped / $3.14 earned, so the account is fully qualified and PA-API 5.0 is now UNLOCKED (the price-tracker prerequisite is met; build when Mario asks). The site credits collisionre03-20 everywhere: gear-grabber bookmarklet (gear-grabber.js), gear-helper.html, gear.html (normalizeAmazonTag() at render), AND the homepage "Top Picks" slider (index.html) โ€” each strips any existing/missing tag= and forces collisionre03-20 (amzn.to shortlinks left alone), so old-tagged Google-Sheet rows are auto-corrected live (no Sheet re-edit needed). Homepage gear section got the required "As an Amazon Associateโ€ฆ" disclosure. docs/ppe-gear-grab-checklist.md old-tag URLs fixed too (0 old-tag refs remain in source). Mario still must add Payment + Tax info in Associates Central before payout.

TO DO (later, Mario's call): send the featured-gear email campaign via MailerLite to drive gear clicks/sales โ€” a ready-to-paste draft (subject options, preview text, body, /gear CTA, Amazon-Associate disclosure) was written 2026-07-09; also a strong fit for the past-body-shop-customers list. Optional companions: a "Gear I recommend" link inside the simulator, and building it as full branded HTML matching the newsletter template.

Gear price tracker (was PARKED 2026-05-29 โ€” now UNLOCKED, see above)

Now that the 3 qualifying sales are in, build Option C (Amazon PA-API 5.0) when Mario wants it. Skip Option A/B (A was only a bridge; B breaks ToS + Google Cloud IPs blocked). Banner slider UI design: horizontal carousel near top of gear page, only visible if at least 1 item has a price drop in the last 7 days. Hide banner entirely when no drops.

When Mario comes back (suggested opener):

"Welcome back. Want to keep going on the gear price tracker, or is something else more urgent? Also โ€” did you click Refresh Now on dashboard2 yet to confirm the GA4 daily sync now works?"


Who you're working with

Communication rules

  1. No technical jargon. Say "the button now shows for all terms," not "removed the hasImage gate from the conditional."

  2. Ask before big changes. Anything touching more than one file or replacing significant content โ€” describe the plan first and wait for "go ahead." 2a. ๐Ÿšซ NO ASSUMPTIONS โ€” ask when unsure (Mario's rule, 2026-06-30). If you are unsure about ANYTHING โ€” which option Mario wants, where something should go, what a "good spot" is, scope, naming, a design choice, whether to include or drop something โ€” STOP and ASK before acting. Do not guess and do not silently drop or add things based on your own judgment (this rule came after I assumed the REMEMBER banner shouldn't have icons and removed them). When a request leaves a real choice open, present the specific options (prefer AskUserQuestion chips per [[feedback_ask_with_choices]]) and wait for his answer. Acting on the obvious/only interpretation is fine; guessing on a genuine fork is not.

  3. Verify visually. After UI changes, take a screenshot in preview or send a deploy so the user can see it on the live site.

  4. Always deploy. The user goes straight to the live site to check work. After confirmed changes: npx @11ty/eleventy && firebase deploy --only hosting --project ase-portal-5d37c

  5. Auto-deploy is approved. User has granted permission to deploy without asking each time. Build โ†’ deploy โ†’ report URL. 5a. ๐Ÿšจ VERIFY THE FIREBASE PROJECT BEFORE EVERY DEPLOY, TWICE (Mario's rule, 2026-06-14). The ONLY correct project is ase-portal-5d37c. Before running ANY firebase deploy (hosting, functions, extensions, storage, rules): (1) first check โ€” confirm the command includes --project ase-portal-5d37c explicitly (never rely on the default/active project), and state out loud which project you're deploying to; (2) second check โ€” right before you actually run it, re-read the command and confirm again it says ase-portal-5d37c. Never deploy without the explicit --project ase-portal-5d37c flag. A PreToolUse deploy-guard hook in .Codex/settings.json (a Bash matcher that inspects the command) automatically BLOCKS any firebase deploy that doesn't contain --project ase-portal-5d37c, as a backstop โ€” but you still do the two manual checks. (If a deploy is ever wrongly blocked, fix the command to include the flag, don't disable the hook.)

  6. Always recommend the "better / right way" โ€” never the quick hack. Mario said explicitly: "I like to do things the better way all the time." When there's a proper solution and a shortcut (placeholder values, mock data, "we can fix this later" workarounds), default to the proper one. The shortcut should only appear if it's truly faster AND creates zero technical debt. Don't even offer the shortcut as a fallback unless he asks.

  7. Keep the project running cost at $0. Mario said explicitly: "keep the cost of this project to $0." Don't propose anything that requires paid services, paid APIs, or new monthly subscriptions. Stay within Firebase free tier limits. For AI-assisted work (e.g. blog expansion, question audits), do it INSIDE the Codex conversation (no API calls to Anthropic from his account) rather than via Cloud Functions that consume his prepaid credits โ€” unless he explicitly approves spending. Existing paid services already in use: Firebase Stripe Extension (transaction fees only when sales happen), Anthropic prepaid credits (already paid, use sparingly). Don't add new ones.

  8. Always prefix "ASE" before B-series module names in all written content (blog articles, marketing copy, page text). Write "ASE B2", "ASE B3", "ASE B4", "ASE B5", "ASE B6" โ€” never bare "B2 exam" or "the B3". This is for SEO and brand consistency. Tags, code identifiers, and Firestore field names can stay as bare "B2"/"B3" since those are internal.

  9. Never quote specific ASE test prices in articles or marketing copy. ASE raises prices every year โ€” last-known reference rates (May 2026): ~$62 per test on ase.com plus ~$33 Prometric test-center sitting fee, but these climb annually. Instead of a number, write "check ase.com for current pricing" or describe the structure generically ("ASE charges a registration fee plus a per-test fee, and the Prometric center charges a separate sitting fee"). Stale prices on a live SEO article hurt credibility and Mario's authority.

  10. Embedded HTML inside markdown articles: keep it on ONE LINE with no blank lines and no indentation. Markdown parsers (Eleventy's markdown-it) wrap raw HTML blocks in <p> tags when they encounter blank lines or 4-space indentation between tags โ€” that produces invalid <p><div></p> HTML that mangles the DOM and can blank the page. Pattern: write the whole <div>...</div> block as one long line, OR start every line at column 0 with no blank lines between. Put any associated <script> tag on its own line(s) below the HTML block. This applies to the inline quiz on /blog/understanding-ase-b6 and any future embedded interactive widgets.

  11. Don't put .pre-animate on a container with very long content (10k+ px tall). The .pre-animate CSS sets opacity: 0 with a 1.8s transition that gets stuck at currentTime=0 on giant pages โ€” the transition runs forever without advancing, leaving the entire content invisible. Symptom: page header renders, body is completely blank, no JS errors, element.getAnimations() shows playState: "running" but currentTime: 0. Even style="opacity: 1 !important" inline can't beat the frozen transition; only animation.cancel() recovers. The article-container on blog posts is no longer using .pre-animate for this reason โ€” keep it that way. If you want a fade-in on long pages, do it manually after a requestAnimationFrame tick or use a much shorter transition.

  12. Never use em dashes (โ€”) in written content. Em dashes are an AI-writing tell. In blog articles, marketing copy, and any user-facing text, use commas, periods, parentheses, or colons instead. This applies to: blog markdown, page copy, email drafts, social posts. Code comments and AGENTS.md itself are fine. Quick swap rules: , X, for interjections; . for sentence breaks; (X) for asides; โ€” โ†’ , as a safe default. If you catch yourself reaching for an em dash, rewrite the sentence.

  13. Blog scope is the whole automotive Collision Repair and Refinish industry, not just ASE. Mario said: "not every article has to be about ASE. Most articles will be about any of the automotive Collision Repair and Refinish Industry." Treat the blog as a broad collision-trade publication where ASE certification is one important topic among many. Valid article topics include: paint and refinish technique (color match, mottling, gun setup, materials), body repair (panel work, plastics, adhesives, welding, OEM procedures), structural work (frame, unibody, boron, aluminum, measurement systems), mechanical and electrical (ADAS, EV/hybrid, restraint systems), estimating (P-Pages, software, DRP), shop business (DRP relationships, OEM-certified shops, hiring, pay tiers), tools and equipment (spray guns, compressors, scan tools, calibration), career topics (apprenticeships, certifications, mobility), industry news (regulations, manufacturer announcements, trade events), and history/culture of the trade. Module-specific ASE study guides remain valuable but should not dominate the publishing mix. When generating article ideas, default to "what would interest a working collision technician this week?" rather than "which ASE module hasn't been covered yet?" Cross-link to relevant ASE module pages where natural but don't force every article into an ASE-prep frame.

  14. When a task needs an API key or secret, send Mario the terminal command to set it himself, never ask him to paste the raw key into chat. Mario said: "every time you need an api, send me the terminal instructions." Pasting a key into the conversation leaks it into the transcript. Instead, hand him the copy-paste Terminal command that sets the secret with a hidden prompt, e.g. for a Cloud Functions secret: firebase functions:secrets:set RESEND_API_KEY --project ase-portal-5d37c (he runs it in the project folder, pastes the value at the hidden prompt). For local env/config, give the matching export โ€ฆ / file-write command. After he confirms it's set, I do the deploy. Same pattern for any new service (Resend, Stripe, MailerLite, etc.): (1) brief web steps to obtain the key, (2) the exact terminal command to store it, (3) "tell me when it's set and I'll deploy." Only fall back to a different method if he explicitly asks.

  15. Keep every page consistent in the bento-box design language. Mario said: "keep all pages in bento boxes consistent across the entire website." The whole site uses a bento-box / card layout system โ€” every major content section sits inside a rounded card (.bento-card, .admin-panel, .patches-bento, .home-gear-section, .showcase-bento, etc.) with: a top accent border, soft shadow (var(--shadow-base)), rounded corners (var(--card-radius)), and a clear section title (h2 in var(--primary) navy, 900 weight) plus a short description line above the content. When adding ANY new section to ANY page, wrap it in a bento card and give it a matching title header โ€” never drop raw content directly onto the page background. Match the spacing, border-radius, accent-border, and title styling of the surrounding sections on that page. Section titles follow the pattern: centered <h2> (2.5em, navy, 900 weight) + a muted description <p> (1.1em, max-width 800px, centered). This consistency is a hard requirement across index, b2-b6, blog, dashboard(s), simulator, gear, glossary, and every other page.

  16. Blog article conventions for expanded posts (the #74 rewrite): Use a <table class="comparison"> (red/green-tinted columns) for any Shop-vs-Test or right-vs-wrong section instead of bullet lists โ€” the styling lives in _includes/article.html. Right before the final "Stop Guessing. Start Passing." CTA, include a "This Is What You're Working Toward" patch display card (single-line <div> HTML block) showing the official ASE patch for the matching module from /images/. Module โ†’ patch image paths: B2 โ†’ ase_certified_refinish_technician_patch.png, B3 โ†’ ase_certified_collision_technician_patch.png, B6 โ†’ ase_certified_collision_estimator_patch.png (B4 and B5 โ€” verify the exact filename in /images/ before referencing). Always include the ASEยฎ trademark disclaimer paragraph inside the patch card. End every article with a <div class="article-pricing"> block showing the CURRENT 2-option strategy (updated 2026-06-19; the old 1/2/3-month $99/$158/$207 tiers are RETIRED everywhere โ€” do NOT reintroduce them): a green "Available Now" card Simulator Access $99 (one-time, 30 days) and an orange "Best Value" card Simulator + ASE Academy $298 (Academy bundle "launching soon"), each CTA pointing to /bX#pricing for the matching module. The .article-pricing / .ap-* styles live in article.html; keep the whole block on ONE line per rule 10. Don't use a plain markdown link as the final CTA.

  17. ASE Academy lesson-authoring standard (grounded-source content). Mario set this on 2026-06-07 as the guide for adding information to every academy lesson section. When writing or expanding any academy_lessons/{MOD-CODE} section, follow this framework: Role = an expert Automotive Master Technician and Instructor teaching a student who is preparing for both the certification exam and real shop application. Goal = an exhaustive, standalone study guide for the section's task category (e.g. "Surface Preparation") โ€” complete enough to fully learn, understand, and apply, with no clarifying questions asked back. Grounded source = Mario's Drive folder /Users/mariohernandez/My Drive/Sheridan Technical/ + the literal-backtick `OCPs folder (Florida program competency profiles). Map each module to its OCP: B2 Refinish โ†’ `OCP B - ARR0141 Refinish Tech; that folder holds the ASE task lists, the matching textbook chapters (e.g. "Week 03 - Chapter 27 Surface Preparation and Masking"), the I-CAR manuals, and the per-category week folders (e.g. "Week 07 - ASE B2 (A) Surface Preparation"). Use the OCP task list to enumerate the universally-recognized tasks so coverage is complete and matches what Mario teaches. Per-task structure (required for EVERY task): (1) The Why โ€” purpose and why it matters; (2) The How โ€” step-by-step procedure and technique; (3) The What โ€” tools, materials, and required safety equipment; (4) Common Pitfalls โ€” what goes wrong and how to avoid it. Copyright constraint (hard rule): never reproduce or quote proprietary certification material (ASE, I-CAR, textbooks) verbatim โ€” synthesize the concepts in fully original language based on universal, standard industry practice (this mirrors AGENTS.md rule 9's spirit and the IP-protection note). Render the content using the lesson's existing callout styling (.fast 30-second overview, .steps/.asetask for procedure, .safety for PPE/hazards, .factbox/.know for key facts, .mistakes/.defect for pitfalls) and keep the bite-size-page structure the player splits on. Show Mario a sample task before mass-authoring a whole module, and back up the lesson doc before any write (see functions/scripts/rename-30second-heading.js for the backup-then-merge pattern). Lab placement (Mario, 2026-06-08): place each "๐Ÿ”ง Practice in the Lab" drill IMMEDIATELY AFTER the specific task it practices โ€” so a student reading "Inspect the surface & make a plan" sees "๐Ÿ”ง Practice in the Lab: Inspect and measure film build" right there โ€” NOT bunched at the end of the module. DONE for B2-A via functions/scripts/move-labs-into-tasks-b2a.js (split each module by <div class="defect">, relabel the numbered drill h4 with the "๐Ÿ”ง Practice in the Lab:" prefix, insert it after the matching task card, delete the end-of-module "Practice in the Lab" <h3> block). TO DO โ€” apply this same labs-next-to-tasks placement to every other lesson (B2-Bโ€ฆF and B3-B6) as they're authored. Also keep the bottom-of-page "๐Ÿ“˜ Key Terms on this page" footer (player-rendered, _acKeyTermsFooter in simulator.html) on every new lesson.


Project at a glance


Critical commands

All run from project root: /Users/mariohernandez/Library/CloudStorage/GoogleDrive-mm.hdz@icloud.com/My Drive/Sheridan Technical/ASE_website/ASE_Portal

What Command
Build the site npx @11ty/eleventy
Build + deploy hosting (most common) npx @11ty/eleventy && firebase deploy --only hosting --project ase-portal-5d37c
Deploy Cloud Functions firebase deploy --only functions --project ase-portal-5d37c
Deploy Firestore Stripe Extension config firebase deploy --only extensions --project ase-portal-5d37c
Local dev preview npx @11ty/eleventy --serve (port 8080)
View hosted site https://asecollisiontestprep.com

File structure

ASE_Portal/
โ”œโ”€โ”€ _includes/
โ”‚   โ”œโ”€โ”€ layout.html       # Base template โ€” has site-wide head, schema.org, header, footer
โ”‚   โ”œโ”€โ”€ header.html       # Top nav
โ”‚   โ”œโ”€โ”€ footer.html       # Bottom footer
โ”‚   โ””โ”€โ”€ article.html      # Blog post layout (has share buttons at bottom)
โ”œโ”€โ”€ b2.html ... b6.html   # Per-module landing pages with Stripe checkout buttons
โ”œโ”€โ”€ simulator.html        # THE SIMULATOR โ€” single page serves all 5 modules via ?module=B2 URL param. Huge file (~340KB)
โ”œโ”€โ”€ dashboard.html        # Student dashboard: module unlocks, leaderboard, pass probability gauges
โ”œโ”€โ”€ dashboard2.html       # Admin dashboard: user roster, telemetry, support tickets, analytics
โ”œโ”€โ”€ login.html / register.html / forgot-password.html / verify.html
โ”œโ”€โ”€ why-us.html, blog.html, gear.html, faq.html, contact.html, etc.
โ”œโ”€โ”€ functions/
โ”‚   โ”œโ”€โ”€ index.js          # Cloud Functions (Stripe webhook backup, analytics sync, etc.)
โ”‚   โ””โ”€โ”€ scripts/          # One-time migration scripts (e.g. create-stripe-live-products.js)
โ”œโ”€โ”€ extensions/
โ”‚   โ””โ”€โ”€ firestore-stripe-payments.env  # Stripe Extension manifest
โ”œโ”€โ”€ _site/                # Eleventy build output โ€” DO NOT EDIT, regenerated on every build
โ”œโ”€โ”€ firebase.json         # Hosting + functions + extensions config
โ”œโ”€โ”€ firestore.rules       # Firestore security rules
โ””โ”€โ”€ AGENTS.md             # This file

Firestore data model

Collection / Doc What it holds
users/{uid} Profile: fullName, email, address, role, isAdmin, unlockedModules[], accessExpires, redeemedCode
customers/{uid} Stripe customer record (managed by Firebase Stripe Extension)
customers/{uid}/checkout_sessions/ Pending Stripe sessions โ€” Extension writes back url
customers/{uid}/payments/{sessionId} Succeeded payments โ€” webhook writes here, dashboard reads to unlock modules
customers/{uid}/subscriptions/ Stripe subscriptions (currently unused โ€” all sales are one-time)
leaderboards/{uid} Per-user stats: { name, role, loc, stats: { B2: {correct, wrong, total, time}, B3: {...}, ... } }
bundled_questions_b2 โ€ฆ _b6 Question banks per module (one doc per category, with questions array). ๐Ÿ” ENCRYPTED since 2026-06-10 (QUESTION VAULT) โ€” each array entry is {v, catLabel, uniqueId, hasImage, qt, e} where e = AES-256-GCM blob of the full question, base64(12-byte IV โ€– ciphertext โ€– tag), and qt = precomputed style tag (d/t/e/m/l = direct/TechA&B/EXCEPT/MOST/LEAST). Array length still equals question count, so all count-only readers work unchanged. Plaintext entries still readable (legacy path). Encrypt/restore: functions/scripts/encrypt-question-banks.js (+ _vault.js helpers, plaintext backups in gitignored _vault_backups/). The question uploader encrypts on upload automatically.
question_keys/{B2..B6} Question-vault key + bank version per module: {k: base64 AES key, v: int}. Read gated by the SAME module access as the bank; admins read all. v busts the simulator's 7-day question cache โ€” bump v after ANY direct bank edit (the encrypt script and uploader both do it automatically).
bundled_data/master_terminology_b2 โ€ฆ _b6 Key-term flashcard data per module
bundled_data/master_terminology LEGACY single-doc terminology (still readable as fallback)
support_tickets/{ticketId} Student error reports + general support messages
analytics_daily/{YYYY-MM-DD} Daily GA4 + Clarity aggregate, written by dailyAnalyticsSync Cloud Function

Firebase Storage folders: term_images/, question_images/, feedback_images/. Term image filename pattern: {uniqueId}.jpg|.gif|.mp4. The imageExt field on each term doc drives the button label (VIEW IMAGE / VIEW GIF / VIEW VIDEO).


Caching strategy (IMPORTANT for read costs)

Where Key TTL What it caches
simulator.html q_cache_v10_{mod} (+_ts,_v) 7 days ENCRYPTED question entries (localStorage). Busted early when question_keys/{mod}.v changes, so bank updates reach students next visit. Costs 1 extra read/session (the key doc).
simulator.html t_cache_v8_{mod} 24h Terminology per module (localStorage)
dashboard.html rc_counts_v1 24h Per-module question + term counts (localStorage)
dashboard.html lb_cache_v1 5 min Full leaderboards collection (sessionStorage)
simulator.html window.__userDocCache session users/{uid} doc deduped across access check + profile load
simulator.html window.__lastDashRefresh 30s throttle refreshDashboardStats() rate limit

If you add a new Firestore read, ASK: does it need to be live? Can it cache? Without caching, 19 students = 1,400 reads each = quota burn.


Stripe integration

Live mode is ACTIVE. Real money is being charged.

Architecture: Firebase Stripe Extension (publisher invertase, instance firestore-stripe-payments v0.3.12, region us-east1).

Flow:

  1. Client writes to customers/{uid}/checkout_sessions/ with { line_items: [{ price: priceId }], metadata: { module: "BX" } }
  2. Extension creates a Stripe Checkout Session, writes url back
  3. Client redirects to Stripe Checkout
  4. After payment, Extension's webhook writes customers/{uid}/payments/{sessionId}
  5. dashboard.html listens to that subcollection, looks up the priceId in priceToModuleMap, and unlocks only the matching module for exactly the matching duration

LIVE Price IDs (per module โ€” all 5 modules priced identically: $99 / $158 / $207)

B2 โ€” Painting and Refinishing

B3 โ€” Non-Structural Analysis and Damage Repair

B4 โ€” Structural Analysis and Damage Repair

B5 โ€” Mechanical and Electrical Components

B6 โ€” Damage Analysis and Estimating

Where these IDs live in code:

Promo codes: allow_promotion_codes: true is set in all checkout sessions. Promo codes managed in Stripe Dashboard โ†’ Coupons.

Adaptive Pricing: DISABLED at https://dashboard.stripe.com/settings/adaptive-pricing โ€” left off because it interferes with promo codes.

Secrets (in Google Cloud Secret Manager under project ase-portal-5d37c):

NEVER print API keys, webhook secrets, or any credentials in chat. They live only in Secret Manager.


Open roadmap

Cleared on 2026-05-28 โ€” Mario wiped the legacy task list. The active tasks below are what we're working through now.

Active (next up)

  1. Gear price tracker on /gear โ€” see "Next active task" section above. Option A vs B vs C decision pending.

Queued

simulator.html

blog

404.html

gear.html

blog.html

index.html

Long-standing items (still open, not actively being worked)


Things that bit us recently โ€” don't repeat

  1. Firestore reads can explode fast. 19 students = 27K reads in one day before optimization. Always cache static data (questions, terminology, counts) in localStorage with TTL. Use sessionStorage for medium-lived data (leaderboards). Throttle anything called on navigation.
  2. Stripe Coupon duration MUST be "Once" for one-time payment Checkout. "Multiple months" / "Repeating" only works for subscriptions and will silently reject the code with no error.
  3. Stripe 100% off codes don't work in mode: payment. Use 99% off if you need effectively-free.
  4. Adaptive Pricing breaks promo codes. Leave it OFF.
  5. hasImage field in Firestore is unreliable โ€” show the VIEW IMAGE button based on uniqueId presence, not on hasImage.
  6. Don't use --no-verify or skip git hooks unless the user explicitly asks.
  7. Tracking task list: Use TaskCreate/TaskUpdate for multi-step work so the user can see progress.
  8. Simulator side-menu items MUST stay on ONE line (Mario's standing rule). Every .nav-item label sits on a single line with its count/status badge pinned to the right. This is enforced by the shared CSS in simulator.html: .nav-item-text { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + .nav-item span:not(.nav-item-text) { flex-shrink: 0; } + .nav-item { white-space: nowrap; align-items: center; }. When ADDING a new side-menu item (or a longer label/badge), confirm it still fits on one line at the 280px sidebar width; if a long label would truncate, shrink the nav font/padding/gap rather than letting it wrap. Don't reintroduce white-space: normal on .nav-item.
  9. ๐Ÿ” QUESTION VAULT (2026-06-10): the question banks are ENCRYPTED โ€” never write plaintext questions to bundled_questions_* or read q.text off Firestore docs directly. Full architecture: simulator holds plaintext ONLY inside the vault closure (top of simulator.html's main script); window.dbQuestionsPool is light stubs ({catLabel, qt, uniqueId, hasImage}); use the closure helpers (_qvHasTermQ, _qvIds, _qvTake, _qvHydrateBank, buildToolsFromQuestions, _acPickQuiz) for anything needing question content. active_test saves id stubs, not content. classroom.html (_vaultFetchModuleQuestions) and dashboard2.html (_qvAdminDecrypt) decrypt their own reads. The uploader encrypts on upload + bumps question_keys/{mod}.v. Old maintenance scripts that rewrite banks must decryptโ†’editโ†’re-encrypt via functions/scripts/_vault.js (dedupe-questions.js has an abort guard). Wire format everywhere: base64(12-byte IV โ€– AES-256-GCM ciphertext โ€– tag). Plaintext backups: functions/scripts/_vault_backups/ (gitignored); restore with node scripts/encrypt-question-banks.js --restore <MOD>.

Working with the user โ€” quick checklist

Before starting work:

While working:

When stuck or unsure:


If something looks weird and not documented here

Check the auto-memory files (live on the current Mac only, may not be present on other machines):

If you're on a fresh machine and those don't exist, this AGENTS.md is the canonical context.