1<!doctype html> 2<html lang="en"> 3<head> 4<meta charset="utf-8"> 5<meta name="viewport" content="width=device-width, initial-scale=1"> 6<meta name="referrer" content="no-referrer"> 7<meta name="robots" content="noindex,nofollow,noarchive"> 8<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://scrivara-backend.onrender.com; form-action 'self'; object-src 'none'; base-uri 'self'; upgrade-insecure-requests"> 9<link rel="stylesheet" href="fonts/fonts.css"> 10<title>MLS Reputation â Review Finder</title> 11<style> 12 /* Rethemed 2026-07-13: Editorial Calm palette transplant (b197). 13 Previously: patient-portal, send-portal-invite and phone pages. Structure unchanged. */ 14 :root{ --bg:#FBFAF7; --card:#ffffff; --card2:#F6FBF8; --line:#E7E5DD; --ink:#1A211C; --mut:#55605A; --blue:#2E6A4B; --blue-d:#204034; --green:#3B7C5A; --amber:#B07636; --violet:#7A5CC0; } 15 *{box-sizing:border-box} 16 body{margin:0;background:linear-gradient(160deg,#FBFAF7,#F4F2EC 60%,#FBFAF7);color:var(--ink);font:15px/1.5 "Public Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;min-height:100vh} 17 a{color:var(--blue)} 18 .wrap{max-width:1040px;margin:0 auto;padding:26px 18px 80px} 19 .top{display:flex;align-items:center;gap:12px;margin-bottom:6px} 20 .logo{width:38px;height:38px;border-radius:11px;background:linear-gradient(135deg,#204034,#2E6A4B);display:flex;align-items:center;justify-content:center;font-size:20px} 21 h1{font-size:24px;margin:0;color:var(--blue-d)} 22 .sub{color:var(--mut);font-size:13px;margin:2px 0 22px} 23 .card{background:var(--card);border:1px solid var(--line);border-radius:18px;padding:20px;margin-bottom:18px;box-shadow:0 2px 16px rgba(23,64,120,.09)} 24 .card h2{font-size:17px;margin:0 0 4px;display:flex;align-items:center;gap:9px;color:var(--blue-d)} 25 .card .hint{color:var(--mut);font-size:13px;margin:0 0 16px} 26 label{display:block;font-size:12px;color:var(--blue-d);font-weight:700;margin:0 0 5px} 27 input,textarea,select{width:100%;background:#FCFBF8;border:1px solid var(--line);border-radius:10px;color:var(--ink);padding:10px 12px;font-size:14px;font-family:inherit} 28 input:focus,textarea:focus{outline:none;border-color:var(--blue);box-shadow:0 0 0 3px rgba(46,106,75,.15)} 29 textarea{min-height:90px;resize:vertical} 30 .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px} 31 .btn{background:var(--blue);border:none;color:#fff;border-radius:11px;padding:11px 18px;font-weight:800;font-size:14px;cursor:pointer;transition:filter .15s} 32 .btn:hover{filter:brightness(1.08)} 33 .btn.ghost{background:#F2F0E9;border:1px solid var(--line);color:var(--blue-d);font-weight:700} 34 .btn.ghost:hover{filter:none;background:#EAE7DE} 35 .btn.green{background:var(--green)} .btn.violet{background:var(--violet)} 36 .row{display:flex;gap:10px;flex-wrap:wrap;align-items:center} 37 .platforms{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:12px} 38 .plat{background:var(--card2);border:1px solid var(--line);border-radius:13px;padding:13px} 39 .plat .ph{display:flex;align-items:center;gap:9px;justify-content:space-between} 40 .plat .nm{font-weight:800} 41 .plat .sub2{color:var(--mut);font-size:12px;margin:2px 0 10px} 42 .plat .cap{display:grid;grid-template-columns:1fr 74px 74px;gap:7px;margin-top:9px} 43 .chip{display:inline-block;background:#EAF1EE;border:1px solid #DCE5DF;color:#204034;border-radius:999px;padding:3px 9px;font-size:11px;font-weight:700} 44 .chip.miss{background:#FCF8EF;border-color:#EFE4CE;color:#8A5A22} 45 .chip.ok{background:#EAF1EE;border-color:#CFE0D6;color:#2E6A4B} 46 .kpis{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px} 47 .kpi{background:var(--card2);border:1px solid var(--line);border-radius:13px;padding:14px 16px} 48 .kpi .n{font-size:26px;font-weight:800} .kpi .l{color:var(--mut);font-size:12px} 49 .stars{color:#C99A3C;letter-spacing:2px} 50 .out{background:var(--card2);border-left:3px solid var(--blue);border-radius:9px;padding:12px 14px;white-space:pre-wrap;font-size:14px;margin-top:10px} 51 .quote{background:rgba(22,163,74,.08);border-left:3px solid var(--green);border-radius:8px;padding:9px 12px;margin:7px 0;font-size:13.5px} 52 .note{font-size:12px;color:var(--mut)} 53 .banner{background:#FCF8EF;border:1px solid #EFE4CE;border-radius:12px;padding:11px 14px;font-size:13px;color:#8A5A22;margin-bottom:16px} 54 .foot{color:var(--mut);font-size:12px;text-align:center;margin-top:26px;line-height:1.7} 55 .two{display:grid;grid-template-columns:1fr 1fr;gap:14px} 56 @media(max-width:680px){ .two{grid-template-columns:1fr} } 57 /* --- auto-reputation additions (v1) --- */ 58 .scopebadge{display:inline-block;border-radius:999px;padding:2px 10px;font-size:11px;font-weight:800;letter-spacing:.2px} 59 .scopebadge.doctor{background:#EAF1EE;border:1px solid #DCE5DF;color:#204034}
60 .scopebadge.practice{background:#EFEAF8;border:1px solid #DFD5F0;color:#7A5CC0} 61 .scopebadge.unknown{background:#F2F0E9;border:1px solid #E4E1D8;color:#55605A} 62 .grp{margin-top:14px} 63 .grp .lbl{font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;color:var(--mut);margin:0 0 8px;display:flex;align-items:center;gap:8px} 64 .srcgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:8px;margin-top:6px} 65 .srccell{background:var(--card2);border:1px solid var(--line);border-radius:10px;padding:9px 11px;font-size:12px} 66 .srccell .sn{font-weight:800;color:var(--blue-d)} 67 .srccell .ss{font-size:11px;margin-top:2px} 68 .dot2{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:5px;vertical-align:middle} 69 .dot2.ok{background:var(--green)} .dot2.off{background:#C4CBC2} .dot2.err{background:#B23B3B} 70 .loadrow{display:flex;align-items:center;gap:9px;color:var(--mut);font-size:13px} 71 .lspin{width:15px;height:15px;border:2px solid rgba(46,106,75,.25);border-top-color:var(--blue);border-radius:50%;display:inline-block;animation:lsp .7s linear infinite} 72 @keyframes lsp{to{transform:rotate(360deg)}} 73 details.manual > summary{cursor:pointer;font-weight:800;color:var(--blue-d);font-size:15px;list-style:none} 74 details.manual > summary::-webkit-details-marker{display:none} 75 details.manual > summary::before{content:'⸠';color:var(--mut)} 76 details.manual[open] > summary::before{content:'â¾ '} 77</style> 78</head> 79<body> 80<div class="wrap"> 81 <div class="top"><div class="logo">ð</div><div><h1>Review Finder & Reputation</h1></div></div> 82 <p class="sub">Find every review your practice already has scattered across the web, pull it into one dashboard, and turn it into more patients â the compliant way. Part of <b>MLS Scribe</b>.</p> 83 84 <div class="banner">â Compliant by design: this tool <b>finds, summarizes, links, and helps you respond</b> to real reviews, and helps you <b>request new reviews from real patients</b>. It never fabricates reviews, never hides legitimate ones, and never auto-posts copied reviews to Google (that violates Google's policy and can get a listing suspended).</div> 85 86 <!-- 1. Doctor details --> 87 <div class="card"> 88 <h2>𩺠Who are we scanning for?</h2> 89 <p class="hint">Enter the doctor + practice. Everything below auto-fills from this.</p> 90 <div class="grid"> 91 <div><label for="dName">Doctor name</label><input id="dName" placeholder="Jane Smith, MD"></div> 92 <div><label for="dSpec">Specialty</label><input id="dSpec" placeholder="Orthopedics / Pain Management"></div> 93 <div><label for="dPractice">Practice name</label><input id="dPractice" placeholder="Main Street Spine & Ortho"></div> 94 <div><label for="dCity">City, State</label><input id="dCity" placeholder="Philadelphia, PA"></div> 95 <div><label for="dGoogle">Google review link (optional)</label><input id="dGoogle" placeholder="https://g.page/r/â¦/review"></div> 96 <div><label for="dSite">Website (optional)</label><input id="dSite" placeholder="https://â¦"></div> 97 </div> 98 <div class="row" style="margin-top:14px"><button class="btn" onclick="save()">ð¾ Save</button><span id="savedMsg" class="note" role="status" aria-live="polite" aria-atomic="true"></span></div> 99 </div> 100 101 <!-- OPTIONAL manual tools (collapsed by default â the automatic results above are primary) --> 102 <details class="manual card"> 103 <summary>Manual capture & open-a-search tools (optional)</summary> 104 <p class="hint" style="margin-top:12px">The automatic results above already pull your real ratings. Use these only if you want to hand-check a specific directory or record a rating the auto-scan canât reach yet.</p> 105 106 <!-- 2. Scan across the web --> 107 <div class="card" style="box-shadow:none;margin-top:6px"> 108 <h2>ð Scan across the web</h2> 109 <p class="hint">Click each site to open a pre-filled search in a new tab. When you find the profile, paste its rating + review count. Leave blank & hit âNo profileâ to flag a gap you should claim.</p> 110 <div id="platforms" class="platforms"></div> 111 </div> 112 113 <!-- 3. Reputation dashboard (manual) --> 114 <div class="card" style="box-shadow:none"> 115 <h2>ð Manual capture summary</h2> 116 <p class="hint">Summary of only what you typed in by hand above (kept separate from the automatic results).</p> 117 <div class="kpis" id="kpis"></div> 118 <div id="coverage" style="margin-top:14px"></div> 119 </div> 120 </details> 121 122 <div class="two"> 123 <!-- 4. Positive quotes --> 124 <div class="card"> 125 <h2>â Pull positive quotes</h2> 126 <p class="hint">Paste review text (from any site). We surface the strongest positive lines for compliant testimonial use.</p> 127 <textarea id="qIn" placeholder="Paste one or more reviews hereâ¦"></textarea> 128 <div class="row" style="margin-top:10px"><button class="btn ghost" onclick="quotes()">Find positives</button></div> 129 <div id="qOut"></div> 130 <p class="note" style="margin-top:8px">
130Only use quotes as testimonials where the source platform + the patient allow it.</p> 131 </div> 132 <!-- 5. Respond to a tough review --> 133 <div class="card"> 134 <h2>ð¬ Respond to a tough review</h2> 135 <p class="hint">Paste a negative review â get a calm, professional, HIPAA-safe response draft (no PHI, no arguing).</p> 136 <textarea id="rIn" placeholder="Paste the negative reviewâ¦"></textarea> 137 <div class="row" style="margin-top:10px"><button class="btn ghost" onclick="respond()">Draft response</button><button class="btn ghost" onclick="flag()">Does it break the rules?</button></div> 138 <div id="rOut"></div> 139 </div> 140 </div> 141 142 <!-- 6. Get more reviews --> 143 <div class="card"> 144 <h2>ð£ Get more reviews (compliant)</h2> 145 <p class="hint">Generate a request to send real, satisfied patients â text or email, with your Google review link. In MLS Assist this can auto-offer after a signed visit.</p> 146 <div class="row"><button class="btn green" onclick="reqText()">Text template</button><button class="btn green" onclick="reqEmail()">Email template</button><button class="btn ghost" onclick="copyOut('reqOut')">Copy</button></div> 147 <div id="reqOut"></div> 148 </div> 149 150 <!-- 7. Site testimonials --> 151 <div class="card"> 152 <h2>ðï¸ Your website testimonials â more Google reviews</h2> 153 <p class="hint">Have testimonials on your own site? List them here to showcase them and, compliantly, invite those same patients to post the review on Google (we never copy it there for them).</p> 154 <textarea id="tIn" placeholder="One testimonial per lineâ¦"></textarea> 155 <div class="row" style="margin-top:10px"><button class="btn ghost" onclick="testimonials()">Build showcase + invites</button></div> 156 <div id="tOut"></div> 157 </div> 158 159 <div class="foot"> 160 MLS Scribe â Review Finder. Compliance first: find, summarize, link, respond, and request from real patients only.<br> 161 Not affiliated with Google, Healthgrades, Vitals, Zocdoc, RateMDs, Yelp, or WebMD. 162 </div> 163</div> 164
165<script> 166var PLATFORMS=[ 167 {k:'google', nm:'Google', sub:'Maps + Search â the big one', url:function(d){return 'https://www.google.com/search?q='+q(d.name+' '+d.spec+' '+d.city+' reviews');}}, 168 {k:'gmaps', nm:'Google Maps', sub:'Your Business Profile', url:function(d){return 'https://www.google.com/maps/search/'+q(d.name+' '+d.city);}}, 169 {k:'healthgrades', nm:'Healthgrades', sub:'Top doctor directory', url:function(d){return 'https://www.healthgrades.com/search?q='+q(d.name);}}, 170 {k:'vitals', nm:'Vitals', sub:'Ratings + profiles', url:function(d){return 'https://www.vitals.com/search?q='+q(d.name);}}, 171 {k:'zocdoc', nm:'Zocdoc', sub:'Booking + reviews', url:function(d){return 'https://www.zocdoc.com/search?q='+q(d.name);}}, 172 {k:'ratemds', nm:'RateMDs', sub:'Patient ratings', url:function(d){return 'https://www.ratemds.com/search/?q='+q(d.name);}}, 173 {k:'yelp', nm:'Yelp', sub:'Local reviews', url:function(d){return 'https://www.yelp.com/search?find_desc='+q(d.name)+'&find_loc='+q(d.city);}}, 174 {k:'webmd', nm:'WebMD', sub:'Provider directory', url:function(d){return 'https://doctor.webmd.com/results?q='+q(d.name);}}, 175 {k:'facebook', nm:'Facebook', sub:'Page reviews/recommends', url:function(d){return 'https://www.facebook.com/search/pages/?q='+q(d.name+' '+d.practice);}} 176]; 177function q(s){return encodeURIComponent((s||'').trim());} 178function $(id){return document.getElementById(id);} 179function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c];});} 180var REVIEW_HOSTS=['google.com','maps.app.goo.gl','g.page','healthgrades.com','vitals.com','zocdoc.com','ratemds.com','yelp.com','webmd.com','facebook.com']; 181function safeReviewUrl(value){ 182 try{ 183 var u=new URL(String(value||'')); 184 if(u.protocol!=='https:'||u.username||u.password||String(value).length>2048) return ''; 185 var host=u.hostname.toLowerCase(); 186 for(var i=0;i<REVIEW_HOSTS.length;i++) if(host===REVIEW_HOSTS[i]||host.slice(-(REVIEW_HOSTS[i].length+1))==='.'+REVIEW_HOSTS[i]) return u.href; 187 }catch(e){} 188 return ''; 189} 190function doc(){return {name:$('dName').value,spec:$('dSpec').value,practice:$('dPractice').value,city:$('dCity').value,google:$('dGoogle').value,site:$('dSite').value};} 191function copyText(t){ try{ navigator.clipboard.writeText(String(t||'')); }catch(e){} } 192function copyOut(id){ var el=$(id); copyText(el?el.innerText:''); } 193var CAP={}; // per-platform {rating, count, missing} 194/* b385: auto-fill from the MLS Settings the doctor already saved. Settings 195 keys are account-namespaced (sf_u::<email>::<key>); on a single-account 196 device the prefix is unambiguous. Only fills fields the mlsRF blob hasn't 197 set â an explicit entry here always wins. */ 198function settingsPrefill(){ 199 var out={}; 200 try{ 201 var prefixes={}; 202 for(var i=0;i<localStorage.length;i++){ var k=localStorage.key(i); var m=/^(sf_u::.+::)(?:practiceName|providerName|docname)$/.exec(k); if(m && localStorage.getItem(k)) prefixes[m[1]]=1; } 203 var pre=Object.keys(prefixes); 204 if(pre.length===1){ 205 var g=function(suffix){ try{ return localStorage.getItem(pre[0]+suffix)||''; }catch(e){ return ''; } }; 206 out.practice=g('practiceName'); out.name=g('providerName')||g('docname'); out.spec=g('docspec'); 207 out.google=g('googleBusinessUrl'); 208 var addr=g('clinicAddress'); if(addr){ var parts=addr.split(','); out.city=(parts.length>=2?parts.slice(-2).join(',').trim():addr); } 209 } 210 }catch(e){} 211 return out; 212} 213function load(){ try{ var d=JSON.parse(localStorage.getItem('mlsRF')||'{}'); var pf=settingsPrefill(); d.doc=d.doc||{}; ['name','spec','practice','city','google','site'].forEach(function(k){ var el=$('d'+k.charAt(0).toUpperCase()+k.slice(1)); if(k==='name')el=$('dName'); if(!el)return;
213 if(d.doc[k])el.value=d.doc[k]; else if(pf[k])el.value=pf[k]; }); CAP=d.cap||{}; }catch(e){} } 214function save(){ try{ localStorage.setItem('mlsRF', JSON.stringify({doc:doc(),cap:CAP})); }catch(e){} $('savedMsg').textContent='Saved.'; setTimeout(function(){$('savedMsg').textContent='';},1500); renderPlatforms(); dash(); } 215function renderPlatforms(){ 216 var d=doc(); 217 $('platforms').innerHTML=PLATFORMS.map(function(p){ 218 var c=CAP[p.k]||{}; 219 var badge = c.missing ? '<span class="chip miss">no profile</span>' : (c.rating? '<span class="chip ok">'+esc(c.rating)+'â · '+esc(c.count||'?')+'</span>' : '<span class="chip">not scanned</span>'); 220 return '<div class="plat"><div class="ph"><div><div class="nm">'+p.nm+'</div><div class="sub2">'+p.sub+'</div></div>'+badge+'</div>' 221 +'<div class="row"><a class="btn ghost" style="text-decoration:none;font-size:12px;padding:7px 11px" target="_blank" rel="noopener noreferrer" href="'+esc(safeReviewUrl(p.url(d)))+'">Open search â</a></div>' 222 +'<div class="cap"><input placeholder="rating (e.g. 4.8)" value="'+esc(c.rating||'')+'" oninput="setCap(\''+p.k+'\',\'rating\',this.value)">' 223 +'<input placeholder="#" value="'+esc(c.count||'')+'" oninput="setCap(\''+p.k+'\',\'count\',this.value)">' 224 +'<button class="btn ghost" style="font-size:11px;padding:0" onclick="setCap(\''+p.k+'\',\'missing\',!('+(c.missing?'true':'false')+'))">'+(c.missing?'â gap':'No profile')+'</button></div></div>'; 225 }).join(''); 226} 227function setCap(k,f,v){ CAP[k]=CAP[k]||{}; if(f==='missing'){CAP[k].missing=v; if(v){CAP[k].rating='';CAP[k].count='';}} else {CAP[k][f]=v; CAP[k].missing=false;} save(); } 228function dash(){ 229 var rated=PLATFORMS.filter(function(p){return CAP[p.k]&&CAP[p.k].rating;}); 230 var totR=0, wsum=0, totC=0; 231 rated.forEach(function(p){ var c=CAP[p.k]; var r=parseFloat(c.rating)||0; var n=parseInt(c.count,10)||1; wsum+=r*n; totC+=n; totR++; }); 232 var avg = totC? (wsum/totC) : 0; 233 var missing=PLATFORMS.filter(function(p){return CAP[p.k]&&CAP[p.k].missing;}); 234 var covered=rated.length; var pct=Math.round(covered/PLATFORMS.length*100); 235 var stars = avg? 'â '.repeat(Math.round(avg))+'â'.repeat(5-Math.round(avg)) : 'â'; 236 $('kpis').innerHTML= 237 '<div class="kpi"><div class="n stars">'+(avg?avg.toFixed(1):'â')+'</div><div class="l">avg rating <span class="stars">'+stars+'</span></div></div>' 238 +'<div class="kpi"><div class="n">'+totC+'</div><div class="l">total reviews found</div></div>' 239 +'<div class="kpi"><div class="n">'+covered+' / '+PLATFORMS.length+'</div><div class="l">sites with a profile ('+pct+'%)</div></div>' 240 +'<div class="kpi"><div class="n" style="color:var(--amber)">'+missing.length+'</div><div class="l">profiles to claim</div></div>'; 241 $('coverage').innerHTML = missing.length? '<div class="out" style="border-left-color:var(--amber)"><b>Claim these profiles</b> â patients search here and you\'re not showing up: '+missing.map(function(p){return esc(PLATFORMS.filter(function(x){return x.k===p.k;})[0].nm);}).join(', ')+'.</div>' : (covered? '<div class="out" style="border-left-color:var(--green)">Good coverage. Keep review volume growing with the request tools below.</div>' : ''); 242} 243function quotes(){ 244 var t=$('qIn').value; var sents=t.split(/(?<=[.!?])\s+|\n+/).map(function(s){return s.trim();}).filter(Boolean); 245 var pos=/\b(great|excellent|amazing|wonderful|best|caring|kind|thorough|professional|highly recommend|recommend|helpful|friendly|listened|attentive|gentle|painless|life[- ]changing|fantastic|outstanding|compassionate|knowledgeable|patient|trust|comfortable|relief|better)\b/i; 246 var neg=/\b(rude|wait|worst|terrible|awful|never|refused|dismissive|pain|angry|billing|overcharg|cancel|late)\b/i; 247 var picks=sents.filter(function(s){return pos.test(s)&&!neg.test(s)&&s.length>15&&s.length<240;}).slice(0,8); 248 var out=$('qOut'); 249 out.innerHTML = picks.length? picks.map(function(s){return '<div class="quote">â'+esc(s.replace(/^["'ââ]+|["'ââ]+$/g,''))+'â</div>';}).join('')+'<button class="btn ghost" id="qCopyAll" type="button" style="margin-top:8px">Copy all</button>' : '<div class="note" style="margin-top:8px">No clearly-positive lines detected â paste more review text.</div>'; 250 var copy=$('qCopyAll'); if(copy) copy.addEventListener('click',function(){ copyText(picks.join(' | ')); }); 251} 252function respond(){ 253 var t=$('rIn').value.trim(); var d=doc(); 254 var name=(d.practice||'our office');
255 var out="Thank you for taking the time to share your feedback. We're sorry your experience didn't meet the standard we hold ourselves to. We take concerns like this seriously and would like to make it right. Please reach out to "+name+" directly at your convenience so we can understand what happened and improve. We appreciate the opportunity to do better."; 256 $('rOut').innerHTML='<div class="out">'+esc(out)+'</div><div class="note" style="margin-top:6px">Never include patient details/PHI in a public reply. Take specifics offline.</div><button class="btn ghost" style="margin-top:8px" onclick="copyOut(\'rOut\')">Copy</button>'; 257} 258function flag(){ 259 var t=$('rIn').value.toLowerCase(); var reasons=[]; 260 if(/https?:\/\/|www\.|\.com/.test(t)) reasons.push('contains a link (often disallowed)'); 261 if(/(idiot|stupid|scam|fraud|liar|hate|kill|racist|\bslur\b)/.test(t)) reasons.push('abusive / offensive language'); 262 if(/(never went|never seen|never a patient|competitor|didn\'t even go)/.test(t)) reasons.push('may be from a non-patient (fake)'); 263 if(/(ssn|dob|phone number|address is)/.test(t)) reasons.push('discloses personal info'); 264 $('rOut').innerHTML = reasons.length? '<div class="out" style="border-left-color:var(--amber)"><b>Possibly reportable</b> â you may be able to flag this to the platform for: '+reasons.map(esc).join('; ')+'. Report it in the platform\'s review tools; don\'t delete legitimate criticism.</div>' : '<div class="out" style="border-left-color:var(--green)">Nothing here clearly violates platform rules â best handled with a calm public response + an offline fix.</div>'; 265} 266function reqText(){ var d=doc(); var link=d.google||'[your Google review link]'; $('reqOut').innerHTML='<div class="out">Hi! This is '+esc(d.practice||'the office')+'. It was great seeing you'+(d.name?' with '+esc(d.name):'')+'. If you have 30 seconds, a quick Google review really helps other patients find us: '+esc(link)+' â thank you!</div><div class="note" style="margin-top:6px">Send only to real, satisfied patients. Don\'t incentivize or filter by rating.</div>'; } 267function reqEmail(){ var d=doc(); var link=d.google||'[your Google review link]'; $('reqOut').innerHTML='<div class="out">Subject: How was your visit?\n\nHi [First name],\n\nThank you for trusting '+esc(d.practice||'our practice')+' with your care. If you had a good experience'+(d.name?' with '+esc(d.name):'')+', would you share it in a quick Google review? It takes under a minute and helps other patients:\n\n'+esc(link)+'\n\nWith appreciation,\n'+esc(d.practice||'The team')+'</div><div class="note" style="margin-top:6px">Real patients only. Never ask for or reward positive ratings specifically.</div>'; } 268function testimonials(){ 269 var lines=$('tIn').value.split(/\n+/).map(function(s){return s.trim();}).filter(Boolean); var d=doc(); var link=d.google||'[your Google review link]'; 270 if(!lines.length){ $('tOut').innerHTML='<div class="note">Paste testimonials above, one per line.</div>'; return; } 271 $('tOut').innerHTML='<div style="margin-top:6px">'+lines.slice(0,20).map(function(l){return '<div class="quote">â'+esc(l)+'â</div>';}).join('')+'</div>' 272 +'<div class="out" style="margin-top:12px"><b>Compliant next step:</b> these are yours to showcase on your site. To turn them into <i>Google</i> reviews, invite each of those same patients to re-post it themselves â never copy it there for them. Send: âYou once shared such kind words about your care â would you mind posting a quick review on Google? '+esc(link)+'â.</div>'; 273} 274load(); renderPlatforms(); dash(); 275</script>
vendor: 1 bytes, line 275
275
276<script> 277/* ===================================================================== 278 __mlsRepAuto2 (v1, 2026-07-07) â AUTOMATIC, multi-source reputation. 279 Codes to the LIVE backend route src/routes/reviewsFind.js: 280 GET/POST /api/reviews/find?doctor=&practice=&city=&specialty=&links= 281 -> { ok, query, summary:{doctor,practice}, listings[], reviews[], 282 sources{}, manualLinks[] } 283 Every listing/review carries scope:'doctor'|'practice'|'unknown'. 284 HARD RULE: a PRACTICE-wide rating is NEVER shown as the doctor's own 285 personal rating. Doctor vs practice are always split + labelled. 286 Sources beyond Google (Healthgrades, Vitals, WebMD, RateMDs, Zocdoc) 287 are shown with an honest per-source status from `sources`, and a 288 MANUAL LINK INPUT lets the doctor paste any profile URL today. 289 Fully automatic: runs on load + after Save; graceful loading / empty / 290 error / offline states. Compliant: reads real reviews only; never 291 fabricates, never auto-posts. 292 ===================================================================== */ 293(function(){ 294 var BK='https://scrivara-backend.onrender.com'; 295 function tok(){ try{ return sessionStorage.getItem('sf_bk_token')||localStorage.getItem('sf_bk_token')||''; }catch(e){ return ''; } } 296 function el(id){ return document.getElementById(id); } 297 function h(s){ return String(s==null?'':s).replace(/[&<>"']/g,function(c){return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[c];}); } 298 function stars(r){ if(r==null) return ''; var n=Math.round(r); var out=''; for(var i=0;i<5;i++) out+= i<n?'â ':'â'; return out; } 299 var SRC_NAMES={google:'Google',healthgrades:'Healthgrades',vitals:'Vitals',webmd:'WebMD',ratemds:'RateMDs',zocdoc:'Zocdoc',yelp:'Yelp',facebook:'Facebook',website:'Practice website'}; 300 function srcName(k){ return SRC_NAMES[k]||(k?k.charAt(0).toUpperCase()+k.slice(1):'Source'); } 301 function scopeBadge(sc){ sc=sc||'unknown'; var lab={doctor:'Doctor',practice:'Practice-wide',unknown:'Unverified match'}[sc]||'Unverified match'; return '<span class="scopebadge '+sc+'">'+lab+'</span>'; } 302 303 /* ---------- inject the new cards right under the doctor card ---------- */ 304 var docCard = el('dName') ? el('dName').closest('.card') : null; 305 var host=document.createElement('div'); 306 host.innerHTML = 307 '<div class="card" id="repLiveCard"><h2>ð Your reputation across the web <span class="chip ok" id="repLiveChip" style="display:none">auto-found</span></h2>'+ 308 '<p class="hint">MLS pulls your REAL ratings and latest reviews automatically â from Google today, and from Healthgrades, Vitals, WebMD, RateMDs & Zocdoc as those connect. Doctor-specific and practice-wide ratings are always shown <b>separately</b>.</p>'+ 309 '<div id="repLiveBody"><div class="loadrow"><span class="lspin"></span> Enter the practice/doctor above â results appear here automatically.</div></div>'+ 310 '<div id="repSources"></div>'+ 311 '<div class="grp"><div class="lbl">â Add a profile link (Healthgrades, Vitals, WebMD, RateMDs, Zocdoc, your websiteâ¦)</div>'+ 312 '<p class="note" style="margin:0 0 6px">Paste an HTTPS profile URL from Google, Healthgrades, Vitals, WebMD, RateMDs, Zocdoc, Yelp, or Facebook â one per line. Unknown or insecure hosts are ignored.</p>'+ 313 '<textarea id="repLinks" aria-label="Review-site profile links, one per line" placeholder="https://www.healthgrades.com/physician/... https://www.vitals.com/doctors/... https://www.zocdoc.com/doctor/..."></textarea>'+ 314 '<div class="row" style="margin-top:8px"><button class="btn ghost" onclick="window.__repScan(true)">â» Save links & re-scan</button><span class="note" id="repLiveNote"></span></div></div></div>'+ 315 316 '<div class="card" id="repAiCard"><h2>⨠Your Google listing, written by MLS</h2>'+ 317 '<p class="hint">Add the doctorâs resume / bio / anything that shows what makes the practice great (paste text or attach a .txt/.md file). MLS combines it with your REAL reviews and writes a marketing-quality listing draft â you approve before anything goes live.</p>'+
318 '<textarea id="repMat" aria-label="Background material about the doctor" placeholder="Paste the doctorâs resume, bio, fellowships, awards, special training, services offeredâ¦"></textarea>'+ 319 '<div class="row" style="margin-top:8px"><input type="file" id="repMatFile" accept=".txt,.md,.text" style="max-width:280px">'+ 320 '<button class="btn violet" id="repGenBtn" onclick="window.__repGen()">⨠Write my Google listing</button><span class="note" id="repGenNote"></span></div>'+ 321 '<div id="repGenOut"></div></div>'+ 322 323 /* The FREE guided setup lives in its own card, deliberately separate from 324 the paid upsell below. mls_reviews_scrape_app.js hides #repUpsell about a 325 second after load (it owns the single reputation surface), and this link 326 used to sit inside it - so the only route to gbp-setup.html from any 327 published page vanished on its own a moment after appearing. A free path 328 must not be removed by the code that retires a paid pitch. */ 329 '<div class="card" id="repGuided" style="border-color:rgba(46,106,75,.45)"><h2>ð Set up your Google Business Profile</h2>'+ 330 '<p class="hint">A free, guided walkthrough: every field pre-filled and copy-buttoned, every Google page deep-linked. You create and verify the listing yourself â MLS never sees your Google password.</p>'+ 331 '<div class="row" style="gap:8px;flex-wrap:wrap"><a class="btn green" style="text-decoration:none" href="gbp-setup.html">ð Set up my Google Business Profile (guided)</a></div></div>'+ 332 333 '<div class="card" id="repUpsell" style="border-color:rgba(46,106,75,.45)"><h2>ð Let MLS run your marketing</h2>'+ 334 '<p class="hint"><b>MLS Marketing</b> keeps your Google Business Profile optimized, drafts a reply to every review, and runs compliant review-request campaigns to real patients â every change doctor-approved before it publishes. Start with the free guided setup; add paid automation whenever you\'re ready.</p>'+ 335 '<p class="note" style="margin-top:6px">Paid automation is optional â you approve everything before it publishes. The guided setup above stays free.</p></div>'; 336 if(docCard && docCard.parentNode){ docCard.parentNode.insertBefore(host, docCard.nextSibling); } 337 else document.querySelector('.wrap').appendChild(host); 338 339 /* resume file -> textarea; persist materials + links */ 340 try{ el('repMatFile').addEventListener('change', function(){ 341 var f=this.files&&this.files[0]; if(!f) return; 342 var rd=new FileReader(); rd.onload=function(){ try{ el('repMat').value=(el('repMat').value?el('repMat').value+'\n\n':'')+String(rd.result||'').slice(0,20000); try{localStorage.setItem('mlsRepMat', el('repMat').value.slice(0,40000));}catch(e){} }catch(e){} }; rd.readAsText(f); 343 }); }catch(e){} 344 try{ var savedM=localStorage.getItem('mlsRepMat'); if(savedM) el('repMat').value=savedM; el('repMat').addEventListener('input', function(){ try{ localStorage.setItem('mlsRepMat', el('repMat').value.slice(0,40000)); }catch(e){} }); }catch(e){} 345 try{ var savedL=localStorage.getItem('mlsRFLinks'); if(savedL) el('repLinks').value=savedL; el('repLinks').addEventListener('input', function(){ try{ localStorage.setItem('mlsRFLinks', el('repLinks').value.slice(0,4000)); }catch(e){} }); }catch(e){} 346 347 function linksArr(){ try{ return (el('repLinks').value||'').split(/[\n,]+/).map(function(s){return safeReviewUrl(s.trim());}).filter(Boolean).slice(0,6); }catch(e){ return []; } } 348 349 /* ---------- render helpers ---------- */ 350 function summaryHTML(sum, dName){ 351 sum=sum||{}; var d=sum.doctor||{}, p=sum.practice||{}; 352 var out='<div class="kpis" style="margin-bottom:4px">'; 353 // Doctor's OWN rating â only from doctor-scoped listings. 354 out+='<div class="kpi"><div class="n stars">'+(d.rating!=null?d.rating.toFixed(1):'â')+'</div>'+ 355 '<div class="l">'+scopeBadge('doctor')+' '+h(dName||'the doctor')+'âs own rating'+(d.reviewCount?(' · '+d.reviewCount+' reviews'):'')+'</div></div>'; 356 // Practice-wide rating â clearly NOT the doctor's personal rating. 357 out+='<div class="kpi"><div class="n stars">'+(p.rating!=null?p.rating.toFixed(1):'â')+'</div>'+ 358 '<div class="l">'+scopeBadge('practice')+' practice-wide rating'+(p.reviewCount?(' · '+p.reviewCount+' reviews'):'')+'</div></div>'; 359 out+='</div>'; 360 if(d.rating==null && p.rating!=null){ out+='<div class="note" style="margin:2px 0 6px">Only a <b>practice-wide</b> rating was found so far â this reflects the whole office, not '+h(dName||'the doctor')+' personally. Add the doctorâs own profile links below to surface their individual rating.</div>'; } 361 return out; 362 } 363 function listingsHTML(listings, dName){ 364 if(!listings||!listings.length) return ''; 365 var order={doctor:0,practice:1,unknown:2}; 366 listings=listings.slice().sort(function(a,b){ return (order[a.scope]||2)-(order[b.scope]||2) || ((b.reviewCount||0)-(a.reviewCount||0)); }); 367 var groups={doctor:[],practice:[],unknown:[]};
368 listings.forEach(function(L){ (groups[L.scope]||groups.unknown).push(L); }); 369 var titles={doctor:'ð¤ '+(dName?h(dName)+'âs own listings':'Doctorâs own listings'),practice:'ð¥ Practice-wide listings',unknown:'â Possible matches (unverified â check these are you)'}; 370 var out=''; 371 ['doctor','practice','unknown'].forEach(function(sc){ 372 var g=groups[sc]; if(!g.length) return; 373 out+='<div class="grp"><div class="lbl">'+titles[sc]+'</div>'; 374 out+=g.map(function(L){ 375 var rc=(L.rating!=null)?('<span class="chip ok">'+h(String(L.rating))+' â · '+h(String(L.reviewCount||0))+' reviews</span>'):'<span class="chip miss">no rating yet</span>'; 376 return '<div class="plat" style="margin-bottom:8px"><div class="ph"><span class="nm">'+h(L.name||'(unnamed)')+' '+scopeBadge(L.scope)+'</span>'+rc+'</div>'+ 377 '<div class="sub2">'+h(srcName(L.source))+(L.address?(' · '+h(L.address)):'')+'</div>'+ 378 (L.rating!=null?('<div class="stars">'+stars(L.rating)+'</div>'):'')+ 379 (safeReviewUrl(L.url)?('<a href="'+h(safeReviewUrl(L.url))+'" target="_blank" rel="noopener noreferrer">Open profile â</a>'):'')+'</div>'; 380 }).join(''); 381 out+='</div>'; 382 }); 383 return out; 384 } 385 function reviewsHTML(reviews){ 386 if(!reviews||!reviews.length) return ''; 387 var out='<div class="grp"><div class="lbl">ð¬ Latest reviews (real, pulled live)</div>'; 388 out+=reviews.slice(0,8).map(function(rv){ 389 return '<div class="quote">'+stars(rv.rating)+' '+scopeBadge(rv.scope)+' <span class="chip">'+h(srcName(rv.source))+'</span><br>â'+h((rv.text||'').slice(0,260))+'â '+ 390 '<span class="note">â '+h(rv.author||'a patient')+(rv.relativeTime?(', '+h(rv.relativeTime)):'')+(rv.listingName?(' · on '+h(rv.listingName)):'')+'</span></div>'; 391 }).join(''); 392 out+='</div>'; 393 return out; 394 } 395 function sourcesHTML(sources, manualLinks){ 396 var cells=''; 397 var keys=['google','healthgrades','vitals','webmd','ratemds','zocdoc']; 398 keys.forEach(function(k){ 399 var s=(sources&&sources[k])||{status:'not_configured'}; 400 var dot='off', txt=''; 401 if(s.status==='ok'){ dot='ok'; txt='Connected · '+(s.listings||0)+' listing(s)'; } 402 else if(s.status==='error'){ dot='err'; txt='Lookup error'; } 403 else { dot='off'; txt='Not connected yet'; } 404 cells+='<div class="srccell"><div class="sn"><span class="dot2 '+dot+'"></span>'+h(srcName(k))+'</div><div class="ss" style="color:var(--mut)">'+h(txt)+'</div></div>'; 405 }); 406 // manual links echoed back, classified by the backend 407 if(manualLinks&&manualLinks.length){ 408 manualLinks.forEach(function(m){ 409 var safe=safeReviewUrl(m.url); 410 if(safe) cells+='<div class="srccell"><div class="sn"><span class="dot2 ok"></span>'+h(srcName(m.source))+' (you added)</div><div class="ss"><a href="'+h(safe)+'" target="_blank" rel="noopener noreferrer">open link â</a></div></div>'; 411 }); 412 } 413 return '<div class="grp"><div class="lbl">ð Where MLS is looking</div><div class="srcgrid">'+cells+'</div>'+ 414 '<p class="note" style="margin-top:6px">Google is live now. The other directories light up automatically once their API key is added on the server (no page change needed) â until then, paste their profile links below.</p></div>'; 415 } 416 417 /* ---------- 1) auto-scan (the whole reputation flow) ---------- */ 418 var lastData=null, scanning=false; 419 window.__repScan=function(force){ 420 if(scanning) return; 421 var d; try{ d=doc(); }catch(e){ d={}; } 422 var practice=(d.practice||'').trim(), doctor=(d.name||'').trim(), city=(d.city||'').trim(), specialty=(d.spec||'').trim(); 423 if(!practice && !doctor){ el('repLiveBody').innerHTML='<div class="note">Enter the practice or doctor name above first â then MLS scans automatically.</div>'; el('repSources').innerHTML=''; return; } 424 scanning=true; el('repLiveNote').textContent=''; 425 el('repLiveBody').innerHTML='<div class="loadrow"><span class="lspin"></span> Scanning the web for your real reviewsâ¦</div>'; 426 var links=linksArr(); 427 fetch(BK+'/api/reviews/find',{method:'POST',cache:'no-store',credentials:'omit',referrerPolicy:'no-referrer',headers:{'Content-Type':'application/json','Accept':'application/json'},body:JSON.stringify({doctor:doctor,practice:practice,city:city,specialty:specialty,links:links})}).then(function(r){return r.json();}).then(function(j){ 428 scanning=false; 429 if(!j||!j.ok){ el('repLiveBody').innerHTML='<div class="note">â ï¸ '+h((j&&(j.message||j.error))||'Could not reach the review service. Your details are saved â try Re-scan in a moment.')+'</div>'; return; } 430 lastData=j; 431 var dName=(j.query&&j.query.doctor)||doctor; 432 var hasListings=j.listings&&j.listings.length; 433 var body=''; 434 if(hasListings){ 435 el('repLiveChip').style.display='inline-block'; 436 body+=summaryHTML(j.summary, dName); 437 body+=listingsHTML(j.listings, dName); 438 body+=reviewsHTML(j.reviews); 439 } else { 440 body+='<div class="note">No listing found yet for â'+h(dName||practice)+'â (check spelling / add the city, or paste a profile link below).</div>'+ 441 '<div class="plat" style="margin-top:10px;border-color:rgba(22,163,74,.5)">'+ 442 '<div class="nm">⨠No Google listing? MLS will help you CREATE one.</div>'+ 443 '<div class="sub2">A Google Business Profile is free and is how new patients find you. MLS pre-fills everything from your details above (and the resume materials below) â you approve before anything goes live.</div>'+ 444 '<div class="row" style="margin-top:8px">'+ 445 '<a class="btn green" style="text-decoration:none" href="https://business.google.com/create" target="_blank" rel="noopener noreferrer">ð Start my Google listing â</a>'+ 446 '<button class="btn ghost" onclick="window.__repCopyPack()">ð Copy my pre-filled info</button></div></div>'; 447 } 448 el('repLiveBody').innerHTML=body; 449 el('repSources').innerHTML=sourcesHTML(j.sources, j.manualLinks); 450 }).catch(function(){ scanning=false; el('repLiveBody').innerHTML='<div class="note">â ï¸ Network problem reaching the review service. Your details are saved locally â hit Re-scan when youâre back online.</div>'; }); 451 }; 452 453 /* ---------- 2) AI listing (real shape) ---------- */ 454 window.__repGen=function(){ 455 var t=tok(); 456 var note=el('repGenNote'), out=el('repGenOut'), btn=el('repGenBtn'); 457 if(!t){ note.textContent='Open this from inside MLS while signed in (the AI uses your MLS account).'; return; } 458 var d; try{ d=doc(); }catch(e){ d={}; } 459 var mat=(el('repMat').value||'').trim(); 460 var dName=d.name||''; 461 var sumTxt='', lstTxt='', revTxt=''; 462 try{ 463 var S=lastData&&lastData.summary; if(S){ 464 if(S.doctor&&S.doctor.rating!=null) sumTxt+='Doctorâs OWN rating: '+S.doctor.rating+' stars ('+(S.doctor.reviewCount||0)+' reviews).\n'; 465 if(S.practice&&S.practice.rating!=null) sumTxt+='Practice-wide rating (NOT the doctorâs personal rating): '+S.practice.rating+' stars ('+(S.practice.reviewCount||0)+' reviews).\n'; 466 } 467 }catch(e){} 468 try{ lstTxt=(lastData&&lastData.listings||[]).slice(0,5).map(function(L){return '- ['+L.scope+'] '+L.name+(L.address?(', '+L.address):'')+(L.rating!=null?(' â '+L.rating+'â / '+(L.reviewCount||0)+' reviews'):'');}).join('\n'); }catch(e){} 469 try{ revTxt=(lastData&&lastData.reviews||[]).slice(0,6).map(function(r){return '- ['+r.scope+'] "'+(r.text||'').slice(0,200)+'" ('+(r.rating||'?')+' stars)';}).join('\n'); }catch(e){} 470 var prompt='Write an optimized Google Business Profile for this medical practice that markets it strongly but truthfully.\n'+ 471 'Doctor: '+(d.name||'')+' | Specialty: '+(d.spec||'')+' | Practice: '+(d.practice||'')+' | City: '+(d.city||'')+' | Website: '+(d.site||'')+'\n'+ 472 (sumTxt?('Ratings found:\n'+sumTxt):'')+ 473 (lstTxt?('Listings found (scope in brackets):\n'+lstTxt+'\n'):'')+ 474 (revTxt?('Real patient reviews (scope in brackets):\n'+revTxt+'\n'):'')+ 475 (mat?('Doctor-provided materials (resume/bio):\n'+mat.slice(0,6000)+'\n'):'')+ 476 'IMPORTANT: a practice-wide rating is the whole officeâs, not the doctorâs personal rating â never claim it as the doctorâs own.\n'+ 477 'Output EXACTLY these sections, each starting with the header on its own line:\n'+ 478 '== BUSINESS DESCRIPTION == (max 750 characters, patient-friendly, why choose this practice)\n'+ 479 '== SERVICES == (bulleted list of services to add to the profile)\n'+ 480 '== HIGHLIGHTS == (short attributes/selling points)\n'+ 481 '== POSTS == (2 short Google-post drafts announcing what the practice offers)\n'+
482 '== REVIEW REPLIES == (a warm reply template for 5-star reviews and a professional HIPAA-safe reply template for critical reviews; never confirm someone is a patient)\n'+ 483 'Truthful only; do not invent credentials that are not in the materials.'; 484 btn.disabled=true; note.textContent='Writing your listing⦠(a few seconds)'; 485 fetch(BK+'/api/widget/ai',{method:'POST',cache:'no-store',credentials:'omit',referrerPolicy:'no-referrer',headers:{'Content-Type':'application/json','Authorization':'Bearer '+t},body:JSON.stringify({prompt:prompt,system:'You are an expert healthcare marketing copywriter. Truthful, compliant, warm, specific.'})}) 486 .then(function(r){return r.json().then(function(j){return {ok:r.ok,j:j};});}) 487 .then(function(x){ 488 btn.disabled=false; note.textContent=''; 489 var text=(x.j&&(x.j.text||x.j.reply))||''; 490 if(!x.ok||!text){ note.textContent=(x.j&&x.j.error)||'The AI is busy â try again in a minute.'; return; } 491 var secs=text.split(/==\s*([A-Z ]+?)\s*==/).filter(function(s){return s.trim();}); 492 var htmlOut=''; 493 if(secs.length>=2){ for(var i=0;i+1<secs.length;i+=2){ var nm2=secs[i].trim(), b2=secs[i+1].trim(); htmlOut+='<div class="out" style="margin-top:10px"><b>'+h(nm2.charAt(0)+nm2.slice(1).toLowerCase())+'</b> <button class="btn ghost" style="padding:3px 10px;font-size:11px;float:right" onclick="navigator.clipboard.writeText(this.parentNode.querySelector(\'pre\').textContent)">ð Copy</button><pre style="white-space:pre-wrap;font:inherit;margin:8px 0 0">'+h(b2)+'</pre></div>'; } } 494 else htmlOut='<div class="out" style="margin-top:10px"><pre style="white-space:pre-wrap;font:inherit;margin:0">'+h(text)+'</pre></div>'; 495 htmlOut+='<div class="note" style="margin-top:8px">Review it, then paste each section into your Google Business Profile (business.google.com). MLS never auto-posts â you stay in control.</div>'; 496 out.innerHTML=htmlOut; 497 }).catch(function(){ btn.disabled=false; note.textContent='Network problem â try again.'; }); 498 }; 499 500 /* pre-filled info pack for creating a brand-new Google Business Profile */ 501 window.__repCopyPack=function(){ 502 var d; try{ d=doc(); }catch(e){ d={}; } 503 var mat=(el('repMat')&&el('repMat').value||'').slice(0,1500); 504 var pack='GOOGLE BUSINESS PROFILE â pre-filled by MLS\n'+ 505 'Business name: '+(d.practice||d.name||'')+'\n'+ 506 'Category: '+(d.spec||'Medical practice')+'\n'+ 507 'Doctor: '+(d.name||'')+'\n'+ 508 'City: '+(d.city||'')+'\n'+ 509 'Website: '+(d.site||'')+'\n'+ 510 (mat?('About (from your materials):\n'+mat+'\n'):''); 511 try{ navigator.clipboard.writeText(pack); }catch(e){} 512 alert('Copied â paste as you go through Googleâs listing steps.'); 513 }; 514 515 /* Network scans are explicit only. Saving profile fields never starts one. */ 516})(); 517</script>
vendor: 1 bytes, line 517
517
518<script src="mls_reviews_scrape_app.js?v=20260802c3"></script>
vendor: 1 bytes, line 518
518
519<script> 520 /* Add one native hover explanation only where the compact label is not self-explanatory. */ 521 (function(){ 522 var tips=[ 523 ['button[onclick="quotes()"]','Pull out strong positive quotes from the review text above'], 524 ['button[onclick="respond()"]','Draft a privacy-safe public response to the review above'], 525 ['button[onclick="flag()"]',"Check whether the review may violate the platform's removal policies"], 526 ['button[onclick="testimonials()"]','Turn positive quotes into a showcase and patient invitation drafts'] 527 ]; 528 tips.forEach(function(row){var el=document.querySelector(row[0]);if(el)el.title=row[1];}); 529 })(); 530</script>
530 531</body> 532</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.