Commit deefac32 authored by Mahmoud Aglan's avatar Mahmoud Aglan

content(proposal): reprice to 500k, 2-4 weeks, add club news, trim tech detail

Commercial terms:
- Price 900,000 -> 500,000 EGP, breakdown rebased to sum exactly
  (230k app + 160k portal/CMS + 60k gate/invites + 50k launch)
- Payments simplified from three milestones to two: 360,000 on signing,
  140,000 on delivery
- Timeline 4-6 weeks -> 2-4 weeks, timeline recompressed from five
  milestones to four
- Early-signing discount recalculated: 5% = 25,000 (was 45,000)
- "غير شاملة ضريبة القيمة المضافة" now stated in the price hero, the
  totals row, under the payment schedule, in the hero stat and footer,
  and bolded in the FAQ

New scope — club news / blog:
- Fourth axis added; news moved out of the deferred list into phase one
- Live prototype gains a news feed, article page and a fifth tab, with
  home showing the two latest items
- Portal gains a news management screen: article list with reach stats,
  editor with category, image drop and publish-notification toggle
- Article artwork is a branded crest placeholder, not stock photography —
  the club supplies real images at launch and inventing them would
  misrepresent what has been approved

Reduced technical detail per request:
- Dropped the per-template column-name lists (six of them) and the three
  import-engine cards, replaced with one plain note
- Removed API/OTP/RTL/iOS-14/Android-8 jargon throughout; rewrote the
  security and extensibility cards in business terms
- Simplified the in-scope list wording

