last_edit / frontend /client.html
Moharek
Deploy Moharek GEO Platform
a74b879
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>تقرير GEO — محرك</title>
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<link rel="stylesheet" href="/theme.css">
<style>
:root {
/* Shared from theme.css */
--brand-color: var(--accent2);
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
body{background:var(--bg);color:var(--text);font-family:var(--font);overflow-x:hidden}
.bg-mesh{position:fixed;inset:0;z-index:0;background:radial-gradient(circle at 15% 15%,rgba(59,130,246,.1) 0%,transparent 45%),radial-gradient(circle at 85% 85%,rgba(251,191,36,.07) 0%,transparent 45%);filter:blur(80px);pointer-events:none}
nav{position:sticky;top:0;z-index:100;background:rgba(3,7,18,.88);backdrop-filter:blur(24px);border-bottom:1px solid var(--border);height:68px;display:flex;align-items:center;padding:0 40px;justify-content:space-between}
.nav-brand{display:flex;align-items:center;gap:10px}
.nav-brand-icon{width:36px;height:36px;border-radius:10px;background:var(--brand-color);display:grid;place-items:center;font-weight:900;font-size:15px;color:#fff}
.nav-brand-name{font-size:15px;font-weight:900;color:#fff}
.nav-badge{padding:5px 14px;background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.25);border-radius:20px;font-size:11.5px;color:#10b981;font-weight:700}
.wrap{max-width:1100px;margin:0 auto;padding:0 28px;position:relative;z-index:1}
/* Loading / Error */
.center-state{text-align:center;padding:100px 20px;color:var(--muted)}
.center-state i{font-size:48px;display:block;margin-bottom:16px}
.center-state h2{font-size:22px;font-weight:900;color:#fff;margin-bottom:8px}
/* Report sections */
.report-header{padding:48px 0 32px;text-align:center}
.report-url{font-size:14px;color:var(--blue);margin-bottom:8px}
.report-title{font-size:32px;font-weight:900;margin-bottom:8px}
.report-date{font-size:13px;color:var(--muted)}
/* Score hero */
.score-hero{background:linear-gradient(135deg,rgba(59,130,246,.08),rgba(16,185,129,.05));border:1px solid rgba(59,130,246,.2);border-radius:20px;padding:32px;margin-bottom:28px;display:flex;align-items:center;gap:32px;flex-wrap:wrap}
.score-ring-wrap{position:relative;width:120px;height:120px;flex-shrink:0}
.score-ring-wrap svg{transform:rotate(-90deg)}
.ring-bg{fill:none;stroke:rgba(255,255,255,.06);stroke-width:9}
.ring-fill{fill:none;stroke-width:9;stroke-linecap:round;stroke-dasharray:302;stroke-dashoffset:302;transition:stroke-dashoffset 1.5s ease}
.score-center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center}
.score-num{font-size:30px;font-weight:900;line-height:1}
.score-lbl{font-size:10px;color:var(--muted)}
.score-info h2{font-size:20px;font-weight:900;margin-bottom:6px}
.score-info p{font-size:13px;color:var(--muted);line-height:1.7}
.score-badge{padding:8px 18px;border-radius:100px;font-size:13px;font-weight:800;white-space:nowrap;margin-top:12px;display:inline-block}
/* Stats */
.stats-row{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:24px}
.stat-box{background:rgba(255,255,255,.025);border:1px solid var(--border);border-radius:14px;padding:18px;text-align:center}
.stat-box-num{font-size:24px;font-weight:900;line-height:1;margin-bottom:4px}
.stat-box-lbl{font-size:11.5px;color:var(--muted)}
/* Breakdown */
.section-card{background:rgba(255,255,255,.025);border:1px solid var(--border);border-radius:16px;padding:24px;margin-bottom:20px}
.section-title{font-size:15px;font-weight:800;margin-bottom:18px;display:flex;align-items:center;gap:9px}
.bar-row{display:grid;grid-template-columns:90px 1fr 40px;align-items:center;gap:10px;margin-bottom:12px}
.bar-lbl{font-size:12px;color:var(--muted);font-weight:600;text-align:right}
.bar-track{height:8px;background:rgba(255,255,255,.05);border-radius:100px;overflow:hidden}
.bar-fill{height:100%;border-radius:100px;width:0;transition:width 1.4s ease}
.bar-val{font-size:12px;font-weight:800;text-align:left}
/* Actions */
.action-item{display:flex;gap:12px;padding:12px 14px;border-radius:10px;background:rgba(255,255,255,.025);border:1px solid var(--border);margin-bottom:8px}
.action-ico{width:28px;height:28px;border-radius:8px;display:grid;place-items:center;font-size:12px;flex-shrink:0}
.action-body{flex:1}
.action-title{font-size:13px;font-weight:700;margin-bottom:2px}
.action-pri{font-size:10px;font-weight:800;padding:2px 7px;border-radius:5px;display:inline-block;margin-top:4px}
/* Pages table */
.r-table{width:100%;border-collapse:collapse;font-size:12.5px}
.r-table th{color:var(--muted);font-weight:700;padding:8px 12px;text-align:right;border-bottom:1px solid var(--border);font-size:11px}
.r-table td{padding:10px 12px;border-bottom:1px solid rgba(255,255,255,.04);vertical-align:top}
.r-table tr:last-child td{border-bottom:none}
.r-url{color:var(--blue);font-size:11.5px;word-break:break-all}
.r-pill{display:inline-block;padding:2px 8px;border-radius:5px;font-size:10px;font-weight:700}
.r-pill.ok{background:rgba(16,185,129,.15);color:#10b981}
.r-pill.warn{background:rgba(251,191,36,.15);color:#fbbf24}
/* Footer */
.report-footer{text-align:center;padding:40px 0;color:var(--dim);font-size:13px;border-top:1px solid var(--border);margin-top:40px}
@media(max-width:640px){.stats-row{grid-template-columns:repeat(2,1fr)}.score-hero{flex-direction:column;text-align:center}}
</style>
</head>
<body>
<div class="bg-mesh"></div>
<nav>
<div class="nav-brand">
<div class="nav-brand-icon" id="brandIcon">م</div>
<div class="nav-brand-name" id="brandName">محرك GEO</div>
</div>
<div class="nav-badge"><i class="fas fa-lock"></i> تقرير خاص</div>
</nav>
<div class="wrap" id="mainContent">
<div class="center-state">
<i class="fas fa-circle-notch fa-spin" style="color:var(--blue)"></i>
<h2>جارٍ تحميل التقرير...</h2>
<p>يرجى الانتظار</p>
</div>
</div>
<script>
const API = 'http://localhost:8001/api';
const token = new URLSearchParams(location.search).get('token');
if(!token){
document.getElementById('mainContent').innerHTML = `<div class="center-state">
<i class="fas fa-lock" style="color:var(--red)"></i>
<h2>رابط غير صالح</h2>
<p>هذا الرابط غير صالح أو منتهي الصلاحية</p>
</div>`;
} else {
loadReport();
}
async function loadReport(){
try{
const r = await fetch(`${API}/client/report?token=${token}`);
const d = await r.json();
if(!d.ok){
document.getElementById('mainContent').innerHTML = `<div class="center-state">
<i class="fas fa-exclamation-circle" style="color:var(--red)"></i>
<h2>${d.error||'خطأ في التحميل'}</h2>
</div>`;
return;
}
renderReport(d);
}catch(e){
document.getElementById('mainContent').innerHTML = `<div class="center-state">
<i class="fas fa-wifi" style="color:var(--red)"></i>
<h2>خطأ في الاتصال</h2>
</div>`;
}
}
function renderReport(d){
const analysis = d.analysis || {};
const audit = d.audit || {};
const geo = analysis.geo_score || {};
const score = Math.round(geo.score || 0);
const breakdown = geo.breakdown || {};
const counts = geo.counts || {};
const pages = audit.pages || [];
const audits = audit.audits || [];
const aiVis = audit.ai_visibility || {};
const aiPct = Math.round(((aiVis.mentions||0)/(aiVis.total_queries||1))*100);
const compIntel = analysis.competitor_insight || {};
const industry = compIntel.industry || '';
const sColor = score>=80?'#10b981':score>=60?'#fbbf24':score>=40?'#f97316':'#ef4444';
const sLabel = score>=80?'ممتاز':'جيد' ;
const offset = 302 - (302 * score / 100);
const bkRows = [
{label:'العناوين',key:'headings',color:'#3b82f6'},
{label:'الكثافة',key:'density',color:'#10b981'},
{label:'الكيانات',key:'entities',color:'#a855f7'},
{label:'FAQ',key:'faq',color:'#fbbf24'},
{label:'رؤية AI',key:'ai_visibility',color:'#06b6d4'},
{label:'السلطة',key:'authority_index',color:'#f97316'},
];
const pagesHTML = pages.slice(0,5).map((p,i)=>{
const a = audits[i]||{};
const hOk = a.headings_ok;
const words = a.density ? Math.round(a.density.avg_words||0) : 0;
return `<tr>
<td><div class="r-url">${p.url}</div><div style="font-size:11px;color:var(--muted)">${p.title||''}</div></td>
<td><span class="r-pill ${hOk?'ok':'warn'}">${hOk?'✓ جيد':'⚠ يحتاج'}</span></td>
<td style="color:var(--muted);font-size:12px">${words} كلمة</td>
<td style="color:var(--muted);font-size:12px">${(p.links||[]).length} رابط</td>
</tr>`;
}).join('');
document.getElementById('mainContent').innerHTML = `
<div class="report-header">
<div class="report-url"><i class="fas fa-globe"></i> ${audit.url||''}</div>
<h1 class="report-title">تقرير GEO الشامل</h1>
<div class="report-date"><i class="fas fa-calendar"></i> ${new Date().toLocaleDateString('ar-SA',{year:'numeric',month:'long',day:'numeric'})}</div>
${industry?`<div style="margin-top:8px;font-size:13px;color:var(--muted)">الصناعة: <strong style="color:var(--blue)">${industry}</strong></div>`:''}
</div>
<div class="score-hero">
<div class="score-ring-wrap">
<svg width="120" height="120" viewBox="0 0 120 120">
<circle class="ring-bg" cx="60" cy="60" r="48"/>
<circle class="ring-fill" id="ringFill" cx="60" cy="60" r="48" stroke="${sColor}" style="stroke-dashoffset:${offset}"/>
</svg>
<div class="score-center">
<span class="score-num" style="color:${sColor}" id="scoreNum">0</span>
<span class="score-lbl">/100</span>
</div>
</div>
<div class="score-info">
<h2>درجة GEO الإجمالية</h2>
<p>الأخطاء الحرجة: <strong style="color:#ef4444">${counts.critical||0}</strong> &nbsp;·&nbsp; التحذيرات: <strong style="color:#fbbf24">${counts.warnings||0}</strong> &nbsp;·&nbsp; اجتاز: <strong style="color:#10b981">${counts.passed||0}</strong></p>
<span class="score-badge" style="background:${sColor}22;color:${sColor};border:1px solid ${sColor}44">${sLabel}</span>
</div>
</div>
<div class="stats-row">
<div class="stat-box"><div class="stat-box-num" style="color:${sColor}">${score}</div><div class="stat-box-lbl">درجة GEO</div></div>
<div class="stat-box"><div class="stat-box-num" style="color:#06b6d4">${aiPct}%</div><div class="stat-box-lbl">رؤية AI</div></div>
<div class="stat-box"><div class="stat-box-num" style="color:#a855f7">${pages.length}</div><div class="stat-box-lbl">صفحة مُزحوفة</div></div>
<div class="stat-box"><div class="stat-box-num" style="color:#ef4444">${counts.critical||0}</div><div class="stat-box-lbl">أخطاء حرجة</div></div>
</div>
<div class="section-card">
<div class="section-title"><i class="fas fa-chart-bar" style="color:#3b82f6"></i> تفصيل درجة GEO</div>
<div id="barsArea"></div>
</div>
<div class="section-card">
<div class="section-title"><i class="fas fa-file-alt" style="color:#a855f7"></i> تفاصيل الصفحات</div>
<div style="overflow-x:auto">
<table class="r-table">
<thead><tr><th>الصفحة</th><th>العناوين</th><th>الكثافة</th><th>الروابط</th></tr></thead>
<tbody>${pagesHTML||'<tr><td colspan="4" style="text-align:center;color:var(--muted);padding:20px">لا توجد صفحات</td></tr>'}</tbody>
</table>
</div>
</div>
<div class="report-footer">
<p>تقرير مُولَّد بواسطة <strong>محرك GEO</strong> — منصة SEO &amp; GEO بالذكاء الاصطناعي</p>
<p style="margin-top:6px">للاستفسار: <a href="mailto:support@mohrek.com" style="color:var(--blue)">support@mohrek.com</a></p>
</div>
`;
// Animate score
let c=0; const iv=setInterval(()=>{c+=2;if(c>=score){c=score;clearInterval(iv);}document.getElementById('scoreNum').textContent=c;},18);
// Bars
const barsArea = document.getElementById('barsArea');
barsArea.innerHTML = bkRows.map(r=>{
const pct = Math.min(100, Math.round(breakdown[r.key]||0));
return `<div class="bar-row">
<span class="bar-lbl">${r.label}</span>
<div class="bar-track"><div class="bar-fill" style="background:${r.color}" data-w="${pct}"></div></div>
<span class="bar-val" style="color:${r.color}">${pct}</span>
</div>`;
}).join('');
setTimeout(()=>barsArea.querySelectorAll('.bar-fill').forEach(f=>f.style.width=f.dataset.w+'%'), 200);
}
</script>
</body>
</html>