Files
Siro/knowledge/siro_driver_registration_simulation.html
2026-06-19 01:47:48 +03:00

1057 lines
50 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
<title>Siro — محاكاة تسجيل السائق (سوريا)</title>
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;900&display=swap" rel="stylesheet"/>
<style>
:root{
--bg:#080c14;--s1:#0f1423;--s2:#161d30;--s3:#1e273d;
--bdr:#252f4a;--bdr2:#2d3a58;
--pri:#5b8dff;--pri2:#4070e0;
--amber:#f59e0b;--amber2:#d97706;
--green:#22c55e;--red:#ef4444;
--purple:#a855f7;--teal:#14b8a6;--cyan:#06b6d4;
--txt:#e2e8f0;--muted:#64748b;--muted2:#475569;
}
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:'Tajawal',sans-serif;background:var(--bg);color:var(--txt);min-height:100vh;}
/* ── Header ─────────────────────────────────────── */
.hdr{
background:linear-gradient(135deg,#0d1630 0%,#141e38 100%);
border-bottom:1px solid var(--bdr);
padding:14px 24px;display:flex;align-items:center;justify-content:space-between;
position:sticky;top:0;z-index:200;
}
.hdr-brand{display:flex;align-items:center;gap:12px;}
.hdr-logo{width:42px;height:42px;border-radius:12px;
background:linear-gradient(135deg,var(--pri),var(--purple));
display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:900;}
.hdr h1{font-size:17px;font-weight:700;line-height:1.3;}
.hdr p{font-size:11px;color:var(--muted);margin-top:2px;}
.speed-row{display:flex;align-items:center;gap:10px;background:var(--s2);
border:1px solid var(--bdr);border-radius:10px;padding:7px 14px;}
.speed-row label{font-size:12px;color:var(--muted);}
.speed-row input{width:80px;accent-color:var(--pri);}
.speed-row span{font-size:13px;font-weight:700;color:var(--pri);min-width:30px;}
/* ── Layout ─────────────────────────────────────── */
.main{display:grid;grid-template-columns:300px 1fr 320px;height:calc(100vh - 67px);}
/* ── Left panel ─────────────────────────────────── */
.lpanel{background:var(--s1);border-left:1px solid var(--bdr);
overflow-y:auto;padding:18px 16px;display:flex;flex-direction:column;gap:6px;}
.panel-title{font-size:11px;font-weight:700;text-transform:uppercase;
letter-spacing:1px;color:var(--muted);margin-bottom:8px;}
/* Phase step */
.step{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;
border-radius:10px;border:1.5px solid transparent;
background:var(--s2);cursor:pointer;transition:all .2s;}
.step:hover{border-color:var(--pri);}
.step.active{border-color:var(--pri);background:rgba(91,141,255,.12);}
.step.done{opacity:.5;}
.step-num{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;
justify-content:center;font-size:12px;font-weight:700;flex-shrink:0;}
.step-info{flex:1;min-width:0;}
.step-name{font-size:13px;font-weight:600;line-height:1.3;}
.step-sub{font-size:11px;color:var(--muted);margin-top:2px;line-height:1.4;}
.step-badge{font-size:10px;font-weight:700;padding:2px 7px;border-radius:12px;flex-shrink:0;}
/* ── Center panel ────────────────────────────────── */
.center{background:var(--s1);overflow-y:auto;position:relative;padding:20px;}
/* Phone mockup */
.phone-wrap{display:flex;justify-content:center;padding:10px 0 20px;}
.phone{
width:320px;min-height:580px;
background:var(--s3);
border:2px solid var(--bdr2);border-radius:38px;
box-shadow:0 24px 60px rgba(0,0,0,.5),
inset 0 1px 0 rgba(255,255,255,.06);
overflow:hidden;position:relative;display:flex;flex-direction:column;
}
.phone-bar{height:36px;background:rgba(0,0,0,.3);
display:flex;align-items:center;justify-content:center;gap:8px;}
.phone-dot{width:10px;height:10px;border-radius:50%;background:rgba(255,255,255,.1);}
.phone-notch{width:90px;height:18px;background:#000;border-radius:0 0 12px 12px;}
.phone-screen{flex:1;overflow:hidden;position:relative;}
.screen{
display:none;flex-direction:column;height:100%;
animation:screenIn .35s ease;
}
.screen.active{display:flex;}
@keyframes screenIn{from{opacity:0;transform:translateX(-20px);}to{opacity:1;transform:none;}}
/* App topbar */
.app-bar{
background:linear-gradient(135deg,var(--pri2),var(--purple));
padding:16px 18px 12px;
}
.app-bar h2{font-size:16px;font-weight:700;}
.app-bar p{font-size:11px;opacity:.8;margin-top:2px;}
/* Screen content */
.screen-body{flex:1;overflow-y:auto;padding:16px;}
.screen-body::-webkit-scrollbar{width:3px;}
.screen-body::-webkit-scrollbar-thumb{background:var(--bdr2);}
/* Form fields simulation */
.field{margin-bottom:12px;}
.field label{font-size:11px;color:var(--muted);margin-bottom:4px;display:block;}
.field-input{
background:var(--s2);border:1px solid var(--bdr);border-radius:8px;
padding:10px 12px;font-size:13px;color:var(--txt);font-family:inherit;
width:100%;transition:border-color .2s;
}
.field-input:focus{outline:none;border-color:var(--pri);}
.field-input.filled{border-color:var(--green);color:var(--green);}
.field-input.error{border-color:var(--red);}
/* OTP circles */
.otp-row{display:flex;gap:10px;justify-content:center;padding:20px 0;}
.otp-box{
width:48px;height:56px;background:var(--s2);border:2px solid var(--bdr);
border-radius:12px;display:flex;align-items:center;justify-content:center;
font-size:22px;font-weight:700;color:var(--txt);transition:all .3s;
}
.otp-box.filled{border-color:var(--pri);background:rgba(91,141,255,.15);color:var(--pri);}
.otp-box.ok{border-color:var(--green);background:rgba(34,197,94,.12);color:var(--green);}
/* Doc upload cards */
.doc-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:14px;}
.doc-card{
background:var(--s2);border:1.5px dashed var(--bdr);
border-radius:10px;padding:12px 8px;
display:flex;flex-direction:column;align-items:center;gap:6px;
font-size:11px;color:var(--muted);text-align:center;cursor:pointer;
transition:all .25s;min-height:80px;justify-content:center;
}
.doc-card.uploading{border-color:var(--amber);color:var(--amber);}
.doc-card.done{border-color:var(--green);background:rgba(34,197,94,.08);color:var(--green);}
.doc-card.error{border-color:var(--red);color:var(--red);}
.doc-icon{font-size:22px;}
/* AI badge */
.ai-badge{
background:linear-gradient(135deg,rgba(168,85,247,.25),rgba(91,141,255,.25));
border:1px solid rgba(168,85,247,.5);border-radius:8px;
padding:8px 12px;font-size:11px;margin-bottom:12px;
display:flex;align-items:center;gap:8px;
}
.ai-spin{width:14px;height:14px;border-radius:50%;
border:2px solid rgba(168,85,247,.3);border-top-color:var(--purple);
animation:spin .6s linear infinite;}
@keyframes spin{to{transform:rotate(360deg);}}
/* Submit btn */
.btn-submit{
width:100%;padding:13px;border-radius:12px;border:none;
background:linear-gradient(135deg,var(--pri),var(--purple));
color:#fff;font-family:inherit;font-size:14px;font-weight:700;
cursor:pointer;transition:all .2s;
}
.btn-submit:hover{transform:translateY(-1px);box-shadow:0 8px 20px rgba(91,141,255,.3);}
.btn-submit:disabled{opacity:.5;cursor:not-allowed;transform:none;}
/* Progress bar in phone */
.steps-bar{display:flex;gap:4px;padding:10px 16px;}
.bar-seg{height:4px;border-radius:2px;flex:1;background:var(--bdr);transition:background .4s;}
.bar-seg.done{background:var(--green);}
.bar-seg.active{background:var(--pri);}
/* Status card */
.status-card{
background:var(--s3);border:1px solid var(--bdr2);border-radius:12px;
padding:14px;margin-bottom:12px;display:flex;gap:12px;align-items:center;
}
.status-icon{font-size:28px;}
.status-info h3{font-size:13px;font-weight:700;}
.status-info p{font-size:11px;color:var(--muted);margin-top:3px;}
/* Controls */
.ctrl-row{display:flex;gap:10px;justify-content:center;margin-top:16px;}
.btn{padding:9px 18px;border-radius:10px;border:none;
font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;transition:all .2s;}
.btn:hover{transform:translateY(-1px);}
.btn:disabled{opacity:.45;cursor:not-allowed;transform:none;}
.btn-pri{background:var(--pri);color:#fff;}
.btn-amb{background:var(--amber);color:#000;}
.btn-red{background:var(--red);color:#fff;}
.btn-ghost{background:var(--s3);color:var(--txt);border:1px solid var(--bdr);}
/* ── Right panel ─────────────────────────────────── */
.rpanel{background:var(--s1);border-right:1px solid var(--bdr);
overflow-y:auto;padding:18px 16px;}
/* Stats grid */
.stats{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:14px;}
.stat{background:var(--s2);border:1px solid var(--bdr);border-radius:10px;padding:10px 12px;}
.stat-lbl{font-size:10px;color:var(--muted);}
.stat-val{font-size:16px;font-weight:700;margin-top:2px;}
/* Flow diagram */
.flow{display:flex;flex-direction:column;gap:4px;margin-bottom:14px;}
.flow-node{
background:var(--s2);border:1px solid var(--bdr);border-radius:8px;
padding:8px 12px;font-size:11px;display:flex;align-items:center;gap:8px;
transition:all .25s;
}
.flow-node.active{border-color:var(--pri);background:rgba(91,141,255,.1);}
.flow-node.done{border-color:var(--green);opacity:.7;}
.flow-node.err{border-color:var(--red);}
.flow-dot{width:8px;height:8px;border-radius:50%;background:var(--bdr);flex-shrink:0;}
.flow-node.active .flow-dot{background:var(--pri);}
.flow-node.done .flow-dot{background:var(--green);}
.flow-node.err .flow-dot{background:var(--red);}
.flow-arrow{text-align:center;font-size:10px;color:var(--muted);}
/* API log */
.api-log{display:flex;flex-direction:column;gap:5px;}
.api-entry{
background:var(--s2);border-radius:7px;padding:8px 10px;
font-size:11px;line-height:1.5;border-right:3px solid var(--bdr);
animation:fadeIn .3s ease;
}
.api-entry.req{border-color:var(--cyan);}
.api-entry.res{border-color:var(--green);}
.api-entry.err{border-color:var(--red);}
.api-entry.ai{border-color:var(--purple);}
.api-entry.info{border-color:var(--amber);}
.api-time{color:var(--muted);font-size:9px;margin-bottom:2px;}
@keyframes fadeIn{from{opacity:0;transform:translateX(6px);}to{opacity:1;transform:none;}}
/* DB table preview */
.db-preview{background:var(--s2);border:1px solid var(--bdr);border-radius:10px;
overflow:hidden;margin-bottom:12px;}
.db-header{background:var(--s3);padding:8px 12px;font-size:11px;font-weight:700;
color:var(--cyan);border-bottom:1px solid var(--bdr);display:flex;align-items:center;gap:6px;}
.db-row{padding:7px 12px;font-size:11px;display:flex;justify-content:space-between;
border-bottom:1px solid rgba(255,255,255,.03);}
.db-row:last-child{border-bottom:none;}
.db-key{color:var(--muted);}
.db-val{color:var(--green);max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.db-val.enc{color:var(--amber);}
.db-val.pending{color:var(--amber);}
/* Toast */
.toast-wrap{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);
z-index:999;display:flex;flex-direction:column;gap:8px;align-items:center;pointer-events:none;}
.toast{background:rgba(22,29,48,.97);border:1px solid var(--bdr2);
border-radius:12px;padding:10px 20px;font-size:13px;font-weight:500;
backdrop-filter:blur(12px);white-space:nowrap;pointer-events:auto;}
/* Divider */
.div{height:1px;background:var(--bdr);margin:12px 0;}
/* Country badge */
.country-badge{display:inline-flex;align-items:center;gap:6px;
background:rgba(239,68,68,.15);border:1px solid rgba(239,68,68,.3);
border-radius:20px;padding:3px 10px;font-size:11px;font-weight:700;color:var(--red);}
/* Security badge */
.sec-badges{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px;}
.sec-badge{font-size:10px;font-weight:700;padding:3px 8px;border-radius:8px;}
.sec-badge.jwt{background:rgba(91,141,255,.15);color:var(--pri);}
.sec-badge.hmac{background:rgba(245,158,11,.15);color:var(--amber);}
.sec-badge.enc{background:rgba(20,184,166,.15);color:var(--teal);}
.sec-badge.rate{background:rgba(239,68,68,.15);color:var(--red);}
.sec-badge.ai{background:rgba(168,85,247,.15);color:var(--purple);}
</style>
</head>
<body>
<!-- Header -->
<header class="hdr">
<div class="hdr-brand">
<div class="hdr-logo">S</div>
<div>
<h1>Siro Driver — محاكاة تسجيل السائق <span class="country-badge">🇸🇾 سوريا</span></h1>
<p>OTP • Document Upload • AI Gemini • DB Encryption • CarRegistration + driver tables</p>
</div>
</div>
<div class="speed-row">
<label>سرعة</label>
<input type="range" id="spdSlider" min="0.5" max="5" step="0.5" value="1.5" oninput="setSpeed(this.value)"/>
<span id="spdLbl">1.5×</span>
</div>
</header>
<div class="main">
<!-- ── LEFT PANEL : Steps ── -->
<div class="lpanel">
<div class="panel-title">خطوات التسجيل</div>
<div id="stepList"></div>
</div>
<!-- ── CENTER : Phone mockup ── -->
<div class="center">
<div class="phone-wrap">
<div class="phone">
<div class="phone-bar">
<div class="phone-dot"></div>
<div class="phone-notch"></div>
<div class="phone-dot"></div>
</div>
<!-- Steps progress bar -->
<div class="steps-bar" id="stepsBar"></div>
<div class="phone-screen">
<!-- SCREEN 0: Welcome -->
<div class="screen" id="sc0">
<div class="app-bar">
<h2>مرحباً بك في سيرو 🚗</h2>
<p>ابدأ رحلتك معنا كسائق محترف</p>
</div>
<div class="screen-body">
<div style="text-align:center;padding:20px 0 10px;">
<div style="font-size:52px;margin-bottom:12px;">🇸🇾</div>
<div style="font-size:15px;font-weight:700;margin-bottom:6px;">تسجيل سائق — سوريا</div>
<div style="font-size:12px;color:var(--muted);line-height:1.6">
سيتم التحقق من رقم هاتفك أولاً<br/>ثم رفع وثائقك للمراجعة
</div>
</div>
<div class="div"></div>
<div class="sec-badges">
<span class="sec-badge jwt">JWT Bearer</span>
<span class="sec-badge hmac">HMAC Auth</span>
<span class="sec-badge enc">AES Encrypt</span>
<span class="sec-badge rate">Rate Limit</span>
<span class="sec-badge ai">Gemini AI</span>
</div>
</div>
</div>
<!-- SCREEN 1: Phone entry -->
<div class="screen" id="sc1">
<div class="app-bar">
<h2>رقم الهاتف</h2>
<p>أدخل رقمك السوري للتحقق</p>
</div>
<div class="screen-body">
<div class="field">
<label>رقم الجوال</label>
<input class="field-input" id="phoneInput" value="963944xxxxxx" readonly/>
</div>
<div style="font-size:11px;color:var(--muted);margin-bottom:14px;line-height:1.6;">
📌 <b>Phone Formatting Logic:</b><br/>
<code style="color:var(--cyan);">09xxxxxxx</code><code style="color:var(--green);">9639xxxxxxx</code><br/>
<code style="color:var(--cyan);">00963xxxxxxx</code><code style="color:var(--green);">963xxxxxxx</code>
</div>
<button class="btn-submit" onclick="nextStep()">إرسال OTP عبر WhatsApp ▶</button>
</div>
</div>
<!-- SCREEN 2: OTP -->
<div class="screen" id="sc2">
<div class="app-bar">
<h2>رمز التحقق</h2>
<p>تم الإرسال عبر Intaleq WhatsApp</p>
</div>
<div class="screen-body">
<div style="text-align:center;font-size:12px;color:var(--muted);margin-bottom:4px;">
أدخل الرمز المرسل لـ <b style="color:var(--txt);">+963944xxx</b>
</div>
<div class="otp-row" id="otpRow">
<div class="otp-box" id="ob0">_</div>
<div class="otp-box" id="ob1">_</div>
<div class="otp-box" id="ob2">_</div>
</div>
<div style="text-align:center;font-size:11px;color:var(--muted);margin-bottom:14px;">
⏱️ عداد إعادة الإرسال: <b id="otpTimer" style="color:var(--amber);">120</b> ثانية
</div>
<div style="background:var(--s2);border:1px solid var(--bdr);border-radius:8px;
padding:10px 12px;font-size:11px;line-height:1.7;margin-bottom:14px;">
<div><b style="color:var(--cyan);">POST</b> /auth/otp/request.php</div>
<div>📱 country: Syria → Intaleq WhatsApp</div>
<div>🔁 Failover: Nabeh JWT → SMS</div>
<div>⏱️ صلاحية: 5 دقائق</div>
<div>🔒 OTP مشفر في DB (AES-GCM)</div>
</div>
<button class="btn-submit" onclick="nextStep()">تحقق من الرمز ▶</button>
</div>
</div>
<!-- SCREEN 3: Driver Info -->
<div class="screen" id="sc3">
<div class="app-bar">
<h2>المعلومات الشخصية</h2>
<p>الخطوة 1 من 3</p>
</div>
<div class="screen-body">
<div class="field"><label>الاسم الأول</label>
<input class="field-input filled" value="محمد" readonly/></div>
<div class="field"><label>اسم العائلة</label>
<input class="field-input filled" value="الأحمد" readonly/></div>
<div class="field"><label>رقم الهوية الوطنية</label>
<input class="field-input filled" value="012345678" readonly/></div>
<div class="field"><label>تاريخ الميلاد</label>
<input class="field-input filled" value="1990" readonly/></div>
<div class="field"><label>انتهاء رخصة القيادة</label>
<input class="field-input filled" value="2026-12-31" readonly/></div>
<button class="btn-submit" onclick="nextStep()">التالي: معلومات المركبة ▶</button>
</div>
</div>
<!-- SCREEN 4: Car Info -->
<div class="screen" id="sc4">
<div class="app-bar">
<h2>معلومات المركبة</h2>
<p>الخطوة 2 من 3</p>
</div>
<div class="screen-body">
<div class="field"><label>لوحة السيارة</label>
<input class="field-input filled" value="155186 درعا" readonly/></div>
<div class="field"><label>الشركة المصنعة</label>
<input class="field-input filled" value="Hyundai" readonly/></div>
<div class="field"><label>الموديل</label>
<input class="field-input filled" value="Elantra" readonly/></div>
<div class="field"><label>سنة الصنع</label>
<input class="field-input filled" value="2019" readonly/></div>
<div class="field"><label>نوع الوقود</label>
<input class="field-input filled" value="Petrol (fuel_type_id: 1)" readonly/></div>
<div class="field"><label>تصنيف المركبة</label>
<input class="field-input filled" value="سيارة (vehicle_category_id: 1)" readonly/></div>
<button class="btn-submit" onclick="nextStep()">التالي: رفع الوثائق ▶</button>
</div>
</div>
<!-- SCREEN 5: Doc Upload -->
<div class="screen" id="sc5">
<div class="app-bar">
<h2>الوثائق المطلوبة</h2>
<p>الخطوة 3 من 3 — سوريا</p>
</div>
<div class="screen-body">
<div class="doc-grid" id="docGrid">
<div class="doc-card" id="doc_id_front">
<div class="doc-icon">🪪</div>
<div>هوية — وجه</div>
</div>
<div class="doc-card" id="doc_id_back">
<div class="doc-icon">🪪</div>
<div>هوية — خلف</div>
</div>
<div class="doc-card" id="doc_driver_license">
<div class="doc-icon">📋</div>
<div>رخصة القيادة — وجه</div>
</div>
<div class="doc-card" id="doc_driver_license_back">
<div class="doc-icon">📋</div>
<div>رخصة القيادة — خلف <br/><small style="color:var(--amber)">🇸🇾 إلزامي</small></div>
</div>
<div class="doc-card" id="doc_profile_picture">
<div class="doc-icon">🤳</div>
<div>صورة شخصية</div>
</div>
<div class="doc-card" id="doc_criminal_record">
<div class="doc-icon">📄</div>
<div>لا حكم عليه</div>
</div>
<div class="doc-card" id="doc_car_license_front">
<div class="doc-icon">🚗</div>
<div>ترخيص المركبة — وجه</div>
</div>
<div class="doc-card" id="doc_car_license_back">
<div class="doc-icon">🚗</div>
<div>ترخيص المركبة — خلف</div>
</div>
</div>
<button class="btn-submit" id="docBtn" onclick="nextStep()" disabled>في انتظار رفع الوثائق...</button>
</div>
</div>
<!-- SCREEN 6: AI Processing -->
<div class="screen" id="sc6">
<div class="app-bar" style="background:linear-gradient(135deg,#6b21a8,#4338ca);">
<h2>🤖 معالجة الذكاء الاصطناعي</h2>
<p>Gemini Flash — تحليل الوثائق</p>
</div>
<div class="screen-body">
<div class="ai-badge">
<div class="ai-spin" id="aiSpin"></div>
<div>Gemini يحلل الوثائق ويطابق الوجه...</div>
</div>
<div id="aiSteps" style="display:flex;flex-direction:column;gap:6px;font-size:11px;"></div>
<div class="div"></div>
<div id="aiResult" style="display:none;background:rgba(34,197,94,.1);border:1px solid var(--green);
border-radius:8px;padding:10px 12px;font-size:11px;line-height:1.8;"></div>
</div>
</div>
<!-- SCREEN 7: Pending Review -->
<div class="screen" id="sc7">
<div class="app-bar" style="background:linear-gradient(135deg,#92400e,#b45309);">
<h2>قيد المراجعة ⏳</h2>
<p>خدمة العملاء تراجع وثائقك</p>
</div>
<div class="screen-body">
<div class="status-card">
<div class="status-icon"></div>
<div class="status-info">
<h3>الحساب قيد المراجعة</h3>
<p>status = "yet" في جدول driver</p>
</div>
</div>
<div style="font-size:11px;color:var(--muted);line-height:1.8;margin-bottom:14px;">
✅ تم رفع وثائقك بنجاح<br/>
📬 تم إشعار فريق خدمة العملاء<br/>
🔔 FCM إلى موضوع "service"<br/>
⏱️ المراجعة تستغرق 24-48 ساعة
</div>
<div style="background:var(--s2);border:1px solid var(--bdr);border-radius:8px;
padding:10px 12px;font-size:11px;line-height:1.8;">
<div>📊 <b>driver.status:</b> <span style="color:var(--amber)">yet</span></div>
<div>📊 <b>CarRegistration.status:</b> <span style="color:var(--amber)">yet</span></div>
<div>📊 <b>driver_documents:</b> <span style="color:var(--green)">8 records ✓</span></div>
</div>
</div>
</div>
<!-- SCREEN 8: Activated -->
<div class="screen" id="sc8">
<div class="app-bar" style="background:linear-gradient(135deg,#064e3b,#065f46);">
<h2>🎉 تم التفعيل!</h2>
<p>خدمة العملاء فعّلت حسابك</p>
</div>
<div class="screen-body">
<div style="text-align:center;padding:20px 0;">
<div style="font-size:52px;margin-bottom:12px;"></div>
<div style="font-size:15px;font-weight:700;margin-bottom:6px;color:var(--green);">
مرحباً محمد الأحمد!
</div>
<div style="font-size:12px;color:var(--muted);">حسابك مفعّل وجاهز للعمل</div>
</div>
<div style="background:rgba(34,197,94,.1);border:1px solid var(--green);
border-radius:8px;padding:10px 12px;font-size:11px;line-height:1.8;margin-bottom:12px;">
<div><b>driver.status:</b> <span style="color:var(--green)">active</span></div>
<div><b>CarRegistration.status:</b> <span style="color:var(--green)">active</span></div>
<div>🔔 FCM إشعار للسائق: "تم تفعيل حسابك"</div>
<div>🔑 JWT Token صادر + تخزين في driverToken</div>
</div>
<button class="btn-submit" style="background:linear-gradient(135deg,var(--green),#16a34a)">
🚗 ابدأ العمل الآن!
</button>
</div>
</div>
</div><!-- /phone-screen -->
</div><!-- /phone -->
</div><!-- /phone-wrap -->
<!-- Controls -->
<div class="ctrl-row">
<button class="btn btn-pri" id="btnNext" onclick="nextStep()">▶ التالي</button>
<button class="btn btn-amb" id="btnAuto" onclick="toggleAuto()">⚡ تلقائي</button>
<button class="btn btn-ghost" onclick="resetSim()">↺ إعادة</button>
</div>
</div>
<!-- ── RIGHT PANEL : Logs & DB ── -->
<div class="rpanel">
<div class="panel-title">تحليل تقني</div>
<div class="stats">
<div class="stat"><div class="stat-lbl">المرحلة</div><div class="stat-val" id="stPhase">0/8</div></div>
<div class="stat"><div class="stat-lbl">OTP Status</div><div class="stat-val" id="stOtp" style="color:var(--muted)"></div></div>
<div class="stat"><div class="stat-lbl">وثائق مرفوعة</div><div class="stat-val" id="stDocs">0/8</div></div>
<div class="stat"><div class="stat-lbl">AI Gemini</div><div class="stat-val" id="stAI" style="color:var(--muted)"></div></div>
</div>
<div class="div"></div>
<div class="panel-title">تدفق API</div>
<div class="flow" id="flowDiagram"></div>
<div class="div"></div>
<div class="panel-title">سجل الطلبات</div>
<div class="api-log" id="apiLog"></div>
<div class="div"></div>
<div class="panel-title">قاعدة البيانات — schema_primary</div>
<div id="dbPreviews"></div>
</div>
</div><!-- /main -->
<div class="toast-wrap" id="toastWrap"></div>
<script>
// ═══════════════════════════════════════════════════
// DATA
// ═══════════════════════════════════════════════════
let simSpeed = 1.5;
let currentStep = -1;
let isAuto = false;
let autoTimer = null;
let otpTimerRef = null;
let docsUploaded = 0;
const STEPS = [
{ name:'شاشة الترحيب', sub:'onboarding_controller', color:'#64748b', icon:'🏠', badge:'START' },
{ name:'إدخال رقم الهاتف', sub:'RegisterCaptainController', color:'#06b6d4', icon:'📱', badge:'PHONE' },
{ name:'التحقق بـ OTP', sub:'OtpVerificationController', color:'#a855f7', icon:'🔐', badge:'OTP' },
{ name:'المعلومات الشخصية', sub:'RegistrationController — Page 1', color:'#5b8dff', icon:'👤', badge:'STEP1' },
{ name:'معلومات المركبة', sub:'RegistrationController — Page 2', color:'#5b8dff', icon:'🚗', badge:'STEP2' },
{ name:'رفع الوثائق', sub:'uploadToSyria() × 8', color:'#f59e0b', icon:'📂', badge:'DOCS' },
{ name:'AI Gemini تحليل', sub:'Gemini Flash — مطابقة وجه + OCR', color:'#a855f7', icon:'🤖', badge:'AI' },
{ name:'قيد المراجعة', sub:'خدمة العملاء — status: yet', color:'#f59e0b', icon:'⏳', badge:'PENDING' },
{ name:'تم التفعيل', sub:'Admin Panel — status: active', color:'#22c55e', icon:'✅', badge:'ACTIVE' },
];
const FLOW_NODES = [
{ id:'otp_req', label:'POST /auth/otp/request.php', detail:'Intaleq WhatsApp' },
{ id:'otp_verify', label:'POST /auth/otp/verify.php', detail:'AES-GCM encrypted DB' },
{ id:'doc_upload', label:'POST /uploadSyrianDocs.php × 8', detail:'Signed URL — private_uploads' },
{ id:'ai_process', label:'Gemini Flash — Vision AI', detail:'Face match + OCR + JSON' },
{ id:'register', label:'POST /register_driver_and_car.php', detail:'Transaction: driver + CarRegistration' },
{ id:'notify', label:'FCM → topic:service', detail:'Admin panel notification' },
{ id:'activate', label:'Admin → status:active', detail:'JWT issued + driverToken stored' },
];
const DOCS = [
'id_front','id_back','driver_license','driver_license_back',
'profile_picture','criminal_record','car_license_front','car_license_back'
];
// ═══════════════════════════════════════════════════
// INIT
// ═══════════════════════════════════════════════════
function init(){
renderStepList();
renderFlowDiagram();
renderStepsBar();
showScreen(0);
setTimeout(nextStep, 800);
}
function renderStepList(){
const el = document.getElementById('stepList');
el.innerHTML = '';
STEPS.forEach((s,i)=>{
const d = document.createElement('div');
d.className = 'step' + (i===currentStep?' active':(i<currentStep?' done':''));
d.id = `ste_${i}`;
d.onclick = ()=>jumpTo(i);
d.innerHTML = `
<div class="step-num" style="background:${s.color}22;color:${s.color}">${s.icon}</div>
<div class="step-info">
<div class="step-name">${s.name}</div>
<div class="step-sub">${s.sub}</div>
</div>
<div class="step-badge" style="background:${s.color}22;color:${s.color}">${s.badge}</div>`;
el.appendChild(d);
});
}
function renderFlowDiagram(){
const el = document.getElementById('flowDiagram');
el.innerHTML = '';
FLOW_NODES.forEach((n,i)=>{
const d = document.createElement('div');
d.className='flow-node';d.id=`fn_${n.id}`;
d.innerHTML = `<div class="flow-dot"></div><div><div style="font-weight:600">${n.label}</div><div style="color:var(--muted);font-size:10px">${n.detail}</div></div>`;
el.appendChild(d);
if(i<FLOW_NODES.length-1){
const arr=document.createElement('div');arr.className='flow-arrow';arr.textContent='↓';
el.appendChild(arr);
}
});
}
function renderStepsBar(){
const el = document.getElementById('stepsBar');
el.innerHTML='';
STEPS.forEach((_,i)=>{
const d=document.createElement('div');
d.className='bar-seg'+(i===currentStep?' active':(i<currentStep?' done':''));
d.id=`bar_${i}`;
el.appendChild(d);
});
}
function showScreen(n){
document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active'));
const sc = document.getElementById(`sc${n}`);
if(sc) sc.classList.add('active');
}
// ═══════════════════════════════════════════════════
// STEP ACTIONS
// ═══════════════════════════════════════════════════
const stepActions = [
enterWelcome,
enterPhone,
enterOTP,
enterDriverInfo,
enterCarInfo,
enterDocUpload,
enterAIProcess,
enterPending,
enterActivated,
];
function nextStep(){
if(currentStep >= STEPS.length-1){ showToast('✅ اكتملت المحاكاة!','#22c55e'); return; }
currentStep++;
showScreen(currentStep);
renderStepList();
renderStepsBar();
document.getElementById('stPhase').textContent = `${currentStep+1}/${STEPS.length}`;
if(stepActions[currentStep]) stepActions[currentStep]();
document.getElementById('btnNext').disabled = currentStep >= STEPS.length-1;
}
function jumpTo(i){
if(isAuto) toggleAuto();
// reset docs if jumping back
if(i < 5){ docsUploaded=0; DOCS.forEach(d=>{ const el=document.getElementById('doc_'+d); if(el){el.className='doc-card';el.innerHTML=`<div class="doc-icon">${docIcon(d)}</div><div>${docLabel(d)}</div>`;}}); }
currentStep = i-1;
nextStep();
}
// ─── Step 0: Welcome ───────────────────────────────
function enterWelcome(){
apiLog('🏠 تشغيل onboarding_controller.dart — فحص الدولة: Syria','info');
apiLog('📦 getStorage → countryCode: "Syria"','info');
showToast('🏠 مرحباً — بيئة سوريا محددة','#64748b');
}
// ─── Step 1: Phone ─────────────────────────────────
function enterPhone(){
setFlow('otp_req','active');
const input = document.getElementById('phoneInput');
const rawPhone = '0944123456';
const finalPhone = '963944123456';
let i = 0;
input.value = '';
input.classList.remove('filled');
const interval = setInterval(()=>{
if(i <= rawPhone.length){ input.value = rawPhone.substring(0,i); i++; }
else{ clearInterval(interval);
setTimeout(()=>{
input.value = '→ ' + finalPhone;
input.classList.add('filled');
apiLog('📱 Phone formatting: 09xx → 963xx (Syria logic)','info');
apiLog('→ strpos("09",0)===0 → "963" + substr(phone,1)','req');
}, 300/simSpeed);
}
}, 80/simSpeed);
showToast('📱 إدخال رقم الهاتف السوري','#06b6d4');
}
// ─── Step 2: OTP ────────────────────────────────────
function enterOTP(){
setFlow('otp_verify','done');
setFlow('otp_verify','active');
document.getElementById('stOtp').textContent='إرسال...';
document.getElementById('stOtp').style.color='var(--amber)';
// Countdown
let cd = 120;
if(otpTimerRef) clearInterval(otpTimerRef);
otpTimerRef = setInterval(()=>{
cd--;
const el = document.getElementById('otpTimer');
if(el) el.textContent = cd;
if(cd <= 0){ clearInterval(otpTimerRef); }
}, 1000/simSpeed);
apiLog('🔐 POST /auth/otp/request.php','req');
apiLog('→ receiver: "963944123456", user_type: "driver"','req');
apiLog('→ country auto-detect: Syria → Intaleq WhatsApp','req');
apiLog('→ OTP: 3-digit random_int() e.g. "427"','info');
apiLog('→ DB: INSERT INTO phone_verification (encrypted_phone, encrypted_otp, expires: +5min)','req');
setTimeout(()=>{
// Simulate OTP input
const digits = ['4','2','7'];
digits.forEach((d,i)=>{
setTimeout(()=>{
const box = document.getElementById(`ob${i}`);
if(box){ box.textContent=d; box.classList.add('filled'); }
if(i===2){
setTimeout(()=>{
digits.forEach((_,j)=>{ const b=document.getElementById(`ob${j}`); if(b) b.classList.replace('filled','ok'); });
apiLog('✅ POST /auth/otp/verify.php','res');
apiLog('→ SELECT WHERE phone=encrypted & token=encrypted & expiry>NOW()','res');
apiLog('→ UPDATE is_verified=1','res');
document.getElementById('stOtp').textContent='✓ تم';
document.getElementById('stOtp').style.color='var(--green)';
showToast('✅ OTP صحيح — تم التحقق','#22c55e');
}, 600/simSpeed);
}
}, i*400/simSpeed);
});
}, 1500/simSpeed);
}
// ─── Step 3: Driver Info ────────────────────────────
function enterDriverInfo(){
setFlow('otp_verify','done');
apiLog('👤 RegistrationController — Page 0 (driverInfoFormKey)','info');
apiLog('→ validate(): firstName, lastName, nationalId, birthdate, licenseExpiry','info');
showToast('👤 معلومات السائق الشخصية','#5b8dff');
}
// ─── Step 4: Car Info ───────────────────────────────
function enterCarInfo(){
apiLog('🚗 RegistrationController — Page 1 (carInfoFormKey)','info');
apiLog('→ carPlate, make, model, year, color_hex, vehicle_category_id, fuel_type_id','info');
apiLog('→ فئة: 1 (Car) | وقود: 1 (Petrol)','info');
showToast('🚗 معلومات المركبة','#5b8dff');
}
// ─── Step 5: Doc Upload ─────────────────────────────
function enterDocUpload(){
setFlow('doc_upload','active');
docsUploaded = 0;
document.getElementById('stDocs').textContent = '0/8';
document.getElementById('docBtn').disabled = true;
document.getElementById('docBtn').textContent = 'في انتظار رفع الوثائق...';
apiLog('📂 بدء رفع الوثائق — uploadToSyria() × 8','info');
apiLog('→ 🇸🇾 Syria: driver_license_back إلزامي','info');
apiLog('→ Compress: quality=70, maxWidth=1024px (EXIF-fixed)','info');
apiLog('→ POST /uploadSyrianDocs.php — MultipartRequest','req');
apiLog('→ Headers: Authorization:Bearer JWT, X-HMAC-Auth','req');
apiLog('→ Response: { status:"success", file_url: Signed URL (2 days TTL) }','info');
DOCS.forEach((docId, idx)=>{
setTimeout(()=>{
const card = document.getElementById('doc_'+docId);
if(!card) return;
card.className = 'doc-card uploading';
card.innerHTML = `<div class="ai-spin" style="width:18px;height:18px;border-color:rgba(245,158,11,.3);border-top-color:var(--amber)"></div><div>${docLabel(docId)}</div><div style="color:var(--amber);font-size:10px">Uploading...</div>`;
setTimeout(()=>{
card.className = 'doc-card done';
card.innerHTML = `<div class="doc-icon">${docIcon(docId)}</div><div>${docLabel(docId)}</div><div style="font-size:10px">✅ Signed URL</div>`;
docsUploaded++;
document.getElementById('stDocs').textContent = `${docsUploaded}/8`;
apiLog(`${docId} → secured_url/siro/secure_image.php?driver_id=...&sig=...`,'res');
if(docsUploaded === 8){
setFlow('doc_upload','done');
document.getElementById('docBtn').disabled = false;
document.getElementById('docBtn').textContent = '✅ جميع الوثائق مرفوعة — إرسال للمراجعة ▶';
showToast('📂 تم رفع كل الوثائق بنجاح!','#22c55e');
}
}, 900/simSpeed);
}, idx * 700/simSpeed);
});
showToast('📂 جاري رفع الوثائق...','#f59e0b');
}
// ─── Step 6: AI Process ─────────────────────────────
function enterAIProcess(){
setFlow('ai_process','active');
document.getElementById('stAI').textContent='⚙️ يعمل';
document.getElementById('stAI').style.color='var(--purple)';
const aiSteps = document.getElementById('aiSteps');
aiSteps.innerHTML = '';
const aiResult = document.getElementById('aiResult');
aiResult.style.display = 'none';
const logs = [
['🔗 SSRF Check: validate doc URLs against allowedHosts','info'],
['📥 تحميل الصور كـ base64 → Gemini Flash','ai'],
['👁️ Gemini: فحص الهوية الوطنية (وجه + خلف)','ai'],
['🪪 OCR: استخراج الاسم + الرقم الوطني + DOB','ai'],
['📋 OCR: رخصة القيادة — فئة، انتهاء، رقم الرخصة','ai'],
['🚗 OCR: ترخيص المركبة — VIN، لوحة، اللون','ai'],
['🤳 FACE MATCHING: صورة شخصية ↔ هوية ↔ رخصة','ai'],
['📄 فحص وثيقة "لا حكم عليه" — التحقق من الاسم','ai'],
['✅ AI status: "success", face_match_confidence: "high"','res'],
];
logs.forEach((log, i)=>{
setTimeout(()=>{
const d = document.createElement('div');
d.style.cssText='display:flex;gap:6px;align-items:flex-start;padding:4px 0;border-bottom:1px solid rgba(255,255,255,.04);';
d.innerHTML = `<span style="flex-shrink:0;font-size:9px;color:var(--muted);margin-top:2px">${String(i+1).padStart(2,'0')}</span><span>${log[0]}</span>`;
aiSteps.appendChild(d);
apiLog(log[0], log[1]);
if(i === logs.length-1){
setTimeout(()=>{
document.getElementById('aiSpin').style.display='none';
aiResult.style.display = 'block';
aiResult.innerHTML = `
<b style="color:var(--green)">✅ AI Response JSON:</b><br/>
<code style="color:var(--cyan);">{</code><br/>
&nbsp;"status": <span style="color:var(--green)">"success"</span>,<br/>
&nbsp;"face_match_confidence": <span style="color:var(--green)">"high"</span>,<br/>
&nbsp;"driver": { full_name: <span style="color:var(--amber)">"محمد الأحمد"</span>, dob: <span style="color:var(--amber)">"1990-01-01"</span> },<br/>
&nbsp;"car": { car_plate: <span style="color:var(--amber)">"155186 درعا"</span>, vin: <span style="color:var(--amber)">"KMH..."</span> }<br/>
<code style="color:var(--cyan);">}</code>`;
document.getElementById('stAI').textContent='✓ نجح';
document.getElementById('stAI').style.color='var(--green)';
showToast('🤖 AI: مطابقة ناجحة — face_match: high','#a855f7');
}, 500/simSpeed);
}
}, i * 500/simSpeed);
});
}
// ─── Step 7: Pending ────────────────────────────────
function enterPending(){
setFlow('ai_process','done');
setFlow('register','done');
setFlow('notify','done');
apiLog('📤 POST /register_driver_and_car.php — Transaction','req');
apiLog('→ 1) Phone format: 09xx → 963xx (Syria)','req');
apiLog('→ 2) birthdate: "1990" → "1990-01-01"','req');
apiLog('→ 3) AES-GCM Encrypt: phone, email, first_name, last_name, national_number...','req');
apiLog('→ 4) Password: HMAC-SHA256(id|phone|nationalNum) → bcrypt','req');
apiLog('→ 5) Check duplicate: SELECT WHERE phone=enc OR email=enc','req');
apiLog('→ 6) INSERT driver (status="yet")','req');
apiLog('→ 7) INSERT CarRegistration (vehicle_category_id=1, fuel_type_id=1, status="yet")','req');
apiLog('→ 8) INSERT driver_documents × 8 + imageProfileCaptain','req');
apiLog('→ 9) COMMIT transaction','req');
apiLog('→ 10) FCM: topic:service "سائق جديد" notification','req');
apiLog('✅ { status:"success", driverID:"DRV2024...", carRegID:1796 }','res');
renderDBPreview();
showToast('⏳ تم التسجيل — قيد مراجعة خدمة العملاء','#f59e0b');
}
// ─── Step 8: Activated ──────────────────────────────
function enterActivated(){
setFlow('notify','done');
setFlow('activate','done');
apiLog('🔑 Admin Panel → UPDATE driver SET status="active"','res');
apiLog('🔑 Admin Panel → UPDATE CarRegistration SET status="active"','res');
apiLog('🔔 FCM Push → driverToken: "تم تفعيل حسابك!"','res');
apiLog('🔑 JWT issued → INSERT driverToken (captain_id, fingerPrint)','res');
apiLog('✅ السائق جاهز للعمل','res');
showToast('🎉 تم تفعيل حساب السائق!','#22c55e');
}
// ═══════════════════════════════════════════════════
// HELPERS
// ═══════════════════════════════════════════════════
function setFlow(id, state){
const n = document.getElementById(`fn_${id}`);
if(!n) return;
n.className = 'flow-node ' + state;
}
function apiLog(msg, type='info'){
const el = document.getElementById('apiLog');
const now = new Date();
const t = `${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}:${String(now.getSeconds()).padStart(2,'0')}`;
const d = document.createElement('div');
d.className = `api-entry ${type}`;
d.innerHTML = `<div class="api-time">${t}</div>${msg}`;
el.insertBefore(d, el.firstChild);
while(el.children.length > 50) el.removeChild(el.lastChild);
}
function renderDBPreview(){
const el = document.getElementById('dbPreviews');
el.innerHTML = `
<div class="db-preview">
<div class="db-header">⚡ driver</div>
<div class="db-row"><span class="db-key">id</span><span class="db-val">DRV20240619xxx</span></div>
<div class="db-row"><span class="db-key">phone</span><span class="db-val enc">AES{963944...}</span></div>
<div class="db-row"><span class="db-key">first_name</span><span class="db-val enc">AES{محمد}</span></div>
<div class="db-row"><span class="db-key">national_number</span><span class="db-val enc">AES{012345678}</span></div>
<div class="db-row"><span class="db-key">password</span><span class="db-val enc">bcrypt(HMAC(...))</span></div>
<div class="db-row"><span class="db-key">status</span><span class="db-val pending">yet</span></div>
</div>
<div class="db-preview" style="margin-top:8px">
<div class="db-header">⚡ CarRegistration</div>
<div class="db-row"><span class="db-key">driverID</span><span class="db-val">DRV20240619xxx</span></div>
<div class="db-row"><span class="db-key">car_plate</span><span class="db-val enc">AES{155186 درعا}</span></div>
<div class="db-row"><span class="db-key">vin</span><span class="db-val enc">AES{KMH...}</span></div>
<div class="db-row"><span class="db-key">vehicle_category_id</span><span class="db-val">1 (Car)</span></div>
<div class="db-row"><span class="db-key">fuel_type_id</span><span class="db-val">1 (Petrol)</span></div>
<div class="db-row"><span class="db-key">status</span><span class="db-val pending">yet</span></div>
</div>
<div class="db-preview" style="margin-top:8px">
<div class="db-header">⚡ driver_documents (8 records)</div>
${DOCS.map(d=>`<div class="db-row"><span class="db-key">${d}</span><span class="db-val">Signed URL ✓</span></div>`).join('')}
</div>`;
}
function docLabel(id){
const m = {
id_front:'هوية — وجه',id_back:'هوية — خلف',
driver_license:'رخصة — وجه',driver_license_back:'رخصة — خلف 🇸🇾',
profile_picture:'صورة شخصية',criminal_record:'لا حكم عليه',
car_license_front:'ترخيص سيارة — وجه',car_license_back:'ترخيص سيارة — خلف'
};
return m[id]||id;
}
function docIcon(id){
const m = {id_front:'🪪',id_back:'🪪',driver_license:'📋',driver_license_back:'📋',
profile_picture:'🤳',criminal_record:'📄',car_license_front:'🚗',car_license_back:'🚗'};
return m[id]||'📎';
}
function showToast(msg, color='#5b8dff'){
const wrap = document.getElementById('toastWrap');
const t = document.createElement('div');
t.className='toast';t.style.borderColor=color;t.style.color=color;
t.textContent=msg;wrap.appendChild(t);
setTimeout(()=>{ t.style.opacity='0';t.style.transition='opacity .4s';
setTimeout(()=>t.remove(),400); },2800);
}
function toggleAuto(){
if(isAuto){
clearTimeout(autoTimer);isAuto=false;
document.getElementById('btnAuto').textContent='⚡ تلقائي';
document.getElementById('btnAuto').className='btn btn-amb';
} else {
isAuto=true;
document.getElementById('btnAuto').textContent='⏸ إيقاف';
document.getElementById('btnAuto').className='btn btn-red';
const delays = [2000,2500,3000,2500,2500,9000,6000,3000,3000];
const go = ()=>{
if(currentStep >= STEPS.length-1){ toggleAuto(); return; }
nextStep();
const d = (delays[currentStep]||2500)/simSpeed;
autoTimer = setTimeout(go, d);
};
autoTimer = setTimeout(go, 1500/simSpeed);
}
}
function resetSim(){
if(isAuto) toggleAuto();
if(otpTimerRef) clearInterval(otpTimerRef);
currentStep = -1;docsUploaded=0;
document.getElementById('stPhase').textContent='0/8';
document.getElementById('stOtp').textContent='—';document.getElementById('stOtp').style.color='var(--muted)';
document.getElementById('stDocs').textContent='0/8';
document.getElementById('stAI').textContent='—';document.getElementById('stAI').style.color='var(--muted)';
document.getElementById('apiLog').innerHTML='';
document.getElementById('dbPreviews').innerHTML='';
document.getElementById('btnNext').disabled=false;
document.getElementById('aiSpin').style.display='';
document.getElementById('aiSteps').innerHTML='';
document.getElementById('aiResult').style.display='none';
DOCS.forEach(d=>{ const el=document.getElementById('doc_'+d); if(el){el.className='doc-card';el.innerHTML=`<div class="doc-icon">${docIcon(d)}</div><div>${docLabel(d)}</div>`;}});
FLOW_NODES.forEach(n=>{ const el=document.getElementById(`fn_${n.id}`); if(el) el.className='flow-node'; });
renderStepList();renderStepsBar();showScreen(0);
setTimeout(nextStep,600);
}
function setSpeed(v){ simSpeed=parseFloat(v); document.getElementById('spdLbl').textContent=`${v}×`; }
window.onload = init;
</script>
</body>
</html>