Files
pest-nocturne/add_features.py

168 lines
9.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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(
" <div class=\"wa-cats\">${cats}</div>\n"
" <div style=\"margin-top:14px\">",
" <div class=\"wa-cats\">${cats}</div>\n"
" <div class=\"wa-progress\">\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 `<div class=\"wa-mrow\"><span class=\"wa-mcat\">${c}</span><div class=\"wa-mbar\"><div class=\"wa-mfill\" style=\"width:${pct}%\"></div></div><span class=\"wa-mpct\">${pr.t?pr.c+'/'+pr.t+' ('+pct+'%)':'untouched'}</span></div>`;}).join('')}\n"
" <button class=\"wa-clear\" id=\"wa-clear\">Reset progress</button>\n"
" </div>\n"
" <div style=\"margin-top:14px\">",
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'''
<div class="wa-checklist">
<div class="chk-head"><h3>License application checklist</h3><button class="btn" id="wa-print" style="padding:8px 16px;font-size:.8rem">Print / save PDF</button></div>
<ol class="chk-list">
<li><b>Confirm your category.</b> Structural = the commercial pest-control route; add Ornamental &amp; Turf or Agricultural as needed. Each category = its own exam.</li>
<li><b>Order study materials.</b> WSU Extension (<a href="https://extension.wsu.edu" target="_blank">extension.wsu.edu</a>) + the EM019 manual. The field guide, flashcards and WA Prep tab on this site cover the same ground.</li>
<li><b>Schedule the exam</b> through WSDA at (360) 902-2020. The core (general) exam is required first; category exams after.</li>
<li><b>Pay the fee.</b> $33 exam + $73 license. Bring ID.</li>
<li><b>Sit the exam.</b> 50&ndash;100 multiple-choice, 120 minutes, closed book, 70% to pass.</li>
<li><b>After passing</b> — your WSDA license is issued; renew every 5 years with 40 CEUs.</li>
<li><b>Keep records.</b> Restricted-use pesticide applications must be recorded for 2 years.</li>
</ol>
<p class="chk-note">One-pager · WSDA (360) 902-2020 · agr.wa.gov · Olympia WA 98504</p>
</div>
<style>@media print{body *{visibility:hidden}.wa-checklist,.wa-checklist *{visibility:visible}.wa-checklist{position:absolute;left:0;top:0;width:100%;padding:20px}#wa-print{display:none}}</style>
'''
# insert the checklist before the WA quick test in the v-wa render
s = s.replace(
" <div class=\"wa-checklist\"", # no-op guard
" <div class=\"wa-checklist\"", # keep
1,
) if "wa-checklist" in s else None
# find the v-wa render and insert checklist before the quick test section
anchor = '<div class="facts"'
# Actually inject into the v-wa innerHTML — find a stable anchor inside it
old_wa_anchor = 'WA quick test'
if old_wa_anchor in s:
# find the start of the WA quick test section within the v-wa render
idx = s.find(old_wa_anchor)
# back up to the section start
sec = s.rfind('<section', 0, idx)
s = s[:sec] + CHECKLIST + s[sec:]
# print button wiring (v-wa render is a bare innerHTML at EOF; wire via a tiny script)
s = s.rstrip() + r'''
<script>
(function(){ var b=document.getElementById('wa-print'); if(b) b.addEventListener('click', function(){ window.print(); }); })();
</script>
'''
# ============ 3. DISTINGUISH-THESE-PESTS ID section ============
DIST = r'''
<div class="wa-dist">
<h3>Distinguish these pests (exam favorites)</h3>
<div class="dist-grid">
<div class="dist-card"><h4>Ant vs. termite</h4><ul>
<li><b>Wings</b> — ant: front pair longer than hind; termite: all four equal.</li>
<li><b>Waist</b> — ant: pinched (narrow); termite: broad, straight-sided.</li>
<li><b>Antennae</b> — ant: elbowed (bent); termite: straight, bead-like.</li>
</ul></div>
<div class="dist-card"><h4>Carpenter ant vs. moisture ant</h4><ul>
<li><b>Thorax</b> — carpenter: smooth, evenly rounded (no notch); moisture: uneven/notched.</li>
<li><b>Habitat</b> — carpenter: sound wood, hollows galleries; moisture: rotting, damp wood.</li>
<li><b>Frass</b> — carpenter: coarse sawdust + dead ants; moisture: fine, mixed with soil.</li>
</ul></div>
<div class="dist-card"><h4>Dampwood vs. subterranean vs. drywood termite</h4><ul>
<li><b>Dampwood</b> — largest, big dark-headed soldiers, needs moist wood, NO ground contact.</li>
<li><b>Subterranean</b> — needs soil contact, builds mud shelter tubes.</li>
<li><b>Drywood</b> — dry wood only, kicks out hard 6-sided pellets (no mud).</li>
</ul></div>
<div class="dist-card"><h4>Hobo spider vs. giant house spider</h4><ul>
<li><b>Legs</b> — hobo: shorter, ~1.5× body; giant house: very long, ~2× body.</li>
<li><b>Markings</b> — hobo: chevron pattern on abdomen; giant house: plainer.</li>
<li><b>Bite</b> — hobo's "necrotic bite" is a disproven myth; neither is medically significant.</li>
</ul></div>
<div class="dist-card"><h4>German vs. Oriental vs. American roach</h4><ul>
<li><b>German</b> — small (~½"), two dark stripes on pronotum, indoor, fastest breeder.</li>
<li><b>Oriental</b> — dark, shiny, ~1", damp basements/sewers, male wings short.</li>
<li><b>American</b> — largest (~1½"), reddish-brown, sewers/basements, can fly.</li>
</ul></div>
<div class="dist-card"><h4>Western black widow vs. false widow</h4><ul>
<li><b>Hourglass</b> — widow: red hourglass on the <em>underside</em> (not the top).</li>
<li><b>False widow</b> — brownish, no red hourglass.</li>
<li><b>Only the female widow</b> is dangerously venomous; males are tiny and harmless.</li>
</ul></div>
</div>
</div>
<style>
.wa-dist{margin-top:28px;border-top:1px solid var(--line,#2C3455);padding-top:22px}
.wa-dist h3{font-family:Fraunces,serif;font-size:1.4rem;margin-bottom:14px}
.dist-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:14px}
.dist-card{background:var(--surface,#141A30);border:1px solid var(--line,#2C3455);border-radius:12px;padding:16px}
.dist-card h4{color:var(--brass,#E3A53F);margin-bottom:8px;font-size:1rem}
.dist-card ul{margin:0;padding-left:18px}
.dist-card li{margin-bottom:6px;font-size:.88rem;line-height:1.45;color:var(--text,#EDE7D6)}
.dist-card li b{color:var(--verdigris,#54BFA9)}
.wa-checklist{margin-top:28px;border:1px solid var(--line,#2C3455);border-radius:14px;padding:20px;background:var(--surface,#141A30)}
.chk-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;gap:10px}
.chk-head h3{font-family:Fraunces,serif;font-size:1.4rem;margin:0}
.chk-list{padding-left:20px;margin:0}
.chk-list li{margin-bottom:8px;line-height:1.5;color:var(--text,#EDE7D6)}
.chk-note{font-size:.78rem;color:var(--text-faint,#6E7688);margin-top:12px}
</style>
'''
# 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('</div>\n <style>@media print', DIST + '\n <style>@media print', 1)
open(p, "w").write(s)
print("added progress tracker + WSDA checklist + ID distinctions; size", len(s))