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
This commit is contained in:
2026-06-28 23:02:48 +02:00
parent 5b08f8b041
commit a1c5fecd83
117 changed files with 8619 additions and 2837 deletions
+723
View File
@@ -0,0 +1,723 @@
<!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>