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
170 changes: 170 additions & 0 deletions src/components/TalkCard.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
---
import TagPill from "./TagPill.astro";

type TalkLinks = {
video?: string;
slides?: string;
notes?: string;
repo?: string;
};

type Props = {
year: number;
venue: string;
title: string;
topics: readonly string[];
links: TalkLinks;
};

const { year, venue, title, topics, links } = Astro.props;
---

<article
class:list={[
"group relative overflow-hidden rounded-xl border border-border/80 bg-surface/35 shadow-sm shadow-black/15",
"motion-safe:transition-[transform,box-shadow,background-color,border-color] motion-safe:duration-300 motion-safe:ease-out",
"hover:border-accent/40 hover:bg-surface/55 hover:shadow-lg hover:shadow-black/20",
"motion-safe:hover:-translate-y-1",
"p-5 md:p-6",
]}
>
<div
class="pointer-events-none absolute left-0 top-0 h-full w-1 bg-gradient-to-b from-accent via-accent/50 to-accent/10 opacity-90 motion-safe:transition-opacity group-hover:opacity-100"
aria-hidden="true"
>
</div>
<div class="relative min-w-0 pl-4 sm:pl-5">
<div class="flex items-baseline gap-3 mb-2">
<span class="text-2xl font-bold tabular-nums text-accent">{year}</span>
<span class="text-sm text-ink-muted">{venue}</span>
</div>
<h2 class="m-0">
<span
class="font-semibold tracking-tight text-ink text-lg md:text-xl leading-snug"
>
{title}
</span>
</h2>
<ul class="m-0 mt-4 flex flex-wrap list-none gap-2 p-0">
{
topics.map((t) => (
<li class="list-none">
<TagPill label={t} />
</li>
))
}
</ul>
<div
class="mt-5 flex flex-wrap items-center gap-3 border-t border-border/55 pt-4"
>
{
links.video && (
<a
href={links.video}
class="inline-flex items-center gap-1.5 text-sm text-ink-muted hover:text-accent motion-safe:transition-colors"
target="_blank"
rel="noopener noreferrer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-4 w-4"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z"
/>
</svg>
Video
</a>
)
}
{
links.slides && (
<a
href={links.slides}
class="inline-flex items-center gap-1.5 text-sm text-ink-muted hover:text-accent motion-safe:transition-colors"
target="_blank"
rel="noopener noreferrer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-4 w-4"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.75 3v11.25A2.25 2.25 0 0 0 6 16.5h2.25M3.75 3h-1.5m1.5 0h16.5m0 0h1.5m-1.5 0v11.25A2.25 2.25 0 0 1 18 16.5h-2.25m-7.5 0h7.5m-7.5 0-1 3m8.5-3 1 3m0 0 .5 1.5m-.5-1.5h-9.5m0 0-.5 1.5M9 11.25v1.5M12 9v3.75m3-6v6"
/>
</svg>
Slides
</a>
)
}
{
links.notes && (
<a
href={links.notes}
class="inline-flex items-center gap-1.5 text-sm text-ink-muted hover:text-accent motion-safe:transition-colors"
target="_blank"
rel="noopener noreferrer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-4 w-4"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"
/>
</svg>
Notes
</a>
)
}
{
links.repo && (
<a
href={links.repo}
class="inline-flex items-center gap-1.5 text-sm text-ink-muted hover:text-accent motion-safe:transition-colors"
target="_blank"
rel="noopener noreferrer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-4 w-4"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.25 6.75 22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3-4.5 16.5"
/>
</svg>
Repo
</a>
)
}
</div>
</div>
</article>
34 changes: 0 additions & 34 deletions src/content/legal/imprint.md

This file was deleted.

15 changes: 0 additions & 15 deletions src/content/legal/privacy.md

This file was deleted.

61 changes: 61 additions & 0 deletions src/pages/talks.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import TalkCard from "../components/TalkCard.astro";
import TopHeroCard from "../components/TopHeroCard.astro";
import { siteConfig } from "../site.config";

const talks = siteConfig.talks;
const title = `Talks · ${siteConfig.name}`;
const count = talks.length;
---

<BaseLayout title={title} description={siteConfig.talksLead} path="/talks">
<article class="page-stack">
<TopHeroCard labelledBy="talks-heading">
<div class="space-y-3">
<p
class="hero-stagger-1 text-sm uppercase tracking-widest m-0 bg-gradient-to-r from-accent via-accent-hover to-accent-muted bg-clip-text text-transparent"
>
Speaking
</p>
<h1
id="talks-heading"
class="hero-stagger-2 text-3xl md:text-4xl lg:text-5xl font-semibold tracking-tighter text-ink leading-[1.1] m-0"
>
Talks
</h1>
</div>
<p class="hero-stagger-3 text-xl font-normal text-ink leading-snug m-0 max-w-2xl mt-6 md:mt-7">
{siteConfig.talksLead}
</p>
<p
class="hero-stagger-4 text-sm text-ink-subtle m-0 mt-8 md:mt-10 pt-6 border-t border-border/50 max-w-2xl"
>
{count} {count === 1 ? "presentation" : "presentations"}
</p>
</TopHeroCard>

<section class="page-section" aria-label="Conference talks">
<div
class="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-4 border-b border-border pb-2 mb-6 md:mb-8"
>
<h2 class="text-xl font-semibold text-ink m-0">Presentations</h2>
</div>
<ul class="m-0 list-none space-y-5 md:space-y-6 p-0">
{
talks.map((talk) => (
<li class="list-none">
<TalkCard
year={talk.year}
venue={talk.venue}
title={talk.title}
topics={talk.topics}
links={talk.links}
/>
</li>
))
}
</ul>
</section>
</article>
</BaseLayout>
54 changes: 54 additions & 0 deletions src/site.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const siteConfig = {
calendlyUrl: "" as string | undefined,
nav: [
{ label: "Projects", href: "/projects" },
{ label: "Talks", href: "/talks" },
{ label: "Blog", href: "/blog" },
{ label: "Resume", href: "/resume" },
{ label: "Contact", href: "/contact" },
Expand Down Expand Up @@ -53,6 +54,59 @@ export const siteConfig = {
],
footerNote:
"Open to senior software engineering roles focused on platform, cloud infrastructure, Kubernetes, and IaC—plus selective consulting—where ownership and measurable impact are explicit.",
talksLead:
"Conference presentations on platform engineering, Kubernetes, and developer experience.",
talks: [
{
year: 2026,
venue: "Samvera Connect",
title: "From Whack-a-Mole to Edge Protection: Mitigating AI Scraping in Shared Repository Infrastructure",
topics: ["Cloudflare", "Kubernetes", "Terraform/OpenTofu", "DevEx", "Reliability", "Platform Engineering"],
links: {
video: "https://www.youtube.com/watch?v=H89E_KHrK9Q",
slides: "https://github.com/notch8/cloudflare-iac-example/releases/download/fedora-showcase-april-2026/n8-from-whack-a-mole-to-edge-protection.pdf",
},
},
{
year: 2023,
venue: "Samvera Developer Training",
title: "Helm Mastery: Transform Your Hyku Deployments with Confidence",
topics: ["Helm", "Kubernetes", "Platform Engineering", "Developer Education", "Deployment Automation"],
links: {
video: "https://drive.google.com/file/d/1P0yL-e2chqWQW8srMlA-jfIYFNz6cHLT/view",
slides: "https://drive.google.com/file/d/1o0FnPvy3fHfG7EzYOE_Jlrv1vtpAIjqW/view",
notes: "https://drive.google.com/file/d/1QxNnRqxv6Rq0a06fntdB4VMKPf4Y_wv3/view",
repo: "https://github.com/notch8/softserv-training-workshops-2023",
},
},
{
year: 2022,
venue: "Samvera Connect",
title: "A Tour of the New Hyrax Analytics Features",
topics: ["Analytics", "Product Education", "Hyrax", "Developer Enablement"],
links: {
video: "https://www.youtube.com/watch?v=nJAt3SWb144",
},
},
{
year: 2021,
venue: "Samvera Connect",
title: "Expanding Hyku's Versatility with Custom Themes",
topics: ["Frontend Customization", "UX", "Platform Flexibility", "Product Education"],
links: {
video: "https://www.youtube.com/live/F3G1IIZwGNc?si=P4VUQE6w_KXfWI0I&t=8504",
},
},
{
year: 2020,
venue: "Samvera Connect",
title: "A Samvera Internship: New to Coding, New to the Community",
topics: ["Onboarding", "Mentorship", "Open Source", "Community", "Developer Education"],
links: {
video: "https://www.youtube.com/watch?v=w42d5LguF4s",
},
},
],
} as const;

export type SiteConfig = typeof siteConfig;
Loading