/* SAB Eligibility Checker v6 — full-screen Apple-style takeover. v6 (2026-10-19) = Mohamed's locked design, built after the freeze lifted: nine tap-first questions → REVEAL → "claim your guide" finish. (1) NEEDS chips (live "kinds of help found" counter in the chrome) → (2) ZIP → (3) HOUSEHOLD number taps → (4) SITUATIONS in the home → (5) ALREADY GETTING HELP → (6) AGE range → (7) MONEY SOURCE → (8) INCOME range (the engine's existing brackets/values, "I'm not sure") → (9) HOME SITUATION → REVEAL (kinds count + top matches, problem-first, before any personal info) → CLAIM (a) name+birthday (b) address with autocomplete, city/state/ZIP pre-filled from their ZIP (c) email AND phone, both required, trust line "We never sell your information." → tap screens: job situation, citizenship, language, best time, how they heard + the optional "anything else going on?" box → RESULTS. The engine (match) is untouched from v4; its situation inputs are DERIVED by deriveSit(): senior = 65+ chip or age range; youngchild = kids-under-5 or pregnant chip; kids = youngchild or the child-care need; unemployed = "none right now" money source or the unemployed job situation. Extra answers (already-getting, money source, tenure, bill help) ride the lead's situations string as letter-only flag keys for the owner alert + guides. Server contract (sab-tracker) honored: valid email AND phone, real DOB, street, city, a named job situation + citizenship, >=1 need; an "I'm not sure" income is sent as amount 0/month (not stated) so leads never bounce. Funnel beacons keep the tracker's stage meanings (household = zip done, situations = household done, home = income done, reveal = front done; completion event on submit) vs the 44 starts → 19 finishes baseline. Returning-visitor memory + on-device in-progress resume kept. Deploy: upload AS eligibility-checker-js-v6.txt to the Media Library (PHP/JS uploads are blocked) and point the loader snippet's enqueue URL at it. */ (function(){ "use strict"; /* ---------- verified program data (from programs_db.py, research 2026-09-26/27) ---------- */ var PROGRAMS = [ {id:"snap", name:"SNAP Food Benefits (Texas HHSC)", what:"Monthly grocery money on a Lone Star Card — up to $994/mo for a family of 4 (FY2026 max).", phone:"2-1-1 or 877-541-7905", apply:"https://www.yourtexasbenefits.com/Learn/Home", est:"snap"}, {id:"wic", name:"WIC — Women, Infants & Children", what:"Healthy food, nutrition classes and breastfeeding support for pregnant women and kids under 5. No citizenship requirement.", phone:"800-942-3678", apply:"https://texaswic.org/apply"}, {id:"medicaid", name:"Texas Medicaid", what:"FREE health coverage for kids, pregnant women, seniors and people with disabilities.", phone:"2-1-1 or 1-877-541-7905", apply:"https://www.yourtexasbenefits.com/Learn/Home"}, {id:"chip", name:"Texas CHIP — Children's Health Insurance", what:"Low-cost health coverage for kids 18 & under — enrollment $50 or less per year, co-pays $3–$35.", phone:"2-1-1", apply:"https://www.yourtexasbenefits.com/Learn/Home"}, {id:"tanf", name:"TANF Cash Help", what:"Monthly cash for families with kids and little to no income (family of 3 ≈ $382/mo).", phone:"2-1-1 or 877-541-7905", apply:"https://www.yourtexasbenefits.com/Learn/Home", est:"tanf"}, {id:"opportunity_home", name:"Opportunity Home — Public Housing & Section 8", what:"Rent based on income (around 30%). Apply the moment a waitlist opens — they fill fast.", phone:"210-477-6000", apply:"https://homesa.org/housing/find-a-home/apply-for-housing/"}, {id:"hap", name:"City of San Antonio Housing Assistance (HAP)", what:"Up to 3 months of rent (max ~$3,500) + up to $1,500 in utility help, paid straight to your landlord. Time-sensitive: enrollment windows fill fast.", phone:"210-207-6459", apply:"https://www.sa.gov/Directory/Departments/NHSD/Housing-Support/Renter-Support/About-HAP", hot:true}, {id:"trla_rtc", name:"Right to Counsel — Free Eviction Lawyer", what:"A FREE attorney if you're facing eviction. Call immediately if you have court papers. Time-sensitive.", phone:"210-212-3703", apply:"https://www.trla.org/", hot:true}, {id:"cps_discount", name:"CPS Energy Affordability Discount", what:"Up to 25% off your electric bill every month (35% if you're 60+).", phone:"1-888-207-7434", apply:"https://www.sa.gov/Directory/Departments/DHS/Financial-Assistance/Utility-Assistance", est:"utility"}, {id:"sa_utility", name:"City of San Antonio Utility Assistance", what:"Help paying energy bills — priority for households with kids, disabilities, or adults 60+.", phone:"210-207-8198", apply:"https://www.sa.gov/Directory/Departments/DHS/Financial-Assistance/Utility-Assistance"}, {id:"vita", name:"VITA — Free Tax Preparation", what:"IRS-certified volunteers do your taxes FREE (Jan–Apr) — including EITC and Child Tax Credit.", phone:"210-207-6575", apply:"https://vitasa.org/"}, {id:"twc_unemp", name:"Texas Unemployment Benefits", what:"Partial income replacement if you lost your job or had hours cut — roughly $75–$605/week.", phone:"800-939-6631", apply:"https://www.twc.texas.gov/services/apply-benefits"}, {id:"wioa", name:"WIOA — Free Career Training (Workforce Solutions Alamo)", what:"FREE career counseling, job training and certifications at 16 Alamo-area career centers.", phone:"210-438-0581", apply:"https://www.workforcesolutionsalamo.org/information-sessions/"}, {id:"ccs", name:"Child Care Scholarships (CCS)", what:"Helps working parents pay for child care. Apply early — there can be a waitlist.", phone:"1-888-265-6461", apply:"https://childcare.twc.texas.gov/find/register?mode=signup"}, {id:"va", name:"VA Benefits", what:"Health care, disability compensation, GI Bill, home loans and more for veterans.", phone:"1-800-827-1005", apply:"https://benefits.va.gov/benefits/applying.asp"}, {id:"tvc", name:"Texas Veterans Commission", what:"FREE help filing VA disability claims, job placement and education benefits.", phone:"1-800-252-8387", apply:"https://tvc.texas.gov/"}, {id:"hud_vash", name:"HUD-VASH — Housing for Homeless Veterans", what:"Rental voucher + VA case management for veterans facing homelessness. Time-sensitive.", phone:"877-424-3838", apply:"https://www.va.gov/south-texas-health-care/health-services/homeless-veteran-care/", hot:true}, {id:"foodbank", name:"San Antonio Food Bank — Free Food + Application Help", what:"Free groceries AND free one-on-one help applying for SNAP, Medicaid and TANF. No citizenship requirement for food.", phone:"210-431-8326", apply:"https://safoodbank.org/our-programs/benefits-assistance/"}, {id:"trla", name:"Texas RioGrande Legal Aid", what:"FREE civil legal help — housing, family, benefits denials. English and Spanish.", phone:"(833) 329-8752", apply:"https://www.trla.org/"}, {id:"via_link", name:"VIA Link — Rides On Demand + VIA Buses", what:"VIA's on-demand vans pick you up near your home and take you anywhere inside your zone for $1.30 a ride (65¢ with a reduced fare). Book up to 7 days ahead — good for job interviews and appointments. Regular VIA buses cover the rest of the city.", phone:"210-655-5465", apply:"https://www.viainfo.net/Link/"}, {id:"lifeline", name:"Lifeline — Discount Phone & Internet", what:"A federal program that takes $9.25 a month off your phone or internet bill — some companies turn it into a completely free phone plan. You qualify with SNAP, Medicaid or SSI, or income at or below 135% of the federal poverty line.", phone:"1-800-234-9473", apply:"https://www.lifelinesupport.org/"}, {id:"211", name:"2-1-1 Texas Helpline", what:"One free call connects you to thousands of local services. 24/7, 100+ languages.", phone:"2-1-1", apply:"https://www.211texas.org/"} ]; /* Inline SVG icons (no emoji anywhere in the UI) */ var SVG_PHONE = ''; var SVG_CLOCK = ''; /* 2025 federal poverty guidelines (48 states), monthly */ var FPL_M = {1:1304.17, 2:1762.50, 3:2220.83, 4:2679.17, 5:3137.50, 6:3595.83, 7:4054.17, 8:4512.50}; var SNAP_MAX = {1:298, 2:548, 3:783, 4:994, 5:1158, 6:1390, 7:1536, 8:1755}; /* FY2026 max allotments */ function fpl(hh, pct){ var b = FPL_M[Math.min(hh,8)] || 4512.50; if(hh>8) b += (hh-8)*478.33; return b*pct; } function initChecker(root){ if(!root || root.dataset.init) return; root.dataset.init = "1"; var $ = function(s){ return root.querySelector(s); }; var $$ = function(s){ return Array.prototype.slice.call(root.querySelectorAll(s)); }; var REDUCED = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; var leadSent = false; /* guard: never send the same lead twice (e.g. back-nav + resubmit) */ var state = { zip:"", hh:3, income:3000, incomeUnknown:false, sit:{}, sitRaw:{}, flags:{}, ageRange:"", moneySrc:"", tenure:"", focus:null, name:"", phone:"", email:"", pref:"", consent:false, dob:"", street:"", city:"", employment:"", income_amount:"", income_period:"", citizenship:"", top_needs:[], language:"en", contact_time:"", hear_about:"", notes:"" }; /* v6: the engine's situation inputs are derived from the answers. sitRaw holds the Q4 chip states (youngchild/pregnant/senior/disabled/ veteran); state.sit is the engine map match() has always read; state.flags holds the extra letter-only keys (already-getting, money source, tenure, bill help) that ride the lead's situations string for the owner + guides. */ var FLAG_KEYS = ["getsnap","getmedicaid","gethousing","srcjob","srcbenefits","srcmix","srcnone","renting","ownhome","withfamily","utilbills"]; function deriveSit(){ var raw = state.sitRaw || {}; state.sit.veteran = !!raw.veteran; state.sit.disabled = !!raw.disabled; state.sit.youngchild = !!raw.pregnant || !!raw.youngchild; state.sit.senior = !!raw.senior || state.ageRange === "65plus"; state.sit.kids = state.sit.youngchild || state.top_needs.indexOf("childcare") !== -1; state.sit.unemployed = state.moneySrc === "srcnone" || state.employment === "unemployed"; } try { var sc = root.closest('[data-sabck-focus]'); if (sc) state.focus = sc.getAttribute('data-sabck-focus'); } catch(e){} /* ---------- returning-visitor memory (v4.3 — device-only, nothing sent anywhere) ---------- */ var WB_KEY = "sabck_saved_v1"; var quizLive = false; /* a quiz run is underway in this page session */ function wbLoad(){ try{ var raw = window.localStorage.getItem(WB_KEY); if(!raw) return null; var d = JSON.parse(raw); if(!d || typeof d !== "object" || !d.zip || !d.matched || !d.matched.length) return null; return d; }catch(e){ return null; } } function wbSave(res){ try{ var sit = {}; Object.keys(state.sit).forEach(function(k){ if(state.sit[k]) sit[k] = true; }); Object.keys(state.flags).forEach(function(k){ if(state.flags[k]) sit[k] = true; }); var d = { v:2, zip: state.zip, hh: state.hh, income: state.income, incomeUnknown: !!state.incomeUnknown, sit: sit, top_needs: state.top_needs.slice(), matched: res.map(function(r){ return r.p.id; }), ts: Date.now() }; window.localStorage.setItem(WB_KEY, JSON.stringify(d)); }catch(e){} } function wbForget(){ try{ window.localStorage.removeItem(WB_KEY); }catch(e){} } function wbResetQuiz(){ state.zip = ""; state.hh = 3; state.income = null; state.incomeUnknown = false; state.sit = {}; state.sitRaw = {}; state.flags = {}; state.ageRange = ""; state.moneySrc = ""; state.tenure = ""; state.top_needs = []; state.notes = ""; state.name = ""; state.email = ""; state.phone = ""; state.dob = ""; state.street = ""; state.city = ""; state.employment = ""; state.citizenship = ""; state.pref = ""; state.contact_time = ""; state.hear_about = ""; state.consent = false; progClear(); var z = $("#sabck-zip"); if(z) z.value = ""; $$("#sabck-hhgrid button").forEach(function(b){ b.classList.toggle("sel", b.getAttribute("data-hh") === "3"); }); $$(".sabck-checks input").forEach(function(c){ c.checked = false; }); $$(".sabck-options button").forEach(function(b){ b.classList.remove("sel"); }); $$(".sabck-prefchip").forEach(function(b){ b.classList.remove("sel"); }); ["sabck-name","sabck-email","sabck-phone","sabck-notes","sabck-street","sabck-city","sabck-state","sabck-zip2"].forEach(function(id){ var el = document.getElementById(id); if(el) el.value = ""; }); var db = $("#sabck-dob"); if(db) db.value = ""; var cs = $("#sabck-consent"); if(cs) cs.checked = false; var ab = $("#sabck-alerts"); if(ab) ab.checked = false; addrPrefilled = false; updatePill(); } /* ---------- in-progress save (v5: leaving mid-quiz resumes where they stopped) ---------- */ var PROG_KEY = "sabck_prog_v1"; function progSave(){ try{ if(!QUIZ_SCREENS[current]) return; var raw = {}; Object.keys(state.sitRaw).forEach(function(k){ if(state.sitRaw[k]) raw[k] = true; }); var fl = {}; Object.keys(state.flags).forEach(function(k){ if(state.flags[k]) fl[k] = true; }); window.localStorage.setItem(PROG_KEY, JSON.stringify({ zip: state.zip, hh: state.hh, income: state.income, incomeUnknown: !!state.incomeUnknown, sitRaw: raw, flags: fl, ageRange: state.ageRange, moneySrc: state.moneySrc, tenure: state.tenure, top_needs: state.top_needs.slice(), screen: current, ts: Date.now() })); }catch(e){} } function progLoad(){ try{ var raw = window.localStorage.getItem(PROG_KEY); if(!raw) return null; var d = JSON.parse(raw); if(!d || typeof d !== "object" || !d.screen || !QUIZ_SCREENS[d.screen]) return null; return d; }catch(e){ return null; } } function progClear(){ try{ window.localStorage.removeItem(PROG_KEY); }catch(e){} } function progApply(d){ state.zip = d.zip || ""; state.hh = d.hh || 3; state.incomeUnknown = !!d.incomeUnknown; state.income = d.incomeUnknown ? null : (typeof d.income === "number" ? d.income : null); state.sitRaw = {}; if(d.sitRaw){ Object.keys(d.sitRaw).forEach(function(k){ if(d.sitRaw[k]) state.sitRaw[k] = true; }); } state.flags = {}; if(d.flags){ Object.keys(d.flags).forEach(function(k){ if(d.flags[k]) state.flags[k] = true; }); } state.ageRange = d.ageRange || ""; state.moneySrc = d.moneySrc || ""; state.tenure = d.tenure || ""; state.top_needs = (d.top_needs || []).slice(); deriveSit(); var z = $("#sabck-zip"); if(z) z.value = state.zip; $$("#sabck-hhgrid button").forEach(function(b){ b.classList.toggle("sel", parseInt(b.getAttribute("data-hh"),10) === state.hh); }); $$("#sabck-sits input").forEach(function(c){ c.checked = !!state.sitRaw[c.value]; }); $$("#sabck-needs input").forEach(function(c){ c.checked = state.top_needs.indexOf(c.value) !== -1; }); $$("#sabck-getting input").forEach(function(c){ c.checked = !!state.flags[c.value]; }); $$("#sabck-tenure-opts button").forEach(function(b){ b.classList.toggle("sel", b.getAttribute("data-tenure") === state.tenure); }); var ub = $("#sabck-utilbills"); if(ub) ub.checked = !!state.flags.utilbills; $$("#sabck-money-opts button").forEach(function(b){ b.classList.toggle("sel", b.getAttribute("data-src") === state.moneySrc); }); updatePill(); } /* Welcome-back screen: built here so the markup snippet stays untouched. Reuses the existing screen/card/button classes, so it looks native. */ var wbScreen = document.createElement("div"); wbScreen.className = "sabck-screen"; wbScreen.setAttribute("data-screen", "welcome"); wbScreen.innerHTML = '

Welcome back

' + '

' + '
' + '
' + '' + '' + '

We remember your answers on this phone only, so you don\'t have to answer again. Nothing extra is sent to us.

'; var wbAnchor = root.querySelector(".sabck-screen"); if(wbAnchor && wbAnchor.parentNode) wbAnchor.parentNode.insertBefore(wbScreen, wbAnchor); function wbRender(){ var d = wbLoad(); if(!d) return; /* restore the saved answers so the matcher re-runs against today's data */ state.zip = d.zip || ""; state.hh = d.hh || 3; state.incomeUnknown = !!d.incomeUnknown; state.income = d.incomeUnknown ? null : (typeof d.income === "number" ? d.income : null); state.sit = {}; if(d.sit){ Object.keys(d.sit).forEach(function(k){ if(d.sit[k]) state.sit[k] = true; }); } state.top_needs = (d.top_needs || []).slice(); /* v6: split the saved map back into engine situations, Q4 chips and flags, then re-derive — keeping any kids/unemployed flags older saves earned */ var keepKids = !!state.sit.kids, keepUnemp = !!state.sit.unemployed; state.flags = {}; FLAG_KEYS.forEach(function(k){ if(state.sit[k]) state.flags[k] = true; }); state.sitRaw = { veteran: !!state.sit.veteran, disabled: !!state.sit.disabled, senior: !!state.sit.senior, youngchild: !!state.sit.youngchild, pregnant: false }; if(state.sit.senior) state.ageRange = "65plus"; state.moneySrc = state.flags.srcnone ? "srcnone" : (state.flags.srcjob ? "srcjob" : (state.flags.srcmix ? "srcmix" : (state.flags.srcbenefits ? "srcbenefits" : ""))); state.tenure = state.flags.renting ? "renting" : (state.flags.ownhome ? "ownhome" : (state.flags.withfamily ? "withfamily" : "")); deriveSit(); if(keepKids) state.sit.kids = true; if(keepUnemp) state.sit.unemployed = true; var res = match(); var savedIds = d.matched || []; var newOnes = res.filter(function(r){ return savedIds.indexOf(r.p.id) === -1; }); var hotOnes = res.filter(function(r){ return r.p.hot; }); var when = ""; try{ when = new Date(d.ts).toLocaleDateString("en-US", {month:"long", day:"numeric", year:"numeric"}); }catch(e){} var sub = $("#sabck-wb-sub"); if(sub) sub.textContent = "You last checked on " + when + ". Here are your matches today, based on the answers you gave then."; var news = $("#sabck-wb-news"); if(news){ var html = ""; if(!newOnes.length && !hotOnes.length){ html = '

What\'s new for you

' + '

Nothing has changed since your last check — your matches are below.

'; } else { html = '

What\'s new for you

'; if(newOnes.length){ html += '

New since your last check

'; newOnes.forEach(function(r){ html += '

' + r.p.name + '

'; }); } if(hotOnes.length){ html += '

Time-sensitive for you

'; hotOnes.forEach(function(r){ html += '

' + r.p.name + '

'; }); } html += '
'; } news.innerHTML = html; } var list = $("#sabck-wb-list"); if(!list) return; list.innerHTML = ""; res.forEach(function(r){ var card = document.createElement("div"); card.className = "sabck-card"; var badge = r.p.hot ? '' + SVG_CLOCK + 'Time-sensitive' : '✓ May qualify'; var valLine = r.val ? '

Worth up to ~$' + r.val.toLocaleString() + '/mo

' : ''; card.innerHTML = '

' + r.p.name + '

' + badge + '
' + '

' + r.why + '

' + valLine + '

' + r.p.what + '

' + '
' + '' + SVG_PHONE + '' + r.p.phone + '' + 'Apply →' + '
'; list.appendChild(card); }); } /* ---------- takeover chrome ---------- */ var tk = $("#sabck-tk"), tkBody = $("#sabck-tk-body"), tkBack = $("#sabck-tk-back"), tkNext = $("#sabck-tk-next"), tkClose = $("#sabck-tk-close"); var current = "needs"; var PREV = {needs:null, zip:"needs", household:"zip", situations:"household", getting:"situations", age:"getting", money:"age", income:"money", home:"income", reveal:"home", claimname:"reveal", claimaddr:"claimname", claimcontact:"claimaddr", job:"claimcontact", citizenship:"job", language:"citizenship", reachtime:"language", hear:"reachtime", results:"hear"}; /* Footer Continue per screen: label + which hidden inline control to activate (reuses the exact validation/handlers — no duplicated logic). */ var NEXT_ACTION = { needs: {label:"Continue →", click:"#sabck-needs-next"}, zip: {label:"Continue →", click:"#sabck-zip-next"}, household: null, /* number taps auto-advance */ situations: {label:"Continue →", click:"#sabck-sit-next"}, getting: {label:"Continue →", click:"#sabck-get-next"}, age: null, /* auto-advances on tap */ money: null, /* auto-advances on tap */ income: null, /* auto-advances on tap */ home: {label:"Continue →", click:"#sabck-home-next"}, reveal: {label:"See my results →", click:"#sabck-gate-next"}, claimname: {label:"Continue →", click:"#sabck-claimname-next"}, claimaddr: {label:"Continue →", click:"#sabck-claimaddr-next"}, claimcontact:{label:"Continue →", click:"#sabck-claimcontact-next"}, job: null, /* auto-advances on tap */ citizenship: null, /* auto-advances on tap */ language: null, /* auto-advances on tap */ reachtime: null, /* auto-advances on tap */ hear: {label:"Send my guide →", click:"#sabck-final-submit"}, results: {label:"Done", close:true} }; function updateChrome(name){ current = name; if(!tk) return; var nx = NEXT_ACTION[name]; if(!nx){ tkNext.style.display = "none"; } else { tkNext.style.display = ""; tkNext.textContent = nx.label; tkNext.setAttribute("data-tkact", nx.close ? "close" : "click"); tkNext.setAttribute("data-tkclick", nx.click || ""); } if(tkBody){ try{ tkBody.scrollTop = 0; }catch(e){} } } var QUIZ_SCREENS = {needs:1, zip:1, household:1, situations:1, getting:1, age:1, money:1, income:1, home:1, reveal:1, claimname:1, claimaddr:1, claimcontact:1, job:1, citizenship:1, language:1, reachtime:1, hear:1}; function openTakeover(){ if(!tk) return; var resume; /* welcome-back wins on ANY open once answers are saved — except mid-quiz, where reopening resumes the run already in progress */ var midQuiz = quizLive && QUIZ_SCREENS[current]; if(wbLoad() && !midQuiz){ resume = "welcome"; } else if(midQuiz){ resume = current; } else { /* an unfinished run saved on this device resumes where it stopped */ var prog = progLoad(); if(prog){ progApply(prog); resume = prog.screen; } else { resume = "needs"; } } tk.hidden = false; try{ document.body.classList.add("sabck-tk-lock"); }catch(e){} show(resume, "fwd"); if(!REDUCED){ tk.classList.remove("sabck-tk-opening"); void tk.offsetWidth; tk.classList.add("sabck-tk-opening"); } if(tkClose){ try{ tkClose.focus({preventScroll:true}); }catch(e){} } } function closeTakeover(){ if(!tk) return; tk.hidden = true; try{ document.body.classList.remove("sabck-tk-lock"); }catch(e){} } try{ document.addEventListener("keydown", function(e){ if(e.key === "Escape" && tk && !tk.hidden) closeTakeover(); }); }catch(e){} /* ---------- navigation ---------- */ /* v6: progress is "Question X of 10" + the slim bar + the live kinds counter */ var STEPS = {needs:["Question 1 of 10",6], zip:["Question 2 of 10",12], household:["Question 3 of 10",18], situations:["Question 4 of 10",24], getting:["Question 5 of 10",30], age:["Question 6 of 10",36], money:["Question 7 of 10",42], income:["Question 8 of 10",48], home:["Question 9 of 10",54], reveal:["Your matches",62], claimname:["Question 10 of 10",68], claimaddr:["Question 10 of 10",74], claimcontact:["Question 10 of 10",80], job:["Question 10 of 10",84], citizenship:["Question 10 of 10",88], language:["Question 10 of 10",92], reachtime:["Question 10 of 10",96], hear:["Question 10 of 10",98], results:["",100]}; var FRONT_SCREENS = {needs:1, zip:1, household:1, situations:1, getting:1, age:1, money:1, income:1, home:1, reveal:1}; function show(name, dir){ dir = (dir === "back") ? "back" : "fwd"; if(name === "welcome"){ wbRender(); } else if(name !== "results"){ quizLive = true; } if(name === "reveal"){ renderReveal(); } if(name === "claimaddr"){ prefillAddress(); } var pw = $("#sabck-pillwrap"); if(pw) pw.classList.toggle("on", !!FRONT_SCREENS[name]); if(QUIZ_SCREENS[name]){ current = name; progSave(); } $$(".sabck-screen").forEach(function(el){ var on = el.dataset.screen === name; el.classList.toggle("sabck-active", on); el.classList.remove("sabck-dir-f","sabck-dir-b"); if(on) el.classList.add(dir === "back" ? "sabck-dir-b" : "sabck-dir-f"); }); var st = STEPS[name] || ["",0]; var lbl = $("#sabck-step-label"); if(lbl) lbl.textContent = st[0]; var pf = $("#sabck-pfill"); if(pf) pf.style.width = st[1] + "%"; updateChrome(name); /* funnel analytics: quiz start + per-step progress (server dedupes per visitor/day). v6 keeps the tracker's stage meanings: household = zip done, situations = household done, home = income done, reveal = front questions done; the completion event (in renderResults) stays the finish line — comparable to the old flow's 44 starts → 19 finishes. */ try{ if(name==="needs") sabckFunnel("quiz_start"); else if(name==="household") sabckFunnel("quiz_step", 2); else if(name==="situations") sabckFunnel("quiz_step", 3); else if(name==="home") sabckFunnel("quiz_step", 4); else if(name==="reveal") sabckFunnel("quiz_step", 5); }catch(e){} } /* Animated count-up (respects prefers-reduced-motion) */ function countUp(el, to, prefix){ if(!el) return; prefix = prefix || ""; if(REDUCED || !window.requestAnimationFrame){ el.textContent = prefix + to.toLocaleString(); return; } var start = null, dur = 900; function frame(t){ if(!start) start = t; var p = Math.min((t - start) / dur, 1); var e = 1 - Math.pow(1 - p, 3); el.textContent = prefix + Math.round(to * e).toLocaleString(); if(p < 1) requestAnimationFrame(frame); } requestAnimationFrame(frame); } /* Gentle inline validation: highlight the fields that need attention */ function markInvalid(id, valid){ var el = root.querySelector("#" + id); if(!el) return; var wrap = (el.closest && (el.closest(".sabck-field") || el)) || el; if(valid) wrap.classList.remove("sabck-invalid"); else wrap.classList.add("sabck-invalid"); } /* Anonymous funnel beacon: start/step events only, no answers attached. */ function sabckFunnel(ev, step){ try{ var d = {event: ev, page: window.location.pathname}; if(step) d.step = step; var x = new XMLHttpRequest(); x.open("POST", "/wp-json/sab/v1/quiz-event", true); x.setRequestHeader("Content-Type", "application/json"); x.send(JSON.stringify(d)); }catch(e){} } /* Lead capture: contact info + quiz answers, stored and emailed to the owner. */ function sendLead(){ try{ var res = match(); var sitKeys = Object.keys(state.sit).filter(function(k){ return state.sit[k]; }); Object.keys(state.flags).forEach(function(k){ if(state.flags[k] && sitKeys.indexOf(k) === -1) sitKeys.push(k); }); var d = { name: state.name, phone: state.phone, email: state.email, pref: state.pref, consent: state.consent ? 1 : 0, dob: state.dob, street: state.street, city: state.city, employment: state.employment, income_amount: state.income_amount, income_period: state.income_period, citizenship: state.citizenship, top_need: state.top_needs.join(","), language: state.language, contact_time: state.contact_time, hear_about: state.hear_about, notes: state.notes, zip: state.zip, hh: state.hh, income: state.income, sit: sitKeys.join(","), matched: res.map(function(r){ return r.p.id; }).join(","), count: res.length, value: res.reduce(function(s,r){ return s+(r.val||0); }, 0), page: window.location.pathname }; var x = new XMLHttpRequest(); x.open("POST", "/wp-json/sab/v1/quiz-lead", true); x.setRequestHeader("Content-Type", "application/json"); x.send(JSON.stringify(d)); try{ var ab = document.querySelector("#sabck-alerts"); if(ab && ab.checked && d.email){ var ax = new XMLHttpRequest(); ax.open("POST", "/wp-json/sab/v1/alerts-signup", true); ax.setRequestHeader("Content-Type", "application/json"); ax.send(JSON.stringify({ email: d.email, source: "checker", website: "" })); } }catch(e){} }catch(e){} } /* Gate field: Enter submits the gate, same as tapping the button. */ var gateEl = $("#sabck-gate-email"); if(gateEl){ gateEl.addEventListener("keydown", function(e){ if(e.key === "Enter"){ e.preventDefault(); var gb = $("#sabck-gate-next"); if(gb) gb.click(); } }); } /* ZIP auto-advance: 5 valid digits completes the step — no dead tap. */ var zipEl = $("#sabck-zip"); if(zipEl){ zipEl.addEventListener("input", function(){ var v = zipEl.value.replace(/\D/g,"").slice(0,5); if(v !== zipEl.value) zipEl.value = v; var err = $("#sabck-zip-err"); if(err) err.style.display = "none"; if(/^\d{5}$/.test(v)){ var zipActive = root.querySelector('.sabck-screen[data-screen="zip"].sabck-active'); if(zipActive){ state.zip = v; show("household","fwd"); } } }); } /* Delegated clicks: one listener on the checker root survives DOM rewrites by translators/optimizers that replace child nodes. */ root.addEventListener("click", function(e){ var t = e.target; var el = (t && t.nodeType === 1) ? t : (t && t.parentElement ? t.parentElement : null); function closest(sel){ var n = el; while(n && n !== root){ if(n.matches && n.matches(sel)) return n; n = n.parentElement; } return null; } /* takeover chrome first */ var opener = closest("[data-sabck-open]"); if(opener){ if(e.preventDefault) e.preventDefault(); openTakeover(); return; } if(closest("#sabck-tk-close")){ closeTakeover(); return; } if(closest("#sabck-tk-back")){ if(current === "needs" || current === "welcome"){ closeTakeover(); } else { var p = PREV[current]; show(p || "needs", "back"); } return; } if(closest("#sabck-wb-retake")){ quizLive = true; wbResetQuiz(); current = "needs"; show("needs", "fwd"); return; } if(closest("#sabck-wb-forget")){ wbForget(); quizLive = false; wbResetQuiz(); current = "needs"; closeTakeover(); return; } if(closest("#sabck-tk-next")){ var act = tkNext.getAttribute("data-tkact"); if(act === "close"){ closeTakeover(); return; } var sel = tkNext.getAttribute("data-tkclick"); if(sel){ var tgt = root.querySelector(sel); if(tgt) tgt.click(); } return; } var nx = closest("[data-next]"); if(nx){ show(nx.getAttribute("data-next"), "fwd"); return; } var bk = closest("[data-back]"); if(bk){ show(bk.getAttribute("data-back"), "back"); return; } if(closest("#sabck-needs-next")){ syncNeeds(); /* the server requires at least one need — "Something else" is the escape */ if(!state.top_needs.length){ $("#sabck-needs-err").style.display = "block"; return; } $("#sabck-needs-err").style.display = "none"; show("zip","fwd"); return; } if(closest("#sabck-zip-next")){ var z = $("#sabck-zip").value.trim(); if(!/^\d{5}$/.test(z)){ $("#sabck-zip-err").style.display="block"; return; } $("#sabck-zip-err").style.display="none"; state.zip = z; show("household","fwd"); return; } var hhBtn = closest("#sabck-hhgrid button"); if(hhBtn){ state.hh = parseInt(hhBtn.getAttribute("data-hh"), 10) || 3; $$("#sabck-hhgrid button").forEach(function(b){ b.classList.toggle("sel", b === hhBtn); }); progSave(); /* big number taps advance on their own — one less button to hunt for */ setTimeout(function(){ if(current === "household") show("situations","fwd"); }, REDUCED ? 0 : 300); return; } if(closest("#sabck-sit-none")){ /* escape: clear every ticked box on THIS screen and continue with zero situations */ $$("#sabck-sits input").forEach(function(c){ c.checked=false; state.sitRaw[c.value]=false; }); deriveSit(); updatePill(); show("getting","fwd"); return; } if(closest("#sabck-sit-next")){ $$("#sabck-sits input").forEach(function(c){ state.sitRaw[c.value]=c.checked; }); deriveSit(); show("getting","fwd"); return; } if(closest("#sabck-get-next")){ $$("#sabck-getting input").forEach(function(c){ if(c.value !== "getnone") state.flags[c.value] = c.checked; }); progSave(); show("age","fwd"); return; } var ageBtn = closest("#sabck-age-opts button"); if(ageBtn){ $$("#sabck-age-opts button").forEach(function(b){ b.classList.toggle("sel", b === ageBtn); }); state.ageRange = ageBtn.getAttribute("data-skip") ? "" : (ageBtn.getAttribute("data-age") || ""); deriveSit(); progSave(); updatePill(); setTimeout(function(){ if(current === "age") show("money","fwd"); }, REDUCED ? 0 : 300); return; } var moneyBtn = closest("#sabck-money-opts button"); if(moneyBtn){ $$("#sabck-money-opts button").forEach(function(b){ b.classList.toggle("sel", b === moneyBtn); }); state.moneySrc = moneyBtn.getAttribute("data-src") || ""; ["srcjob","srcbenefits","srcmix","srcnone"].forEach(function(k){ state.flags[k] = (k === state.moneySrc); }); deriveSit(); progSave(); updatePill(); setTimeout(function(){ if(current === "money") show("income","fwd"); }, REDUCED ? 0 : 300); return; } var incBtn = closest("#sabck-income-opts button"); if(incBtn){ $$("#sabck-income-opts button").forEach(function(b){ b.classList.toggle("sel", b === incBtn); }); if(incBtn.getAttribute("data-skip")){ state.incomeUnknown=true; state.income=null; } else { state.incomeUnknown=false; state.income=parseFloat(incBtn.getAttribute("data-val")); } progSave(); updatePill(); setTimeout(function(){ if(current === "income") show("home","fwd"); }, REDUCED ? 0 : 300); return; } var tenBtn = closest("#sabck-tenure-opts button"); if(tenBtn){ state.tenure = tenBtn.getAttribute("data-tenure") || ""; $$("#sabck-tenure-opts button").forEach(function(b){ b.classList.toggle("sel", b === tenBtn); }); ["renting","ownhome","withfamily"].forEach(function(k){ state.flags[k] = (k === state.tenure); }); progSave(); return; } if(closest("#sabck-home-next")){ show("reveal","fwd"); return; } if(closest("#sabck-gate-next")){ /* EMAIL GATE: a valid email is required to go any further. Giving it is the agreement the gate copy states — guide emailed + newsletter add — so consent is recorded here, not by a later tick box. */ var gem = $("#sabck-gate-email"); var gemail = gem ? gem.value.trim() : ""; var gOk = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(gemail); markInvalid("sabck-gate-email", gOk); var gerr = $("#sabck-gate-err"); if(gerr) gerr.style.display = gOk ? "none" : "block"; if(!gOk) return; state.email = gemail; state.consent = true; var cem = $("#sabck-email"); if(cem) cem.value = gemail; progSave(); show("claimname","fwd"); return; } if(closest("#sabck-claimname-next")){ var nm = $("#sabck-name").value.trim(); var dob = $("#sabck-dob").value; var dobOk = /^\d{4}-\d{2}-\d{2}$/.test(dob) && (function(){ var p = dob.split("-"); var y = +p[0], m = +p[1], d = +p[2]; if(y < 1900 || y > new Date().getFullYear()) return false; var dt = new Date(y, m-1, d); return dt.getFullYear()===y && dt.getMonth()===m-1 && dt.getDate()===d && dt <= new Date(); })(); var nameOk = nm.length >= 2; markInvalid("sabck-name", nameOk); markInvalid("sabck-dob", dobOk); $("#sabck-claimname-err").style.display = (nameOk && dobOk) ? "none" : "block"; if(!nameOk || !dobOk) return; state.name = nm; state.dob = dob; show("claimaddr","fwd"); return; } if(closest("#sabck-claimaddr-next")){ var street = $("#sabck-street").value.trim(); var city = $("#sabck-city").value.trim(); var st = $("#sabck-state").value.trim(); var z2 = $("#sabck-zip2").value.replace(/\D/g, "").slice(0, 5); var streetOk = street.length >= 3, cityOk = city.length >= 2, stOk = st.length >= 2, zipOk = /^\d{5}$/.test(z2); markInvalid("sabck-street", streetOk); markInvalid("sabck-city", cityOk); markInvalid("sabck-state", stOk); markInvalid("sabck-zip2", zipOk); $("#sabck-claimaddr-err").style.display = (streetOk && cityOk && stOk && zipOk) ? "none" : "block"; if(!(streetOk && cityOk && stOk && zipOk)) return; state.street = street; state.city = city; state.zip = z2; hideSugg(); show("claimcontact","fwd"); return; } var prefChip = closest("#sabck-prefrow .sabck-prefchip"); if(prefChip){ state.pref = prefChip.getAttribute("data-pref") || ""; $$("#sabck-prefrow .sabck-prefchip").forEach(function(b){ b.classList.toggle("sel", b === prefChip); }); return; } if(closest("#sabck-claimcontact-next")){ /* email AND phone, both required — exactly what the server accepts */ var em = $("#sabck-email").value.trim(); var ph = $("#sabck-phone").value.trim(); var digits = ph.replace(/[^0-9]/g, ""); var emailOk = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(em); var phoneOk = digits.length >= 7 && digits.length <= 15; markInvalid("sabck-email", emailOk); markInvalid("sabck-phone", phoneOk); $("#sabck-claimcontact-err").style.display = (emailOk && phoneOk) ? "none" : "block"; if(!emailOk || !phoneOk) return; state.email = em; state.phone = ph; if(!state.pref) state.pref = "email"; state.consent = true; /* agreed at the email gate; this screen restates the same terms */ show("job","fwd"); return; } var jobBtn = closest("#sabck-job-opts button"); if(jobBtn){ $$("#sabck-job-opts button").forEach(function(b){ b.classList.toggle("sel", b === jobBtn); }); state.employment = jobBtn.getAttribute("data-emp") || ""; deriveSit(); progSave(); setTimeout(function(){ if(current === "job") show("citizenship","fwd"); }, REDUCED ? 0 : 300); return; } var citBtn = closest("#sabck-cit-opts button"); if(citBtn){ $$("#sabck-cit-opts button").forEach(function(b){ b.classList.toggle("sel", b === citBtn); }); state.citizenship = citBtn.getAttribute("data-cit") || ""; progSave(); setTimeout(function(){ if(current === "citizenship") show("language","fwd"); }, REDUCED ? 0 : 300); return; } var langBtn = closest("#sabck-lang-opts button"); if(langBtn){ $$("#sabck-lang-opts button").forEach(function(b){ b.classList.toggle("sel", b === langBtn); }); state.language = langBtn.getAttribute("data-lang") || "en"; progSave(); setTimeout(function(){ if(current === "language") show("reachtime","fwd"); }, REDUCED ? 0 : 300); return; } var timeBtn = closest("#sabck-time-opts button"); if(timeBtn){ $$("#sabck-time-opts button").forEach(function(b){ b.classList.toggle("sel", b === timeBtn); }); state.contact_time = timeBtn.getAttribute("data-time") || ""; progSave(); setTimeout(function(){ if(current === "reachtime") show("hear","fwd"); }, REDUCED ? 0 : 300); return; } var hearBtn = closest("#sabck-hear-opts button"); if(hearBtn){ $$("#sabck-hear-opts button").forEach(function(b){ b.classList.toggle("sel", b === hearBtn); }); state.hear_about = hearBtn.getAttribute("data-hear") || ""; return; } if(closest("#sabck-final-submit")){ var nt = $("#sabck-notes"); state.notes = nt ? nt.value.trim().slice(0, 2000) : ""; /* income as the server needs it: the tapped range's monthly value, or 0/month ("not stated") when they tapped "I'm not sure" — a lead must never bounce over an honestly skipped question */ if(!state.incomeUnknown && typeof state.income === "number"){ state.income_amount = state.income; state.income_period = "month"; } else { state.income_amount = 0; state.income_period = "month"; state.income = 0; } if(!leadSent){ sendLead(); leadSent = true; } progClear(); renderResults(); show("results","fwd"); return; } var sh = closest("[data-share]"); if(sh){ doShare(sh.getAttribute("data-share")); } }); /* Live tap sync: needs + situations update state (and the counter pill) the moment a chip is tapped — tap order is kept for needs, because the first need named is the first one solved (problem-first). */ function syncNeeds(){ var boxes = $$("#sabck-needs input"); var now = boxes.filter(function(c){ return c.checked; }).map(function(c){ return c.value; }); state.top_needs = state.top_needs.filter(function(n){ return now.indexOf(n) !== -1; }); now.forEach(function(n){ if(state.top_needs.indexOf(n) === -1) state.top_needs.push(n); }); } root.addEventListener("change", function(e){ var t = e.target; if(!t || t.type !== "checkbox") return; if(t.closest && t.closest("#sabck-needs")){ syncNeeds(); deriveSit(); updatePill(); progSave(); return; } if(t.closest && t.closest("#sabck-sits")){ state.sitRaw[t.value] = t.checked; deriveSit(); updatePill(); progSave(); return; } if(t.closest && t.closest("#sabck-getting")){ /* "none" is exclusive: ticking it clears the rest, and vice versa */ if(t.value === "getnone" && t.checked){ $$("#sabck-getting input").forEach(function(c){ if(c.value !== "getnone"){ c.checked = false; state.flags[c.value] = false; } }); } else if(t.value !== "getnone" && t.checked){ var nn = root.querySelector('#sabck-getting input[value="getnone"]'); if(nn) nn.checked = false; state.flags[t.value] = true; } else if(t.value !== "getnone"){ state.flags[t.value] = false; } progSave(); return; } if(t.id === "sabck-utilbills"){ state.flags.utilbills = t.checked; var ui = state.top_needs.indexOf("utilities"); if(t.checked && ui === -1) state.top_needs.push("utilities"); if(!t.checked && ui !== -1) state.top_needs.splice(ui, 1); updatePill(); progSave(); return; } }); /* ---------- v5: kinds counter + problem-first order + the reveal ---------- */ /* Which checker programs answer which need (the need named first is solved first). */ var NEED_PROGRAMS = { food:["snap","wic","foodbank"], housing:["trla_rtc","hap","opportunity_home","hud_vash"], utilities:["cps_discount","sa_utility"], health:["medicaid","chip"], childcare:["ccs"], jobs:["wioa","twc_unemp"], transport:["via_link"], phone:["lifeline"], other:[] }; function needRank(pid){ var flat = []; state.top_needs.forEach(function(n){ (NEED_PROGRAMS[n] || []).forEach(function(id){ if(flat.indexOf(id) === -1) flat.push(id); }); }); var i = flat.indexOf(pid); return i === -1 ? 999 : i; } /* Program → the "kind of help" it belongs to (needs use the same kind keys). */ var KIND_OF = {snap:"food", wic:"food", foodbank:"food", hap:"housing", opportunity_home:"housing", trla_rtc:"housing", hud_vash:"housing", cps_discount:"utilities", sa_utility:"utilities", medicaid:"health", chip:"health", ccs:"childcare", wioa:"jobs", twc_unemp:"jobs", va:"veteran", tvc:"veteran", vita:"money", tanf:"money", trla:"legal", via_link:"transport", lifeline:"phone", "211":"other"}; function kindsCount(res){ var s = {}; state.top_needs.forEach(function(n){ s[n] = 1; }); (res || match()).forEach(function(r){ var k = KIND_OF[r.p.id]; if(k) s[k] = 1; }); return Object.keys(s).length; } /* The chrome counter: one pill, always visible across the front questions, counting the kinds of help found so far from the answers given. */ function updatePill(){ var pill = $("#sabck-pill"); if(!pill) return; var answered = state.top_needs.length > 0 || Object.keys(state.sitRaw).some(function(k){ return state.sitRaw[k]; }) || !!state.ageRange || !!state.moneySrc || typeof state.income === "number"; var k = answered ? kindsCount() : 0; pill.innerHTML = k ? "✓ " + k + " kinds of help found so far" : ""; pill.classList.toggle("on", k > 0); if(k && !REDUCED){ pill.classList.remove("pop"); void pill.offsetWidth; pill.classList.add("pop"); } } /* ---------- claim address: ZIP prefill + street autocomplete ---------- */ var addrPrefilled = false; function prefillAddress(){ var zipEl = $("#sabck-zip2"); if(zipEl && !zipEl.value && state.zip) zipEl.value = state.zip; if(addrPrefilled) return; addrPrefilled = true; var cityEl = $("#sabck-city"), stEl = $("#sabck-state"); if(/^782/.test(state.zip)){ if(cityEl && !cityEl.value) cityEl.value = "San Antonio"; if(stEl && !stEl.value) stEl.value = "TX"; return; } /* other ZIPs: look the city/state up (free ZIP service); typing still works */ try{ fetch("https://api.zippopotam.us/us/" + encodeURIComponent(state.zip)) .then(function(r){ return r.ok ? r.json() : null; }) .then(function(j){ if(!j || !j.places || !j.places.length) return; if(cityEl && !cityEl.value) cityEl.value = j.places[0]["place name"] || ""; if(stEl && !stEl.value) stEl.value = j.places[0]["state abbreviation"] || ""; }).catch(function(){}); }catch(e){} } function hideSugg(){ var box = $("#sabck-addr-sugg"); if(box){ box.hidden = true; box.innerHTML = ""; } } /* Street autocomplete via Photon (OpenStreetMap-based, no key needed). Best-effort: if it is slow or down, the field is just a normal field. */ var suggTimer = null; var streetEl = $("#sabck-street"); if(streetEl){ streetEl.addEventListener("input", function(){ var q = streetEl.value.trim(); if(suggTimer) clearTimeout(suggTimer); if(q.length < 4){ hideSugg(); return; } suggTimer = setTimeout(function(){ try{ fetch("https://photon.komoot.io/api/?limit=5&lang=en&q=" + encodeURIComponent(q)) .then(function(r){ return r.ok ? r.json() : null; }) .then(function(j){ var box = $("#sabck-addr-sugg"); if(!box || !j || !j.features) return; var feats = j.features.filter(function(f){ return f.properties && f.properties.countrycode === "US" && (f.properties.housenumber || f.properties.street); }).slice(0, 5); if(!feats.length){ hideSugg(); return; } box.innerHTML = ""; feats.forEach(function(f){ var p = f.properties; var line1 = ((p.housenumber || "") + " " + (p.street || p.name || "")).trim(); var line2 = ((p.city || "") + ", " + (p.state || "") + " " + (p.postcode || "")).trim(); var b = document.createElement("button"); b.type = "button"; b.innerHTML = ""; b.textContent = line1; var sm = document.createElement("small"); sm.textContent = line2; b.appendChild(sm); b.addEventListener("click", function(){ streetEl.value = line1; var c = $("#sabck-city"); if(c && p.city) c.value = p.city; var s = $("#sabck-state"); if(s && p.state) s.value = p.state; var z = $("#sabck-zip2"); if(z && p.postcode) z.value = String(p.postcode).slice(0, 5); hideSugg(); }); box.appendChild(b); }); box.hidden = false; }).catch(function(){}); }catch(e){} }, 280); }); streetEl.addEventListener("blur", function(){ setTimeout(hideSugg, 200); }); } /* The reveal is now the EMAIL GATE (v6.7, Mohamed's order 2026-10-23): no program names show before an email is given. Count only as a teaser; the gate copy in the markup states the terms plainly. */ function renderReveal(){ var res = match(); countUp($("#sabck-rev-count"), kindsCount(res), ""); var ge = $("#sabck-gate-email"); if(ge && state.email && !ge.value) ge.value = state.email; } /* ---------- matching engine (unchanged) ---------- */ function match(){ var hh=state.hh, inc=state.income, s=state.sit, out=[]; var incK = !state.incomeUnknown && typeof inc === "number"; function incLte(lim){ return !incK || inc <= lim; } function valK(v){ return incK ? v : 0; } function add(id, why, val){ var p=null; PROGRAMS.forEach(function(x){ if(x.id===id) p=x; }); if(p) out.push({p:p, why:why, val:val||0}); } var snapLim = fpl(hh,1.65), medLim = fpl(hh,1.33), chipLim = fpl(hh,2.00); if(incLte(snapLim)) add("snap", incK ? "Your income is under the SNAP limit for a household of "+hh+"." : "SNAP is income-based — households under the limit for their size qualify; the agency confirms when you apply.", valK(SNAP_MAX[Math.min(hh,8)]||1755)); if(s.youngchild) add("wic","You have a young child or are pregnant — WIC has no citizenship requirement."); if(s.kids && incLte(medLim)) add("medicaid", incK ? "Your kids likely qualify for free coverage at your income." : "Kids' Medicaid is income-based — this is the first coverage to check."); else if((s.senior||s.disabled) && incLte(medLim)) add("medicaid", incK ? "Seniors and people with disabilities can qualify at your income." : "Seniors and people with disabilities can qualify for Medicaid — it is income-based, so apply to confirm."); if(s.kids && incK && inc > medLim && inc <= chipLim) add("chip","Your income is above Medicaid but under the CHIP limit — low-cost kids' coverage."); else if(s.kids && incLte(chipLim)) add("chip","Backup option if Medicaid doesn't come through — CHIP covers kids cheaply."); if(s.kids && incLte(1000)) add("tanf", incK ? "Very low income with kids — TANF provides monthly cash help." : "For families with kids and very low income — TANF provides monthly cash help.", valK(382)); if(incLte(snapLim)) add("opportunity_home","Low-income households can apply when waitlists open."); if(s.eviction){ add("trla_rtc","You're facing eviction — a free lawyer can fight it."); add("hap","Behind on rent? HAP can cover up to 3 months."); } else if(incLte(fpl(hh,0.80))) add("hap", incK ? "Your income is under 80% of area median — HAP rental help when enrollment opens." : "HAP rental help is income-based — watch for enrollment openings."); if(incLte(fpl(hh,1.25))) add("cps_discount", incK ? "Your income qualifies for up to 25% off your CPS Energy bill." : "Income-based — qualifying households get up to 25% off their CPS Energy bill.", valK(45)); if(incLte(fpl(hh,1.50))) add("sa_utility", incK ? "Your income is under the city utility-assistance limit." : "City utility assistance is income-based — worth applying."); if(incLte(5417)) add("vita", incK ? "Your income is under $65k/yr — get your taxes done free." : "Free tax prep for households under about $65k/yr."); if(s.unemployed){ add("twc_unemp","You lost work or hours — unemployment can replace part of your income."); add("wioa","Free job training while you get back on your feet."); } if(s.kids && (s.unemployed || true)) add("ccs","Working or training with kids? Child care scholarships help pay for it."); if(s.veteran){ add("va","As a veteran, start with your full VA benefits check."); add("tvc","Free help filing VA claims and finding work."); if(s.eviction) add("hud_vash","Veteran facing housing loss — HUD-VASH combines a voucher with VA support."); } add("foodbank","Open to everyone in the area — free food plus free help applying for benefits."); add("via_link","Rides across San Antonio — on-demand vans from $1.30 a ride, plus regular VIA bus routes."); if(incLte(fpl(hh,1.35))) add("lifeline", incK ? "Your income is under the Lifeline limit — $9.25/month off phone or internet, free plans available." : "Lifeline discounts phone/internet for lower-income households — apply free to confirm."); if(s.eviction || incLte(snapLim)) add("trla","Free legal help if any application is denied or you need legal aid."); add("211","Catch-all: one call finds any local help we didn't list."); /* de-dupe + sort (v5): focus first, then PROBLEM-FIRST — programs that answer the needs the person named, in the order they named them — then time-sensitive, then by value. The matched SET is unchanged from v4. */ var seen={}, ded=[]; out.forEach(function(r){ if(!seen[r.p.id]){ seen[r.p.id]=1; ded.push(r); } }); ded.sort(function(a,b){ if(state.focus){ if(a.p.id===state.focus) return -1; if(b.p.id===state.focus) return 1; } var ra = needRank(a.p.id), rb = needRank(b.p.id); if(ra !== rb) return ra - rb; if(!!a.p.hot!==!!b.p.hot) return a.p.hot?-1:1; return (b.val||0)-(a.val||0); }); return ded; } function renderResults(){ var res = match(); wbSave(res); /* v4.3: remember this visitor's answers on their device */ var total = res.reduce(function(s,r){ return s+(r.val||0); }, 0); $("#sabck-res-headline").innerHTML = 'You may qualify for 0 programs'; countUp($("#sabck-res-count"), res.length, ""); var inSA = /^782\d{2}$/.test(state.zip); if(state.incomeUnknown){ $("#sabck-res-sub").textContent = "Based on a household of "+state.hh+" in "+state.zip+". You skipped the income question, so this list shows everything you may qualify for — each program confirms income when you apply."; } else { $("#sabck-res-sub").textContent = inSA ? "Based on a household of "+state.hh+" with ~$"+state.income.toLocaleString()+"/mo income in "+state.zip+"." : "Based on a household of "+state.hh+" with ~$"+state.income.toLocaleString()+"/mo income. (Note: "+state.zip+" looks outside San Antonio — some local programs may not apply.)"; } if(total>0){ $("#sabck-res-value").style.display="block"; countUp($("#sabck-res-value-num"), total, "$"); } else { $("#sabck-res-value").style.display="none"; } var list = $("#sabck-res-list"); list.innerHTML=""; res.forEach(function(r, i){ var card=document.createElement("div"); card.className="sabck-card sabck-card-in"; if(!REDUCED) card.style.animationDelay = Math.min(i * 60, 420) + "ms"; var badge = r.p.hot ? ''+SVG_CLOCK+'Time-sensitive' : '✓ May qualify'; var valLine = r.val ? '

Worth up to ~$'+r.val.toLocaleString()+'/mo

' : ''; card.innerHTML = '

'+r.p.name+'

'+badge+'
' + '

'+r.why+'

' + valLine + '

'+r.p.what+'

' + '
' + ''+SVG_PHONE+''+r.p.phone+'' + 'Apply →' + '
'; list.appendChild(card); }); /* ---------- anonymous usage analytics (no IP / no email stored) ---------- */ try { var ev = { event: "complete", zip3: (state.zip||"").substring(0,3), hh: state.hh, income: state.income, sit: Object.keys(state.sit).filter(function(k){ return state.sit[k]; }).join(","), matched: res.map(function(r){ return r.p.id; }).join(","), count: res.length, value: res.reduce(function(s,r){ return s+(r.val||0); }, 0), page: window.location.pathname }; var xhr = new XMLHttpRequest(); xhr.open("POST", "/wp-json/sab/v1/quiz-event", true); xhr.setRequestHeader("Content-Type", "application/json"); xhr.send(JSON.stringify(ev)); } catch(e){} } /* ---------- share ---------- */ var shareUrl = window.location.href.split('#')[0]; var shareText = "I just checked which San Antonio benefits I qualify for — it takes 60 seconds and it's free:"; function doShare(k){ var u=encodeURIComponent(shareUrl), t=encodeURIComponent(shareText); if(k==="facebook") window.open("https://www.facebook.com/sharer/sharer.php?u="+u,"_blank","width=600,height=500"); else if(k==="whatsapp") window.open("https://wa.me/?text="+t+"%20"+u,"_blank"); else if(k==="x") window.open("https://twitter.com/intent/tweet?text="+t+"&url="+u,"_blank"); else if(k==="copy"){ var done=function(){ var d=$("#sabck-share-done"); d.style.display="block"; setTimeout(function(){d.style.display="none";},2500); }; if(navigator.clipboard) navigator.clipboard.writeText(shareText+" "+shareUrl).then(done,done); else done(); } } } /* end initChecker */ function initAllCheckers(){ var nodes = document.querySelectorAll(".sab-checker"); for(var i=0;i