Commit cf882849 authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat(proposal): replace static mockup with a working app prototype

The prototype in the proposal was eight inline <div>s toggled with
display:none — screenshots in a phone bezel. It now embeds a real
single-page app from app/, so the screens shown in the proposal are the
screens that ship, and the board can actually use it.

app/ — hash routed, so any screen is linkable (#/dues):
  splash, login, otp, home, dues, pay, paying, success, receipts, qr,
  invites, activities, activity/:id, schedule, notifications, profile

State is live, not scripted. Selecting dues recomputes the total before
paying; paying clears those dues, creates a receipt and posts a
notification; issuing an invite decrements the balance; subscribing to an
activity adds the subscription, consumes a place and generates the first
invoice into المستحقات. The QR regenerates every 60s against a countdown
ring. Dark/light theme persists. Verified end to end by driving the app in
headless Chrome, not just by rendering it.

Chose an SPA over the multi-page pattern used by the older Proposal/
prototype: no white flash between screens, real forward/back transitions,
and shared state across screens, which is the whole point of showing a
collection flow.

Proposal integration:
- Phone hosts <iframe src="app/">; the side list drives it over
  postMessage and the prototype reports its route back, so the list and
  the annotation stay in sync when someone navigates inside the phone
- Theme toggle, reset, and open-fullscreen controls
- Annotation panel rewritten per screen

Removed 409 lines of static screen markup and the 93 lines of CSS that
served it (.sbar/.app-hd/.mcard/.tabbar/.sport-item/.otp-row/.qr-*),
verified dead by checking real class= usage, not substring matches.

Adds .dockerignore: the image is built with `COPY . /usr/share/nginx/html`,
so any stray working file in this folder would be served publicly.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
parent ff106889
# The image is built with `COPY . /usr/share/nginx/html`, so anything left in
# this folder is served publicly. Keep working files and exports out of it.
_*.html
*.md
.DS_Store
This diff is collapsed.
<!DOCTYPE html>
<html lang="ar" dir="rtl" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<title>نادي الصيد المصري — نموذج تطبيق الأعضاء</title>
<meta name="theme-color" content="#070F1E">
<link rel="icon" href="../assets/club-logo.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@700;800;900&family=IBM+Plex+Sans+Arabic:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/app.css">
<style>
.ico.flip{ width:16px;height:16px;color:var(--txt-3);transform:scaleX(-1) }
.ico.tiny{ width:12px;height:12px;stroke-width:3 }
.ico.big{ width:30px;height:30px }
.flagsvg{ width:22px;height:15px;stroke:none }
</style>
</head>
<body>
<svg class="sprite" aria-hidden="true"><defs>
<symbol id="i-home" viewBox="0 0 24 24"><path d="M3.8 10.4 12 3.8l8.2 6.6"/><path d="M6 9.6V20.2h12V9.6"/><path d="M9.9 20.2v-5h4.2v5"/></symbol>
<symbol id="i-activity" viewBox="0 0 24 24"><path d="M4 9v6M7.2 6.8v10.4M16.8 6.8v10.4M20 9v6M7.2 12h9.6"/></symbol>
<symbol id="i-gate" viewBox="0 0 24 24"><rect x="3.5" y="3.5" width="7" height="7" rx="1.3"/><rect x="13.5" y="3.5" width="7" height="7" rx="1.3"/><rect x="3.5" y="13.5" width="7" height="7" rx="1.3"/><path d="M13.5 14.2h3v3h-3zM19.4 14.2h1.1M13.5 20.4h3M19.6 18.6v1.8"/></symbol>
<symbol id="i-user" viewBox="0 0 24 24"><circle cx="12" cy="8" r="3.6"/><path d="M4.6 20.2c0-3.9 3.4-6 7.4-6s7.4 2.1 7.4 6"/></symbol>
<symbol id="i-bell" viewBox="0 0 24 24"><path d="M6.2 9.4a5.8 5.8 0 0 1 11.6 0c0 3.9 1.5 5.4 1.5 5.4H4.7s1.5-1.5 1.5-5.4Z"/><path d="M10 18.1a2.2 2.2 0 0 0 4 0"/></symbol>
<symbol id="i-mail" viewBox="0 0 24 24"><rect x="2.8" y="5.4" width="18.4" height="13.2" rx="2.2"/><path d="m3.8 7.1 7.3 5.3a1.6 1.6 0 0 0 1.8 0l7.3-5.3"/></symbol>
<symbol id="i-receipt" viewBox="0 0 24 24"><path d="M6 3.6h12v16.8l-2.4-1.6-2.4 1.6-2.4-1.6-2.4 1.6Z"/><path d="M9.4 8.6h5.2M9.4 12.6h5.2"/></symbol>
<symbol id="i-card" viewBox="0 0 24 24"><rect x="2.6" y="5" width="18.8" height="14" rx="2.4"/><path d="M2.6 9.8h18.8"/><path d="M6.2 14.6h3.6"/></symbol>
<symbol id="i-wallet" viewBox="0 0 24 24"><rect x="6.6" y="2.6" width="10.8" height="18.8" rx="2.4"/><path d="M10.6 5.7h2.8M11 18.4h2"/></symbol>
<symbol id="i-bank" viewBox="0 0 24 24"><path d="M3.6 9.6 12 4.2l8.4 5.4"/><path d="M5.8 10.8v6.8M10 10.8v6.8M14 10.8v6.8M18.2 10.8v6.8"/><path d="M3.6 20.2h16.8"/></symbol>
<symbol id="i-check" viewBox="0 0 24 24"><path d="m5 12.5 4.6 4.5L19 7.5"/></symbol>
<symbol id="i-chev" viewBox="0 0 24 24"><path d="m9 6 6 6-6 6"/></symbol>
<symbol id="i-back" viewBox="0 0 24 24"><path d="M20 12H8.5"/><path d="m12.5 7-5 5 5 5"/><path d="M4 7v10"/></symbol>
<symbol id="i-download" viewBox="0 0 24 24"><path d="M12 4.4v11.8"/><path d="m7.6 11.8 4.4 4.4 4.4-4.4"/><path d="M4.6 15v3.4a2 2 0 0 0 2 2h10.8a2 2 0 0 0 2-2V15"/></symbol>
<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.4"/><path d="M12 6.9v5.4l3.4 2"/></symbol>
<symbol id="i-refresh" viewBox="0 0 24 24"><path d="M20.1 12a8.1 8.1 0 1 1-2.6-6"/><path d="M20.3 4.3v4.4h-4.4"/></symbol>
<symbol id="i-moon" viewBox="0 0 24 24"><path d="M20.5 14.3A8.6 8.6 0 0 1 9.7 3.5a8.6 8.6 0 1 0 10.8 10.8Z"/></symbol>
<symbol id="i-swim" viewBox="0 0 24 24"><circle cx="15.6" cy="6.4" r="2"/><path d="M4.6 13.2 9.2 10l4 3.1"/><path d="M2.6 17.9c1.7 0 1.7 1.5 3.5 1.5s1.7-1.5 3.5-1.5 1.7 1.5 3.5 1.5 1.7-1.5 3.5-1.5 1.7 1.5 3.4 1.5"/></symbol>
<symbol id="i-football" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.4"/><path d="m12 7.6 3.5 2.5-1.3 4.1H9.8l-1.3-4.1Z"/><path d="M12 3.6v4M19.6 9.7l-3.8.4M17 18.9l-2.8-4.7M7 18.9l2.8-4.7M4.4 9.7l3.8.4"/></symbol>
<symbol id="i-karate" viewBox="0 0 24 24"><path d="M3.2 9.4h17.6v4H3.2z"/><path d="m10 13.6-1.4 6.2 3.4-2.2 3.4 2.2-1.4-6.2"/></symbol>
<symbol id="i-tennis" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.4"/><path d="M6.1 5.6c2.5 2 3.8 4.6 3.8 7.9 0 1.6-.3 3.2-1 4.7"/><path d="M17.9 5.6c-2.5 2-3.8 4.6-3.8 7.9 0 1.6.3 3.2 1 4.7"/></symbol>
<symbol id="i-gym" viewBox="0 0 24 24"><circle cx="12" cy="4.8" r="2.1"/><path d="M12 7.3v6.3"/><path d="m6.6 9.6 5.4 1.6 5.4-1.6"/><path d="m9.1 20.4 2.9-6.8 2.9 6.8"/></symbol>
<symbol id="i-flag-eg" viewBox="0 0 24 16">
<rect x="0" y="0" width="24" height="5.33" fill="#CE1126"/>
<rect x="0" y="5.33" width="24" height="5.34" fill="#F5F5F5"/>
<rect x="0" y="10.67" width="24" height="5.33" fill="#0B0B0B"/>
</symbol>
</defs></svg>
<div id="app"></div>
<script src="js/data.js"></script>
<script src="js/app.js"></script>
</body>
</html>
This diff is collapsed.
/* Mock dataset for the prototype. Everything the screens read and mutate lives
here, so a demo session behaves like a real account: paying clears dues and
creates a receipt, subscribing adds an invoice, issuing an invite decrements
the balance. Reset from the profile screen. */
window.DB = {
member: {
id: 10245,
name: 'محمد أحمد سيد إبراهيم',
short: 'محمد أحمد',
type: 'عضو عامل',
validUntil: '2026/12/31',
phone: '100 123 4567',
kids: [
{ id: 'k1', name: 'يوسف محمد' },
{ id: 'k2', name: 'لُجين محمد' }
]
},
dues: [
{ id:'d1', title:'قسط تجديد العضوية', sub:'القسط ٢ من ٣ — فبراير 2026', due:'2026/02/28',
amount:1500, state:'late', kind:'renewal' },
{ id:'d2', title:'اشتراك سباحة مبتدئين', sub:'يوسف محمد — مارس 2026', due:'2026/03/05',
amount:900, state:'soon', kind:'activity' },
{ id:'d3', title:'غرامة تأخير سداد', sub:'عن تأخر قسط يناير', due:'2026/02/28',
amount:350, state:'late', kind:'fine' }
],
receipts: [
{ id:'r0', title:'اشتراك جمباز فني — لُجين', amount:1000, date:'2026/02/03', method:'بطاقة بنكية' },
{ id:'r1', title:'قسط تجديد العضوية — القسط ١', amount:1500, date:'2026/01/08', method:'محفظة إلكترونية' }
],
invites: { free: 4, used: 1, bought: 0, priceAdult: 150, priceChild: 75, maxBuy: 10 },
issued: [
{ id:'i1', name:'أحمد سامي', date:'15/03', state:'دخل' }
],
activities: [
{ id:'SW-01', name:'سباحة مبتدئين', cat:'سباحة', icon:'i-swim', age:'٦ - ٩ سنوات', sess:12, price:900, join:300, cap:24, taken:18, coach:'كابتن هشام صلاح' },
{ id:'SW-02', name:'سباحة متقدم', cat:'سباحة', icon:'i-swim', age:'١٠ - ١٤ سنة', sess:16, price:1200, join:300, cap:20, taken:14, coach:'كابتن هشام صلاح' },
{ id:'FB-01', name:'كرة القدم — الموهبة', cat:'كرة', icon:'i-football', age:'٨ - ١١ سنة', sess:12, price:1100, join:400, cap:28, taken:26, coach:'كابتن طارق منير' },
{ id:'KR-01', name:'كاراتيه مبتدئين', cat:'فردي', icon:'i-karate', age:'٦ - ١٢ سنة', sess:8, price:700, join:250, cap:30, taken:22, coach:'كابتن أمير فؤاد' },
{ id:'TN-01', name:'تنس أرضي — أطفال', cat:'فردي', icon:'i-tennis', age:'٧ - ١٢ سنة', sess:8, price:1400, join:350, cap:12, taken:12, coach:'كابتن نهى سامي' },
{ id:'GY-01', name:'جمباز فني — سيدات', cat:'فردي', icon:'i-gym', age:'١٢ - ١٨ سنة', sess:12, price:1000, join:300, cap:18, taken:11, coach:'كابتن دينا عادل' }
],
subs: [
{ id:'s1', actId:'SW-01', kid:'يوسف محمد', group:'مجموعة أ — صباحي', state:'نشط' },
{ id:'s2', actId:'GY-01', kid:'لُجين محمد', group:'مجموعة ب — مسائي', state:'تُجدَّد خلال ٥ أيام' }
],
schedule: {
'SW-01': { group:'مجموعة أ — صباحي', coach:'كابتن هشام صلاح', place:'حمام السباحة الأولمبي',
days:[ {d:'السبت',f:'09:00',t:'10:00'}, {d:'الاثنين',f:'09:00',t:'10:00'}, {d:'الأربعاء',f:'09:00',t:'10:00', note:'حصة قياس مستوى'} ],
changes:['أُلغيت حصة الاثنين 10/03 لصيانة حمام السباحة.','حصة تعويضية الجمعة 14/03 الساعة 10:00.'] },
'GY-01': { group:'مجموعة ب — مسائي', coach:'كابتن دينا عادل', place:'صالة الجمباز',
days:[ {d:'الأحد',f:'17:00',t:'18:30'}, {d:'الثلاثاء',f:'17:00',t:'18:30'} ], changes:[] }
},
entries: [
{ gate:'البوابة الرئيسية', when:'اليوم 08:14' },
{ gate:'بوابة حمام السباحة', when:'أمس 17:02' },
{ gate:'البوابة الرئيسية — ومعه ضيف', when:'أمس 16:58' }
],
notifications: [
{ id:'n1', title:'قسط مستحق منذ يومين', body:'قسط تجديد العضوية ٢ من ٣ بقيمة 1,500 ج.م.', when:'اليوم 09:10', kind:'due', unread:true },
{ id:'n2', title:'حصة تعويضية', body:'أُضيفت حصة سباحة الجمعة 14/03 الساعة 10:00.', when:'أمس 18:40', kind:'sched', unread:true },
{ id:'n3', title:'تجديد اشتراك لُجين', body:'اشتراك الجمباز يُجدَّد خلال ٥ أيام.', when:'أمس 09:00', kind:'act', unread:true },
{ id:'n4', title:'تم استلام دفعتك', body:'وصل سداد 1,000 ج.م — إيصال رقم 20260203.', when:'03/02', kind:'ok', unread:false }
]
};
......@@ -255,98 +255,9 @@ h2.title{ font-size:clamp(1.7rem,3.4vw,2.5rem); color:var(--navy); margin-bottom
background:#F4F6FA; border-radius:34px; overflow:hidden; height:686px;
position:relative; display:flex; flex-direction:column;
}
.sbar{
height:46px; flex-shrink:0; display:flex; align-items:flex-end; justify-content:space-between;
padding:0 26px 6px; font-size:.72rem; font-weight:700; color:#0E1A2B; position:relative; z-index:6;
}
.sbar.on-dark{ color:#fff; }
.scr-body{ flex:1; overflow-y:auto; overflow-x:hidden; }
.scr-body::-webkit-scrollbar{ width:0; }
/* app chrome */
.app-hd{ padding:6px 20px 18px; }
.app-hd.navy{ background:linear-gradient(150deg,#0C2247,#123566 60%,#16407A); color:#fff; padding-bottom:26px; border-radius:0 0 26px 26px; }
.app-hd h3{ font-size:1.08rem; }
.app-hd .sm{ font-size:.74rem; opacity:.7; }
.app-back{ font-size:1.1rem; }
.tabbar{
flex-shrink:0; height:66px; background:#fff; border-top:1px solid var(--line);
display:flex; align-items:center; justify-content:space-around; padding-bottom:8px;
}
.tab{ display:flex; flex-direction:column; align-items:center; gap:3px; font-size:.62rem; color:var(--txt-3); font-weight:600; }
.tab i{ font-size:1.15rem; font-style:normal; }
.tab.on{ color:var(--navy); }
.tab.on i{ filter:drop-shadow(0 2px 6px rgba(18,53,102,.3)); }
/* app widgets */
.mcard{
margin:0 16px 14px; background:#fff; border-radius:16px; padding:16px;
border:1px solid var(--line); box-shadow:0 1px 3px rgba(10,25,50,.04);
}
.mcard-t{ font-size:.78rem; font-weight:700; color:var(--txt-3); margin-bottom:10px; display:flex; justify-content:space-between; align-items:center; }
.mrow{ display:flex; justify-content:space-between; align-items:center; padding:9px 0; border-bottom:1px solid var(--paper-3); font-size:.8rem; }
.mrow:last-child{ border-bottom:none; }
.mrow .l{ color:var(--txt-2); }
.mrow .v{ font-weight:700; color:var(--txt); }
.chip{ display:inline-block; font-size:.66rem; font-weight:700; padding:3px 10px; border-radius:99px; }
.chip.due{ background:var(--rose-dim); color:var(--rose); }
.chip.ok{ background:var(--mint-dim); color:#0E8C60; }
.chip.soon{ background:var(--gold-dim); color:#9A7B12; }
.chip.info{ background:rgba(62,140,224,.12); color:var(--sky); }
.mbtn{
display:block; width:100%; text-align:center; padding:13px; border-radius:13px;
font-family:var(--f-head); font-weight:700; font-size:.86rem;
background:linear-gradient(135deg,var(--gold),#B8901C); color:#fff; box-shadow:0 6px 18px rgba(201,162,39,.3);
}
.mbtn.navy{ background:var(--navy); box-shadow:0 6px 18px rgba(18,53,102,.28); }
.mbtn.ghost{ background:#fff; color:var(--navy); border:1.5px solid var(--line-2); box-shadow:none; }
/* membership digital card */
.dcard{
margin:-38px 16px 18px; border-radius:20px; padding:18px;
background:linear-gradient(135deg,#123566,#0C2247 60%,#1B4A8C);
color:#fff; position:relative; overflow:hidden; box-shadow:0 14px 34px rgba(12,34,71,.35);
}
.dcard::after{
content:''; position:absolute; inset-inline-end:-40px; top:-40px; width:150px; height:150px;
border-radius:50%; background:radial-gradient(circle,rgba(201,162,39,.45),transparent 70%);
}
.dcard .who{ font-family:var(--f-head); font-weight:800; font-size:1.02rem; }
.dcard .meta{ font-size:.7rem; opacity:.72; }
.dcard .idno{ font-family:var(--f-head); letter-spacing:.16em; font-size:1rem; color:var(--gold-2); margin-top:12px; }
.qr-box{
width:190px; height:190px; margin:0 auto; background:#fff; border-radius:18px; padding:12px;
box-shadow:0 10px 30px rgba(10,25,50,.14); display:grid; place-items:center;
}
.qr-grid{ width:100%; height:100%; display:grid; grid-template-columns:repeat(11,1fr); grid-template-rows:repeat(11,1fr); gap:2px; }
.qr-grid i{ background:#0E1A2B; border-radius:1px; }
.seg{ display:flex; background:var(--paper-3); border-radius:11px; padding:3px; margin:0 16px 14px; }
.seg button{ flex:1; font-size:.76rem; font-weight:700; padding:8px; border-radius:9px; color:var(--txt-3); }
.seg button.on{ background:#fff; color:var(--navy); box-shadow:var(--sh-s); }
.sport-item{ display:flex; gap:12px; align-items:center; margin:0 16px 10px; background:#fff; border:1px solid var(--line); border-radius:15px; padding:12px; }
.sport-ico{ width:44px; height:44px; border-radius:12px; display:grid; place-items:center; font-size:1.25rem; background:var(--paper-3); flex-shrink:0; }
.sport-item .nm{ font-weight:700; font-size:.85rem; }
.sport-item .ds{ font-size:.7rem; color:var(--txt-3); }
.sport-item .pr{ margin-inline-start:auto; text-align:center; }
.sport-item .pr b{ font-size:.9rem; color:var(--navy); font-family:var(--f-head); }
.sport-item .pr span{ display:block; font-size:.62rem; color:var(--txt-3); }
.tt{ margin:0 16px 10px; background:#fff; border:1px solid var(--line); border-radius:14px; overflow:hidden; }
.tt-d{ background:var(--paper-2); padding:7px 14px; font-size:.74rem; font-weight:700; color:var(--navy); border-bottom:1px solid var(--line); }
.tt-r{ display:flex; align-items:center; gap:10px; padding:10px 14px; font-size:.76rem; border-bottom:1px solid var(--paper-3); }
.tt-r:last-child{ border-bottom:none; }
.tt-r .tm{ font-weight:700; color:var(--navy); font-size:.72rem; direction:ltr; }
.otp-row{ display:flex; gap:10px; justify-content:center; margin:22px 0; direction:ltr; }
.otp-row i{
width:52px; height:60px; border-radius:13px; background:#fff; border:1.5px solid var(--line-2);
display:grid; place-items:center; font-style:normal; font-family:var(--f-head); font-weight:800; font-size:1.4rem; color:var(--navy);
}
.otp-row i.f{ border-color:var(--gold); box-shadow:0 0 0 3px var(--gold-dim); }
/* The in-page phone UI that used to live here (.sbar/.app-hd/.mcard/.tabbar/
.sport-item/.otp-row/.qr-*) was removed with the static prototype — the
phone now embeds the real app from app/, which carries its own styles. */
/* ================= BROWSER / PORTAL ================= */
.browser{
......@@ -617,9 +528,6 @@ footer::before{
}
footer > *{ position:relative; z-index:1; }
/* Hairline gold rule closing each major light section. */
.rule-gold{ height:1px; border:0; margin:0;
background:linear-gradient(90deg,transparent,rgba(201,162,39,.42),transparent); }
@media (max-width:760px){
.wm::before{ width:74vw; inset-inline-end:-26%; opacity:.05; }
......@@ -652,3 +560,14 @@ footer > *{ position:relative; z-index:1; }
/* .card h4 is declared after .dark h4 at equal specificity, so cards placed
inside a .dark section inherited navy-on-navy headings. Raise specificity. */
.dark .card h4{ color:#fff; }
/* ================= LIVE PROTOTYPE =================
The phone now hosts the real prototype in an iframe rather than a stack of
static divs, so the screens in the proposal are the screens that ship. */
.phone iframe.screen{ width:100%; border:0; display:block; background:#070F1E; }
.proto-tools{ display:flex; gap:8px; flex-wrap:wrap; margin-top:14px; }
.proto-tools .pbtn{ cursor:pointer; }
.proto-nav{ max-height:none; }
@media (max-width:1020px){
.proto-tools{ justify-content:center; }
}
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment