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.
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?"
B2-L01-S2-CMPA.pdf); L01.16 comparison docs got a strong 3D layered shadow; L01.17 circled numbers upright (transform:none on .subs--num li::before); keyterms presentation-mode holds the DEFINITION until Next (kt2-hold, _ktPend/_ktReveal); crumb now includes the lesson name on every slide ("ASE B2 ยท Surface Preparation ยท OEM Procedures and Position Statements ยท L01.34").TYPE: tools + TOOLS: list ("Name | how it's used | optional img") renders the Key-Terms 30/70 master-detail (case 'tools' shares keyterms renderer, image default tool-<slug>.jpg). All 68 "Tools Mentioned/Used" slides converted, all decks rebuilt + reseeded. Media tracker auto-emits one product-shot slot + prompt per tool (PREFIX_BENCH, ~236 slots, tracker now 2,136 images).s.rubric + s.oralQuestions (supersedes the old drop-the-rubric rule; INSTRUCTOR NOTES/CRITERION still dropped). wireLab4() in academy-player.html. All 74 decks rebuilt, all reseeded.TYPE: dovsdont (green DO / red DON'T habit columns, check/cross markers, optional images; DO/DONT LABEL+BULLETS+IMAGE fields). asetasks slide now stacks tasks one below the other + "Covered in" topics each on their own line (L12.3). ANIMATION PROMPT field registered in the builder (write prompts now, video generation stays deferred โ Mario's call). L04 got the "Read Film Build with the Paint Thickness Gauge" lab (L04.30, the approved sample).labintro (objective+materials+<DECK>-LAB<k>-INTRO.jpg) โ one labstep per procedure step (big action image <DECK>-LAB<k>-STEP<n>.jpg + "I did this step" tap-check) โ labwrap (quality+safety) โ labgrade (instructorOnly:true) with the tap-to-check rubric + live score + oral questions. Students NEVER see labgrade; interim gate = ?instructor=1 URL param (window.__ACAD_INSTRUCTOR), the slide filter is in academy-player.html where slides is assembled. NEXT PROJECT (interview LOCKED): real instructor accounts โ "Instructor/Teacher" role at registration โ shows PENDING in dashboard2 โ Mario approves โ instructor sees labgrade slides + saves a FULL grade record per student per lab (each rubric line pass/fail, score, oral result, date, grader) โ student sees their score + which lines they passed. Build with Firestore rules after the content batch..s-labintro/.s-labwrap now share ONE type size (1.08rem), text stays inside the card (desktop: zero overflow, li-grid uses grid-template-rows:minmax(0,1fr) + li-txt internal scroll; phone: rows auto + natural page scroll). NOTE: inserting the L04 "Three Verbs" slide shifted L04 numbering โ the gauge lab intro is now L04.31.`OCPs/`I-CAR ACADEMY 01/05/07 "Student Lab Exercise" PDFs).academy-tools-slide-and-lab-program, academy-signature-shop-zones. Deploy gotcha: Google Drive evicts _site files mid-upload โ ALWAYS hydrate first (find _site -type f -exec cat {} + > /dev/null) then deploy.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?"
functions/scripts/academy-build/MEDIA-STYLE-GUIDE.md (binding). Memory: academy-signature-shop-zones.txt-backup-pre-promptrev/, then a Python pass (scratchpad/promptrev.py) classified each prompt into a zone by keyword and swapped the old generic env line for the zone's environment + PPE/people clause. 1,443 prompts rewritten across 74 lesson files (zones: floor 447, bench 369, booth 271, mixing 143, prep 127, office 44, detail 39, delivery 3). The ASE-GUIDE file was EXCLUDED (its scenes are test centers, not shop zones). Hand-reviewed all 47 office/customer prompts and fixed 14 misclassifications where a car would've ended up inside the office (walk-arounds โ delivery/outdoor zone) plus one sanding scene โ floor. Verified 0 old generic-env lines remain in any lesson file.academy-media-tracker.html (77 lessons, 1,900 images) โ every prompt there is the new zoned version; verified in preview (office 30 / booth 271 / 0 old-generic). Also updated the tracker's PREFIX/SUFFIX in build-media-tracker.js so any FUTURE prompt starts with a "[Name the exact zoneโฆ]" hint + the strict-PPE suffix. Built + deployed.convert-academy-media.js <folder> โ sized JPGs filed into media/<deck>/ โ rebuild tracker + search index โ deploy). Backlog still ~1,150+ slots.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?"
`OCPs/`OCP B .../`Product Brochures/Color Wheel.pdf), his flat metal sprayout-card photos (.../Week 05 .../Color Matching/sprayout card holder1-3.JPG), DeVilbiss DV1 gun (images/gear/Devilbiss_DV704520_DV1...jpg), plus a generated micrograph for flake and a real color-chip book (no magnifier). Uploaded refs โ Higgsfield image-to-image (nano_banana_pro, medias role image). Files in ase-academy/slideshow/media/b2-l36/. TOOLS shot = gun laid SIDEWAYS on the bench (Mario's pick over the I-SGH-7 rack).nano_banana_pro through Jul 13" claim below is WRONG for the MCP pipeline. The 7-day Unlimited only covers the Higgsfield website/app; the MCP always spends credits (verified: โ2 cr/image nano_banana_pro, โ1 nano_banana, not refunded). Check balance before batches and confirm spend with Mario ($0 rule). L36 batch โ 50 cr. See memory higgsfield-mcp-costs-credits + higgsfield-grounded-reference-pipeline.L36-corrections.md.[BUILT] marks complete; search index rebuilt (72 decks). Mario-review items live in functions/scripts/academy-build/FABLE5-FACT-FLAGS.md (incl. the L40-vs-L41 orientation-coat wording question). Audio still FINAL-only, not generated.aseAcadResume_<mod> localStorage written in academyOpenLesson; whole-course bar; falls back to next unstarted lesson) ยท Practice/Test = small round icons (dumbbell + fa-file-pen โ Mario asked for a test-looking icon), one primary button per row ยท time+slide chips via window.ACADEMY_LESSON_META (auto-generated after ACADEMY_LESSON_DECKS; regen by scanning decks/*.deck.js; aliases b2-f01โb2-f1, ase-guideโb2-ase because the menu uses doc ids). Sidebar: "Courses" header, labels "ASE B2 ยท Unit N" (no -code), hover/active stroke in the unit's own color, vertical color bar removed, unit 5 recolored pink #db2777. NOT built: #4 unit-complete celebration, #5 mobile swipe chips โ mockup at https://Codex.ai/code/artifact/b4ee29f9-c12d-482b-a0b3-93bab43bf722Sheridan Technical/Pictures/ โ Student Pictures (229), Me As a Painter (165), Sheridan Technical College (61). **Pilot DONE: L09, all 21 images LIVE** from two student photos (gallery: https://Codex.ai/code/artifact/364ca278-dd8a-4882-b4bb-9d2c4933d9fc). Reusable uploaded media_ids: DA-sanding IMG_0304 = 44019a4f-0163-4897-9ecc-1ef736b231ff, guide-coat IMG_0322 = 605da27e-8c3b-4fb6-885f-d40ebdfe06be. Workflow: media_upload presigned โ curl PUT โ media_confirm โ generate_image with medias role reference โ show_generations lists all results in one call โ curl rawUrl โ sips โ media/B2-L51-S1-DIV.jpg from the STG Welding guide's plug-welding figure. Bookโlesson map: Measuring and MixingโL34/L35 ยท welding guides + GM Sheet MetalโL51 ยท IML Module 1 + Auto Body ConstructionโFoundation/L03/L06 ยท 3M+Finixa catalogsโtool shapes.nano_banana (~2 cr/image, ~50 cr total). Per the unlimited-models rule below, batches until Jul 13 should use nano_banana_pro/nano_banana_2 (UNLIMITED = $0 credits). Backlog: 1,275 slots / 72 decks, ~1,187 remaining. Next: Mario picks the batch (Foundation โ200, L33โL56 โ480, or all).higgsfield-media-automation)403de4aa-e8e2-40db-b595-ed78eca51fb5) on Mario's Ultra plan (~3,010 monthly credits). This supersedes the earlier custom-connector plan (that scaffold at functions/scripts/higgsfield-mcp/ stays only as a fallback; its build-queue.js is still the backlog driver).nano_banana_pro ("Nano Banana Pro Unlimited") or nano_banana_2 ("Nano Banana 2 Unlimited") โ both 2K, unlimited. After Jul 13 those two 7-day unlimiteds expire โ fall back to plain nano_banana ("Nano Banana" 365 Unlimited, auto-renewing, 1K) as the standing default. Do NOT use per-credit models when an unlimited one covers the task. (Video: Kling v3.0 is unlimited through Jul 13; seedance_2_0 still costs credits and video is deferred anyway.)generate_image model nano_banana_pro (routes to nano_banana_2), aspect_ratio:"16:9", ~1 credit at 1k / 2 at 2k (1k is sharp enough for slides). Video: generate_video model seedance_2_0, duration:5 = 22.5 credits (720p + audio). Both wrap args in a params object; get_cost:true preflights.job_display(id) โ results.rawUrl (.png/.mp4) โ curl -sSL "<url>" -o /tmp/hf.png && sips -s format jpeg -s formatOptions 88 /tmp/hf.png --out "<FILE>.jpg" into ase-academy/slideshow/media/<deck>/. Grab many at once with show_generations. Fix a flaw without a full regen = a nano_banana reference edit (pass the prior job_id as medias:[{value,role:"image"}] + a minimal-change prompt).node functions/scripts/higgsfield-mcp/build-queue.js <deckId> lists every image a lesson references but is missing, with the house prompt from the source .txt. ~720 across the built lessons.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.
b2-l01 / doc B2-L01, 39 slides, all LIVE)B2-L01-S2-DOC-procedure.pdf, Ford GT350 fender), L01.15 TSB (B2-L01-S2-DOC-tsb.pdf, Toyota Bulletin #170), L01.16 comparison (real page-1 renders of all three), plus a new L01.27 "The Three Documents, Up Close" multi-doc viewer. Render a PDF's page 1 โ jpg with qlmanage -t -s 1400 -o <dir> file.pdf then sips to jpeg.support_tickets (type "Academy Slide Report", with deck+slide#+title+message); shows in dashboard2. Works signed-in; keyed via window._acUser/window._acDb.PRES_TYPES/presSetup/presReveal/presPending near the top of the main IIFE.s-kt2): single-column term list left (30%), click a term โ its picture/document on the right (70%) with the description in a fixed area beneath; divider line under the subtitle.auto-fit (stretch to fill the width). Progress Review keyboard: type the answer letter to select, Enter to check. asetasks slide groups by UNIQUE task (no repeated description; "Covered in:" lists the topics). Wide-screen content image hugs the right with the caption under it.academy-resume-progress-key): the player mirrors progress under BOTH key formats (doc id + deck.meta.id) so resume always finds the furthest slide; the simulator Academy two-pane remembers the open unit across a page refresh (localStorage['aseAcadUnit_'+mod]) instead of defaulting to B2-0. L01.30 link parens are now bold + clickable.academyLessonExam / academyLessonDrill in simulator.html) pull questions by the whole UNIT's ASE category (ACADEMY_UNIT_CATS[mod][unitCode] โ startDrill(cat)), so L01's test shows surface-prep questions the lesson never taught. Fix = per-lesson question targeting. On hold: Mario is first replacing the entire question bank with a better-formatted version โ revisit after that lands. Open decisions to interview then: source (write-new-grounded vs tag-existing vs reuse the lesson's built-in 10-Q quiz), test size, and scope (per-lesson system for all lessons vs L01 only).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?"
โฆ/B2 Slides/_B2-TABLE-OF-CONTENTS (v1.3).txt (67 lessons + subtopics + build-status marks). Golden format exemplar for authoring = F04 v1.3 (most current rules baked in).B2-F1), F02 v1.3 (28 โ B2-F2), F03 v1.3 (41 โ B2-F3), F04 v1.3 (41 โ B2-F4). Public links /academy-player/?deck=b2-f0N.node scripts/seed-academy-slides.js b2-f0N --docid B2-FN (from functions/) โ add 'FN':'B2-FN' to ACADEMY_LESSON_DECKS in simulator.html โ eleventy โ deploy โ archive the old txt to _archive/ โ mark the TOC.sds-clearcoat.jpg + sds-reducer.jpg โ ase-academy/slideshow/media/b2-f03/; repair-order.jpg โ media/b2-f01/. The 3 real TDS jpgs were also copied into media/b2-f06/ for the F06 draft.fitCmp() shrinks image height only until the slide stops scrolling.last moves only if the tab actually navigated) โ a stale open tab can no longer wipe a student back to slide 1. Restart Lesson = force reset. NOTE: progress is still per-browser; account-synced progress is a possible future build.#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.b2f_inventory_*.md etc.), one Technician A/B item per quiz.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.
--project ase-portal-5d37c, verified twice + hook), question-bank answer-key audit chip still pending (task_b4cc66fa), simulator L-lesson menu rewire comes after the Foundation batch.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?"
โฆ/B2 Slides/ASE-GUIDE-How the ASE Certification Test Works (v1.0).txt, built to deck ase-guide (public: /academy-player/?deck=ase-guide). It is module-neutral (MODULE: ASE) so ONE lesson can be shown first in ALL 5 tests (B2โB6). Covers: what ASE is + why it matters + the 5 collision tests + Blue Seal; the 5 question types (Direct/Completion, Technician A/B, EXCEPT, LEAST Likely, MOST Likely) each with a real worked example (from Mario's own ASE slideshow, Google Slides id 1ZHiGโฆ, read via the Drive MCP); test length/timing (B2/B3 = 65 total/55 scored/90 min, B4/B5/B6 = 60/50/75, 10 unscored); the on-screen glossary/flag/timer tools; how to register (my.ase.com โ store โ pay โ pick center โ schedule) + a sequence drill; results (emailed in minutes, pass/fail) + a pass-vs-not comparison; the 2-year work-experience requirement (workexp.ase.com); Master (B2+B3+B4+B5) + recert; tips for success; keyterms + termsmatch + cheat sheet + a 10-Q quiz. Grounded in the official ASE 2026 guide ( `OCPs/ASE/`ASE Study Guides/ASE Collision Repair and Refinish Tests Guide 2026.pdf); where his older slides disagreed I followed the official guide (2 yr not 1, 65/55 not 67/12, no fixed pass count, no dollar price per rule 9). Kept GENERIC (no Sheridan reimbursement / "email me" content) so it's sellable to any Academy student. Mario's 3 decisions: module-neutral shown first ยท generic ยท follow official guide.module: ASE now reads as "ASE Certification Guide" everywhere (crumb, side label, splash) instead of the ugly "ASE ASE", and numbers its slides ASE.1โฆASE.N (isGuide/crumbBase in academy-player.html near line 1486).academy_slides/B2-F1.media-manifest.js (the media shopping list, AI-scenes vs real-documents) and structure-check.js (verifies the F01 golden arc + enforces "ASE-intro content lives ONLY in the shared ASE Guide/F01"). Plus lint-lesson.js from before. All three documented in the playbook ยงK. Guide passes: lint 0 errors, structure 0/0.ACADEMY_LESSON_DECKS/ACADEMY_SECTIONS in simulator.html). Not done yet โ it's live only on the public ?deck=ase-guide link.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.node functions/scripts/generate-academy-audio.js ase-guide --force.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?"
โฆ/B2 Slides/ASE-B2-F01-Foundation โฆ (v1.1).txt. Pre-accident-condition's "5 things to match" became 5 separate slides; the 4 documents each got a deep-dive slide; dense slides were split so each fits at full font.REMEMBER: segmented banner (builder + player): a navy header bar over equal-width word segments for a 2โ6 item "remember these" set, with optional inline-SVG icons. Authoring: REMEMBER: heading then - Label | icon-key lines. Icon keys live in REM_ICONS in academy-player.html (palette, grain, sparkles, layers, shield, gauge, ruler, eye, doc, clock, check, alert, camera, spray). Mario WANTS the icons. Banner renders inside the left text column, left-aligned (not full-width โ he rejected the stretched look). Used on F01 for: the 5 matches, Stop/Photograph/Report, the 3 TDS numbers, Report/Refuse/Document. - item) โ renders indented beneath with a lighter marker, SAME font (rule 8). Used on F01 ADAS features, film-build layers, OEM examples, TDS brands.no-assumptions-ask-when-unsure, academy-remember-banner, academy-40px-padding-split-not-shrink, academy-list-as-bullets..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).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.)node functions/scripts/generate-academy-audio.js b2-f01 --force.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.
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".)
/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.)/academy-decks/** + /academy-audio/** to no-cache, must-revalidate, AND the player appends ?cb=<page-load timestamp> (ASSET_BUST) to the deck + audio URLs..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.comparison "The Four Documents That Govern the Job" (Work Authorization ยท Supplement ยท TDS ยท OEM Position Statement) now comes FIRST (F01.10), THEN the TDS deep-dive (F01.11), then later the "Match the Document" imagematch. (Was: TDS shown before any overview, and the overview was wrongly a content slide.) TODO: Mario likely wants a separate deep-dive slide for each of the other 3 documents (Work Auth, Supplement, OEM) to complete "overview โ each one separately โ match".B2-F01-IM1..IM4.jpg. Mario supplies ALL real documents โ never generate/fake one (I made placeholder TDS once; he rejected + they were deleted).wrongright now reads WRONG IMAGE:/RIGHT IMAGE: (not only โฆIMAGE NAME).DOCUMENTS: list field โ multiple clickable doc icons on one slide (- Label | file.jpg per line โ s.documents; player docBtnHTML renders a .doc-row).fitContent shrinks the IMAGE first, THEN the font โ so body text stays full-size and MATCHES the review slides (Mario flagged the review font looked bigger because dense content slides were being font-crushed first). Re-fits on image-settle + fonts.ready (a missing image swaps to a tall 54vh placeholder async). New fitDyk() does the same for Did You Know. Overflow tolerance = >2px (zero scrollbar).fitTitle collapses a split "Label: rest" title to one line, shrinks to โค80% to fit; splash/divider keep their fixed shared size, only wrapping to 2 rows when genuinely long).F01.1โฆ; splash = unnumbered cover, so a 21-slide lesson numbers to F01.20). catName falls back to the unit title when the category is a bare letter (reads "Program Foundation", not "F")..dyk-row max-width 1320โ1550). Divider title = splash title size.DOCUMENT(S): slots.comparison columns, not a content slide).--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.REMEMBER: banner instead. Watch density (more bullets can trigger rule 7's split).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.
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.
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).
TYPE: labassignment โ BUILT. Render student parts only (objective/materials/procedure/quality standard/safety); the builder HIDES the INSTRUCTOR RUBRIC + ORAL QUESTIONS answer key (those feed a future separate lab-grading app).VIDEO PROMPT 1/2/3 on content slides โ BUILT (3 demo-clip briefs per slide; placeholders until Veo clips exist).[VIDEO N] bullet markers โ BUILT. A bullet tagged [VIDEO N] is a STEP. Render as Option 4: one big featured video + a numbered, clickable step playlist (videoSteps()/wireVideoSteps()); builder strips the marker, maps videoCaptions[N]=bullet, pulls tagged bullets out of the left list. (Mario rejected: tiny chip, 3-across storyboard, horizontal small-video rows โ see the memory file.)TYPE: wrongright (red/green two-image compare) and TYPE: comparison (2โ3 column table) โ NOT BUILT YET (don't appear in L26). Build when a lesson uses them.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.)
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.
wrongright + comparison (builder + player), then they're ready for the batch.ACADEMY_SECTIONS/units in simulator.html) to the new 66-lesson map, then retire the old decks.academy-media/<deckId>/) and the Veo demo clips (the VIDEO PROMPT/[VIDEO N] slots are wired and waiting).โฆ/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.
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).
lab slides + the title/divider/content IMAGE PROMPTS. It drops one-line markers where assessment/interactives go: >>> REVIEW:, >>> INTERACTIVE CHECK:, >>> INTERACTIVE: VIN Decoder Practice, >>> INTERACTIVE: OEM Procedure Walkthrough, >>> FINAL QUIZ.academy-media/<deckId>/ folder).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).
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).
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.
**bold** now renders in bullets/body/DYK/Pro Tip/callouts (reverses the old "never use **" rule โ cowork should bold must-remember facts).#4f5a66, light #9aa7b2 (contrasty, themed everywhere).calc(1.08rem*var(--tscale))); breathing room is INNER padding (not outside margin).compact (shrink image/gaps, fonts unchanged) and, if still too tall, an in-slide pager splits them into "1 / 2" screens via Next (keeps page count/TOC/audio/gating intact).viewport-fit=cover + env(safe-area-inset-*) padding on .shell (the simulator runs cover-mode; notch/fullscreen was hiding the top), and made the topbar size to content + center items (was fixed-height flex-end, clipped under zoom)..side.collapsed{display:none!important} on desktop (mobile keeps the absolute-overlay transform slide). Button handlers simplified to a plain click. Lesson: an unreproducible "not clickable" bug = a covering element; remove it from layout (display:none), don't rely on pointer-events/z-index/off-screen.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.
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).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?"
B2-L01 OEM Procedures โฆ (v1.2).txt) holding ONLY metadata (title, goal, objectives, on-the-ASE-test, intro, key terms, tools, supplies โ NO sections) plus one file per section named {LESSON}-S{N} {Section Title}.txt (e.g. B2-L01-S1 Why OEM Procedures Are Mandatory.txt), each containing exactly one --- SECTION --- โฆ --- END SECTION --- block. In โฆ/Academy Lessons/B2 Lessons/ there are 28 header files + 120 section files.build-v3-unit.js was updated to handle this: it groups files by lesson, reads the header then each -S{N} in order, concatenates them, and feeds the existing parser (concatenated = the old single-file layout). Verified: all 5 units parse, section counts match (Foundations 15, U1 40, U2 16, U3 46, U4 3 = 120).IMAGE PROMPT + IMAGE NAME + IMAGE DESCRIPTION (June 18)## Topic cowork now writes KEY POINTS:, then IMAGE PROMPT: (the dev art brief), then IMAGE NAME: (canonical id like B2-L01-S1-IMG1), then IMAGE DESCRIPTION: (one plain-English sentence). The builder uses DESCRIPTION as the visible caption + alt text (NOT the prompt โ that was the bug Mario flagged twice) and NAME as the image filename (b2-l01-s1-img1.jpg, so generated art named that way auto-links). The full prompt stays in the hidden .aiprompt brief.โฆ/B2 Lessons/_spanish_backup/ (restore from there if needed). Verified zero accented + zero Spanish-word remnants. The cowork instructions doc now has a prominent "EVERYTHING IN PLAIN ENGLISH ONLY" rule.build-v3-unit.js other changes this session (run node scripts/build-v3-unit.js <unit> from functions/)## Topic headings now always render as titles even when body text follows immediately (was printing a literal "##").<strong>B2-A.4</strong>).safetyIsFiller). Cowork rule updated: SAFETY is physical-only; legal/professional risk goes in The Why / Common Pitfalls.WHERE THIS IS HEADING: (a new cowork field, a forward-looking note) now renders as a "Where This Is Heading" sub-title + paragraph (was inline text). Present in 5 of the Unit-1 lessons.<p> of section 1 gets .acad-leadcap); the overview intro already had it via .lo-intro.simulator.html (player) changes this session โ all LIVE, deployed to ase-portal-5d37cwindow._qIdb (tiny IndexedDB key/value store, DB aseQCache) now holds the encrypted bank (key qbank_<MOD>, with {entries, ts, v}); loadQuestionsFromDB reads/writes it instead of localStorage (old localStorage q_cache keys are removed). First load caches; later loads are fast. Falls back to no-cache in private mode. Verified _qIdb round-trips in-browser..lesson-hero class; remaining images float L/R.flex-wrap:nowrap), incl. mobile.48pxโ10px 17px 48px (top way down, sides ~65% down), mobile 18px 13px 46pxโ4px 5px 46px; the sticky .subhead / .am-title.am-stick negative margins were matched (-17 desktop, -5 mobile) so the edge-to-edge title backgrounds still bleed cleanly..acad-content .acad-leadcap::first-letter added (matches the .lo-intro drop cap).simulator.html edits inside the big JS string-concatenation block are easy to break (a CSS rule lives in "โฆ" + strings; a stray newline splits a string โ the ENTIRE main script fails to parse). Earlier this session that exact bug shipped once. ALWAYS syntax-check before deploy: node -e 'const fs=require("fs");const h=fs.readFileSync("simulator.html","utf8");const re=/<script(?![^>]*\bsrc=)[^>]*>([\s\S]*?)<\/script>/gi;let m,i=0,b=0;while((m=re.exec(h))){i++;if(!m[1].trim())continue;try{new Function(m[1])}catch(e){b++;console.log("BLK#"+i,e.message)}}console.log(i+" scripts, "+b+" errors")' (expect 0 errors). One known false-positive only appears if a real break exists.build-v3-unit.js) are live without a hosting deploy (the doc is read live); reload + hard-refresh to see. Only simulator.html (player) changes need node node_modules/@11ty/eleventy/cmd.cjs && firebase deploy --only hosting --project ase-portal-5d37c.aseQCache), not localStorage โ if debugging a stale bank, clear that.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.IMAGE NAME ids, upload via upload-academy-media.js, deploy.WHERE THIS IS HEADING / images to any sections missing them.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?"
โฆ/Sheridan Technical/ai-Codex/ASE B-Series Academy (DATA)/Academy Lessons/B2 Lessons/ โ ONE FILE PER LESSON: B2-F1โฆB2-F5 (Foundations) and B2-L01โฆB2-L23. Sibling B3 Lessons/โฆB6 Lessons/. _archive/ holds the old _v3 PILOT + per-unit files โ DO NOT edit _archive/. (The active folder was _v3 PILOT earlier this session; it moved to B2 Lessons/.)UNIT, UNIT TITLE, LESSON: L#/F#, plus a PILOT NOTE preamble before the === ASE ACADEMY LESSON === marker (some files use === ASE ACADEMY LESSON (v3) ===). Each --- SECTION --- has the standard fields PLUS instructor blocks (INSTRUCTOR DEMONSTRATION, DISCUSSION QUESTIONS, PASS STANDARD, COMPETENCY SIGN-OFF, BLOOM) and student blocks (FROM THE FLOOR, LOOK IT UP links, ON THE ASE TEST, TOOLS, SUPPLIES, WORKED EXAMPLE, DID YOU KNOW, inline [IMAGE N] tags).B2-0; U1 Surface Preparation (L1-L9, cat A)โB2-1; U2 Spray Gun Operation (L10-L12, cat B)โB2-2; U3 Paint Mixing/Matching/Applying (L13-L22, cat C/D/E/F)โB2-3; U4 Vehicle Technology (L23, supplemental)โB2-4. Only B2-1 is built/seeded so far.functions/scripts/build-v3-unit.jsRun 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.
factbox removed from the float set). Blue clickable key terms are bold. The drawer breadcrumb chip is a box matching "Return to Lessons". The drawer's redundant "Table of Contents" row was removed (kept the blue "LESSON CONTENTS" header). Per-page Key Terms footer is page-specific (only terms whose name appears on that page).functions/scripts/academy-build/COWORK-LESSON-INSTRUCTIONS.mdNow 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.
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.
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.
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.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.
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).
academy-media-incoming/ (named per the Question Image Helper cards) โ node functions/scripts/upload-academy-media.js (uploads to Storage academy_media/ + refreshes the ACADEMY_MEDIA manifest in simulator.html) โ deploy. The 15 images he made for lessons 1 & 11 are live. wire-academy-images.js already stamped data-file onto all 156 placeholders in the live doc (one-time), so future drops just need upload + deploy. The build STRIPS the dev .aiprompt filenames, so the player matches by data-file โ don't expect filenames in the live doc HTML.b2-u<unit>-l<lesson>-hero.jpg).add-bigpicture-recaps.js).enhance-sds-tds-content.js: "How to Find an SDS Online (free)" + real PPG/Axalta links, a find-it-online first step in the Read-Five-SDS lab, a GHS Hazard Pictograms section, and "How to Find a TDS Online". Pictograms use the REAL official public-domain GHS SVGs (swap-official-pictograms.js). External links open in a new tab via a new bridge extlink handler.academy-media-incoming/, run upload-academy-media.js, deploy.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).
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?"
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).
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).
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.startDrill('1')/startSimulation('sectionExam','1') but '1' is not a real ASE category; map each unit to its ASE category (Unit 1 โ F).mockups/academy-animations.html) to embed into lessons..imgph placeholders with the real generated media (the 289 prompts in the Question Image Helper) once hosted in academy-gated Storage.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?"
question-image-helper.html). The page now has two toggle tabs at the top: Question Images (the original 510 per-question prompts, untouched) and ASE Academy. The Academy view is a library of 289 ready-to-paste Google Labs prompts (253 images + 36 animations) covering all 34 ASE categories across B2โB6, grouped under per-category headers, filterable by module (B2 52 / B3 53 / B4 35 / B5 68 / B6 81), with type badges (Image/Animation), Copy Prompt, exact filename + copy-name, and the same localStorage Done-tracking/progress bar as the questions. Mario's image recipe is kept verbatim (only the Subject changes); a parallel short-clip recipe was added for Animation items (a still-image prompt can't make video). Filenames follow {MOD}-{CAT}-{slug}.jpg|.mp4 (e.g. B2-A-surface-prep-hero-overview.jpg, B2-A-da-cross-hatch-stroke-motion.mp4). Destination for the generated media: Firebase Storage academy_media/ (to be wired into each Academy lesson as it's built). Subjects are deliberately VERY descriptive (object/state, materials, camera angle, lighting, PPE; animations include an explicit "Motion:" clause) so Google Labs needs fewer retries โ that was Mario's explicit ask./tmp/academy_prompts/{MOD}-{LETTER}.json ({mod,letter,label,items:[{type,title,slug,subject}]}); then /tmp/assemble_academy.js <page.html> assembled them (built uid/file/prompt, deduped slugs, kept Mario's verbatim image recipe + the animation recipe) and replaced the const QI_ACADEMY = []; /* ACADEMY_DATA_PLACEHOLDER */ line in the page. To add/edit categories later, re-run that pattern (those /tmp files are ephemeral, regenerate them). UI plumbing in the page: qiView ('questions'|'academy'), qiSet(), qiSwitchView(), QI_RECIPE_IMG/QI_RECIPE_ANIM, and a branch in qiRender().npx @11ty/eleventy failed again with eleventy: Permission denied (Google Drive evicted/stripped exec on node_modules/.bin/eleventy). Workaround that worked: node node_modules/@11ty/eleventy/cmd.cjs then firebase deploy --only hosting --project ase-portal-5d37c. (See the May 29 env note.)syncNewsletterSignup (Firestore onCreate trigger on newsletter_signups) using the MAILERLITE_API_KEY secret. backfillNewsletterToMailerLite (admin callable) + a green "Push to MailerLite" button on dashboard2 push the existing list. MailerLite chosen over Brevo. (Mario still to click "Push" once.)/newsletter-template (also embedded in dashboard2 โ Newsletter as "Compose Monthly Email" via a cache-busted iframe โ it sets src with Date.now() so it never serves stale). Fill-in-the-blanks: subject line (char counter + Copy button), inbox preview text (injected as a hidden preheader), auto-current-month, this-month's-tip, featured-article dropdown (from collections.blog, with hero image), a "Fresh from the blog" auto-section (3 latest posts w/ hero thumbnails), and centered brand social icons (images/social/{instagram,facebook,tiktok,linkedin}.png, generated via PIL โ email-safe, since FontAwesome doesn't render in sent mail). Matches the welcome-email design (logo, navy/orange). Uses MailerLite tags {$name} / {$unsubscribe}. "Send test to Mr. Hernandez" button โ sendTestNewsletterEmail (recipient HARDCODED to mr.hernandez@asecollisiontestprep.com).sendVerificationEmail Cloud Function mints the link with generateEmailVerificationLink (respects the /action handler) and sends branded HTML via iCloud SMTP, replacing Firebase's plain template. register.html + verify.html call it, with a fallback to Firebase's built-in so signup can't break. Has a "Website Features" section (no access/pricing pitch). Admin "Email me a test verification" button (sendTestVerificationEmail) on the dashboard2 Roster panel.rk-single). Every admin panel has a menu home (Question Coverage rides with Database, Blog Images with Blog Composer, Compose with Newsletter โ via RK_GROUPS).relatedPosts filter returns image; falls back to the gradient+icon only if a post has none).window.db; the page exposes db).syncNewsletterSignup, backfillNewsletterToMailerLite, sendVerificationEmail, sendTestVerificationEmail, sendTestNewsletterEmail. Secret added: MAILERLITE_API_KEY.window.updateNavBadges() (called on nav, stats load, flashcard load, auth, test clear): Weakness Drills #drillsNavBadge (# cats <70% from userCatStats), Flashcards #flashNavBadge (unviewed cards = fcData vs viewed_terms), Review #reviewResumeIcon (pulsing โธ, CSS navResumePulse), Support #supportNavBadge (window.updateSupportBadge, unseen admin replies, cached ~10min in support_reply_ids_v1/seen in support_seen_ids_v1, cleared via markSupportRepliesSeen on opening Support). Firestore rule added so a student can read their OWN support_tickets (resource.data.studentId == request.auth.uid). CONFIRMED LIVE 2026-06-04 (Mario's screenshot shows Review 5 / Drills 6 / Flashcards 570).resumeTestState/clearTestState: (1) pulsing โธ on the Review nav item; (2) #reviewResumeBanner at top of Review Sections (Resume/Discard, updateReviewResumeBanner/discardReviewResume); (3) an orange "Resume Practice/Cert Exam" button + accent border on the SPECIFIC ASE category card whose paused test matches (in populateReviewSections, detected via _paused from active_test; switchView('review') re-renders cards when dbQuestionsPool is loaded). All gate on active_test having mode === 'practice' || 'sectionExam'./simulator/, /dashboard2/, etc.) were served with Firebase's default max-age=3600 because the header rule only matched **/*.html (clean URLs have no extension). This made deploys invisible for up to an hour without a hard refresh โ the reason the simulator resume features "weren't showing." Added a "source": "**/" headers rule in firebase.json โ directory clean URLs now max-age=300, must-revalidate; asset files (with extensions) keep their 1-year cache. Verified live.active_test has mode practice/sectionExam). NEXT: start a Review Section practice test โ click another menu item โ "Resume Later" โ confirm all three appear on Review. If they still don't, inspect localStorage key {APP_CONFIG.lsPrefix}active_test โ confirm it exists and mode is 'practice'. The lsPrefix is ase_<module>_ (e.g. ase_b2_). updateNavBadges + updateReviewResumeBanner read that exact key. Suspect to check: does saveTestState actually persist during a review practice test (autosave hooks), and is currentExamMode truly 'practice' for section-review tests (startSimulation line ~5794 sets currentExamMode = mode).gearType column โ Mario populated it in the Sheet (filter sub-row reads it, ~10-min cache).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.
Branded password-reset email โ the matching companion to the verification email (Console reset template is still Firebase's plain one).
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.
Tool ID data โ populate tools_b2โฆb6 at /tool-uploader (offer to generate starter B2 bulk-JSON).
Newsletter extras not yet chosen โ a "welcome to the newsletter" auto-email (MailerLite automation, guided) and an auto plain-text version.
First-visit simulator tutorial (mobile + desktop); support-ticket resolution notifications to students.
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.
More SEO blog articles (for AdSense).
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.
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
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.
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.
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).
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.
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.
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).
asecollisiontestprep.com (and www.asecollisiontestprep.com if used). Code now retries without the redirect URL so emails still send, but allowlisting restores the post-verify redirect to /dashboard.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?"
flagged_questions/{uid}, one doc per student, items[] of snapshots: uniqueId, module, catLabel, text, options, correct, exp, flaggedAt). Cross-device. Loaded with ONE read on auth; saves are debounced (1.5s). Firestore rule added: owner read/write, admin read. Key fns in simulator.html: loadFlagLocker, saveFlagLockerNow/scheduleFlagLockerSave, addFlagToLocker/removeFlagFromLocker, syncFlagLockerFromTest (hooked into toggleFlag/toggleFlagFromSidebar), isQuestionFlagged, flagKey ("MODULE::uniqueId"), cap 500..bento-card class there โ that class forces flex-direction:column !important + big padding and made it huge. We use a custom barStyleBase inline style). Tapping it (or its Review button) calls window.startFlaggedPractice() โ opens a re-quiz (mode 'practice', isDrillMode) of ONLY the current module's flagged questions inside the exam view. Count badge on the Review nav item = #flagNavBadge via updateFlagCountBadge.ase_cookie_consent_v1 consent the rest of the site uses, and defines window.trackEvent. Active simulator users now appear in GA4 realtime.ase_last_login_log) in layout.html onAuthStateChanged AND simulator.html auth block, writing to recent_logins with method 'session'. login.html now also sets the throttle key after its manual write to avoid double-logging.#view-tools (per Mario, still building it out)..feature-img opens the #featureLightbox). Mario was seeing a cached page. Resolution = hard refresh. No bug.schoolName == "Sheridan Technical College (FL)", but the register page ALSO offers "Sheridan Technical College โ Hollywood" (same school). Students who picked the Hollywood variant are missing from dashboard3. Pending Mario's call: merge the duplicate register option OR make dashboard3 match both spellings.#flagNavBadge style:
nav-drills) โ # categories below 70% (from window.userCatStats; free).nav-review) โ a SEPARATE small โธ / pulsing-dot icon NEXT TO the existing flagged count, shown ONLY when a paused REVIEW/practice test exists (currentExamMode 'practice'/'sectionExam' saved in localStorage active_test). Do NOT put a resume badge on Test Simulator (nav-exam) โ full exams never nudge resume. (Mario's explicit decision.)nav-flashcards) โ cards left to review/unviewed this module (localStorage viewed_terms vs fcData; free).nav-feedback) โ # new admin replies on the student's tickets; costs a small Firestore read, so CACHE it (short TTL).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.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.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.
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?"
/tool-uploader (NEW admin page) โ full add/edit/delete UI for tools_b2โฆb6 with image upload to Firebase Storage, icon picker, 3-distractor quiz fields, ASE task refs, bulk JSON paste, live preview card. Admin-only._capRect() caps tall targets to 280px so the tooltip stays visible on the Leaderboard, Cert Journey, Student Code panels. Tall targets scroll with block: 'start'. New newsletter step added at step 16.newsletter_signups doc (for the dedup check).leaderboards/{uid}.schoolName from users/{uid}.schoolName for every student. Busts the 5-min leaderboard cache so the column lights up immediately on next dashboard visit.bundled_tools_* to tools_b2โฆb6.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:
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.Mario said: "going home to work from Mac Studio" โ meaning he wants to test the new uploader against a real .txt file at home.
/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.doPost for ASE Simulator Reports sheet โ Mario still needs to share the Apps Script code from Sheet โ Extensions โ Apps Script.gearType column โ populate to enable the cascading filter sub-row."Welcome back from the Mac Studio. The new question uploader and tool uploader both shipped โ open
/Firebase Question Uploader/admin_uploader.htmlto 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?"
Gear page overhaul (gear.html + supporting pages):
1IsL6k5_gbf9LNyhvAnoDEz5-9tUksKIvuwmR1Rp3njo wired into GEAR_SHEET_ID. Cache TTL reduced from 1h โ 10min. Gviz parser fixed with headers=1 + safety net for missing labels./gear-helper โ drag-to-toolbar Amazon scraper. Auto-extracts title/image/price/rating/reviewCount from any Amazon product page, opens an editable panel, "Copy Row" puts a tab-separated row on clipboard ready to paste into Sheet. Chrome 148 stripped large javascript: URLs, so the bookmarklet is now a tiny loader (~640 chars) that fetches /gear-grabber.js from our domain via fetch+eval. Works perfectly with Amazon's CSP. CORS header added in firebase.json. Confirmed extraction works on real Amazon pages (3M 8210 respirator: title, image, $18.99, 4.4 stars, 19306 reviews all extracted).productType group: TOP RATED (rating โฅ 4.5), MOST REVIEWED (highest reviewCount), BEST VALUE (lowest price), MOST EXP (highest price). Manual ribbon overrides any auto. New manual ribbons: pick ("INSTRUCTOR'S PICK", red) and required ("CLASS REQUIRED", deep red).gearType chips for the selected module. New gearType column (M) in the sheet; module can be multi-value (b2,b3) or all for universal items.rgba(243,156,18,0.45)).support_tickets Firestore with type: "Gear Report". Admin sees in dashboard2 alongside other tickets./gear-audit โ scans every item, checks image loads + URL reachability (no-cors fetch). Shows table with image/URL status badges. Admin-only./images/gear/ local hosting). FALLBACK_GEAR array emptied.gear-template.csv updated with new columns (productType, price, rating, reviewCount, gearType).rel="sponsored noopener" on every Amazon link.Media release form (NEW โ /media-release):
users/{uid} doc. Shows green "We pre-filled your info" banner when authenticated; blue "Tip: log in to auto-fill" hint when not.dob).media_releases Firestore collection with IP (via ipify.org), user agent, timestamp, releaseVersion. Firestore rules allow anonymous create with required-field validation; only admin can read.Jeopardy game rebuild (/jeopardy):
SOURCE: Auto Body Repair Technology 7th Edition - Chapter N (word-boundary-safe).Classroom (/classroom):
/jeopardy opens in new tab).bundled_data/master_terminology_<mod> (with legacy master_terminology fallback).SOURCE: marker in explanation.--text-dark which inverted in dark mode โ unreadable).Dashboard.html (student):
Dashboard2 (admin):
runAnalyticsSyncNow โ wrapped in proper top-level try/catch; surfaces specific error messages (missing secrets, GA4 permission, Firestore write failures) instead of generic "internal". Clarity is now optional (function continues with GA4 only if Clarity secret is missing).Simulator (/simulator):
dob on user doc.correctCount, incorrectCount, skippedCount, scoredQuestions, totalQuestions (+ totalScored backward-compat).B2-B6 (/b2 through /b6):
applyStudentCode JS function. Student codes are now only redeemed from dashboard.html.?ase= param)#view-tools) โ needs tools_b2โฆb6 Firestore data populatedase_dash_tutorial_seen_v1 + page tour + โ help button)markPatchAsPassed)maybeShowUpsellAd() at simulator.html line 5445; full overlay+card UI, fires from navQuestion forward + answer selection, free-tier onlyApps 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.
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.
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).
collisionre03-20Amazon 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.
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.
"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?"
admin_uploader.html toolNo technical jargon. Say "the button now shows for all terms," not "removed the hasImage gate from the conditional."
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.
Verify visually. After UI changes, take a screenshot in preview or send a deploy so the user can see it on the live site.
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
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.)
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
ase-portal-5d37c (project number: 319996537465)G-NX6HH6GKCG) + Microsoft Clarity (wwplh93pi6)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 |
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
| 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).
| 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.
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:
customers/{uid}/checkout_sessions/ with { line_items: [{ price: priceId }], metadata: { module: "BX" } }url backcustomers/{uid}/payments/{sessionId}priceId in priceToModuleMap, and unlocks only the matching module for exactly the matching durationB2 โ Painting and Refinishing
price_1TbUc0IwFVQrPexDJ3lvUi6Lprice_1TbUc1IwFVQrPexDOKfF5GMpprice_1TbUc2IwFVQrPexDMlwZTb61B3 โ Non-Structural Analysis and Damage Repair
price_1TbUc3IwFVQrPexDYFrV5aPK ยท 60d: price_1TbUc5IwFVQrPexDNkKei1mH ยท 90d: price_1TbUc6IwFVQrPexDAT3rLIW1B4 โ Structural Analysis and Damage Repair
price_1TbUc7IwFVQrPexDlohesIUl ยท 60d: price_1TbUc8IwFVQrPexDM96Rzn26 ยท 90d: price_1TbUc9IwFVQrPexD09bx5vDwB5 โ Mechanical and Electrical Components
price_1TbUcAIwFVQrPexDtTWjdqsU ยท 60d: price_1TbUcBIwFVQrPexDtD8tCy8F ยท 90d: price_1TbUcCIwFVQrPexDkBb0Dr8iB6 โ Damage Analysis and Estimating
price_1TbUcDIwFVQrPexDYpH8QMvw ยท 60d: price_1TbUcEIwFVQrPexDIVdxXFyL ยท 90d: price_1TbUcFIwFVQrPexDJfQVZ6ZZWhere these IDs live in code:
b2.htmlโb6.html: const price1Month/2Month/3Month near top of script sectiondashboard.html: priceToModuleMap (~line 1007) and MODULE_PRICES (~line 1404)functions/index.js: priceCatalog (custom webhook backup)Promo codes: allow_promotion_codes: true is set in all checkout sessions. Promo codes managed in Stripe Dashboard โ Coupons.
TECHSAVE10 โ 10% off, duration: once โ WORKSFREETESTPREP2026 โ 100% off โ REJECTED BY STRIPE (Checkout in mode: payment cannot apply discounts that result in $0 totals). User has chosen to skip this; will create a different code.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):
firestore-stripe-payments-STRIPE_API_KEY โ live rk_live_... restricted keyext-firestore-stripe-payments-STRIPE_WEBHOOK_SECRET โ live whsec_...STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRET โ not yet set (only needed if reviving the custom webhook in functions/index.js)NEVER print API keys, webhook secrets, or any credentials in chat. They live only in Secret Manager.
Cleared on 2026-05-28 โ Mario wiped the legacy task list. The active tasks below are what we're working through now.
/gear โ see "Next active task" section above. Option A vs B vs C decision pending.simulator.html
tools_b2โฆb6 Firestore data so the Tool ID Gallery + Quiz views stop showing "Coming soon".blog
404.html
gear.html
/gear highlighting items that dropped in price recently. Two ways to build this at $0; pick one with Mario before starting:
{price, date} to a Firestore gear_prices/{asin}/history/ subcollection. Mario re-grabs his ~30 products weekly. Gear page reads the last 2 entries per item, flags items where current < previous as "Price Drop". 100% within Firebase free tier. Downside: requires Mario to actually re-click the bookmarklet on each product weekly.pubsub.schedule('every day 06:00')) fetches each Amazon product page, parses the price with the same selectors the bookmarklet uses, writes to Firestore. ~30 products ร daily = 900 invocations/month โ well within Firebase Spark plan free tier. Risks: (1) Amazon blocks data-center IPs aggressively โ Cloud Functions egress comes from Google Cloud ranges which Amazon flags fast; (2) breaks Amazon ToS technically; (3) selectors break when Amazon redesigns; (4) if blocked you'd need a residential proxy ($$$ โ breaks the $0 rule).collisionre03-20, so this path is now available. Fully ToS-compliant, free, reliable; this is the one to build.blog.html
index.html
docs/index-futureproofing-audit.md). Mario wants suggestions on what modifications to make.mode: payment. Use 99% off if you need effectively-free.hasImage field in Firestore is unreliable โ show the VIEW IMAGE button based on uniqueId presence, not on hasImage.--no-verify or skip git hooks unless the user explicitly asks..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.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>.Before starting work:
While working:
npx @11ty/eleventy to verify build is cleanfirebase deploy --only hosting --project ase-portal-5d37cWhen stuck or unsure:
Check the auto-memory files (live on the current Mac only, may not be present on other machines):
~/.Codex/projects/-Users-mariohernandez-Library-CloudStorage-GoogleDrive-mm-hdz-icloud-com-My-Drive-Sheridan-Technical-ASE-website-ASE-Portal/memory/MEMORY.md (and siblings)If you're on a fresh machine and those don't exist, this AGENTS.md is the canonical context.