| <!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> · التحذيرات: <strong style="color:#fbbf24">${counts.warnings||0}</strong> · اجتاز: <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 & 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> |
|
|