import "./styles/tokens.css"; import "./styles/base.css"; import "./styles/components.css"; import { initI18n, setLang, getLang, onLangChange, applyTranslations, t } from "./i18n"; import { initTheme, toggleTheme, getTheme, onThemeChange } from "./theme"; import { projects } from "./data/projects"; import { infraServices } from "./data/infra"; import { contacts } from "./data/contacts"; import { fetchInfraSnapshot, type InfraSnapshot } from "./komari"; const app = document.querySelector("#app")!; app.innerHTML = `

--
--
${projects.length}
01

2025
2026
02

--
--
03

04

`; const SKILLS: Array<{ icon: string; label: string }> = [ { icon: "ph-code", label: "TypeScript" }, { icon: "ph-brackets-curly", label: "Go" }, { icon: "ph-terminal-window", label: "Linux" }, { icon: "ph-cloud", label: "Self-hosting" }, { icon: "ph-git-branch", label: "Git" }, { icon: "ph-brain", label: "AI / ML basics" }, ]; function renderSkills(): void { const el = document.getElementById("skills-list")!; el.innerHTML = SKILLS.map( (s) => `
${s.label}
` ).join(""); } function renderInfraServices(): void { const el = document.getElementById("infra-services")!; el.innerHTML = infraServices .map( (s) => `
` ) .join(""); } function renderContacts(): void { const el = document.getElementById("contact-links")!; el.innerHTML = contacts .map( (c) => ` ` ) .join(""); } const infraSnapshotPromise = fetchInfraSnapshot(); let infraSnapshotCache: InfraSnapshot | null = null; function paintRegionChips(): void { if (!infraSnapshotCache) return; const chips = document.getElementById("region-chips")!; if (infraSnapshotCache.live && infraSnapshotCache.regions.length) { chips.innerHTML = infraSnapshotCache.regions .map((r) => `${r}`) .join(""); } else { const label = t("infra.regionsFallback").replace("{count}", String(infraSnapshotCache.regionCount)); chips.innerHTML = `${label}`; } } async function renderInfraStats(): Promise { infraSnapshotCache = await infraSnapshotPromise; document.getElementById("stat-servers")!.textContent = String(infraSnapshotCache.serverCount); document.getElementById("stat-regions")!.textContent = String(infraSnapshotCache.regionCount); document.getElementById("hero-stat-servers")!.textContent = String(infraSnapshotCache.serverCount); document.getElementById("hero-stat-regions")!.textContent = String(infraSnapshotCache.regionCount); paintRegionChips(); } function setupInfraGlobe(): void { const container = document.getElementById("infra-globe")!; const canvasMount = document.createElement("div"); canvasMount.className = "infra__globe-canvas"; container.prepend(canvasMount); const observer = new IntersectionObserver( (entries) => { if (entries.some((e) => e.isIntersecting)) { observer.disconnect(); mountGlobeWhenReady(container, canvasMount); } }, { threshold: 0.2 } ); observer.observe(container); } async function mountGlobeWhenReady(container: HTMLElement, canvasMount: HTMLElement): Promise { const snapshot = await infraSnapshotPromise; if (!snapshot.regionBreakdown.length && !snapshot.nodes.length && !snapshot.nodeLocations.length) return; const { mountGlobe } = await import("./globe"); container.classList.add("has-live"); mountGlobe(canvasMount, snapshot); } function openModal(html: string): void { const overlay = document.getElementById("modal-overlay")!; document.getElementById("modal-body")!.innerHTML = html; overlay.classList.add("is-open"); } function closeModal(): void { document.getElementById("modal-overlay")!.classList.remove("is-open"); } function renderProjects(): void { const featuredSlot = document.getElementById("project-featured-slot")!; const grid = document.getElementById("project-grid")!; const featured = projects.filter((p) => p.featured); const rest = projects.filter((p) => !p.featured); featuredSlot.innerHTML = featured .map( (p) => ` ` ) .join(""); grid.innerHTML = rest .map( (p) => `

` ) .join("") + (rest.length === 0 ? `
` : ""); document.querySelectorAll("[data-project]").forEach((card) => { card.addEventListener("click", () => { const project = projects.find((p) => p.id === card.dataset.project); if (!project) return; openModal(` ${ project.image ? `${t(`${project.id}.imageAlt`)}` : `` } `); applyTranslations(); }); }); } function setupThemeToggle(): void { const btn = document.getElementById("theme-toggle")!; const paint = () => { btn.innerHTML = getTheme() === "dark" ? `` : ``; }; paint(); onThemeChange(paint); btn.addEventListener("click", toggleTheme); } function setupLangToggle(): void { const btn = document.getElementById("lang-toggle")!; const paint = () => { btn.textContent = getLang() === "zh" ? "EN" : "中文"; }; paint(); onLangChange(() => { paint(); paintRegionChips(); }); btn.addEventListener("click", () => setLang(getLang() === "zh" ? "en" : "zh")); } function setupModal(): void { document.getElementById("modal-close")!.addEventListener("click", closeModal); document.getElementById("modal-overlay")!.addEventListener("click", (e) => { if (e.target === document.getElementById("modal-overlay")) closeModal(); }); document.addEventListener("keydown", (e) => { if (e.key === "Escape") closeModal(); }); } function setupScrollReveal(): void { const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; const targets = document.querySelectorAll(".reveal"); if (reduceMotion) { targets.forEach((el) => el.classList.add("is-visible")); return; } const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add("is-visible"); observer.unobserve(entry.target); } }); }, { threshold: 0.15 } ); targets.forEach((el) => observer.observe(el)); } initTheme(); initI18n(); renderSkills(); renderInfraServices(); renderProjects(); renderContacts(); setupThemeToggle(); setupLangToggle(); setupModal(); setupScrollReveal(); setupInfraGlobe(); applyTranslations(); renderInfraStats(); // Ensure the empty i18n keys used as placeholders (e.g. tag on non-featured items) don't render "undefined" document.querySelectorAll('[data-i18n=""]').forEach((el) => el.remove());