Files
falah-mobile/handover-infographic.html
wmj a1c5fecd83 pivot: migrate V1 mobile from Docker to Netlify serverless
- Switch Prisma schema from SQLite to PostgreSQL
- Add netlify.toml for Netlify deployment config
- Add @netlify/plugin-nextjs for serverless Next.js
- Remove Docker build files (Dockerfile, docker-compose, start.sh)
- Remove auto-healer and scripts directories
- Update next.config.ts (remove standalone output)
- Database: falah_mobile_v1 on Contabo Postgres
2026-06-28 23:02:48 +02:00

724 lines
25 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Falah Mobile — Micro Learning Handover</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #07090C;
color: #E8E0D0;
font-family: 'Inter', -apple-system, sans-serif;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 24px;
}
.container {
max-width: 1200px;
width: 100%;
}
/* Header */
.header {
text-align: center;
margin-bottom: 48px;
position: relative;
}
.header::after {
content: '';
position: absolute;
bottom: -24px;
left: 50%;
transform: translateX(-50%);
width: 120px;
height: 2px;
background: linear-gradient(90deg, transparent, #C9A84C, transparent);
}
.badge {
display: inline-block;
padding: 4px 16px;
border-radius: 20px;
background: rgba(201, 168, 76, 0.1);
border: 1px solid rgba(201, 168, 76, 0.25);
color: #C9A84C;
font-size: 12px;
font-weight: 600;
letter-spacing: 1.5px;
text-transform: uppercase;
margin-bottom: 16px;
}
.header h1 {
font-size: 42px;
font-weight: 800;
letter-spacing: -1px;
line-height: 1.15;
margin-bottom: 8px;
}
.header h1 span.gold { color: #C9A84C; }
.header h1 span.green { color: #00C48C; }
.header p {
color: #8A8478;
font-size: 16px;
max-width: 600px;
margin: 12px auto 0;
line-height: 1.6;
}
/* Repo Bar */
.repo-bar {
display: flex;
align-items: center;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
margin-top: 20px;
padding: 12px 24px;
background: #0F1219;
border: 1px solid #1A1F2E;
border-radius: 12px;
font-size: 13px;
color: #8A8478;
}
.repo-bar .label {
color: #C9A84C;
font-weight: 600;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1px;
}
.repo-bar code {
font-family: 'JetBrains Mono', monospace;
color: #E8E0D0;
background: #1A1F2E;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.repo-bar .dot {
color: #1A1F2E;
}
/* Bento Grid */
.bento {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 16px;
margin-top: 32px;
}
.card {
background: #0F1219;
border: 1px solid #1A1F2E;
border-radius: 16px;
padding: 24px;
position: relative;
overflow: hidden;
transition: border-color 0.2s, transform 0.15s;
}
.card:hover {
border-color: rgba(201, 168, 76, 0.3);
transform: translateY(-2px);
}
.card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
border-radius: 16px 16px 0 0;
}
.card.gold::before { background: linear-gradient(90deg, #C9A84C, #E8C84C); }
.card.green::before { background: linear-gradient(90deg, #00C48C, #00E8A0); }
.card.blue::before { background: linear-gradient(90deg, #4A90D9, #6BB5FF); }
.card.purple::before { background: linear-gradient(90deg, #9B59B6, #C084FC); }
.card.orange::before { background: linear-gradient(90deg, #E67E22, #F0A050); }
.card.teal::before { background: linear-gradient(90deg, #1ABC9C, #48D1B0); }
/* Hero card — spans 2 columns */
.card.hero {
grid-column: span 2;
background: linear-gradient(135deg, #0F1219 0%, #151A28 100%);
border-color: rgba(201, 168, 76, 0.15);
}
.card.hero::before { background: linear-gradient(90deg, #C9A84C, #00C48C, #4A90D9); }
/* Side card — right column */
.card.side {
grid-column: span 1;
}
/* Full width */
.card.full {
grid-column: span 3;
}
/* Double width */
.card.double {
grid-column: span 2;
}
.card-icon {
width: 36px;
height: 36px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
margin-bottom: 16px;
}
.card-icon.gold { background: rgba(201, 168, 76, 0.12); }
.card-icon.green { background: rgba(0, 196, 140, 0.12); }
.card-icon.blue { background: rgba(74, 144, 217, 0.12); }
.card-icon.purple { background: rgba(155, 89, 182, 0.12); }
.card-icon.orange { background: rgba(230, 126, 34, 0.12); }
.card-icon.teal { background: rgba(26, 188, 156, 0.12); }
.card h2 {
font-size: 16px;
font-weight: 700;
margin-bottom: 6px;
letter-spacing: -0.3px;
}
.card h3 {
font-size: 13px;
font-weight: 600;
color: #8A8478;
margin-bottom: 14px;
letter-spacing: 0.3px;
text-transform: uppercase;
}
.card p {
font-size: 13px;
line-height: 1.6;
color: #A8A090;
}
.card ul {
list-style: none;
margin-top: 8px;
}
.card ul li {
font-size: 13px;
line-height: 1.5;
color: #A8A090;
padding: 6px 0;
border-bottom: 1px solid rgba(26, 31, 46, 0.6);
display: flex;
align-items: flex-start;
gap: 8px;
}
.card ul li:last-child { border-bottom: none; }
.card ul li .bullet {
color: #C9A84C;
flex-shrink: 0;
font-size: 14px;
line-height: 1.5;
}
.card ul li strong { color: #E8E0D0; font-weight: 600; }
.stat-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-top: 8px;
}
.stat-item {
background: rgba(0,0,0,0.2);
border-radius: 10px;
padding: 12px 14px;
text-align: center;
}
.stat-number {
font-size: 28px;
font-weight: 800;
letter-spacing: -1px;
line-height: 1;
}
.stat-number.gold { color: #C9A84C; }
.stat-number.green { color: #00C48C; }
.stat-number.blue { color: #4A90D9; }
.stat-label {
font-size: 11px;
color: #8A8478;
margin-top: 4px;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 500;
}
.tech-pill {
display: inline-block;
padding: 3px 10px;
border-radius: 6px;
font-size: 11px;
font-weight: 600;
margin: 2px 3px;
background: rgba(201, 168, 76, 0.08);
color: #C9A84C;
border: 1px solid rgba(201, 168, 76, 0.15);
}
.tech-pill.green {
background: rgba(0, 196, 140, 0.08);
color: #00C48C;
border-color: rgba(0, 196, 140, 0.15);
}
.tech-pill.blue {
background: rgba(74, 144, 217, 0.08);
color: #6BB5FF;
border-color: rgba(74, 144, 217, 0.15);
}
.tech-pill.purple {
background: rgba(155, 89, 182, 0.08);
color: #C084FC;
border-color: rgba(155, 89, 182, 0.15);
}
.tech-pill.orange {
background: rgba(230, 126, 34, 0.08);
color: #F0A050;
border-color: rgba(230, 126, 34, 0.15);
}
.status-dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 6px;
}
.status-dot.green { background: #00C48C; box-shadow: 0 0 8px rgba(0,196,140,0.4); }
.status-dot.yellow { background: #C9A84C; box-shadow: 0 0 8px rgba(201,168,76,0.4); }
.status-dot.red { background: #E74C3C; box-shadow: 0 0 8px rgba(231,76,60,0.4); }
.route-block {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
line-height: 1.8;
color: #6BB5FF;
background: rgba(0,0,0,0.3);
border-radius: 8px;
padding: 12px 14px;
margin-top: 8px;
}
.route-block .comment { color: #5A5A5A; }
.route-block .method { color: #00C48C; }
.two-col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-top: 8px;
}
.cmd-box {
background: rgba(0,0,0,0.3);
border-radius: 8px;
padding: 10px 14px;
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: #8A8478;
margin-top: 6px;
line-height: 1.8;
}
.cmd-box .prompt { color: #00C48C; }
.cmd-box .cmd { color: #E8E0D0; }
.issue-list li {
font-size: 12px;
padding: 7px 0 !important;
}
.issue-list li .issue-id {
color: #C9A84C;
font-weight: 600;
font-size: 11px;
}
.hero-content {
display: flex;
flex-direction: column;
gap: 14px;
}
.hero-content .row {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.hero-content .info-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
}
.hero-content .info-item .key {
color: #8A8478;
font-weight: 500;
}
.hero-content .info-item .val {
color: #E8E0D0;
font-weight: 600;
}
.hero-content .info-item .val.code {
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
background: #1A1F2E;
padding: 2px 8px;
border-radius: 4px;
}
.section-tag {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1.5px;
font-weight: 700;
margin-bottom: 10px;
opacity: 0.7;
}
.progress-bar {
height: 4px;
background: #1A1F2E;
border-radius: 2px;
margin: 10px 0 4px;
overflow: hidden;
}
.progress-bar .fill {
height: 100%;
border-radius: 2px;
background: linear-gradient(90deg, #C9A84C, #00C48C);
}
.progress-label {
font-size: 11px;
color: #8A8478;
display: flex;
justify-content: space-between;
}
.price-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
background: rgba(0, 196, 140, 0.1);
border: 1px solid rgba(0, 196, 140, 0.2);
border-radius: 6px;
font-size: 12px;
color: #00C48C;
font-weight: 600;
}
.price-badge.warn {
background: rgba(201, 168, 76, 0.08);
border-color: rgba(201, 168, 76, 0.2);
color: #C9A84C;
}
.price-badge.danger {
background: rgba(231, 76, 60, 0.08);
border-color: rgba(231, 76, 60, 0.2);
color: #E74C3C;
}
/* Footer */
.footer {
text-align: center;
margin-top: 48px;
padding-top: 24px;
border-top: 1px solid #1A1F2E;
color: #5A5A5A;
font-size: 12px;
letter-spacing: 0.3px;
}
/* Responsive */
@media (max-width: 900px) {
.bento { grid-template-columns: 1fr 1fr; }
.card.hero { grid-column: span 2; }
.card.full { grid-column: span 2; }
.card.double { grid-column: span 1; }
.two-col { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
.bento { grid-template-columns: 1fr; }
.card.hero, .card.full, .card.double { grid-column: span 1; }
.header h1 { font-size: 28px; }
body { padding: 20px 12px; }
.stat-grid { grid-template-columns: 1fr; }
.repo-bar { flex-direction: column; text-align: center; }
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<div class="header">
<div class="badge">📚 Micro Learning Platform</div>
<h1><span class="gold">Falah Mobile</span> <span style="color:#5A5A5A;font-weight:300;"></span> <span class="green">Handover</span></h1>
<p>Complete E2E system for browsing, purchasing, consuming, and certifying bite-sized (5 min) courses derived from books.</p>
<div class="repo-bar">
<span class="label">Repository</span>
<code>git.falahos.my/wmj/falah-mobile.git</code>
<span class="dot">·</span>
<span class="label">Source</span>
<code>/root/falah-mobile</code>
<span class="dot">·</span>
<span class="label">Branch</span>
<code>main</code>
<span class="dot">·</span>
<span class="label">Commit</span>
<code>bfd3509</code>
<span class="dot">·</span>
<span class="label">GitHub</span>
<code>maifors/falah-mobile</code>
</div>
</div>
<!-- Bento Grid -->
<div class="bento">
<!-- Hero Card: Overview -->
<div class="card hero">
<div class="hero-content">
<div style="display:flex;align-items:center;gap:12px;margin-bottom:4px;">
<div class="card-icon gold" style="margin-bottom:0;">📊</div>
<div>
<h2 style="font-size:20px;">Phase 1 Complete — Live in Production</h2>
<p style="color:#8A8478;font-size:13px;">Production: <a href="https://falahos.my/mobile" style="color:#00C48C;text-decoration:none;">falahos.my/mobile</a> &nbsp;·&nbsp; Staging: <a href="https://falahos.my:4014/mobile" style="color:#C9A84C;text-decoration:none;">falahos.my:4014/mobile</a></p>
</div>
</div>
<div class="row">
<div class="info-item"><span class="key">Deployment</span> <span class="val code">Docker + Traefik</span></div>
<div class="info-item"><span class="key">Host</span> <span class="val code">Contabo VPS (Ubuntu 24.04)</span></div>
<div class="info-item"><span class="key">Auth</span> <span class="val code">Ummah ID (JWT)</span></div>
<div class="info-item"><span class="key">Payments</span> <span class="val code">Polar.sh</span></div>
</div>
</div>
</div>
<!-- Cell: ✅ Complete — Courses -->
<div class="card gold">
<div class="card-icon gold"></div>
<h2>Courses Built</h2>
<div class="stat-grid">
<div class="stat-item">
<div class="stat-number gold">4</div>
<div class="stat-label">Courses Live</div>
</div>
<div class="stat-item">
<div class="stat-number green">21</div>
<div class="stat-label">Modules</div>
</div>
<div class="stat-item">
<div class="stat-number blue">6</div>
<div class="stat-label">Categories</div>
</div>
<div class="stat-item">
<div class="stat-number" style="color:#F0A050;">35</div>
<div class="stat-label">Courses Left</div>
</div>
</div>
<div style="margin-top:12px;">
<div style="display:flex;gap:6px;flex-wrap:wrap;">
<span class="tech-pill">Atomic Habits</span>
<span class="tech-pill green">Defining Decade</span>
<span class="tech-pill blue">Simplicity Parenting</span>
<span class="tech-pill orange">Tao Te Ching</span>
</div>
<div class="progress-bar">
<div class="fill" style="width:10%"></div>
</div>
<div class="progress-label">
<span>Library completion</span>
<span>4 / 39 courses (10%)</span>
</div>
</div>
<p style="margin-top:10px;font-size:12px;">All 4 in <strong>Self Improvement</strong>. Quran, Hadith, Prayer, History, Finance categories empty — ready for content.</p>
</div>
<!-- Cell: ✅ Complete — Features -->
<div class="card green">
<div class="card-icon green">🎓</div>
<h2>Delivery Features</h2>
<ul>
<li><span class="bullet"></span> <strong>Course catalog</strong> — search bar + category filter tabs</li>
<li><span class="bullet"></span> <strong>Module reader</strong> — markdown content + progress bar + quiz badges</li>
<li><span class="bullet"></span> <strong>TTS audio</strong> — Web Speech API, Google UK English Female (preferred)</li>
<li><span class="bullet"></span> <strong>Markdown stripping</strong> — 11 patterns clean text for TTS</li>
<li><span class="bullet"></span> <strong>Quiz system</strong> — 35 MCQs per module, submit/score/retry</li>
<li><span class="bullet"></span> <strong>Certificate issuance</strong> — auto-issued on 100% completion, PDF + serial</li>
</ul>
</div>
<!-- Cell: 💳 Payments -->
<div class="card purple">
<div class="card-icon purple">💳</div>
<h2>Payments &amp; Subscription</h2>
<ul>
<li>
<span class="bullet"></span>
<span class="price-badge">$9.99/mo</span> <strong>Learn Pass Monthly</span></strong> <span style="color:#00C48C;">✅ Live</span>
</li>
<li>
<span class="bullet"></span>
<span class="price-badge">$79.99/yr</span> <strong>Learn Pass Annual</span></strong> <span style="color:#00C48C;">✅ Live</span>
</li>
<li>
<span class="bullet"></span>
<span class="price-badge danger">$4.99 each</span> <strong>One-time course purchases</strong> <span style="color:#E74C3C;">❌ Not mapped</span>
</li>
<li style="font-size:12px;color:#C9A84C;padding-top:8px;">
<strong>Action:</strong> Add Polar price IDs for 4 new courses to <code style="font-size:11px;">checkout/route.ts</code> PRODUCTS map
</li>
</ul>
</div>
<!-- Cell: 📋 Pending -->
<div class="card orange">
<div class="card-icon orange">📋</div>
<h2>Priority Backlog</h2>
<ul>
<li><span class="bullet"></span> <strong>35 more courses</strong> — build &amp; seed across 6 categories</li>
<li><span class="bullet"></span> <strong>Checkout mapping</strong> — Polar price IDs for each new course</li>
<li><span class="bullet"></span> <strong>Course thumbnails</strong> — replace emoji/gradient with real cover art</li>
<li><span class="bullet"></span> <strong>Gitea sync</strong> — test course sync script</li>
<li><span class="bullet"></span> <strong>Server-side TTS</strong> — Azure / ElevenLabs for higher quality</li>
<li><span class="bullet"></span> <strong>Admin dashboard</strong> — course management UI</li>
<li><span class="bullet"></span> <strong>Analytics</strong> — completion rates, quiz scores</li>
</ul>
</div>
<!-- Cell: 📦 Tech Stack (double width) -->
<div class="card blue double">
<div class="card-icon blue">📦</div>
<h2>Tech Stack</h2>
<div style="margin-top:8px;">
<div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px;">
<span class="tech-pill green">Next.js 16</span>
<span class="tech-pill">TypeScript</span>
<span class="tech-pill blue">SQLite</span>
<span class="tech-pill purple">Prisma v5.22</span>
<span class="tech-pill orange">Tailwind CSS</span>
<span class="tech-pill green">Polar.sh</span>
<span class="tech-pill blue">Docker</span>
<span class="tech-pill">Traefik</span>
<span class="tech-pill green">Web Speech API</span>
<span class="tech-pill">Gitea</span>
</div>
<div class="two-col">
<div>
<p style="color:#8A8478;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:4px;">Database</p>
<p style="font-size:12px;">SQLite (<code style="font-family:'JetBrains Mono',monospace;font-size:11px;">/app/data/dev.db</code>) via Prisma ORM</p>
<p style="font-size:12px;margin-top:6px;">Schema: <code style="font-family:'JetBrains Mono',monospace;font-size:11px;color:#C9A84C;">prisma/schema.prisma</code></p>
</div>
<div>
<p style="color:#8A8478;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:4px;">Deployment</p>
<p style="font-size:12px;">Source → <code style="font-family:'JetBrains Mono',monospace;font-size:11px;">npm run build</code> → Docker → Traefik reverse proxy</p>
<p style="font-size:12px;margin-top:6px;">Host: Contabo VPS</p>
</div>
</div>
</div>
</div>
<!-- Cell: 🏗 Architecture (full width) -->
<div class="card full">
<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px;">
<div class="card-icon teal" style="margin-bottom:0;">🏗</div>
<h2>Route Architecture</h2>
</div>
<div class="route-block">
<span class="comment"># Next.js App Router — basePath: "/mobile"</span><br>
<span class="method">GET</span> /mobile/learn <span class="comment">← Course catalog (search + category tabs)</span><br>
<span class="method">GET</span> /mobile/learn/<span style="color:#E8E0D0;">[slug]</span> <span class="comment">← Course detail + module timeline</span><br>
<span class="method">GET</span> /mobile/learn/<span style="color:#E8E0D0;">[slug]/[moduleId]</span> <span class="comment">← Module reader + TTS + Quiz</span><br>
<span class="method">GET</span> /mobile/api/learn/courses <span class="comment">← ?category=&search=</span><br>
<span class="method">GET</span> /mobile/api/learn/courses/<span style="color:#E8E0D0;">[slug]</span> <span class="comment">← Course detail API</span><br>
<span class="method">GET</span> /mobile/api/learn/categories <span class="comment">← List categories</span><br>
<span class="method">POST</span> /mobile/api/learn/checkout <span class="comment">← Polar.sh checkout</span><br>
<span class="method">POST</span> /mobile/api/learn/progress <span class="comment">← Mark module complete + auto-certificate</span><br>
<span class="method">GET</span> /mobile/api/learn/certificates <span class="comment">← User certificates</span><br>
<span class="method">POST</span> /mobile/api/learn/sync <span class="comment">← Gitea sync</span>
</div>
<div style="margin-top:16px;">
<p style="color:#8A8478;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:6px;">Key Source Files (26 files)</p>
<div style="display:flex;flex-wrap:wrap;gap:4px;">
<span class="tech-pill" style="font-family:'JetBrains Mono',monospace;">prisma/schema.prisma</span>
<span class="tech-pill green" style="font-family:'JetBrains Mono',monospace;">learn/page.tsx</span>
<span class="tech-pill blue" style="font-family:'JetBrains Mono',monospace;">[slug]/page.tsx</span>
<span class="tech-pill orange" style="font-family:'JetBrains Mono',monospace;">checkout/route.ts</span>
<span class="tech-pill" style="font-family:'JetBrains Mono',monospace;">progress/route.ts</span>
<span class="tech-pill green" style="font-family:'JetBrains Mono',monospace;">courses/route.ts</span>
<span class="tech-pill purple" style="font-family:'JetBrains Mono',monospace;">lib/learn.ts</span>
<span class="tech-pill" style="font-family:'JetBrains Mono',monospace;">Dockerfile</span>
<span class="tech-pill blue" style="font-family:'JetBrains Mono',monospace;">LearnPassCard.tsx</span>
</div>
</div>
</div>
<!-- Cell: ⚙️ Commands -->
<div class="card gold">
<div class="card-icon gold">⚙️</div>
<h2>Common Commands</h2>
<div class="cmd-box">
<span class="prompt">$</span> <span class="cmd">cd /root/falah-mobile</span><br>
<span class="prompt">$</span> <span class="cmd">npm run build</span><br>
<span class="prompt">$</span> <span class="cmd">docker build -t falah-mobile:latest .</span><br>
<span class="prompt">$</span> <span class="cmd">docker compose up -d --no-deps falah-mobile</span><br>
<span class="prompt">$</span> <span class="cmd">npx prisma db push</span>
</div>
<p style="margin-top:10px;font-size:12px;">DB ops via <code style="font-size:11px;">docker cp</code> + <code style="font-size:11px;">sqlite3</code> for quick data fixes</p>
</div>
<!-- Cell: 🐛 Known Issues -->
<div class="card double" style="border-color:rgba(231,76,60,0.2);">
<div class="card-icon orange" style="background:rgba(231,76,60,0.12);">🐛</div>
<h2 style="color:#E74C3C;">Known Issues</h2>
<ul class="issue-list">
<li><span class="issue-id">#1</span> <span class="bullet">🟡</span> <strong>Health check slow</strong> — 30s+ but API works immediately</li>
<li><span class="issue-id">#2</span> <span class="bullet">🟡</span> <strong>.env permission error</strong><code style="font-size:11px;">EACCES</code> in logs (env still loads correctly)</li>
<li><span class="issue-id">#3</span> <span class="bullet">🔴</span> <strong>Checkout missing courses</strong> — Only 3 old courses in PRODUCTS map</li>
<li><span class="issue-id">#4</span> <span class="bullet">🟡</span> <strong>Staging DB sharing</strong> — prod & staging share same volume</li>
<li><span class="issue-id">#5</span> <span class="bullet">🟢</span> <strong>Missing thumbnails</strong> — emoji/gradient placeholders</li>
<li><span class="issue-id">#6</span> <span class="bullet">🟢</span> <strong>Course card navigation</strong><code style="font-size:11px;">stopPropagation</code> edge case</li>
</ul>
<div style="margin-top:10px;display:flex;gap:12px;font-size:12px;">
<span><span class="status-dot green"></span> Low</span>
<span><span class="status-dot yellow"></span> Medium</span>
<span><span class="status-dot red"></span> High</span>
</div>
</div>
</div>
<!-- Footer -->
<div class="footer">
FalahOS · Generated 2026 · falahos.my/mobile · <span style="color:#C9A84C;">Next: Build 35 more courses →</span>
</div>
</div>
</body>
</html>