diff --git a/ar/contribute.html b/ar/contribute.html
new file mode 100644
index 0000000..d81afd3
--- /dev/null
+++ b/ar/contribute.html
@@ -0,0 +1,173 @@
+
+
+
+
+
+ NexCore Labs | مركز المساهمين
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
مركز المساهمين
+
ساعد شيئًا مفيدًا على المضي قدمًا.
+
اختر طريقة عملية للمساهمة—انشر أعمال الطلبة، أو ساهم في تشكيل خارطة الطريق، أو اختبر التجربة، أو حسّن قاعدة الشيفرة مفتوحة المصدر.
+
+
+
+
شاركانشر عملًا مفيدًا
+
ابنِحسّن الأدوات المفتوحة
+
اختبراجعل الوصول أوضح
+
ساهم برأيكوجّه ما سيُطلق لاحقًا
+
+
+
+
+ اختر مسار مساهمة
ابدأ بما يمكنك تقديمه الآن. يقود كل مسار إلى إجراء حقيقي داخل NexCore.
+ نشاط مساهماتك
تظهر هنا المشاريع وأفكار خارطة الطريق التي ترسلها عند تسجيل الدخول.
+ هل لديك تحسين صغير في ذهنك؟
الفكرة المحددة والمشروحة جيدًا مساهمة قيّمة أيضًا.
شارك الفكرة
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/css/contribute.css b/assets/css/contribute.css
new file mode 100644
index 0000000..cfa43ce
--- /dev/null
+++ b/assets/css/contribute.css
@@ -0,0 +1,548 @@
+.contribute-hero {
+ min-height: 0;
+ padding: 132px 0 54px;
+}
+
+.contribute-shell {
+ width: min(1180px, calc(100% - 48px));
+ margin: 0 auto;
+}
+
+.contribute-hero-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1.12fr) minmax(320px, .88fr);
+ align-items: center;
+ gap: clamp(32px, 6vw, 76px);
+ padding-top: 58px;
+}
+
+.contribute-eyebrow {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ margin: 0 0 14px;
+ color: #6ee7f3;
+ font-size: .76rem;
+ font-weight: 800;
+ letter-spacing: .12em;
+ text-transform: uppercase;
+}
+
+.contribute-title {
+ max-width: 720px;
+ margin: 0 0 20px;
+ font-size: clamp(2.8rem, 5vw, 5rem);
+ line-height: .98;
+ letter-spacing: -.055em;
+}
+
+.contribute-title em {
+ color: #6ee7f3;
+ font-style: normal;
+}
+
+.contribute-lead {
+ max-width: 60ch;
+ margin: 0;
+ color: var(--muted);
+ font-size: 1rem;
+ line-height: 1.7;
+}
+
+.contribute-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+ margin-top: 26px;
+}
+
+.contribute-actions .btn {
+ justify-content: center;
+}
+
+.contribute-summary {
+ position: relative;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 10px;
+ padding: 16px;
+ overflow: hidden;
+ border: 2px solid rgba(255, 255, 255, .035);
+ border-radius: 20px;
+ background: linear-gradient(135deg, rgba(110, 231, 243, .08), rgba(10, 11, 28, .58) 58%, rgba(172, 217, 229, .04));
+ box-shadow: var(--card-shadow);
+ backdrop-filter: blur(8px);
+ -webkit-backdrop-filter: blur(8px);
+}
+
+.contribute-summary::before {
+ position: absolute;
+ top: -90px;
+ right: -70px;
+ width: 230px;
+ height: 230px;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(110, 231, 243, .2), transparent 68%);
+ content: "";
+ pointer-events: none;
+}
+
+.contribute-summary-item {
+ position: relative;
+ z-index: 1;
+ min-height: 112px;
+ padding: 18px;
+ border: 1px solid rgba(255, 255, 255, .06);
+ border-radius: 14px;
+ background: rgba(255, 255, 255, .025);
+}
+
+.contribute-summary-item i {
+ color: #6ee7f3;
+ font-size: 1rem;
+}
+
+.contribute-summary-item strong {
+ display: block;
+ margin-top: 14px;
+ color: #eaf6fb;
+ font-size: 1.25rem;
+ line-height: 1.1;
+}
+
+.contribute-summary-item span {
+ display: block;
+ margin-top: 6px;
+ color: var(--muted);
+ font-size: .75rem;
+}
+
+.contribute-section {
+ padding: 52px 0;
+}
+
+.contribute-section-head {
+ display: flex;
+ align-items: end;
+ justify-content: space-between;
+ gap: 24px;
+ margin-bottom: 24px;
+}
+
+.contribute-section-head .section-lead {
+ margin-bottom: 0;
+}
+
+.contribute-filters {
+ display: flex;
+ max-width: 100%;
+ gap: 8px;
+ overflow-x: auto;
+ padding: 4px;
+ scrollbar-width: thin;
+}
+
+.contribute-filter {
+ flex: 0 0 auto;
+ padding: 8px 14px;
+ border: 1px solid rgba(255, 255, 255, .1);
+ border-radius: 999px;
+ background: rgba(255, 255, 255, .015);
+ color: var(--muted);
+ font: inherit;
+ font-size: .82rem;
+ cursor: pointer;
+}
+
+.contribute-filter:hover,
+.contribute-filter[aria-pressed="true"] {
+ border-color: rgba(110, 231, 243, .55);
+ background: rgba(110, 231, 243, .1);
+ color: #dffbff;
+}
+
+.contribute-filter:focus-visible,
+.contribute-card:focus-visible,
+.activity-link:focus-visible {
+ outline: 2px solid #6ee7f3;
+ outline-offset: 3px;
+}
+
+.contribute-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 16px;
+}
+
+.contribute-card {
+ display: flex;
+ min-width: 0;
+ flex-direction: column;
+ padding: 24px;
+ border: 1px solid rgba(255, 255, 255, .055);
+ border-radius: 16px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, .038), rgba(255, 255, 255, .012));
+ box-shadow: 0 10px 30px rgba(2, 6, 23, .48);
+}
+
+.contribute-card:hover {
+ border-color: rgba(110, 231, 243, .22);
+ box-shadow: 0 18px 48px rgba(2, 6, 23, .68), 0 0 24px rgba(110, 231, 243, .07);
+ transform: translateY(-4px);
+}
+
+.contribute-card-top {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16px;
+}
+
+.contribute-card-icon {
+ display: inline-flex;
+ width: 42px;
+ height: 42px;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 auto;
+ border: 1px solid rgba(110, 231, 243, .18);
+ border-radius: 12px;
+ background: rgba(110, 231, 243, .08);
+ color: #6ee7f3;
+}
+
+.contribute-card-kind {
+ padding: 5px 9px;
+ border-radius: 999px;
+ background: rgba(255, 255, 255, .05);
+ color: var(--muted);
+ font-size: .68rem;
+ font-weight: 700;
+ letter-spacing: .06em;
+ text-transform: uppercase;
+}
+
+.contribute-card h3 {
+ margin: 20px 0 8px;
+ color: #eaf6fb;
+ font-size: 1.15rem;
+}
+
+.contribute-card p {
+ margin: 0 0 20px;
+ color: var(--muted);
+ font-size: .9rem;
+ line-height: 1.65;
+}
+
+.contribute-card-meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 7px;
+ margin-bottom: 22px;
+}
+
+.contribute-card-meta span {
+ padding: 5px 8px;
+ border: 1px solid rgba(255, 255, 255, .06);
+ border-radius: 8px;
+ color: #b8c4cf;
+ font-size: .7rem;
+}
+
+.contribute-card .btn {
+ width: fit-content;
+ margin-top: auto;
+ font-size: .78rem;
+}
+
+.contribute-empty {
+ grid-column: 1 / -1;
+ padding: 36px;
+ border: 1px dashed rgba(110, 231, 243, .2);
+ border-radius: 16px;
+ color: var(--muted);
+ text-align: center;
+}
+
+.activity-panel {
+ display: grid;
+ grid-template-columns: minmax(250px, .72fr) minmax(0, 1.28fr);
+ gap: 16px;
+ padding: 18px;
+ border: 1px solid rgba(255, 255, 255, .055);
+ border-radius: 18px;
+ background: linear-gradient(135deg, rgba(110, 231, 243, .055), rgba(10, 11, 28, .5) 52%, rgba(255, 255, 255, .018));
+ box-shadow: var(--card-shadow);
+}
+
+.activity-overview,
+.activity-feed {
+ min-width: 0;
+ padding: 22px;
+ border-radius: 14px;
+ background: rgba(10, 11, 28, .48);
+}
+
+.activity-kicker {
+ display: inline-flex;
+ align-items: center;
+ gap: 7px;
+ color: #6ee7f3;
+ font-size: .72rem;
+ font-weight: 800;
+ letter-spacing: .09em;
+ text-transform: uppercase;
+}
+
+.activity-overview h3 {
+ margin: 12px 0 8px;
+ color: #eaf6fb;
+ font-size: 1.35rem;
+}
+
+.activity-overview p,
+.activity-feed p {
+ color: var(--muted);
+ font-size: .86rem;
+ line-height: 1.6;
+}
+
+.activity-stats {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 8px;
+ margin-top: 20px;
+}
+
+.activity-stat {
+ padding: 12px 8px;
+ border: 1px solid rgba(255, 255, 255, .055);
+ border-radius: 10px;
+ text-align: center;
+}
+
+.activity-stat strong {
+ display: block;
+ color: #eaf6fb;
+ font-size: 1.15rem;
+}
+
+.activity-stat span {
+ color: var(--muted);
+ font-size: .66rem;
+}
+
+.activity-feed-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ margin-bottom: 12px;
+}
+
+.activity-feed-head h3 {
+ margin: 0;
+ color: #eaf6fb;
+ font-size: 1rem;
+}
+
+.activity-list {
+ display: grid;
+ gap: 8px;
+}
+
+.activity-item {
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr) auto;
+ align-items: center;
+ gap: 12px;
+ padding: 12px;
+ border: 1px solid rgba(255, 255, 255, .05);
+ border-radius: 11px;
+ background: rgba(255, 255, 255, .018);
+}
+
+.activity-item-icon {
+ display: inline-flex;
+ width: 32px;
+ height: 32px;
+ align-items: center;
+ justify-content: center;
+ border-radius: 9px;
+ background: rgba(110, 231, 243, .08);
+ color: #6ee7f3;
+}
+
+.activity-item-title {
+ overflow: hidden;
+ color: #eaf6fb;
+ font-size: .84rem;
+ font-weight: 700;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.activity-item-meta {
+ color: var(--muted);
+ font-size: .68rem;
+}
+
+.activity-status {
+ padding: 5px 8px;
+ border-radius: 999px;
+ background: rgba(110, 231, 243, .09);
+ color: #bffcff;
+ font-size: .65rem;
+ white-space: nowrap;
+}
+
+.activity-status--completed,
+.activity-status--published,
+.activity-status--approved {
+ background: rgba(34, 197, 94, .12);
+ color: #86efac;
+}
+
+.activity-status--pending,
+.activity-status--planned,
+.activity-status--draft {
+ background: rgba(245, 166, 35, .12);
+ color: #ffd27a;
+}
+
+.activity-link {
+ color: #6ee7f3;
+ font-size: .75rem;
+ font-weight: 700;
+}
+
+.activity-message {
+ display: flex;
+ min-height: 190px;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ gap: 10px;
+ color: var(--muted);
+ text-align: center;
+}
+
+.activity-message i {
+ color: #6ee7f3;
+ font-size: 1.5rem;
+}
+
+.contribute-cta {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 28px;
+ margin: 38px 0 72px;
+ padding: 30px;
+ border: 1px solid rgba(110, 231, 243, .14);
+ border-radius: 18px;
+ background: linear-gradient(135deg, rgba(110, 231, 243, .08), rgba(10, 11, 28, .5) 54%, rgba(172, 217, 229, .04));
+ box-shadow: var(--card-shadow);
+}
+
+.contribute-cta h2 {
+ margin: 0 0 6px;
+ color: #eaf6fb;
+ font-size: 1.35rem;
+}
+
+.contribute-cta p {
+ margin: 0;
+ color: var(--muted);
+}
+
+html[dir="rtl"] .contribute-eyebrow,
+html[dir="rtl"] .contribute-card-kind,
+html[dir="rtl"] .activity-kicker {
+ letter-spacing: 0;
+}
+
+@media (max-width: 900px) {
+ .contribute-hero-grid,
+ .activity-panel {
+ grid-template-columns: 1fr;
+ }
+
+ .contribute-summary {
+ width: min(560px, 100%);
+ }
+
+ .contribute-section-head {
+ align-items: flex-start;
+ flex-direction: column;
+ }
+}
+
+@media (max-width: 680px) {
+ .contribute-shell {
+ width: min(100% - 28px, 1180px);
+ }
+
+ .contribute-hero {
+ padding: 104px 0 28px;
+ }
+
+ .contribute-hero-grid {
+ gap: 28px;
+ padding-top: 42px;
+ text-align: center;
+ }
+
+ .contribute-eyebrow,
+ .contribute-actions {
+ justify-content: center;
+ }
+
+ .contribute-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .contribute-card,
+ .activity-overview,
+ .activity-feed {
+ padding: 18px;
+ }
+
+ .activity-panel {
+ padding: 10px;
+ }
+
+ .activity-item {
+ grid-template-columns: auto minmax(0, 1fr);
+ }
+
+ .activity-status {
+ grid-column: 2;
+ width: fit-content;
+ }
+
+ .contribute-cta {
+ align-items: stretch;
+ flex-direction: column;
+ text-align: center;
+ }
+
+ .contribute-cta .btn {
+ justify-content: center;
+ }
+}
+
+@media (max-width: 420px) {
+ .contribute-summary {
+ grid-template-columns: 1fr;
+ }
+
+ .contribute-summary-item {
+ min-height: 94px;
+ }
+
+ .activity-stats {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/assets/js/auth-ui-db.js b/assets/js/auth-ui-db.js
index c47ca32..490ed52 100644
--- a/assets/js/auth-ui-db.js
+++ b/assets/js/auth-ui-db.js
@@ -80,6 +80,28 @@
hubLink.insertAdjacentElement('afterend', link);
}
+ function ensureContributeNavigation() {
+ const menu = document.getElementById('myDropdown');
+ if (!menu || menu.querySelector('[data-contribute-nav]')) return;
+
+ const initiativesLink = menu.querySelector('[data-initiatives-nav]');
+ const hubLink = [...menu.querySelectorAll('a')].find((link) =>
+ /(^|\/)hub(?:\.html)?(?:#|$)/.test(link.getAttribute('href') || '')
+ );
+ const anchor = initiativesLink || hubLink;
+ if (!anchor) return;
+
+ const link = document.createElement('a');
+ link.href = isArabicPage ? '/ar/contribute' : '/contribute';
+ link.dataset.contributeNav = 'true';
+ link.title = isArabicPage ? 'مركز المساهمين' : 'Contributor Center';
+ link.innerHTML = ` ${isArabicPage ? 'مركز المساهمين' : 'Contributor Center'} ${isArabicPage ? 'جديد' : 'New'}`;
+ if (window.location.pathname.replace(/\/$/, '') === link.getAttribute('href')) {
+ link.setAttribute('aria-current', 'page');
+ }
+ anchor.insertAdjacentElement('afterend', link);
+ }
+
/**
* Fetch approved emails from database
*/
@@ -397,6 +419,7 @@
// Initialize
function init() {
ensureInitiativesNavigation();
+ ensureContributeNavigation();
// Ensure nav elements exist
ensureNavElements();
diff --git a/assets/js/contribute.js b/assets/js/contribute.js
new file mode 100644
index 0000000..a6b638c
--- /dev/null
+++ b/assets/js/contribute.js
@@ -0,0 +1,340 @@
+(function () {
+ "use strict";
+
+ const isArabic = document.documentElement.lang.toLowerCase().startsWith("ar");
+ const locale = isArabic ? "ar" : "en";
+ const prefix = isArabic ? "../" : "";
+ const cleanPrefix = isArabic ? "/ar" : "";
+ const text = (en, ar) => (isArabic ? ar : en);
+ const byId = (id) => document.getElementById(id);
+
+ const paths = Object.freeze([
+ {
+ id: "share-project",
+ category: "share",
+ icon: "fa-solid fa-diagram-project",
+ kind: { en: "Share", ar: "شارك" },
+ title: { en: "Publish your project", ar: "انشر مشروعك" },
+ description: {
+ en: "Create a project profile, explain what you built, and make useful student work easier for the SQU community to discover.",
+ ar: "أنشئ ملفًا لمشروعك، واشرح ما بنيته، واجعل أعمال الطلبة المفيدة أسهل اكتشافًا لمجتمع جامعة السلطان قابوس."
+ },
+ meta: [{ en: "Project owners", ar: "أصحاب المشاريع" }, { en: "15–25 min", ar: "15–25 دقيقة" }],
+ href: `${cleanPrefix}/dashboard`,
+ cta: { en: "Create a project", ar: "أنشئ مشروعًا" }
+ },
+ {
+ id: "shape-roadmap",
+ category: "shape",
+ icon: "fa-solid fa-lightbulb",
+ kind: { en: "Shape", ar: "ساهم برأيك" },
+ title: { en: "Suggest an improvement", ar: "اقترح تحسينًا" },
+ description: {
+ en: "Share a focused feature idea, vote on community requests, and follow what moves from planned to shipped.",
+ ar: "شارك فكرة ميزة محددة، وصوّت على طلبات المجتمع، وتابع ما ينتقل من التخطيط إلى الإطلاق."
+ },
+ meta: [{ en: "Everyone", ar: "للجميع" }, { en: "5 min", ar: "5 دقائق" }],
+ href: `${cleanPrefix}/roadmap?action=suggest`,
+ cta: { en: "Suggest an idea", ar: "اقترح فكرة" }
+ },
+ {
+ id: "explore-initiatives",
+ category: "build",
+ icon: "fa-solid fa-wand-magic-sparkles",
+ kind: { en: "Build", ar: "ابنِ" },
+ title: { en: "Find an initiative to support", ar: "اعثر على مبادرة لدعمها" },
+ description: {
+ en: "Understand what NexCore Labs is launching, building, and exploring before offering the skill or perspective that fits.",
+ ar: "تعرّف على ما تطلقه NexCore Labs وما تبنيه وتستكشفه قبل تقديم المهارة أو المنظور المناسب."
+ },
+ meta: [{ en: "Collaborators", ar: "المتعاونون" }, { en: "Explore first", ar: "ابدأ بالاستكشاف" }],
+ href: `${cleanPrefix}/initiatives`,
+ cta: { en: "Explore initiatives", ar: "استكشف المبادرات" }
+ },
+ {
+ id: "improve-code",
+ category: "build",
+ icon: "fa-brands fa-github",
+ kind: { en: "Build", ar: "ابنِ" },
+ title: { en: "Improve the NexCore codebase", ar: "حسّن قاعدة شيفرة NexCore" },
+ description: {
+ en: "Review the open-source repository, report a reproducible issue, or propose a focused code improvement through GitHub.",
+ ar: "راجع المستودع مفتوح المصدر، أو أبلغ عن مشكلة قابلة للتكرار، أو اقترح تحسينًا محددًا للشيفرة عبر GitHub."
+ },
+ meta: [{ en: "Developers", ar: "المطورون" }, { en: "Open source", ar: "مفتوح المصدر" }],
+ href: "https://github.com/NexCoreLabs/NexCore",
+ external: true,
+ cta: { en: "Open GitHub", ar: "افتح GitHub" }
+ },
+ {
+ id: "test-experience",
+ category: "test",
+ icon: "fa-solid fa-universal-access",
+ kind: { en: "Test", ar: "اختبر" },
+ title: { en: "Test the community experience", ar: "اختبر تجربة المجتمع" },
+ description: {
+ en: "Use NexCore in English or Arabic, then turn accessibility, clarity, or mobile issues into a focused roadmap suggestion.",
+ ar: "استخدم NexCore بالعربية أو الإنجليزية، ثم حوّل مشكلات الوصول أو الوضوح أو الهاتف إلى اقتراح محدد في خارطة الطريق."
+ },
+ meta: [{ en: "Testers", ar: "المختبرون" }, { en: "EN + AR", ar: "عربي + إنجليزي" }],
+ href: `${cleanPrefix}/roadmap?action=suggest`,
+ cta: { en: "Report an improvement", ar: "أبلغ عن تحسين" }
+ },
+ {
+ id: "support-community",
+ category: "share",
+ icon: "fa-solid fa-people-group",
+ kind: { en: "Share", ar: "شارك" },
+ title: { en: "Help useful work get noticed", ar: "ساعد الأعمال المفيدة على الظهور" },
+ description: {
+ en: "Browse published projects, share the ones that solve real problems, and help more students find practical work from their peers.",
+ ar: "تصفّح المشاريع المنشورة، وشارك ما يحل مشكلات حقيقية، وساعد مزيدًا من الطلبة على اكتشاف أعمال عملية من زملائهم."
+ },
+ meta: [{ en: "Community", ar: "المجتمع" }, { en: "Any time", ar: "في أي وقت" }],
+ href: `${cleanPrefix}/hub#projects`,
+ cta: { en: "Browse projects", ar: "تصفّح المشاريع" }
+ }
+ ]);
+
+ const copy = {
+ filters: {
+ all: text("All paths", "كل المسارات"),
+ build: text("Build", "البناء"),
+ test: text("Test", "الاختبار"),
+ share: text("Share", "المشاركة"),
+ shape: text("Shape", "إبداء الرأي")
+ },
+ signInTitle: text("Your contribution story starts here", "تبدأ قصة مساهمتك من هنا"),
+ signInBody: text("Sign in to see your submitted projects and roadmap ideas together in one place.", "سجّل الدخول لترى مشاريعك وأفكار خارطة الطريق التي أرسلتها معًا في مكان واحد."),
+ signIn: text("Sign in", "تسجيل الدخول"),
+ activityTitle: text("Your contribution activity", "نشاط مساهماتك"),
+ activityBody: text("A live view of the work and ideas you have shared with NexCore.", "عرض مباشر للأعمال والأفكار التي شاركتها مع NexCore."),
+ projects: text("Projects", "المشاريع"),
+ ideas: text("Ideas", "الأفكار"),
+ shipped: text("Shipped", "المُطلقة"),
+ recent: text("Recent submissions", "أحدث المشاركات"),
+ viewAll: text("Open dashboard", "افتح لوحة التحكم"),
+ emptyTitle: text("No submissions yet", "لا توجد مشاركات بعد"),
+ emptyBody: text("Choose one contribution path above and your activity will appear here.", "اختر أحد مسارات المساهمة أعلاه وسيظهر نشاطك هنا."),
+ loading: text("Loading your activity…", "جارٍ تحميل نشاطك…"),
+ unavailable: text("Your activity could not be loaded right now.", "تعذر تحميل نشاطك الآن."),
+ project: text("Project", "مشروع"),
+ idea: text("Roadmap idea", "فكرة خارطة طريق"),
+ filtersLabel: text("Filter contribution paths", "تصفية مسارات المساهمة")
+ };
+
+ const state = { activeFilter: "all" };
+
+ function renderFilters() {
+ const container = byId("contributeFilters");
+ if (!container) return;
+ container.setAttribute("aria-label", copy.filtersLabel);
+ container.replaceChildren(...Object.entries(copy.filters).map(([value, label]) => {
+ const button = document.createElement("button");
+ button.type = "button";
+ button.className = "contribute-filter";
+ button.dataset.filter = value;
+ button.setAttribute("aria-pressed", String(value === state.activeFilter));
+ button.textContent = label;
+ return button;
+ }));
+ }
+
+ function createPathCard(path) {
+ const article = document.createElement("article");
+ article.className = "contribute-card";
+ article.dataset.category = path.category;
+ article.dataset.pathId = path.id;
+
+ const top = document.createElement("div");
+ top.className = "contribute-card-top";
+ const icon = document.createElement("span");
+ icon.className = "contribute-card-icon";
+ icon.setAttribute("aria-hidden", "true");
+ icon.innerHTML = ``;
+ const kind = document.createElement("span");
+ kind.className = "contribute-card-kind";
+ kind.textContent = path.kind[locale];
+ top.append(icon, kind);
+
+ const title = document.createElement("h3");
+ title.textContent = path.title[locale];
+ const description = document.createElement("p");
+ description.textContent = path.description[locale];
+ const meta = document.createElement("div");
+ meta.className = "contribute-card-meta";
+ path.meta.forEach((item) => {
+ const tag = document.createElement("span");
+ tag.textContent = item[locale];
+ meta.append(tag);
+ });
+ const link = document.createElement("a");
+ link.className = "btn ghost";
+ link.href = path.href;
+ if (path.external) {
+ link.target = "_blank";
+ link.rel = "noopener";
+ }
+ link.innerHTML = ` ${path.cta[locale]}`;
+ article.append(top, title, description, meta, link);
+ return article;
+ }
+
+ function renderPaths() {
+ const grid = byId("contributeGrid");
+ if (!grid) return;
+ const visible = paths.filter((path) => state.activeFilter === "all" || path.category === state.activeFilter);
+ grid.replaceChildren(...visible.map(createPathCard));
+ }
+
+ function bindFilters() {
+ byId("contributeFilters")?.addEventListener("click", (event) => {
+ const button = event.target.closest("[data-filter]");
+ if (!button || button.dataset.filter === state.activeFilter) return;
+ state.activeFilter = button.dataset.filter;
+ renderFilters();
+ renderPaths();
+ });
+ }
+
+ function safeStatus(value, fallback) {
+ const status = String(value || fallback || "").trim().toLowerCase().replace(/[^a-z_\-]/g, "");
+ return status || "draft";
+ }
+
+ function statusLabel(status) {
+ const labels = {
+ draft: text("Draft", "مسودة"),
+ pending: text("Pending", "قيد المراجعة"),
+ approved: text("Approved", "معتمد"),
+ published: text("Published", "منشور"),
+ planned: text("Planned", "مخطط"),
+ in_progress: text("In progress", "قيد التنفيذ"),
+ completed: text("Shipped", "تم الإطلاق")
+ };
+ return labels[status] || status.replace(/[_-]/g, " ");
+ }
+
+ function renderSignedOut() {
+ const panel = byId("activityPanel");
+ if (!panel) return;
+ panel.innerHTML = `
+
+
${text("Member view", "عرض العضو")}
+
${copy.signInTitle}
+
${copy.signInBody}
+
${copy.signIn}
+
+ `;
+ }
+
+ function renderLoading() {
+ const panel = byId("activityPanel");
+ if (!panel) return;
+ panel.innerHTML = ``;
+ }
+
+ function createActivityItem(item) {
+ const row = document.createElement("div");
+ row.className = "activity-item";
+ const icon = document.createElement("span");
+ icon.className = "activity-item-icon";
+ icon.innerHTML = ``;
+ const body = document.createElement("div");
+ const title = document.createElement("div");
+ title.className = "activity-item-title";
+ title.textContent = item.title;
+ const meta = document.createElement("div");
+ meta.className = "activity-item-meta";
+ meta.textContent = `${item.type === "project" ? copy.project : copy.idea} · ${new Intl.DateTimeFormat(locale, { dateStyle: "medium" }).format(new Date(item.date))}`;
+ body.append(title, meta);
+ const status = document.createElement("span");
+ status.className = `activity-status activity-status--${item.status}`;
+ status.textContent = statusLabel(item.status);
+ row.append(icon, body, status);
+ return row;
+ }
+
+ function renderActivity(user, projects, ideas) {
+ const panel = byId("activityPanel");
+ if (!panel) return;
+ const shipped = ideas.filter((idea) => idea.status === "completed").length;
+ const name = String(user.user_metadata?.full_name || user.user_metadata?.name || user.email?.split("@")[0] || text("Member", "عضو"));
+ const items = [
+ ...projects.map((project) => ({
+ type: "project",
+ title: project.name || text("Untitled project", "مشروع بلا عنوان"),
+ status: safeStatus(project.published ? "published" : project.moderation_status, "draft"),
+ date: project.updated_at || project.created_at || new Date().toISOString()
+ })),
+ ...ideas.map((idea) => ({
+ type: "idea",
+ title: idea.title || text("Untitled idea", "فكرة بلا عنوان"),
+ status: safeStatus(idea.status, "planned"),
+ date: idea.created_at || new Date().toISOString()
+ }))
+ ].sort((a, b) => new Date(b.date) - new Date(a.date)).slice(0, 6);
+
+ panel.innerHTML = `
+
+
${text("Member view", "عرض العضو")}
+
${copy.activityTitle}
+
${name}, ${copy.activityBody.charAt(0).toLocaleLowerCase(locale)}${copy.activityBody.slice(1)}
+
+
${projects.length}${copy.projects}
+
${ideas.length}${copy.ideas}
+
${shipped}${copy.shipped}
+
+
+ `;
+
+ const list = byId("activityList");
+ if (!items.length) {
+ list.innerHTML = `${copy.emptyTitle}${copy.emptyBody}
`;
+ return;
+ }
+ list.replaceChildren(...items.map(createActivityItem));
+ }
+
+ async function loadActivity() {
+ const db = window.supabaseClient;
+ if (!db) {
+ renderSignedOut();
+ return;
+ }
+ renderLoading();
+ try {
+ const { data: { session } } = await db.auth.getSession();
+ if (!session?.user) {
+ renderSignedOut();
+ return;
+ }
+ const [projectsResult, ideasResult] = await Promise.all([
+ db.from("projects").select("id,name,slug,published,moderation_status,created_at,updated_at").eq("owner_user_id", session.user.id),
+ db.from("features").select("id,title,status,votes_count,created_at").eq("created_by", session.user.id)
+ ]);
+ if (projectsResult.error || ideasResult.error) throw projectsResult.error || ideasResult.error;
+ renderActivity(session.user, projectsResult.data || [], ideasResult.data || []);
+ } catch (error) {
+ console.warn("Contributor activity unavailable:", error?.message || error);
+ const panel = byId("activityPanel");
+ if (panel) panel.innerHTML = ``;
+ }
+ }
+
+ function init() {
+ const year = byId("year");
+ if (year) year.textContent = new Date().getFullYear();
+ renderFilters();
+ renderPaths();
+ bindFilters();
+ loadActivity();
+ }
+
+ window.ContributorCenter = Object.freeze({ paths, safeStatus, statusLabel });
+ if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init);
+ else init();
+}());
diff --git a/assets/js/roadmap.js b/assets/js/roadmap.js
index 1f93c1c..15277eb 100644
--- a/assets/js/roadmap.js
+++ b/assets/js/roadmap.js
@@ -124,6 +124,9 @@ async function init() {
// Load data
loadAnonVotes();
await loadFeatures();
+ if (new URLSearchParams(window.location.search).get('action') === 'suggest') {
+ openSuggestModal();
+ }
}
// ── REVEAL OBSERVER ────────────────────────────────────────
diff --git a/contribute.html b/contribute.html
new file mode 100644
index 0000000..40a2194
--- /dev/null
+++ b/contribute.html
@@ -0,0 +1,182 @@
+
+
+
+
+
+ NexCore Labs | Contributor Center
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Contributor Center
+
Make something useful move forward.
+
Choose a practical way to contribute—publish student work, shape the roadmap, test the experience, or improve the open-source codebase.
+
+
+
+
SharePublish useful work
+
BuildImprove open tools
+
TestMake access clearer
+
ShapeGuide what ships next
+
+
+
+
+
+
+
Choose a contribution path
Start with what you can offer now. Each path leads to a real NexCore workflow.
+
+
+
+
+
+ Your contribution activity
Projects and roadmap ideas you submit appear here when you sign in.
+
+
+ Have a small improvement in mind?
A focused, well-explained idea is a valuable contribution too.
Share the idea
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
index 215cc1a..c61f3ce 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"lib": "lib"
},
"scripts": {
- "test": "npm run release:test && npm run pricing:test && npm run branding:test && npm run seo:test && npm run auth-nav:test && npm run anchor-nav:test && npm run initiatives:test && npm run initiatives-admin:test && npm run roadmap:test && npm run ai-api:test && npm run homepage-metrics:test && npm run counter-coverage:test && npm run account-ux:test && npm run team-id-cards:test",
+ "test": "npm run release:test && npm run pricing:test && npm run branding:test && npm run seo:test && npm run auth-nav:test && npm run anchor-nav:test && npm run initiatives:test && npm run initiatives-admin:test && npm run roadmap:test && npm run contributor-center:test && npm run ai-api:test && npm run homepage-metrics:test && npm run counter-coverage:test && npm run account-ux:test && npm run team-id-cards:test",
"release": "node scripts/sync-release-version.js",
"release:test": "node scripts/release-version-test.js",
"security:audit": "npm audit --omit=dev --audit-level=high",
@@ -19,6 +19,7 @@
"initiatives:test": "node scripts/initiatives-test.js",
"initiatives-admin:test": "node scripts/initiatives-admin-test.js",
"roadmap:test": "node scripts/roadmap-shared-test.js",
+ "contributor-center:test": "node scripts/contributor-center-test.js",
"ai-api:test": "node scripts/ai-api-test.js",
"homepage-metrics:test": "node scripts/homepage-metrics-test.js",
"counter-coverage:test": "node scripts/counter-coverage-test.js",
diff --git a/scripts/contributor-center-test.js b/scripts/contributor-center-test.js
new file mode 100644
index 0000000..ac6014a
--- /dev/null
+++ b/scripts/contributor-center-test.js
@@ -0,0 +1,101 @@
+"use strict";
+
+const assert = require("assert");
+const fs = require("fs");
+const path = require("path");
+const vm = require("vm");
+
+const root = path.resolve(__dirname, "..");
+const read = (file) => fs.readFileSync(path.join(root, file), "utf8");
+
+const pages = {
+ "contribute.html": {
+ title: "NexCore Labs | Contributor Center",
+ canonical: "https://nexcorelabs.vercel.app/contribute",
+ alternate: "https://nexcorelabs.vercel.app/ar/contribute",
+ css: "assets/css/contribute.css",
+ js: "assets/js/contribute.js",
+ label: "Contributor Center",
+ },
+ "ar/contribute.html": {
+ title: "NexCore Labs | مركز المساهمين",
+ canonical: "https://nexcorelabs.vercel.app/ar/contribute",
+ alternate: "https://nexcorelabs.vercel.app/contribute",
+ css: "../assets/css/contribute.css",
+ js: "../assets/js/contribute.js",
+ label: "مركز المساهمين",
+ },
+};
+
+for (const [file, expected] of Object.entries(pages)) {
+ const html = read(file);
+ assert(html.includes(`${expected.title}`), `${file} must use the localized title`);
+ assert(html.includes(``), `${file} must use a clean canonical URL`);
+ assert(html.includes(expected.alternate), `${file} must link to its alternate locale`);
+ assert(html.includes(`href="${expected.css}"`), `${file} must load shared Contributor Center styles`);
+ assert(html.includes(`src="${expected.js}"`), `${file} must load shared Contributor Center behavior`);
+ assert(html.includes('id="contributeFilters"'), `${file} must include path filters`);
+ assert(html.includes('id="contributeGrid"'), `${file} must include contribution paths`);
+ assert(html.includes('id="activityPanel"'), `${file} must include member activity`);
+ assert(html.includes('data-contribute-nav'), `${file} must identify Contributor Center navigation`);
+ assert(html.includes('aria-current="page"'), `${file} must mark the active navigation item`);
+ assert(html.includes(expected.label), `${file} must include the localized Contributor Center label`);
+ assert(html.includes("roadmap.html?action=suggest"), `${file} must deep-link to idea submission`);
+ assert(!html.includes("fa-sparkles"), `${file} must only use supported Font Awesome Free icons`);
+}
+
+const contributorJs = read("assets/js/contribute.js");
+for (const contract of [
+ 'db.from("projects")',
+ '.eq("owner_user_id", session.user.id)',
+ 'db.from("features")',
+ '.eq("created_by", session.user.id)',
+ 'window.ContributorCenter',
+]) {
+ assert(contributorJs.includes(contract), `Contributor activity must retain ${contract}`);
+}
+
+const windowStub = { ContributorCenter: null };
+const documentStub = {
+ readyState: "loading",
+ documentElement: { lang: "en" },
+ addEventListener() {},
+ getElementById() { return null; },
+};
+vm.runInNewContext(contributorJs, {
+ window: windowStub,
+ document: documentStub,
+ console,
+ Intl,
+ Date,
+});
+assert.strictEqual(windowStub.ContributorCenter.paths.length, 6, "Contributor Center must expose six honest contribution paths");
+assert.strictEqual(windowStub.ContributorCenter.safeStatus("In Progress"), "inprogress", "Activity status classes must be sanitized");
+assert.strictEqual(windowStub.ContributorCenter.safeStatus("", "draft"), "draft", "Missing activity status must fall back safely");
+
+const authUi = read("assets/js/auth-ui-db.js");
+assert(authUi.includes("function ensureContributeNavigation()"), "Shared navigation must inject Contributor Center where static markup is older");
+assert(authUi.includes("data.contributeNav") || authUi.includes("dataset.contributeNav"), "Injected Contributor Center links must carry a stable data attribute");
+assert(authUi.includes("fa-handshake-angle"), "Shared navigation must use the supported Contributor Center icon");
+assert(authUi.includes("ensureContributeNavigation();"), "Contributor Center navigation injection must run during auth UI initialization");
+
+const roadmap = read("assets/js/roadmap.js");
+assert(roadmap.includes("get('action') === 'suggest'"), "Roadmap must recognize Contributor Center idea deep-links");
+assert(roadmap.includes("openSuggestModal();"), "Roadmap idea deep-links must open the real submission workflow");
+
+const routeConfig = JSON.parse(read("vercel.json"));
+for (const [source, destination] of [["/contribute", "/contribute.html"], ["/ar/contribute", "/ar/contribute.html"]]) {
+ assert(routeConfig.rewrites.some((rewrite) => rewrite.source === source && rewrite.destination === destination), `Vercel must rewrite ${source}`);
+}
+
+const serviceWorker = read("service-worker.js");
+for (const url of ["/contribute", "/contribute.html", "/ar/contribute", "/ar/contribute.html", "/assets/css/contribute.css", "/assets/js/contribute.js"]) {
+ assert(serviceWorker.includes(`'${url}'`), `Service worker must precache ${url}`);
+}
+
+const css = read("assets/css/contribute.css");
+assert(css.includes('html[dir="rtl"]'), "Contributor Center styles must include explicit RTL details");
+assert(css.includes("@media (max-width: 680px)"), "Contributor Center must include a mobile layout");
+assert(css.includes("var(--card-shadow)"), "Contributor Center must reuse NexCore design tokens");
+
+console.log("Contributor Center tests passed.");
diff --git a/scripts/seo-assets.js b/scripts/seo-assets.js
index 8426730..87c73aa 100644
--- a/scripts/seo-assets.js
+++ b/scripts/seo-assets.js
@@ -154,6 +154,26 @@ const PAGE_PAIRS = [
breadcrumb: "المبادرات",
},
},
+ {
+ key: "contribute",
+ priority: "0.82",
+ en: {
+ file: "contribute.html",
+ path: "/contribute",
+ title: "NexCore Labs | Contributor Center",
+ description:
+ "Find practical ways to contribute to NexCore Labs, share projects, suggest improvements, and follow your community activity.",
+ breadcrumb: "Contributor Center",
+ },
+ ar: {
+ file: "ar/contribute.html",
+ path: "/ar/contribute",
+ title: "NexCore Labs | مركز المساهمين",
+ description:
+ "اكتشف طرقًا عملية للمساهمة في NexCore Labs، وشارك المشاريع، واقترح التحسينات، وتابع نشاطك المجتمعي.",
+ breadcrumb: "مركز المساهمين",
+ },
+ },
{
key: "how-to-use",
priority: "0.80",
diff --git a/service-worker.js b/service-worker.js
index f9dfca9..e1f2036 100644
--- a/service-worker.js
+++ b/service-worker.js
@@ -18,6 +18,10 @@ const PRECACHE_URLS = [
'/initiatives.html',
'/ar/initiatives',
'/ar/initiatives.html',
+ '/contribute',
+ '/contribute.html',
+ '/ar/contribute',
+ '/ar/contribute.html',
'/how-to-use',
'/how-to-use.html',
'/ar/how-to-use',
@@ -107,6 +111,7 @@ const PRECACHE_URLS = [
'/assets/css/roadmap.css',
'/assets/css/initiatives.css',
'/assets/css/initiatives-admin.css',
+ '/assets/css/contribute.css',
'/assets/js/script.js',
'/assets/js/team-id-cards.js',
'/assets/js/ai-chat.js',
@@ -120,6 +125,7 @@ const PRECACHE_URLS = [
'/assets/js/roadmap.js',
'/assets/js/initiatives.js',
'/assets/js/initiatives-admin.js',
+ '/assets/js/contribute.js',
'/assets/js/supabase-client.js',
'/assets/js/count-up.js',
'/assets/js/github-contributions.js',
diff --git a/sitemap.xml b/sitemap.xml
index a4c25ad..cf1ff30 100644
--- a/sitemap.xml
+++ b/sitemap.xml
@@ -30,6 +30,16 @@
2026-06-23
0.82
+
+ https://nexcorelabs.vercel.app/contribute
+ 2026-07-02
+ 0.82
+
+
+ https://nexcorelabs.vercel.app/ar/contribute
+ 2026-07-02
+ 0.82
+
https://nexcorelabs.vercel.app/how-to-use
2026-06-16
diff --git a/vercel.json b/vercel.json
index 8b22adf..0a88ef0 100644
--- a/vercel.json
+++ b/vercel.json
@@ -15,6 +15,7 @@
{ "source": "/how-to-use", "destination": "/how-to-use.html" },
{ "source": "/hub", "destination": "/hub.html" },
{ "source": "/initiatives", "destination": "/initiatives.html" },
+ { "source": "/contribute", "destination": "/contribute.html" },
{ "source": "/offline", "destination": "/offline.html" },
{ "source": "/privacy-policy", "destination": "/privacy-policy.html" },
{ "source": "/project", "destination": "/project.html" },
@@ -39,6 +40,7 @@
{ "source": "/ar/how-to-use", "destination": "/ar/how-to-use.html" },
{ "source": "/ar/hub", "destination": "/ar/hub.html" },
{ "source": "/ar/initiatives", "destination": "/ar/initiatives.html" },
+ { "source": "/ar/contribute", "destination": "/ar/contribute.html" },
{ "source": "/ar/offline", "destination": "/ar/offline.html" },
{ "source": "/ar/order-confirmation", "destination": "/ar/order-confirmation.html" },
{ "source": "/ar/pricing", "destination": "/ar/pricing.html" },