Upload 15 files
Browse files- static/favicon.svg +1 -0
- static/index.html +1 -0
- static/login.html +1 -0
- static/signup.html +1 -0
- static/verify-email.html +500 -0
static/favicon.svg
ADDED
|
|
static/index.html
CHANGED
|
@@ -3,6 +3,7 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
| 6 |
<title>نيّة — سوق نوايا الاستثمار المحلي</title>
|
| 7 |
<style>
|
| 8 |
@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');
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<link rel="icon" href="favicon.svg" type="image/svg+xml">
|
| 7 |
<title>نيّة — سوق نوايا الاستثمار المحلي</title>
|
| 8 |
<style>
|
| 9 |
@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');
|
static/login.html
CHANGED
|
@@ -3,6 +3,7 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
| 6 |
<title>تسجيل الدخول — نيّة</title>
|
| 7 |
<style>
|
| 8 |
@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');
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<link rel="icon" href="favicon.svg" type="image/svg+xml">
|
| 7 |
<title>تسجيل الدخول — نيّة</title>
|
| 8 |
<style>
|
| 9 |
@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');
|
static/signup.html
CHANGED
|
@@ -3,6 +3,7 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
| 6 |
<title>حساب جديد — نيّة</title>
|
| 7 |
<style>
|
| 8 |
@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');
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<link rel="icon" href="favicon.svg" type="image/svg+xml">
|
| 7 |
<title>حساب جديد — نيّة</title>
|
| 8 |
<style>
|
| 9 |
@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');
|
static/verify-email.html
ADDED
|
@@ -0,0 +1,500 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="ar" dir="rtl">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<link rel="icon" href="favicon.svg" type="image/svg+xml">
|
| 7 |
+
<title>تأكيد الإيميل — نيّة</title>
|
| 8 |
+
<style>
|
| 9 |
+
@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');
|
| 10 |
+
|
| 11 |
+
:root{
|
| 12 |
+
--bg:#FFFFFF;
|
| 13 |
+
--ink:#0A0A0A;
|
| 14 |
+
--line: rgba(10,10,10,0.12);
|
| 15 |
+
--line-strong: rgba(10,10,10,0.22);
|
| 16 |
+
--muted: rgba(10,10,10,0.55);
|
| 17 |
+
--muted-soft: rgba(10,10,10,0.4);
|
| 18 |
+
--surface: rgba(10,10,10,0.03);
|
| 19 |
+
--surface-hover: rgba(10,10,10,0.05);
|
| 20 |
+
--radius: 14px;
|
| 21 |
+
--max-w: 1080px;
|
| 22 |
+
}
|
| 23 |
+
*{ box-sizing:border-box; }
|
| 24 |
+
html,body{ margin:0; padding:0; }
|
| 25 |
+
|
| 26 |
+
/* ---------- app polish: smooth motion + native app feel ---------- */
|
| 27 |
+
@media (prefers-reduced-motion: no-preference){
|
| 28 |
+
body{ animation: pageFadeIn .28s ease both; }
|
| 29 |
+
.card, .interest-item, .grid > *{ animation: cardRise .32s ease both; }
|
| 30 |
+
.overlay.show .modal{ animation: modalPop .22s cubic-bezier(.2,.8,.3,1.2) both; }
|
| 31 |
+
.toast.show{ animation: toastRise .3s ease both; }
|
| 32 |
+
}
|
| 33 |
+
@keyframes pageFadeIn{ from{ opacity:0; transform:translateY(6px);} to{ opacity:1; transform:none; } }
|
| 34 |
+
@keyframes cardRise{ from{ opacity:0; transform:translateY(8px);} to{ opacity:1; transform:none; } }
|
| 35 |
+
@keyframes modalPop{ from{ opacity:0; transform:scale(.96) translateY(8px);} to{ opacity:1; transform:none; } }
|
| 36 |
+
@keyframes toastRise{ from{ opacity:0; transform:translateX(-50%) translateY(14px);} to{ opacity:1; transform:translateX(-50%) translateY(0);} }
|
| 37 |
+
.btn, .nav-link, .file-drop, .interest-item, .card{ transition: all .18s ease; }
|
| 38 |
+
.btn:active{ transform: scale(.96); }
|
| 39 |
+
.btn-primary:active{ opacity:.85; }
|
| 40 |
+
a.card:hover, .card:hover{ box-shadow: 0 4px 18px rgba(10,10,10,0.06); }
|
| 41 |
+
input, textarea, select{ transition: border-color .15s ease, box-shadow .15s ease; }
|
| 42 |
+
input:focus, textarea:focus, select:focus{ box-shadow: 0 0 0 3px rgba(10,10,10,0.06); }
|
| 43 |
+
::-webkit-scrollbar{ width:8px; height:8px; }
|
| 44 |
+
::-webkit-scrollbar-thumb{ background: rgba(10,10,10,0.15); border-radius:8px; }
|
| 45 |
+
@supports (padding: max(0px)){
|
| 46 |
+
body{ padding-bottom: env(safe-area-inset-bottom); }
|
| 47 |
+
.topbar{ padding-top: max(18px, env(safe-area-inset-top)); }
|
| 48 |
+
}
|
| 49 |
+
@media (max-width:720px){
|
| 50 |
+
.btn{ font-size:14px; }
|
| 51 |
+
.card{ border-radius:16px; }
|
| 52 |
+
.modal{ border-radius:20px 20px 0 0; position:fixed; bottom:0; left:0; right:0; max-width:100%; max-height:85vh; margin:0; }
|
| 53 |
+
.overlay{ align-items:flex-end; padding:0; }
|
| 54 |
+
.overlay.show .modal{ animation: sheetUp .25s cubic-bezier(.2,.8,.3,1) both; }
|
| 55 |
+
}
|
| 56 |
+
@keyframes sheetUp{ from{ transform:translateY(100%);} to{ transform:translateY(0);} }
|
| 57 |
+
body{
|
| 58 |
+
background: var(--bg);
|
| 59 |
+
color: var(--ink);
|
| 60 |
+
font-family:'IBM Plex Sans Arabic', sans-serif;
|
| 61 |
+
min-height:100vh;
|
| 62 |
+
-webkit-font-smoothing: antialiased;
|
| 63 |
+
line-height:1.6;
|
| 64 |
+
}
|
| 65 |
+
h1,h2,h3,.display,.btn,.brand-name{ font-family:'Cairo', sans-serif; }
|
| 66 |
+
a{ color: inherit; text-decoration:none; }
|
| 67 |
+
img{ max-width:100%; display:block; }
|
| 68 |
+
|
| 69 |
+
/* ---------- layout ---------- */
|
| 70 |
+
.page{ max-width: var(--max-w); margin: 0 auto; padding: 0 20px 60px; }
|
| 71 |
+
.center-page{ max-width: 440px; margin: 0 auto; padding: 20px 20px 60px; }
|
| 72 |
+
|
| 73 |
+
/* ---------- topbar ---------- */
|
| 74 |
+
.topbar{
|
| 75 |
+
display:flex; justify-content:space-between; align-items:center;
|
| 76 |
+
padding: 18px 24px; border-bottom: 1px solid var(--line);
|
| 77 |
+
position: sticky; top:0; background: var(--bg); z-index: 40;
|
| 78 |
+
}
|
| 79 |
+
.brand{ display:flex; align-items:center; gap:9px; }
|
| 80 |
+
.brand-mark{
|
| 81 |
+
width:30px; height:30px; border-radius:50%;
|
| 82 |
+
border: 2px solid var(--ink); position:relative; flex-shrink:0;
|
| 83 |
+
}
|
| 84 |
+
.brand-mark::after{
|
| 85 |
+
content:''; position:absolute; inset:7px; border-radius:50%; background: var(--ink);
|
| 86 |
+
}
|
| 87 |
+
.brand-name{ font-weight:800; font-size:20px; letter-spacing:0.3px; }
|
| 88 |
+
.nav-links{ display:flex; align-items:center; gap:6px; }
|
| 89 |
+
.nav-link{
|
| 90 |
+
padding:9px 14px; border-radius:999px; font-size:14px; font-weight:600; color: var(--muted);
|
| 91 |
+
transition: all .15s ease;
|
| 92 |
+
}
|
| 93 |
+
.nav-link:hover{ background: var(--surface); color: var(--ink); }
|
| 94 |
+
.nav-link.active{ background: var(--ink); color: var(--bg); }
|
| 95 |
+
.hamburger{ display:none; }
|
| 96 |
+
|
| 97 |
+
@media (max-width:720px){
|
| 98 |
+
.nav-links{ display:none; }
|
| 99 |
+
.hamburger{ display:block; }
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
/* ---------- buttons ---------- */
|
| 103 |
+
.btn{
|
| 104 |
+
display:inline-flex; align-items:center; justify-content:center; gap:8px;
|
| 105 |
+
padding: 13px 26px; border-radius: 11px; border: 1.5px solid var(--ink); cursor:pointer;
|
| 106 |
+
font-weight:700; font-size:14.5px; transition: all .15s ease; background: transparent; color: var(--ink);
|
| 107 |
+
}
|
| 108 |
+
.btn-primary{ background: var(--ink); color: var(--bg); }
|
| 109 |
+
.btn-primary:hover{ opacity:0.82; transform: translateY(-1px); }
|
| 110 |
+
.btn-ghost{ background: transparent; color: var(--ink); }
|
| 111 |
+
.btn-ghost:hover{ background: var(--surface); }
|
| 112 |
+
.btn-block{ width:100%; }
|
| 113 |
+
.btn:disabled{ opacity:0.4; cursor:not-allowed; }
|
| 114 |
+
.btn-sm{ padding: 9px 16px; font-size:13px; border-radius:9px; }
|
| 115 |
+
|
| 116 |
+
/* ---------- cards ---------- */
|
| 117 |
+
.card{
|
| 118 |
+
background: var(--bg); border: 1.5px solid var(--line); border-radius: var(--radius);
|
| 119 |
+
padding: 22px; transition: border-color .15s ease, transform .15s ease;
|
| 120 |
+
}
|
| 121 |
+
.card:hover{ border-color: var(--line-strong); }
|
| 122 |
+
|
| 123 |
+
/* ---------- forms ---------- */
|
| 124 |
+
.field{ margin-bottom:18px; }
|
| 125 |
+
.field label{ display:block; font-size:13.5px; font-weight:700; margin-bottom:8px; }
|
| 126 |
+
.field .hint{ font-size:12px; color: var(--muted-soft); margin-top:6px; line-height:1.6; }
|
| 127 |
+
input, textarea, select{
|
| 128 |
+
width:100%; padding: 13px 15px; border-radius:10px; border: 1.5px solid var(--line);
|
| 129 |
+
background: var(--bg); color: var(--ink); font-family:inherit; font-size:14.5px;
|
| 130 |
+
}
|
| 131 |
+
input:focus, textarea:focus, select:focus{ outline:none; border-color: var(--ink); }
|
| 132 |
+
textarea{ resize:vertical; min-height:100px; }
|
| 133 |
+
.field-row{ display:flex; gap:14px; }
|
| 134 |
+
.field-row .field{ flex:1; }
|
| 135 |
+
@media (max-width:520px){ .field-row{ flex-direction:column; gap:0; } }
|
| 136 |
+
|
| 137 |
+
.file-drop{
|
| 138 |
+
border: 1.5px dashed var(--line-strong); border-radius:12px; padding: 26px 16px;
|
| 139 |
+
text-align:center; cursor:pointer; transition: all .15s ease; font-size:13.5px; color: var(--muted);
|
| 140 |
+
}
|
| 141 |
+
.file-drop:hover{ border-color: var(--ink); background: var(--surface); }
|
| 142 |
+
.file-list{ display:flex; flex-wrap:wrap; gap:10px; margin-top:12px; }
|
| 143 |
+
.file-thumb{
|
| 144 |
+
width:64px; height:64px; border-radius:8px; overflow:hidden; border:1px solid var(--line); position:relative;
|
| 145 |
+
}
|
| 146 |
+
.file-thumb img{ width:100%; height:100%; object-fit:cover; }
|
| 147 |
+
.file-chip{
|
| 148 |
+
display:flex; align-items:center; gap:6px; padding:6px 12px; border:1px solid var(--line);
|
| 149 |
+
border-radius:999px; font-size:12px;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
/* ---------- grid / misc ---------- */
|
| 153 |
+
.grid{ display:grid; grid-template-columns: repeat(auto-fill, minmax(290px,1fr)); gap:18px; }
|
| 154 |
+
.section-head{ display:flex; justify-content:space-between; align-items:flex-end; margin: 30px 0 20px; flex-wrap:wrap; gap:10px; }
|
| 155 |
+
.section-head h2{ font-size:22px; font-weight:800; margin:0; }
|
| 156 |
+
.muted{ color: var(--muted); }
|
| 157 |
+
.badge{
|
| 158 |
+
display:inline-block; font-size:11px; font-weight:700; padding:4px 11px; border-radius:999px;
|
| 159 |
+
border: 1px solid var(--line-strong); color: var(--muted);
|
| 160 |
+
}
|
| 161 |
+
.badge-ink{ background: var(--ink); color: var(--bg); border-color: var(--ink); }
|
| 162 |
+
.empty{ text-align:center; padding: 60px 20px; color: var(--muted-soft); border: 1.5px dashed var(--line); border-radius: var(--radius); }
|
| 163 |
+
.divider{ height:1px; background: var(--line); margin: 24px 0; border:none; }
|
| 164 |
+
|
| 165 |
+
.progress-track{ height:8px; background: var(--surface); border-radius:999px; overflow:hidden; }
|
| 166 |
+
.progress-fill{ height:100%; background: var(--ink); border-radius:999px; transition: width .4s ease; }
|
| 167 |
+
|
| 168 |
+
.toast{
|
| 169 |
+
position:fixed; bottom:26px; left:50%; transform:translateX(-50%) translateY(20px);
|
| 170 |
+
background: var(--ink); color: var(--bg); padding:14px 24px; border-radius:12px;
|
| 171 |
+
font-family:'Cairo',sans-serif; font-weight:700; font-size:14px; opacity:0; transition: all .3s ease;
|
| 172 |
+
pointer-events:none; z-index:100; white-space:nowrap; max-width: 90vw; text-align:center;
|
| 173 |
+
}
|
| 174 |
+
.toast.show{ opacity:1; transform:translateX(-50%) translateY(0); }
|
| 175 |
+
|
| 176 |
+
.spinner{
|
| 177 |
+
width:20px; height:20px; border:2.5px solid var(--line); border-top-color: var(--ink);
|
| 178 |
+
border-radius:50%; animation: spin .7s linear infinite; margin: 0 auto;
|
| 179 |
+
}
|
| 180 |
+
@keyframes spin{ to{ transform: rotate(360deg); } }
|
| 181 |
+
|
| 182 |
+
.loading-row{ display:flex; justify-content:center; padding: 40px 0; }
|
| 183 |
+
|
| 184 |
+
/* ---------- modal ---------- */
|
| 185 |
+
.overlay{
|
| 186 |
+
position:fixed; inset:0; background: rgba(10,10,10,0.5);
|
| 187 |
+
display:none; align-items:center; justify-content:center; z-index:60; padding:20px;
|
| 188 |
+
}
|
| 189 |
+
.overlay.show{ display:flex; }
|
| 190 |
+
.modal{
|
| 191 |
+
background: var(--bg); border: 1.5px solid var(--ink); border-radius: 18px;
|
| 192 |
+
width:100%; max-width:460px; padding:26px; max-height:88vh; overflow-y:auto; position:relative;
|
| 193 |
+
}
|
| 194 |
+
.modal h3{ font-size:19px; margin:0 0 16px; }
|
| 195 |
+
.close-x{
|
| 196 |
+
position:absolute; top:18px; left:18px; background:none; border:none; font-size:19px; cursor:pointer; opacity:0.5;
|
| 197 |
+
}
|
| 198 |
+
.close-x:hover{ opacity:1; }
|
| 199 |
+
|
| 200 |
+
/* footer */
|
| 201 |
+
.footer-note{ text-align:center; padding: 40px 20px 30px; color: var(--muted-soft); font-size:12.5px; }
|
| 202 |
+
|
| 203 |
+
@media (max-width: 640px){
|
| 204 |
+
.topbar{ padding:14px 16px; }
|
| 205 |
+
.page{ padding: 0 14px 50px; }
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
/* ---------- mobile app shell (bottom tab bar, thndr-style) ---------- */
|
| 210 |
+
.bottom-nav{ display:none; }
|
| 211 |
+
@media (max-width:720px){
|
| 212 |
+
body{ padding-bottom: calc(64px + env(safe-area-inset-bottom)); }
|
| 213 |
+
.bottom-nav{
|
| 214 |
+
display:flex; position:fixed; bottom:0; left:0; right:0; z-index:50;
|
| 215 |
+
background: var(--bg); border-top:1px solid var(--line);
|
| 216 |
+
padding: 6px 4px calc(6px + env(safe-area-inset-bottom));
|
| 217 |
+
box-shadow: 0 -2px 14px rgba(10,10,10,0.05);
|
| 218 |
+
transform: translateY(0); transition: transform .28s cubic-bezier(.4,0,.2,1);
|
| 219 |
+
will-change: transform;
|
| 220 |
+
}
|
| 221 |
+
.bottom-nav.bn-hidden{ transform: translateY(100%); }
|
| 222 |
+
.bn-item{
|
| 223 |
+
flex:1; display:flex; flex-direction:column; align-items:center; gap:3px;
|
| 224 |
+
padding:6px 2px; border-radius:12px; color: var(--muted-soft);
|
| 225 |
+
font-size:10.5px; font-weight:600; transition: all .15s ease; -webkit-tap-highlight-color:transparent;
|
| 226 |
+
}
|
| 227 |
+
.bn-item svg{ width:22px; height:22px; stroke: currentColor; fill:none; stroke-width:1.8; transition: transform .15s ease; }
|
| 228 |
+
.bn-item.active{ color: var(--ink); }
|
| 229 |
+
.bn-item.active svg{ transform: translateY(-1px) scale(1.05); }
|
| 230 |
+
.bn-item:active{ transform: scale(.92); }
|
| 231 |
+
.topbar .nav-links{ display:none !important; }
|
| 232 |
+
.hamburger{ display:none !important; }
|
| 233 |
+
.topbar{ padding:14px 18px; }
|
| 234 |
+
.brand-name{ font-size:18px; }
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
/* ---------- final QA polish: no overflow, no overlap, no glitches ---------- */
|
| 238 |
+
html{ overflow-x:hidden; }
|
| 239 |
+
body{ overflow-x:hidden; }
|
| 240 |
+
img,video{ max-width:100%; height:auto; }
|
| 241 |
+
*{ word-wrap:break-word; overflow-wrap:break-word; }
|
| 242 |
+
.page, .center-page{ overflow-x:hidden; }
|
| 243 |
+
@media (max-width:720px){
|
| 244 |
+
.toast{ bottom: calc(78px + env(safe-area-inset-bottom)) !important; max-width: calc(100vw - 32px); }
|
| 245 |
+
.modal{ max-height: calc(85vh - env(safe-area-inset-bottom)); padding-bottom: calc(26px + env(safe-area-inset-bottom)); }
|
| 246 |
+
.overlay.show{ z-index:70; }
|
| 247 |
+
table{ display:block; overflow-x:auto; -webkit-overflow-scrolling:touch; }
|
| 248 |
+
}
|
| 249 |
+
</style>
|
| 250 |
+
</head>
|
| 251 |
+
<body>
|
| 252 |
+
|
| 253 |
+
<div class="topbar" id="topbar"></div>
|
| 254 |
+
|
| 255 |
+
<div class="center-page">
|
| 256 |
+
<div style="text-align:center; margin: 40px 0 30px;">
|
| 257 |
+
<div class="brand-mark" style="margin:0 auto 14px;"></div>
|
| 258 |
+
<h1 id="ve-title" style="font-size:26px; font-weight:800; margin:0 0 8px;">جاري تأكيد إيميلك...</h1>
|
| 259 |
+
<p class="muted" id="ve-sub" style="font-size:14px;">لحظة وهندخلك المنصة على طول</p>
|
| 260 |
+
</div>
|
| 261 |
+
|
| 262 |
+
<div class="card" style="text-align:center;">
|
| 263 |
+
<div class="spinner" id="ve-spinner"></div>
|
| 264 |
+
<p id="ve-msg" style="display:none; font-size:14.5px; margin:0;"></p>
|
| 265 |
+
</div>
|
| 266 |
+
|
| 267 |
+
<p style="text-align:center; margin-top:20px; font-size:14px;" class="muted">
|
| 268 |
+
<a href="login.html" style="font-weight:700; color:var(--ink); text-decoration:underline;">رجوع لتسجيل الدخول</a>
|
| 269 |
+
</p>
|
| 270 |
+
</div>
|
| 271 |
+
|
| 272 |
+
<script>
|
| 273 |
+
// ---------- نيّة: طبقة الاتصال بالـ API + إدارة الجلسة ----------
|
| 274 |
+
const API = {
|
| 275 |
+
base: '',
|
| 276 |
+
|
| 277 |
+
token(){ return localStorage.getItem('neya_token'); },
|
| 278 |
+
user(){ try{ return JSON.parse(localStorage.getItem('neya_user') || 'null'); }catch(e){ return null; } },
|
| 279 |
+
isLoggedIn(){ return !!this.token(); },
|
| 280 |
+
|
| 281 |
+
setSession(token, user){
|
| 282 |
+
localStorage.setItem('neya_token', token);
|
| 283 |
+
localStorage.setItem('neya_user', JSON.stringify(user));
|
| 284 |
+
},
|
| 285 |
+
clearSession(){
|
| 286 |
+
localStorage.removeItem('neya_token');
|
| 287 |
+
localStorage.removeItem('neya_user');
|
| 288 |
+
},
|
| 289 |
+
|
| 290 |
+
async _fetch(path, opts = {}){
|
| 291 |
+
const headers = opts.headers || {};
|
| 292 |
+
if(this.token()) headers['Authorization'] = 'Bearer ' + this.token();
|
| 293 |
+
if(opts.body && !(opts.body instanceof FormData)){
|
| 294 |
+
headers['Content-Type'] = 'application/json';
|
| 295 |
+
}
|
| 296 |
+
const res = await fetch(this.base + path, { ...opts, headers });
|
| 297 |
+
let data = null;
|
| 298 |
+
try{ data = await res.json(); }catch(e){ /* no body */ }
|
| 299 |
+
if(!res.ok){
|
| 300 |
+
const msg = (data && data.detail) ? data.detail : 'حصل خطأ، حاول تاني';
|
| 301 |
+
throw new Error(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
| 302 |
+
}
|
| 303 |
+
return data;
|
| 304 |
+
},
|
| 305 |
+
|
| 306 |
+
signup(payload){ return this._fetch('/api/auth/signup', { method:'POST', body: JSON.stringify(payload) }); },
|
| 307 |
+
login(payload){ return this._fetch('/api/auth/login', { method:'POST', body: JSON.stringify(payload) }); },
|
| 308 |
+
me(){ return this._fetch('/api/auth/me'); },
|
| 309 |
+
forgotPassword(email){ return this._fetch('/api/auth/forgot-password', { method:'POST', body: JSON.stringify({email}) }); },
|
| 310 |
+
resetPassword(token, new_password){ return this._fetch('/api/auth/reset-password', { method:'POST', body: JSON.stringify({token, new_password}) }); },
|
| 311 |
+
verifyEmail(token){ return this._fetch('/api/auth/verify-email', { method:'POST', body: JSON.stringify({token}) }); },
|
| 312 |
+
|
| 313 |
+
listProjects(){ return this._fetch('/api/projects'); },
|
| 314 |
+
createProject(payload){ return this._fetch('/api/projects', { method:'POST', body: JSON.stringify(payload) }); },
|
| 315 |
+
uploadFile(projectId, kind, file){
|
| 316 |
+
const fd = new FormData();
|
| 317 |
+
fd.append('kind', kind);
|
| 318 |
+
fd.append('file', file);
|
| 319 |
+
return this._fetch(`/api/projects/${projectId}/files`, { method:'POST', body: fd });
|
| 320 |
+
},
|
| 321 |
+
|
| 322 |
+
listInterests(projectId){ return this._fetch(`/api/projects/${projectId}/interests`); },
|
| 323 |
+
addInterest(projectId, payload){ return this._fetch(`/api/projects/${projectId}/interests`, { method:'POST', body: JSON.stringify(payload) }); },
|
| 324 |
+
|
| 325 |
+
bookAppointment(projectId, payload){ return this._fetch(`/api/projects/${projectId}/appointments`, { method:'POST', body: JSON.stringify(payload) }); },
|
| 326 |
+
listAppointments(projectId){ return this._fetch(`/api/projects/${projectId}/appointments`); },
|
| 327 |
+
|
| 328 |
+
fileUrl(filename){ return `/api/uploads/${filename}`; },
|
| 329 |
+
|
| 330 |
+
listConversations(){ return this._fetch('/api/conversations'); },
|
| 331 |
+
getThread(projectId, withUser){ return this._fetch(`/api/projects/${projectId}/messages?with_user=${encodeURIComponent(withUser)}`); },
|
| 332 |
+
sendMessage(projectId, payload){ return this._fetch(`/api/projects/${projectId}/messages`, { method:'POST', body: JSON.stringify(payload) }); },
|
| 333 |
+
|
| 334 |
+
getUserProfile(userId){ return this._fetch(`/api/users/${userId}/profile`); },
|
| 335 |
+
getUserProjects(userId){ return this._fetch(`/api/users/${userId}/projects`); },
|
| 336 |
+
listReviews(userId){ return this._fetch(`/api/users/${userId}/reviews`); },
|
| 337 |
+
addReview(userId, payload){ return this._fetch(`/api/users/${userId}/reviews`, { method:'POST', body: JSON.stringify(payload) }); },
|
| 338 |
+
submitReport(payload){ return this._fetch('/api/reports', { method:'POST', body: JSON.stringify(payload) }); },
|
| 339 |
+
uploadAvatar(file){
|
| 340 |
+
const fd = new FormData();
|
| 341 |
+
fd.append('file', file);
|
| 342 |
+
return this._fetch('/api/auth/me/avatar', { method:'POST', body: fd });
|
| 343 |
+
},
|
| 344 |
+
};
|
| 345 |
+
|
| 346 |
+
function formatMoney(n){ return Number(n || 0).toLocaleString('en-US'); }
|
| 347 |
+
|
| 348 |
+
function escapeHtml(str){
|
| 349 |
+
const div = document.createElement('div');
|
| 350 |
+
div.textContent = str || '';
|
| 351 |
+
return div.innerHTML;
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
function showToast(msg){
|
| 355 |
+
let toast = document.getElementById('global-toast');
|
| 356 |
+
if(!toast){
|
| 357 |
+
toast = document.createElement('div');
|
| 358 |
+
toast.id = 'global-toast';
|
| 359 |
+
toast.className = 'toast';
|
| 360 |
+
document.body.appendChild(toast);
|
| 361 |
+
}
|
| 362 |
+
toast.textContent = msg;
|
| 363 |
+
toast.classList.add('show');
|
| 364 |
+
clearTimeout(toast._t);
|
| 365 |
+
toast._t = setTimeout(() => toast.classList.remove('show'), 2800);
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
// يبني التوب بار الموحّد في كل الصفحات ويعكس حالة تسجيل الدخول
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
function renderBottomNav(active){
|
| 372 |
+
if(window.innerWidth > 720) return;
|
| 373 |
+
if(document.getElementById('bottom-nav')) return;
|
| 374 |
+
const user = API.user();
|
| 375 |
+
const icons = {
|
| 376 |
+
browse: '<svg viewBox="0 0 24 24"><path d="M12 3l9 8h-2v9h-5v-6H10v6H5v-9H3z"/></svg>',
|
| 377 |
+
create: '<svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>',
|
| 378 |
+
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>',
|
| 379 |
+
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>',
|
| 380 |
+
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>',
|
| 381 |
+
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>',
|
| 382 |
+
about: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 8v5M12 16h.01"/></svg>'
|
| 383 |
+
};
|
| 384 |
+
let tabs = [
|
| 385 |
+
{ key:'browse', href:'index.html', label:'\u0627\u0633\u062a\u0643\u0634\u0641' },
|
| 386 |
+
{ key:'create', href:'create.html', label:'\u0627\u0639\u0631\u0636' },
|
| 387 |
+
];
|
| 388 |
+
if(user){
|
| 389 |
+
tabs.push({ key:'dashboard', href:'dashboard.html', label:'\u0644\u0648\u062d\u062a\u064a' });
|
| 390 |
+
tabs.push({ key:'messages', href:'messages.html', label:'\u0631\u0633\u0627\u0626\u0644' });
|
| 391 |
+
tabs.push({ key:'profile', href:'profile.html?id='+user.id, label:'\u062d\u0633\u0627\u0628\u064a' });
|
| 392 |
+
} else {
|
| 393 |
+
tabs.push({ key:'login', href:'login.html', label:'\u062f\u062e\u0648\u0644' });
|
| 394 |
+
}
|
| 395 |
+
tabs.push({ key:'about', href:'about.html', label:'\u062a\u0648\u0627\u0635\u0644' });
|
| 396 |
+
const nav = document.createElement('div');
|
| 397 |
+
nav.id = 'bottom-nav';
|
| 398 |
+
nav.className = 'bottom-nav';
|
| 399 |
+
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('');
|
| 400 |
+
document.body.appendChild(nav);
|
| 401 |
+
initBottomNavAutoHide(nav);
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
// اخفاء شريط التنقل السفلي عند التمرير لتحت، وإظهاره عند التمرير لفوق (زي التطبيقات الأصلية)
|
| 405 |
+
function initBottomNavAutoHide(nav){
|
| 406 |
+
let lastY = window.scrollY, ticking = false;
|
| 407 |
+
window.addEventListener('scroll', () => {
|
| 408 |
+
if(ticking) return;
|
| 409 |
+
ticking = true;
|
| 410 |
+
requestAnimationFrame(() => {
|
| 411 |
+
const y = window.scrollY;
|
| 412 |
+
const delta = y - lastY;
|
| 413 |
+
if(y < 40){ nav.classList.remove('bn-hidden'); }
|
| 414 |
+
else if(delta > 6){ nav.classList.add('bn-hidden'); }
|
| 415 |
+
else if(delta < -6){ nav.classList.remove('bn-hidden'); }
|
| 416 |
+
lastY = y;
|
| 417 |
+
ticking = false;
|
| 418 |
+
});
|
| 419 |
+
}, { passive:true });
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
function renderFooter(){
|
| 423 |
+
if(document.getElementById('global-footer-note')) return;
|
| 424 |
+
const el = document.createElement('p');
|
| 425 |
+
el.id = 'global-footer-note';
|
| 426 |
+
el.className = 'footer-note';
|
| 427 |
+
el.style.cssText = 'margin-top:8px;';
|
| 428 |
+
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>';
|
| 429 |
+
document.body.appendChild(el);
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
function renderTopbar(activePage){
|
| 433 |
+
const el = document.getElementById('topbar');
|
| 434 |
+
if(!el) return;
|
| 435 |
+
const user = API.user();
|
| 436 |
+
const links = [
|
| 437 |
+
{ href:'index.html', label:'استكشف المشاريع', key:'browse' },
|
| 438 |
+
{ href:'create.html', label:'اعرض مشروعك', key:'create' },
|
| 439 |
+
];
|
| 440 |
+
if(user){
|
| 441 |
+
links.push({ href:'dashboard.html', label:'لوحتي', key:'dashboard' });
|
| 442 |
+
links.push({ href:'messages.html', label:'الرسائل', key:'messages' });
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
const authArea = user
|
| 446 |
+
? `<a class="nav-link" href="profile.html?id=${user.id}">أهلاً، ${escapeHtml(user.name.split(' ')[0])}</a>
|
| 447 |
+
<button class="btn btn-ghost btn-sm" onclick="API.clearSession(); location.href='index.html';">خروج</button>`
|
| 448 |
+
: `<a class="btn btn-ghost btn-sm" href="login.html">دخول</a>
|
| 449 |
+
<a class="btn btn-primary btn-sm" href="signup.html">حساب جديد</a>`;
|
| 450 |
+
|
| 451 |
+
el.innerHTML = `
|
| 452 |
+
<a class="brand" href="index.html">
|
| 453 |
+
<div class="brand-mark"></div>
|
| 454 |
+
<div class="brand-name">نيّة</div>
|
| 455 |
+
</a>
|
| 456 |
+
<div class="nav-links">
|
| 457 |
+
${links.map(l => `<a class="nav-link ${l.key===activePage?'active':''}" href="${l.href}">${l.label}</a>`).join('')}
|
| 458 |
+
${authArea}
|
| 459 |
+
</div>
|
| 460 |
+
`;
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
// حماية صفحات لازم تسجيل دخول لها
|
| 464 |
+
function requireAuth(){
|
| 465 |
+
if(!API.isLoggedIn()){
|
| 466 |
+
location.href = 'login.html';
|
| 467 |
+
return false;
|
| 468 |
+
}
|
| 469 |
+
return true;
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
</script>
|
| 473 |
+
<script>
|
| 474 |
+
renderTopbar(null); renderFooter(); renderBottomNav('login');
|
| 475 |
+
|
| 476 |
+
function veDone(ok, title, sub){
|
| 477 |
+
document.getElementById('ve-spinner').style.display = 'none';
|
| 478 |
+
document.getElementById('ve-title').textContent = title;
|
| 479 |
+
document.getElementById('ve-sub').textContent = sub;
|
| 480 |
+
const msg = document.getElementById('ve-msg');
|
| 481 |
+
msg.style.display = 'block';
|
| 482 |
+
msg.textContent = ok ? 'حسابك اتفعّل بنجاح ✅' : '';
|
| 483 |
+
msg.style.color = ok ? '#0a0' : '#c00';
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
(async function verify(){
|
| 487 |
+
const token = new URLSearchParams(location.search).get('token');
|
| 488 |
+
if(!token){ veDone(false, 'رابط غير صالح', 'تأكد إنك فاتح الرابط اللي جالك في الإيميل كامل'); return; }
|
| 489 |
+
try{
|
| 490 |
+
const res = await API.verifyEmail(token);
|
| 491 |
+
API.setSession(res.token, res.user);
|
| 492 |
+
veDone(true, 'اتأكّد إيميلك 🎉', 'هندخلك المنصة دلوقتي...');
|
| 493 |
+
setTimeout(()=>location.href='index.html', 1200);
|
| 494 |
+
}catch(e){
|
| 495 |
+
veDone(false, 'الرابط منتهي أو غير صالح', e.message || 'اطلب رابط تأكيد جديد من صفحة الدخول');
|
| 496 |
+
}
|
| 497 |
+
})();
|
| 498 |
+
</script>
|
| 499 |
+
</body>
|
| 500 |
+
</html>
|