diff --git a/public/theme.css b/public/theme.css
index a947127..ea6dcb7 100644
--- a/public/theme.css
+++ b/public/theme.css
@@ -356,28 +356,32 @@ nav {
justify-content: flex-end;
list-style: none;
padding-block: var(--stack-small);
- padding-inline: 1ch;
+ padding-inline: 0;
}
.display-prefs-nav summary::-webkit-details-marker {
display: none;
}
-.display-prefs-nav summary:hover {
- padding-inline: 0;
+.display-prefs-nav summary::before {
+ color: transparent;
+ text-shadow: none;
+ content: "[" / "";
}
-.display-prefs-nav summary:hover::before {
- color: var(--color-glow);
- content: "[";
+.display-prefs-nav summary::after {
+ color: transparent;
+ text-shadow: none;
+ content: "]" / "";
}
+.display-prefs-nav summary:hover::before,
.display-prefs-nav summary:hover::after {
color: var(--color-glow);
- content: "]";
+ text-shadow: inherit;
}
.display-prefs-nav summary > span {
translate: 0 -0.225em;
}
.display-prefs-panel {
background-color: var(--color-bg);
- border: 2px solid var(--color-accent);
+ border: 1px solid var(--color-accent);
min-inline-size: 10em;
padding-block: var(--stack-small);
padding-inline: var(--gutter-small);
@@ -414,21 +418,30 @@ form p {
a {
color: var(--color-fg);
margin: 0 -1ch;
- padding: 0 1ch;
text-decoration: underline;
text-decoration-color: var(--color-glow);
- &:hover::before {
- color: var(--color-glow);
- content: "[";
+ /* 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: "[" / "";
}
+ &::after {
+ color: transparent;
+ text-shadow: none;
+ content: "]" / "";
+ }
+ &:hover::before,
&:hover::after {
color: var(--color-glow);
- content: "]";
+ text-shadow: inherit;
}
&:hover {
color: var(--color-fg);
- padding: 0;
text-decoration: none;
}
}
@@ -439,10 +452,8 @@ footer a {
margin: 0 auto;
padding: 0;
- &:hover::before {
- content: none;
- }
- &:hover::after {
+ &::before,
+ &::after {
content: none;
}
}
diff --git a/src/components/PageHeader.astro b/src/components/PageHeader.astro
new file mode 100644
index 0000000..71000de
--- /dev/null
+++ b/src/components/PageHeader.astro
@@ -0,0 +1,64 @@
+---
+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
new file mode 100644
index 0000000..92afeec
--- /dev/null
+++ b/src/components/ProjectCard.astro
@@ -0,0 +1,73 @@
+---
+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.description && {project.description}
}
+ {project.lastCommitMessage && (
+
+ {project.lastCommitSha} {project.lastCommitMessage}
+
+ )}
+
+ {project.languages.length > 0 && (
+ `${l.name} ${l.percent}%`).join(", ")}`}>
+ {project.languages.map((l) => (
+
+ ))}
+
+
+ {project.languages.slice(0, 4).map((l) => {l.name} {l.percent}%)}
+
+ )}
+
+ {project.topics.length > 0 && (
+
+ {project.topics.map((t) => - #{t}
)}
+
+ )}
+
diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro
index 9f3eadd..fbbfc67 100644
--- a/src/layouts/BaseLayout.astro
+++ b/src/layouts/BaseLayout.astro
@@ -44,6 +44,7 @@
@@ -106,7 +107,7 @@
/>
- Made from scratch with BAHz: Bun, Astro, and Htmz!
+ Made from scratch with BAAHz: Bun, Astro, Alpine.js, and Htmz!