diff --git a/README.md b/README.md index af15644..f1ceb2b 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,25 @@ -# Pest Nocturne ๐Ÿชฒ +# Evergreen Applicator ๐ŸŒฒ -A midnight specimen-cabinet study suite for the **WSU EM019** pest management -certification exam. The complete *Study Manual for Pest Management -Professionals* rebuilt as an interactive, illustrated single-page app. +**Washington pesticide applicator exam prep** โ€” an interactive single-page study +suite for passing the WSDA pesticide applicator license (Core + Structural + +Ornamental & Turf + Agricultural). Built from the *Study Manual for Pest +Management Professionals* (WSU EM019) plus an original question bank. ## Live -**https://nocturne.thetempleofdoom.com** +**https://evergreenapplicator.thetempleofdoom.com** ## What's inside -- **Field guide** โ€” all 13 chapters with illustrated plates +- **WA Cert** โ€” WSDA licensing guide: exam format, fees, categories, CE requirements, printable application checklist +- **Field guide** โ€” all 13 chapters with real pest photos (Wikimedia, CC/PD) - **Identification keys** โ€” branching keys, walked one couplet at a time - **House faults** โ€” 22 points of faulty construction/maintenance, in order -- **Flashcards** ๐Ÿ”’ โ€” premium -- **Practice exam** ๐Ÿ”’ โ€” premium (scored, with the official answer key) -- **Glossary** โ€” every defined term, searchable as you type +- **WA Prep** โ€” 318 questions (249 original + 69 from the manual) with instant feedback + a timed 50-question mock exam (120 min, 70% pass) +- **Flashcards** ๐Ÿ”’ / **Practice exam** ๐Ÿ”’ โ€” premium +- **Glossary** โ€” every defined term, searchable ## Monetization -Freemium: the field guide, keys, and house faults are free. **Flashcards and -the practice exam are a $5 Bitcoin unlock** via BTCPay. +Freemium: the field guide, keys, house faults, WA Cert, and WA Prep are free. +**Flashcards and the practice exam are a $5 Bitcoin unlock** via BTCPay. - BTCPay store: `FDT6DHWEaA7DF5WFePkp4pufgQ9yy1G6JfzTCwey1jYn` - Price: $5 USD (on-chain BTC) @@ -25,17 +27,20 @@ the practice exam are a $5 Bitcoin unlock** via BTCPay. ## Stack - Single self-contained `index.html` (vanilla JS SPA, no build step) -- Flask (`app.py`) for the paywall + BTCPay webhook +- Flask (`app.py`) for the paywall + BTCPay webhook (HMAC-verified) - nginx reverse proxy โ†’ Flask :5000 - SQLite (`orders.db`) for order state ## Deploy - Proxmox LXC CT **117** (`nocturne`) @ 10.30.20.234 - systemd `nocturne.service`, nginx `sites-enabled/nocturne` -- Cloudflare tunnel `thetemple-fleet` (`1aeb1ac0`), ingress โ†’ `:80` +- Cloudflare tunnel `thetemple-fleet` (`1aeb1ac0-3c49-4801-b61d-bc92e6e4932a`), ingress โ†’ `:80` ## Files -- `index.html` โ€” the study suite + injected paywall gate + BMAC footer -- `app.py` โ€” Flask backend (BTCPay invoice, webhook, unlock status) -- `inject.py` โ€” one-shot script that injects the paywall + footer into the HTML +- `index.html` โ€” the study suite (paywall gate, photos, WA Prep, BMAC footer injected) +- `app.py` โ€” Flask backend (BTCPay invoice, webhook, unlock status, image serving) +- `images/` โ€” 25 real pest photos (Wikimedia Commons) +- `wa_exam_data.py` / `wa_exam_data2.py` โ€” the original 249-question bank (part 2 imports part 1) +- `rebuild_wa_prep.py` โ€” build script for the WA Prep view + mock exam +- `inject.py` / `integrate_photos.py` / `add_wa_view.py` / `rename.py` โ€” one-shot build scripts - `nocturne.service` / `nocturne.nginx` โ€” service + nginx config diff --git a/add_features.py b/add_features.py new file mode 100644 index 0000000..1fc71b7 --- /dev/null +++ b/add_features.py @@ -0,0 +1,167 @@ +import io + +p = "/Users/drjones/pest-nocturne/index.html" +s = open(p).read() + +# ============ 1. PROGRESS TRACKER (localStorage, per-category) ============ +# add progress state after the mock declaration +s = s.replace( + " let mock = { qs: [], answers: [], secs: 0, timer: null };", + " let mock = { qs: [], answers: [], secs: 0, timer: null };\n" + " let progress = JSON.parse(localStorage.getItem('pn_progress')||'{}');\n" + " function saveProgress(){ localStorage.setItem('pn_progress', JSON.stringify(progress)); }\n" + " function clearProgress(){ progress = {}; localStorage.removeItem('pn_progress'); }", + 1, +) + +# record progress on every practice answer +s = s.replace( + " if (correct) score++;\n answered++;", + " if (correct) score++;\n answered++;\n" + " const pc = q.cat; if (!progress[pc]) progress[pc] = {c:0, t:0};\n" + " progress[pc].t++; if (correct) progress[pc].c++; saveProgress();", + 1, +) + +# progress bars UI (after the category buttons row) +s = s.replace( + "
${cats}
\n" + "
", + "
${cats}
\n" + "
\n" + " ${CATS.filter(c=>c!=='All').map(c=>{const pr=progress[c]||{c:0,t:0};const pct=pr.t?Math.round(pr.c/pr.t*100):0;return `
${c}
${pr.t?pr.c+'/'+pr.t+' ('+pct+'%)':'untouched'}
`;}).join('')}\n" + " \n" + "
\n" + "
", + 1, +) + +# wire the clear button +s = s.replace( + " const modeBtn = root.querySelector('#wa-mode');", + " const clearBtn = root.querySelector('#wa-clear');\n" + " if (clearBtn) clearBtn.addEventListener('click', ()=>{ clearProgress(); render(); });\n" + " const modeBtn = root.querySelector('#wa-mode');", + 1, +) + +# progress CSS +s = s.replace( + ".wa-cats .wac.on{", + ".wa-progress{margin-top:16px;display:flex;flex-direction:column;gap:7px;max-width:560px}\n" + ".wa-mrow{display:flex;align-items:center;gap:10px;font-size:.78rem}\n" + ".wa-mcat{width:130px;color:var(--text-dim,#A3A99C);flex-shrink:0}\n" + ".wa-mbar{flex:1;height:8px;border-radius:999px;background:var(--surface-2,#1B2239);overflow:hidden}\n" + ".wa-mfill{height:100%;background:linear-gradient(90deg,var(--brass,#E3A53F),var(--verdigris,#54BFA9));border-radius:999px;transition:width .3s}\n" + ".wa-mpct{width:120px;text-align:right;color:var(--text-dim,#A3A99C);font-family:monospace}\n" + ".wa-clear{margin-top:10px;background:none;border:1px solid var(--line,#2C3455);color:var(--text-faint,#6E7688);padding:5px 12px;border-radius:8px;cursor:pointer;font-size:.72rem;align-self:flex-start}\n" + ".wa-clear:hover{border-color:var(--crimson,#DE5F45);color:var(--crimson,#DE5F45)}\n" + ".wa-cats .wac.on{", + 1, +) + +# ============ 2. WSDA CHECKLIST (printable one-pager in WA Cert view) ============ +CHECKLIST = r''' + + +''' + +# insert the checklist before the WA quick test in the v-wa render +s = s.replace( + "
+(function(){ var b=document.getElementById('wa-print'); if(b) b.addEventListener('click', function(){ window.print(); }); })(); + +''' + +# ============ 3. DISTINGUISH-THESE-PESTS ID section ============ +DIST = r''' + + +''' + +# inject DIST into the v-wa render, after the checklist (or before quick test) +if 'wa-checklist' in s and 'distinguish these pests' not in s.lower(): + # inject after the checklist block + s = s.replace('
\n