PageSourceSearch

https://mlsscribe.com/review-finder.html

html mlsscribe.com collected 2026-10-02 01:36:06 UTC 45,376 bytes, 532 lines download raw bytes

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 &amp; 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 &amp; 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 &amp; 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 {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[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 {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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 &amp; 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/...&#10;https://www.vitals.com/doctors/...&#10;https://www.zocdoc.com/doctor/..."></textarea>'+
314    '<div class="row" style="margin-top:8px"><button class="btn ghost" onclick="window.__repScan(true)">↻ Save links &amp; 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.