diff --git a/src/components/Footer.astro b/src/components/Footer.astro index cedb805..54de895 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -4,11 +4,6 @@ import { siteConfig } from '../lib/site'; diff --git a/src/components/Header.astro b/src/components/Header.astro index 223da38..22fa8e4 100644 --- a/src/components/Header.astro +++ b/src/components/Header.astro @@ -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' }, ]; --- + + diff --git a/src/components/Hero.astro b/src/components/Hero.astro index 10c70fb..5d5e208 100644 --- a/src/components/Hero.astro +++ b/src/components/Hero.astro @@ -14,6 +14,7 @@ const { avatar } = Astro.props;

Hi, I'm

{siteConfig.author}

+

{siteConfig.role}

{siteConfig.description}

{Object.values(siteConfig.socials).map((s) => ( diff --git a/src/components/ProjectCard.astro b/src/components/ProjectCard.astro new file mode 100644 index 0000000..15e9a55 --- /dev/null +++ b/src/components/ProjectCard.astro @@ -0,0 +1,21 @@ +--- +import type { CollectionEntry } from 'astro:content'; + +interface Props { + project: CollectionEntry<'projects'>; +} + +const { project } = Astro.props; +--- + + diff --git a/src/content.config.ts b/src/content.config.ts index 3434a34..f3cda41 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -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' }), @@ -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, }; diff --git a/src/content/pages/about.md b/src/content/pages/about.md deleted file mode 100644 index 991e0d6..0000000 --- a/src/content/pages/about.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: About -date: 2026-01-01 -description: Placeholder about page. ---- - -Placeholder page content. Real portfolio content lands in a later chunk. diff --git a/src/content/projects/alter-learning.md b/src/content/projects/alter-learning.md new file mode 100644 index 0000000..7098e5a --- /dev/null +++ b/src/content/projects/alter-learning.md @@ -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 +--- diff --git a/src/content/projects/safe-migrate.md b/src/content/projects/safe-migrate.md new file mode 100644 index 0000000..165a0de --- /dev/null +++ b/src/content/projects/safe-migrate.md @@ -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 +--- diff --git a/src/content/schema.ts b/src/content/schema.ts index cfd054a..4a1dbe9 100644 --- a/src/content/schema.ts +++ b/src/content/schema.ts @@ -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), +}); diff --git a/src/lib/site.ts b/src/lib/site.ts index 0fd63d9..ffeff0c 100644 --- a/src/lib/site.ts +++ b/src/lib/site.ts @@ -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' }, diff --git a/src/pages/now.astro b/src/pages/now.astro new file mode 100644 index 0000000..8dbae60 --- /dev/null +++ b/src/pages/now.astro @@ -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' }, +]; +--- + + +
+

Now

+

last updated: {updated}

+
+ { + status.map((s) => ( +
+ {s.key}: {s.value} +
+ )) + } + +
+
+
diff --git a/src/pages/projects/index.astro b/src/pages/projects/index.astro new file mode 100644 index 0000000..2a43f3a --- /dev/null +++ b/src/pages/projects/index.astro @@ -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() +); +--- + + +
+

Projects

+
+ {projects.map((project) => )} +
+
+
diff --git a/src/styles/global.css b/src/styles/global.css index 6c56bff..c180c1c 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -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); @@ -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; @@ -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 { @@ -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;