/* dlstack — distilled * Loads data/links.json and renders sections, cards, YouTube facades. * No search, no filter, no clock, no theme toggle. The page is the index. */ (() => { "use strict"; const $ = (s, el = document) => el.querySelector(s); const $$ = (s, el = document) => Array.from(el.querySelectorAll(s)); const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); const frag = (html) => document.createRange().createContextualFragment(html); const hostOf = (url) => { try { return new URL(url, location.href).hostname.replace(/^www\./, ""); } catch { return ""; } }; const faviconFor = (url) => { const h = hostOf(url); return h ? `https://www.google.com/s2/favicons?domain=${encodeURIComponent(h)}&sz=128` : ""; }; const ytThumb = (id) => `https://i.ytimg.com/vi/${encodeURIComponent(id)}/hqdefault.jpg`; function renderLink(it) { const host = hostOf(it.url); const custom = it.image || it.icon; const src = custom || faviconFor(it.url); const isFavicon = !custom; const initial = (it.title || host || "·").trim().charAt(0).toUpperCase(); return ` ${src ? `` : esc(initial)} ${esc(it.title)} ${it.description ? `${esc(it.description)}` : ""} ${host ? `${esc(host)}` : ""} `; } function renderProject(it) { const featured = it.featured ? " card--featured" : ""; const tag = it.url ? "a" : "div"; const attrs = it.url ? `href="${esc(it.url)}" target="_blank" rel="noopener noreferrer"` : ""; const tags = (it.tags || []).map(t => `${esc(t)}`).join(""); return ` <${tag} class="card card--project${featured} reveal" ${attrs}> ${esc(it.title)} ${it.description ? `

${esc(it.description)}

` : ""} ${tags ? `
${tags}
` : ""} `; } function renderYouTube(it) { return `
${esc(it.title)}
`; } function renderPortfolio(it) { const src = it.image || it.url; const tag = it.url ? "a" : "div"; const attrs = it.url ? `href="${esc(it.url)}" target="_blank" rel="noopener noreferrer"` : ""; const ratio = it.ratio ? ` style="--portfolio-ratio:${esc(String(it.ratio).replace(":", " / "))}"` : ""; return ` <${tag} class="card card--portfolio reveal" ${attrs}>
${src ? `${esc(it.title || ` : ""}
${(it.title || it.description) ? `
${it.title ? `${esc(it.title)}` : ""} ${it.description ? `${esc(it.description)}` : ""}
` : ""} `; } function renderClient(it) { const host = hostOf(it.url); const custom = it.image || it.icon; const src = custom || faviconFor(it.url); const isFavicon = !custom; const initial = (it.title || host || "·").trim().charAt(0).toUpperCase(); const tag = it.url ? "a" : "div"; const attrs = it.url ? `href="${esc(it.url)}" target="_blank" rel="noopener noreferrer"` : ""; return ` <${tag} class="card card--client reveal" ${attrs} title="${esc(it.title || host || "")}"> ${it.title ? `${esc(it.title)}` : ""} `; } const renderItem = (it) => it.type === "youtube" ? renderYouTube(it) : it.type === "card" ? renderProject(it) : it.type === "client" ? renderClient(it) : it.type === "portfolio" ? renderPortfolio(it) : renderLink(it); function renderSection(sec, n) { const items = (sec.items || []).map(renderItem).join(""); const num = String(n).padStart(2, "0"); const isClients = sec.layout === "clients" || sec.items?.[0]?.type === "client"; const gridClass = isClients ? "grid--clients" : "grid"; return `
${num}

${esc(sec.label)}

${sec.kicker ? `${esc(sec.kicker)}` : ""}
${items}
`; } const SOCIAL_ICONS = { github: '', linkedin: '', mail: '', rss: '', link: '', calendar: '', bluesky: 'Bluesky' }; function renderSocial(items) { return items.map(s => `${SOCIAL_ICONS[s.icon] || SOCIAL_ICONS.link}${esc(s.label)}` ).join(""); } function nameMarkup(name) { if (!name) return ""; const parts = String(name).trim().split(/\s+/); if (parts.length < 2) return esc(name); const last = parts.pop(); return `${esc(parts.join(" "))} ${esc(last)}`; } function attachFaviconFallback(root) { $$("img[data-fallback-initial]", root).forEach(img => { img.addEventListener("error", () => { const initial = img.dataset.fallbackInitial || "·"; const parent = img.parentElement; if (parent) { parent.setAttribute("data-fallback", ""); parent.textContent = initial; } }, { once: true }); }); } function attachYouTube(root) { const open = (fac) => { const iframe = document.createElement("iframe"); iframe.src = `https://www.youtube-nocookie.com/embed/${encodeURIComponent(fac.dataset.yt)}?autoplay=1&rel=0`; iframe.allow = "accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"; iframe.allowFullscreen = true; iframe.title = fac.getAttribute("aria-label") || "YouTube video"; fac.replaceChildren(iframe); fac.style.cursor = "default"; }; root.addEventListener("click", (e) => { const fac = e.target.closest("[data-yt]"); if (fac) { e.preventDefault(); open(fac); } }); root.addEventListener("keydown", (e) => { if (e.key !== "Enter" && e.key !== " ") return; const fac = e.target.closest("[data-yt]"); if (fac) { e.preventDefault(); open(fac); } }); } function attachTheme() { const root = document.documentElement; const btn = $("#theme"); if (!btn) return; const order = ["auto", "light", "dark"]; const label = { auto: "Auto", light: "Light", dark: "Dark" }; const stored = localStorage.getItem("dlstack-theme"); if (!order.includes(root.dataset.theme)) root.dataset.theme = "auto"; if (order.includes(stored)) root.dataset.theme = stored; const sync = () => { btn.textContent = label[root.dataset.theme]; btn.setAttribute("aria-label", `Theme: ${label[root.dataset.theme]}. Click to change.`); }; sync(); btn.addEventListener("click", () => { const i = order.indexOf(root.dataset.theme); root.dataset.theme = order[(i + 1) % order.length]; localStorage.setItem("dlstack-theme", root.dataset.theme); sync(); }); } function attachReveal(root) { if (!("IntersectionObserver" in window)) { $$(".reveal", root).forEach(el => el.classList.add("in")); return; } const io = new IntersectionObserver((entries) => { entries.forEach(en => { if (en.isIntersecting) { en.target.classList.add("in"); io.unobserve(en.target); } }); }, { threshold: 0.08, rootMargin: "0px 0px -40px 0px" }); $$(".reveal", root).forEach(el => io.observe(el)); } async function loadData() { const sources = ["data/links.json", "data/links.example.json"]; let lastErr; for (const src of sources) { try { const res = await fetch(src, { cache: "no-cache" }); if (!res.ok) { lastErr = new Error(`${src}: HTTP ${res.status}`); continue; } return await res.json(); } catch (err) { lastErr = err; } } throw lastErr || new Error("No data file found."); } async function main() { const app = $("#app"); let data; try { data = await loadData(); } catch (err) { const p = document.createElement("p"); p.style.cssText = "color:var(--accent);font-family:var(--mono);padding:2rem;max-width:48ch"; p.textContent = `Couldn't load data/links.json or data/links.example.json. Check that one exists and is valid JSON. Details: ${err.message}`; app.replaceChildren(p); return; } if (data.theme?.accent) { document.documentElement.style.setProperty("--accent", data.theme.accent); } const tpl = data.theme?.template === "swiss" ? "swiss" : "editorial"; document.documentElement.dataset.template = tpl; try { localStorage.setItem("dlstack-template", tpl); } catch (e) {} const p = data.profile || {}; const sections = data.sections || []; const social = data.social || []; document.title = `${p.name || "Links"}`; const taglineMarkup = (t) => esc(t).replace(/\s*[·•|]\s*/g, ''); const html = `
Index №01 MMXXVI

${nameMarkup(p.name)}

${p.tagline ? `

${taglineMarkup(p.tagline)}

` : ""} ${p.bio ? `

${esc(p.bio)}

` : ""} ${social.length ? `` : ""}
${sections.map((s, i) => renderSection(s, i + 1)).join("")}
`; app.replaceChildren(frag(html)); attachFaviconFallback(app); attachYouTube(app); attachReveal(app); attachTheme(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", main); } else { main(); } })();