updating modes and structures

This commit is contained in:
2026-04-02 11:27:50 +01:00
parent 529acd4fcc
commit 172a1f85c3
297 changed files with 1328 additions and 3350 deletions

0
assets/scripts/bigger-picture.min.js vendored Normal file → Executable file
View File

0
assets/scripts/comments.js Normal file → Executable file
View File

0
assets/scripts/competency-status-board.js Normal file → Executable file
View File

0
assets/scripts/gallery-init.js Normal file → Executable file
View File

0
assets/scripts/lunr.js Normal file → Executable file
View File

0
assets/scripts/mermaid.min.js vendored Normal file → Executable file
View File

0
assets/scripts/notes.js Normal file → Executable file
View File

0
assets/scripts/script.js Normal file → Executable file
View File

0
assets/scripts/search.js Normal file → Executable file
View File

0
assets/scripts/sitemap-interactive.js Normal file → Executable file
View File

0
assets/scripts/svg-pan-zoom.min.js vendored Normal file → Executable file
View File

166
assets/scripts/wird-tracker.js Normal file → Executable file
View File

@@ -327,17 +327,18 @@
// ── Boot ─────────────────────────────────────────────────
document.addEventListener("DOMContentLoaded", async () => {
setTodayLabel();
await Promise.all([loadAll(), loadMotalah()]);
renderToday();
renderNafl();
renderMeetingPanel();
renderKhatm();
renderHistory();
renderChart();
renderMotalah();
renderHeatmap();
bindControls();
setTodayLabel();
await Promise.all([loadAll(), loadMotalah()]);
renderToday();
renderNafl();
renderMeetingPanel();
renderKhatm();
renderHistory();
renderChart();
renderMotalah();
renderSummary();
renderHeatmap();
bindControls();
});
function setTodayLabel() {
@@ -827,8 +828,9 @@
const HIDE_TYPES = new Set(["shaykh_meeting", ...Object.keys(NAFL_WIRD), "khatm"]);
const sorted = [...allEntries]
.filter(e => !HIDE_TYPES.has(e.wirdType))
.sort((a, b) => b.date.localeCompare(a.date));
.filter(e => !HIDE_TYPES.has(e.wirdType))
.sort((a, b) => b.date.localeCompare(a.date))
.slice(0, 20);
if (sorted.length === 0) {
tbody.innerHTML = `<tr><td colspan="5" class="loading-cell">No entries yet.</td></tr>`;
@@ -1192,6 +1194,144 @@
return `${label}: ${Math.round(raw).toLocaleString()} ${meta.unit}`;
}
// ============================================================
// DAILY SUMMARY TABLE
// ============================================================
let summaryDays = 30;
let summaryFilter = "__all__";
function renderSummary() {
const wrap = document.getElementById("summary-table-wrap");
if (!wrap) return;
const today = isoDate();
const cols = summaryFilter === "__all__"
? Object.keys(DAILY_WIRD)
: [summaryFilter];
// Build date rows newest→oldest
const rows = [];
for (let i = 0; i < summaryDays; i++) {
const d = new Date();
d.setDate(d.getDate() - i);
rows.push(isoDate(d));
}
// Pre-aggregate entries into { date: { type: val } }
const agg = {};
for (const e of allEntries) {
if (!cols.includes(e.wirdType)) continue;
if (!agg[e.date]) agg[e.date] = {};
const meta = DAILY_WIRD[e.wirdType];
if (meta && meta.type === "rating") {
agg[e.date][e.wirdType] = Math.max(
agg[e.date][e.wirdType] || 0, Number(e.value)
);
} else {
agg[e.date][e.wirdType] = (agg[e.date][e.wirdType] || 0) + Number(e.value);
}
}
// Build header
const thCols = cols.map(t => {
const meta = DAILY_WIRD[t];
const unitHint = meta.type === "rating" ? "/5" : meta.unit ? meta.unit : "";
return `<th title="${meta.label} · target: ${meta.target}${unitHint}">
${meta.label}<br>
<span style="font-weight:400;opacity:.6">/ ${meta.target}${unitHint}</span>
</th>`;
}).join("");
// Build body rows
const bodyRows = rows.map(dateIso => {
const isToday = dateIso === today;
const dayData = agg[dateIso] || {};
const displayDate = new Date(dateIso + "T00:00:00").toLocaleDateString("en-GB", {
weekday: "short", day: "numeric", month: "short"
});
const cells = cols.map(type => {
const meta = DAILY_WIRD[type];
const val = dayData[type];
if (val === undefined || val === null) {
return `<td class="summary-cell-empty">—</td>`;
}
const cls = statusClass(type, val);
const cellCls = cls === "over" ? "summary-cell-over"
: cls === "done" ? "summary-cell-done"
: cls === "miss" ? "summary-cell-miss"
: "";
let display;
if (meta.type === "rating") {
display = `${val}<span style="opacity:.45">/5</span>`;
} else {
display = Number(val).toLocaleString();
}
return `<td class="${cellCls}">${display}</td>`;
}).join("");
return `<tr class="${isToday ? "summary-today" : ""}">
<td>${displayDate}${isToday ? " ·" : ""}</td>
${cells}
</tr>`;
}).join("");
// Footer: days target met per column
const footCells = cols.map(type => {
const meta = DAILY_WIRD[type];
let metCount = 0;
for (const dateIso of rows) {
const val = (agg[dateIso] || {})[type];
if (val !== undefined && val >= meta.target) metCount++;
}
const pct = Math.round((metCount / summaryDays) * 100);
const cls = pct >= 80 ? "summary-cell-done" : pct >= 50 ? "" : "summary-cell-miss";
return `<td class="summary-tfoot ${cls}">${metCount}d <span style="opacity:.55">(${pct}%)</span></td>`;
}).join("");
wrap.innerHTML = `
<table id="summary-table">
<thead>
<tr>
<th>Date</th>
${thCols}
</tr>
</thead>
<tbody>${bodyRows}</tbody>
<tfoot>
<tr>
<td class="summary-tfoot" style="color:var(--muted)">days met</td>
${footCells}
</tr>
</tfoot>
</table>
`;
}
// Controls
document.querySelectorAll(".summary-preset").forEach(btn => {
btn.addEventListener("click", () => {
summaryDays = parseInt(btn.dataset.days);
document.querySelectorAll(".summary-preset").forEach(b => b.classList.remove("active"));
btn.classList.add("active");
renderSummary();
});
});
const summaryFilterEl = document.getElementById("summary-filter");
if (summaryFilterEl) {
summaryFilterEl.addEventListener("change", () => {
summaryFilter = summaryFilterEl.value;
renderSummary();
});
}
function renderHeatmapStats(scoreMap, type, startIso, endIso) {
const el = $("#heatmap-stats");
const daysLogged = Object.values(scoreMap).filter(v => v > 0).length;

191
assets/scripts/zhd.js Executable file
View File

@@ -0,0 +1,191 @@
/* =============================================================
dashboard.js — Home page dashboard behaviour
Runs after DOM is ready. All IDs are db-* scoped.
============================================================= */
(function () {
"use strict";
/* ── Greeting ──────────────────────────────────────────────── */
function updateGreeting() {
const el = document.getElementById("db-greeting");
if (!el) return;
const h = new Date().getHours();
if (h < 5) el.textContent = "Burning the midnight oil";
else if (h < 12) el.textContent = "Good morning";
else if (h < 17) el.textContent = "Good afternoon";
else if (h < 21) el.textContent = "Good evening";
else el.textContent = "Good night";
}
/* ── Live clock ────────────────────────────────────────────── */
function updateClock() {
const el = document.getElementById("db-clock");
if (!el) return;
const now = new Date();
const pad = (n) => String(n).padStart(2, "0");
el.textContent = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
}
/* ── Date stats ────────────────────────────────────────────── */
function updateStats() {
const now = new Date();
const year = now.getFullYear();
// Day of year
const start = new Date(year, 0, 0);
const diff = now - start;
const oneDay = 1000 * 60 * 60 * 24;
const dayOfYear = Math.floor(diff / oneDay);
// ISO week number
const d = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()));
const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
const weekNum = Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
// Days left
const isLeap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
const daysInYear = isLeap ? 366 : 365;
const daysLeft = daysInYear - dayOfYear;
// Month name
const months = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
];
// Write out
const set = (id, val) => {
const el = document.getElementById(id);
if (el) el.textContent = val;
};
set("db-stat-day", dayOfYear);
set("db-stat-week", "W" + weekNum);
set("db-stat-month", months[now.getMonth()]);
set("db-stat-left", daysLeft);
// Year progress bar
const pct = ((dayOfYear / daysInYear) * 100).toFixed(1);
const fill = document.getElementById("db-year-fill");
const label = document.getElementById("db-year-pct");
if (fill) fill.style.width = pct + "%";
if (label) label.textContent = pct + "%";
}
/* ── Feed builder ──────────────────────────────────────────── */
/**
* Parse a simple HTML sitemap/list page and extract links.
* Looks for <li> elements containing <a> tags.
* @param {Document} doc - parsed document
* @param {number} max - max items to return
* @param {string} prefix - path prefix to prepend to relative hrefs (e.g. "/blogs")
* @returns {{ href: string, title: string, date: string|null }[]}
*/
function extractLinks(doc, max, prefix) {
const base = (prefix || "").replace(/\/$/, ""); // strip trailing slash
const items = [];
// org-publish sitemaps use <li> inside #content
const lis = doc.querySelectorAll("#content li, .org-ul li, ul li");
for (const li of lis) {
const a = li.querySelector("a[href]");
if (!a) continue;
let href = a.getAttribute("href");
if (!href || href.startsWith("http")) continue; // skip external
// Prepend the section prefix when the href doesn't already start with it
if (base && !href.startsWith(base)) {
// href may start with "/" or be relative like "2026/03-march/foo.html"
href = href.startsWith("/")
? base + href
: base + "/" + href;
}
const title = a.textContent.trim();
if (!title) continue;
// Grab any date text that follows the link
const dateMatch = li.textContent.replace(title, "").match(/\d{4}-\d{2}-\d{2}/);
items.push({ href, title, date: dateMatch ? dateMatch[0] : null });
if (items.length >= max) break;
}
return items;
}
/**
* Render items into a db-feed <ul>.
*/
function renderFeed(ulId, items, fallbackHref) {
const ul = document.getElementById(ulId);
if (!ul) return;
if (!items || items.length === 0) {
// keep the fallback link already in the HTML
return;
}
ul.innerHTML = items
.map(
({ href, title, date }) => `
<li class="db-feed__item">
<span class="db-feed__dot"></span>
<a href="${escHtml(href)}">${escHtml(title)}</a>
${date ? `<span class="db-feed__meta">${escHtml(date)}</span>` : ""}
</li>`
)
.join("");
}
function escHtml(str) {
return str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
/**
* Fetch a sitemap page and populate a feed.
* Fails silently — the fallback link is already in the DOM.
* @param {string} listUrl - absolute path to the sitemap page to fetch
* @param {string} ulId - id of the <ul> to populate
* @param {number} max - max entries to show
* @param {string} prefix - path prefix to prepend to extracted hrefs
*/
async function loadFeed(listUrl, ulId, max, prefix) {
try {
const resp = await fetch(listUrl, { credentials: "same-origin" });
if (!resp.ok) return;
const html = await resp.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
const items = extractLinks(doc, max, prefix);
renderFeed(ulId, items);
} catch (_) {
// Network error or cross-origin — silently ignore
}
}
/* ── Init ──────────────────────────────────────────────────── */
function init() {
updateGreeting();
updateStats();
updateClock();
setInterval(updateClock, 1000);
// Populate feeds — pass the section prefix so relative hrefs are correct
loadFeed("/blogs/blogs-list.html", "db-feed-blogs", 6, "/blogs");
loadFeed("/posts/posts-list.html", "db-feed-posts", 6, "/posts");
loadFeed("/recently-updated.html", "db-feed-recent", 6, "");
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();