Moving states?
Your license
comes with you.

A step-by-step guide to transferring your dental license — built for dentists who are relocating.

What is DentaMove?

The fastest way to get
licensed in a new state

Moving between states as a dentist means navigating a maze of different boards, requirements, and timelines. DentaMove cuts through all of it and walks you through every step.

Learn more
The problem

Interstate dental licensing
is genuinely hard

Every state has its own board, its own application, its own fees, its own timeline. Most dentists spend weeks just figuring out what they need — and then miss something anyway.

01
No two states are the same
Some accept your existing exam scores. Others require completely different ones. Requirements change without notice and are buried in government websites.
02
The timeline is a mystery
Most dentists underestimate how long licensing takes by 2–3 months, delaying their start date and income.
03
One missing document resets everything
A single missing notarization or background check can push you back 6–8 weeks while the board waits.
How it works

We build your roadmap.
You follow it.

Tell us where you are and where you are going. Everything else is handled.

1
You tell us your move
Select your current state and destination. We instantly build your personalized roadmap — no guesswork, no reading through board websites.
All 50 states covered. Requirements, fees, timelines, and exam score policies all researched and kept current.
2
We give you a step-by-step checklist
Every step in the right order, with a plain-language explanation of why it matters. No legal jargon. No guessing what comes next.
16 steps across 4 phases — from gathering credentials all the way to updating your DEA registration after you arrive.
3
You track every credential and document
Direct links to every portal you need, pre-written email templates, call scripts, and a journey tracker for each credential — including confirmation of receipt.
Direct-send credentials tracked separately. We make it clear what must go institution-to-board, and track each stage including confirmation numbers.
4
You know your exact timeline
See a realistic license date based on real board processing times for your destination state, not generic averages.
State coverage

Every state.
Every requirement.

Common relocation routes and what to expect.

New York → Texas
Accepts NERB · Jurisprudence exam required · FBI + TX DPS · $200
10–14 weeks
Florida → Arizona
Accepts NERB/CRDTS · DPS background check · $250
8–12 weeks
Any state → California
WREB scores only — NERB not accepted · DOJ + FBI · $400
20–26 weeks
Any state → Colorado
Accepts NERB · No jurisprudence exam · CBI check · $150
6–10 weeks
Texas → Florida
Accepts NERB · Jurisprudence exam · FDLE LiveScan · $325
12–18 weeks

Ready to get
your new license?

Create your free account and your personalized roadmap is ready in under two minutes.

No credit card. Free during beta.
Step 1 of 3 — Your account
Create your free account
No credit card required. Takes under two minutes.
Required
Required
Valid email required
At least 8 characters required
By continuing you agree to our Terms and Privacy Policy
Already have an account? Log in
Step 2 of 3 — Your specialty
Your specialty
Requirements vary by specialty. We tailor your checklist.
Step 3 of 3 — Your move
Where are you relocating?
We build your personalized roadmap instantly.
Account created
Welcome. Your roadmap is ready.
My Roadmap
1
Your Roadmap
Your personalized roadmap
New York Texas
General Dentist · $200 application fee · 10–14 week estimate
0 of 16 steps0%
0%
Complete
Steps Done
0
of 16 steps
Weeks Left
12
estimated
Action Required
0
items
Est. License
Timeline
Destination state
Texas requirements
Application fee$200
Accepts NERBYes
Jurisprudence examRequired
Background checkFBI + TX DPS
Processing time10–14 weeks
2
Credentials & Portals
Direct links · Stage tracking · Confirmation numbers

Every portal you need. Click any credential to expand it — copy the URL, open the site, follow the instructions, and log confirmation numbers at each stage.