Also makes .tbl scroll rather than clip: a table too wide for its column
silently lost its last cells instead of becoming reachable.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
parent cf882849
......@@ -376,3 +376,43 @@ img{ display:block; max-width:100%; }
.hd .sub{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.row .l b, .row .l span{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.due .mid b{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* ================= NEWS =================
Article artwork is a branded placeholder, not stock photography: the club
supplies real images at launch, and inventing them here would misrepresent
what has been approved. Each tone tints the same crest-backed panel. */
.nimg{
position:relative; height:104px; border-radius:var(--r-m); overflow:hidden;
flex-shrink:0; width:112px; display:grid; place-items:center;
background:linear-gradient(140deg,#123566,#0A1C38);
}
.nimg.tall{ width:auto; height:186px; border-radius:0; }
.nimg.gold{ background:linear-gradient(140deg,#8A6E14,#2A2003); }
.nimg.mint{ background:linear-gradient(140deg,#0E7355,#04241A); }
.nimg.sky { background:linear-gradient(140deg,#1D5E9E,#0A1C38); }
.nimg.rose{ background:linear-gradient(140deg,#9B3244,#2C0F16); }
.nimg .crest{
position:absolute; inset:0; opacity:.30;
background:url(../../assets/club-logo.png) center/58% no-repeat;
filter:grayscale(1) brightness(2.4);
}
.nimg.tall .crest{ background-size:34%; }
.nimg .ph{ position:relative; width:22px; height:22px; color:#fff; opacity:.5; }
.nimg.tall .ph{ width:30px; height:30px; }
.ncard{
display:flex; gap:12px; align-items:stretch; background:var(--surface);
border:1px solid var(--line); border-radius:var(--r-m); overflow:hidden;
margin-bottom:10px; cursor:pointer; transition:border-color .2s;
}
.ncard:active{ border-color:var(--line-2); }
.ncard.lead{ display:block; }
.ncard .nbody{ flex:1; min-width:0; padding:12px 13px; display:flex; flex-direction:column; gap:5px; justify-content:center; }
.ncard.lead .nbody{ padding:14px; }
.ncard .nmeta{ display:flex; align-items:center; gap:8px; font-size:.66rem; color:var(--txt-3); }
.ncard b{ font-family:var(--f-head); font-size:.87rem; line-height:1.5;
display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.ncard.lead b{ font-size:1rem; -webkit-line-clamp:3; }
.ncard .nlead{ font-size:.75rem; color:var(--txt-3); line-height:1.7;
display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.ncard:not(.lead) .nlead{ display:none; }
......@@ -36,6 +36,8 @@
<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-news" viewBox="0 0 24 24"><path d="M17.5 20.4H5.2a2 2 0 0 1-2-2V5.6a2 2 0 0 1 2-2h12.3v14.8a2 2 0 0 0 2 2 2 2 0 0 0 2-2V9.4h-4"/><path d="M6.6 7.6h7.4M6.6 11.4h7.4M6.6 15.2h4.6"/></symbol>
<symbol id="i-image" viewBox="0 0 24 24"><rect x="3.2" y="4.6" width="17.6" height="14.8" rx="2.2"/><circle cx="8.6" cy="9.8" r="1.7"/><path d="m3.8 17.2 4.9-4.4a1.8 1.8 0 0 1 2.4 0l4.2 3.8"/><path d="m13.6 13.4 2.2-1.9a1.8 1.8 0 0 1 2.4 0l2.4 2.1"/></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>
......
......@@ -72,6 +72,7 @@
const t = [
['home', 'i-home', 'الرئيسية'],
['activities', 'i-activity', 'الأنشطة'],
['news', 'i-news', 'الأخبار'],
['qr', 'i-gate', 'الدخول'],
['profile', 'i-user', 'حسابي']
];
......@@ -297,6 +298,16 @@
<b style="font-size:.82rem"><span class="num">${invLeft()}</span> من <span class="num">${invTotal()}</span></b>
</div>
</div>
<div class="card-t" style="margin:18px 0 10px"><h3 style="font-size:.9rem">آخر أخبار النادي</h3>
<span class="more" data-go="#/news">كل الأخبار</span></div>
${S.news.slice(0, 2).map(a => `
<div class="ncard row-style" data-go="#/article/${a.id}">
${newsArt(a.tone)}
<div class="nbody">
<div class="nmeta"><span class="chip info">${a.cat}</span><span>${a.date}</span></div>
<b>${a.title}</b>
</div></div>`).join('')}
</div>${tabbar('home')}`
};
};
......@@ -728,6 +739,67 @@
};
};
/* Placeholder artwork: a branded gradient plus the crest, not stock photography.
The club supplies real images at launch; inventing them here would misrepresent
what has been approved. */
function newsArt(tone, tall) {
return `<div class="nimg ${tone} ${tall ? 'tall' : ''}">
<span class="crest"></span>${ico('i-image', 'ph')}</div>`;
}
SCREENS.news = () => {
let cat = 'الكل';
return {
tab: 'news',
body: `${statusbar()}${header({ title: 'أخبار النادي', sub: 'آخر ما نُشر' })}
<div class="screen-body pad">
<div class="chips" id="cats"></div><div id="list"></div>
</div>${tabbar('news')}`,
mount(root) {
const cats = ['الكل', ...new Set(S.news.map(a => a.cat))];
const cbox = $('#cats', root), list = $('#list', root);
const paint = () => {
cbox.innerHTML = cats.map(c => `<button class="${c === cat ? 'on' : ''}" data-c="${c}">${c}</button>`).join('');
cbox.querySelectorAll('[data-c]').forEach(b => b.onclick = () => { cat = b.dataset.c; paint(); });
const items = S.news.filter(a => cat === 'الكل' || a.cat === cat);
list.innerHTML = items.map((a, i) => {
const lead = i === 0 && cat === 'الكل';
return `<div class="ncard ${lead ? 'lead' : ''}" data-go="#/article/${a.id}">
${newsArt(a.tone, lead)}
<div class="nbody">
<div class="nmeta"><span class="chip info">${a.cat}</span>
<span>${a.date}</span></div>
<b>${a.title}</b>
<span class="nlead">${a.lead}</span>
</div></div>`;
}).join('');
bindAll(list);
};
paint();
}
};
};
SCREENS.article = (id) => {
const a = S.news.find(x => x.id === id);
if (!a) return SCREENS.news();
return {
body: `${statusbar()}${header({ back: true, title: a.cat, sub: a.date })}
<div class="screen-body">
${newsArt(a.tone, true)}
<div class="pad">
<h2 style="font-size:1.15rem;line-height:1.45">${a.title}</h2>
<p style="font-size:.85rem;color:var(--txt-2);margin-top:8px;font-weight:600">${a.lead}</p>
<div style="height:1px;background:var(--line);margin:14px 0"></div>
${a.body.split('\n\n').map(par =>
`<p style="font-size:.85rem;color:var(--txt-2);line-height:1.95;margin-bottom:12px">${par}</p>`).join('')}
<button class="btn btn-ghost" data-toast="نموذج تجريبي — المشاركة غير مفعَّلة">مشاركة الخبر</button>
<div style="height:16px"></div>
</div>
</div>`
};
};
SCREENS.notifications = () => ({
body: `${statusbar()}${header({ back: true, title: 'الإشعارات',
sub: unreadCount() ? `${unreadCount()} غير مقروء` : 'لا جديد' })}
......
......@@ -64,6 +64,24 @@ window.DB = {
{ gate:'البوابة الرئيسية — ومعه ضيف', when:'أمس 16:58' }
],
/* Club blog. Images are described by a tone key rather than a file: the
prototype paints a branded placeholder, so the demo never ships stock
photos the club has not approved. */
news: [
{ id:'a1', cat:'بطولات', tone:'gold', date:'11 مارس 2026', title:'فريق السباحة يحصد ٧ ميداليات في بطولة الجمهورية',
lead:'حقق فريق النادي للسباحة نتائج هي الأفضل منذ خمس سنوات في بطولة الجمهورية للناشئين.',
body:'اختتمت أمس منافسات بطولة الجمهورية للناشئين، وحصد فريق نادي الصيد المصري سبع ميداليات: ثلاث ذهبيات وميداليتين فضيتين وبرونزيتين، ليحتل المركز الثاني في الترتيب العام.\n\nوجّه مجلس الإدارة الشكر إلى الجهاز الفني بقيادة كابتن هشام صلاح، وإلى أولياء الأمور على دعمهم المتواصل. وتبدأ استعدادات الفريق للبطولة العربية مطلع الشهر القادم.', pinned:true },
{ id:'a2', cat:'فعاليات', tone:'mint', date:'9 مارس 2026', title:'فتح باب التسجيل في معسكر الصيف للأطفال',
lead:'يبدأ المعسكر أول يوليو ويستمر ستة أسابيع، ويشمل السباحة وكرة القدم والتنس.',
body:'أعلنت إدارة الأنشطة الرياضية فتح باب التسجيل في معسكر الصيف للأطفال من سن ٦ إلى ١٤ سنة.\n\nيشمل المعسكر تدريبات يومية في السباحة وكرة القدم والتنس، بالإضافة إلى أنشطة ترفيهية ووجبة غداء. الأماكن محدودة والتسجيل من التطبيق مباشرة عبر قسم الأنشطة.' },
{ id:'a3', cat:'إعلانات', tone:'sky', date:'6 مارس 2026', title:'صيانة حمام السباحة الأولمبي الأسبوع القادم',
lead:'يغلق الحمام الأولمبي من الاثنين إلى الأربعاء لأعمال الصيانة الدورية.',
body:'تُجرى أعمال الصيانة الدورية لحمام السباحة الأولمبي خلال الفترة من الاثنين ١٠ مارس إلى الأربعاء ١٢ مارس.\n\nتُنقل حصص السباحة خلال هذه الأيام إلى الحمام المغطى، وستصل رسالة بالمواعيد الجديدة لكل مجموعة. نعتذر عن أي إزعاج.' },
{ id:'a4', cat:'أخبار المجلس', tone:'rose', date:'2 مارس 2026', title:'مجلس الإدارة يعتمد خطة تطوير الملاعب',
lead:'اعتمد المجلس في اجتماعه الأخير خطة تطوير ملاعب التنس وإنشاء صالة جديدة متعددة الأغراض.',
body:'اعتمد مجلس إدارة النادي خطة تطوير المنشآت الرياضية للعام الجاري، وتشمل إعادة تأهيل ملاعب التنس الأربعة وإنشاء صالة متعددة الأغراض.\n\nمن المقرر أن تبدأ الأعمال مطلع الشهر القادم وتنتهي قبل بداية الموسم الصيفي.' }
],
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 },
......
......@@ -301,7 +301,7 @@ h2.title{ font-size:clamp(1.7rem,3.4vw,2.5rem); color:var(--navy); margin-bottom
.kpi .d.up{ color:var(--mint); }
.kpi .d.dn{ color:var(--rose); }
.tbl{ background:#fff; border:1px solid var(--line); border-radius:12px; overflow:hidden; }
.tbl{ background:#fff; border:1px solid var(--line); border-radius:12px; overflow-x:auto; overflow-y:hidden; }
.tbl table{ font-size:.78rem; }
.tbl th{
background:var(--paper-2); text-align:start; padding:10px 14px; font-weight:700;
......@@ -571,3 +571,11 @@ footer > *{ position:relative; z-index:1; }
@media (max-width:1020px){
.proto-tools{ justify-content:center; }
}
/* .tbl clips rather than scrolls and its cells are nowrap, so a long Arabic
headline in a narrow column loses the status badge off the edge. Let the
title column wrap in the news table only. */
.news-tbl td:first-child{ white-space:normal; min-width:150px; line-height:1.55; }
@media (max-width:1020px){
#portal .grid[style*="1.3fr"]{ grid-template-columns:1fr !important; }
}
This diff is collapsed.
......@@ -49,7 +49,7 @@
var NOTES = {
login: { t:'الدخول برقم الموبايل',
p:'يدخل العضو برقم موبايله المسجَّل في بيانات النادي، دون إنشاء حساب أو كلمة مرور، ودون تفعيل يدوي من أي موظف.',
b:['التعرف الفوري على العضو من ملف الأعضاء المرفوع','كود تحقق OTP صالح لدقيقتين','جهاز واحد نشط لكل عضوية لمنع تداول الحساب'] },
b:['التعرف الفوري على العضو من ملف الأعضاء المرفوع','كود تحقق صالح لدقيقتين','جهاز واحد نشط لكل عضوية لمنع تداول الحساب'] },
home: { t:'الشاشة الرئيسية',
p:'تعرض حالة العضوية وإجمالي المستحق وطريقة السداد في موضع واحد، وتغني البطاقة الرقمية عن الكارنيه الورقي.',
b:['حالة العضوية وتاريخ سريانها','إجمالي المستحق مع تنبيه التأخير','اشتراكات الأبناء ورصيد الدعوات'] },
......@@ -74,6 +74,12 @@
schedule: { t:'جداول التدريب',
p:'يرفع المدرب جدول مجموعته من قالب موحَّد، فيظهر لكل مشترك في تطبيقه، ويصل تنبيه فوري عند أي إلغاء أو حصة تعويضية.',
b:['جدول أسبوعي لكل لاعب على حدة','ملاحظات المدرب على الحصة','إشعار فوري بالتغييرات والحصص التعويضية'] },
news: { t:'أخبار النادي',
p:'قسم أخبار داخل التطبيق تنشر عليه الإدارة الأخبار والفعاليات والبطولات بالصور، ويصل الإشعار للعضو فور النشر.',
b:['نشر من البوابة: عنوان ونص وصور','تصنيف الأخبار (بطولات، فعاليات، إعلانات)','إشعار فوري للأعضاء عند النشر'] },
article: { t:'صفحة الخبر',
p:'الخبر كاملًا بصوره داخل التطبيق دون فتح متصفح. الصور المعروضة في النموذج نائبة عن صور النادي الحقيقية التي تُرفع عند التشغيل.',
b:['عرض كامل بالصور داخل التطبيق','عدد غير محدود من المنشورات والصور','مشاركة الخبر خارج التطبيق'] },
receipts: { t:'الإيصالات',
p:'كل عملية سداد تُنشئ إيصالًا فوريًا داخل التطبيق. جرّبوا السداد من شاشة المستحقات وسيظهر الإيصال هنا.',
b:['إيصال فوري بعد كل عملية','أرشيف كامل بوسيلة الدفع والتاريخ','قابل للتحميل بصيغة PDF'] },
......@@ -144,7 +150,8 @@
members: 'https://portal.sayd-club.eg/members',
catalog: 'https://portal.sayd-club.eg/activities/pricing',
invites: 'https://portal.sayd-club.eg/invitations/policy',
sched: 'https://portal.sayd-club.eg/schedules'
sched: 'https://portal.sayd-club.eg/schedules',
news: 'https://portal.sayd-club.eg/news'
};
var burl = document.getElementById('burl');
var pBtns = document.querySelectorAll('.pn-item[data-pscr]');
......
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