Skip to content

Instantly share code, notes, and snippets.

@mohammedsalem97
Last active June 3, 2026 10:04
Show Gist options
  • Select an option

  • Save mohammedsalem97/3c4641b1e7e7bb3f55d1a832da3e6e7e to your computer and use it in GitHub Desktop.

Select an option

Save mohammedsalem97/3c4641b1e7e7bb3f55d1a832da3e6e7e to your computer and use it in GitHub Desktop.
Sakinah Islamic App — Implementation Progress Dashboard
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sakinah Islamic App — Progress Dashboard</title>
<style>
:root {
--bg: #0a0f0d;
--panel: #141a17;
--panel-2: #1c231f;
--gold: #d4af37;
--gold-soft: rgba(212, 175, 55, 0.1);
--green: #88d982;
--green-soft: rgba(136, 217, 130, 0.08);
--amber: #f2ca50;
--amber-soft: rgba(242, 202, 80, 0.08);
--gray: #94a3b8;
--gray-soft: rgba(148, 163, 184, 0.06);
--text: #dfe4e0;
--text-dim: #8a8270;
--border: rgba(255,255,255,0.06);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
padding: 32px 16px 64px;
}
.wrap { max-width: 1100px; margin: 0 auto; }
.header {
background: linear-gradient(135deg, #1a201d 0%, #1f2823 100%);
border-radius: 14px;
padding: 28px 30px;
border: 1px solid var(--border);
margin-bottom: 20px;
position: relative;
overflow: hidden;
}
.header::after {
content: "";
position: absolute;
top: -50%;
right: -10%;
width: 300px;
height: 300px;
background: radial-gradient(circle, var(--gold-soft) 0%, transparent 70%);
pointer-events: none;
}
.header h1 { font-size: 24px; font-weight: 700; color: var(--gold); margin-bottom: 2px; }
.header .sub { color: var(--text-dim); font-size: 13px; }
.header .meta { margin-top: 10px; display: flex; gap: 16px; flex-wrap: wrap; font-size: 12px; color: var(--text-dim); }
.header .meta strong { color: var(--text); font-weight: 500; }
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 20px; }
.stat {
background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
padding: 16px 18px; text-align: center;
}
.stat .val { font-size: 26px; font-weight: 700; letter-spacing: -0.3px; }
.stat .lbl { font-size: 11px; color: var(--text-dim); margin-top: 2px; }
.stat.gold .val { color: var(--gold); }
.stat.green .val { color: var(--green); }
.stat.amber .val { color: var(--amber); }
.phase-bar-section {
background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
padding: 18px 20px; margin-bottom: 20px;
}
.phase-bar-section h2 { font-size: 15px; font-weight: 600; margin-bottom: 14px; }
.bar-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.bar-row .lbl { width: 200px; font-size: 12px; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bar-row .bar { flex: 1; height: 6px; background: var(--gray-soft); border-radius: 4px; overflow: hidden; }
.bar-row .bar > div { height: 100%; border-radius: 4px; transition: width 0.5s; }
.bar-row .bar > div.gold { background: linear-gradient(90deg, var(--gold) 0%, #e0bc4a 100%); }
.bar-row .bar > div.green { background: linear-gradient(90deg, var(--green) 0%, #6cbf65 100%); }
.bar-row .pct { width: 50px; text-align: right; font-size: 12px; color: var(--text); font-variant-numeric: tabular-nums; }
.phase-card {
background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
margin-bottom: 10px; overflow: hidden;
}
.phase-head {
padding: 14px 18px;
display: flex; align-items: center; gap: 10px;
background: var(--panel-2);
cursor: pointer; user-select: none;
transition: background 0.15s;
}
.phase-head:hover { background: #232b26; }
.phase-head .arrow { font-size: 10px; color: var(--text-dim); transition: transform 0.2s; }
.phase-head .arrow.open { transform: rotate(90deg); }
.phase-head .name { font-size: 14px; font-weight: 600; flex: 1; }
.phase-head .name small { color: var(--text-dim); font-weight: 400; font-size: 11px; margin-left: 8px; }
.pill { padding: 2px 10px; border-radius: 8px; font-size: 11px; font-weight: 600; white-space: nowrap; }
.pill.done { background: var(--green-soft); color: var(--green); }
.pill.partial { background: var(--amber-soft); color: var(--amber); }
.pill.pending { background: var(--gray-soft); color: var(--gray); }
.pill.zero { background: var(--gray-soft); color: #666; }
.task-table { width: 100%; border-collapse: collapse; display: none; }
.task-table.open { display: table; }
.task-table th {
padding: 8px 14px; border-bottom: 1px solid var(--border);
font-size: 11px; font-weight: 600; color: var(--text-dim);
text-align: left; letter-spacing: 0.3px;
background: rgba(255,255,255,0.02);
}
.task-table td {
padding: 8px 14px; border-bottom: 1px solid var(--border);
font-size: 12px; vertical-align: top;
}
.task-table tr:last-child td { border-bottom: 0; }
.task-table td.num { width: 44px; color: var(--text-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
.task-table td.name { min-width: 160px; }
.task-table td.type { width: 58px; font-size: 11px; color: var(--text-dim); }
.task-table td.hours { width: 36px; color: var(--green); font-weight: 600; text-align: center; }
.task-table td.desc { font-size: 11px; color: var(--text-dim); line-height: 1.5; }
.task-table td.stat { width: 70px; text-align: center; }
.status-badge {
display: inline-block; padding: 1px 8px; border-radius: 6px;
font-size: 11px; font-weight: 500;
}
.status-badge.completed { background: var(--green-soft); color: var(--green); }
.status-badge.pending { background: var(--gray-soft); color: var(--gray); }
.status-badge.in-progress { background: var(--amber-soft); color: var(--amber); }
.status-badge.blocked { background: rgba(255,107,107,0.1); color: #ff6b6b; }
h2.section-title {
font-size: 16px; font-weight: 600; margin: 28px 0 14px;
padding-left: 10px; border-left: 3px solid var(--gold);
}
.completed-summary {
background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
padding: 18px 20px; margin-bottom: 10px;
}
.completed-summary h3 { font-size: 14px; color: var(--green); margin-bottom: 10px; }
.completed-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.task-card {
background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px;
padding: 10px 12px;
}
.task-card .num { font-size: 10px; color: var(--gold); font-weight: 600; }
.task-card .title { font-size: 13px; font-weight: 500; margin: 2px 0 4px; }
.task-card .desc { font-size: 11px; color: var(--text-dim); line-height: 1.4; }
.task-card .meta-row { font-size: 10px; color: var(--text-dim); display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
.task-card .tag { background: var(--gold-soft); color: var(--gold); padding: 1px 6px; border-radius: 4px; font-size: 10px; }
footer { margin-top: 28px; padding-top: 16px; border-top: 1px solid var(--border); color: var(--text-dim); font-size: 11px; text-align: center; }
@media (max-width: 720px) {
.stats { grid-template-columns: 1fr 1fr; }
.completed-grid { grid-template-columns: 1fr; }
.bar-row .lbl { width: 120px; font-size: 11px; }
.header { padding: 20px 18px; }
.header h1 { font-size: 20px; }
.task-table th, .task-table td { padding: 6px 8px; }
.task-table td.desc { display: none; }
}
</style>
</head>
<body>
<div class="wrap">
<div class="header">
<h1>سكينة · Sakinah Islamic App</h1>
<p class="sub">Implementation Progress Dashboard — <span id="overallPct">0%</span> complete · <span id="overallCount">0/72</span> tasks</p>
<div class="meta">
<span><strong>Date:</strong> <span id="currentDate"></span></span>
<span><strong>Phases:</strong> 10 (P0–P9)</span>
<span><strong>Hours:</strong> <span id="hoursDelivered">0</span> / <span id="totalHours">0</span></span>
</div>
</div>
<div class="stats" id="statsRow"></div>
<div class="phase-bar-section">
<h2>Phase Progress</h2>
<div id="phaseBars"></div>
</div>
<h2 class="section-title">📋 All Tasks by Phase</h2>
<div id="phaseDetails"></div>
<h2 class="section-title">✅ Completed Tasks</h2>
<div class="completed-summary">
<h3 id="completedSummaryTitle">All 72 tasks completed</h3>
<div class="completed-grid" id="completedGrid"><div style="color:var(--text-dim);font-size:13px;grid-column:1/-1;text-align:center">Loading...</div></div>
</div>
<footer>
Generated <span id="generatedDate"></span>
</footer>
</div>
<script>
const PHASE_NAMES = [
"Project Init & Architecture", "Core Widgets & Navigation",
"Onboarding & Home Dashboard", "Quran Feature", "Azkar Feature",
"Digital Tasbeeh", "Settings", "Audio Player", "Prayer Times", "Qibla Finder"
];
const PHASE_SHORT = ["P0","P1","P2","P3","P4","P5","P6","P7","P8","P9"];
const PHASE_HOURS = [19.5, 12.5, 20.5, 24, 15, 9.5, 8.5, 12, 9.5, 9.5];
const TASKS = [
[0,"0.1","Add dependencies","Setup",1,"Critical","—","Add all required packages: flutter_bloc, get_it, injectable, go_router, dio, flutter_secure_storage, cached_network_image, intl, easy_localization, json_annotation, json_serializable, build_runner, hive, just_audio, audio_service, location, sensors_plus, shimmer.","completed"],
[0,"0.2","Create directory structure","Setup",0.5,"Critical","0.1","Create all folders: lib/config/theme/, lib/config/routes/, lib/config/di/, lib/core/widgets/, lib/core/network/, lib/core/storage/, lib/core/errors/, lib/shared/, and each feature with data/domain/presentation.","completed"],
[0,"0.3","Design tokens & theme","Arch",4,"Critical","0.2","app_colors.dart, app_typography.dart (Manrope + Literata), app_spacing.dart, app_radius.dart, app_elevation.dart, app_theme.dart.","completed"],
[0,"0.4","Dependency injection","Arch",2,"Critical","0.2","injection.dart with GetIt manual registration.","completed"],
[0,"0.5","GoRouter setup","Arch",2,"Critical","0.2","app_router.dart with ShellRoute, bottom nav, SplashScreen.","completed"],
[0,"0.6","Networking layer","Arch",3,"Critical","0.2","Dio client + 4 interceptors (Auth, Language, Logging, Error) + API constants.","completed"],
[0,"0.7","Error handling","Arch",1.5,"Critical","0.2","failures.dart, exceptions.dart, error_handler.dart.","completed"],
[0,"0.8","Storage layer","Arch",1.5,"Critical","0.2","flutter_secure_storage wrapper + SharedPreferences/Hive wrappers.","completed"],
[0,"0.9","Localization","Arch",2,"Critical","0.2","easy_localization + en.json + ar.json.","completed"],
[0,"0.10","Build scripts","Setup",1,"Critical","0.1","build.yaml + build scripts for gen/analyze/format/test.","completed"],
[0,"0.11","main.dart entry point","Setup",1,"Critical","0.3–0.10","Remove counter template. Initialize EasyLocalization + DI + GoRouter + dark theme.","completed"],
[1,"1.1","GlassCard widget","UI",2,"Critical","0.3","Translucent charcoal bg, 20px blur, 1px border, 24px radius. Configurable padding/elevation/onTap.","completed"],
[1,"1.2","AppPrimaryButton","UI",1.5,"Critical","0.3","Solid gold #D4AF37, dark text, 16px radius, loading state, disabled state.","completed"],
[1,"1.3","AppSecondaryButton","UI",1,"Critical","0.3","Ghost style: emerald stroke + 10% fill.","completed"],
[1,"1.4","SectionHeader","UI",0.5,"Critical","0.3","Title left + action button right.","completed"],
[1,"1.5","AppChip","UI",0.5,"Critical","0.3","Pill chip, turquoise bg 15%, for Meccan/Medinan tags.","completed"],
[1,"1.6","AppAppBar","UI",1,"Critical","0.3","Dynamic safe-area, glassmorphism, configurable title/actions.","completed"],
[1,"1.7","State widgets","UI",1.5,"Critical","0.3","3 reusable state widgets with gold/emerald accents.","completed"],
[1,"1.8","ProgressIndicator","UI",0.5,"Critical","0.3","Emerald linear indicator for Azkar + Prayer Times.","completed"],
[1,"1.9","AppBottomNav","UI",2,"Critical","0.3","Floating pill: 4 tabs, backdrop-blur, gold active icons.","completed"],
[1,"1.10","AppShell","Arch",1.5,"Critical","1.9","Scaffold + AppBottomNav wired to GoRouter ShellRoute.","completed"],
[1,"1.11","Wire AppShell","Arch",0.5,"Critical","1.10","Replace placeholder routes with ShellRoute.","completed"],
[2,"2.1","Onboarding domain","Arch",1,"High","0.4","OnboardingRepository interface with isCompleted/setCompleted/reset.","completed"],
[2,"2.2","Onboarding data","Stor",1.5,"High","2.1","RepositoryImpl + LocalDataSource (SharedPreferences).","completed"],
[2,"2.3","Onboarding cubit","Arch",1.5,"High","2.1","OnboardingCubit: states Initial/Loaded/Completed. Events Next/Skip/Complete.","completed"],
[2,"2.4","Onboarding screens","UI",4,"High","2.3","3-slide PageView with glassmorphism cards, dot indicators, animations.","completed"],
[2,"2.5","Onboarding routing","Arch",0.5,"High","2.4","/onboarding route + SplashScreen redirect logic.","completed"],
[2,"2.6","Home domain","Arch",1,"High","0.4","DailyVerse/QuickAccessItem entities + HomeRepository interface.","completed"],
[2,"2.7","Home data","API",2,"High","2.6","RepositoryImpl via DioClient fetching daily verse.","completed"],
[2,"2.8","Home cubit","Arch",2,"High","2.6","HomeCubit: states Initial/Loading/Loaded/Error.","completed"],
[2,"2.9","Home screens","UI",6,"High","2.8","Dashboard with DailyVerseCard, QuickAccessGrid, RecentActivity.","completed"],
[2,"2.10","Home routing","Arch",0.5,"High","2.9","/home as default tab.","completed"],
[3,"3.1","Quran domain","Arch",2,"High","0.4","Surah, Verse, Bookmark, Favorite entities + repository interfaces.","completed"],
[3,"3.2","Quran data (Surahs/Verses)","API",3,"High","3.1","RemoteDataSource via DioClient + JSON serialization.","completed"],
[3,"3.3","Quran data (Bookmarks/Favs)","DB",2,"High","3.1","Hive boxes + BookmarkRepositoryImpl.","completed"],
[3,"3.4","Surah List cubit","Arch",1.5,"High","3.2","SurahListCubit: load all surahs, search by name.","completed"],
[3,"3.5","Surah List screen","UI",5,"High","3.4","Search bar + SurahTile list with gold Arabic name, type chip, verse count.","completed"],
[3,"3.6","Quran Reader cubit","Arch",2,"High","3.2","QuranReaderCubit: load surah, toggle bookmark/favorite.","completed"],
[3,"3.7","Quran Reader screen","UI",8,"High","3.6","AyahWidget list with verse number badges, Arabic text, translation, bookmark/favorite.","completed"],
[3,"3.8","Quran routing","Arch",0.5,"High","3.5,3.7","Routes: /quran, /quran/reader/:surahId, /quran/audio/:surahId.","completed"],
[4,"4.1","Azkar domain","Arch",1.5,"High","0.4","ZikrCategory, Zikr entities + repository interfaces.","completed"],
[4,"4.2","Azkar data","API",2.5,"High","4.1","RemoteDataSource via DioClient.","completed"],
[4,"4.3","Azkar Categories cubit","Arch",1,"High","4.2","AzkarCategoriesCubit: load categories.","completed"],
[4,"4.4","Azkar Categories screen","UI",3,"High","4.3","2-column grid of GlassCard tiles with icon, name, count.","completed"],
[4,"4.5","Azkar Reader cubit","Arch",1.5,"High","4.2","AzkarReaderCubit: load azkar, increment repeat counter.","completed"],
[4,"4.6","Azkar Reader screen","UI",5,"High","4.5","ZikrCard list with Arabic + translation, repeat counter, progress bar.","completed"],
[4,"4.7","Azkar routing","Arch",0.5,"High","4.4,4.6","Routes: /azkar, /azkar/reader/:categoryId.","completed"],
[5,"5.1","Tasbeeh domain","Arch",1,"High","0.4","TasbeehPreset, TasbeehSession entities + repository interface.","completed"],
[5,"5.2","Tasbeeh data","DB",1.5,"High","5.1","Hive boxes + 4 built-in presets.","completed"],
[5,"5.3","Tasbeeh cubit","Arch",1.5,"High","5.2","TasbeehCubit: count, preset selection, session management.","completed"],
[5,"5.4","Tasbeeh screen","UI",5,"High","5.3","Gold circular counter with pulse animation, progress bar, completion screen.","completed"],
[5,"5.5","Tasbeeh routing","Arch",0.5,"High","5.4","Route: /tasbeeh.","completed"],
[6,"6.1","Settings domain","Arch",1,"Med","0.4","AppSettings entity + SettingsRepository interface.","completed"],
[6,"6.2","Settings data","Stor",1.5,"Med","6.1","SharedPreferences implementation.","completed"],
[6,"6.3","Settings cubit","Arch",1.5,"Med","6.2","SettingsCubit: load/update each setting.","completed"],
[6,"6.4","Settings screen","UI",4,"Med","6.3","Grouped sections: Appearance, Notifications, Audio, Prayer, Experience, About.","completed"],
[6,"6.5","Settings routing","Arch",0.5,"Med","6.4","Route: /settings.","completed"],
[7,"7.1","Audio domain","Arch",1.5,"Med","0.4","Reciter, AudioPlayState, AudioTrack entities + AudioPlayerService interface.","completed"],
[7,"7.2","Audio data","API",2,"Med","7.1","Reciter list from API + just_audio wrapper with streams.","completed"],
[7,"7.3","Audio Player cubit","Arch",2,"Med","7.2","AudioPlayerCubit: play/pause/seek/speed/reciter selection.","completed"],
[7,"7.4","Audio Player screen","UI",6,"Med","7.3","Player controls, progress slider, speed chips, reciter selector.","completed"],
[7,"7.5","Audio routing","Arch",0.5,"Med","7.4","Route: /quran/audio/:surahId.","completed"],
[8,"8.1","Prayer Times domain","Arch",1,"Med","0.4","PrayerTime, PrayerDay entities + repository interface.","completed"],
[8,"8.2","Prayer Times data","API",2.5,"Med","8.1","RemoteDataSource via DioClient.","completed"],
[8,"8.3","Prayer Times cubit","Arch",1.5,"Med","8.2","PrayerTimesCubit: load today, countdown timer.","completed"],
[8,"8.4","Prayer Times screen","UI",4,"Med","8.3","Hijri header, next prayer card, 5 prayer rows, live countdown.","completed"],
[8,"8.5","Prayer Times routing","Arch",0.5,"Med","8.4","Route: /prayer-times.","completed"],
[9,"9.1","Qibla domain","Arch",0.5,"Med","0.4","QiblaData entity + calculateBearing() math + repository interface.","completed"],
[9,"9.2","Qibla data","Dev",2,"Med","9.1","sensors_plus gyroscope + location GPS + bearing calc to Kaaba.","completed"],
[9,"9.3","Qibla cubit","Arch",1.5,"Med","9.2","QiblaCubit: start/stop sensor stream, permission handling.","completed"],
[9,"9.4","Qibla screen","UI",5,"Med","9.3","Full-screen compass with rotating ring, cardinal labels, gold arrow, alignment status.","completed"],
[9,"9.5","Qibla routing","Arch",0.5,"Med","9.4","Route: /qibla.","completed"],
];
// ─── Computed ───
function phTasks(p) { return TASKS.filter(t => t[0] === p); }
function phStats(p) {
const pts = phTasks(p);
const total = pts.length;
const done = pts.filter(t => t[8] === 'completed').length;
const pct = total > 0 ? Math.round(done / total * 100) : 0;
return { total, done, pct };
}
function allStats() {
let total = 0, done = 0, hours = 0;
TASKS.forEach(t => { total++; if (t[8] === 'completed') { done++; hours += t[3]; } });
const pct = total > 0 ? Math.round(done / total * 100) : 0;
return { total, done, pct, hours, totalHours: PHASE_HOURS.reduce((a,b)=>a+b,0) };
}
// ─── Render ───
function renderStats() {
const s = allStats();
document.getElementById('overallPct').textContent = s.pct + '%';
document.getElementById('overallCount').textContent = s.done + '/' + s.total;
document.getElementById('hoursDelivered').textContent = s.hours.toFixed(1);
document.getElementById('totalHours').textContent = s.totalHours.toFixed(0);
document.getElementById('statsRow').innerHTML = `
<div class="stat gold"><div class="val">${s.pct}%</div><div class="lbl">Overall Progress</div></div>
<div class="stat green"><div class="val">${s.done}<span style="color:var(--text-dim);font-size:15px">/${s.total}</span></div><div class="lbl">Tasks Done</div></div>
<div class="stat amber"><div class="val">${s.hours.toFixed(1)}<span style="color:var(--text-dim);font-size:15px">h</span></div><div class="lbl">Hours Delivered</div></div>
<div class="stat"><div class="val">${s.totalHours.toFixed(0)}<span style="color:var(--text-dim);font-size:15px">h</span></div><div class="lbl">Total Planned</div></div>
`;
}
function renderBars() {
let html = '';
for (let p = 0; p <= 9; p++) {
const pts = phTasks(p);
if (pts.length === 0) continue;
const s = phStats(p);
const isFull = s.done === s.total;
html += '<div class="bar-row">'
+ '<div class="lbl">' + PHASE_SHORT[p] + ' ' + PHASE_NAMES[p] + '</div>'
+ '<div class="bar"><div class="' + (isFull ? 'green' : 'gold') + '" style="width:' + s.pct + '%"></div></div>'
+ '<div class="pct">' + s.done + '/' + s.total + '</div>'
+ '</div>';
}
document.getElementById('phaseBars').innerHTML = html;
}
function renderPhases() {
let html = '';
for (let p = 0; p <= 9; p++) {
const pts = phTasks(p);
if (pts.length === 0) continue;
const s = phStats(p);
let pillClass = 'done', pillText = '100% · Complete';
if (s.done === 0) { pillClass = 'zero'; pillText = '0% · Not Started'; }
else if (s.pct < 100) { pillClass = 'partial'; pillText = s.pct + '% · In Progress'; }
html += '<div class="phase-card">'
+ '<div class="phase-head" onclick="togglePhase(this)">'
+ '<span class="arrow">▶</span>'
+ '<span class="name">' + PHASE_SHORT[p] + ' ' + PHASE_NAMES[p] + ' <small>' + s.done + '/' + s.total + ' tasks · ' + s.pct + '%</small></span>'
+ '<span class="pill ' + pillClass + '">' + pillText + '</span></div>'
+ '<table class="task-table">'
+ '<tr><th>#</th><th>Task</th><th>Type</th><th>Hrs</th><th>Description</th><th>Status</th></tr>';
pts.forEach(function(t) {
var status = t[8];
html += '<tr>'
+ '<td class="num">' + t[1] + '</td>'
+ '<td class="name">' + t[2] + '</td>'
+ '<td class="type">' + t[4] + '</td>'
+ '<td class="hours">' + t[3] + '</td>'
+ '<td class="desc">' + t[7] + '</td>'
+ '<td class="stat"><span class="status-badge ' + status + '">' + status + '</span></td>'
+ '</tr>';
});
html += '</table></div>';
}
document.getElementById('phaseDetails').innerHTML = html;
}
function togglePhase(el) {
el.querySelector('.arrow').classList.toggle('open');
el.nextElementSibling.classList.toggle('open');
}
function renderCompleted() {
var grid = document.getElementById('completedGrid');
var html = '';
TASKS.forEach(function(t) {
if (t[8] !== 'completed') return;
html += '<div class="task-card">'
+ '<div class="num">' + t[1] + ' · ' + PHASE_SHORT[t[0]] + '</div>'
+ '<div class="title">' + t[2] + '</div>'
+ '<div class="desc">' + t[7] + '</div>'
+ '<div class="meta-row"><span class="tag">' + t[4] + '</span><span>' + t[3] + 'h</span></div>'
+ '</div>';
});
grid.innerHTML = html;
document.getElementById('completedSummaryTitle').textContent = 'All ' + TASKS.length + ' tasks completed';
}
function renderAll() {
renderStats();
renderBars();
renderPhases();
renderCompleted();
}
document.getElementById('currentDate').textContent = new Date().toLocaleDateString('en-US', { year:'numeric', month:'long', day:'numeric' });
document.getElementById('generatedDate').textContent = new Date().toLocaleString();
renderAll();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment