Rebuild the site as a terminal-style agent on Astro
My Gatsby Deployment pipeline / build-and-deploy (push) Successful in 1m4s
My Gatsby Deployment pipeline / build-and-deploy (push) Successful in 1m4s
Replace the single Gatsby page with a dark, terminal-style agent that answers questions about Dennis's work from one curated knowledge base. There is no model and no network call, so answers cannot be invented. A plain "Classic view" and an optional light theme sit one click away. Site - Terminal UI (React island): typed answers, tool-call lines, slash commands, autocomplete, history, deep links, a short /oncall game - Content focused on what recruiters scan for; personal and employer-internal details are left out - No profile photo anywhere, including the new link-preview card and icons - Corrected facts from the old page (dates, titles, tenses, skill tiers) Tooling - Migrate Gatsby 5 to Astro 7 (Node 22.23+); builds in about 1 second - Self-host fonts, so visitors make no request to third parties - Link-preview card, favicon set, web manifest and a service-worker clean-up for returning visitors - Deploy workflow builds with npm ci on Node 22 and uploads dist/ - Track package-lock.json Quality - Tests for routing and for markup injection from visitor input - A content guard scans every reachable answer; the list of things that must never be published lives in a git-ignored local file Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8817235310
commit
61465cc67e
40 files changed
+8625
-480
No files matched your search
@@ -0,0 +1,163 @@
|
||||
import React from 'react'
|
||||
|
||||
import {
|
||||
CERTIFICATIONS,
|
||||
EDUCATION,
|
||||
IDENTITY,
|
||||
LANGUAGES,
|
||||
ROLES,
|
||||
ROUTE,
|
||||
SKILLS,
|
||||
SUMMARY,
|
||||
TIERS,
|
||||
} from '../data/profile.mjs'
|
||||
|
||||
const Groups = ({ groups }) => (
|
||||
<div className="stack">
|
||||
{groups.map((group) => (
|
||||
<div className="group" key={group.label}>
|
||||
<p className="group__label">{group.label}</p>
|
||||
<p className="group__tags">
|
||||
{group.items.map((item, i) => (
|
||||
<React.Fragment key={item}>
|
||||
{i > 0 && ' · '}
|
||||
<span className="t">{item}</span>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
const SKILL_GROUPS = TIERS.map((tier) => ({
|
||||
label: tier.label,
|
||||
items: SKILLS.filter((s) => s.tier === tier.id && s.listed !== false).map(
|
||||
(s) => s.name
|
||||
),
|
||||
})).filter((g) => g.items.length)
|
||||
|
||||
const CREDENTIAL_GROUPS = [
|
||||
{ label: 'Education', items: EDUCATION.map((e) => e.title) },
|
||||
{
|
||||
label: 'Certifications',
|
||||
items: CERTIFICATIONS.map((c) => `${c.name} (${c.year})`),
|
||||
},
|
||||
{
|
||||
label: 'Languages',
|
||||
items: LANGUAGES.map(([name, level]) => `${name}, ${level}`),
|
||||
},
|
||||
]
|
||||
|
||||
// The same facts as the terminal, laid out as a page. It is also what search engines and
|
||||
// visitors without JavaScript get.
|
||||
const Classic = () => (
|
||||
<div className="wrap">
|
||||
<section className="hero">
|
||||
<div className="hero__text">
|
||||
<h1 className="hero__name">{IDENTITY.name}</h1>
|
||||
<p className="hero__lead">
|
||||
Data pipelines, data products and the automation that keeps them
|
||||
running.
|
||||
</p>
|
||||
<p className="hero__meta">
|
||||
<strong>
|
||||
{IDENTITY.title} at {IDENTITY.employer}
|
||||
</strong>
|
||||
, based in {IDENTITY.location}, with {IDENTITY.experience} across data
|
||||
engineering, ML infrastructure and DevOps.
|
||||
</p>
|
||||
<nav className="actions">
|
||||
<a href={`mailto:${IDENTITY.email}`}>{IDENTITY.email}</a>
|
||||
<a
|
||||
href={IDENTITY.linkedinUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
LinkedIn
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section">
|
||||
<h2 className="section__label">Profile</h2>
|
||||
<div className="prose">
|
||||
{SUMMARY.map((text) => (
|
||||
<p key={text}>{text}</p>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section">
|
||||
<h2 className="section__label">Experience</h2>
|
||||
<div className="experience">
|
||||
<p className="experience__route">
|
||||
{ROUTE.map((stop) => stop.place).join(' → ')}
|
||||
</p>
|
||||
<ol className="roles">
|
||||
{ROLES.map((role) => (
|
||||
<li
|
||||
className={`role${role.live ? ' role--live' : ''}`}
|
||||
key={role.id}
|
||||
>
|
||||
<div className="role__rail" aria-hidden="true">
|
||||
<span className="role__node" />
|
||||
</div>
|
||||
<div className="role__body">
|
||||
<p className="role__year">{role.period}</p>
|
||||
<div className="role__head">
|
||||
<h3 className="role__co">{role.company}</h3>
|
||||
<span className="role__loc">{role.place}</span>
|
||||
</div>
|
||||
<p className="role__title">{role.title}</p>
|
||||
<p className="role__desc">{role.summary}</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section">
|
||||
<h2 className="section__label">Stack</h2>
|
||||
<Groups groups={SKILL_GROUPS} />
|
||||
</section>
|
||||
|
||||
<section className="section">
|
||||
<h2 className="section__label">Credentials</h2>
|
||||
<Groups groups={CREDENTIAL_GROUPS} />
|
||||
</section>
|
||||
|
||||
<section className="section">
|
||||
<h2 className="section__label">Contact</h2>
|
||||
<div>
|
||||
<p className="contact__intro">
|
||||
Always happy to talk about data, ML and platform engineering. Email is
|
||||
the fastest way to reach me.
|
||||
</p>
|
||||
<a className="contact__email" href={`mailto:${IDENTITY.email}`}>
|
||||
{IDENTITY.email}
|
||||
</a>
|
||||
<div className="contact__links">
|
||||
<a
|
||||
href={IDENTITY.linkedinUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
LinkedIn
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer className="colophon">
|
||||
<span>
|
||||
© {new Date().getFullYear()} {IDENTITY.name}
|
||||
</span>
|
||||
<span>{IDENTITY.location}</span>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default Classic
|
||||
Reference in new issue
Block a user