mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-08 04:08:24 +00:00
Initial Commit
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
---
|
||||
/**
|
||||
* Footer.astro
|
||||
* ---------------------------------------------------------------------------
|
||||
* Minimal footer: social links (mdi icons), copyright, and the spec microcopy
|
||||
* "Built with intention. Powered by caffeine." No emojis per spec.
|
||||
*/
|
||||
import Icon from '@components/Icon.astro';
|
||||
import { socials, footerMicrocopy, siteIdentity } from '@lib/site';
|
||||
const year = new Date().getFullYear();
|
||||
const socialList = Object.values(socials);
|
||||
---
|
||||
|
||||
<footer
|
||||
class="border-t"
|
||||
style="border-color: var(--color-border); background: var(--color-surface-sunken);"
|
||||
>
|
||||
<div class="container-prose flex flex-col items-center gap-6 py-10 text-center sm:flex-row sm:justify-between sm:text-left">
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="font-sans text-sm font-semibold" style="color: var(--color-text);">
|
||||
{siteIdentity.name}
|
||||
</p>
|
||||
<p class="font-mono text-xs italic" style="color: var(--color-text-subtle);">
|
||||
{footerMicrocopy}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ul class="flex items-center gap-2">
|
||||
{socialList.map((s) => (
|
||||
<li>
|
||||
<a
|
||||
href={s.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={s.label}
|
||||
title={s.label}
|
||||
class="inline-flex h-10 w-10 items-center justify-center rounded-lg border transition-colors hover:bg-[color-mix(in_srgb,var(--color-accent)_15%,transparent)]"
|
||||
style="color: var(--color-text-muted); border-color: var(--color-border);"
|
||||
>
|
||||
<Icon name={s.icon} class="h-5 w-5" />
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="container-prose pb-8 text-center text-xs" style="color: var(--color-text-subtle);">
|
||||
© {year} {siteIdentity.name}. All rights reserved.
|
||||
</div>
|
||||
</footer>
|
||||
@@ -0,0 +1,104 @@
|
||||
---
|
||||
/**
|
||||
* InterestHero.astro
|
||||
* ---------------------------------------------------------------------------
|
||||
* Shared header for every interest sub-page, implementing the spec's
|
||||
* per-interest template (Header: title + tagline; Overview: narrative). Uses the
|
||||
* warm "interest" surface tone via InterestLayout.
|
||||
*
|
||||
* The decorative icon and the tagline (in mono, as a pull quote) give each page
|
||||
* a consistent rhythm while the page supplies its own title + overview copy.
|
||||
*
|
||||
* An optional `image` prop renders a wide celestial-framed banner behind / above
|
||||
* the heading so each interest page leads with a vibe-matched photo. A faint
|
||||
* starfield + alchemical accent reinforces the esoteric mood.
|
||||
*/
|
||||
import Icon from '@components/Icon.astro';
|
||||
import Reveal from '@components/motion/Reveal.astro';
|
||||
import AnimatedBackground from '@components/hero/AnimatedBackground.tsx';
|
||||
import StarBurst from '@components/mystic/StarBurst.astro';
|
||||
|
||||
interface Props {
|
||||
/** mdi icon used as the decorative mark. */
|
||||
icon: string;
|
||||
title: string;
|
||||
/** The italicized pull-quote tagline from the spec. */
|
||||
tagline: string;
|
||||
/** Back-to-hub breadcrumb label. */
|
||||
eyebrow?: string;
|
||||
/** Optional hero banner image (vibe-matched stock; swap for real photos). */
|
||||
image?: string;
|
||||
imageAlt?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
icon,
|
||||
title,
|
||||
tagline,
|
||||
eyebrow = 'Interests',
|
||||
image,
|
||||
imageAlt = '',
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<section class="relative overflow-hidden pt-28 sm:pt-32">
|
||||
<!-- Calmer background: gradient only, no particles on interest pages. -->
|
||||
<AnimatedBackground client:idle particles={false} />
|
||||
|
||||
<div class="container-prose relative z-10 flex flex-col items-start gap-6 py-12">
|
||||
<Reveal>
|
||||
<a
|
||||
href="/interests"
|
||||
class="inline-flex items-center gap-1.5 font-mono text-xs uppercase tracking-[0.18em] transition-colors"
|
||||
style="color: var(--color-text-subtle);"
|
||||
>
|
||||
<Icon name="arrow-left" class="h-4 w-4" />
|
||||
{eyebrow}
|
||||
</a>
|
||||
</Reveal>
|
||||
|
||||
{image && (
|
||||
<Reveal delay={40} class="w-full">
|
||||
<figure
|
||||
class="relative w-full overflow-hidden rounded-2xl border"
|
||||
style="border-color: color-mix(in srgb, var(--color-accent) 40%, transparent);"
|
||||
>
|
||||
<img
|
||||
src={image}
|
||||
alt={imageAlt}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
class="aspect-[21/9] w-full object-cover"
|
||||
/>
|
||||
<StarBurst
|
||||
class="pointer-events-none absolute left-3 top-3 h-5 w-5 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-accent)_60%,transparent)]"
|
||||
style="color: var(--color-accent);"
|
||||
/>
|
||||
</figure>
|
||||
</Reveal>
|
||||
)}
|
||||
|
||||
<Reveal delay={60}>
|
||||
<span
|
||||
class="inline-flex h-16 w-16 items-center justify-center rounded-2xl"
|
||||
style="background: color-mix(in srgb, var(--color-accent) 18%, transparent); color: var(--color-accent);"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Icon name={icon} class="h-8 w-8" />
|
||||
</span>
|
||||
</Reveal>
|
||||
|
||||
<Reveal delay={120}>
|
||||
<h1 class="text-balance">{title}</h1>
|
||||
</Reveal>
|
||||
|
||||
<Reveal delay={160}>
|
||||
<p
|
||||
class="max-w-2xl border-l-2 pl-5 font-sans text-lg italic sm:text-xl"
|
||||
style="border-color: var(--color-accent); color: var(--color-text);"
|
||||
>
|
||||
{tagline}
|
||||
</p>
|
||||
</Reveal>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
/**
|
||||
* Placeholder.astro
|
||||
* ---------------------------------------------------------------------------
|
||||
* Visual marker for content Greg still needs to provide (the spec's many
|
||||
* "[Greg to fill]" items). Renders a dashed slot with a small mdi pencil badge
|
||||
* and a "TODO" label so the structure is visible and demoable, but it's obvious
|
||||
* what needs swapping. The chosen content approach is "verbatim spec copy, leave
|
||||
* gaps", so these mark every gap rather than inventing fake content.
|
||||
*
|
||||
* Usage:
|
||||
* <Placeholder label="Project name" hint="Add 3-6 portfolio entries" />
|
||||
* <Placeholder label="Email"><p>Inline hint...</p></Placeholder>
|
||||
*/
|
||||
import Icon from '@components/Icon.astro';
|
||||
|
||||
interface Props {
|
||||
/** Short name of what's missing, e.g. "Email". */
|
||||
label: string;
|
||||
/** Optional longer hint shown as muted text. */
|
||||
hint?: string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { label, hint, class: className = '' } = Astro.props;
|
||||
---
|
||||
|
||||
<div
|
||||
class:list={[
|
||||
'flex items-start gap-3 rounded-xl border border-dashed p-4',
|
||||
className,
|
||||
]}
|
||||
style="border-color: color-mix(in srgb, var(--color-accent) 45%, transparent); background: color-mix(in srgb, var(--color-accent) 6%, transparent);"
|
||||
>
|
||||
<span
|
||||
class="mt-0.5 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md"
|
||||
style="background: var(--color-accent); color: var(--color-accent-contrast);"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Icon name="pencil" class="h-4 w-4" />
|
||||
</span>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="font-mono text-[0.65rem] font-bold uppercase tracking-[0.18em]" style="color: var(--color-accent);">
|
||||
TODO · {label}
|
||||
</span>
|
||||
{hint && <p class="text-sm" style="color: var(--color-text-muted);">{hint}</p>}
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
/**
|
||||
* RelatedInterests.astro
|
||||
* ---------------------------------------------------------------------------
|
||||
* The "You might also like..." cross-link block that ends each interest page,
|
||||
* per the spec's Interests Page Flow. Pass an array of related slugs/titles and
|
||||
* this renders attractive teaser cards.
|
||||
*/
|
||||
import Icon from '@components/Icon.astro';
|
||||
|
||||
export interface RelatedLink {
|
||||
href: string;
|
||||
title: string;
|
||||
blurb: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
items: RelatedLink[];
|
||||
}
|
||||
|
||||
const { items } = Astro.props;
|
||||
---
|
||||
|
||||
<section class="mt-16">
|
||||
<h3 class="mb-5 font-mono text-xs uppercase tracking-[0.22em]" style="color: var(--color-accent);">
|
||||
You might also like
|
||||
</h3>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
{items.map((item) => (
|
||||
<a href={item.href} class="card card-interactive group flex items-start gap-4">
|
||||
<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-1">
|
||||
<span class="flex items-center gap-1 font-sans font-semibold" style="color: var(--color-text);">
|
||||
{item.title}
|
||||
<Icon name="arrow-right" class="h-4 w-4 transition-transform group-hover:translate-x-1" style="color: var(--color-accent);" />
|
||||
</span>
|
||||
<span class="text-sm" style="color: var(--color-text-muted);">{item.blurb}</span>
|
||||
</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
/**
|
||||
* SectionHeader.astro
|
||||
* ---------------------------------------------------------------------------
|
||||
* Reusable section title block: a mono "kicker" (e.g. "02"), an h2 heading, and
|
||||
* an optional supporting line. Keeps section openers visually consistent.
|
||||
*
|
||||
* The kicker is rendered as a monospace number/label per the spec's "Accent/
|
||||
* quotes: a monospace or display font sparingly for section numbers" guidance.
|
||||
*/
|
||||
import Icon from '@components/Icon.astro';
|
||||
|
||||
interface Props {
|
||||
/** Monospace kicker, e.g. "02" or "Experience". */
|
||||
kicker?: string;
|
||||
title: string;
|
||||
/** Optional subtitle shown under the title. */
|
||||
subtitle?: string;
|
||||
/** Optional mdi icon shown beside the kicker. */
|
||||
icon?: string;
|
||||
class?: string;
|
||||
align?: 'left' | 'center';
|
||||
}
|
||||
|
||||
const { kicker, title, subtitle, icon, class: className = '', align = 'left' } = Astro.props;
|
||||
---
|
||||
|
||||
<div
|
||||
class:list={[
|
||||
'flex flex-col gap-3',
|
||||
align === 'center' && 'items-center text-center',
|
||||
className,
|
||||
]}
|
||||
>
|
||||
{kicker && (
|
||||
<span
|
||||
class="inline-flex items-center gap-2 font-mono text-xs uppercase tracking-[0.22em]"
|
||||
style="color: var(--color-accent);"
|
||||
>
|
||||
{icon && <Icon name={icon} class="h-4 w-4" />}
|
||||
{kicker}
|
||||
</span>
|
||||
)}
|
||||
<h2>{title}</h2>
|
||||
{subtitle && (
|
||||
<p class="max-w-2xl text-base sm:text-lg" style="color: var(--color-text-muted);">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
/**
|
||||
* SplitTransition.astro
|
||||
* ---------------------------------------------------------------------------
|
||||
* The "fork" used at the end of the About page (and reusable elsewhere) that
|
||||
* reinforces the spec's "two wings of the same house" idea: two side-by-side
|
||||
* paths inviting the visitor into Professional or Interests.
|
||||
*
|
||||
* Left -> Professional (polished tone)
|
||||
* Right -> Interests (warm tone)
|
||||
*/
|
||||
import Icon from '@components/Icon.astro';
|
||||
|
||||
interface Props {
|
||||
/** Optional override copy for each side. Defaults to spec phrasing. */
|
||||
leftHref?: string;
|
||||
rightHref?: string;
|
||||
leftText?: string;
|
||||
rightText?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
leftHref = '/professional',
|
||||
rightHref = '/interests',
|
||||
leftText = 'See where I apply that curiosity professionally',
|
||||
rightText = 'See where it takes me outside of work',
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<a
|
||||
href={leftHref}
|
||||
class="card card-interactive group flex items-center justify-between gap-4"
|
||||
>
|
||||
<span class="flex flex-col gap-1">
|
||||
<span class="font-mono text-xs uppercase tracking-[0.18em]" style="color: var(--color-text-subtle);">
|
||||
The professional wing
|
||||
</span>
|
||||
<span class="font-sans text-lg font-semibold" style="color: var(--color-text);">
|
||||
{leftText}
|
||||
</span>
|
||||
</span>
|
||||
<Icon name="arrow-right" class="h-6 w-6 shrink-0 transition-transform group-hover:translate-x-1" style="color: var(--color-accent);" />
|
||||
</a>
|
||||
|
||||
<a
|
||||
href={rightHref}
|
||||
class="card card-interactive group flex items-center justify-between gap-4"
|
||||
>
|
||||
<span class="flex flex-col gap-1">
|
||||
<span class="font-mono text-xs uppercase tracking-[0.18em]" style="color: var(--color-text-subtle);">
|
||||
The personal wing
|
||||
</span>
|
||||
<span class="font-sans text-lg font-semibold" style="color: var(--color-text);">
|
||||
{rightText}
|
||||
</span>
|
||||
</span>
|
||||
<Icon name="arrow-right" class="h-6 w-6 shrink-0 transition-transform group-hover:translate-x-1" style="color: var(--color-accent);" />
|
||||
</a>
|
||||
</div>
|
||||
Reference in New Issue
Block a user