3
Passport Photo
Not uploaded
Requirements
Size
2×2 in · Min 600×600 px
Background
Plain white or off-white
Recency
Within the last 6 months
No glasses
No glasses or head coverings
Full face
Front view, both eyes open
Format
JPG or PNG · Max 5 MB
Upload your photo — we'll check the requirements
Drag photo here or click to browse
JPG or PNG · Max 5 MB
Take your photo — PhotoAiD
Open PhotoAiD to take your photo
Automatically formats your photo to the correct size and white background. Free — no account required.
photoaid.com/en/passport-photo-usa
Or visit CVS · Walgreens · USPS for in-store service
{p.classList.remove('active');p.style.display='none';}); var el=document.getElementById(pageId); el.style.display='flex'; el.classList.add('active'); window.scrollTo(0,0); // Init about page reveal if(pageId==='pg-about'){ setTimeout(initReveal,100); } } /* ══════════════════════════ ABOUT PAGE ══════════════════════════ */ function initReveal(){ var obs=new IntersectionObserver(entries=>{entries.forEach(e=>{if(e.isIntersecting)e.target.classList.add('vis')})},{threshold:.1}); document.querySelectorAll('.reveal').forEach(el=>obs.observe(el)); } /* ══════════════════════════ SIGN UP FLOW ══════════════════════════ */ // Populate state selects ['inp-from','inp-to'].forEach(id=>{ var el=document.getElementById(id); ALL_STATES.forEach(s=>{var o=document.createElement('option');o.value=s;o.textContent=s;el.appendChild(o)}); }); var scCurrentStep=1; function scNext(n){ if(n>scCurrentStep&&!validateScStep(scCurrentStep)) return; scCurrentStep=n; document.querySelectorAll('.sc-step').forEach((s,i)=>s.classList.toggle('active',i===n-1)); ['prog0','prog1','prog2'].forEach((id,i)=>{ var el=document.getElementById(id); el.classList.remove('active','done'); if(iv.length>0); var v2=vf('f-last','inp-last',v=>v.length>0); var v3=vf('f-email','inp-email',v=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)); var v4=vf('f-pw','inp-pw',v=>v.length>=8); return v1&&v2&&v3&&v4; } if(n===2){ if(!document.getElementById('inp-spec').value){showErr('Please select your specialty.');return false;} return true; } return true; } function vf(fid,iid,check){ var f=document.getElementById(fid),i=document.getElementById(iid); var ok=check(i.value.trim()); f.classList.toggle('err',!ok); return ok; } function showErr(msg){var e=document.getElementById('scErr');e.textContent=msg;e.classList.add('show')} function hideErr(){document.getElementById('scErr').classList.remove('show')} // Password strength var pwVis=false; function checkPw(v){ var s=0; if(v.length>=8)s++;if(/[A-Z]/.test(v))s++;if(/[0-9]/.test(v))s++;if(/[^a-zA-Z0-9]/.test(v))s++; var c=['','#c93b3b','#c97a0a','#16a364','#1a7f74']; var l=['','Weak','Fair','Good','Strong']; ['pb0','pb1','pb2','pb3'].forEach((id,i)=>{document.getElementById(id).style.background=i0?l[s]:'';lbl.style.color=v.length>0?c[s]:''; } function togglePw(){ pwVis=!pwVis; document.getElementById('inp-pw').type=pwVis?'text':'password'; document.getElementById('eye-svg').innerHTML=pwVis ?'' :''; } // Specialty function selectSpec(btn){ document.querySelectorAll('.spec-opt').forEach(b=>b.classList.remove('sel')); btn.classList.add('sel'); document.getElementById('inp-spec').value=btn.textContent; document.getElementById('sc-btn2').disabled=false; hideErr(); } // States function checkStates(){ var from=document.getElementById('inp-from').value; var to=document.getElementById('inp-to').value; var btn=document.getElementById('sc-btn3'); btn.disabled=!(from&&to&&from!==to); var prev=document.getElementById('statePreview'); if(from&&to&&from!==to){ var info=STATE_DB[to]||{fee:'Varies',wkMin:8,wkMax:16,nerb:true,juris:false,bg:'State'}; prev.innerHTML=`${from} → ${to}
Estimated timeline: ${info.wkMin}–${info.wkMax} weeks · Fee: $${info.fee} · Background check: ${info.bg} · Jurisprudence exam: ${info.juris?'Required':'Not required'}`; prev.style.display='block'; } else { prev.style.display='none'; } if(from&&to&&from===to) showErr('Current state and destination cannot be the same.'); else hideErr(); } // Launch function launchApp(){ APP.name = (document.getElementById('inp-first').value.trim()||'Jane')+' '+(document.getElementById('inp-last').value.trim()||'Smith'); APP.name = 'Dr. '+APP.name; APP.specialty = document.getElementById('inp-spec').value||'General Dentist'; APP.from = document.getElementById('inp-from').value||'New York'; APP.to = document.getElementById('inp-to').value||'Texas'; var info = STATE_DB[APP.to]||{fee:200,wkMin:10,wkMax:14,nerb:true,juris:true,bg:'State'}; APP.tasks = buildTasks(APP.from,APP.to,info); APP.timeline = buildTimeline(APP.from,APP.to,info); APP.done = new Set(); // Init cred state PORTALS.forEach(p=>{ APP.credState[p.id] = p.stages ? {stages:p.stages.map(()=>({done:false,date:'',conf:''})),notes:''} : {stageIdx:0,notes:''}; }); // Show success briefly then go to app var btn=document.getElementById('sc-btn3'); btn.classList.add('loading');btn.disabled=true; document.getElementById('sc-step3').style.display='none'; document.getElementById('sc-success').classList.add('show'); document.getElementById('ss-sub-text').textContent=`Welcome, ${APP.name.replace('Dr.','')}. Your ${APP.from} → ${APP.to} roadmap is ready.`; // Update sidebar var initials=APP.name.replace(/Dr\.?\s*/i,'').trim().split(' ').map(w=>w[0]||'').join('').toUpperCase().slice(0,2)||'DR'; document.getElementById('sbAv').textContent=initials; document.getElementById('sbName').textContent=APP.name; document.getElementById('sbRole').textContent=APP.specialty; document.getElementById('sbFrom').textContent=APP.from.split(' ').map(w=>w[0]).join('').toUpperCase().slice(0,2); document.getElementById('sbTo').textContent=APP.to.split(' ').map(w=>w[0]).join('').toUpperCase().slice(0,2); // State facts document.getElementById('fbTitle').innerHTML=`${APP.to} requirements`; document.getElementById('fbFee').textContent='$'+info.fee; document.getElementById('fbNerb').textContent=info.nerb?'Yes':'No — WREB required'; document.getElementById('fbNerb').className='fb-v '+(info.nerb?'ok':'warn'); document.getElementById('fbJuris').textContent=info.juris?'Required':'Not required'; document.getElementById('fbBg').textContent=info.bg; document.getElementById('fbTimeline').textContent=info.wkMin+'–'+info.wkMax+' weeks'; document.getElementById('rhRoute').innerHTML=`${APP.from} ${APP.to}`; document.getElementById('rhMeta').textContent=`${APP.specialty} · $${info.fee} application fee · ${info.wkMin}–${info.wkMax} week estimate`; document.getElementById('tbSub').textContent=`${APP.from} → ${APP.to}`; document.getElementById('credsIntro').textContent=`Everything you need to collect for your ${APP.from} → ${APP.to} license transfer. Direct links to every portal, pre-written email templates, and stage-by-stage tracking for each credential.`; var d=new Date();d.setDate(d.getDate()+Math.round((info.wkMin+info.wkMax)/2)*7); document.getElementById('scDate').textContent=d.toLocaleDateString('en-US',{month:'short',year:'numeric'}); document.getElementById('scRange').textContent=info.wkMin+'–'+info.wkMax+' week range'; PORTALS = buildPortals(APP.from, APP.to, info); PORTALS.forEach(p=>{ APP.credState[p.id] = p.stages ? {stages:p.stages.map(()=>({done:false,date:"",conf:""})),notes:""} : {stageIdx:0,notes:""}; }); renderDashboard(); renderCreds(); } /* ══════════════════════════ APP NAVIGATION ══════════════════════════ */ function scrollSec(secId, navEl){ document.querySelectorAll('.nav-item').forEach(n=>n.classList.remove('active')); if(navEl) navEl.classList.add('active'); document.getElementById(secId)&&scrollIntoView({behavior:'smooth', block:'start'}); var titles={'sec-roadmap':'My Roadmap','sec-creds':'Credentials & Portals','sec-photo':'Passport Photo'}; document.getElementById('tbTitle').textContent=titles[secId]||'My Roadmap'; } /* ══════════════════════════ DASHBOARD RENDER ══════════════════════════ */ function renderAll(){renderDashboard();renderCreds();} function renderDashboard(){ updateProgress(); renderNextStep(); renderChecklistPreview(); renderChecklist(); renderTimeline(); } function getPct(){return APP.tasks.length?Math.round(APP.done.size/APP.tasks.length*100):0} function getUrgent(){return APP.tasks.filter(t=>t.urgent&&!APP.done.has(t.id)).length} function getNext(){return APP.tasks.find(t=>!APP.done.has(t.id))||null} function updateProgress(){ var p=getPct(),done=APP.done.size,total=APP.tasks.length; var info=STATE_DB[APP.to]||{wkMin:10,wkMax:14}; var wksLeft=Math.max(0,Math.round(Math.round((info.wkMin+info.wkMax)/2)*(1-p/100))); document.getElementById('rhFill').style.width=p+'%'; document.getElementById('rhPct').textContent=p+'%'; document.getElementById('rhPctLabel').textContent=p+'%'; document.getElementById('rhSteps').textContent=`${done} of ${total} steps`; document.getElementById('scDone').textContent=done; document.getElementById('scWeeks').textContent=wksLeft; document.getElementById('scUrgent').textContent=getUrgent(); document.getElementById('sbFill').style.width=p+'%'; document.getElementById('sbVal').textContent=p+'%'; document.getElementById('navCredBadge').textContent=PORTALS.filter(p=>!isCredDone(p)).length; } function renderNextStep(){ var next=getNext(); var phase=next?APP.tasks.find(t=>t.id===next.id)?APP.tasks.filter(t=>t.phase===next.phase)[0]:null:null; var phaseName={ 'Phase 1':'Gather credentials','Phase 2':'Submit application', 'Phase 3':'State requirements','Phase 4':'Post-license updates' }; if(document.getElementById('nsBody')) document.getElementById('nsBody').innerHTML=next?`
${next.phase} — ${phaseName[next.phase]||''}
${next.title}
${next.why}
`:`
All steps complete
Every item is checked off. Your ${APP.to} license is on its way.
`; } function renderChecklistPreview(){ var start=Math.max(0,APP.done.size-1); var preview=APP.tasks.slice(start,start+5); if(document.getElementById('checklistPreview')) document.getElementById('checklistPreview').innerHTML=preview.map(t=>{ var isDone=APP.done.has(t.id),isUrgent=t.urgent&&!isDone; var tag=isDone?'Complete':isUrgent?'Action required':'Pending'; return`
${t.title}
${tag}  ${t.phase}
`; }).join(''); } function renderTimeline(){ document.getElementById('tlPreview').innerHTML=APP.timeline.map(s=>`
${s.state==='now'?'
Current
':''}
${s.wk}
${s.ev}
${s.desc}
`).join(''); } function toggleTask(id){ APP.done.has(id)?APP.done.delete(id):(APP.done.add(id),checkPhaseComplete(id)); renderDashboard(); setTimeout(()=>{document.getElementById('rhFill').style.width=getPct()+'%'},50); } function completeTask(id){APP.done.add(id);checkPhaseComplete(id);renderDashboard();setTimeout(()=>{document.getElementById('rhFill').style.width=getPct()+'%'},50)} function skipTask(id){APP.done.add(id);renderDashboard();setTimeout(()=>{document.getElementById('rhFill').style.width=getPct()+'%'},50);showToast('Skipped — uncheck it in the checklist anytime')} function checkPhaseComplete(id){ var task=APP.tasks.find(t=>t.id===id); if(!task) return; var phaseTasks=APP.tasks.filter(t=>t.phase===task.phase); var allPhaseDone=phaseTasks.every(t=>APP.done.has(t.id)); var allDone=APP.tasks.every(t=>APP.done.has(t.id)); var pName={'Phase 1':'Credentials gathered','Phase 2':'Application submitted','Phase 3':'Requirements complete','Phase 4':'Fully licensed'}; if(allDone){showComp('Roadmap complete','Every step is finished. Your '+APP.to+' dental license is on its way.');confetti()} else if(allPhaseDone){showComp(pName[task.phase]||'Phase complete','You have finished '+task.phase+'. Keep going — your license is closer.');confetti()} else showToast('Step marked as complete'); } /* ══════════════════════════ CREDENTIALS — fully dynamic for all 50-state pairs ══════════════════════════ */ /* State board data — official URLs, addresses, agency codes, fees */ var BOARD_DATA = { "Alabama": {name:"Alabama Board of Dental Examiners",url:"https://www.dentalboard.org",addr:"2660 Eastchase Lane, Suite 200, Montgomery, AL 36117",fee:200,bgAgency:"SBI",bgNote:"State Bureau of Investigation LiveScan",jurisUrl:"https://www.dentalboard.org/licensure",licUrl:"https://www.dentalboard.org"}, "Alaska": {name:"Alaska Board of Dental Examiners",url:"https://www.commerce.alaska.gov/web/cbpl/ProfessionalLicensing/DentalExaminers.aspx",addr:"PO Box 110806, Juneau, AK 99811",fee:600,bgAgency:"State DPS",bgNote:"Alaska DPS fingerprinting",jurisUrl:null,licUrl:"https://www.commerce.alaska.gov/web/cbpl/ProfessionalLicensing/DentalExaminers.aspx"}, "Arizona": {name:"Arizona State Board of Dental Examiners",url:"https://www.azdentalboard.org",addr:"1400 W Washington St, Suite 230, Phoenix, AZ 85007",fee:250,bgAgency:"DPS",bgNote:"Arizona DPS LiveScan",jurisUrl:"https://www.azdentalboard.org/licensure/jurisprudence",licUrl:"https://www.azdentalboard.org"}, "Arkansas": {name:"Arkansas State Board of Dental Examiners",url:"https://www.asbde.org",addr:"101 E Capitol Ave, Suite 111, Little Rock, AR 72201",fee:300,bgAgency:"State",bgNote:"Arkansas background check via state portal",jurisUrl:"https://www.asbde.org/jurisprudence",licUrl:"https://www.asbde.org"}, "California": {name:"Dental Board of California",url:"https://www.dbc.ca.gov",addr:"2005 Evergreen St, Suite 1550, Sacramento, CA 95815",fee:400,bgAgency:"DOJ + FBI",bgNote:"California DOJ and FBI LiveScan — two separate submissions",jurisUrl:"https://www.dbc.ca.gov/applicants/jurisprudence.shtml",licUrl:"https://www.dbc.ca.gov/applicants/out_of_state.shtml"}, "Colorado": {name:"Colorado Dental Board",url:"https://dpo.colorado.gov/Dental",addr:"1560 Broadway, Suite 1350, Denver, CO 80202",fee:150,bgAgency:"CBI",bgNote:"Colorado Bureau of Investigation fingerprinting",jurisUrl:null,licUrl:"https://dpo.colorado.gov/Dental"}, "Connecticut": {name:"Connecticut Dental Commission",url:"https://portal.ct.gov/DPH/Practitioner-Licensing--Investigations/Dental/Dental-Licensure-Information",addr:"410 Capitol Ave, Hartford, CT 06134",fee:565,bgAgency:"State",bgNote:"State background check via DESPP",jurisUrl:null,licUrl:"https://portal.ct.gov/DPH/Practitioner-Licensing--Investigations/Dental/Dental-Licensure-Information"}, "Delaware": {name:"Delaware Board of Dentistry and Dental Hygiene",url:"https://dpr.delaware.gov/boards/dental",addr:"861 Silver Lake Blvd, Dover, DE 19904",fee:200,bgAgency:"State",bgNote:"Delaware State Police fingerprinting",jurisUrl:null,licUrl:"https://dpr.delaware.gov/boards/dental"}, "Florida": {name:"Florida Board of Dentistry",url:"https://floridasdentistry.gov",addr:"4052 Bald Cypress Way, Bin C08, Tallahassee, FL 32399",fee:325,bgAgency:"FDLE",bgNote:"Florida FDLE LiveScan fingerprinting",jurisUrl:"https://floridasdentistry.gov/licensing/jurisprudence-exam",licUrl:"https://floridasdentistry.gov/licensing/by-licensure"}, "Georgia": {name:"Georgia Board of Dentistry",url:"https://sos.ga.gov/index.php/licensing/plb/5",addr:"2 Martin Luther King Jr Dr SE, Suite 620, Atlanta, GA 30334",fee:275,bgAgency:"GBI",bgNote:"Georgia Bureau of Investigation background check",jurisUrl:"https://sos.ga.gov/index.php/licensing/plb/5",licUrl:"https://sos.ga.gov/index.php/licensing/plb/5"}, "Hawaii": {name:"Hawaii Dental Examiners Board",url:"https://cca.hawaii.gov/pvl/boards/dental",addr:"335 Merchant St, Room 301, Honolulu, HI 96813",fee:350,bgAgency:"State",bgNote:"Hawaii Criminal Justice Data Center",jurisUrl:null,licUrl:"https://cca.hawaii.gov/pvl/boards/dental"}, "Idaho": {name:"Idaho State Board of Dentistry",url:"https://isbd.idaho.gov",addr:"PO Box 83720, Boise, ID 83720",fee:200,bgAgency:"State",bgNote:"Idaho State Police background check",jurisUrl:null,licUrl:"https://isbd.idaho.gov"}, "Illinois": {name:"Illinois Department of Financial and Professional Regulation",url:"https://idfpr.illinois.gov/profs/Dentist.asp",addr:"320 W Washington St, Springfield, IL 62786",fee:500,bgAgency:"ISP",bgNote:"Illinois State Police fingerprinting",jurisUrl:null,licUrl:"https://idfpr.illinois.gov/profs/Dentist.asp"}, "Indiana": {name:"Indiana Professional Licensing Agency — Dental",url:"https://www.in.gov/pla/dental.htm",addr:"402 W Washington St, Room W072, Indianapolis, IN 46204",fee:225,bgAgency:"State",bgNote:"Indiana State Police background check",jurisUrl:null,licUrl:"https://www.in.gov/pla/dental.htm"}, "Iowa": {name:"Iowa Dental Board",url:"https://dentalboard.iowa.gov",addr:"400 SW 8th St, Suite D, Des Moines, IA 50309",fee:275,bgAgency:"State",bgNote:"Iowa DCI background check",jurisUrl:"https://dentalboard.iowa.gov/licensure/jurisprudence",licUrl:"https://dentalboard.iowa.gov"}, "Kansas": {name:"Kansas Dental Board",url:"https://www.kdb.ks.gov",addr:"900 SW Jackson, Suite 564, Topeka, KS 66612",fee:200,bgAgency:"KBI",bgNote:"Kansas Bureau of Investigation fingerprinting",jurisUrl:null,licUrl:"https://www.kdb.ks.gov"}, "Kentucky": {name:"Kentucky Board of Dentistry",url:"https://kbdexams.ky.gov",addr:"312 Whittington Pkwy, Suite 101, Louisville, KY 40222",fee:350,bgAgency:"State",bgNote:"Kentucky State Police background check",jurisUrl:"https://kbdexams.ky.gov/jurisprudence",licUrl:"https://kbdexams.ky.gov"}, "Louisiana": {name:"Louisiana State Board of Dentistry",url:"https://www.lsbd.org",addr:"365 Canal St, Suite 2680, New Orleans, LA 70130",fee:450,bgAgency:"State",bgNote:"Louisiana State Police background check",jurisUrl:"https://www.lsbd.org/licensure",licUrl:"https://www.lsbd.org"}, "Maine": {name:"Maine Board of Dental Examiners",url:"https://www.maine.gov/boardofdentalexaminers",addr:"143 State House Station, Augusta, ME 04333",fee:300,bgAgency:"State",bgNote:"Maine State Police background check",jurisUrl:null,licUrl:"https://www.maine.gov/boardofdentalexaminers"}, "Maryland": {name:"Maryland Board of Dental Examiners",url:"https://health.maryland.gov/boardsdentistry",addr:"4201 Patterson Ave, Baltimore, MD 21215",fee:350,bgAgency:"State",bgNote:"Maryland background check via CJIS",jurisUrl:null,licUrl:"https://health.maryland.gov/boardsdentistry"}, "Massachusetts":{name:"Massachusetts Board of Registration in Dentistry",url:"https://www.mass.gov/orgs/board-of-registration-in-dentistry",addr:"239 Causeway St, Boston, MA 02114",fee:360,bgAgency:"State",bgNote:"Massachusetts CORI background check",jurisUrl:null,licUrl:"https://www.mass.gov/orgs/board-of-registration-in-dentistry"}, "Michigan": {name:"Michigan Board of Dentistry",url:"https://www.michigan.gov/lara/bureau-list/bpl/occ/professions/dentist",addr:"PO Box 30670, Lansing, MI 48909",fee:229,bgAgency:"MSP",bgNote:"Michigan State Police fingerprinting via IdentoGo",jurisUrl:null,licUrl:"https://www.michigan.gov/lara/bureau-list/bpl/occ/professions/dentist"}, "Minnesota": {name:"Minnesota Board of Dentistry",url:"https://mn.gov/boards/dentistry",addr:"2829 University Ave SE, Suite 450, Minneapolis, MN 55414",fee:350,bgAgency:"BCA",bgNote:"Minnesota Bureau of Criminal Apprehension fingerprinting",jurisUrl:null,licUrl:"https://mn.gov/boards/dentistry"}, "Mississippi": {name:"Mississippi State Board of Dental Examiners",url:"https://www.msbde.ms.gov",addr:"Box 8 at No. 600 E. Amite St. Suite 100, Jackson, MS 39201",fee:300,bgAgency:"State",bgNote:"Mississippi State background check",jurisUrl:"https://www.msbde.ms.gov/jurisprudence",licUrl:"https://www.msbde.ms.gov"}, "Missouri": {name:"Missouri Dental Board",url:"https://pr.mo.gov/dental.asp",addr:"PO Box 1367, Jefferson City, MO 65102",fee:275,bgAgency:"State",bgNote:"Missouri State Highway Patrol background check",jurisUrl:null,licUrl:"https://pr.mo.gov/dental.asp"}, "Montana": {name:"Montana Board of Dentistry",url:"https://boards.bsd.dli.mt.gov/dentistry",addr:"301 S Park Ave, Helena, MT 59601",fee:200,bgAgency:"State",bgNote:"Montana background check via DOJ",jurisUrl:null,licUrl:"https://boards.bsd.dli.mt.gov/dentistry"}, "Nebraska": {name:"Nebraska Dental Association — DHHS Licensing",url:"https://dhhs.ne.gov/Pages/Dental-Licensing.aspx",addr:"301 Centennial Mall S, Lincoln, NE 68509",fee:225,bgAgency:"State",bgNote:"Nebraska State Patrol background check",jurisUrl:null,licUrl:"https://dhhs.ne.gov/Pages/Dental-Licensing.aspx"}, "Nevada": {name:"Nevada State Board of Dental Examiners",url:"https://nvdental.org",addr:"6010 S Rainbow Blvd, Suite A-1, Las Vegas, NV 89118",fee:400,bgAgency:"State",bgNote:"Nevada background check via fingerprinting",jurisUrl:"https://nvdental.org/licensure/jurisprudence",licUrl:"https://nvdental.org"}, "New Hampshire":{name:"New Hampshire Dental Board",url:"https://www.oplc.nh.gov/dental",addr:"7 Eagle Square, Concord, NH 03301",fee:275,bgAgency:"State",bgNote:"NH State Police background check",jurisUrl:null,licUrl:"https://www.oplc.nh.gov/dental"}, "New Jersey": {name:"New Jersey State Board of Dentistry",url:"https://www.njconsumeraffairs.gov/den",addr:"124 Halsey St, Newark, NJ 07102",fee:450,bgAgency:"State",bgNote:"NJ State Police fingerprinting via IdentoGo",jurisUrl:null,licUrl:"https://www.njconsumeraffairs.gov/den"}, "New Mexico": {name:"New Mexico Board of Dental Health Care",url:"https://www.rld.nm.gov/boards-and-commissions/individual-boards-and-commissions/dental-health-care",addr:"2550 Cerrillos Rd, Santa Fe, NM 87505",fee:200,bgAgency:"State",bgNote:"New Mexico background check via DPS",jurisUrl:"https://www.rld.nm.gov/boards-and-commissions/individual-boards-and-commissions/dental-health-care",licUrl:"https://www.rld.nm.gov/boards-and-commissions/individual-boards-and-commissions/dental-health-care"}, "New York": {name:"NY Office of the Professions — Dental",url:"https://www.op.nysed.gov/professions/dentists",addr:"89 Washington Ave, Albany, NY 12234",fee:370,bgAgency:"DCJS",bgNote:"NY Division of Criminal Justice Services",jurisUrl:null,licUrl:"https://www.op.nysed.gov/professions/dentists"}, "North Carolina":{name:"North Carolina State Board of Dental Examiners",url:"https://www.ncdentalboard.org",addr:"507 Airport Blvd, Suite 105, Morrisville, NC 27560",fee:375,bgAgency:"State",bgNote:"NC SBI background check",jurisUrl:"https://www.ncdentalboard.org/applicants/jurisprudence",licUrl:"https://www.ncdentalboard.org"}, "North Dakota": {name:"North Dakota State Board of Dentistry",url:"https://www.nddentalboard.org",addr:"PO Box 7246, Bismarck, ND 58507",fee:175,bgAgency:"State",bgNote:"ND background check via BCI",jurisUrl:null,licUrl:"https://www.nddentalboard.org"}, "Ohio": {name:"Ohio State Dental Board",url:"https://dental.ohio.gov",addr:"77 S High St, 18th Floor, Columbus, OH 43215",fee:300,bgAgency:"BCI + FBI",bgNote:"Ohio BCI and FBI dual fingerprinting via WebCheck",jurisUrl:null,licUrl:"https://dental.ohio.gov"}, "Oklahoma": {name:"Oklahoma Board of Dentistry",url:"https://www.ok.gov/dentistry",addr:"2401 NW 23rd St, Suite 11, Oklahoma City, OK 73107",fee:350,bgAgency:"State",bgNote:"Oklahoma background check via OSBI",jurisUrl:"https://www.ok.gov/dentistry/jurisprudence",licUrl:"https://www.ok.gov/dentistry"}, "Oregon": {name:"Oregon Board of Dentistry",url:"https://www.oregon.gov/obd",addr:"1600 SW 4th Ave, Suite 770, Portland, OR 97201",fee:487,bgAgency:"State",bgNote:"Oregon background check via state police",jurisUrl:"https://www.oregon.gov/obd/applicants/pages/jurisprudence.aspx",licUrl:"https://www.oregon.gov/obd"}, "Pennsylvania": {name:"Pennsylvania State Board of Dentistry",url:"https://www.dos.pa.gov/ProfessionalLicensing/BoardsCommissions/Dentistry",addr:"PO Box 2649, Harrisburg, PA 17105",fee:350,bgAgency:"State",bgNote:"Pennsylvania background check via SP fingerprinting",jurisUrl:null,licUrl:"https://www.dos.pa.gov/ProfessionalLicensing/BoardsCommissions/Dentistry"}, "Rhode Island": {name:"Rhode Island Board of Examiners in Dentistry",url:"https://health.ri.gov/licenses/detail.php?id=219",addr:"3 Capitol Hill, Providence, RI 02908",fee:375,bgAgency:"State",bgNote:"Rhode Island background check via RISP",jurisUrl:null,licUrl:"https://health.ri.gov/licenses/detail.php?id=219"}, "South Carolina":{name:"South Carolina Board of Dentistry",url:"https://www.llr.sc.gov/pol/dentistry",addr:"110 Centerview Dr, Columbia, SC 29210",fee:300,bgAgency:"SLED",bgNote:"SC Law Enforcement Division fingerprinting",jurisUrl:"https://www.llr.sc.gov/pol/dentistry/jurisprudence",licUrl:"https://www.llr.sc.gov/pol/dentistry"}, "South Dakota": {name:"South Dakota Board of Dentistry",url:"https://dss.sd.gov/dentistry",addr:"PO Box 1079, Pierre, SD 57501",fee:200,bgAgency:"State",bgNote:"SD background check via DCI",jurisUrl:null,licUrl:"https://dss.sd.gov/dentistry"}, "Tennessee": {name:"Tennessee Board of Dentistry",url:"https://www.tn.gov/health/health-program-areas/health-professional-boards/dentistry-board.html",addr:"665 Mainstream Dr, Nashville, TN 37243",fee:350,bgAgency:"TBI",bgNote:"Tennessee Bureau of Investigation fingerprinting",jurisUrl:"https://www.tn.gov/health/health-program-areas/health-professional-boards/dentistry-board/jurisprudence.html",licUrl:"https://www.tn.gov/health/health-program-areas/health-professional-boards/dentistry-board.html"}, "Texas": {name:"Texas State Board of Dental Examiners",url:"https://www.tsbde.texas.gov",addr:"333 Guadalupe, Tower 3, Suite 800, Austin, TX 78701",fee:200,bgAgency:"FBI + TX DPS",bgNote:"FBI and Texas DPS — two separate checks via IdentoGo (code: 11G2XN)",jurisUrl:"https://www.tsbde.texas.gov/jurisprudence",licUrl:"https://www.tsbde.texas.gov/licensing/new-applicants"}, "Utah": {name:"Utah Dentist and Dental Hygienist Licensing Board",url:"https://dopl.utah.gov/dentist",addr:"PO Box 146741, Salt Lake City, UT 84114",fee:175,bgAgency:"State",bgNote:"Utah background check via BCI",jurisUrl:"https://dopl.utah.gov/dentist/jurisprudence",licUrl:"https://dopl.utah.gov/dentist"}, "Vermont": {name:"Vermont Board of Dental Examiners",url:"https://sos.vermont.gov/dental-examiners",addr:"89 Main St, Montpelier, VT 05620",fee:275,bgAgency:"State",bgNote:"Vermont background check via VSP",jurisUrl:null,licUrl:"https://sos.vermont.gov/dental-examiners"}, "Virginia": {name:"Virginia Board of Dentistry",url:"https://www.dhp.virginia.gov/dentistry",addr:"9960 Mayland Dr, Suite 300, Richmond, VA 23233",fee:302,bgAgency:"State",bgNote:"Virginia background check via VSP",jurisUrl:null,licUrl:"https://www.dhp.virginia.gov/dentistry"}, "Washington": {name:"Washington State Dental Quality Assurance Commission",url:"https://www.doh.wa.gov/LicensesPermitsandCertificates/ProfessionsNewReneworUpdate/Dentist",addr:"PO Box 47852, Olympia, WA 98504",fee:400,bgAgency:"WSP",bgNote:"Washington State Patrol fingerprinting",jurisUrl:"https://www.doh.wa.gov/LicensesPermitsandCertificates/ProfessionsNewReneworUpdate/Dentist",licUrl:"https://www.doh.wa.gov/LicensesPermitsandCertificates/ProfessionsNewReneworUpdate/Dentist"}, "West Virginia":{name:"West Virginia Board of Dental Examiners",url:"https://www.wvdentalboard.org",addr:"102 Dee Dr, Suite 1, Charleston, WV 25311",fee:250,bgAgency:"State",bgNote:"WV background check via WVSP",jurisUrl:null,licUrl:"https://www.wvdentalboard.org"}, "Wisconsin": {name:"Wisconsin Dentistry Examining Board",url:"https://dsps.wi.gov/Pages/Professions/Dentist/Default.aspx",addr:"PO Box 8935, Madison, WI 53708",fee:300,bgAgency:"State",bgNote:"Wisconsin background check via DOJ",jurisUrl:null,licUrl:"https://dsps.wi.gov/Pages/Professions/Dentist/Default.aspx"}, "Wyoming": {name:"Wyoming Board of Dental Examiners",url:"https://wbde.wyo.gov",addr:"130 Hobbs Ave, Suite B, Cheyenne, WY 82002",fee:150,bgAgency:"State",bgNote:"Wyoming background check via DCI",jurisUrl:null,licUrl:"https://wbde.wyo.gov"}, }; /* Origin state license verification URLs */ var LICENSE_VERIFY_URL = { "New York": "https://www.op.nysed.gov/verification", "California": "https://www.dbc.ca.gov/consumers/licensee_lookup.shtml", "Florida": "https://floridasdentistry.gov/consumers/verify-a-license", "Texas": "https://www.tsbde.texas.gov/verify", "Illinois": "https://idfpr.illinois.gov/LicenseLookup/DefLicenseLookup.asp", "Ohio": "https://elicense.ohio.gov/oh_verifylicense", "Pennsylvania": "https://www.dos.pa.gov/ProfessionalLicensing/LicenseLookup", "Georgia": "https://verify.sos.ga.gov/verification", "Michigan": "https://aca-prod.accela.com/MILARA/GeneralProperty/PropertyLookUp.aspx", "Arizona": "https://www.azdentalboard.org/verify", }; function getLicVerifyUrl(state){ return LICENSE_VERIFY_URL[state] || ((BOARD_DATA[state]?BOARD_DATA[state].licUrl:undefined)) || `https://www.google.com/search?q=${encodeURIComponent(state+' dental board license verification')}`; } /* Exam score portals */ var EXAM_PORTALS = { nerb: { name:'NERB', url:'https://www.nerb.org/score-transcripts', display:'nerb.org/score-transcripts', phone:'(312) 440-2890' }, crdts:{ name:'CRDTS',url:'https://www.crdts.org/transcripts', display:'crdts.org/transcripts', phone:'(312) 440-2890' }, wreb: { name:'WREB', url:'https://www.wreb.org/transcripts', display:'wreb.org/transcripts', phone:'(602) 944-3315' }, }; /* Build dynamic portals for any from→to pair */ function buildPortals(from, to, info) { var toBoard = BOARD_DATA[to] || {name:`${to} State Dental Board`,url:`https://www.google.com/search?q=${encodeURIComponent(to+' dental board')}`,addr:`${to} State Dental Board`,fee:info.fee||200,bgAgency:info.bg||'State',bgNote:'State background check',jurisUrl:null,licUrl:null}; var fromBoard= BOARD_DATA[from]|| {name:`${from} State Dental Board`,url:`https://www.google.com/search?q=${encodeURIComponent(from+' dental board')}`}; var exam = info.nerb ? exam_portal_for(to) : EXAM_PORTALS.wreb; var toAbbr = to.split(' ').map(w=>w[0]).join(''); var fromAbbr = from.split(' ').map(w=>w[0]).join(''); var portals = [ /* ── 1. ORIGIN LICENSE VERIFICATION ── */ {id:'lic-verify', group:'direct', iconType:'t', icon:'', name:`${from} — License Verification`, sub:`Request certified verification — sent directly to ${to} Board`, url: getLicVerifyUrl(from), urlDisplay: getLicVerifyUrl(from).replace('https://','').split('?')[0], directSend:`The ${fromBoard.name} sends this directly to the ${toBoard.name}. You initiate the request — you never handle the document yourself.`, steps:[ {action:`Log in to the ${from} licensing portal`,detail:`Use your ${from} dental license number and date of birth or account credentials.`}, {action:'Request a verification letter to be sent to another state board',detail:`Select "Send to another state board" or equivalent option.`}, {action:`Enter the ${to} Board mailing address`,detail:toBoard.addr}, {action:'Pay the verification fee (~$15–$25) and save your confirmation number',detail:`Screenshot your confirmation — you will need it if the ${to} Board reports non-receipt.`}, ], infoNote:`${from} typically processes verification requests in 5–10 business days. Follow up if not confirmed within 2 weeks.`, stages:[{label:`You requested it from ${from}`},{label:`${fromAbbr} Board confirmed it was sent`},{label:`${toAbbr} Board confirmed receipt`},{label:'Accepted into your file'}] }, /* ── 2. EXAM SCORES ── */ {id:'exam-scores', group:'direct', iconType:'t', icon:'', name:`${exam.name} — Score Transcript Request`, sub:`${info.nerb?'NERB or CRDTS':'WREB'} scores sent directly to ${to} Board`, url: exam.url, urlDisplay: exam.display, directSend:`${exam.name} sends your score transcript directly to the ${to} Board. You request it — they send it. A personal copy submitted by you will be rejected.`, ...(!info.nerb ? {urgentNote:`${to} requires WREB scores only. NERB and CRDTS scores are not accepted. If you have NERB scores, you may need to retake the clinical exam.`} : {}), steps:[ {action:`Log in to ${exam.name} with your candidate ID`,detail:`Your candidate ID was in your original exam confirmation email. Call ${exam.phone} if you cannot locate it.`}, {action:'Select "Request Score Transcript" or equivalent',detail:`Choose "State Board" as the recipient type and select ${to} from the dropdown.`}, {action:`Confirm the ${to} Board as recipient`,detail:toBoard.addr}, {action:'Pay the transcript fee (~$25–$50)',detail:'Pay by credit or debit card. Save your order confirmation number.'}, ], infoNote: info.nerb ? `CRDTS applicants: visit crdts.org/transcripts — the process is identical to NERB.` : `Only WREB scores are accepted by ${to}. If you have NERB or CRDTS scores only, contact the ${to} Board directly to discuss your options.`, stages:[{label:`You submitted the transcript request`},{label:`${exam.name} confirmed it was sent`},{label:`${toAbbr} Board confirmed receipt`},{label:'Scores accepted into your file'}] }, /* ── 3. DENTAL SCHOOL TRANSCRIPT ── */ {id:'transcript', group:'direct', iconType:'t', icon:'', name:'Official Transcript — Parchment / NSC / e-Scrip-Safe', sub:`Sent electronically direct to ${to} Board — cannot be hand-delivered`, url:'https://www.parchment.com/request-transcript/', urlDisplay:'parchment.com/request-transcript', directSend:`Your transcript must travel directly from your dental school to the ${toBoard.name}. Most schools now send electronically through Parchment, National Student Clearinghouse, or e-Scrip-Safe. Check your school's registrar page to confirm which service they use — you cannot submit it yourself under any method.`, steps:[ {action:'Check which service your school uses',detail:'Visit your dental school registrar or alumni portal. Most dental schools now use Parchment. Some use National Student Clearinghouse (NSC) or e-Scrip-Safe.'}, {action:'Order through Parchment (most common)',detail:`Log in at parchment.com, search for your dental school, and request an official transcript to be sent to the ${to} Board. Select "Third Party / Licensing Board" as the recipient type and enter the board address: ${toBoard.addr}`}, {action:'If your school uses NSC',detail:'Order at studentclearinghouse.org — same process. Search for your school, request an official transcript, and select the '+to+' Board as the recipient.'}, {action:'If your school uses e-Scrip-Safe',detail:'Order at escrip-safe.com. Log in with your school credentials, request a transcript, and send directly to the board.'}, {action:'If your school still uses paper only',detail:`Email the registrar directly. Include your full legal name, graduation year, student ID, and the board mailing address. Request a sealed envelope sent directly — ask for written confirmation and a tracking number.`}, ], infoNote:'Electronic transcripts via Parchment, NSC, or e-Scrip-Safe are accepted by all state dental boards and arrive faster than paper. Keep your order confirmation number — you will need it if the board reports non-receipt.', warnNote:`Your name on the transcript must exactly match your ${from} dental license. A name mismatch will delay processing and may require a corrected resubmission.`, stages:[{label:'You placed the transcript order'},{label:'Service confirmed transcript was sent'},{label:`${toAbbr} Board confirmed receipt`},{label:'Transcript accepted into your file'}] }, {id:'dest-board', group:'direct', iconType:'t', icon:'', name:`${toBoard.name}`, sub:`Application download, submission${info.juris?', and jurisprudence exam':''}`, url: toBoard.licUrl || toBoard.url, urlDisplay: (toBoard.licUrl||toBoard.url).replace('https://','').split('?')[0], steps:[ {action:`Create an account on the ${to} Board portal`,detail:`Visit the board website and click "New Applicant" or "Apply for Licensure" to register.`}, {action:'Download the out-of-state dentist application packet',detail:'Select "General Dentist by Examination — Out of State" or equivalent. Download and print the full packet.'}, {action:'Complete every section and have the application notarized',detail:`Every field must be complete. Notary services are available at most UPS, FedEx, and bank branches.`}, {action:`Mail with $${toBoard.fee} money order or check payable to "${toBoard.name}"`,detail:`Mailing address: ${toBoard.addr}. Use regular mail only — do not use certified mail requiring a signature.`}, ...(info.juris ? [{action:`Access the ${to} Jurisprudence Exam through your portal account`,detail:`After your application is approved, log back in to access the exam. It is online, open-book, and covers ${to} dental law and ethics.`}] : []), ], warnNote:`Do not use certified mail with a signature requirement — the board mail room will not sign for it and your package will be returned.`, stages:[{label:`${toAbbr} Board account created`},{label:'Application downloaded and completed'},{label:`Application mailed with $${toBoard.fee} fee`},{label:`${toAbbr} Board confirmed receipt`}] }, /* ── 5. BACKGROUND CHECK ── */ {id:'background', group:'fingerprint', iconType:'a', icon:'', name:`${to} Background Check — ${toBoard.bgAgency}`, sub:`Fingerprint appointment — results go directly to ${to} Board`, url:'https://www.identogo.com', urlDisplay:'identogo.com', urgentNote:`Book this immediately. Background check results take 3–4 weeks and are the most common cause of delays in ${to} dental licensing.`, directSend:`You attend the fingerprinting appointment. ${toBoard.bgAgency} sends results directly to the ${to} Board. You never see the results — the board receives them automatically.`, steps:[ ...(toBoard.bgNote.includes('11G2XN')||to==='Texas' ? [{action:'Enter the board agency code when booking',detail:`When prompted on IdentoGo, enter 11G2XN — this is the ${to} Board code for the DPS check.`}] : [{action:'Select the correct agency when booking',detail:`When prompted, select "${toBoard.name}" as the agency. ${toBoard.bgNote}.`}]), {action:'Search for the earliest available appointment near you',detail:'Take any available slot — do not wait for a convenient time. IdentoGo locations are commonly at Walgreens and UPS stores.'}, {action:'Bring a government-issued photo ID to your appointment',detail:'Passport or driver\'s license required. The appointment takes approximately 15 minutes.'}, {action:'Results are sent directly to the board — you will not receive a copy',detail:`The ${to} Board will update your application status when results are received.`}, ], infoNote:`${toBoard.bgNote}. Fee is approximately $38–$65 depending on the state.`, stages:[{label:'Appointment booked'},{label:'Appointment completed'},{label:`Results sent to ${toAbbr} Board`},{label:`${toAbbr} Board confirmed all checks received`}] }, /* ── 6. CPR/BLS ── */ {id:'cpr', group:'self', iconType:'i', icon:'', name:'AHA — BLS Certification', sub:`Find an in-person BLS class — required by ${to}`, url:'https://cpr.heart.org/en/cpr-courses-and-kits/healthcare-professional', urlDisplay:'cpr.heart.org — BLS for Healthcare Providers', steps:[ {action:'Select "BLS for Healthcare Providers" and "In-person" as the format',detail:`${to} does not accept online-only certifications. You must complete an in-person skills check with an instructor.`}, {action:'Enter your ZIP code and find the earliest available class',detail:'Classes are 3–4 hours and available at hospitals, dental schools, and community centers.'}, {action:'Verify the expiry date before you register',detail:`Your certification must be valid for at least 2 years from the date you submit your ${to} application.`}, ], warnNote:`Online-only BLS certifications are not accepted by the ${to} Board. The course must include a hands-on skills evaluation with a certified instructor.`, selfStages:['Not started','Class booked','Certified — ready to submit'] }, /* ── 7. PASSPORT PHOTO ── */ {id:'photo', group:'self', iconType:'i', icon:'', name:'Passport-Style Photograph', sub:`2 × 2 inch photo — submitted with your ${to} application`, url:'https://photoaid.com/en/passport-photo-usa', urlDisplay:'photoaid.com — auto-formats to correct size and background', steps:[ {action:'Use PhotoAiD to take or format your photo',detail:'PhotoAiD automatically applies the correct dimensions, white background, and cropping. Free, no account required.'}, {action:'Or visit CVS, Walgreens, or USPS for an in-store photo',detail:'Same-day service, typically $15–$20. Tell them it is for a state licensing application.'}, {action:'Upload your finished photo in the Passport Photo section',detail:'DentaMove will check it meets all requirements before you submit.'}, ], infoNote:'Your photo must be taken within the last 6 months. Plain white or off-white background only. No glasses or head coverings.', selfStages:['Not started','Photo taken','Uploaded and verified — ready to submit'] }, /* ── 8. DEA UPDATE ── */ {id:'dea', group:'post', iconType:'i', icon:'', name:'DEA — Update Registration Address', sub:`Update your DEA address to ${to} — after license is issued`, url:'https://www.deadiversion.usdoj.gov/webforms/loginController.jsp', urlDisplay:'deadiversion.usdoj.gov', steps:[ {action:'Log in with your DEA number and password',detail:'Your DEA number begins with a letter followed by 7 digits (e.g. BS1234567).'}, {action:'Select "Modify Registration" — do not apply for a new registration',detail:'Your DEA number stays the same. Only your registered address changes.'}, {action:`Update your address to your ${to} practice address`,detail:`Must match your ${to} dental license address exactly.`}, {action:`Add your ${to} dental license number in the state license field`,detail:''}, ], infoNote:'Updating your DEA address is free. Your DEA number and expiration date do not change.', selfStages:[`Waiting for ${to} license`,'Updated at DEA.gov',`Confirmed — shows ${to} address`] }, /* ── 9. NPI UPDATE ── */ {id:'npi', group:'post', iconType:'g', icon:'', name:'NPPES — Update NPI Record', sub:`Update practice address and add ${to} license — after license is issued`, url:'https://nppes.cms.hhs.gov', urlDisplay:'nppes.cms.hhs.gov', steps:[ {action:'Log in to the NPI Registry',detail:'Use your NPPES username and password. Use "Forgot Username/Password" if needed.'}, {action:'Select your provider record and click "Edit"',detail:'Navigate to your individual NPI record.'}, {action:`Update your practice location to your ${to} address`,detail:''}, {action:`Add your ${to} dental license in the License Information section`,detail:`Include the license number, issuing state (${to.slice(0,2).toUpperCase()}), and expiration date.`}, {action:'Submit — changes are processed the same business day',detail:'You will receive a confirmation email from CMS.'}, ], infoNote:'NPPES updates are free and same-day. Insurance billing depends on your NPI address being current.', selfStages:[`Waiting for ${to} license`,'Updated at nppes.cms.hhs.gov',`Confirmed — ${to} license shows`] }, ]; /* Add jurisprudence exam portal if required */ if(info.juris && toBoard.jurisUrl){ portals.splice(5,0,{ id:'juris', group:'self', iconType:'t', icon:'', name:`${to} Jurisprudence Exam`, sub:`Required by ${to} — taken online after application approval`, url: toBoard.jurisUrl, urlDisplay: toBoard.jurisUrl.replace('https://','').split('?')[0], steps:[ {action:`Access the exam through your ${to} Board account`,detail:`Log back in to your account after your application is approved. The exam link will appear in your dashboard.`}, {action:`Study the ${to} Dental Practice Act`,detail:`The exam is open-book. Download the ${to} Dental Practice Act and board rules from the board website before starting.`}, {action:'Complete the exam — most applicants pass on the first attempt',detail:'The exam typically consists of 50 multiple-choice questions. You need 75% or above to pass.'}, ], infoNote:`The ${to} Jurisprudence Exam is online and open-book. It covers ${to} dental law, ethics, and board regulations. Most applicants complete it in under 2 hours.`, selfStages:['Not started — waiting for application approval','Studying / preparing','Passed — score recorded'] }); } return portals; } function exam_portal_for(toState) { // States that require WREB var wrebStates = ['California','Oregon','Washington']; return wrebStates.includes(toState) ? EXAM_PORTALS.wreb : EXAM_PORTALS.nerb; } var PORTALS = []; var GROUPS=[ {key:'direct', label:'Must be sent directly — institution to board', note:'Direct send only'}, {key:'fingerprint', label:'Background check — book immediately', note:'Results sent to board'}, {key:'self', label:'You obtain and submit yourself', note:'Bring with application'}, {key:'post', label:'Complete after your new license is issued', note:'Post-license updates'}, ]; var openCredId=null; function isCredDone(p){ var s=APP.credState[p.id]; if(!s) return false; return p.stages?s.stages.every(st=>st.done):s.stageIdx===(p.selfStages||[]).length-1; } function isCredInProgress(p){ if(isCredDone(p)) return false; var s=APP.credState[p.id]; if(!s) return false; return p.stages?s.stages.some(st=>st.done):s.stageIdx>0; } function renderCreds(){ var html=''; GROUPS.forEach(g=>{ var ps=PORTALS.filter(p=>p.group===g.key); if(!ps.length) return; html+=`
${g.label}
${g.note}
`; ps.forEach(p=>{html+=renderCredCard(p);}); }); document.getElementById('credsList').innerHTML=html; document.getElementById('navCredBadge').textContent=PORTALS.filter(p=>!isCredDone(p)).length; } function renderCredCard(p){ var isOpen=openCredId===p.id; var isDone=isCredDone(p); var isProgress=isCredInProgress(p); var cardCls=isDone?'done':p.urgentNote?'urgent-card':isProgress?'':''; var [stsCls,stsLabel]=isDone?['phs-d','Complete']:p.urgentNote&&!isProgress?['phs-u','Book now']:isProgress?['phs-i','In progress']:p.group==='post'?['phs-p','After '+APP.to+' license']:['phs-p','Pending']; // Dots var dots=''; var s=APP.credState[p.id]; if(p.stages&&s){ p.stages.forEach((st,i)=>{ var dn=s.stages[i].done; var cur=!dn&&i===s.stages.findIndex(x=>!x.done); if(i>0) dots+=`
`; dots+=`
`; }); } else if(p.selfStages&&s){ p.selfStages.forEach((_,i)=>{ var dn=s.stageIdx>i,cur=s.stageIdx===i; if(i>0) dots+=`
`; dots+=`
`; }); } var bodyHtml=isOpen?renderCredBody(p):''; return`
${p.icon}
${p.name}
${p.sub}
${dots}
${stsLabel}
${bodyHtml}
`; } function renderCredBody(p){ var html=''; var isEmail=p.url.startsWith('mailto'); if(!isEmail){ var parts=p.urlDisplay.split('/'); html+=`
${parts[0]}${parts.length>1?'/'+parts.slice(1).join('/'):''}
`; } else { html+=`
${p.urlDisplay}
`; } if(p.urgentNote) html+=`
Act now: ${p.urgentNote}
`; if(p.directSend) html+=`
Direct send only. ${p.directSend}
`; if((p.steps&&p.steps.length)){ html+=`
What to do
`; p.steps.forEach((s,i)=>{html+=`
${i+1}
${s.action}
${s.detail}
`;}); html+=`
`; } if(p.infoNote) html+=`
Note: ${p.infoNote}
`; if(p.warnNote) html+=`
Important: ${p.warnNote}
`; html+=`
Track this credential
`; var s=APP.credState[p.id]; if(p.stages&&s){ html+=`
`; p.stages.forEach((stage,i)=>{ var st=s.stages[i]; var isDone=st.done,isActive=!isDone&&i===s.stages.findIndex(x=>!x.done); var cls=isDone?'ts-done':isActive?'ts-active':'ts-locked'; html+=`
${stage.label}
`; if(isDone){ html+=`
${st.date||'Complete'}${st.conf?` · Ref: ${st.conf}`:''}
`; } else if(isActive){ html+=`
`; } html+=`
`; }); html+=`
`; } else if(p.selfStages&&s){ html+=`
`; p.selfStages.forEach((lbl,i)=>{ var isCur=s.stageIdx===i,isDone=s.stageIdx>i; html+=``; }); html+=`
`; } var notes=(APP.credState[p.id]||{}).notes||''; html+=`
Notes
`; return html; } function toggleCred(id){ var wasOpen=openCredId===id; openCredId=wasOpen?null:id; renderCreds(); if(!wasOpen) setTimeout(()=>{document.getElementById('pc-'+id)&&scrollIntoView({behavior:'smooth',block:'nearest'})},50); } function markCredStage(pid,idx){ var s=APP.credState[pid]; var conf=(function(){var _oc3=document.getElementById(`tc-${pid}-${idx}`);return _oc3?_oc3.value.trim():''})()||''; var dateEl=document.getElementById(`td-${pid}-${idx}`); var date=(dateEl&&dateEl.value)?new Date(dateEl.value+'T12:00:00').toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'}):new Date().toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'}); s.stages[idx].done=true;s.stages[idx].conf=conf;s.stages[idx].date=date; var p=PORTALS.find(x=>x.id===pid); var allDone=s.stages.every(st=>st.done); showToast(allDone?`${p.name} — complete`:`Stage ${idx+1} marked: ${p.stages[idx].label}`); renderCreds(); document.getElementById('navCredBadge').textContent=PORTALS.filter(p=>!isCredDone(p)).length; } function undoCredStage(pid,idx){ var s=APP.credState[pid]; for(var i=idx;ix.id===pid); showToast((p.selfStages||[])[idx]||''); renderCreds(); } function saveCredNotes(pid,val){if(APP.credState[pid]) APP.credState[pid].notes=val;} function copyUrl(url,btnId){ if(navigator.clipboard) navigator.clipboard.writeText(url); else{var t=document.createElement('textarea');t.value=url;document.body.appendChild(t);t.select();document.execCommand('copy');document.body.removeChild(t);} var btn=document.getElementById(btnId); if(btn){var o=btn.textContent;btn.textContent='Copied';btn.classList.add('copied');setTimeout(()=>{btn.textContent=o;btn.classList.remove('copied')},2000);} showToast('URL copied to clipboard'); } function openUrl(url){ if(confirm(`Open in a new tab?\n\n${url}`)) window.open(url,'_blank'); } /* ══════════════════════════ PHOTO UPLOAD ══════════════════════════ */ // Check iframe embed setTimeout(()=>{ try{ var f=document.getElementById('photoFrame'); if(f&&(f.contentDocument&&f.contentDocument.body&&f.contentDocument.body.innerHTML)==='') document.getElementById('photoOverlay').style.display='flex'; }catch(e){document.getElementById('photoOverlay').style.display='flex';} },2500); function handlePhotoDrop(e){e.preventDefault();var f=e.dataTransfer.files[0];if(f)processPhoto(f);} function handlePhotoUpload(e){var f=e.target.files[0];if(f)processPhoto(f);} function processPhoto(file){ var r=new FileReader(); r.onload=function(e){ var img=new Image(); img.onload=function(){showPhotoResult(file,img);}; img.src=e.target.result; }; r.readAsDataURL(file); } function showPhotoResult(file,img){ var zone=document.getElementById('photoUploadZone'); zone.style.borderColor='var(--teal)';zone.style.background='var(--teal-pale)'; document.getElementById('photoUploadPrompt').style.display='none'; document.getElementById('photoUploadResult').style.display='block'; document.getElementById('photoUploadResult').innerHTML=`
${file.name}
${img.width} × ${img.height} px · ${(file.size/1024).toFixed(0)} KB
`; runPhotoValidation(file,img); } function runPhotoValidation(file,img){ var panel=document.getElementById('photoValidation'); panel.style.display='block'; var checks=[ {label:'File format (JPG or PNG)',pass:['image/jpeg','image/jpg','image/png'].includes(file.type),detail:file.type.replace('image/','').toUpperCase()}, {label:'File size under 5 MB',pass:file.size/1024/1024<=5,detail:`${(file.size/1024).toFixed(0)} KB`}, {label:'Minimum 600 × 600 pixels',pass:img.width>=600&&img.height>=600,detail:`${img.width} × ${img.height} px`}, {label:'Square aspect ratio (2 × 2 inches)',pass:Math.abs(img.width/img.height-1)<.08,detail:`Ratio ${(img.width/img.height).toFixed(2)}`}, ]; var anyFail=checks.some(c=>!c.pass); panel.innerHTML=`
Requirement checks
`+ checks.map(c=>`
${c.pass?'':''}
${c.label} ${c.detail}
`).join('')+ `
${anyFail?'Photo does not meet requirements':'Photo meets all requirements'}
${anyFail?'Please retake or reformat your photo and upload again.':'Your photo is ready to include with your Texas Board application.'}
`; var badge=document.getElementById('photoBadge'); badge.textContent=anyFail?'Requirements not met':'All checks passed'; badge.style.background=anyFail?'var(--red-lt)':'var(--green-lt)'; badge.style.color=anyFail?'var(--red)':'var(--green-mid)'; if(!anyFail) document.getElementById('photoMarkDone').style.display='block'; } function markPhotoDone(){ document.getElementById('photoMarkDone').textContent='Photo confirmed — ready to submit'; document.getElementById('photoMarkDone').style.background='var(--green)'; showToast('Passport photo marked as complete'); (function(){var _oc2=document.getElementById('nav-photo');if(_oc2)_oc2.classList.remove('active');})(); } /* ══════════════════════════ COMPLETION + CONFETTI ══════════════════════════ */ function showComp(title,sub){ document.getElementById('compTitle').innerHTML=title.replace('complete','complete').replace('Complete','complete'); document.getElementById('compSub').textContent=sub; document.getElementById('compOverlay').classList.add('show'); } function closeComp(){document.getElementById('compOverlay').classList.remove('show');} function confetti(){ var c=['#1a7f74','#23a99b','#16a364','#1c3252','#c97a0a']; for(var i=0;i<52;i++){ var p=document.createElement('div');p.className='conf'; var sz=5+Math.random()*7; p.style.cssText=`left:${Math.random()*100}vw;width:${sz}px;height:${sz*(1+Math.random()*.8)}px;background:${c[~~(Math.random()*c.length)]};border-radius:${Math.random()>.5?'50%':'2px'};animation-delay:${Math.random()*1.4}s;animation-duration:${2+Math.random()*1.5}s`; document.body.appendChild(p);setTimeout(()=>p.remove(),4500); } } /* ══════════════════════════ TOAST ══════════════════════════ */ var _tt; function showToast(msg){ var t=document.getElementById('toast'); t.textContent=msg;t.classList.add('show'); clearTimeout(_tt);_tt=setTimeout(()=>t.classList.remove('show'),2800); } /* ══════════════════════════════════════════ FULL CHECKLIST ══════════════════════════════════════════ */ var clFilterStatus = 'all'; var clFilterPhase = 'all'; var clOpenItems = new Set(); var PHASE_NAMES = { 'Phase 1': 'Gather your credentials', 'Phase 2': 'Submit your application', 'Phase 3': 'Complete state requirements', 'Phase 4': 'After your license is issued', }; function setClFilter(val, btn) { clFilterStatus = val; document.querySelectorAll('[data-filter]').forEach(b => b.classList.remove('active')); btn.classList.add('active'); renderChecklist(); } function setClPhase(val, btn) { clFilterPhase = val; document.querySelectorAll('[data-phase]').forEach(b => b.classList.remove('active')); btn.classList.add('active'); renderChecklist(); } function toggleClItem(id, event) { // If clicking the expand chevron, toggle expanded state; else toggle done if (event.target.closest('.cl-expand')) { clOpenItems.has(id) ? clOpenItems.delete(id) : clOpenItems.add(id); renderChecklist(); return; } // Toggle done APP.done.has(id) ? APP.done.delete(id) : (APP.done.add(id), checkPhaseComplete(id)); renderChecklist(); updateProgress(); setTimeout(() => { document.getElementById('rhFill').style.width = getPct() + '%'; }, 50); } function renderChecklist() { var container = document.getElementById('checklistFull'); if (!container) return; var phases = ['Phase 1','Phase 2','Phase 3','Phase 4']; var totalVisible = 0; var html = ''; phases.forEach(phase => { // Skip phase if phase filter active and doesn't match if (clFilterPhase !== 'all' && clFilterPhase !== phase) return; var tasks = APP.tasks.filter(t => t.ph === phase); if (!tasks.length) return; var doneTasks = tasks.filter(t => APP.done.has(t.id)); var urgentTasks = tasks.filter(t => t.urgent && !APP.done.has(t.id)); var phasePct = Math.round(doneTasks.length / tasks.length * 100); // Apply status filter var filtered = tasks.filter(t => { if (clFilterStatus === 'all') return true; if (clFilterStatus === 'done') return APP.done.has(t.id); if (clFilterStatus === 'urgent') return t.urgent && !APP.done.has(t.id); if (clFilterStatus === 'todo') return !APP.done.has(t.id); return true; }); if (!filtered.length) return; totalVisible += filtered.length; html += `
${phase} — ${PHASE_NAMES[phase]||''}
${doneTasks.length} of ${tasks.length}
`; filtered.forEach(t => { var isDone = APP.done.has(t.id); var isUrgent = t.urgent && !isDone; var isOpen = clOpenItems.has(t.id); var tag = isDone ? 'Complete' : isUrgent ? 'Action required' : 'Pending'; html += `
${t.title}
${tag}
${t.why}
`; }); html += `
`; }); if (!totalVisible) { html = `
No steps match this filter.
`; } container.innerHTML = html; // Update summary badges var total = APP.tasks.length; var done = APP.done.size; var pending = APP.tasks.filter(t => !APP.done.has(t.id)).length; var urgent = APP.tasks.filter(t => t.urgent && !APP.done.has(t.id)).length; { var _el = document.getElementById('clSummary'); if (_el) _el.textContent = done + ' of ' + total + ' complete'; } { var _el2 = document.getElementById('clFilterCount'); if (_el2) _el2.textContent = totalVisible + ' step' + (totalVisible !== 1 ? 's' : '') + ' shown'; } { var _el3 = document.getElementById('navClBadge'); if (_el3) _el3.textContent = pending; } } /* ══════════════════════════ SUPABASE AUTH ══════════════════════════ */ const SUPABASE_URL = 'https://cdeolkktvnjsznejenfn.supabase.co'; const SUPABASE_KEY = 'sb_publishable_xx_VkpA7o3BQ8zKlMbLqgw_FIQw9R6y'; const sb = supabase.createClient(SUPABASE_URL, SUPABASE_KEY); let currentUser = null; (async function(){ const { data: { session } } = await sb.auth.getSession(); if (session) { currentUser = session.user; await loadProfileAndLaunch(currentUser.id); } })(); async function loadProfileAndLaunch(userId) { const { data } = await sb.from('profiles').select('*').eq('id', userId).single(); if (data) { APP.name = 'Dr. ' + (data.full_name || 'User'); APP.specialty = data.specialty || 'General Dentist'; APP.from = data.from_state || 'New York'; APP.to = data.to_state || 'Texas'; } launchAppFromProfile(); } function launchAppFromProfile() { const info = STATE_DB[APP.to] || {fee:200,wkMin:10,wkMax:14,nerb:true,juris:true,bg:'FBI + TX DPS'}; APP.tasks = buildTasks(APP.from, APP.to, info); APP.timeline = buildTimeline(APP.from, APP.to, info); APP.done = new Set(); PORTALS = buildPortals(APP.from, APP.to, info); PORTALS.forEach(p => { APP.credState[p.id] = p.stages ? {stages: p.stages.map(() => ({done:false, date:'', conf:''})), notes:''} : {stageIdx: 0, notes:''}; }); const initials = APP.name.replace(/Dr\.?\s*/i,'').trim().split(' ').map(w=>w[0]||'').join('').toUpperCase().slice(0,2)||'DR'; document.getElementById('sbAv').textContent = initials; document.getElementById('sbName').textContent = APP.name; document.getElementById('sbRole').textContent = APP.specialty; document.getElementById('sbFrom').textContent = APP.from.split(' ').map(w=>w[0]).join('').toUpperCase().slice(0,2); document.getElementById('sbTo').textContent = APP.to.split(' ').map(w=>w[0]).join('').toUpperCase().slice(0,2); document.getElementById('tbSub').textContent = APP.from + ' → ' + APP.to; document.getElementById('fbTitle').innerHTML = '' + APP.to + ' requirements'; document.getElementById('fbFee').textContent = '$' + info.fee; document.getElementById('fbNerb').textContent = info.nerb ? 'Yes' : 'No — WREB required'; document.getElementById('fbNerb').className = 'fb-v ' + (info.nerb ? 'ok' : 'warn'); document.getElementById('fbJuris').textContent = info.juris ? 'Required' : 'Not required'; document.getElementById('fbBg').textContent = info.bg; document.getElementById('fbTimeline').textContent = info.wkMin + '–' + info.wkMax + ' weeks'; document.getElementById('rhRoute').innerHTML = APP.from + ' ' + APP.to; document.getElementById('rhMeta').textContent = APP.specialty + ' · $' + info.fee + ' fee · ' + info.wkMin + '–' + info.wkMax + ' week estimate'; document.getElementById('credsIntro').textContent = 'Every portal you need for your ' + APP.from + ' → ' + APP.to + ' license transfer.'; const d = new Date(); d.setDate(d.getDate() + Math.round((info.wkMin + info.wkMax) / 2) * 7); document.getElementById('scDate').textContent = d.toLocaleDateString('en-US', {month:'short', year:'numeric'}); document.getElementById('scRange').textContent = info.wkMin + '–' + info.wkMax + ' wk range'; renderDashboard(); renderCreds(); go('pg-landing'); } async function launchApp() { const firstName = document.getElementById('inp-first').value.trim() || 'Jane'; const lastName = document.getElementById('inp-last').value.trim() || 'Smith'; const email = document.getElementById('inp-email').value.trim(); const password = document.getElementById('inp-pw').value; const specialty = document.getElementById('inp-spec').value || 'General Dentist'; const fromState = document.getElementById('inp-from').value || 'New York'; const toState = document.getElementById('inp-to').value || 'Texas'; const btn = document.getElementById('sc-btn3'); btn.classList.add('loading'); btn.disabled = true; try { const { data, error } = await sb.auth.signUp({ email, password }); if (error) throw error; currentUser = data.user; await sb.from('profiles').upsert({ id: data.user.id, full_name: firstName + ' ' + lastName, specialty: specialty, from_state: fromState, to_state: toState, }); APP.name = 'Dr. ' + firstName + ' ' + lastName; APP.specialty = specialty; APP.from = fromState; APP.to = toState; document.getElementById('sc-step3').style.display = 'none'; document.getElementById('sc-success').classList.add('show'); document.getElementById('ss-sub-text').textContent = `Welcome, ${firstName}. Your ${fromState} → ${toState} roadmap is ready.`; } catch(err) { btn.classList.remove('loading'); btn.disabled = false; showErr(err.message || 'Signup failed. Please try again.'); } } async function loginExistingUser() { const email = document.getElementById('inp-email').value.trim(); const password = document.getElementById('inp-pw').value; if (!email || !password) { showErr('Enter your email and password to log in.'); return; } try { const { data, error } = await sb.auth.signInWithPassword({ email, password }); if (error) throw error; currentUser = data.user; await loadProfileAndLaunch(currentUser.id); } catch(err) { showErr(err.message || 'Login failed.'); } } async function sbLogout() { await sb.auth.signOut(); currentUser = null; sbLogout(); } (function(){ /* ══ MOBILE MENU ══ */ function openMobMenu() { document.querySelector('.sidebar').classList.add('mob-open'); document.getElementById('mobOverlay').classList.add('show'); document.body.style.overflow = 'hidden'; } function closeMobMenu() { document.querySelector('.sidebar').classList.remove('mob-open'); document.getElementById('mobOverlay').classList.remove('show'); document.body.style.overflow = ''; } function bnNav(secId, btn) { document.querySelectorAll('.bn-item').forEach(function(b){b.classList.remove('active');}); btn.classList.add('active'); var el = document.getElementById(secId); if (el) el.scrollIntoView({behavior:'smooth', block:'start'}); } /* ═══════════════════════════════════════ PAGE-SPECIFIC JAVASCRIPT ═══════════════════════════════════════ */ /* ── TIMELINE JS ── */ /* ══════════════════════════════════════ STATE DATABASE ══════════════════════════════════════ */ var TL_ALL_STATES = ["Alabama","Alaska","Arizona","Arkansas","California","Colorado","Connecticut","Delaware","Florida","Georgia","Hawaii","Idaho","Illinois","Indiana","Iowa","Kansas","Kentucky","Louisiana","Maine","Maryland","Massachusetts","Michigan","Minnesota","Mississippi","Missouri","Montana","Nebraska","Nevada","New Hampshire","New Jersey","New Mexico","New York","North Carolina","North Dakota","Ohio","Oklahoma","Oregon","Pennsylvania","Rhode Island","South Carolina","South Dakota","Tennessee","Texas","Utah","Vermont","Virginia","Washington","West Virginia","Wisconsin","Wyoming"]; var TL_STATE_DB = { "Alabama": {fee:200,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"SBI"}, "Alaska": {fee:600,wkMin:12,wkMax:16,nerb:true, juris:false,bg:"State DPS"}, "Arizona": {fee:250,wkMin:8, wkMax:12,nerb:true, juris:true, bg:"DPS"}, "Arkansas": {fee:300,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"State"}, "California": {fee:400,wkMin:20,wkMax:26,nerb:false,juris:true, bg:"DOJ + FBI"}, "Colorado": {fee:150,wkMin:6, wkMax:10,nerb:true, juris:false,bg:"CBI"}, "Connecticut": {fee:565,wkMin:16,wkMax:20,nerb:true, juris:false,bg:"State"}, "Delaware": {fee:200,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"State"}, "Florida": {fee:325,wkMin:12,wkMax:18,nerb:true, juris:true, bg:"FDLE"}, "Georgia": {fee:275,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"GBI"}, "Hawaii": {fee:350,wkMin:14,wkMax:18,nerb:true, juris:false,bg:"State"}, "Idaho": {fee:200,wkMin:8, wkMax:12,nerb:true, juris:false,bg:"State"}, "Illinois": {fee:500,wkMin:14,wkMax:18,nerb:true, juris:false,bg:"ISP"}, "Indiana": {fee:225,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"State"}, "Iowa": {fee:275,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"State"}, "Kansas": {fee:200,wkMin:8, wkMax:12,nerb:true, juris:false,bg:"KBI"}, "Kentucky": {fee:350,wkMin:12,wkMax:16,nerb:true, juris:true, bg:"State"}, "Louisiana": {fee:450,wkMin:12,wkMax:16,nerb:true, juris:true, bg:"State"}, "Maine": {fee:300,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"State"}, "Maryland": {fee:350,wkMin:12,wkMax:16,nerb:true, juris:false,bg:"State"}, "Massachusetts": {fee:360,wkMin:14,wkMax:18,nerb:true, juris:false,bg:"State"}, "Michigan": {fee:229,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"MSP"}, "Minnesota": {fee:350,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"BCA"}, "Mississippi": {fee:300,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"State"}, "Missouri": {fee:275,wkMin:8, wkMax:12,nerb:true, juris:false,bg:"State"}, "Montana": {fee:200,wkMin:8, wkMax:12,nerb:true, juris:false,bg:"State"}, "Nebraska": {fee:225,wkMin:8, wkMax:12,nerb:true, juris:false,bg:"State"}, "Nevada": {fee:400,wkMin:12,wkMax:16,nerb:true, juris:true, bg:"State"}, "New Hampshire": {fee:275,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"State"}, "New Jersey": {fee:450,wkMin:14,wkMax:20,nerb:true, juris:false,bg:"State"}, "New Mexico": {fee:200,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"State"}, "New York": {fee:370,wkMin:16,wkMax:22,nerb:true, juris:false,bg:"DCJS"}, "North Carolina":{fee:375,wkMin:12,wkMax:16,nerb:true, juris:true, bg:"State"}, "North Dakota": {fee:175,wkMin:6, wkMax:10,nerb:true, juris:false,bg:"State"}, "Ohio": {fee:300,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"BCI + FBI"}, "Oklahoma": {fee:350,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"State"}, "Oregon": {fee:487,wkMin:14,wkMax:18,nerb:false,juris:true, bg:"State"}, "Pennsylvania": {fee:350,wkMin:12,wkMax:18,nerb:true, juris:false,bg:"State"}, "Rhode Island": {fee:375,wkMin:12,wkMax:16,nerb:true, juris:false,bg:"State"}, "South Carolina":{fee:300,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"SLED"}, "South Dakota": {fee:200,wkMin:8, wkMax:12,nerb:true, juris:false,bg:"State"}, "Tennessee": {fee:350,wkMin:10,wkMax:16,nerb:true, juris:true, bg:"TBI"}, "Texas": {fee:200,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"FBI + TX DPS"}, "Utah": {fee:175,wkMin:8, wkMax:12,nerb:true, juris:true, bg:"State"}, "Vermont": {fee:275,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"State"}, "Virginia": {fee:302,wkMin:12,wkMax:16,nerb:true, juris:false,bg:"State"}, "Washington": {fee:400,wkMin:14,wkMax:18,nerb:false,juris:true, bg:"WSP"}, "West Virginia": {fee:250,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"State"}, "Wisconsin": {fee:300,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"State"}, "Wyoming": {fee:150,wkMin:6, wkMax:10,nerb:true, juris:false,bg:"State"}, }; /* ══════════════════════════════════════ APP STATE ══════════════════════════════════════ */ var FROM = 'New York', TO = 'Texas'; var taskDone = new Set(); var openPhase = 'phase1'; // default open /* ══════════════════════════════════════ TIMELINE BUILDER ══════════════════════════════════════ */ function buildTimeline(from, to) { var info = STATE_DB[to] || {fee:200,wkMin:10,wkMax:14,nerb:true,juris:true,bg:'State'}; var avg = Math.round((info.wkMin + info.wkMax) / 2); // Phase week boundaries var p1End = 2; var p2End = Math.round(avg * 0.35); var p3End = Math.round(avg * 0.75); var p4End = avg; return [ { id:'phase1', label:'Phase 1', name:'Gather credentials', wkStart:1, wkEnd:p1End, status:'done', milestones:[ { id:'m1', wk:1, title:'Request license verification from '+from, desc:'Contact the '+from+' dental board to request a certified verification sent directly to the '+to+' Board.', status:'done', tasks:[ {id:'t1a',label:'Log in to '+from+' licensing portal',done:true}, {id:'t1b',label:'Submit verification request',done:true}, {id:'t1c',label:'Save confirmation number',done:true}, ], duration:'5–10 business days for processing' }, { id:'m2', wk:1, title:'Order '+(info.nerb?'NERB/CRDTS':'WREB')+' exam score transcript', desc:'Request your clinical exam scores be sent directly to the '+to+' Board through the '+(info.nerb?'NERB or CRDTS':'WREB')+' portal.', status:'done', tasks:[ {id:'t2a',label:'Log in to '+(info.nerb?'nerb.org':'wreb.org'),done:true}, {id:'t2b',label:'Select '+to+' Board as recipient',done:true}, {id:'t2c',label:'Pay transcript fee and save order number',done:true}, ], duration:'3–5 business days for delivery' }, { id:'m3', wk:2, title:'Order official dental school transcript', desc:'Request your dental school send an official transcript directly to the '+to+' Board through Parchment, NSC, or e-Scrip-Safe.', status:'done', tasks:[ {id:'t3a',label:'Check which service your school uses',done:true}, {id:'t3b',label:'Order transcript via Parchment / NSC / e-Scrip-Safe',done:true}, {id:'t3c',label:'Save order confirmation number',done:true}, ], duration:'5–15 business days depending on school' }, { id:'m4', wk:2, title:'Obtain CPR/BLS certification and passport photo', desc:'Complete an in-person AHA BLS class and take a passport-style photograph for your application.', status:'done', tasks:[ {id:'t4a',label:'Book and complete in-person BLS class',done:true}, {id:'t4b',label:'Take 2×2 passport photo (white background)',done:true}, ], duration:'1–3 days' }, ] }, { id:'phase2', label:'Phase 2', name:'Submit application', wkStart:p1End+1, wkEnd:p2End, status:'current', milestones:[ { id:'m5', wk:p1End+1, title:'Complete and notarize the '+to+' Board application', desc:'Download the out-of-state application from the '+to+' Board website. Complete every section and have it notarized.', status:'current', tasks:[ {id:'t5a',label:'Create account on '+to+' Board portal',done:true}, {id:'t5b',label:'Download and print application packet',done:true}, {id:'t5c',label:'Complete all sections',done:false}, {id:'t5d',label:'Get application notarized',done:false}, ], duration:'1–2 days to complete' }, { id:'m6', wk:p1End+2, title:'Book '+info.bg+' fingerprinting appointment', desc:'Book your fingerprint appointment immediately through IdentoGo. Results take 3–4 weeks — this is the most common cause of delays.', status:'pending', tasks:[ {id:'t6a',label:'Book appointment on identogo.com',done:false}, {id:'t6b',label:'Attend fingerprinting appointment',done:false}, ], duration:'Results take 3–4 weeks to arrive at board' }, { id:'m7', wk:p2End, title:'Mail complete application with $'+info.fee+' fee', desc:'Send your completed, notarized application with money order to the '+to+' Board. Use regular mail — no certified mail with signature required.', status:'pending', tasks:[ {id:'t7a',label:'Assemble all documents',done:false}, {id:'t7b',label:'Purchase $'+info.fee+' money order',done:false}, {id:'t7c',label:'Mail application package',done:false}, ], duration:'Allow 1–2 days for mailing' }, ] }, { id:'phase3', label:'Phase 3', name:'Board review', wkStart:p2End+1, wkEnd:p3End, status:'pending', milestones:[ { id:'m8', wk:p2End+1, title:'Board review period begins', desc:'The '+to+' Board reviews your application alongside incoming background check results and directly-sent documents.', status:'pending', tasks:[ {id:'t8a',label:'Confirm '+to+' Board received application',done:false}, {id:'t8b',label:'Confirm background check results received',done:false}, {id:'t8c',label:'Confirm all direct-send documents received',done:false}, ], duration:Math.round((p3End-p2End))+ ' weeks of board processing' }, ...(info.juris ? [{ id:'m9', wk:Math.round((p2End+p3End)/2), title:'Complete '+to+' Jurisprudence Exam', desc:'Log in to your '+to+' Board account and complete the jurisprudence exam. Online, open-book, covers '+to+' dental law and ethics.', status:'pending', tasks:[ {id:'t9a',label:'Access exam through '+to+' Board account',done:false}, {id:'t9b',label:'Study '+to+' Dental Practice Act',done:false}, {id:'t9c',label:'Complete and pass the exam (75%+)',done:false}, ], duration:'1–2 hours to complete' }] : []), ] }, { id:'phase4', label:'Phase 4', name:'License issued & post-license', wkStart:p3End+1, wkEnd:p4End+2, status:'pending', milestones:[ { id:'m10', wk:p3End+1, title:to+' dental license issued', desc:'Your '+to+' dental license is issued. Verify your license number, name, and expiration date on the '+to+' Board public lookup portal.', status:'pending', tasks:[ {id:'t10a',label:'Verify license on '+to+' Board lookup portal',done:false}, {id:'t10b',label:'Confirm name and expiration date are correct',done:false}, ], duration:'License arrives by mail or is available digitally' }, { id:'m11', wk:p4End, title:'Update DEA registration and NPI record', desc:'Update your DEA address to your '+to+' practice address at DEA.gov, and update your NPI record in NPPES with your '+to+' dental license.', status:'pending', tasks:[ {id:'t11a',label:'Update DEA registration address at DEA.gov',done:false}, {id:'t11b',label:'Update NPI record at nppes.cms.hhs.gov',done:false}, {id:'t11c',label:'Notify malpractice insurer of new license',done:false}, ], duration:'DEA and NPI updates process same day' }, ] }, ]; } /* ══════════════════════════════════════ RENDER ══════════════════════════════════════ */ function tlRender() { var info = STATE_DB[TO] || {fee:200,wkMin:10,wkMax:14,nerb:true,juris:true,bg:'State'}; var timeline = buildTimeline(FROM, TO); var avg = Math.round((info.wkMin + info.wkMax) / 2); var estDate = (() => { var d = new Date(); d.setDate(d.getDate() + avg * 7); return d.toLocaleDateString('en-US',{month:'long',year:'numeric'}); })(); // Count tasks var allTasks = timeline.flatMap(p => p.milestones.flatMap(m => m.tasks)); var doneTasks = allTasks.filter(t => taskDone.has(t.id) || t.done).length; var pct = Math.round(doneTasks / allTasks.length * 100); // Update sidebar document.getElementById('sbFrom').textContent = FROM.split(' ').map(w=>w[0]).join('').toUpperCase().slice(0,2); document.getElementById('sbTo').textContent = TO.split(' ').map(w=>w[0]).join('').toUpperCase().slice(0,2); document.getElementById('tbSub').textContent = FROM+' → '+TO; var html = `
${FROM} → ${TO}
Your licensing timeline
A week-by-week view of everything that needs to happen before your ${TO} license is in hand. Check off tasks as you go — your progress updates in real time.
Change states
Personalized timeline
${FROM} ${TO}
General Dentist · $${info.fee} application fee · ${info.bg} background check
${estDate}
Estimated license date
${info.wkMin}–${info.wkMax} weeks total
Overall progress
${pct}%
${timeline.map(p=>`
${p.label}
`).join('')}
`; // Render phases timeline.forEach(phase => { var phaseTasks = phase.milestones.flatMap(m => m.tasks); var phaseDone = phaseTasks.filter(t => taskDone.has(t.id) || t.done).length; var isOpen = openPhase === phase.id; var phaseStatus = phase.status; if (phaseDone === phaseTasks.length) phaseStatus = 'done'; else if (phaseDone > 0) phaseStatus = 'current'; var [stsCls, stsLabel] = phaseStatus === 'done' ? ['tphs-done', 'Complete'] : phaseStatus === 'current' ? ['tphs-active', 'In progress'] : ['tphs-pending', 'Upcoming']; html += `
${phase.label} ${phase.name.replace(/(credentials|application|review|license)/i, '$1')} Weeks ${phase.wkStart}–${phase.wkEnd} ${stsLabel}
`; phase.milestones.forEach(m => { var mTasks = m.tasks; var mDone = mTasks.filter(t => taskDone.has(t.id) || t.done).length; var isAllDone= mDone === mTasks.length; var isCurrent= !isAllDone && mTasks.some(t => t.done || taskDone.has(t.id)); var mStatus = isAllDone ? 'done' : isCurrent ? 'current' : m.status; var dotCls = mStatus === 'done' ? 'done' : mStatus === 'current' ? 'current' : 'pending'; var rowCls = mStatus === 'done' ? 'tl-done' : mStatus === 'current' ? 'tl-current' : ''; html += `
Wk ${m.wk}
Week ${m.wk}
${mStatus==='done'?'Complete':mStatus==='current'?'Current':''}
${m.title}
${m.desc}
${m.tasks.map(t => { var isDone = t.done || taskDone.has(t.id); return `
${t.label}
`; }).join('')}
${m.duration}
`; }); html += `
`; }); html += `
At a glance
${timeline.map(p => { var phaseTasks = p.milestones.flatMap(m => m.tasks); var phaseDone = phaseTasks.filter(t => taskDone.has(t.id) || t.done).length; var isD = phaseDone === phaseTasks.length; var isC = !isD && phaseDone > 0; return `
Wks ${p.wkStart}–${p.wkEnd}
${p.name}
`; }).join('')}
Key dates
Today${new Date().toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'})}
Application deadline${getWeekDate(info.wkMin)}
Background check by${getWeekDate(2)}
Board review ends${getWeekDate(Math.round(info.wkMax*.75))}
Est. license date${estDate}
Destination state
${TO} requirements
Application fee$${info.fee}
Accepts NERB${info.nerb?'Yes':'No — WREB only'}
Jurisprudence exam${info.juris?'Required':'Not required'}
Background check${info.bg}
Processing time${info.wkMin}–${info.wkMax} weeks
`; var tlEl = document.getElementById('tlMainContent') || (document.getElementById('tlMainContent') || document.getElementById('mainContent')); if (tlEl) tlEl.innerHTML = html; } function getWeekDate(weeksFromNow) { var d = new Date(); d.setDate(d.getDate() + weeksFromNow * 7); return d.toLocaleDateString('en-US', {month:'short', day:'numeric'}); } /* ══════════════════════════════════════ INTERACTIONS ══════════════════════════════════════ */ function togglePhase(id) { openPhase = openPhase === id ? null : id; tlRender(); setTimeout(() => { document.getElementById('pb-'+id)&&scrollIntoView({behavior:'smooth', block:'nearest'}); }, 50); } function toggleTask(taskId) { taskDone.has(taskId) ? taskDone.delete(taskId) : taskDone.add(taskId); tlRender(); } function updateRoute() { var f = document.getElementById('selFrom')&&value; var t = document.getElementById('selTo')&&value; if (!f || !t || f === t) { showToast('Please select two different states'); return; } FROM = f; TO = t; openPhase = 'phase1'; tlRender(); showToast('Timeline updated for '+FROM+' → '+TO); } _tt; function showToast(msg) { var t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); clearTimeout(_tt); _tt = setTimeout(() => t.classList.remove('show'), 2600); } // tlRender() called by tlInitPage() when panel opens /* ── COMPARE JS ── */ /* ══════════════════════════════════════ DATA ══════════════════════════════════════ */ var CMP_ALL_STATES = ["Alabama","Alaska","Arizona","Arkansas","California","Colorado","Connecticut","Delaware","Florida","Georgia","Hawaii","Idaho","Illinois","Indiana","Iowa","Kansas","Kentucky","Louisiana","Maine","Maryland","Massachusetts","Michigan","Minnesota","Mississippi","Missouri","Montana","Nebraska","Nevada","New Hampshire","New Jersey","New Mexico","New York","North Carolina","North Dakota","Ohio","Oklahoma","Oregon","Pennsylvania","Rhode Island","South Carolina","South Dakota","Tennessee","Texas","Utah","Vermont","Virginia","Washington","West Virginia","Wisconsin","Wyoming"]; var CMP_DB = { "Alabama": {fee:200,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"SBI", bgAgencies:1}, "Alaska": {fee:600,wkMin:12,wkMax:16,nerb:true, juris:false,bg:"State DPS", bgAgencies:1}, "Arizona": {fee:250,wkMin:8, wkMax:12,nerb:true, juris:true, bg:"DPS", bgAgencies:1}, "Arkansas": {fee:300,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"State", bgAgencies:1}, "California": {fee:400,wkMin:20,wkMax:26,nerb:false,juris:true, bg:"DOJ + FBI", bgAgencies:2}, "Colorado": {fee:150,wkMin:6, wkMax:10,nerb:true, juris:false,bg:"CBI", bgAgencies:1}, "Connecticut": {fee:565,wkMin:16,wkMax:20,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Delaware": {fee:200,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Florida": {fee:325,wkMin:12,wkMax:18,nerb:true, juris:true, bg:"FDLE", bgAgencies:1}, "Georgia": {fee:275,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"GBI", bgAgencies:1}, "Hawaii": {fee:350,wkMin:14,wkMax:18,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Idaho": {fee:200,wkMin:8, wkMax:12,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Illinois": {fee:500,wkMin:14,wkMax:18,nerb:true, juris:false,bg:"ISP", bgAgencies:1}, "Indiana": {fee:225,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Iowa": {fee:275,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"State", bgAgencies:1}, "Kansas": {fee:200,wkMin:8, wkMax:12,nerb:true, juris:false,bg:"KBI", bgAgencies:1}, "Kentucky": {fee:350,wkMin:12,wkMax:16,nerb:true, juris:true, bg:"State", bgAgencies:1}, "Louisiana": {fee:450,wkMin:12,wkMax:16,nerb:true, juris:true, bg:"State", bgAgencies:1}, "Maine": {fee:300,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Maryland": {fee:350,wkMin:12,wkMax:16,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Massachusetts": {fee:360,wkMin:14,wkMax:18,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Michigan": {fee:229,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"MSP", bgAgencies:1}, "Minnesota": {fee:350,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"BCA", bgAgencies:1}, "Mississippi": {fee:300,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"State", bgAgencies:1}, "Missouri": {fee:275,wkMin:8, wkMax:12,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Montana": {fee:200,wkMin:8, wkMax:12,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Nebraska": {fee:225,wkMin:8, wkMax:12,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Nevada": {fee:400,wkMin:12,wkMax:16,nerb:true, juris:true, bg:"State", bgAgencies:1}, "New Hampshire": {fee:275,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"State", bgAgencies:1}, "New Jersey": {fee:450,wkMin:14,wkMax:20,nerb:true, juris:false,bg:"State", bgAgencies:1}, "New Mexico": {fee:200,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"State", bgAgencies:1}, "New York": {fee:370,wkMin:16,wkMax:22,nerb:true, juris:false,bg:"DCJS", bgAgencies:1}, "North Carolina":{fee:375,wkMin:12,wkMax:16,nerb:true, juris:true, bg:"State", bgAgencies:1}, "North Dakota": {fee:175,wkMin:6, wkMax:10,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Ohio": {fee:300,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"BCI + FBI", bgAgencies:2}, "Oklahoma": {fee:350,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"State", bgAgencies:1}, "Oregon": {fee:487,wkMin:14,wkMax:18,nerb:false,juris:true, bg:"State", bgAgencies:1}, "Pennsylvania": {fee:350,wkMin:12,wkMax:18,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Rhode Island": {fee:375,wkMin:12,wkMax:16,nerb:true, juris:false,bg:"State", bgAgencies:1}, "South Carolina":{fee:300,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"SLED", bgAgencies:1}, "South Dakota": {fee:200,wkMin:8, wkMax:12,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Tennessee": {fee:350,wkMin:10,wkMax:16,nerb:true, juris:true, bg:"TBI", bgAgencies:1}, "Texas": {fee:200,wkMin:10,wkMax:14,nerb:true, juris:true, bg:"FBI + TX DPS", bgAgencies:2}, "Utah": {fee:175,wkMin:8, wkMax:12,nerb:true, juris:true, bg:"State", bgAgencies:1}, "Vermont": {fee:275,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Virginia": {fee:302,wkMin:12,wkMax:16,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Washington": {fee:400,wkMin:14,wkMax:18,nerb:false,juris:true, bg:"WSP", bgAgencies:1}, "West Virginia": {fee:250,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Wisconsin": {fee:300,wkMin:10,wkMax:14,nerb:true, juris:false,bg:"State", bgAgencies:1}, "Wyoming": {fee:150,wkMin:6, wkMax:10,nerb:true, juris:false,bg:"State", bgAgencies:1}, }; /* Complexity score 1–10 */ function complexity(s) { var d = DB[s]; if (!d) return 5; var score = 0; score += Math.round((d.wkMax / 26) * 4); // timeline weight (max 4) score += Math.round((d.fee / 600) * 2); // fee weight (max 2) score += d.nerb ? 0 : 2; // WREB-only penalty score += d.juris ? 1 : 0; // juris exam score += d.bgAgencies > 1 ? 1 : 0; // dual background check return Math.min(10, Math.max(1, score)); } function complexityLabel(n) { if (n <= 3) return ['Easy','badge-green']; if (n <= 6) return ['Moderate','badge-amber']; return ['Complex','badge-red']; } function complexityColor(n) { if (n <= 3) return 'var(--green)'; if (n <= 6) return 'var(--amber)'; return 'var(--red)'; } /* ══════════════════════════════════════ INIT SELECTS ══════════════════════════════════════ */ ['selA','selB'].forEach((id,i) => { var el = document.getElementById(id); ALL_STATES.forEach(s => { var o = document.createElement('option'); o.value = s; o.textContent = s; el.appendChild(o); }); el.value = i===0 ? 'Texas' : 'California'; }); /* ══════════════════════════════════════ COMPARE ══════════════════════════════════════ */ function runCompare() { var sA = document.getElementById('cmp-selA').value; var sB = document.getElementById('cmp-selB').value; if (sA === sB) { document.getElementById('cmpCompareOut').innerHTML = `
Please select two different states to compare.
`; return; } document.getElementById('tbSub').textContent = sA + ' vs ' + sB; var a = DB[sA], b = DB[sB]; var cA = complexity(sA), cB = complexity(sB); var avgA = Math.round((a.wkMin+a.wkMax)/2), avgB = Math.round((b.wkMin+b.wkMax)/2); // Overall winner = lower complexity var overallWinner = cA < cB ? 'a' : cB < cA ? 'b' : null; var [clA] = complexityLabel(cA), [clB] = complexityLabel(cB); // Comparison rows config var rows = [ {section:'Fees & Timeline'}, { label:'Application Fee', sub:'Non-refundable', valA:`$${a.fee}`, valB:`$${b.fee}`, winA: a.fee < b.fee, winB: b.fee < a.fee, subA: '', subB: '', }, { label:'Min. Processing', sub:'Fastest scenario', valA:`${a.wkMin} weeks`, valB:`${b.wkMin} weeks`, winA: a.wkMin < b.wkMin, winB: b.wkMin < a.wkMin, }, { label:'Max. Processing', sub:'Slowest scenario', valA:`${a.wkMax} weeks`, valB:`${b.wkMax} weeks`, winA: a.wkMax < b.wkMax, winB: b.wkMax < a.wkMax, }, { label:'Avg. Timeline', sub:'Typical experience', valA:`${avgA} weeks`, valB:`${avgB} weeks`, winA: avgA < avgB, winB: avgB < avgA, }, {section:'Exam Requirements'}, { label:'Accepts NERB / CRDTS', sub:'Clinical exam scores', badgeA: a.nerb ? ['Yes','badge-green'] : ['No — WREB only','badge-red'], badgeB: b.nerb ? ['Yes','badge-green'] : ['No — WREB only','badge-red'], winA: a.nerb && !b.nerb, winB: b.nerb && !a.nerb, }, { label:'Jurisprudence Exam', sub:'State dental law exam', badgeA: a.juris ? ['Required','badge-amber'] : ['Not required','badge-green'], badgeB: b.juris ? ['Required','badge-amber'] : ['Not required','badge-green'], winA: !a.juris && b.juris, winB: !b.juris && a.juris, }, {section:'Background Check'}, { label:'Background Check Agency', sub:'Fingerprinting required through', valA: a.bg, valB: b.bg, winA: a.bgAgencies < b.bgAgencies, winB: b.bgAgencies < a.bgAgencies, }, { label:'Number of Checks', sub:'Separate fingerprint submissions', badgeA: a.bgAgencies > 1 ? [`${a.bgAgencies} checks`,'badge-amber'] : ['1 check','badge-green'], badgeB: b.bgAgencies > 1 ? [`${b.bgAgencies} checks`,'badge-amber'] : ['1 check','badge-green'], winA: a.bgAgencies < b.bgAgencies, winB: b.bgAgencies < a.bgAgencies, }, {section:'Overall Complexity'}, { label:'Complexity Score', sub:'Based on timeline, fees, and requirements', isComplexity: true, valA: cA+'/10', valB: cB+'/10', cA, cB, badgeA: complexityLabel(cA), badgeB: complexityLabel(cB), winA: cA < cB, winB: cB < cA, }, ]; // Build table HTML var tableHtml = `
Criteria
${sA}
Complexity: ${clA}
${overallWinner==='a'?'
Easier to license in
':''}
${sB}
Complexity: ${clB}
${overallWinner==='b'?'
Easier to license in
':''}
`; rows.forEach(row => { if (row.section) { tableHtml += `
${row.section}
`; return; } var winIcon = `
`; var cellA = row.badgeA ? `${row.badgeA[0]}${row.winA?winIcon:''}` : row.isComplexity ? `
${row.valA}
${row.winA?winIcon:''}` : `
${row.valA}
${row.subA?`
${row.subA}
`:''}
${row.winA?winIcon:''}`; var cellB = row.badgeB ? `${row.badgeB[0]}${row.winB?winIcon:''}` : row.isComplexity ? `
${row.valB}
${row.winB?winIcon:''}` : `
${row.valB}
${row.subB?`
${row.subB}
`:''}
${row.winB?winIcon:''}`; tableHtml += `
${row.label}
${row.sub}
${cellA}
${cellB}
`; }); tableHtml += `
`; // Score cards function scoreCard(state, data, cx, col) { var [cxLbl] = complexityLabel(cx); var feeScore = Math.round((1 - data.fee/600) * 100); var timeScore = Math.round((1 - (data.wkMax/26)) * 100); var reqScore = Math.round(((data.nerb?1:0) + (data.juris?0:1) + (data.bgAgencies===1?1:0)) / 3 * 100); var summary = cx<=3 ? `${state} is one of the more straightforward states for dental licensure — lower fees, faster processing, and fewer extra requirements.` : cx<=6 ? `${state} is moderate in complexity. Plan your timeline carefully and pay attention to the background check and any jurisprudence exam requirements.` : `${state} is one of the more complex states for dental licensure. Allow extra time, especially if you need to order different exam scores.`; return `
${state}
Complexity: ${cxLbl} (${cx}/10)
${summary}
${10-cx+1}
/ 10
ease score
Fees
Speed
Requirements
`; } var scoreHtml = `
${scoreCard(sA, a, cA, 'a')} ${scoreCard(sB, b, cB, 'b')}
`; document.getElementById('cmpCompareOut').innerHTML = tableHtml + scoreHtml; } _tt; function cmpToast(msg){var el=document.getElementById('toast');el.textContent=msg;el.classList.add('show');clearTimeout(_tt);_tt=setTimeout(()=>el.classList.remove('show'),2600);} // runCompare() called by cmpInitPage() when panel opens /* ── AI JS ── */ /* ══════════════════════════════════════ STATE ══════════════════════════════════════ */ var USER = { name: 'Dr. Jane Smith', specialty: 'General Dentist', from: 'New York', to: 'Texas', }; var SYSTEM_PROMPT = `You are a dental licensing expert assistant embedded in DentaMove, a platform that helps dentists navigate interstate license transfers in the United States. The dentist you are speaking with is: - Name: ${USER.name} - Specialty: ${USER.specialty} - Currently licensed in: ${USER.from} - Relocating to: ${USER.to} Your role: - Answer questions about dental licensing requirements, timelines, documents, and processes - Be specific to their ${USER.from} → ${USER.to} transfer whenever relevant - Give practical, actionable answers — not vague generalities - Be direct and concise. Dentists are busy professionals - When you don't know something or requirements may have changed, say so clearly and direct them to the relevant state board - Never give legal advice. For legal questions, direct them to a dental attorney Key facts about ${USER.to} licensing for a dentist from ${USER.from}: - Application fee: $200 - Accepts NERB and CRDTS clinical exam scores - Jurisprudence exam required (online, open-book, covers TX dental law) - Background check: FBI + TX DPS (two separate checks via IdentoGo, agency code 11G2XN) - Processing time: 10–14 weeks typically - License verification must be sent directly from NY Office of Professions to TX Board - Transcripts typically sent via Parchment, NSC, or e-Scrip-Safe (not hand-carried) - Do not use certified mail with signature confirmation for mailing applications Format your responses clearly. Use short paragraphs. Use bullet points or numbered lists when listing steps or multiple items. Keep answers focused and practical.`; var aiMessages = []; // conversation history var aiLoading = false; /* ══════════════════════════════════════ SEND MESSAGE ══════════════════════════════════════ */ async function sendMessage(text) { var input = document.getElementById('chatInput'); var msg = (text || input.value).trim(); if (!msg || isLoading) return; // Hide welcome screen document.getElementById('welcome').style.display = 'none'; // Clear input if (!text) { input.value = ''; autoResize(input); } // Add user message to history and UI messages.push({role:'user', content: msg}); appendMessage('user', msg); // Show typing indicator setLoading(true); try { var response = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ model: 'claude-sonnet-4-20250514', max_tokens: 1000, system: SYSTEM_PROMPT, messages: messages, }) }); var data = await response.json(); if (data.error) throw new Error(data.error.message); var reply = (data.content?data.content.map(c => c.text || ''):[]).join('') || 'Sorry, I could not generate a response.'; messages.push({role:'assistant', content: reply}); setLoading(false); appendMessage('ai', reply); } catch (err) { setLoading(false); appendMessage('ai', 'Sorry — I ran into an issue connecting. Please try again in a moment.\n\n' + (err.message||'')); console.error(err); } } function sendSuggestion(btn) { sendMessage(btn.textContent.trim()); } /* ══════════════════════════════════════ RENDER ══════════════════════════════════════ */ function appendMessage(role, text) { var container = document.getElementById('messages'); var time = new Date().toLocaleTimeString('en-US', {hour:'numeric', minute:'2-digit'}); var div = document.createElement('div'); div.className = `msg msg-${role}`; var avatar = role === 'ai' ? `
` : `
JS
`; var formatted = role === 'ai' ? formatMarkdown(text) : `

${escHtml(text)}

`; div.innerHTML = ` ${avatar}
${formatted}
${role === 'ai' ? 'Assistant' : 'You'} · ${time}
`; container.appendChild(div); container.scrollTop = container.scrollHeight; } function formatMarkdown(text) { // Basic markdown → HTML var html = escHtml(text); // Bold html = html.replace(/\*\*(.+?)\*\*/g, '$1'); // Inline code html = html.replace(/`([^`]+)`/g, '$1'); // Numbered list html = html.replace(/^(\d+)\. (.+)$/gm, '
  • $2
  • '); html = html.replace(/(
  • .*<\/li>\n?)+/g, m => `
      ${m}
    `); // Bullet list html = html.replace(/^[-•] (.+)$/gm, '
  • $1
  • '); html = html.replace(/(
  • .*<\/li>\n?)+/g, m => { if (m.includes('
      ')) return m; return `
        ${m}
      `; }); // Paragraphs html = html.split(/\n{2,}/).map(block => { if (block.startsWith('
        ') || block.startsWith('
          ') || block.startsWith('
        1. ')) return block; return `

          ${block.replace(/\n/g, '
          ')}

          `; }).join(''); return html; } function escHtml(s) { return s.replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } /* ══════════════════════════════════════ LOADING ══════════════════════════════════════ */ var typingEl = null; function setLoading(loading) { isLoading = loading; document.getElementById('sendBtn').disabled = loading; var container = document.getElementById('messages'); if (loading) { var div = document.createElement('div'); div.className = 'typing-indicator'; div.id = 'typingIndicator'; div.innerHTML = `
          `; container.appendChild(div); container.scrollTop = container.scrollHeight; typingEl = div; } else if (typingEl) { typingEl.remove(); typingEl = null; } } /* ══════════════════════════════════════ INPUT HANDLING ══════════════════════════════════════ */ function handleKey(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } } function autoResize(el) { el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, 120) + 'px'; } function clearChat() { messages = []; document.getElementById('messages').innerHTML = ''; document.getElementById('welcome').style.display = ''; document.getElementById('messages').appendChild(document.getElementById('welcome')); document.getElementById('welcome').style.display = 'flex'; aiShowToast('Chat cleared'); } _tt; function aiShowToast(msg) { var t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); clearTimeout(_tt); _tt = setTimeout(() => t.classList.remove('show'), 2600); } /* ── NOTIFICATIONS JS ── */ function notifToggleCard(id) { document.getElementById(id).classList.toggle('open'); } function notifSwitchTab(id, btn) { document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('active'); }); document.querySelectorAll('.tabs .tab').forEach(function(b) { b.classList.remove('active'); }); document.getElementById('tab-' + id).classList.add('active'); btn.classList.add('active'); } function notifSwitchPreview(id, btn) { var ids = ['weekly','deadline','milestone']; ids.forEach(function(p) { var el = document.getElementById('prev-' + p); if (el) el.style.display = (p === id) ? 'block' : 'none'; }); btn.parentElement.querySelectorAll('.prev-tab').forEach(function(b) { b.classList.remove('active'); }); btn.classList.add('active'); } _tt; function notifToast(msg) { var t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); clearTimeout(_tt); _tt = setTimeout(function() { t.classList.remove('show'); }, 2600); } /* ── DOCUMENTS JS ── */ /* ══ DATA ══ */ var DOCS_DATA = [ {id:1, name:'NY License Verification Request — March 2024', folder:'License Verification', type:'pdf', size:'1.2 MB', date:'2024-03-18', notes:'Confirmation #NYV-2024-4421. Processing time 5-10 business days.', isNew:false}, {id:2, name:'NERB Score Transcript — Order Confirmation', folder:'Exam Scores', type:'pdf', size:'0.4 MB', date:'2024-03-20', notes:'Order #NRB-2024-8821. Sent directly to TSBDE.', isNew:true}, {id:3, name:'Parchment Transcript Receipt — NYU Dentistry', folder:'Transcripts', type:'pdf', size:'0.8 MB', date:'2024-03-21', notes:'Parchment order #P-882341. School confirmed sent.', isNew:true}, {id:4, name:'TSBDE Application — Completed Draft', folder:'Application', type:'doc', size:'2.1 MB', date:'2024-03-22', notes:'Notarised copy. Ready to mail.', isNew:false}, ]; var nextId = 5; var currentFolder = 'all'; var currentView = 'grid'; /* ══ ICONS ══ */ var TYPE_ICONS = { pdf: '', img: '', doc: '', other: '' }; var FOLDER_TAGS = { 'License Verification':'dft-lic','Exam Scores':'dft-exam','Transcripts':'dft-tran', 'Background Check':'dft-bg','Application':'dft-app','Other':'dft-misc' }; var THUMB_CLASSES = {pdf:'dc-thumb-pdf',img:'dc-thumb-img',doc:'dc-thumb-doc',other:'dc-thumb-other'}; var ROW_CLASSES = {pdf:'dr-pdf',img:'dr-img',doc:'dr-doc',other:'dr-oth'}; function getType(name) { var ext = (name || '').split('.').pop().toLowerCase(); if (ext === 'pdf') return 'pdf'; if (['jpg','jpeg','png','gif','webp'].indexOf(ext) > -1) return 'img'; if (['doc','docx'].indexOf(ext) > -1) return 'doc'; return 'other'; } /* ══ RENDER ══ */ function docsRender(){renderDocs();} function renderDocs() { var q = document.getElementById('searchInp').value.trim().toLowerCase(); var sort = document.getElementById('sortSel').value; var filtered = DOCS.filter(function(d) { var matchFolder = currentFolder === 'all' || d.folder === currentFolder; var matchSearch = !q || d.name.toLowerCase().indexOf(q) > -1 || d.folder.toLowerCase().indexOf(q) > -1; return matchFolder && matchSearch; }); filtered.sort(function(a, b) { if (sort === 'name') return a.name.localeCompare(b.name); if (sort === 'size') return parseFloat(b.size) - parseFloat(a.size); return b.date.localeCompare(a.date); }); updateCounts(); var out = document.getElementById('docsOutput'); if (!filtered.length) { out.innerHTML = '
          No documents here
          Upload your first document using the button above, or drag and drop a file into the zone. Try a different folder if you have already uploaded files.
          '; return; } var newDocs = filtered.filter(function(d){return d.isNew;}); var oldDocs = filtered.filter(function(d){return !d.isNew;}); var html = ''; if (newDocs.length) { html += '
          Recent uploads
          '+newDocs.length+'
          '; html += currentView === 'grid' ? renderGrid(newDocs) : renderList(newDocs); } if (oldDocs.length) { html += '
          All documents
          '+oldDocs.length+'
          '; html += currentView === 'grid' ? renderGrid(oldDocs) : renderList(oldDocs); } out.innerHTML = html; } function renderGrid(docs) { var html = '
          '; docs.forEach(function(d) { var t = getType(d.name); var tc = THUMB_CLASSES[t] || 'dc-thumb-other'; var ico = TYPE_ICONS[t] || TYPE_ICONS.other; var ft = FOLDER_TAGS[d.folder] || 'dft-misc'; var badge = d.isNew ? '
          New
          ' : ''; html += '
          '; html += '
          '+ico+''+badge+'
          '; html += '
          '+d.name+'
          '+d.size+' · '+formatDate(d.date)+'
          '; html += '
          '+d.folder+'
          '; }); html += '
          '; return html; } function renderList(docs) { var html = '
          '; docs.forEach(function(d) { var t = getType(d.name); var rc = ROW_CLASSES[t] || 'dr-oth'; var ico = TYPE_ICONS[t] || TYPE_ICONS.other; var ft = FOLDER_TAGS[d.folder] || 'dft-misc'; html += '
          '; html += '
          '+ico+'
          '; html += '
          '+d.name+'
          '+formatDate(d.date)+(d.notes?' · '+d.notes.substring(0,50)+'…':'')+'
          '; html += '
          '+d.folder+'
          '; html += '
          '+d.size+'
          '; html += '
          '; html += ''; html += ''; html += '
          '; }); html += '
          '; return html; } function formatDate(d) { var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; var parts = d.split('-'); return months[parseInt(parts[1])-1]+' '+parseInt(parts[2])+', '+parts[0]; } function updateCounts() { var total = DOCS.length; var byFolder = {}; DOCS.forEach(function(d) { byFolder[d.folder] = (byFolder[d.folder] || 0) + 1; }); var el = document.getElementById('fc-all'); if (el) el.textContent = total; var folders = {'lic':'License Verification','exam':'Exam Scores','tran':'Transcripts','bg':'Background Check','app':'Application','misc':'Other'}; Object.keys(folders).forEach(function(k) { var cel = document.getElementById('fc-'+k); if (cel) cel.textContent = byFolder[folders[k]] || 0; }); var pct = Math.min(100, Math.round(total / 5 * 100)); var fillEl = document.getElementById('storageFill'); if (fillEl) fillEl.style.width = pct + '%'; var countEl = document.getElementById('storageCount'); if (countEl) countEl.textContent = total; var labelEl = document.getElementById('storageLabel'); if (labelEl) labelEl.textContent = total + ' / 5 files'; var badgeEl = document.getElementById('docCount'); if (badgeEl) badgeEl.textContent = total; } /* ══ INTERACTIONS ══ */ function filterFolder(folder, btn) { currentFolder = folder; document.querySelectorAll('.fn-item').forEach(function(i) { i.classList.remove('active'); }); btn.classList.add('active'); renderDocs(); } function setView(view) { currentView = view; document.getElementById('vt-grid').classList.toggle('active', view === 'grid'); document.getElementById('vt-list').classList.toggle('active', view === 'list'); renderDocs(); } function previewDoc(id) { var doc = DOCS.find(function(d) { return d.id === id; }); if (!doc) return; docsToast(doc.name + ' — download requires server in production'); } function deleteDoc(id) { if (!confirm('Delete this document? This cannot be undone.')) return; DOCS = DOCS.filter(function(d) { return d.id !== id; }); renderDocs(); docsToast('Document deleted'); } function docsHandleDrop(e) { e.preventDefault(); document.getElementById('uploadZone').classList.remove('drag'); handleFiles(e.dataTransfer.files); } function docsHandleFiles(files) { if (!files || !files.length) return; var f = files[0]; document.getElementById('docsModalName').value = f.name.replace(/\.[^.]+$/, ''); docsShowUploadModal(); } function docsShowUploadModal() { document.getElementById('docsUploadModal').classList.add('show'); } function docsCloseModal() { document.getElementById('docsUploadModal').classList.remove('show'); } function docsSaveUpload() { var _modal_name = document.getElementById('docsModalName'); var name = _modal_name ? _modal_name.value.trim() : ''; var _modal_folder = document.getElementById('docsModalFolder'); var folder = _modal_folder ? _modal_folder.value : 'Other'; var _modal_notes = document.getElementById('docsModalNotes'); var notes = _modal_notes ? _modal_notes.value.trim() : ''; if (!name) { docsToast('Please enter a file name'); return; } if (DOCS.length >= 5) { docsToast('Free plan limit reached. Upgrade to Pro for 50 files.'); docsCloseModal(); return; } var today = new Date(); var dateStr = today.getFullYear() + '-' + String(today.getMonth()+1).padStart(2,'0') + '-' + String(today.getDate()).padStart(2,'0'); DOCS.push({id: nextId++, name: name, folder: folder, type: 'pdf', size: '—', date: dateStr, notes: notes, isNew: true}); docsCloseModal(); var _mn = document.getElementById('docsModalName'); if(_mn) _mn.value = ''; var _mno = document.getElementById('docsModalNotes'); if(_mno) _mno.value = ''; renderDocs(); docsToast('Document added: ' + name); } _tt; function docsToast(msg) { var t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); clearTimeout(_tt); _tt = setTimeout(function() { t.classList.remove('show'); }, 2800); } /* renderDocs() called by docsRender() when panel opens */ /* ── ADMIN JS ── */ var ADMIN_DENTISTS = [ {id:1,name:'Dr. Jane Smith',spec:'General Dentist',from:'New York',to:'Texas',pct:62,steps:10,total:16,status:'active',estDate:'Jun 2024',color:'#1a7f74',blocked:false,urgent:false}, {id:2,name:'Dr. Priya Patel',spec:'Orthodontist',from:'California',to:'Florida',pct:34,steps:5,total:16,status:'blocked',estDate:'Jul 2024',color:'#c93b3b',blocked:true,urgent:true}, {id:3,name:'Dr. James Kim',spec:'Periodontist',from:'Illinois',to:'Texas',pct:28,steps:4,total:16,status:'waiting',estDate:'Aug 2024',color:'#c97a0a',blocked:false,urgent:true}, {id:4,name:'Dr. Maria Rodriguez',spec:'General Dentist',from:'New Jersey',to:'Texas',pct:69,steps:11,total:16,status:'active',estDate:'May 2024',color:'#3b6fd4',blocked:false,urgent:true}, ]; var STATUS_LABELS = {active:'On track',blocked:'Blocked',waiting:'Action needed',complete:'Complete'}; var STATUS_CLASSES = {active:'sb-active',blocked:'sb-blocked',waiting:'sb-waiting',complete:'sb-complete'}; function initials(name) { return name.replace('Dr. ','').split(' ').map(function(w){return w[0];}).join('').toUpperCase().slice(0,2); } function adminRenderTable(list) { var tbody = document.getElementById('dentistTbody'); if (!list.length) { tbody.innerHTML = 'No dentists match your search'; return; } tbody.innerHTML = list.map(function(d) { var progColor = d.pct >= 60 ? 'var(--green)' : d.pct >= 30 ? 'var(--teal)' : 'var(--amber)'; if (d.blocked) progColor = 'var(--red)'; return '' + '
          '+initials(d.name)+'
          '+d.name+'
          '+d.spec+'
          ' + '
          '+d.from+''+d.to+'
          ' + '
          '+d.pct+'%'+d.steps+'/'+d.total+' steps
          ' + ''+STATUS_LABELS[d.status]+'' + ''+d.estDate+'' + '' + ''; }).join(''); } function adminFilterTable() { var q = document.getElementById('dtSearch').value.trim().toLowerCase(); var filtered = ADMIN_DENTISTS.filter(function(d) { return !q || d.name.toLowerCase().indexOf(q) > -1 || d.from.toLowerCase().indexOf(q) > -1 || d.to.toLowerCase().indexOf(q) > -1; }); adminRenderTable(filtered); } function adminViewDentist(id) { var d = DENTISTS.find(function(x){return x.id===id;}); if (d) adminToast('Opening '+d.name+'\'s roadmap — '+d.from+' \u2192 '+d.to+' ('+d.pct+'% complete)'); } function adminCloseInviteModal() { var m = document.getElementById("adminInviteModal"); if(m) m.classList.remove("show"); } function adminShowInviteModal() { document.getElementById('adminInviteModal').classList.add('show'); } function docsCloseModal() { document.getElementById('adminInviteModal').classList.remove('show'); } function adminSendInvite() { var name = document.getElementById('admin-inv-name').value.trim(); var email = document.getElementById('admin-inv-email').value.trim(); if (!name || !email) { adminToast('Please enter a name and email address'); return; } docsCloseModal(); document.getElementById('admin-inv-name').value = ''; document.getElementById('admin-inv-email').value = ''; document.getElementById('admin-inv-from').value = ''; document.getElementById('admin-inv-to').value = ''; adminToast('Invitation sent to '+email); } _tt; function adminToast(msg) { var t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); clearTimeout(_tt); _tt = setTimeout(function(){t.classList.remove('show');}, 2800); } // adminRenderTable() called by initPage() when panel opens /* ═══════════════════════════════════════ UNIFIED APP ROUTER ═══════════════════════════════════════ */ var CURRENT_PAGE = 'main'; // main = the original dashboard/creds/photo view function showPage(pageId) { // Hide all panels document.querySelectorAll('.page-panel').forEach(function(p) { p.classList.remove('active'); p.style.display = 'none'; }); // Show pg-app main view or a panel var mainView = document.getElementById('view-main'); var appShell = document.getElementById('pg-app'); if (pageId === 'main') { if (mainView) mainView.style.display = 'block'; var _vmShow2 = document.getElementById('view-main'); if(_vmShow2) { _vmShow2.style.display=''; _vmShow2.classList.add('active'); } } else { if (mainView) mainView.style.display = 'none'; var panel = document.getElementById('panel-' + pageId); if (panel) { panel.style.display = 'flex'; panel.classList.add('active'); initPage(pageId); } } // Update sidebar active state document.querySelectorAll('.nav-item').forEach(function(n) { n.classList.remove('active'); }); var activeNav = document.getElementById('nav-' + pageId); if (activeNav) activeNav.classList.add('active'); CURRENT_PAGE = pageId; closeMobMenu(); // Scroll panel to top var panel = document.getElementById('panel-' + pageId); if (panel) { var scroll = panel.querySelector('.page-panel-scroll'); if (scroll) scroll.scrollTop = 0; } } function initPage(pageId) { // Initialize page-specific state when first shown switch(pageId) { case 'timeline': tlInitPage(); break; case 'compare': cmpInitPage(); break; case 'ai': aiInitPage(); break; case 'documents': docsRender(); break; case 'admin': adminRenderTable(ADMIN_DENTISTS || []); break; case 'settings': settingsInitPage(); break; case 'payments': payShowView('pricing'); break; case 'notifications': break; // Static } } /* ── TIMELINE INIT ── */ var TL_FROM = APP.from || 'New York'; var TL_TO = APP.to || 'Texas'; var TL_TASK_DONE = new Set(); var TL_OPEN_PHASE = 'phase2'; function tlInitPage() { // Populate state selects var selFrom = document.getElementById('tl-selFrom'); var selTo = document.getElementById('tl-selTo'); if (!selFrom || selTo.options.length > 1) return; // already initialised TL_ALL_STATES.forEach(function(s) { var o1 = document.createElement('option'); o1.value = s; o1.textContent = s; if (s === TL_FROM) o1.selected = true; selFrom.appendChild(o1); var o2 = document.createElement('option'); o2.value = s; o2.textContent = s; if (s === TL_TO) o2.selected = true; selTo.appendChild(o2); }); tlRender(); } function tlUpdateRoute() { TL_FROM = document.getElementById('tl-selFrom').value; TL_TO = document.getElementById('tl-selTo').value; if (TL_FROM === TL_TO) { showToast('Please select two different states'); return; } TL_OPEN_PHASE = 'phase1'; tlRender(); showToast('Timeline updated for ' + TL_FROM + ' → ' + TL_TO); } function tlTogglePhase(id) { TL_OPEN_PHASE = TL_OPEN_PHASE === id ? null : id; tlRender(); setTimeout(function() { var el = document.getElementById('tl-pb-' + id); if (el) el.scrollIntoView({behavior:'smooth', block:'nearest'}); }, 50); } function tlToggleTask(taskId) { TL_TASK_DONE.has(taskId) ? TL_TASK_DONE.delete(taskId) : TL_TASK_DONE.add(taskId); tlRender(); } /* ── COMPARE INIT ── */ function cmpInitPage() { var selA = document.getElementById('cmp-selA'); var selB = document.getElementById('cmp-selB'); if (!selA || selA.options.length > 1) return; CMP_ALL_STATES.forEach(function(s) { var o1 = document.createElement('option'); o1.value = s; o1.textContent = s; if (s === 'Texas') o1.selected = true; selA.appendChild(o1); var o2 = document.createElement('option'); o2.value = s; o2.textContent = s; if (s === 'California') o2.selected = true; selB.appendChild(o2); }); runCompare(); } /* ── AI INIT ── */ var AI_INITIALISED = false; function aiInitPage() { if (AI_INITIALISED) return; AI_INITIALISED = true; // Welcome screen already visible — nothing to do } /* ── SETTINGS INIT ── */ function settingsInitPage() { var selFrom = document.getElementById('s-from'); var selTo = document.getElementById('s-to'); if (!selFrom || selFrom.options.length > 1) return; SETT_ALL_STATES.forEach(function(s) { var o1 = document.createElement('option'); o1.value=s; o1.textContent=s; if (s === (APP.from||'New York')) o1.selected=true; selFrom.appendChild(o1); var o2 = document.createElement('option'); o2.value=s; o2.textContent=s; if (s === (APP.to||'Texas')) o2.selected=true; selTo.appendChild(o2); }); } /* Auto-init */ /* ── PAYMENTS VIEW SWITCHER ── */ function payShowView(viewId) { ['pricing','checkout','success'].forEach(function(v) { var el = document.getElementById('view-'+v); if (el) { el.classList.remove('active'); el.style.display = 'none'; } }); var el = document.getElementById('view-'+viewId); if (el) { el.classList.add('active'); el.style.display = 'block'; } var titles = {pricing:'Upgrade to Pro',checkout:'Checkout',success:'Payment confirmed'}; var subs = {pricing:'Choose your plan',checkout:'Complete your upgrade',success:'Welcome to Pro'}; var tbTitle = document.querySelector('#panel-payments .tb-title'); var tbSub = document.getElementById('tb-sub-payments'); if (tbTitle) tbTitle.textContent = titles[viewId]||'Payments'; if (tbSub) tbSub.textContent = subs[viewId]||''; } function payGoBack() { payShowView('pricing'); } function settingsShowPanel(id) { document.querySelectorAll('.panel').forEach(function(p){p.classList.remove('active');}); document.querySelectorAll('.sn-item').forEach(function(n){n.classList.remove('active');}); var panel=document.getElementById('sett-panel-'+id); var nav=document.getElementById('sett-sn-'+id); if(panel)panel.classList.add('active'); if(nav)nav.classList.add('active'); } ssList.remove('active');}); document.querySelectorAll('.sn-item').forEach(function(n){n.classList.remove('active');}); var panel=document.getElementById('sett-panel-'+id); var nav=document.getElementById('sett-sn-'+id); if(panel)panel.classList.add('active'); if(nav)nav.classList.add('active'); }