Rename to Evergreen Applicator + SEO (title, meta, OG, JSON-LD, canonical)

This commit is contained in:
drjones
2026-09-01 14:23:25 -07:00
parent b975b445ca
commit 9ed89ae6ea
5 changed files with 279 additions and 24 deletions

167
add_features.py Normal file
View File

@@ -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(
" <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))