Files
com.gregkilmer.www/src/pages/contact.astro
T
2026-07-07 21:58:37 -04:00

250 lines
8.7 KiB
Plaintext

---
/**
* contact.astro — Contact / Connect
* ---------------------------------------------------------------------------
* A single place for all ways to reach Greg, professional and social, per the
* spec's two-column layout. Also hosts the contact form (a Node backend is
* available, so this is the natural place to use it). Closes with the spec's
* warm sign-off line.
*
* Spec items Greg still needs to fill (email, Discord, etc.) appear as
* Placeholder slots.
*/
import BaseLayout from "@layouts/BaseLayout.astro";
import Reveal from "@components/motion/Reveal.astro";
import Icon from "@components/Icon.astro";
import SectionHeader from "@components/layout/SectionHeader.astro";
import Placeholder from "@components/layout/Placeholder.astro";
import ContactForm from "@components/professional/ContactForm.tsx";
import ObfuscatedEmail from "@components/ObfuscatedEmail.astro";
import { turnstileSiteKey as getTurnstileSiteKey } from "@lib/env";
// Email is rendered separately via <ObfuscatedEmail> so the plaintext address
// never appears in the HTML source. The other entries are plain links.
const emailAddress = "contact@gregkilmer.com";
const professional = [
{
icon: "linkedin",
label: "LinkedIn",
href: "https://www.linkedin.com/in/gregory-kilmer-36646bb5/",
hint: "Gregory Kilmer",
},
{
icon: "github",
label: "GitHub",
href: "https://github.com/patiun",
hint: "patiun",
},
];
const social = [
{ icon: "discord", label: "Discord", hint: "Handle or invite link." },
{
icon: "twitter",
label: "Twitter / X",
hint: "Profile link, if applicable.",
},
{
icon: "paw",
label: "Pokémon",
hint: "Showdown or Trainer name, if shared.",
},
{
icon: "cube-outline",
label: "Foundry VTT",
hint: "Server link, if public.",
},
];
// Turnstile site key. Public per Cloudflare (safe to ship to the client).
// Read via the env helper so it resolves in BOTH `astro dev` (Vite loads `.env`
// into import.meta.env) AND the Docker runtime (env_file sets process.env).
// See src/lib/env.ts for why neither source alone covers both runtimes.
// Undefined when Turnstile is not configured, which makes ContactForm skip the
// widget entirely.
const turnstileSiteKey = getTurnstileSiteKey();
---
<BaseLayout
title="Contact"
description="Reach Greg Kilmer for cloud development, SAP BTP, technical leadership, or just to debate the best Gundam series or find a fourth for game night."
noindex={true}
>
<section class="container-prose flex flex-col gap-10 pt-28 sm:pt-32">
<Reveal>
<SectionHeader
kicker="Contact"
icon="email"
title="Let's connect"
subtitle="Whether you want to talk cloud architecture, debate the best Gundam series, or need a fourth for game night, reach out."
/>
</Reveal>
</section>
<div class="container-prose grid gap-8 py-12 lg:grid-cols-[1fr_1.1fr]">
{/* Left: contact methods */}
<div class="flex flex-col gap-10">
{/* Professional */}
<Reveal>
<section class="flex flex-col gap-4">
<h2
class="font-mono text-xs uppercase tracking-[0.22em]"
style="color: var(--color-accent);"
>
Professional
</h2>
<ul class="flex flex-col gap-3">
{
/* Email: rendered via ObfuscatedEmail so the address is not in
the HTML source. JS decodes it into a mailto: on click. */
}
<li class="card flex items-center gap-3">
<span
class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg"
style="background: color-mix(in srgb, var(--color-accent) 16%, transparent); color: var(--color-accent);"
aria-hidden="true"
>
<Icon name="email-outline" class="h-5 w-5" />
</span>
<span class="flex flex-col gap-0.5">
<span
class="font-sans text-sm font-semibold"
style="color: var(--color-text);"
>
Email
</span>
<span class="text-xs" style="color: var(--color-text-subtle);">
<ObfuscatedEmail
address={emailAddress}
class="font-mono uppercase tracking-[0.1em]"
/>
</span>
</span>
</li>
{
professional.map((item) => (
<li class="card flex items-center gap-3">
<span
class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg"
style="background: color-mix(in srgb, var(--color-accent) 16%, transparent); color: var(--color-accent);"
aria-hidden="true"
>
<Icon name={item.icon} class="h-5 w-5" />
</span>
<span class="flex flex-col gap-0.5">
<span
class="font-sans text-sm font-semibold"
style="color: var(--color-text);"
>
{item.label}
</span>
<span
class="text-xs"
style="color: var(--color-text-subtle);"
>
<a
href={item.href}
target="_blank"
rel="noopener noreferrer"
>
{item.hint}
</a>
</span>
</span>
</li>
))
}
</ul>
</section>
</Reveal>
{
/* Social / personal
<Reveal delay={80}>
<section class="flex flex-col gap-4">
<h2
class="font-mono text-xs uppercase tracking-[0.22em]"
style="color: var(--color-secondary);"
>
Personal / Social
</h2>
<ul class="grid gap-3 sm:grid-cols-2">
{
social.map((item) => (
<li class="card flex items-center gap-3">
<span
class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg"
style="background: color-mix(in srgb, var(--color-secondary) 18%, transparent); color: var(--color-secondary);"
aria-hidden="true"
>
<Icon name={item.icon} class="h-5 w-5" />
</span>
<span class="flex flex-col gap-0.5">
<span
class="font-sans text-sm font-semibold"
style="color: var(--color-text);"
>
{item.label}
</span>
<span
class="text-xs"
style="color: var(--color-text-subtle);"
>
{item.hint}
</span>
</span>
</li>
))
}
</ul>
</section>
</Reveal>
*/
}
{
/* Availability note
<Reveal delay={120}>
<p
class="rounded-xl border-l-2 p-4 text-sm"
style="border-color: var(--color-accent); background: color-mix(in srgb, var(--color-accent) 8%, transparent); color: var(--color-text-muted);"
>
<strong style="color: var(--color-text);">Availability:</strong> Open to
opportunities in AI/ML, app and cloud development, SAP BTP, and technical
leadership.
</p>
</Reveal>*/
}
</div>
{/* Right: contact form */}
<Reveal delay={100}>
<section class="card flex flex-col gap-5">
<div class="flex flex-col gap-1">
<h2 class="font-sans" style="color: var(--color-text);">
Send a message
</h2>
<p class="text-sm" style="color: var(--color-text-muted);">
Prefer a form? Drop a note and it will land in my inbox.
</p>
</div>
<ContactForm client:idle turnstileSiteKey={turnstileSiteKey} />
</section>
</Reveal>
</div>
{/* Closing note */}
<section class="container-prose py-12">
<Reveal>
<blockquote
class="mx-auto max-w-2xl border-l-2 pl-5 text-center font-sans text-lg italic"
style="border-color: var(--color-accent); color: var(--color-text-muted);"
>
Whether you want to talk cloud architecture, debate the best Gundam
series, or need a fourth for game night, reach out.
</blockquote>
</Reveal>
</section>
</BaseLayout>