diff --git a/.github/aw/logs/.gitignore b/.github/aw/logs/.gitignore
new file mode 100644
index 0000000..986a321
--- /dev/null
+++ b/.github/aw/logs/.gitignore
@@ -0,0 +1,5 @@
+# Ignore all downloaded workflow logs
+*
+
+# But keep the .gitignore file itself
+!.gitignore
diff --git a/package-lock.json b/package-lock.json
index b0fc0c2..990ea56 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,8 @@
"dependencies": {
"@astrojs/rss": "^4.0.19",
"@astrojs/sitemap": "^3.7.4",
+ "@fontsource-variable/inter": "^5.3.0",
+ "@fontsource-variable/jetbrains-mono": "^5.3.0",
"astro": "^7.3.2",
"zod": "^4.5.4"
},
@@ -1132,6 +1134,24 @@
"node": ">=18"
}
},
+ "node_modules/@fontsource-variable/inter": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.3.0.tgz",
+ "integrity": "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@fontsource-variable/jetbrains-mono": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@fontsource-variable/jetbrains-mono/-/jetbrains-mono-5.3.0.tgz",
+ "integrity": "sha512-F32xpS2NsGYoQi2ADSkKTgpJj7ozajsGgDJ8woTnqjmIB+dxDIqImjl4pXZVEExu8UFZ2ndhmX18EBS/hdz3Lw==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
"node_modules/@img/colour": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
diff --git a/package.json b/package.json
index 11d966e..4d6904d 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,8 @@
"dependencies": {
"@astrojs/rss": "^4.0.19",
"@astrojs/sitemap": "^3.7.4",
+ "@fontsource-variable/inter": "^5.3.0",
+ "@fontsource-variable/jetbrains-mono": "^5.3.0",
"astro": "^7.3.2",
"zod": "^4.5.4"
},
diff --git a/src/components/Footer.astro b/src/components/Footer.astro
new file mode 100644
index 0000000..f329158
--- /dev/null
+++ b/src/components/Footer.astro
@@ -0,0 +1,20 @@
+---
+import { siteConfig } from '../lib/site';
+---
+
+
diff --git a/src/components/Header.astro b/src/components/Header.astro
new file mode 100644
index 0000000..314e32a
--- /dev/null
+++ b/src/components/Header.astro
@@ -0,0 +1,34 @@
+---
+import ThemeToggle from './ThemeToggle.astro';
+import { siteConfig } from '../lib/site';
+
+const navLinks = [
+ { href: '/', label: 'Home' },
+ { href: '/about/', label: 'About' },
+];
+---
+
+
diff --git a/src/components/Hero.astro b/src/components/Hero.astro
new file mode 100644
index 0000000..10c70fb
--- /dev/null
+++ b/src/components/Hero.astro
@@ -0,0 +1,29 @@
+---
+import Icon from './Icon.astro';
+import { siteConfig } from '../lib/site';
+
+interface Props {
+ avatar?: string;
+}
+
+const { avatar } = Astro.props;
+---
+
+
+
+
+
Hi, I'm
+
{siteConfig.author}
+
{siteConfig.description}
+
+ {Object.values(siteConfig.socials).map((s) => (
+
+
+ {s.label}
+
+ ))}
+
+
+ {avatar &&

}
+
+
diff --git a/src/components/Icon.astro b/src/components/Icon.astro
new file mode 100644
index 0000000..9203e30
--- /dev/null
+++ b/src/components/Icon.astro
@@ -0,0 +1,49 @@
+---
+export interface Props {
+ name: 'github' | 'linkedin' | 'email' | 'rss' | 'sun' | 'moon';
+ size?: number;
+ class?: string;
+}
+
+const { name, size = 20, class: className } = Astro.props;
+---
+
+{name === 'github' && (
+
+)}
+{name === 'linkedin' && (
+
+)}
+{name === 'email' && (
+
+)}
+{name === 'rss' && (
+
+)}
+{name === 'sun' && (
+
+)}
+{name === 'moon' && (
+
+)}
diff --git a/src/components/PostCard.astro b/src/components/PostCard.astro
new file mode 100644
index 0000000..79734f6
--- /dev/null
+++ b/src/components/PostCard.astro
@@ -0,0 +1,26 @@
+---
+import type { CollectionEntry } from 'astro:content';
+import { entrySlug } from '../lib/slugify';
+
+interface Props {
+ post: CollectionEntry<'posts'>;
+}
+
+const { post } = Astro.props;
+const dateFmt = new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'short', day: 'numeric', timeZone: 'UTC' });
+---
+
+
+
+
+
+
+ {post.data.title}
+ {post.data.description}
+ {post.data.tags.length > 0 && (
+
+ {post.data.tags.map((t) => {t})}
+
+ )}
+
+
diff --git a/src/components/ThemeToggle.astro b/src/components/ThemeToggle.astro
new file mode 100644
index 0000000..6df0171
--- /dev/null
+++ b/src/components/ThemeToggle.astro
@@ -0,0 +1,27 @@
+---
+import Icon from './Icon.astro';
+---
+
+
+
+
+
+
diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro
index 48008b7..4ac9b1e 100644
--- a/src/layouts/BaseLayout.astro
+++ b/src/layouts/BaseLayout.astro
@@ -1,4 +1,9 @@
---
+import '@fontsource-variable/inter';
+import '@fontsource-variable/jetbrains-mono';
+import '../styles/global.css';
+import Header from '../components/Header.astro';
+import Footer from '../components/Footer.astro';
import { siteConfig } from '../lib/site';
interface Props {
@@ -7,30 +12,44 @@ interface Props {
}
const { title, description } = Astro.props;
+const canonical = new URL(Astro.url.pathname, siteConfig.url).toString();
---
-
+
- {title} · Dominic Serrano
+ {title} · {siteConfig.author}
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
diff --git a/src/layouts/PageLayout.astro b/src/layouts/PageLayout.astro
index c9c5230..b6f809a 100644
--- a/src/layouts/PageLayout.astro
+++ b/src/layouts/PageLayout.astro
@@ -10,10 +10,12 @@ const { title, description } = Astro.props;
---
-
+
-
+
+
+
diff --git a/src/layouts/PostLayout.astro b/src/layouts/PostLayout.astro
index 302663d..fa090a3 100644
--- a/src/layouts/PostLayout.astro
+++ b/src/layouts/PostLayout.astro
@@ -9,23 +9,26 @@ interface Props {
}
const { title, description, date, tags } = Astro.props;
+const dateFmt = new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'short', day: 'numeric', timeZone: 'UTC' });
---
-
+
-
+
+
+
diff --git a/src/lib/site.ts b/src/lib/site.ts
index 527c47a..0fd63d9 100644
--- a/src/lib/site.ts
+++ b/src/lib/site.ts
@@ -1,6 +1,19 @@
+export interface SocialLink {
+ url: string;
+ label: string;
+ icon: 'github' | 'linkedin' | 'email' | 'rss';
+}
+
export const siteConfig = {
title: 'Dominic Serrano',
+ shortTitle: 'dmserrano',
author: 'Dominic Serrano',
url: 'https://dominicserrano.com',
description: 'Personal site of Dominic Serrano.',
+ socials: {
+ github: { url: 'https://github.com/dmserrano', label: 'GitHub', icon: 'github' },
+ linkedin: { url: 'https://www.linkedin.com/in/dominic-serrano/', label: 'LinkedIn', icon: 'linkedin' },
+ email: { url: 'mailto:dmsrojo@gmail.com', label: 'Email', icon: 'email' },
+ rss: { url: '/feed.xml', label: 'RSS', icon: 'rss' },
+ } satisfies Record,
};
diff --git a/src/pages/index.astro b/src/pages/index.astro
index c6f6012..5ad9d41 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -1,8 +1,9 @@
---
import { getCollection } from 'astro:content';
import BaseLayout from '../layouts/BaseLayout.astro';
+import Hero from '../components/Hero.astro';
+import PostCard from '../components/PostCard.astro';
import { excludeDrafts } from '../lib/drafts';
-import { entrySlug } from '../lib/slugify';
import { siteConfig } from '../lib/site';
const posts = excludeDrafts(await getCollection('posts')).sort(
@@ -11,14 +12,11 @@ const posts = excludeDrafts(await getCollection('posts')).sort(
---
-
- {posts.map((post) => (
- -
- {post.data.title}
-
-
- ))}
-
+
+
+
Posts
+
+ {posts.map((post) =>
)}
+
+
diff --git a/src/pages/tags/[tag].astro b/src/pages/tags/[tag].astro
index 70a25d9..0c4b3b0 100644
--- a/src/pages/tags/[tag].astro
+++ b/src/pages/tags/[tag].astro
@@ -1,8 +1,8 @@
---
import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
+import PostCard from '../../components/PostCard.astro';
import { excludeDrafts } from '../../lib/drafts';
-import { entrySlug } from '../../lib/slugify';
export async function getStaticPaths() {
const posts = excludeDrafts(await getCollection('posts'));
@@ -22,15 +22,10 @@ const { tag, posts } = Astro.props;
---
- Tag: {tag}
-
- {posts.map((post) => (
- -
- {post.data.title}
-
-
- ))}
-
+
+
Tag: {tag}
+
+ {posts.map((post) =>
)}
+
+
diff --git a/src/styles/global.css b/src/styles/global.css
new file mode 100644
index 0000000..6c56bff
--- /dev/null
+++ b/src/styles/global.css
@@ -0,0 +1,499 @@
+/* Design tokens, base reset, and component styles.
+ Ported (and trimmed to what this site actually uses — no work/travel
+ sections) from Astro Wanderer by igagansingh (MIT):
+ https://github.com/igagansingh/astro-wanderer */
+
+/* ==========================================================================
+ Design tokens
+ ========================================================================== */
+
+:root {
+ --font-sans: 'Inter Variable', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
+ --font-mono: 'JetBrains Mono Variable', ui-monospace, SFMono-Regular, Menlo, monospace;
+
+ --bg: #fafafa;
+ --surface: #ffffff;
+ --surface-2: #f4f4f5;
+ --text: #18181b;
+ --text-strong: #09090b;
+ --muted: #52525b;
+ --border: #e4e4e7;
+ --accent: #2563eb;
+ --accent-strong: #1d4ed8;
+ --accent-2: #0891b2;
+ --accent-glow: rgba(37, 99, 235, 0.18);
+ --on-accent: #ffffff;
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
+ --shadow-md: 0 8px 24px -8px rgba(0, 0, 0, 0.14);
+ --shadow-lg: 0 24px 48px -16px rgba(0, 0, 0, 0.22);
+ --radius-sm: 8px;
+ --radius-md: 12px;
+ --radius-lg: 20px;
+ --container: 960px;
+ --nav-h: 64px;
+ color-scheme: light;
+}
+
+html[data-theme='dark'] {
+ --bg: #0b0f1a;
+ --surface: #111827;
+ --surface-2: #1a2234;
+ --text: #d4d4d8;
+ --text-strong: #f4f4f5;
+ --muted: #9ca3af;
+ --border: #262e3f;
+ --accent: #60a5fa;
+ --accent-strong: #93c5fd;
+ --accent-2: #22d3ee;
+ --accent-glow: rgba(96, 165, 250, 0.22);
+ --on-accent: #0b0f1a;
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
+ --shadow-md: 0 8px 24px -8px rgba(0, 0, 0, 0.5);
+ --shadow-lg: 0 24px 48px -16px rgba(0, 0, 0, 0.6);
+ color-scheme: dark;
+}
+
+/* ==========================================================================
+ Reset & base
+ ========================================================================== */
+
+*, *::before, *::after {
+ box-sizing: border-box;
+}
+
+html {
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(var(--nav-h) + 16px);
+}
+
+body {
+ margin: 0;
+ font-family: var(--font-sans);
+ font-size: 16.5px;
+ line-height: 1.65;
+ color: var(--text);
+ background: var(--bg);
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+ transition: background-color 0.3s ease, color 0.3s ease;
+}
+
+h1, h2, h3, h4 {
+ color: var(--text-strong);
+ line-height: 1.2;
+ margin: 0 0 0.5em;
+ font-weight: 700;
+ letter-spacing: -0.02em;
+}
+
+h1 { font-size: clamp(2.2rem, 5vw, 3.2rem); }
+h2 { font-size: clamp(1.5rem, 3vw, 1.9rem); }
+h3 { font-size: 1.2rem; }
+
+p { margin: 0 0 1em; }
+
+a {
+ color: var(--accent);
+ text-decoration: none;
+}
+
+a:hover {
+ color: var(--accent-strong);
+}
+
+img {
+ max-width: 100%;
+ height: auto;
+ display: block;
+}
+
+code, pre, kbd {
+ font-family: var(--font-mono);
+}
+
+code {
+ font-size: 0.88em;
+ background: var(--surface-2);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 0.1em 0.35em;
+}
+
+pre {
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ padding: 1rem 1.25rem;
+ overflow-x: auto;
+ line-height: 1.6;
+ font-size: 0.88rem;
+}
+
+pre code {
+ background: none;
+ border: none;
+ padding: 0;
+}
+
+blockquote {
+ margin: 0 0 1em;
+ padding: 0.25rem 0 0.25rem 1.25rem;
+ border-left: 3px solid var(--accent);
+ color: var(--muted);
+}
+
+hr {
+ border: none;
+ border-top: 1px solid var(--border);
+ margin: 2.5rem 0;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 1.5rem 0;
+}
+
+th, td {
+ border: 1px solid var(--border);
+ padding: 0.5rem 0.85rem;
+ text-align: left;
+}
+
+th {
+ background: var(--surface-2);
+}
+
+::selection {
+ background: var(--accent-glow);
+}
+
+/* ==========================================================================
+ Layout
+ ========================================================================== */
+
+.container {
+ width: 100%;
+ max-width: var(--container);
+ margin-inline: auto;
+ padding-inline: 1.25rem;
+}
+
+.section {
+ padding-block: 3.5rem;
+}
+
+.section-alt {
+ background: var(--surface);
+ border-block: 1px solid var(--border);
+}
+
+.eyebrow {
+ font-family: var(--font-mono);
+ font-size: 0.8rem;
+ text-transform: uppercase;
+ letter-spacing: 0.14em;
+ color: var(--accent);
+ margin: 0 0 0.35rem;
+}
+
+.section-heading {
+ margin-bottom: 2.25rem;
+}
+
+/* ==========================================================================
+ Header / nav
+ ========================================================================== */
+
+.site-header {
+ position: sticky;
+ top: 0;
+ z-index: 50;
+ height: var(--nav-h);
+ background: color-mix(in srgb, var(--bg) 82%, transparent);
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
+ border-bottom: 1px solid var(--border);
+}
+
+.nav {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ height: var(--nav-h);
+}
+
+.brand {
+ font-weight: 700;
+ color: var(--text-strong);
+ letter-spacing: -0.02em;
+}
+
+.brand span {
+ color: var(--accent);
+}
+
+.nav-links {
+ display: flex;
+ align-items: center;
+ gap: 0.35rem;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.nav-links a {
+ color: var(--muted);
+ padding: 0.4rem 0.7rem;
+ border-radius: var(--radius-sm);
+ font-weight: 500;
+ font-size: 0.95rem;
+}
+
+.nav-links a:hover {
+ color: var(--text-strong);
+ background: var(--surface-2);
+}
+
+.nav-links a[aria-current='page'] {
+ color: var(--text-strong);
+}
+
+/* ==========================================================================
+ Hero (simple: avatar, greeting, bio, socials, CTA — no typing animation)
+ ========================================================================== */
+
+.hero {
+ padding: clamp(3.5rem, 8vw, 5.5rem) 0 clamp(2.5rem, 5vw, 3.5rem);
+}
+
+.hero-inner {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ gap: 2.5rem;
+ align-items: center;
+}
+
+.hero-avatar {
+ width: clamp(140px, 22vw, 180px);
+ height: clamp(140px, 22vw, 180px);
+ border-radius: 50%;
+ object-fit: cover;
+ border: 3px solid var(--surface);
+ box-shadow: var(--shadow-lg);
+}
+
+.hero-greeting {
+ font-family: var(--font-mono);
+ color: var(--muted);
+ font-size: 0.95rem;
+ margin: 0 0 0.5rem;
+}
+
+.hero h1 {
+ font-size: clamp(2.2rem, 5vw, 3rem);
+ margin-bottom: 0.75rem;
+}
+
+.hero-bio {
+ color: var(--muted);
+ font-size: clamp(1rem, 2vw, 1.1rem);
+ line-height: 1.65;
+ max-width: 34rem;
+ margin: 0 0 1.5rem;
+}
+
+.social-row {
+ display: flex;
+ gap: 0.6rem;
+ flex-wrap: wrap;
+}
+
+.social-link {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.45rem;
+ color: var(--muted);
+ font-size: 0.95rem;
+ padding: 0.45rem 0.85rem;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ background: var(--surface);
+ transition: color 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
+}
+
+.social-link:hover {
+ color: var(--accent);
+ border-color: var(--accent);
+ transform: translateY(-1px);
+}
+
+.social-link svg {
+ width: 1.05em;
+ height: 1.05em;
+}
+
+/* ==========================================================================
+ Blog: post list, post card, tags, prose
+ ========================================================================== */
+
+.post-list {
+ display: grid;
+ gap: 1.25rem;
+}
+
+.post-card {
+ display: block;
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ padding: 1.5rem;
+ color: inherit;
+ box-shadow: var(--shadow-sm);
+ transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
+}
+
+.post-card:hover {
+ transform: translateY(-3px);
+ box-shadow: var(--shadow-md);
+ border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
+ color: inherit;
+}
+
+.post-card h3 { margin: 0.3rem 0 0.4rem; }
+
+.post-meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem 1.1rem;
+ align-items: center;
+ font-family: var(--font-mono);
+ font-size: 0.78rem;
+ color: var(--muted);
+}
+
+.post-excerpt { color: var(--muted); margin: 0.75rem 0 0; font-size: 0.95rem; }
+
+.tag-list {
+ display: flex;
+ gap: 0.4rem;
+ flex-wrap: wrap;
+ margin-top: 0.85rem;
+}
+
+.tag {
+ font-family: var(--font-mono);
+ font-size: 0.74rem;
+ padding: 0.2rem 0.55rem;
+ border-radius: 999px;
+ background: var(--surface-2);
+ border: 1px solid var(--border);
+ color: var(--muted);
+ transition: color 0.15s ease, border-color 0.15s ease;
+}
+
+.tag:hover { color: var(--accent); border-color: var(--accent); }
+
+.prose { max-width: 70ch; }
+
+.prose h2, .prose h3 {
+ margin-top: 2rem;
+ scroll-margin-top: calc(var(--nav-h) + 16px);
+}
+
+.prose a { text-decoration: underline; text-underline-offset: 3px; }
+.prose a:hover { text-decoration-thickness: 2px; }
+
+/* ==========================================================================
+ Footer
+ ========================================================================== */
+
+.site-footer {
+ border-top: 1px solid var(--border);
+ padding: 2.5rem 0 3rem;
+ margin-top: 3rem;
+ color: var(--muted);
+ font-size: 0.92rem;
+}
+
+.footer-inner {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ align-items: center;
+ text-align: center;
+}
+
+.footer-links {
+ display: flex;
+ gap: 1.1rem;
+ flex-wrap: wrap;
+ justify-content: center;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.footer-links a { color: var(--muted); }
+.footer-links a:hover { color: var(--accent); }
+
+/* ==========================================================================
+ Misc / utilities
+ ========================================================================== */
+
+.muted { color: var(--muted); }
+
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
+.theme-toggle {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 2.3rem;
+ height: 2.3rem;
+ border-radius: 50%;
+ border: 1px solid var(--border);
+ background: var(--surface);
+ color: var(--text-strong);
+ cursor: pointer;
+ font-size: 1rem;
+ transition: border-color 0.15s ease, transform 0.15s ease;
+}
+
+.theme-toggle:hover { border-color: var(--accent); transform: rotate(12deg); }
+
+/* ==========================================================================
+ Responsive
+ ========================================================================== */
+
+@media (max-width: 720px) {
+ .hero-inner {
+ grid-template-columns: 1fr;
+ }
+
+ .hero-avatar {
+ order: -1;
+ width: 130px;
+ height: 130px;
+ }
+
+ .nav-links {
+ gap: 0.1rem;
+ }
+
+ .nav-links a {
+ padding: 0.35rem 0.5rem;
+ font-size: 0.9rem;
+ }
+}