neya / static /project.html
my0919175's picture
Upload 20 files
50c11e2 verified
Raw
History Blame Contribute Delete
46.8 kB
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>تفاصيل المشروع — نيّة</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700;800&family=IBM+Plex+Sans+Arabic:wght@300;400;500;600&display=swap');
:root{
--bg:#FFFFFF;
--ink:#0A0A0A;
--line: rgba(10,10,10,0.12);
--line-strong: rgba(10,10,10,0.22);
--muted: rgba(10,10,10,0.55);
--muted-soft: rgba(10,10,10,0.4);
--surface: rgba(10,10,10,0.03);
--surface-hover: rgba(10,10,10,0.05);
--radius: 14px;
--max-w: 1080px;
}
*{ box-sizing:border-box; }
html,body{ margin:0; padding:0; }
/* ---------- app polish: smooth motion + native app feel ---------- */
@media (prefers-reduced-motion: no-preference){
body{ animation: pageFadeIn .28s ease both; }
.card, .interest-item, .grid > *{ animation: cardRise .32s ease both; }
.overlay.show .modal{ animation: modalPop .22s cubic-bezier(.2,.8,.3,1.2) both; }
.toast.show{ animation: toastRise .3s ease both; }
}
@keyframes pageFadeIn{ from{ opacity:0; transform:translateY(6px);} to{ opacity:1; transform:none; } }
@keyframes cardRise{ from{ opacity:0; transform:translateY(8px);} to{ opacity:1; transform:none; } }
@keyframes modalPop{ from{ opacity:0; transform:scale(.96) translateY(8px);} to{ opacity:1; transform:none; } }
@keyframes toastRise{ from{ opacity:0; transform:translateX(-50%) translateY(14px);} to{ opacity:1; transform:translateX(-50%) translateY(0);} }
.btn, .nav-link, .file-drop, .interest-item, .card{ transition: all .18s ease; }
.btn:active{ transform: scale(.96); }
.btn-primary:active{ opacity:.85; }
a.card:hover, .card:hover{ box-shadow: 0 4px 18px rgba(10,10,10,0.06); }
input, textarea, select{ transition: border-color .15s ease, box-shadow .15s ease; }
input:focus, textarea:focus, select:focus{ box-shadow: 0 0 0 3px rgba(10,10,10,0.06); }
::-webkit-scrollbar{ width:8px; height:8px; }
::-webkit-scrollbar-thumb{ background: rgba(10,10,10,0.15); border-radius:8px; }
@supports (padding: max(0px)){
body{ padding-bottom: env(safe-area-inset-bottom); }
.topbar{ padding-top: max(18px, env(safe-area-inset-top)); }
}
@media (max-width:720px){
.btn{ font-size:14px; }
.card{ border-radius:16px; }
.modal{ border-radius:20px 20px 0 0; position:fixed; bottom:0; left:0; right:0; max-width:100%; max-height:85vh; margin:0; }
.overlay{ align-items:flex-end; padding:0; }
.overlay.show .modal{ animation: sheetUp .25s cubic-bezier(.2,.8,.3,1) both; }
}
@keyframes sheetUp{ from{ transform:translateY(100%);} to{ transform:translateY(0);} }
body{
background: var(--bg);
color: var(--ink);
font-family:'IBM Plex Sans Arabic', sans-serif;
min-height:100vh;
-webkit-font-smoothing: antialiased;
line-height:1.6;
}
h1,h2,h3,.display,.btn,.brand-name{ font-family:'Cairo', sans-serif; }
a{ color: inherit; text-decoration:none; }
img{ max-width:100%; display:block; }
/* ---------- layout ---------- */
.page{ max-width: var(--max-w); margin: 0 auto; padding: 0 20px 60px; }
.center-page{ max-width: 440px; margin: 0 auto; padding: 20px 20px 60px; }
/* ---------- topbar ---------- */
.topbar{
display:flex; justify-content:space-between; align-items:center;
padding: 18px 24px; border-bottom: 1px solid var(--line);
position: sticky; top:0; background: var(--bg); z-index: 40;
}
.brand{ display:flex; align-items:center; gap:9px; }
.brand-mark{
width:30px; height:30px; border-radius:50%;
border: 2px solid var(--ink); position:relative; flex-shrink:0;
}
.brand-mark::after{
content:''; position:absolute; inset:7px; border-radius:50%; background: var(--ink);
}
.brand-name{ font-weight:800; font-size:20px; letter-spacing:0.3px; }
.nav-links{ display:flex; align-items:center; gap:6px; }
.nav-link{
padding:9px 14px; border-radius:999px; font-size:14px; font-weight:600; color: var(--muted);
transition: all .15s ease;
}
.nav-link:hover{ background: var(--surface); color: var(--ink); }
.nav-link.active{ background: var(--ink); color: var(--bg); }
.hamburger{ display:none; }
@media (max-width:720px){
.nav-links{ display:none; }
.hamburger{ display:block; }
}
/* ---------- buttons ---------- */
.btn{
display:inline-flex; align-items:center; justify-content:center; gap:8px;
padding: 13px 26px; border-radius: 11px; border: 1.5px solid var(--ink); cursor:pointer;
font-weight:700; font-size:14.5px; transition: all .15s ease; background: transparent; color: var(--ink);
}
.btn-primary{ background: var(--ink); color: var(--bg); }
.btn-primary:hover{ opacity:0.82; transform: translateY(-1px); }
.btn-ghost{ background: transparent; color: var(--ink); }
.btn-ghost:hover{ background: var(--surface); }
.btn-block{ width:100%; }
.btn:disabled{ opacity:0.4; cursor:not-allowed; }
.btn-sm{ padding: 9px 16px; font-size:13px; border-radius:9px; }
/* ---------- cards ---------- */
.card{
background: var(--bg); border: 1.5px solid var(--line); border-radius: var(--radius);
padding: 22px; transition: border-color .15s ease, transform .15s ease;
}
.card:hover{ border-color: var(--line-strong); }
/* ---------- forms ---------- */
.field{ margin-bottom:18px; }
.field label{ display:block; font-size:13.5px; font-weight:700; margin-bottom:8px; }
.field .hint{ font-size:12px; color: var(--muted-soft); margin-top:6px; line-height:1.6; }
input, textarea, select{
width:100%; padding: 13px 15px; border-radius:10px; border: 1.5px solid var(--line);
background: var(--bg); color: var(--ink); font-family:inherit; font-size:14.5px;
}
input:focus, textarea:focus, select:focus{ outline:none; border-color: var(--ink); }
textarea{ resize:vertical; min-height:100px; }
.field-row{ display:flex; gap:14px; }
.field-row .field{ flex:1; }
@media (max-width:520px){ .field-row{ flex-direction:column; gap:0; } }
.file-drop{
border: 1.5px dashed var(--line-strong); border-radius:12px; padding: 26px 16px;
text-align:center; cursor:pointer; transition: all .15s ease; font-size:13.5px; color: var(--muted);
}
.file-drop:hover{ border-color: var(--ink); background: var(--surface); }
.file-list{ display:flex; flex-wrap:wrap; gap:10px; margin-top:12px; }
.file-thumb{
width:64px; height:64px; border-radius:8px; overflow:hidden; border:1px solid var(--line); position:relative;
}
.file-thumb img{ width:100%; height:100%; object-fit:cover; }
.file-chip{
display:flex; align-items:center; gap:6px; padding:6px 12px; border:1px solid var(--line);
border-radius:999px; font-size:12px;
}
/* ---------- grid / misc ---------- */
.grid{ display:grid; grid-template-columns: repeat(auto-fill, minmax(290px,1fr)); gap:18px; }
.section-head{ display:flex; justify-content:space-between; align-items:flex-end; margin: 30px 0 20px; flex-wrap:wrap; gap:10px; }
.section-head h2{ font-size:22px; font-weight:800; margin:0; }
.muted{ color: var(--muted); }
.badge{
display:inline-block; font-size:11px; font-weight:700; padding:4px 11px; border-radius:999px;
border: 1px solid var(--line-strong); color: var(--muted);
}
.badge-ink{ background: var(--ink); color: var(--bg); border-color: var(--ink); }
.empty{ text-align:center; padding: 60px 20px; color: var(--muted-soft); border: 1.5px dashed var(--line); border-radius: var(--radius); }
.divider{ height:1px; background: var(--line); margin: 24px 0; border:none; }
.progress-track{ height:8px; background: var(--surface); border-radius:999px; overflow:hidden; }
.progress-fill{ height:100%; width:0; background: var(--ink); border-radius:999px; transition: width .4s ease; }
.toast{
position:fixed; bottom:26px; left:50%; transform:translateX(-50%) translateY(20px);
background: var(--ink); color: var(--bg); padding:14px 24px; border-radius:12px;
font-family:'Cairo',sans-serif; font-weight:700; font-size:14px; opacity:0; transition: all .3s ease;
pointer-events:none; z-index:100; white-space:nowrap; max-width: 90vw; text-align:center;
}
.toast.show{ opacity:1; transform:translateX(-50%) translateY(0); }
.spinner{
width:20px; height:20px; border:2.5px solid var(--line); border-top-color: var(--ink);
border-radius:50%; animation: spin .7s linear infinite; margin: 0 auto;
}
@keyframes spin{ to{ transform: rotate(360deg); } }
.loading-row{ display:flex; justify-content:center; padding: 40px 0; }
/* ---------- modal ---------- */
.overlay{
position:fixed; inset:0; background: rgba(10,10,10,0.5);
display:none; align-items:center; justify-content:center; z-index:60; padding:20px;
}
.overlay.show{ display:flex; }
.modal{
background: var(--bg); border: 1.5px solid var(--ink); border-radius: 18px;
width:100%; max-width:460px; padding:26px; max-height:88vh; overflow-y:auto; position:relative;
}
.modal h3{ font-size:19px; margin:0 0 16px; }
.close-x{
position:absolute; top:18px; left:18px; background:none; border:none; font-size:19px; cursor:pointer; opacity:0.5;
}
.close-x:hover{ opacity:1; }
/* footer */
.footer-note{ text-align:center; padding: 40px 20px 30px; color: var(--muted-soft); font-size:12.5px; }
@media (max-width: 640px){
.topbar{ padding:14px 16px; }
.page{ padding: 0 14px 50px; }
}
</style>
<style>
.gallery{ display:grid; grid-template-columns: repeat(auto-fit, minmax(140px,1fr)); gap:10px; margin: 18px 0; }
.gallery img{ width:100%; aspect-ratio:4/3; object-fit:cover; border-radius:10px; border:1px solid var(--line); cursor:pointer; }
.p-header{ display:flex; justify-content:space-between; align-items:flex-start; gap:14px; flex-wrap:wrap; margin: 30px 0 6px; }
.p-header h1{ font-size: clamp(22px,4vw,30px); font-weight:800; margin:0 0 6px; }
.two-col{ display:grid; grid-template-columns: 1.6fr 1fr; gap:26px; align-items:start; margin-top:10px; }
@media (max-width:800px){ .two-col{ grid-template-columns:1fr; } }
.sticky-box{ position:sticky; top:90px; display:flex; flex-direction:column; gap:16px; }
.stat-line{ display:flex; justify-content:space-between; font-size:13.5px; margin-bottom:6px; }
.study-link{ display:flex; align-items:center; gap:8px; padding:12px 14px; border:1.5px solid var(--line); border-radius:10px; font-size:13.5px; margin-top:8px; }
.interest-item{ display:flex; justify-content:space-between; padding:10px 0; border-bottom:1px solid var(--line); font-size:13.5px; }
.contact-reveal{ background: var(--ink); color: var(--bg); border-radius:12px; padding:16px; text-align:center; font-weight:700; }
/* ---------- mobile app shell (bottom tab bar, thndr-style) ---------- */
.bottom-nav{ display:none; }
@media (max-width:720px){
body{ padding-bottom: calc(64px + env(safe-area-inset-bottom)); }
.bottom-nav{
display:flex; position:fixed; bottom:0; left:0; right:0; z-index:50;
background: var(--bg); border-top:1px solid var(--line);
padding: 6px 4px calc(6px + env(safe-area-inset-bottom));
box-shadow: 0 -2px 14px rgba(10,10,10,0.05);
transform: translateY(0); transition: transform .28s cubic-bezier(.4,0,.2,1);
will-change: transform;
}
.bottom-nav.bn-hidden{ transform: translateY(100%); }
.bn-item{
flex:1; display:flex; flex-direction:column; align-items:center; gap:3px;
padding:6px 2px; border-radius:12px; color: var(--muted-soft);
font-size:10.5px; font-weight:600; transition: all .15s ease; -webkit-tap-highlight-color:transparent;
}
.bn-item svg{ width:22px; height:22px; stroke: currentColor; fill:none; stroke-width:1.8; transition: transform .15s ease; }
.bn-item.active{ color: var(--ink); }
.bn-item.active svg{ transform: translateY(-1px) scale(1.05); }
.bn-item:active{ transform: scale(.92); }
.topbar .nav-links{ display:none !important; }
.hamburger{ display:none !important; }
.topbar{ padding:14px 18px; }
.brand-name{ font-size:18px; }
}
/* ---------- final QA polish: no overflow, no overlap, no glitches ---------- */
html{ overflow-x:hidden; }
body{ overflow-x:hidden; }
img,video{ max-width:100%; height:auto; }
*{ word-wrap:break-word; overflow-wrap:break-word; }
.page, .center-page{ overflow-x:hidden; }
@media (max-width:720px){
.toast{ bottom: calc(78px + env(safe-area-inset-bottom)) !important; max-width: calc(100vw - 32px); }
.modal{ max-height: calc(85vh - env(safe-area-inset-bottom)); padding-bottom: calc(26px + env(safe-area-inset-bottom)); }
.overlay.show{ z-index:70; }
table{ display:block; overflow-x:auto; -webkit-overflow-scrolling:touch; }
}
/* ================= premium polish layer ================= */
:root{
--radius: 16px; --radius-sm: 10px; --radius-lg: 22px;
--ease: cubic-bezier(.22,1,.36,1);
--shadow-sm: 0 1px 3px rgba(10,10,10,0.05), 0 1px 2px rgba(10,10,10,0.04);
--shadow-md: 0 6px 20px rgba(10,10,10,0.07), 0 2px 6px rgba(10,10,10,0.04);
--shadow-lg: 0 14px 40px rgba(10,10,10,0.12), 0 4px 12px rgba(10,10,10,0.06);
}
*{ -webkit-tap-highlight-color: transparent; }
html{ scroll-behavior:smooth; }
body{ text-rendering:optimizeLegibility; -webkit-font-smoothing:antialiased; }
::selection{ background: var(--ink); color: var(--bg); }
::-webkit-scrollbar-thumb{ border-radius:999px; }
a, button, input, textarea, select{ font-family:inherit; }
:focus-visible{ outline: 2px solid rgba(10,10,10,0.5); outline-offset:2px; border-radius:6px; }
@keyframes fadeSlideUp{ from{ opacity:0; transform:translateY(12px); } to{ opacity:1; transform:none; } }
@keyframes fadeIn{ from{ opacity:0; } to{ opacity:1; } }
@keyframes popIn{ from{ opacity:0; transform:scale(.96) translateY(6px); } to{ opacity:1; transform:none; } }
.topbar{ backdrop-filter:saturate(1.5) blur(10px); -webkit-backdrop-filter:saturate(1.5) blur(10px); background: rgba(255,255,255,0.86); }
.btn{
border-radius: var(--radius-sm) !important; transition: transform .18s var(--ease), box-shadow .18s var(--ease), background-color .18s var(--ease), border-color .18s var(--ease), opacity .18s var(--ease);
}
.btn:hover{ box-shadow: var(--shadow-sm); }
.btn:active{ transform: scale(.96); }
.btn-primary{ box-shadow: 0 2px 8px rgba(10,10,10,0.16); }
.btn-primary:hover{ box-shadow: var(--shadow-md); transform: translateY(-1.5px); }
.btn-ghost:hover{ background: var(--surface); }
.card{
border-radius: var(--radius) !important; transition: box-shadow .28s var(--ease), transform .28s var(--ease), border-color .28s var(--ease);
animation: fadeSlideUp .5s var(--ease) both;
}
.card:hover{ box-shadow: var(--shadow-md); border-color: var(--line-strong); }
.project-card:hover .thumb img{ transform: scale(1.04); }
.thumb img{ transition: transform .5s var(--ease); }
input, textarea, select{
border-radius: var(--radius-sm) !important; transition: border-color .2s var(--ease), box-shadow .2s var(--ease), background-color .2s var(--ease);
}
input:focus, textarea:focus, select:focus{
outline:none !important; border-color: var(--ink) !important; box-shadow: 0 0 0 3.5px rgba(10,10,10,0.07);
}
.badge{ border-radius:999px !important; transition: transform .15s var(--ease); }
.modal{ animation: popIn .3s var(--ease) both; border-radius: var(--radius-lg) !important; }
.overlay{ transition: opacity .22s var(--ease); }
.overlay.show{ animation: fadeIn .22s var(--ease) both; }
.toast{ animation: fadeSlideUp .35s var(--ease) both; }
.progress-track, .progress-fill{ border-radius:999px !important; }
.progress-fill{ transition: width .6s var(--ease) !important; }
.stat-box, .stat{ transition: transform .2s var(--ease), box-shadow .2s var(--ease); border-radius: var(--radius-sm) !important; }
.stat-box:hover, .stat:hover{ transform: translateY(-2px); box-shadow: var(--shadow-sm); }
.filter-chip, .role-card{ transition: all .2s var(--ease); }
.role-card:hover{ transform: translateY(-2px); box-shadow: var(--shadow-sm); }
@media (max-width:720px){
.btn{ min-height:44px; }
input, textarea, select{ font-size:16px !important; }
.card{ border-radius: var(--radius-sm) !important; }
}
</style>
</head>
<body>
<div class="topbar" id="topbar"></div>
<div class="page" id="content" style="display:none;">
<div class="p-header">
<div>
<span class="badge" id="p-sector"></span>
<h1 id="p-name"></h1>
<div id="closed-banner" style="display:none; background:#FDECEC; border:1.5px solid #C00000; color:#7A0000; border-radius:10px; padding:10px 14px; font-size:13.5px; font-weight:700; margin-bottom:10px;"><svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><circle cx="12" cy="12" r="9"/><path d="M6 6l12 12"/></svg> صاحب المشروع قفل الطلبات على المشروع ده حاليًا</div>
<div class="muted" id="p-loc" style="font-size:13.5px;"></div>
</div>
</div>
<div class="gallery" id="gallery"></div>
<div class="card" id="owner-card" style="margin:18px 0;"></div>
<div class="two-col">
<div>
<h3 style="font-size:16px;">عن المشروع</h3>
<p id="p-desc" style="font-size:14.5px; color:var(--muted); line-height:1.9;"></p>
<div id="study-box"></div>
<hr class="divider">
<h3 style="font-size:16px;">مين سجّل نيته</h3>
<div id="interests-list"></div>
</div>
<div class="sticky-box">
<div class="card">
<div class="stat-line"><span>المُجمّع (صفقات مؤكدة فقط)</span><b id="p-committed"></b></div>
<div class="progress-track" style="margin-bottom:6px;"><div class="progress-fill" id="p-progress"></div></div>
<div id="p-pending-note" class="muted" style="font-size:11.5px; margin-bottom:10px; display:none;"></div>
<div class="stat-line"><span>الهدف</span><span id="p-target"></span></div>
<div class="stat-line"><span>أقل مشاركة</span><span id="p-min"></span></div>
<button class="btn btn-primary btn-block" style="margin-top:10px;" id="interest-btn" onclick="openInterestModal()">سجّل نيّتك</button>
<a href="guide.html" style="display:block; text-align:center; font-size:12px; color:var(--muted-soft); text-decoration:underline; margin-top:10px;">إيه اللي بيحصل بعد كده؟ اقرأ الدليل →</a>
</div>
<div class="card" id="message-card"></div>
<div class="card" id="meeting-card"></div>
<div class="card" id="share-card"></div>
</div>
</div>
</div>
<div class="loading-row" id="loading"><div class="spinner"></div></div>
<!-- INTEREST MODAL -->
<div class="overlay" id="interest-overlay">
<div class="modal">
<button class="close-x" onclick="closeModal('interest-overlay')"></button>
<h3>سجّل نيّتك</h3>
<div class="field"><label>اسمك</label><input type="text" id="i-name"></div>
<div class="field"><label>المبلغ (جنيه)</label><input type="number" id="i-amount"></div>
<div class="field"><label>وسيلة تواصل</label><input type="text" id="i-contact"></div>
<button class="btn btn-primary btn-block" onclick="submitInterest()">تأكيد</button>
</div>
</div>
<!-- REPORT MODAL -->
<div class="overlay" id="report-overlay">
<div class="modal">
<button class="close-x" onclick="closeModal('report-overlay')"></button>
<h3>الإبلاغ عن صاحب المشروع</h3>
<p class="muted" style="font-size:13px; margin-top:-8px;">نيّة مجرد وسيط توصيل بينكم، ومسؤوليتكم تتأكدوا بنفسكم قبل أي تعامل مالي. بلاغك بيوصلنا وهنراجعه.</p>
<div class="field"><label>سبب البلاغ</label><input type="text" id="r-reason" placeholder="مثال: بيانات غير حقيقية"></div>
<div class="field"><label>تفاصيل (اختياري)</label><textarea id="r-details"></textarea></div>
<button class="btn btn-primary btn-block" onclick="submitReport()">إرسال البلاغ</button>
</div>
</div>
<!-- MEETING MODAL -->
<div class="overlay" id="meeting-overlay">
<div class="modal">
<button class="close-x" onclick="closeModal('meeting-overlay')"></button>
<h3>احجز موعد</h3>
<div class="field"><label>اسمك</label><input type="text" id="m-name"></div>
<div class="field"><label>وسيلة تواصل</label><input type="text" id="m-contact"></div>
<div class="field"><label>الموعد المفضل</label><input type="datetime-local" id="m-slot"></div>
<button class="btn btn-primary btn-block" onclick="submitMeeting()">تأكيد الحجز</button>
</div>
</div>
<script>
// ---------- نيّة: طبقة الاتصال بالـ API + إدارة الجلسة ----------
const API = {
base: '',
token(){ return localStorage.getItem('neya_token'); },
user(){ try{ return JSON.parse(localStorage.getItem('neya_user') || 'null'); }catch(e){ return null; } },
isLoggedIn(){ return !!this.token(); },
setSession(token, user){
localStorage.setItem('neya_token', token);
localStorage.setItem('neya_user', JSON.stringify(user));
},
clearSession(){
localStorage.removeItem('neya_token');
localStorage.removeItem('neya_user');
},
async _fetch(path, opts = {}){
const headers = opts.headers || {};
if(this.token()) headers['Authorization'] = 'Bearer ' + this.token();
if(opts.body && !(opts.body instanceof FormData)){
headers['Content-Type'] = 'application/json';
}
const res = await fetch(this.base + path, { ...opts, headers });
let data = null;
try{ data = await res.json(); }catch(e){ /* no body */ }
if(!res.ok){
const msg = (data && data.detail) ? data.detail : 'حصل خطأ، حاول تاني';
const err = new Error(typeof msg === 'string' ? msg : JSON.stringify(msg));
err.status = res.status; // بيسمح للصفحات تفرّق بين خطأ صلاحيات (مش هيتحل بإعادة المحاولة) وخطأ شبكة عابر
throw err;
}
return data;
},
signup(payload){ return this._fetch('/api/auth/signup', { method:'POST', body: JSON.stringify(payload) }); },
login(payload){ return this._fetch('/api/auth/login', { method:'POST', body: JSON.stringify(payload) }); },
me(){ return this._fetch('/api/auth/me'); },
listProjects(){ return this._fetch('/api/projects'); },
createProject(payload){ return this._fetch('/api/projects', { method:'POST', body: JSON.stringify(payload) }); },
uploadFile(projectId, kind, file){
const fd = new FormData();
fd.append('kind', kind);
fd.append('file', file);
return this._fetch(`/api/projects/${projectId}/files`, { method:'POST', body: fd });
},
listInterests(projectId){ return this._fetch(`/api/projects/${projectId}/interests`); },
addInterest(projectId, payload){ return this._fetch(`/api/projects/${projectId}/interests`, { method:'POST', body: JSON.stringify(payload) }); },
confirmDeal(projectId, interestId, confirmedAmount){ return this._fetch(`/api/projects/${projectId}/interests/${interestId}/confirm`, { method:'POST', body: JSON.stringify({ confirmed_amount: confirmedAmount }) }); },
unconfirmDeal(projectId, interestId){ return this._fetch(`/api/projects/${projectId}/interests/${interestId}/unconfirm`, { method:'POST' }); },
bookAppointment(projectId, payload){ return this._fetch(`/api/projects/${projectId}/appointments`, { method:'POST', body: JSON.stringify(payload) }); },
listAppointments(projectId){ return this._fetch(`/api/projects/${projectId}/appointments`); },
fileUrl(filename){ return `/api/uploads/${filename}`; },
listConversations(){ return this._fetch('/api/conversations'); },
getThread(projectId, withUser){ return this._fetch(`/api/projects/${projectId}/messages?with_user=${encodeURIComponent(withUser)}`); },
sendMessage(projectId, payload){ return this._fetch(`/api/projects/${projectId}/messages`, { method:'POST', body: JSON.stringify(payload) }); },
getUserProfile(userId){ return this._fetch(`/api/users/${userId}/profile`); },
getUserProjects(userId){ return this._fetch(`/api/users/${userId}/projects`); },
listReviews(userId){ return this._fetch(`/api/users/${userId}/reviews`); },
addReview(userId, payload){ return this._fetch(`/api/users/${userId}/reviews`, { method:'POST', body: JSON.stringify(payload) }); },
submitReport(payload){ return this._fetch('/api/reports', { method:'POST', body: JSON.stringify(payload) }); },
uploadAvatar(file){
const fd = new FormData();
fd.append('file', file);
return this._fetch('/api/auth/me/avatar', { method:'POST', body: fd });
},
};
function formatMoney(n){ return Number(n || 0).toLocaleString('en-US'); }
function escapeHtml(str){
const div = document.createElement('div');
div.textContent = str || '';
return div.innerHTML;
}
function showToast(msg){
let toast = document.getElementById('global-toast');
if(!toast){
toast = document.createElement('div');
toast.id = 'global-toast';
toast.className = 'toast';
document.body.appendChild(toast);
}
toast.innerHTML = msg;
toast.classList.add('show');
clearTimeout(toast._t);
toast._t = setTimeout(() => toast.classList.remove('show'), 2800);
}
// يبني التوب بار الموحّد في كل الصفحات ويعكس حالة تسجيل الدخول
function renderBottomNav(active){
if(window.innerWidth > 720) return;
if(document.getElementById('bottom-nav')) return;
const user = API.user();
const icons = {
browse: '<svg viewBox="0 0 24 24"><path d="M12 3l9 8h-2v9h-5v-6H10v6H5v-9H3z"/></svg>',
create: '<svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>',
dashboard: '<svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></svg>',
messages: '<svg viewBox="0 0 24 24"><path d="M21 15a2 2 0 01-2 2H8l-5 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>',
profile: '<svg viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4.4 3.6-8 8-8s8 3.6 8 8"/></svg>',
login: '<svg viewBox="0 0 24 24"><path d="M15 3h4a2 2 0 012 2v14a2 2 0 01-2 2h-4M10 17l5-5-5-5M15 12H3"/></svg>',
about: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 8v5M12 16h.01"/></svg>'
};
let tabs = [
{ key:'browse', href:'index.html', label:'\u0627\u0633\u062a\u0643\u0634\u0641' },
{ key:'create', href:'create.html', label:'\u0627\u0639\u0631\u0636' },
];
if(user){
tabs.push({ key:'dashboard', href:'dashboard.html', label:'\u0644\u0648\u062d\u062a\u064a' });
tabs.push({ key:'messages', href:'messages.html', label:'\u0631\u0633\u0627\u0626\u0644' });
tabs.push({ key:'profile', href:'profile.html?id='+user.id, label:'\u062d\u0633\u0627\u0628\u064a' });
} else {
tabs.push({ key:'login', href:'login.html', label:'\u062f\u062e\u0648\u0644' });
}
tabs.push({ key:'about', href:'about.html', label:'\u062a\u0648\u0627\u0635\u0644' });
const nav = document.createElement('div');
nav.id = 'bottom-nav';
nav.className = 'bottom-nav';
nav.innerHTML = tabs.map(t => `<a class="bn-item ${t.key===active?'active':''}" href="${t.href}">${icons[t.key]}<span>${t.label}</span></a>`).join('');
document.body.appendChild(nav);
initBottomNavAutoHide(nav);
}
// اخفاء شريط التنقل السفلي عند التمرير لتحت، وإظهاره عند التمرير لفوق (زي التطبيقات الأصلية)
function initBottomNavAutoHide(nav){
let lastY = window.scrollY, ticking = false;
window.addEventListener('scroll', () => {
if(ticking) return;
ticking = true;
requestAnimationFrame(() => {
const y = window.scrollY;
const delta = y - lastY;
if(y < 40){ nav.classList.remove('bn-hidden'); }
else if(delta > 6){ nav.classList.add('bn-hidden'); }
else if(delta < -6){ nav.classList.remove('bn-hidden'); }
lastY = y;
ticking = false;
});
}, { passive:true });
}
function renderFooter(){
if(document.getElementById('global-footer-note')) return;
const el = document.createElement('p');
el.id = 'global-footer-note';
el.className = 'footer-note';
el.style.cssText = 'margin-top:8px;';
el.innerHTML = '\u0646\u064a\u0651\u0629 \u0648\u0633\u064a\u0637 \u062a\u0648\u0627\u0635\u0644 \u0641\u0642\u0637 \u00b7 <a href="terms.html" style="text-decoration:underline;">\u0627\u0644\u0634\u0631\u0648\u0637 \u0648\u0627\u0644\u0623\u062d\u0643\u0627\u0645</a> \u00b7 <a href="about.html" style="text-decoration:underline;">\u062a\u0648\u0627\u0635\u0644 \u0645\u0639\u0627\u0646\u0627</a>';
document.body.appendChild(el);
}
function renderTopbar(activePage){
const el = document.getElementById('topbar');
if(!el) return;
const user = API.user();
const links = [
{ href:'index.html', label:'استكشف المشاريع', key:'browse' },
{ href:'create.html', label:'اعرض مشروعك', key:'create' },
];
if(user){
links.push({ href:'dashboard.html', label:'لوحتي', key:'dashboard' });
links.push({ href:'messages.html', label:'الرسائل', key:'messages' });
}
const authArea = user
? `<a class="nav-link" href="profile.html?id=${user.id}">أهلاً، ${escapeHtml(user.name.split(' ')[0])}</a>
<button class="btn btn-ghost btn-sm" onclick="API.clearSession(); location.href='index.html';">خروج</button>`
: `<a class="btn btn-ghost btn-sm" href="login.html">دخول</a>
<a class="btn btn-primary btn-sm" href="signup.html">حساب جديد</a>`;
el.innerHTML = `
<a class="brand" href="index.html">
<div class="brand-mark"></div>
<div class="brand-name">نيّة</div>
</a>
<div class="nav-links">
${links.map(l => `<a class="nav-link ${l.key===activePage?'active':''}" href="${l.href}">${l.label}</a>`).join('')}
${authArea}
</div>
`;
}
// حماية صفحات لازم تسجيل دخول لها
function requireAuth(){
if(!API.isLoggedIn()){
location.href = 'login.html';
return false;
}
return true;
}
</script>
<script>
renderTopbar(null); renderFooter(); renderBottomNav('browse');
const params = new URLSearchParams(location.search);
const projectId = params.get('id');
let project = null;
async function confirmInterestDeal(interestId, defaultAmount){
const input = prompt('المبلغ الفعلي اللي اتحول (اسيبه فاضي لو نفس المبلغ المسجل):', '');
const amount = input && input.trim() ? Number(input.trim()) : null;
try{
await API.confirmDeal(projectId, interestId, amount);
showToast('اتأكدت الصفقة — دلوقتي بتتحسب في المُجمّع فعليًا');
load();
}catch(e){ showToast(e.message); }
}
async function unconfirmInterestDeal(interestId){
if(!confirm('متأكد إنك عايز تتراجع عن تأكيد الصفقة دي؟')) return;
try{
await API.unconfirmDeal(projectId, interestId);
showToast('اتلغى التأكيد');
load();
}catch(e){ showToast(e.message); }
}
async function load(){
if(!projectId){ location.href = 'index.html'; return; }
try{
const all = await API.listProjects();
project = all.find(p => p.id === projectId);
if(!project){ showToast('المشروع مش موجود'); setTimeout(()=>location.href='index.html', 1200); return; }
// الـ API بترجع object فيه {items, total_amount, ...} مش array مباشرة
let interestsData = { items: [], total_amount: 0 };
try{ interestsData = await API.listInterests(projectId); }
catch(e){ console.error('listInterests failed:', e); }
render(interestsData);
}catch(e){
console.error('load() failed:', e);
showToast('مشكلة في تحميل المشروع');
}
document.getElementById('loading').style.display = 'none';
document.getElementById('content').style.display = 'block';
}
function render(interestsData){
const interests = (interestsData && interestsData.items) || [];
const section = (fn, label) => { try{ fn(); }catch(e){ console.error('render section failed:', label, e); } };
section(() => {
document.getElementById('p-sector').textContent = project.sector;
document.getElementById('p-name').textContent = project.name;
document.getElementById('p-loc').innerHTML = (project.loc ? '<svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M12 21s7-6.3 7-11a7 7 0 10-14 0c0 4.7 7 11 7 11z"/><circle cx="12" cy="10" r="2.3"/></svg> ' + escapeHtml(project.loc) : '');
document.getElementById('p-desc').textContent = project.desc;
}, 'header');
section(() => {
const gallery = document.getElementById('gallery');
gallery.innerHTML = (project.images||[]).map(img => `<img src="${API.fileUrl(img)}">`).join('');
}, 'gallery');
section(loadOwnerCard, 'owner-card');
section(() => {
const studyBox = document.getElementById('study-box');
studyBox.innerHTML = (project.studies||[]).map(s =>
`<a class="study-link" href="${API.fileUrl(s)}" target="_blank"><svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M7 3h7l4 4v14H7z"/><path d="M14 3v4h4"/><path d="M9 12h6M9 16h6"/></svg> دراسة الجدوى / ملف داعم — اضغط للتحميل</a>`
).join('');
}, 'study-box');
section(() => {
// "المجمّع" الحقيقي = بس الصفقات اللي صاحب المشروع أكّدها فعليًا من الداشبورد،
// مش مجموع كل النوايا المسجلة. نوايا لسه مؤكدة معروضة بشكل منفصل تحت.
const confirmed = Number((interestsData && interestsData.confirmed_amount) || 0);
const pendingCount = (interestsData && interestsData.total_count || 0) - (interestsData && interestsData.confirmed_count || 0);
const pct = Math.min(100, Math.round((confirmed/(Number(project.target)||1))*100)) || 0;
document.getElementById('p-committed').textContent = formatMoney(confirmed) + ' جنيه';
document.getElementById('p-target').textContent = formatMoney(project.target) + ' جنيه';
document.getElementById('p-min').textContent = formatMoney(project.min) + ' جنيه';
document.getElementById('p-progress').style.width = pct + '%';
const pendingNote = document.getElementById('p-pending-note');
if(pendingNote){
pendingNote.textContent = pendingCount > 0 ? `+ ${pendingCount} نيّة لسه محتاجة تأكيد من صاحب المشروع` : '';
pendingNote.style.display = pendingCount > 0 ? 'block' : 'none';
}
}, 'funding-stats');
section(() => {
const me = API.user();
const isOwnerNow = !!(me && me.id === project.ownerId);
document.getElementById('interests-list').innerHTML = (interests && interests.length)
? interests.map(i => `
<div class="interest-item" style="flex-direction:column; align-items:stretch; gap:6px;">
<div style="display:flex; justify-content:space-between; align-items:center;">
<span>${escapeHtml(i.name)} ${i.confirmed ? `<span class="badge" style="font-size:10px; margin-inline-start:6px; display:inline-flex; align-items:center; gap:3px;"><svg viewBox="0 0 24 24" style="width:10px;height:10px;stroke:currentColor;fill:none;stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round"><path d="M5 13l4 4L19 7"/></svg>صفقة مؤكدة</span>` : ''}</span>
<b>${formatMoney(i.confirmedAmount != null ? i.confirmedAmount : i.amount)} جنيه</b>
</div>
${isOwnerNow ? `
<div style="display:flex; gap:8px;">
${!i.confirmed
? `<button class="btn btn-primary btn-sm" onclick="confirmInterestDeal('${i.id}', ${i.amount})"><svg viewBox="0 0 24 24" style="width:12px;height:12px;vertical-align:-1px;stroke:currentColor;fill:none;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M5 13l4 4L19 7"/></svg>تأكيد إن الصفقة اتمت فعلاً</button>`
: `<button class="btn btn-ghost btn-sm" onclick="unconfirmInterestDeal('${i.id}')">تراجع عن التأكيد</button>`}
</div>
` : ''}
</div>
`).join('')
: '<p class="muted" style="font-size:13.5px;">لسه محدش سجّل نيته — كن أول واحد.</p>';
}, 'interests-list');
const me = API.user();
const isOwner = !!(me && me.id === project.ownerId);
const isClosed = project.status === 'closed';
section(() => {
if(isOwner){
document.getElementById('message-card').innerHTML = `
<div style="font-weight:700; margin-bottom:6px;"><svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M4 5h16v11H8l-4 4z"/></svg> رسائل المشروع</div>
<p class="muted" style="font-size:13px; line-height:1.7;">شوف كل المحادثات مع المهتمين بالمشروع ده.</p>
<a class="btn btn-ghost btn-block" href="messages.html?project=${project.id}">افتح الرسائل</a>
`;
} else {
document.getElementById('message-card').innerHTML = `
<div style="font-weight:700; margin-bottom:6px;"><svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M4 5h16v11H8l-4 4z"/></svg> راسل صاحب المشروع</div>
<p class="muted" style="font-size:13px; line-height:1.7;">اسأل أي سؤال قبل ما تسجل نيتك أو تحجز موعد.</p>
<button class="btn btn-ghost btn-block" onclick="startMessage()">ابعت رسالة</button>
`;
}
}, 'message-card');
section(() => {
document.getElementById('closed-banner').style.display = isClosed ? 'block' : 'none';
document.getElementById('interest-btn').style.display = (isOwner || isClosed) ? 'none' : 'block';
}, 'interest-btn-visibility');
section(() => {
document.getElementById('meeting-card').innerHTML = isOwner ? `
<div style="font-weight:700; margin-bottom:6px;"><svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M6 3h4l1 5-2.5 2a12 12 0 006.5 6.5l2-2.5 5 1v4a2 2 0 01-2 2A17 17 0 015 5a2 2 0 011-2z"/></svg> اجتماع مباشر</div>
<p class="muted" style="font-size:13px; line-height:1.7;">ده مشروعك، مينفعش تحجز موعد مع نفسك.</p>
` : isClosed ? `
<div style="font-weight:700; margin-bottom:6px;"><svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M6 3h4l1 5-2.5 2a12 12 0 006.5 6.5l2-2.5 5 1v4a2 2 0 01-2 2A17 17 0 015 5a2 2 0 011-2z"/></svg> اجتماع مباشر</div>
<p class="muted" style="font-size:13px; line-height:1.7;">المشروع ده مقفول حاليًا ومش بيستقبل حجوزات جديدة.</p>
` : `
<div style="font-weight:700; margin-bottom:6px;"><svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M6 3h4l1 5-2.5 2a12 12 0 006.5 6.5l2-2.5 5 1v4a2 2 0 01-2 2A17 17 0 015 5a2 2 0 011-2z"/></svg> اجتماع مباشر</div>
<p class="muted" style="font-size:13px; line-height:1.7;">مجاني بالكامل — احجز مكالمة أو لقاء مباشر مع صاحب المشروع.</p>
<button class="btn btn-ghost btn-block" onclick="openModal('meeting-overlay')">احجز موعد</button>
`;
}, 'meeting-card');
section(() => {
document.getElementById('share-card').innerHTML = `
<div style="font-weight:700; margin-bottom:6px;"><svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M3 10v4h3l10 4V6L6 10z"/><path d="M17 9a4 4 0 010 6"/></svg> شارك المشروع</div>
<p class="muted" style="font-size:13px; line-height:1.7;">ساعد صاحب المشروع يوصل لمستثمرين أكتر.</p>
<div style="display:flex; gap:8px;">
<a class="btn btn-ghost btn-block" target="_blank" href="https://wa.me/?text=${encodeURIComponent('شوف المشروع ده على نيّة: ' + project.name + ' — ' + location.href)}">واتساب</a>
<button class="btn btn-ghost btn-block" onclick="copyProjectLink()">نسخ الرابط</button>
</div>
`;
}, 'share-card');
}
function copyProjectLink(){
navigator.clipboard.writeText(location.href).then(() => showToast('اتنسخ الرابط <svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M5 13l4 4L19 7"/></svg>'));
}
async function loadOwnerCard(){
try{
const p = await API.getUserProfile(project.ownerId);
const stars = p.ratingAvg ? `<svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M12 3l2.6 5.7 6.2.6-4.7 4.2 1.4 6.2L12 16.9 6.5 19.7l1.4-6.2L3.2 9.3l6.2-.6z"/></svg> ${p.ratingAvg} (${p.ratingCount})` : 'لسه من غير تقييمات';
document.getElementById('owner-card').innerHTML = `
<div style="display:flex; align-items:center; gap:12px;">
${p.avatar ? `<img src="${API.fileUrl(p.avatar)}" style="width:48px;height:48px;border-radius:50%;object-fit:cover;">`
: `<div style="width:48px;height:48px;border-radius:50%;background:var(--surface);display:flex;align-items:center;justify-content:center;font-weight:800;">${escapeHtml((p.name||'?')[0])}</div>`}
<div style="flex:1;">
<a href="profile.html?id=${p.id}" style="font-weight:700; text-decoration:underline;">${escapeHtml(p.name)}</a>
${p.idVerified ? '<span class="badge badge-ink" style="margin-right:6px;"><svg width="12" height="12" viewBox="0 0 20 20" style="display:inline-block;vertical-align:-2px;margin-left:3px;"><circle cx="10" cy="10" r="10" fill="currentColor"/><path d="M6 10.3l2.6 2.6L14.4 7" fill="none" stroke="var(--ink)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>موثّق</span>' : ''}
<div class="muted" style="font-size:12.5px;">${stars} · ${p.projectsCount} مشروع</div>
</div>
${API.user() && API.user().id === p.id ? '' : `<button class="btn btn-ghost btn-sm" onclick="openModal('report-overlay')"><svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M5 3v18"/><path d="M5 4h13l-3 4 3 4H5"/></svg> إبلاغ</button>`}
</div>`;
}catch(e){ /* تجاهل لو فشل تحميل بروفايل صاحب المشروع */ }
}
async function submitReport(){
const reason = document.getElementById('r-reason').value.trim();
const details = document.getElementById('r-details').value.trim();
if(!reason){ showToast('اكتب سبب البلاغ'); return; }
try{
await API.submitReport({ target_user_id: project.ownerId, project_id: project.id, reason, details });
closeModal('report-overlay');
showToast('اتسجل بلاغك، شكراً لتنبيهنا <svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M5 13l4 4L19 7"/></svg>');
}catch(e){ showToast(e.message); }
}
function startMessage(){
if(!requireAuth()) return;
location.href = `messages.html?project=${project.id}&with=${project.ownerId}`;
}
function openModal(id){ document.getElementById(id).classList.add('show'); }
function closeModal(id){ document.getElementById(id).classList.remove('show'); }
function openInterestModal(){
if(!requireAuth()) return;
openModal('interest-overlay');
}
async function submitInterest(){
const name = document.getElementById('i-name').value.trim();
const amount = Number(document.getElementById('i-amount').value);
const contact = document.getElementById('i-contact').value.trim();
if(!name || !amount || !contact){ showToast('املأ كل الحقول'); return; }
const btn = document.querySelector('#interest-overlay button.btn-primary');
if(btn){ btn.disabled = true; btn.textContent = 'جاري التسجيل...'; }
try{
await API.addInterest(projectId, { name, amount, contact });
showToast('اتسجلت نيّتك بنجاح <svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M5 13l4 4L19 7"/></svg>');
closeModal('interest-overlay');
load();
}catch(e){
const msg = e.message || '';
if(msg.includes('تسجل دخول') || msg.includes('الجلسة انتهت')){
showToast('لازم تسجل دخول الأول');
setTimeout(() => location.href = 'login.html', 900);
} else {
showToast(msg || 'حصل خطأ، حاول تاني');
}
} finally {
if(btn){ btn.disabled = false; btn.textContent = 'تأكيد'; }
}
}
async function submitMeeting(){
const name = document.getElementById('m-name').value.trim();
const contact = document.getElementById('m-contact').value.trim();
const slot_time = document.getElementById('m-slot').value;
if(!name || !contact || !slot_time){ showToast('املأ كل الحقول'); return; }
try{
await API.bookAppointment(projectId, { name, contact, slot_time });
closeModal('meeting-overlay');
showToast('اتحجز الموعد بنجاح <svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M5 13l4 4L19 7"/></svg>');
}catch(e){ showToast(e.message); }
}
load();
</script>
</body>
</html>