fix: broader WhatsApp/social detection, generous assessment rules, overlay popup

- site_analyzer: scan onclick/data-href/data-url/data-link/data-action attrs
  on ALL tags for WhatsApp (wa.me, api.whatsapp, web.whatsapp, wa.link),
  tel: links, and social media URLs; raise dedup cap 5→8
- beauty_ai: rewrite lead quality rules — WARM for any genuine multi-brand
  retailer even with zero portfolio matches; portfolio absence NEVER justifies
  COLD alone; added country_fiscal fallback to ip_country
- index.html: assessPopup overlay modal on quality badge click in Browse tab;
  showAssessPopup() parses beauty_assessment JSON with all_contacts fallback;
  [x-cloak] CSS to prevent flash

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-13 10:37:36 +02:00
parent e426922544
commit dfd47743e3
3 changed files with 227 additions and 25 deletions

View File

@@ -88,6 +88,7 @@ tr:hover td{background:rgba(232,121,160,.04)}
input[type=checkbox]{width:14px;height:14px;accent-color:var(--accent);cursor:pointer}
textarea{width:100%;resize:vertical;font-family:monospace;font-size:12px}
.section-pad{padding:0 24px}
[x-cloak]{display:none!important}
</style>
</head>
<body x-data="app()" x-init="init()">
@@ -225,7 +226,10 @@ textarea{width:100%;resize:vertical;font-family:monospace;font-size:12px}
<td x-text="(row.niche||'—').replace('_',' ')"></td>
<td x-text="(row.site_type||'—').replace('_',' ')"></td>
<td>
<span x-show="row.beauty_lead_quality" class="badge" :class="qualityBadge(row.beauty_lead_quality)" x-text="row.beauty_lead_quality"></span>
<span x-show="row.beauty_lead_quality" class="badge" :class="qualityBadge(row.beauty_lead_quality)"
x-text="row.beauty_lead_quality" style="cursor:pointer"
title="Click to view assessment"
@click.stop="showAssessPopup(row)"></span>
<span x-show="!row.beauty_lead_quality" style="color:var(--muted)"></span>
</td>
<td style="white-space:nowrap;display:flex;gap:4px">
@@ -489,6 +493,120 @@ textarea{width:100%;resize:vertical;font-family:monospace;font-size:12px}
</div>
</div>
<!-- Assessment popup overlay (Browse tab quality badge click) -->
<div x-show="assessPopup" x-cloak @click.self="assessPopup=null"
style="position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:800;display:flex;align-items:center;justify-content:center">
<div @click.stop style="background:var(--card);border:1px solid var(--border);border-radius:12px;
padding:20px 24px;max-width:580px;width:94%;max-height:85vh;overflow-y:auto;position:relative">
<button @click="assessPopup=null"
style="position:absolute;top:12px;right:14px;background:none;border:none;color:var(--muted);font-size:18px;cursor:pointer"></button>
<template x-if="assessPopup">
<div>
<!-- Header -->
<div style="display:flex;align-items:center;gap:10px;margin-bottom:14px">
<span class="badge" :class="qualityBadge(assessPopup.lead_quality)" x-text="assessPopup.lead_quality||'—'" style="font-size:12px;padding:4px 12px"></span>
<span style="font-weight:700;font-size:15px" x-text="assessPopup.business_name||assessPopup._domain||'—'"></span>
<span x-show="assessPopup.country_fiscal" class="chip" x-text="assessPopup.country_fiscal"></span>
<span x-show="assessPopup.business_type" class="chip" x-text="assessPopup.business_type"></span>
</div>
<!-- Pitch angle -->
<template x-if="assessPopup.pitch_angle">
<p style="color:var(--accent);font-size:13px;font-style:italic;margin-bottom:12px;padding:8px 12px;background:rgba(232,121,160,.08);border-radius:6px" x-text="assessPopup.pitch_angle"></p>
</template>
<!-- Summary -->
<template x-if="assessPopup.summary">
<div style="margin-bottom:12px">
<div style="color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px">Summary</div>
<p style="font-size:12px;line-height:1.6" x-text="assessPopup.summary"></p>
</div>
</template>
<!-- Lead reasoning -->
<template x-if="assessPopup.lead_reasoning">
<div style="margin-bottom:12px">
<div style="color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px">Lead Reasoning</div>
<p style="font-size:12px;line-height:1.6" x-text="assessPopup.lead_reasoning"></p>
</div>
</template>
<!-- Categories + portfolio matches -->
<div style="display:flex;gap:16px;margin-bottom:12px;flex-wrap:wrap">
<template x-if="(assessPopup.categories||[]).length">
<div style="flex:1;min-width:140px">
<div style="color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px">Categories</div>
<template x-for="c in (assessPopup.categories||[])" :key="c">
<span class="chip" x-text="c"></span>
</template>
</div>
</template>
<template x-if="(assessPopup.dist_matches||[]).length">
<div style="flex:1;min-width:140px">
<div style="color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px">Portfolio Match</div>
<template x-for="b in (assessPopup.dist_matches||[])" :key="b">
<span class="chip chip-match" x-text="b"></span>
</template>
</div>
</template>
</div>
<!-- Contacts -->
<div style="margin-bottom:12px">
<div style="color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">
Best Contact
<span x-show="assessPopup.best_contact_channel" class="chip chip-match" style="margin-left:4px" x-text="assessPopup.best_contact_channel"></span>
</div>
<div style="font-size:12px;line-height:1.9">
<template x-if="(assessPopup.all_contacts||{}).emails?.length">
<div>
<template x-for="em in (assessPopup.all_contacts.emails||[])" :key="em">
<a :href="'mailto:'+em" x-text="em" style="display:inline-block;margin-right:10px;color:var(--accent)"></a>
</template>
</div>
</template>
<template x-if="(assessPopup.all_contacts||{}).phones?.length">
<div style="color:var(--text)">
<template x-for="ph in (assessPopup.all_contacts.phones||[])" :key="ph">
<span x-text="ph" style="display:inline-block;margin-right:10px"></span>
</template>
</div>
</template>
<template x-if="(assessPopup.all_contacts||{}).whatsapp?.length">
<div>
<template x-for="wa in (assessPopup.all_contacts.whatsapp||[])" :key="wa">
<a :href="wa" target="_blank" style="color:var(--success);display:inline-block;margin-right:10px">WhatsApp ↗</a>
</template>
</div>
</template>
<template x-if="(assessPopup.all_contacts||{}).social?.length">
<div>
<template x-for="s in (assessPopup.all_contacts.social||[])" :key="s">
<a :href="s" target="_blank" style="color:var(--info);display:inline-block;margin-right:10px" x-text="s.replace('https://','').replace('www.','').split('/').slice(0,2).join('/')"></a>
</template>
</div>
</template>
<template x-if="assessPopup.contact_email && !(assessPopup.all_contacts||{}).emails?.length">
<div><a :href="'mailto:'+assessPopup.contact_email" x-text="assessPopup.contact_email" style="color:var(--accent)"></a></div>
</template>
</div>
</div>
<!-- Sales notes -->
<template x-if="assessPopup.outreach_notes">
<p style="font-size:11px;color:var(--warn);border-top:1px solid var(--border);padding-top:8px;line-height:1.5" x-text="assessPopup.outreach_notes"></p>
</template>
<div style="display:flex;gap:8px;margin-top:14px">
<button class="btn-primary btn-sm" @click="copyText(assessPopup.outreach_email||'');assessPopup=null">Copy outreach email</button>
<button class="btn-secondary btn-sm" @click="assessPopup=null">Close</button>
</div>
</div>
</template>
</div>
</div>
<!-- Toasts -->
<div class="toast-wrap">
<template x-for="t in toasts" :key="t.id">
@@ -510,6 +628,7 @@ function app() {
toasts: [],
prescreening: false, validating: false, reassessing: false,
_loadGen: 0, // incremented on every loadDomains() call; stale responses are discarded
assessPopup: null, // parsed _beauty object shown in overlay; null = hidden
exportQuality: '', exportCountry: '',
f: {keyword:'', tld:'', prescreen_status:'live', niche:'beauty_cosmetics',
site_type:'ecommerce', country:'', assessed:'', alpha_only:false, no_sld:false, limit:'100', page:1},
@@ -793,6 +912,25 @@ function app() {
window.open('/api/beauty/export?' + p, '_blank');
},
showAssessPopup(row) {
try {
const b = row.beauty_assessment ? JSON.parse(row.beauty_assessment) : {};
b._domain = row.domain;
// Ensure all_contacts is always an object so x-if on .length works
if (!b.all_contacts || typeof b.all_contacts !== 'object') {
b.all_contacts = {
emails: row.emails ? [row.emails] : [],
phones: row.phones ? [row.phones] : [],
whatsapp: [],
social: [],
};
}
this.assessPopup = b;
} catch(e) {
this.notify('No assessment data yet', 'info');
}
},
qualityBadge(q) {
return {HOT:'badge-hot', WARM:'badge-warm', COLD:'badge-cold', NOT_RELEVANT:'badge-nr'}[q]||'badge-nr';
},