Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions src/components/Footer.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,13 @@ import { siteConfig } from '../lib/site';

<footer class="site-footer">
<div class="container footer-inner">
<ul class="footer-links">
<li><a href="/">Home</a></li>
<li><a href="/blog/">Blog</a></li>
<li><a href="/about/">About</a></li>
</ul>
<ul class="footer-links">
{Object.values(siteConfig.socials).map((s) => (
<li>
<a href={s.url} target={s.url.startsWith('http') ? '_blank' : undefined} rel="noopener">{s.label}</a>
</li>
))}
</ul>
<p>&copy; {siteConfig.author}</p>
<p>&copy; {new Date().getFullYear()} {siteConfig.author}</p>
</div>
</footer>
35 changes: 30 additions & 5 deletions src/components/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ import { siteConfig } from '../lib/site';
const navLinks = [
{ href: '/', label: 'Home' },
{ href: '/blog/', label: 'Blog' },
{ href: '/about/', label: 'About' },
{ href: '/projects/', label: 'Projects' },
{ href: '/now/', label: 'Now' },
];
---

<header class="site-header">
<div class="container nav">
<a class="brand" href="/">
<span aria-hidden="true">&gt;</span> {siteConfig.shortTitle}
<span>_</span>
</a>
<div class="brand-wrap">
<a class="brand" href="/">
<span aria-hidden="true">&gt;</span> {siteConfig.shortTitle}
</a>
<button class="brand-cursor" type="button" aria-label="psst">_</button>
<span class="brand-egg" id="brand-egg"></span>
</div>
<nav aria-label="Main">
<ul class="nav-links">
{
Expand All @@ -33,3 +37,24 @@ const navLinks = [
</nav>
</div>
</header>

<script>
const lines = [
'still compiling dad jokes...',
'fueled by coffee ☕',
'good boy detected 🐕',
'99 bugs in the code, 99 bugs...',
'now pushing boundaries (and belly rubs)',
];
const btn = document.querySelector<HTMLButtonElement>('.brand-cursor');
const egg = document.getElementById('brand-egg');
let hideTimer: ReturnType<typeof setTimeout>;

btn?.addEventListener('click', () => {
if (!egg) return;
egg.textContent = lines[Math.floor(Math.random() * lines.length)];
egg.classList.add('show');
clearTimeout(hideTimer);
hideTimer = setTimeout(() => egg.classList.remove('show'), 2400);
});
</script>
1 change: 1 addition & 0 deletions src/components/Hero.astro
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const { avatar } = Astro.props;
<div>
<p class="hero-greeting">Hi, I'm</p>
<h1>{siteConfig.author}</h1>
<p class="hero-role">{siteConfig.role}</p>
<p class="hero-bio">{siteConfig.description}</p>
<div class="social-row">
{Object.values(siteConfig.socials).map((s) => (
Expand Down
21 changes: 21 additions & 0 deletions src/components/ProjectCard.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
import type { CollectionEntry } from 'astro:content';

interface Props {
project: CollectionEntry<'projects'>;
}

const { project } = Astro.props;
---

<article>
<a class="post-card" href={project.data.repoUrl} target="_blank" rel="noopener">
<h3>{project.data.title}</h3>
<p class="post-excerpt">{project.data.description}</p>
{project.data.stack.length > 0 && (
<div class="tag-list">
{project.data.stack.map((s) => <span class="tag">{s}</span>)}
</div>
)}
</a>
</article>
8 changes: 7 additions & 1 deletion src/content.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { pageSchema, postSchema } from './content/schema';
import { pageSchema, postSchema, projectSchema } from './content/schema';

const postsCollection = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/posts' }),
Expand All @@ -12,7 +12,13 @@ const pagesCollection = defineCollection({
schema: pageSchema,
});

const projectsCollection = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/projects' }),
schema: projectSchema,
});

export const collections = {
posts: postsCollection,
pages: pagesCollection,
projects: projectsCollection,
};
7 changes: 0 additions & 7 deletions src/content/pages/about.md

This file was deleted.

7 changes: 7 additions & 0 deletions src/content/projects/alter-learning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: ALTER
description: A personal-university learning framework for Claude Code. Five coordinated AI roles — advisor, librarian, tutor, editor, roommate — turn any topic into a self-directed learning pathway with a plan, ranked sources, spaced-repetition drills, and milestone critiques.
repoUrl: https://github.com/dmserrano/alter-learning
stack: [Claude Code Plugin, Markdown]
date: 2026-02-01
---
7 changes: 7 additions & 0 deletions src/content/projects/safe-migrate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: safe-migrate
description: Verified characterization tests for JS apps facing breaking dependency upgrades. Generated tests are gated through static, green, stability, and mutation checks so only ones that actually catch real bugs survive — the verification harness is the product, not the agent.
repoUrl: https://github.com/dmserrano/safe-migrate
stack: [TypeScript, Node.js, Docker, Stryker]
date: 2026-01-01
---
9 changes: 9 additions & 0 deletions src/content/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,12 @@ export const pageSchema = z.object({
draft: z.boolean().default(false),
description: z.string(),
});

export const projectSchema = z.object({
title: z.string(),
description: z.string(),
repoUrl: z.string(),
stack: z.array(z.string()).default([]),
date: z.coerce.date(),
draft: z.boolean().default(false),
});
4 changes: 3 additions & 1 deletion src/lib/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ export const siteConfig = {
title: 'Dominic Serrano',
shortTitle: 'dmserrano',
author: 'Dominic Serrano',
role: 'software developer / dog lover / coffee snob in denial',
url: 'https://dominicserrano.com',
description: 'Personal site of Dominic Serrano.',
description:
"I get curious about something, build a small version of it, and usually learn more than I planned to.",
socials: {
github: { url: 'https://github.com/dmserrano', label: 'GitHub', icon: 'github' },
linkedin: { url: 'https://www.linkedin.com/in/dominic-serrano/', label: 'LinkedIn', icon: 'linkedin' },
Expand Down
29 changes: 29 additions & 0 deletions src/pages/now.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';

const updated = 'sep 08 2026';

const status = [
{ key: 'reading', value: 'the fifth season by n. k. jemisin' },
{ key: 'watching', value: 'silo and jack reacher' },
{ key: 'building', value: 'studying the claude certified architect track and bulding agentic workflows' },
{ key: 'outside', value: 'walking the doggo daily and some occasional pickleball' },
];
---

<BaseLayout title="Now" description="What I'm up to right now.">
<div class="container section now-page">
<h1 class="section-heading">Now</h1>
<p class="now-updated">last updated: {updated}</p>
<div class="now-block">
{
status.map((s) => (
<div class="now-line">
<span class="now-key">{s.key}:</span> <span class="now-value">{s.value}</span>
</div>
))
}
<div class="now-line now-cursor" aria-hidden="true">&gt; <span>_</span></div>
</div>
</div>
</BaseLayout>
19 changes: 19 additions & 0 deletions src/pages/projects/index.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
import ProjectCard from '../../components/ProjectCard.astro';
import { excludeDrafts } from '../../lib/drafts';

const projects = excludeDrafts(await getCollection('projects')).sort(
(a, b) => b.data.date.valueOf() - a.data.date.valueOf()
);
---

<BaseLayout title="Projects" description="Things I've built.">
<div class="container section">
<h1 class="section-heading">Projects</h1>
<div class="post-list">
{projects.map((project) => <ProjectCard project={project} />)}
</div>
</div>
</BaseLayout>
103 changes: 102 additions & 1 deletion src/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,12 @@ th {
height: var(--nav-h);
}

.brand-wrap {
position: relative;
display: flex;
align-items: baseline;
}

.brand {
font-weight: 700;
color: var(--text-strong);
Expand All @@ -234,6 +240,54 @@ th {
color: var(--accent);
}

.brand-cursor {
background: none;
border: none;
padding: 0;
margin-left: 1px;
font: inherit;
font-weight: 700;
color: var(--accent);
cursor: pointer;
animation: brand-blink 1.1s steps(1) infinite;
}

.brand-cursor:hover,
.brand-cursor:focus-visible {
animation-play-state: paused;
}

@keyframes brand-blink {
50% {
opacity: 0;
}
}

.brand-egg {
position: absolute;
top: 100%;
left: 0;
margin-top: 0.5rem;
white-space: nowrap;
font-family: var(--font-mono);
font-size: 0.78rem;
color: var(--muted);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 0.35rem 0.6rem;
box-shadow: var(--shadow-sm);
opacity: 0;
transform: translateY(-4px);
transition: opacity 0.18s ease, transform 0.18s ease;
pointer-events: none;
}

.brand-egg.show {
opacity: 1;
transform: translateY(0);
}

.nav-links {
display: flex;
align-items: center;
Expand Down Expand Up @@ -293,7 +347,14 @@ th {

.hero h1 {
font-size: clamp(2.2rem, 5vw, 3rem);
margin-bottom: 0.75rem;
margin-bottom: 0.35rem;
}

.hero-role {
font-family: var(--font-mono);
color: var(--accent);
font-size: 0.95rem;
margin: 0 0 1.1rem;
}

.hero-bio {
Expand Down Expand Up @@ -477,6 +538,46 @@ th {
Responsive
========================================================================== */

/* ==========================================================================
Now page: terminal-style status block
========================================================================== */

.now-updated {
font-family: var(--font-mono);
font-size: 0.85rem;
color: var(--muted);
margin: -1rem 0 1.5rem;
}

.now-block {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
padding: 1.5rem 1.75rem;
font-family: var(--font-mono);
font-size: 0.95rem;
line-height: 2;
}

.now-key {
color: var(--accent);
font-weight: 600;
}

.now-value {
color: var(--text-strong);
}

.now-cursor {
color: var(--muted);
margin-top: 0.4rem;
}

.now-cursor span {
animation: brand-blink 1.1s steps(1) infinite;
}

@media (max-width: 720px) {
.hero-inner {
grid-template-columns: 1fr;
Expand Down