Files
pest-nocturne/add_wa_prep.py

122 lines
6.2 KiB
Python

import json, sys
sys.path.insert(0, "/Users/drjones/pest-nocturne")
from wa_exam_data import WAEXAM
p = "/Users/drjones/pest-nocturne/index.html"
s = open(p).read()
# 1. Add WA Prep tab to VIEWS
old_views = 'const VIEWS=[["home","Suite"],["field","Field Guide"],["keys","Keys"],["house","22 Points"],["cards","Flashcards"],["exam","Exam"],["gloss","Glossary"],["wa","WA Cert"]];'
new_views = 'const VIEWS=[["home","Suite"],["field","Field Guide"],["keys","Keys"],["house","22 Points"],["cards","Flashcards"],["exam","Exam"],["gloss","Glossary"],["wa","WA Cert"],["waprep","WA Prep"]];'
assert old_views in s, "VIEWS not found"
s = s.replace(old_views, new_views)
# 2. Add section
old_sec = '<section class="view" id="v-wa"></section>'
new_sec = old_sec + '\n <section class="view" id="v-waprep"></section>'
assert old_sec in s, "v-wa section not found"
s = s.replace(old_sec, new_sec)
# 3. Build the WA Prep view + quiz (self-contained)
wa_data_json = json.dumps(WAEXAM) # [{cat,q,a:[...],c,x},...]
RENDER = r'''
<script>
/* ==== WA PREP — original practice question bank ==== */
const WAEXAM = __WADATA__;
(function(){
let curCat = 'All', qi = 0, score = 0, answered = 0, qlist = [];
const CATS = ['All','Core','Structural','Ornamental & Turf','Agricultural'];
function buildList(){
qlist = curCat === 'All' ? WAEXAM.slice() : WAEXAM.filter(q => q.cat === curCat);
// shuffle deterministically-ish
qlist = qlist.map((q,i)=>({...q, _r: Math.random()})).sort((a,b)=>a._r-b._r);
qi = 0; score = 0; answered = 0;
}
function render(){
const root = document.getElementById('v-waprep');
const cats = CATS.map(c=>`<button class="wac${c===curCat?' on':''}" data-wacat="${c}">${c}</button>`).join('');
const total = qlist.length;
const q = qlist[qi];
let body = '';
if (total === 0) {
body = `<p class="lede">No questions in this category yet.</p>`;
} else {
const done = answered >= total;
body = `
<div class="wa-prog"><span>${Math.min(answered+1, total)} / ${total}</span><span class="wa-score">Score ${score}/${answered||1}</span></div>
<div class="wa-q">${q.q}</div>
<div class="wa-opts">${q.a.map((opt, i)=>`<button class="wa-opt" data-i="${i}">${String.fromCharCode(65+i)}. ${opt}</button>`).join('')}</div>
<div class="wa-expl" id="wa-expl" style="display:none"></div>
<div class="wa-nav" style="display:none">
<button class="btn" id="wa-next">${qi+1 < total ? 'Next →' : 'Restart'}</button>
</div>`;
}
root.innerHTML = `
<section class="hero" style="padding-top:26px">
<div class="eyebrow">Original question bank · not scraped, written for you</div>
<h1><span class="lo">Pass the license</span>WA<br><em>Exam Prep</em></h1>
<p class="lede">${WAEXAM.length} original Washington pesticide-applicator questions across Core, Structural, Ornamental &amp; Turf, and Agricultural. Answer, read why, and track your score.</p>
<div class="wa-cats">${cats}</div>
</section>
<section class="section" style="padding-top:10px">
<div class="wa-card">${body}</div>
</section>`;
// wire
root.querySelectorAll('[data-wacat]').forEach(b=>b.addEventListener('click', ()=>{ curCat = b.dataset.wacat; buildList(); render(); }));
root.querySelectorAll('.wa-opt').forEach(b=>b.addEventListener('click', ()=>{
const i = +b.dataset.i;
const correct = i === q.c;
if (correct) score++;
answered++;
root.querySelectorAll('.wa-opt').forEach(o=>{
o.disabled = true;
if (+o.dataset.i === q.c) o.classList.add('right');
});
if (!correct) b.classList.add('wrong');
const expl = root.querySelector('#wa-expl');
expl.style.display = 'block';
expl.innerHTML = `<strong>${correct ? 'Correct' : 'Incorrect'}.</strong> ${q.x}`;
root.querySelector('.wa-nav').style.display = 'block';
root.querySelector('.wa-score').textContent = `Score ${score}/${answered}`;
}));
const nx = root.querySelector('#wa-next');
if (nx) nx.addEventListener('click', ()=>{ if (qi+1 < total) { qi++; } else { buildList(); } render(); });
}
// render once when the view is first opened
const origGo = window.go;
window.go = function(v){
if (v === 'waprep') { buildList(); render(); }
origGo(v);
};
})();
</script>
<style>
.wa-cats{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
.wa-cats .wac{padding:8px 16px;border-radius:999px;border:1px solid var(--line,#2C3455);background:var(--surface,#141A30);color:var(--text,#EDE7D6);cursor:pointer;font-size:.85rem}
.wa-cats .wac.on{background:var(--brass,#E3A53F);color:#1a1206;border-color:var(--brass,#E3A53F);font-weight:700}
.wa-card{background:var(--surface,#141A30);border:1px solid var(--line,#2C3455);border-radius:14px;padding:24px;max-width:760px;margin:0 auto}
.wa-prog{display:flex;justify-content:space-between;font-family:"JetBrains Mono",monospace;font-size:.78rem;color:var(--text-faint,#6E7688);margin-bottom:16px}
.wa-score{color:var(--verdigris,#54BFA9)}
.wa-q{font-family:Fraunces,serif;font-size:1.3rem;line-height:1.4;color:var(--text,#EDE7D6);margin-bottom:18px}
.wa-opts{display:flex;flex-direction:column;gap:10px}
.wa-opt{text-align:left;padding:12px 16px;border-radius:10px;border:1px solid var(--line,#2C3455);background:var(--surface-2,#1B2239);color:var(--text,#EDE7D6);cursor:pointer;font-size:.95rem;line-height:1.4}
.wa-opt:hover:not(:disabled){border-color:var(--brass,#E3A53F)}
.wa-opt.right{background:color-mix(in srgb,var(--verdigris,#54BFA9) 22%,transparent);border-color:var(--verdigris,#54BFA9)}
.wa-opt.wrong{background:color-mix(in srgb,var(--crimson,#DE5F45) 22%,transparent);border-color:var(--crimson,#DE5F45)}
.wa-opt:disabled{cursor:default;opacity:.9}
.wa-expl{margin-top:16px;padding:12px 16px;border-left:3px solid var(--verdigris,#54BFA9);background:color-mix(in srgb,var(--verdigris,#54BFA9) 9%,transparent);border-radius:8px;font-size:.9rem;line-height:1.5;color:var(--text,#EDE7D6)}
.wa-nav{margin-top:16px;text-align:right}
</style>
'''.replace('__WADATA__', wa_data_json)
s = s.rstrip() + '\n' + RENDER + '\n'
open(p, "w").write(s)
print(f"injected WA Prep view with {len(WAEXAM)} questions; size {len(s)}")