Start the rebuild: note schema, vault kit and data migration

Phase 1 of the functional design plan.

- Move the old site into legacy/, unchanged, for reference. It still
  builds from there until cutover.
- Add packages/schema: the one definition of all 20 note types, with
  field titles and descriptions, note and vault checks, and JSON Schema
  export so later editing tools and the MCP server share it.
- Add vault-kit: Obsidian templates generated from the schema, and the
  Site guide for 24.01_PersonalWebsite.
- Add tools/migrate: turns the legacy JSON, collections, store data and
  page text into 545 Site folder notes and 84 images, and checks the
  result with the schema.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
This commit is contained in:
Claude
2026-10-11 18:40:55 +00:00
parent 4ded4b277b
commit a2f723ca2d
324 changed files with 11538 additions and 7556 deletions
+281
View File
@@ -0,0 +1,281 @@
---
/**
* gaming.astro — Gaming
* ---------------------------------------------------------------------------
* Per-interest template: header + tagline, verbatim overview, platforms card
* grid, favorite genres, currently playing placeholder, and related interests.
*/
import BaseLayout from "@layouts/BaseLayout.astro";
import Reveal from "@components/motion/Reveal.astro";
import InterestHero from "@components/layout/InterestHero.astro";
import InterestCard from "@components/cards/InterestCard.astro";
import GameCard from "@components/cards/GameCard.astro";
import SectionHeader from "@components/layout/SectionHeader.astro";
import RelatedInterests from "@components/layout/RelatedInterests.astro";
import Placeholder from "@components/layout/Placeholder.astro";
import Icon from "@components/Icon.astro";
import { getCollection, getEntry } from "astro:content";
import { platformToIcon } from "@lib/platforms";
const platforms = [
{
icon: "sony-playstation",
title: "PlayStation 5",
body: "Mainly for single player and exclusives.",
},
{
icon: "nintendo-switch",
title: "Nintendo Switch / Switch 2",
body: "Handheld and docked. Pokemon, Mario, and many other Nintendo and indie gems.",
},
{
icon: "steam",
title: "Steam",
body: "The primary game library. Strategy, RPGs, Adventure, oh my! And an ever-growing backlog.",
},
];
/**
* Favorite genres. Each is a small card with an mdi icon and a one-line blurb
* on what Greg loves about that genre. Page-local; edit freely.
*/
const genres = [
{
icon: "sword-cross",
name: "RPGs (Western & JRPG)",
blurb:
"Character progression and stories I can live in for 100+ hours and then turn around and do it again.",
},
{
icon: "pokeball",
name: "Creature Collecting",
blurb: "If I can catch, train, or befriend a roster, I'm sold.",
},
{
icon: "chess-king",
name: "Strategy & Tactics",
blurb:
"Turn-based puzzles and tactical challengeswhere the right plan is oh so rewarding.",
},
{
icon: "hammer",
name: "Building & Crafting",
blurb:
"Give me a grid, resources, and a goal, and watch the evening vanish.",
},
{
icon: "factory",
name: "Automation",
blurb: "Optimizing a production line until its perfect is my zen.",
},
{
icon: "compass-outline",
name: "Open-world Exploration",
blurb:
"The map IS the point. I will try to climb every mountain and I will check every waterfall for treasure.",
},
{
icon: "star-four-points",
name: "Indie Darlings",
blurb:
"The small, weird, passionate projects that punch far above their size.",
},
];
// Gaming Hall of Fame — one file per game in src/content/games/hall-of-fame/.
const hallOfFame = (await getCollection("games")).sort(
(a, b) => a.data.order - b.data.order,
);
// Currently playing — single list in src/content/games/now-playing.yaml.
const nowPlayingEntry = await getEntry("nowPlaying", "now-playing");
const nowPlaying = nowPlayingEntry?.data.games ?? [];
const related = [
/*{
href: "/interests/pokemon",
title: "Pokémon",
icon: "pokeball",
blurb: "Nintendo console overlap and a lifelong obsession.",
},*/
{
href: "/interests/ttrpgs",
title: "TTRPGs & Tabletop",
icon: "dice-multiple",
blurb: "Digital RPGs as another expression of the same impulse.",
},
];
---
<BaseLayout
surface="interest"
title="Gaming"
description="PlayStation 5, Nintendo Switch 2, and a Steam library that has gotten politely out of hand. RPGs, strategy, and deeply layered worlds."
>
<InterestHero
icon="gamepad-variant"
title="Gaming"
tagline="Walking in another's shoes, escaping reality, or obsessively completing collections, one game at a time."
image="/images/gaming-setup.jpg"
imageAlt="A gaming setup with controller and console"
/>
<div class="container-prose flex flex-col gap-20 py-12 sm:py-16">
{/* Overview */}
<section
class="flex flex-col gap-5 text-lg leading-relaxed"
style="color: var(--color-text-muted);"
>
<Reveal
><p>
Gaming has always been a passion in my life. I span console
generations from the Sega Genesis to the modern era. I play mainly
with a PlayStation 5, Nintendo Switch 2, and a Steam library that has
gotten politely out of hand. I'm particularly drawn to RPGs, strategy
games, and anything with a creature to tame or a deeply layered world
to explore. But a good game loop will get me sucked in harder than the
sunk cost fallacy of a League of Legends streak. I love to play with
friends, but have found myself leaning more towards single-player
experiences. Someone should have told younger me that scheduling would
be the final boss!
</p></Reveal
>
</section>
{/* Platforms */}
<section class="flex flex-col gap-6">
<Reveal
><SectionHeader
kicker="Platforms"
icon="gamepad-variant"
title="Where I play"
/></Reveal
>
<div class="grid gap-5 sm:grid-cols-3">
{
platforms.map((p, i) => (
<Reveal delay={i * 80}>
<InterestCard icon={p.icon} title={p.title}>
{p.body}
</InterestCard>
</Reveal>
))
}
</div>
</section>
{/* Genres */}
<section class="flex flex-col gap-5">
<Reveal
><SectionHeader
kicker="Tastes"
icon="shape"
title="Favorite genres"
/></Reveal
>
<Reveal delay={80}>
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{
genres.map((g, i) => (
<Reveal delay={i * 60}>
<article class="card card-interactive flex h-full flex-col gap-2 p-4">
<div class="flex items-center gap-2.5">
<span
class="inline-flex h-9 w-9 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={g.icon} class="h-5 w-5" />
</span>
<h3
class="font-display text-base"
style="color: var(--color-text);"
>
{g.name}
</h3>
</div>
<p
class="text-sm leading-relaxed"
style="color: var(--color-text-muted);"
>
{g.blurb}
</p>
</article>
</Reveal>
))
}
</div>
</Reveal>
</section>
{/* Currently */}
<section class="flex flex-col gap-6">
<Reveal
><SectionHeader
kicker="Currently"
icon="calendar-clock"
title="Now playing"
/></Reveal
>
{
nowPlaying.length > 0 ? (
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{nowPlaying.map((g, i) => (
<Reveal delay={i * 60}>
<InterestCard
icon={platformToIcon(g.platform)}
title={g.title}
currently
>
{g.platform}
</InterestCard>
</Reveal>
))}
</div>
) : (
<Placeholder
label="Currently playing"
hint="Add games to src/content/games/now-playing.yaml."
/>
)
}
</section>
{/* Gaming Hall of Fame */}
<section class="flex flex-col gap-6">
<Reveal
><SectionHeader
kicker="Hall of Fame"
icon="trophy"
title="All-time favorites"
/></Reveal
>
{
hallOfFame.length > 0 ? (
<div class="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
{hallOfFame.map((g, i) => (
<Reveal delay={i * 80}>
<GameCard
title={g.data.title}
platform={g.data.platform}
blurb={g.data.blurb}
art={g.data.art}
artAlt={g.data.artAlt}
year={g.data.year}
icon={g.data.icon}
/>
</Reveal>
))}
</div>
) : (
<Placeholder
label="Gaming Hall of Fame"
hint="Add games to src/content/games/hall-of-fame/."
/>
)
}
</section>
<RelatedInterests items={related} />
</div>
</BaseLayout>