Files
thiessen.io/src/components/Classic.jsx
T
dennisthiessenandClaude Sonnet 5.5 61465cc67e
My Gatsby Deployment pipeline / build-and-deploy (push) Successful in 1m4s
Rebuild the site as a terminal-style agent on Astro
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>
2026-10-25 22:45:25 +01:00

164 lines
4.5 KiB
React

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