Update dashboard
All checks were successful
Build Org Website / build (push) Successful in 41s

This commit is contained in:
2026-05-06 23:15:20 +01:00
parent 687126b974
commit fa992de25a
3 changed files with 657 additions and 533 deletions

View File

@@ -1,186 +1,166 @@
/* =============================================================
dashboard.js — Home page dashboard behaviour
Runs after DOM is ready. All IDs are db-* scoped.
============================================================= */
/* Home dashboard behaviour. All selectors 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 ────────────────────────────────────────────── */
const hour = new Date().getHours();
if (hour < 5) el.textContent = "Late session";
else if (hour < 12) el.textContent = "Good morning";
else if (hour < 17) el.textContent = "Good afternoon";
else if (hour < 21) el.textContent = "Good evening";
else el.textContent = "Evening review";
}
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())}`;
el.textContent = new Intl.DateTimeFormat(undefined, {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
}).format(new Date());
}
/* ── Date stats ────────────────────────────────────────────── */
function getIsoWeek(date) {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
return Math.ceil(((d - yearStart) / 86400000 + 1) / 7);
}
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 start = new Date(year, 0, 1);
const nextYear = new Date(year + 1, 0, 1);
const dayOfYear = Math.floor((now - start) / 86400000) + 1;
const daysInYear = Math.round((nextYear - start) / 86400000);
const daysLeft = Math.max(0, daysInYear - dayOfYear);
const month = new Intl.DateTimeFormat(undefined, { month: "short" }).format(now);
const pct = ((dayOfYear / daysInYear) * 100).toFixed(1);
setText("db-stat-day", dayOfYear);
setText("db-stat-week", "W" + getIsoWeek(now));
setText("db-stat-month", month);
setText("db-stat-left", daysLeft);
setText("db-year-pct", pct + "%");
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 + "%";
if (fill) fill.style.width = 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 setText(id, value) {
const el = document.getElementById(id);
if (el) el.textContent = value;
}
function escHtml(str) {
return str
return String(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
*/
function normaliseHref(href, prefix) {
if (!href || href.startsWith("http") || href.startsWith("#")) return null;
const base = (prefix || "").replace(/\/$/, "");
if (!base || href.startsWith(base + "/")) return href;
return href.startsWith("/") ? base + href : base + "/" + href;
}
function extractLinks(doc, max, prefix) {
const items = [];
const seen = new Set();
const links = doc.querySelectorAll("#content li a[href], .org-ul li a[href], ul li a[href]");
for (const a of links) {
const href = normaliseHref(a.getAttribute("href"), prefix);
const title = a.textContent.trim();
if (!href || !title || seen.has(href)) continue;
const li = a.closest("li");
const dateMatch = li ? li.textContent.match(/\b\d{4}-\d{2}-\d{2}\b/) : null;
seen.add(href);
items.push({ href, title, date: dateMatch ? dateMatch[0] : null });
if (items.length >= max) break;
}
return items;
}
function renderFeed(ulId, items) {
const ul = document.getElementById(ulId);
if (!ul || !items.length) 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("");
}
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);
const doc = new DOMParser().parseFromString(html, "text/html");
renderFeed(ulId, extractLinks(doc, max, prefix));
} catch (_) {
// Network error or cross-origin — silently ignore
/* Fallback links remain in the HTML. */
}
}
/* ── Init ──────────────────────────────────────────────────── */
function initCommandFilter() {
const input = document.getElementById("db-command-search");
const nav = document.getElementById("db-quicknav");
if (!input || !nav) return;
const items = Array.from(nav.querySelectorAll(".db-qn-item"));
const applyFilter = () => {
const query = input.value.trim().toLowerCase();
items.forEach((item) => {
const haystack = [
item.textContent,
item.getAttribute("href"),
item.dataset.keywords,
].join(" ").toLowerCase();
item.classList.toggle("is-hidden", Boolean(query && !haystack.includes(query)));
});
};
input.addEventListener("input", applyFilter);
document.addEventListener("keydown", (event) => {
if (event.key === "/" && !/^(input|textarea|select)$/i.test(event.target.tagName)) {
event.preventDefault();
input.focus();
}
});
}
function init() {
updateGreeting();
updateStats();
updateClock();
setInterval(updateClock, 1000);
updateStats();
initCommandFilter();
// 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, "");
setInterval(updateClock, 1000);
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") {

View File

@@ -1,230 +1,339 @@
/* =============================================================
dashboard.css — Home page dashboard styles
Scoped entirely under .db-* to avoid clashing with existing styles.
Uses the site's CSS custom properties throughout.
============================================================= */
/* ── Root ───────────────────────────────────────────────────── */
/* Home dashboard styles. Scoped to .db-* so article pages keep their layout. */
.db-root {
--db-surface: color-mix(in oklab, var(--bg) 94%, var(--fg) 6%);
--db-surface-strong: color-mix(in oklab, var(--bg) 88%, var(--fg) 12%);
--db-line: color-mix(in oklab, var(--border) 78%, var(--fg) 22%);
--db-text-soft: color-mix(in oklab, var(--fg) 68%, var(--bg) 32%);
--db-blue: #2f6fed;
--db-green: #15835b;
--db-amber: #9d6400;
--db-red: #b64c45;
width: 100%;
padding: 1.5rem 0 4rem;
box-sizing: border-box;
padding-bottom: 4rem;
}
/* ── Hero ───────────────────────────────────────────────────── */
.db-root *,
.db-root *::before,
.db-root *::after {
box-sizing: border-box;
}
.db-kicker {
margin: 0 0 0.35rem;
color: var(--muted);
font-size: 0.72rem;
font-weight: 800;
letter-spacing: 0.12em;
line-height: 1.2;
text-transform: uppercase;
}
.db-hero {
display: flex;
align-items: flex-end;
align-items: stretch;
justify-content: space-between;
gap: 1rem;
padding: 2.5rem 0 2rem;
border-bottom: 1px solid var(--border);
margin-bottom: 1.75rem;
flex-wrap: wrap;
padding: 1rem;
margin-bottom: 1rem;
background: var(--db-surface);
border: 1px solid var(--db-line);
border-radius: 8px;
}
.db-hero__greeting {
margin: 0 0 0.15rem;
font-size: 0.8rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--muted);
font-weight: 600;
.db-hero__brand {
display: flex;
align-items: center;
gap: 1rem;
min-width: 0;
}
.db-hero__logo {
width: clamp(64px, 10vw, 96px);
height: clamp(64px, 10vw, 96px);
object-fit: cover;
border-radius: 8px;
border: 1px solid var(--db-line);
background: var(--bg);
flex: 0 0 auto;
}
.db-hero__title {
margin: 0 0 0.3rem;
font-size: clamp(2rem, 5vw, 3rem);
font-weight: 700;
color: var(--heading);
letter-spacing: -0.02em;
line-height: 1.1;
margin: 0;
color: var(--fg);
font-size: clamp(2.4rem, 7vw, 5rem);
font-weight: 900;
line-height: 0.95;
}
.db-hero__sub {
margin: 0;
max-width: 58ch;
margin: 0.55rem 0 0;
color: var(--db-text-soft);
font-size: 0.98rem;
line-height: 1.55;
}
.db-now {
display: grid;
align-content: center;
min-width: min(100%, 230px);
padding: 1rem;
background: color-mix(in oklab, var(--bg) 84%, var(--fg) 16%);
border: 1px solid var(--db-line);
border-radius: 8px;
}
.db-now__label {
color: var(--muted);
font-size: 0.95rem;
font-size: 0.72rem;
font-weight: 800;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.db-hero__clock {
font-size: clamp(1.6rem, 4vw, 2.4rem);
font-variant-numeric: tabular-nums;
font-weight: 300;
color: var(--heading);
letter-spacing: 0.05em;
opacity: 0.7;
white-space: nowrap;
align-self: center;
}
/* ── Quick-nav ──────────────────────────────────────────────── */
.db-quicknav {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 2rem;
}
.db-qn-item {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.38rem 0.85rem;
border: 1px solid var(--border);
border-radius: 999px;
font-size: 0.82rem;
font-weight: 600;
.db-now__clock {
display: block;
margin: 0.2rem 0 0.85rem;
color: var(--fg);
text-decoration: none;
background: transparent;
transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.1s;
white-space: nowrap;
}
.db-qn-item:hover {
background: var(--heading);
border-color: var(--heading);
color: #fff;
text-decoration: none;
transform: translateY(-1px);
}
.db-qn-icon {
font-size: 0.9em;
opacity: 0.75;
font-size: clamp(1.9rem, 4vw, 2.75rem);
font-variant-numeric: tabular-nums;
font-weight: 800;
line-height: 1;
}
/* ── Main grid ──────────────────────────────────────────────── */
.db-command {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(260px, 420px);
gap: 1rem;
align-items: center;
padding: 1rem 0;
}
.db-command__title {
margin: 0;
color: var(--fg);
font-size: clamp(1.2rem, 3vw, 1.85rem);
line-height: 1.15;
}
.db-command__search {
display: flex;
align-items: center;
gap: 0.6rem;
min-height: 48px;
padding: 0 0.85rem;
background: var(--db-surface);
border: 1px solid var(--db-line);
border-radius: 8px;
}
.db-command__search:focus-within {
border-color: var(--db-blue);
box-shadow: 0 0 0 3px color-mix(in oklab, var(--db-blue) 18%, transparent);
}
.db-search-icon {
color: var(--muted);
font-size: 1.1rem;
}
.db-command__search input {
width: 100%;
min-width: 0;
border: 0;
outline: 0;
background: transparent;
color: var(--fg);
font: inherit;
}
.db-command__search input::placeholder {
color: var(--muted);
}
.db-quicknav {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 0.75rem;
margin: 0 0 1.25rem;
}
.db-qn-item {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
grid-template-areas:
"icon label"
"icon meta";
column-gap: 0.7rem;
align-items: center;
min-height: 76px;
padding: 0.9rem;
color: var(--fg);
text-decoration: none;
background: var(--db-surface);
border: 1px solid var(--db-line);
border-left: 4px solid var(--db-blue);
border-radius: 8px;
transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
}
.db-qn-item:hover,
.db-qn-item:focus-visible {
color: var(--fg);
text-decoration: none;
border-color: color-mix(in oklab, var(--db-blue) 55%, var(--db-line));
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.08);
transform: translateY(-2px);
}
.db-qn-item.is-hidden {
display: none;
}
.db-qn-item--writing,
.db-qn-item--notes { border-left-color: var(--db-blue); }
.db-qn-item--career,
.db-qn-item--wird { border-left-color: var(--db-green); }
.db-qn-item--lima,
.db-qn-item--status { border-left-color: var(--db-amber); }
.db-qn-item--system,
.db-qn-item--zone { border-left-color: var(--db-red); }
.db-qn-icon {
grid-area: icon;
display: inline-grid;
place-items: center;
width: 36px;
height: 36px;
color: var(--fg);
background: color-mix(in oklab, var(--fg) 8%, transparent);
border: 1px solid var(--db-line);
border-radius: 8px;
font-size: 1.05rem;
line-height: 1;
}
.db-qn-label {
grid-area: label;
min-width: 0;
overflow-wrap: anywhere;
font-size: 1rem;
font-weight: 850;
}
.db-qn-meta {
grid-area: meta;
color: var(--muted);
font-size: 0.76rem;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.db-grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 1.25rem;
grid-template-columns: repeat(12, minmax(0, 1fr));
gap: 1rem;
align-items: start;
}
/* Card spanning rules */
.db-card--blogs { grid-column: span 6; }
.db-card--posts { grid-column: span 6; }
.db-card--stats { grid-column: span 4; }
.db-card--lima { grid-column: span 4; }
.db-card--wip { grid-column: span 4; }
.db-card--recent { grid-column: span 6; }
.db-card--about { grid-column: span 6; }
/* ── Card ───────────────────────────────────────────────────── */
.db-card {
background: color-mix(in oklab, var(--bg) 92%, var(--fg) 8%);
border: 1px solid var(--border);
border-radius: 10px;
padding: 1.25rem 1.4rem;
box-sizing: border-box;
transition: box-shadow 0.2s;
.db-panel {
grid-column: span 4;
min-width: 0;
padding: 1rem;
background: var(--db-surface);
border: 1px solid var(--db-line);
border-radius: 8px;
}
.db-card:hover {
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.07);
.db-panel--wide {
grid-column: span 8;
}
.db-card__head {
.db-panel--stats,
.db-panel--focus {
grid-column: span 4;
}
.db-panel--about {
grid-column: span 8;
}
.db-panel__head {
display: flex;
align-items: baseline;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 0.9rem;
gap: 0.5rem;
gap: 1rem;
margin-bottom: 0.85rem;
}
.db-card__title {
.db-panel__title {
margin: 0;
font-size: 0.85rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--heading);
display: flex;
align-items: center;
gap: 0.35rem;
color: var(--fg);
font-size: 1.05rem;
line-height: 1.25;
}
/* Override the global h2 margin that org-publish may inject */
.db-card .db-card__title {
color: var(--heading) !important;
}
.db-card__accent {
opacity: 0.6;
font-size: 0.9em;
}
.db-card__more {
font-size: 0.75rem;
font-weight: 600;
.db-panel__more {
flex: 0 0 auto;
color: var(--link);
font-size: 0.82rem;
font-weight: 800;
text-decoration: none;
white-space: nowrap;
flex-shrink: 0;
}
.db-card__more:hover {
.db-panel__more:hover {
text-decoration: underline;
}
.db-card__desc {
margin: 0 0 1rem;
font-size: 0.88rem;
color: var(--muted);
.db-panel__desc {
margin: 0;
color: var(--db-text-soft);
font-size: 0.92rem;
line-height: 1.6;
}
/* ── Feed list (blogs / posts / recent) ─────────────────────── */
.db-feed {
display: grid;
gap: 0.15rem;
list-style: none;
margin: 0;
padding: 0;
}
.db-feed__item {
display: flex;
align-items: flex-start;
gap: 0.5rem;
padding: 0.45rem 0;
border-bottom: 1px solid var(--border);
font-size: 0.875rem;
line-height: 1.4;
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
gap: 0.6rem;
align-items: baseline;
min-width: 0;
padding: 0.62rem 0;
margin: 0;
border-top: 1px solid color-mix(in oklab, var(--db-line) 70%, transparent);
line-height: 1.35;
}
/* Remove the global bullet pseudo-element inside db-feed */
.db-feed .db-feed__item::before {
display: none;
}
.db-feed__item:last-child {
border-bottom: none;
.db-feed__item::before {
content: none !important;
}
.db-feed__dot {
width: 5px;
height: 5px;
border-radius: 50%;
background: var(--heading);
flex-shrink: 0;
margin-top: 0.45em;
opacity: 0.5;
width: 0.48rem;
height: 0.48rem;
margin-top: 0.25rem;
background: var(--db-green);
border-radius: 999px;
}
.db-feed__item a {
min-width: 0;
color: var(--fg);
font-size: 0.92rem;
font-weight: 700;
overflow-wrap: anywhere;
text-decoration: none;
flex: 1;
font-weight: 500;
}
.db-feed__item a:hover {
@@ -233,187 +342,207 @@
}
.db-feed__meta {
font-size: 0.72rem;
color: var(--muted);
font-size: 0.74rem;
font-variant-numeric: tabular-nums;
font-weight: 700;
white-space: nowrap;
flex-shrink: 0;
margin-top: 0.1em;
}
/* ── Stats widget ───────────────────────────────────────────── */
.db-stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem 1rem;
margin-bottom: 1.2rem;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.7rem;
}
.db-stat {
display: flex;
flex-direction: column;
gap: 0.1rem;
min-height: 92px;
padding: 0.8rem;
background: color-mix(in oklab, var(--bg) 82%, var(--fg) 18%);
border: 1px solid var(--db-line);
border-radius: 8px;
}
.db-stat__num {
font-size: 1.7rem;
font-weight: 700;
display: block;
color: var(--fg);
font-size: clamp(1.55rem, 4vw, 2.1rem);
font-variant-numeric: tabular-nums;
color: var(--heading);
font-weight: 900;
line-height: 1;
letter-spacing: -0.02em;
}
.db-stat__label {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.1em;
display: block;
margin-top: 0.5rem;
color: var(--muted);
font-weight: 600;
font-size: 0.72rem;
font-weight: 800;
letter-spacing: 0.1em;
text-transform: uppercase;
}
/* Year progress bar */
.db-year-bar {
position: relative;
height: 6px;
background: var(--border);
height: 8px;
background: color-mix(in oklab, var(--border) 75%, var(--bg) 25%);
border-radius: 999px;
overflow: visible;
margin-top: 0.25rem;
}
.db-year-bar__fill {
width: 0%;
min-width: 6px;
height: 100%;
background: var(--db-green);
border-radius: 999px;
background: var(--heading);
transition: width 0.6s ease;
min-width: 4px;
transition: width 0.45s ease;
}
.db-year-bar__label {
position: absolute;
right: 0;
top: 50%;
transform: translate(0, -50%) translateY(-130%);
font-size: 0.68rem;
font-weight: 700;
color: var(--heading);
letter-spacing: 0.05em;
bottom: calc(100% + 0.35rem);
color: var(--muted);
font-size: 0.74rem;
font-weight: 850;
}
/* ── Lima ───────────────────────────────────────────────────── */
.db-task-list,
.db-link-stack {
display: grid;
gap: 0.55rem;
}
.db-lima-links {
.db-task-list a,
.db-link-stack a {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
margin-top: 0.25rem;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
min-height: 44px;
padding: 0.65rem 0.75rem;
color: var(--fg);
background: color-mix(in oklab, var(--bg) 85%, var(--fg) 15%);
border: 1px solid var(--db-line);
border-radius: 8px;
font-weight: 750;
text-decoration: none;
}
.db-lima-chip {
padding: 0.25rem 0.7rem;
border: 1px solid var(--border);
border-radius: 999px;
font-size: 0.78rem;
font-weight: 600;
.db-task-list a:hover,
.db-link-stack a:hover {
color: var(--link);
text-decoration: none;
transition: background 0.15s;
border-color: color-mix(in oklab, var(--link) 45%, var(--db-line));
}
.db-lima-chip:hover {
background: var(--chip-bg);
text-decoration: none;
.db-task-list strong {
color: var(--muted);
font-size: 0.72rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
/* ── WIP ────────────────────────────────────────────────────── */
.db-wip-btn {
display: inline-block;
margin-top: 0.5rem;
font-size: 0.83rem;
font-weight: 600;
color: var(--link);
text-decoration: none;
}
.db-wip-btn:hover {
text-decoration: underline;
}
/* ── Tags ───────────────────────────────────────────────────── */
.db-tags {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
margin-top: 0.5rem;
gap: 0.45rem;
margin-top: 0.9rem;
}
.db-tag {
padding: 0.2rem 0.65rem;
background: var(--chip-bg);
padding: 0.28rem 0.62rem;
color: var(--chip-fg);
background: var(--chip-bg);
border: 1px solid var(--db-line);
border-radius: 999px;
font-size: 0.75rem;
font-weight: 500;
letter-spacing: 0.03em;
font-size: 0.76rem;
font-weight: 750;
}
/* ── Responsive ─────────────────────────────────────────────── */
@media (max-width: 900px) {
.db-card--blogs,
.db-card--posts { grid-column: span 6; }
.db-card--stats,
.db-card--lima,
.db-card--wip { grid-column: span 6; }
.db-card--recent,
.db-card--about { grid-column: span 12; }
:root[data-theme="dark"] .db-root {
--db-surface: color-mix(in oklab, var(--bg) 82%, var(--fg) 18%);
--db-surface-strong: color-mix(in oklab, var(--bg) 72%, var(--fg) 28%);
--db-line: color-mix(in oklab, var(--border) 80%, var(--fg) 20%);
--db-blue: #78a6ff;
--db-green: #63c995;
--db-amber: #d7a451;
--db-red: #e28982;
}
@media (max-width: 620px) {
.db-grid {
grid-template-columns: 1fr;
@media (max-width: 980px) {
.db-quicknav {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.db-card--blogs,
.db-card--posts,
.db-card--stats,
.db-card--lima,
.db-card--wip,
.db-card--recent,
.db-card--about {
grid-column: span 1;
.db-panel,
.db-panel--wide,
.db-panel--stats,
.db-panel--focus,
.db-panel--about {
grid-column: span 6;
}
}
@media (max-width: 720px) {
.db-root {
padding-top: 0.75rem;
}
.db-hero,
.db-command {
grid-template-columns: 1fr;
}
.db-hero {
flex-direction: column;
}
.db-hero__brand {
align-items: flex-start;
}
.db-hero__clock {
align-self: flex-start;
opacity: 0.5;
font-size: 1.2rem;
.db-now {
min-width: 0;
}
.db-quicknav {
gap: 0.4rem;
.db-quicknav,
.db-grid {
grid-template-columns: 1fr;
}
.db-panel,
.db-panel--wide,
.db-panel--stats,
.db-panel--focus,
.db-panel--about {
grid-column: span 1;
}
.db-feed__item {
grid-template-columns: auto minmax(0, 1fr);
}
.db-feed__meta {
grid-column: 2;
}
}
@media (max-width: 460px) {
.db-hero__brand {
display: grid;
}
.db-hero__logo {
width: 58px;
height: 58px;
}
.db-qn-item {
font-size: 0.78rem;
padding: 0.32rem 0.7rem;
min-height: 68px;
}
.db-stats-grid {
grid-template-columns: 1fr;
}
}
/* ── Dark mode adjustments ──────────────────────────────────── */
:root[data-theme="dark"] .db-card {
background: color-mix(in oklab, var(--bg) 85%, var(--fg) 15%);
}
:root[data-theme="dark"] .db-card:hover {
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}

271
index.org
View File

@@ -5,179 +5,194 @@
#+BEGIN_EXPORT html
<div class="db-root">
<!-- ░░ HERO ░░ -->
<header class="db-hero">
<div class="db-hero__text">
<p class="db-hero__greeting" id="db-greeting">Good morning</p>
<h1 class="db-hero__title">zxh</h1>
<p class="db-hero__sub">z's homepage</p>
<section class="db-hero" aria-labelledby="db-home-title">
<div class="db-hero__brand">
<img class="db-hero__logo" src="/assets/images/gr.png" alt="" loading="eager" decoding="async" />
<div>
<p class="db-kicker" id="db-greeting">Good morning</p>
<h1 class="db-hero__title" id="db-home-title">zxh</h1>
<p class="db-hero__sub">A working dashboard for notes, writing, learning, and live site navigation.</p>
</div>
</div>
<div class="db-hero__clock" id="db-clock" aria-live="polite">00:00:00</div>
</header>
<!-- ░░ QUICK LINKS ░░ -->
<nav class="db-quicknav" aria-label="Quick links">
<a class="db-qn-item" href="/blogs/blogs-list.html">
<span class="db-qn-icon"></span>
<aside class="db-now" aria-label="Current time and year progress">
<span class="db-now__label">Local time</span>
<time class="db-now__clock" id="db-clock" aria-live="polite">00:00:00</time>
<div class="db-year-bar" title="Year progress">
<div class="db-year-bar__fill" id="db-year-fill"></div>
<span class="db-year-bar__label" id="db-year-pct">0%</span>
</div>
</aside>
</section>
<section class="db-command" aria-labelledby="db-command-title">
<div class="db-command__main">
<p class="db-kicker">Command center</p>
<h2 class="db-command__title" id="db-command-title">Find the right area quickly</h2>
</div>
<label class="db-command__search">
<span class="db-search-icon" aria-hidden="true"></span>
<input id="db-command-search" type="search" placeholder="Filter links, pages, and tools..." autocomplete="off" />
</label>
</section>
<nav class="db-quicknav" id="db-quicknav" aria-label="Dashboard shortcuts">
<a class="db-qn-item db-qn-item--writing" href="/blogs/blogs-list.html" data-keywords="blogs journal writing weekly review life">
<span class="db-qn-icon"></span>
<span class="db-qn-label">Blogs</span>
<span class="db-qn-meta">journal</span>
</a>
<a class="db-qn-item" href="/posts/posts-list.html">
<span class="db-qn-icon"></span>
<a class="db-qn-item db-qn-item--notes" href="/posts/posts-list.html" data-keywords="posts notes knowledge reference learning">
<span class="db-qn-icon"></span>
<span class="db-qn-label">Posts</span>
<span class="db-qn-meta">notes</span>
</a>
<a class="db-qn-item" href="/posts/career/career-list.html">
<span class="db-qn-icon"></span>
<a class="db-qn-item db-qn-item--career" href="/posts/career/career-list.html" data-keywords="career work engineering probation objectives">
<span class="db-qn-icon"></span>
<span class="db-qn-label">Career</span>
<span class="db-qn-meta">work</span>
</a>
<a class="db-qn-item" href="/lima/index.html">
<span class="db-qn-icon"></span>
<a class="db-qn-item db-qn-item--lima" href="/lima/index.html" data-keywords="lima project archive index entries">
<span class="db-qn-icon"></span>
<span class="db-qn-label">Lima</span>
<span class="db-qn-meta">project</span>
</a>
<a class="db-qn-item" href="/home/wird-tracker.html">
<span class="db-qn-icon"></span>
<a class="db-qn-item db-qn-item--wird" href="/home/wird-tracker.html" data-keywords="wird tracker habit status progress">
<span class="db-qn-icon"></span>
<span class="db-qn-label">Wird</span>
<span class="db-qn-meta">tracker</span>
</a>
<a class="db-qn-item" href="/home/status.html">
<span class="db-qn-icon"></span>
<span class="db-qn-label">Status</span>
</a>
<a class="db-qn-item" href="/home/backlog.html">
<span class="db-qn-icon"></span>
<span class="db-qn-label">Backlog</span>
</a>
<a class="db-qn-item" href="/sitemap.html">
<span class="db-qn-icon"></span>
<a class="db-qn-item db-qn-item--system" href="/sitemap.html" data-keywords="sitemap pages map browse all">
<span class="db-qn-icon"></span>
<span class="db-qn-label">Sitemap</span>
<span class="db-qn-meta">map</span>
</a>
<a class="db-qn-item" href="https://zone.zainezq.com" target="_blank" rel="noopener">
<span class="db-qn-icon"></span>
<span class="db-qn-label">Dashboard</span>
<a class="db-qn-item db-qn-item--status" href="/home/status.html" data-keywords="status services uptime home">
<span class="db-qn-icon"></span>
<span class="db-qn-label">Status</span>
<span class="db-qn-meta">site</span>
</a>
<a class="db-qn-item db-qn-item--zone" href="https://zone.zainezq.com" target="_blank" rel="noopener" data-keywords="external dashboard zone tools">
<span class="db-qn-icon"></span>
<span class="db-qn-label">Zone</span>
<span class="db-qn-meta">external</span>
</a>
</nav>
<!-- ░░ MAIN GRID ░░ -->
<div class="db-grid">
<!-- Recent Blogs -->
<section class="db-card db-card--blogs" aria-labelledby="db-blogs-title">
<header class="db-card__head">
<h2 class="db-card__title" id="db-blogs-title">
<span class="db-card__accent"></span> Recent Blogs
</h2>
<a class="db-card__more" href="/blogs/blogs-list.html">All →</a>
<section class="db-panel db-panel--wide" aria-labelledby="db-blogs-title">
<header class="db-panel__head">
<div>
<p class="db-kicker">Latest writing</p>
<h2 class="db-panel__title" id="db-blogs-title">Recent blogs</h2>
</div>
<a class="db-panel__more" href="/blogs/blogs-list.html">View all</a>
</header>
<ul class="db-feed" id="db-feed-blogs">
<li class="db-feed__item"><span class="db-feed__dot"></span><a href="/blogs/blogs-list.html">Browse all blog posts</a></li>
</ul>
</section>
<!-- Recent Posts -->
<section class="db-card db-card--posts" aria-labelledby="db-posts-title">
<header class="db-card__head">
<h2 class="db-card__title" id="db-posts-title">
<span class="db-card__accent"></span> Recent Posts
</h2>
<a class="db-card__more" href="/posts/posts-list.html">All →</a>
<section class="db-panel db-panel--stats" aria-labelledby="db-stats-title">
<header class="db-panel__head">
<div>
<p class="db-kicker">Snapshot</p>
<h2 class="db-panel__title" id="db-stats-title">At a glance</h2>
</div>
</header>
<div class="db-stats-grid">
<div class="db-stat">
<span class="db-stat__num" id="db-stat-day">-</span>
<span class="db-stat__label">Day</span>
</div>
<div class="db-stat">
<span class="db-stat__num" id="db-stat-week">-</span>
<span class="db-stat__label">Week</span>
</div>
<div class="db-stat">
<span class="db-stat__num" id="db-stat-month">-</span>
<span class="db-stat__label">Month</span>
</div>
<div class="db-stat">
<span class="db-stat__num" id="db-stat-left">-</span>
<span class="db-stat__label">Days left</span>
</div>
</div>
</section>
<section class="db-panel db-panel--wide" aria-labelledby="db-posts-title">
<header class="db-panel__head">
<div>
<p class="db-kicker">Knowledge base</p>
<h2 class="db-panel__title" id="db-posts-title">Recent posts</h2>
</div>
<a class="db-panel__more" href="/posts/posts-list.html">View all</a>
</header>
<ul class="db-feed" id="db-feed-posts">
<li class="db-feed__item"><span class="db-feed__dot"></span><a href="/posts/posts-list.html">Browse all posts</a></li>
</ul>
</section>
<!-- Stats widget -->
<section class="db-card db-card--stats" aria-labelledby="db-stats-title">
<header class="db-card__head">
<h2 class="db-card__title" id="db-stats-title">
<span class="db-card__accent"></span> At a Glance
</h2>
<section class="db-panel db-panel--focus" aria-labelledby="db-focus-title">
<header class="db-panel__head">
<div>
<p class="db-kicker">Work queue</p>
<h2 class="db-panel__title" id="db-focus-title">Active areas</h2>
</div>
</header>
<div class="db-stats-grid">
<div class="db-stat">
<span class="db-stat__num" id="db-stat-day"></span>
<span class="db-stat__label">Day of year</span>
</div>
<div class="db-stat">
<span class="db-stat__num" id="db-stat-week"></span>
<span class="db-stat__label">Week</span>
</div>
<div class="db-stat">
<span class="db-stat__num" id="db-stat-month"></span>
<span class="db-stat__label">Month</span>
</div>
<div class="db-stat">
<span class="db-stat__num" id="db-stat-left"></span>
<span class="db-stat__label">Days left in year</span>
</div>
</div>
<div class="db-year-bar" title="Year progress">
<div class="db-year-bar__fill" id="db-year-fill"></div>
<span class="db-year-bar__label" id="db-year-pct"></span>
<div class="db-task-list">
<a href="/wip.html"><span>Drafts and WIP</span><strong>Open</strong></a>
<a href="/home/backlog.html"><span>Backlog</span><strong>Plan</strong></a>
<a href="/home/countdown.html"><span>Countdowns</span><strong>Track</strong></a>
</div>
</section>
<!-- Lima quick access -->
<section class="db-card db-card--lima" aria-labelledby="db-lima-title">
<header class="db-card__head">
<h2 class="db-card__title" id="db-lima-title">
<span class="db-card__accent"></span> Lima
</h2>
<a class="db-card__more" href="/lima/index.html">Open →</a>
</header>
<p class="db-card__desc">
Limas deidcated sub page
</p>
<div class="db-lima-links">
<a class="db-lima-chip" href="/lima/index.html">Index</a>
<a class="db-lima-chip" href="/lima/lima-list.html">All entries</a>
</div>
</section>
<!-- WIP tracker -->
<section class="db-card db-card--wip" aria-labelledby="db-wip-title">
<header class="db-card__head">
<h2 class="db-card__title" id="db-wip-title">
<span class="db-card__accent"></span> Work in Progress
</h2>
<a class="db-card__more" href="/wip.html">All →</a>
</header>
<p class="db-card__desc">Pages currently being drafted or actively updated.</p>
<a class="db-wip-btn" href="/wip.html">View WIP files →</a>
</section>
<!-- Recently Updated -->
<section class="db-card db-card--recent" aria-labelledby="db-recent-title">
<header class="db-card__head">
<h2 class="db-card__title" id="db-recent-title">
<span class="db-card__accent"></span> Recently Updated
</h2>
<a class="db-card__more" href="/recently-updated.html">All →</a>
<section class="db-panel" aria-labelledby="db-recent-title">
<header class="db-panel__head">
<div>
<p class="db-kicker">Activity</p>
<h2 class="db-panel__title" id="db-recent-title">Recently updated</h2>
</div>
<a class="db-panel__more" href="/recently-updated.html">View all</a>
</header>
<ul class="db-feed" id="db-feed-recent">
<li class="db-feed__item"><span class="db-feed__dot"></span><a href="/recently-updated.html">See all recent changes</a></li>
</ul>
</section>
<!-- About / Site info -->
<section class="db-card db-card--about" aria-labelledby="db-about-title">
<header class="db-card__head">
<h2 class="db-card__title" id="db-about-title">
<span class="db-card__accent"></span> About this site
</h2>
<section class="db-panel" aria-labelledby="db-system-title">
<header class="db-panel__head">
<div>
<p class="db-kicker">System</p>
<h2 class="db-panel__title" id="db-system-title">Site controls</h2>
</div>
</header>
<p class="db-card__desc">
Built entirely in <strong>Emacs Org-mode</strong> and published with
<code>org-publish</code> to static HTML. No JavaScript framework, no build pipeline -
just text files and a Lisp script.
<div class="db-link-stack">
<a href="/home/services.html">Services</a>
<a href="/home/categories.html">Categories</a>
<a href="/home/notes.html">Notes wall</a>
<a href="/home/contact.html">Contact</a>
</div>
</section>
<section class="db-panel db-panel--about" aria-labelledby="db-about-title">
<header class="db-panel__head">
<div>
<p class="db-kicker">Build</p>
<h2 class="db-panel__title" id="db-about-title">Org-powered static site</h2>
</div>
</header>
<p class="db-panel__desc">
Built in Emacs Org-mode and published through <code>org-publish</code>.
The homepage stays lightweight while acting as a practical launch surface for the rest of the site.
</p>
<div class="db-tags">
<span class="db-tag">Emacs</span>
<span class="db-tag">Org-mode</span>
<span class="db-tag">Static site</span>
<span class="db-tag">Arch Linux</span>
<span class="db-tag">Static HTML</span>
<span class="db-tag">No framework</span>
</div>
</section>
</div><!-- /db-grid -->
</div><!-- /db-root -->
</div>
</div>
#+END_EXPORT