diff --git a/public/theme.css b/public/theme.css index ea6dcb7..a947127 100644 --- a/public/theme.css +++ b/public/theme.css @@ -356,32 +356,28 @@ nav { justify-content: flex-end; list-style: none; padding-block: var(--stack-small); - padding-inline: 0; + padding-inline: 1ch; } .display-prefs-nav summary::-webkit-details-marker { display: none; } -.display-prefs-nav summary::before { - color: transparent; - text-shadow: none; - content: "[" / ""; +.display-prefs-nav summary:hover { + padding-inline: 0; } -.display-prefs-nav summary::after { - color: transparent; - text-shadow: none; - content: "]" / ""; +.display-prefs-nav summary:hover::before { + color: var(--color-glow); + content: "["; } -.display-prefs-nav summary:hover::before, .display-prefs-nav summary:hover::after { color: var(--color-glow); - text-shadow: inherit; + content: "]"; } .display-prefs-nav summary > span { translate: 0 -0.225em; } .display-prefs-panel { background-color: var(--color-bg); - border: 1px solid var(--color-accent); + border: 2px solid var(--color-accent); min-inline-size: 10em; padding-block: var(--stack-small); padding-inline: var(--gutter-small); @@ -418,30 +414,21 @@ form p { a { color: var(--color-fg); margin: 0 -1ch; + padding: 0 1ch; text-decoration: underline; text-decoration-color: var(--color-glow); - /* Brackets always occupy their space and hover only reveals them. Swapping - ch-based padding for a rendered glyph shifts the text by a subpixel, - because the fluid root font-size makes 1ch fractional. - The / "" keeps them out of the accessible name. */ - &::before { - color: transparent; - text-shadow: none; - content: "[" / ""; + &:hover::before { + color: var(--color-glow); + content: "["; } - &::after { - color: transparent; - text-shadow: none; - content: "]" / ""; - } - &:hover::before, &:hover::after { color: var(--color-glow); - text-shadow: inherit; + content: "]"; } &:hover { color: var(--color-fg); + padding: 0; text-decoration: none; } } @@ -452,8 +439,10 @@ footer a { margin: 0 auto; padding: 0; - &::before, - &::after { + &:hover::before { + content: none; + } + &:hover::after { content: none; } } diff --git a/src/components/PageHeader.astro b/src/components/PageHeader.astro deleted file mode 100644 index 71000de..0000000 --- a/src/components/PageHeader.astro +++ /dev/null @@ -1,64 +0,0 @@ ---- -interface Props { - /** Shell-style path shown as the heading, e.g. "~/projects" */ - path: string; - /** Command echoed under the heading, without the "$ " prompt */ - command?: string; - /** Heading level; pages with their own

elsewhere should stay at 2 */ - level?: 1 | 2; -} -const { path, command, level = 2 } = Astro.props; -const Heading = level === 1 ? "h1" : "h2"; ---- - - - - diff --git a/src/components/ProjectCard.astro b/src/components/ProjectCard.astro deleted file mode 100644 index 92afeec..0000000 --- a/src/components/ProjectCard.astro +++ /dev/null @@ -1,73 +0,0 @@ ---- -import type { Project } from "@lib/ProjectsCache"; -interface Props { project: Project } -const { project } = Astro.props; -// Space instead of "T" gives the browser somewhere to wrap; the datetime -// attribute keeps the real ISO value. -const committedAtLabel = project.lastCommitAt.replace("T", " "); ---- - -
-
-

- {project.name} - {project.archived && [archived]} -

-

- ★ {project.stars} - ⑂ {project.forks} - -

-
- - {project.description &&

{project.description}

} - {project.lastCommitMessage && ( -

- {project.lastCommitSha} {project.lastCommitMessage} -

- )} - - {project.languages.length > 0 && ( - -

- {project.languages.slice(0, 4).map((l) => {l.name} {l.percent}%)} -

- )} - - {project.topics.length > 0 && ( - - )} -
diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index fbbfc67..9f3eadd 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -44,7 +44,6 @@ @@ -107,7 +106,7 @@ />
- Made from scratch with BAAHz: Bun, Astro, Alpine.js, and Htmz! + Made from scratch with BAHz: Bun, Astro, and Htmz!

diff --git a/src/lib/ForgejoClient.ts b/src/lib/ForgejoClient.ts deleted file mode 100644 index ed65ad2..0000000 --- a/src/lib/ForgejoClient.ts +++ /dev/null @@ -1,52 +0,0 @@ -import httpFetchClient from "@lib/HttpFetchClient"; - - const FORGEJO_BASE = "https://git.badblocks.dev"; - const API = `${FORGEJO_BASE}/api/v1`; - const HEADERS = { Accept: "application/json" }; - - export interface ForgejoRepoDto { - name: string; - full_name: string; - description: string; - topics: string[]; - stars_count: number; - forks_count: number; - updated_at: string; - language: string; - html_url: string; - archived: boolean; - default_branch: string; - } - - export interface LastCommit { date: string; sha: string; message: string } - - export const toIsoSeconds = (ts: string) => - new Date(ts).toISOString().replace(/\.\d{3}Z$/, ""); - - const forgejoClient = { - searchByTopic: async (topic: string): Promise => { - const res = await httpFetchClient.get( - `${API}/repos/search?topic=true&q=${encodeURIComponent(topic)}&limit=50`, HEADERS); - if (!res?.ok) throw new Error("Forgejo search returned not-ok"); - return res.data as ForgejoRepoDto[]; - }, - getLanguages: async (fullName: string): Promise> => - httpFetchClient.get(`${API}/repos/${fullName}/languages`, HEADERS), - getLastCommit: async (fullName: string, branch: string): Promise => { - try { - const res = await httpFetchClient.get( - `${API}/repos/${fullName}/branches/${encodeURIComponent(branch)}`, HEADERS); - const c = res?.commit; - if (!c?.timestamp) return null; - return { - date: toIsoSeconds(c.timestamp), - sha: String(c.id ?? "").slice(0, 7), - message: String(c.message ?? "").split("\n")[0].trim(), - }; - } catch (error: any) { - if (error?.status === 404 || error?.statusCode === 404) return null; - throw error; - } - }, - }; - export default forgejoClient; diff --git a/src/lib/ProjectsCache.ts b/src/lib/ProjectsCache.ts deleted file mode 100644 index c15ad50..0000000 --- a/src/lib/ProjectsCache.ts +++ /dev/null @@ -1,78 +0,0 @@ - export interface ProjectLanguage { name: string; bytes: number; percent: number } - - export interface Project { - name: string; fullName: string; description: string; url: string; - topics: string[]; - stars: number; forks: number; - lastCommitAt: string; - lastCommitSha: string | null; - lastCommitMessage: string | null; - languages: ProjectLanguage[]; - archived: boolean; - } - - export interface ProjectsSnapshot { - projects: Project[]; - fetchedAt: string; - stale: boolean; - } - import forgejoClient, { toIsoSeconds, type ForgejoRepoDto, type LastCommit } from "@lib/ForgejoClient"; - - const SHOWCASE_TOPIC = "showcase"; - const TTL_MS = 24 * 60 * 60 * 1000; - - let snapshot: ProjectsSnapshot | null = null; - let inFlight: Promise | null = null; - - export async function getProjects(): Promise { - if (snapshot && Date.now() - Date.parse(snapshot.fetchedAt) < TTL_MS) return snapshot; - if (inFlight) return inFlight; // dedupe concurrent refreshes - inFlight = refresh() - .catch((err) => { - console.error("[projects] Forgejo refresh failed:", err?.message ?? err); - if (snapshot) return (snapshot = { ...snapshot, stale: true }); - return { projects: [], fetchedAt: new Date(0).toISOString(), stale: true }; - }) - .finally(() => { inFlight = null; }); - return inFlight; - } - - async function refresh(): Promise { - const repos = await forgejoClient.searchByTopic(SHOWCASE_TOPIC); - const projects = await Promise.all( - repos.map(async (repo) => { - // allSettled: a flaky enrichment call degrades that field, not the page - const [langs, commit] = await Promise.allSettled([ - forgejoClient.getLanguages(repo.full_name), - forgejoClient.getLastCommit(repo.full_name, repo.default_branch), - ]); - return normalize(repo, - langs.status === "fulfilled" ? langs.value : {}, - commit.status === "fulfilled" ? commit.value : null); - }), - ); - projects.sort((a, b) => - Date.parse(b.lastCommitAt) - Date.parse(a.lastCommitAt)); - return (snapshot = { projects, fetchedAt: new Date().toISOString(), stale: false }); - } - - function normalize( - repo: ForgejoRepoDto, langBytes: Record, - commit: LastCommit | null, - ): Project { - const total = Object.values(langBytes).reduce((s, n) => s + n, 0); - return { - name: repo.name, fullName: repo.full_name, - description: repo.description ?? "", url: repo.html_url, - topics: (repo.topics ?? []).filter((t) => t !== SHOWCASE_TOPIC), - stars: repo.stars_count, forks: repo.forks_count, - // updated_at only as a last resort: it tracks metadata edits, not commits - lastCommitAt: commit?.date ?? toIsoSeconds(repo.updated_at), - lastCommitSha: commit?.sha ?? null, - lastCommitMessage: commit?.message ?? null, - languages: Object.entries(langBytes) - .map(([name, bytes]) => ({ name, bytes, percent: total ? Math.round((bytes / total) * 1000) / 10 : 0 })) - .sort((a, b) => b.bytes - a.bytes), - archived: repo.archived, - }; - } diff --git a/src/pages/contact.astro b/src/pages/contact.astro index cc0e80b..613af44 100644 --- a/src/pages/contact.astro +++ b/src/pages/contact.astro @@ -1,6 +1,5 @@ --- import Layout from "@layouts/BaseLayout.astro"; -import PageHeader from "@components/PageHeader.astro"; import { actions, isInputError } from "astro:actions"; export const prerender = false; @@ -148,7 +147,7 @@ const msgValue = pickValue("msg") || (await Astro.session?.get("msg")); - +

Contact

{ (nextAction != "complete" && (
diff --git a/src/pages/license.astro b/src/pages/license.astro index 36e754f..de58600 100644 --- a/src/pages/license.astro +++ b/src/pages/license.astro @@ -1,6 +1,5 @@ --- import Layout from "@layouts/BaseLayout.astro"; -import PageHeader from "@components/PageHeader.astro"; import fs from "node:fs"; const txtToHtml = (string: string) => { @@ -24,11 +23,13 @@ const license_file_contents = txtToHtml( LICENSE | badblocks.dev - -

- MIT License -

+
+

+ MIT License +

+
diff --git a/src/pages/projects.astro b/src/pages/projects.astro deleted file mode 100644 index da5d917..0000000 --- a/src/pages/projects.astro +++ /dev/null @@ -1,64 +0,0 @@ ---- -import Layout from "@layouts/BaseLayout.astro"; -import ProjectCard from "@components/ProjectCard.astro"; -import PageHeader from "@components/PageHeader.astro"; -import { getProjects } from "@lib/ProjectsCache"; -export const prerender = false; - -const { projects, stale } = await getProjects(); ---- - - - Projects | badblocks.dev - - -
- - - { - stale && projects.length > 0 && ( -

- [!] showing cached data — git.badblocks.dev unreachable -

- ) - } - { - projects.length === 0 && ( -
- {stale ? ( -

- [!] could not reach{" "} - git.badblocks.dev — - projects live there; try again shortly. -

- ) : ( -

- total 0 — nothing tagged #showcase yet. Browse - everything at{" "} - - git.badblocks.dev - - . -

- )} -
- ) - } - -
- {projects.map((p) => )} -
-
-
-
diff --git a/src/pages/tools.astro b/src/pages/tools.astro index 904a47c..5e78e70 100644 --- a/src/pages/tools.astro +++ b/src/pages/tools.astro @@ -1,6 +1,5 @@ --- import Layout from "@layouts/BaseLayout.astro"; -import PageHeader from "@components/PageHeader.astro"; --- @@ -16,7 +15,7 @@ import PageHeader from "@components/PageHeader.astro";
- +

Tools

Tool 1
Tool 2