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