mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-08 04:08:24 +00:00
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:
@@ -0,0 +1,118 @@
|
||||
---
|
||||
/**
|
||||
* anime.astro — Anime
|
||||
* ---------------------------------------------------------------------------
|
||||
* The primary pillar: 150+ completed series. Favorites render as poster cards
|
||||
* up top (populated by scripts/fetch-anime-art.mjs); the full library is
|
||||
* grouped by category below. Follows the canonical interest-page template.
|
||||
*/
|
||||
import BaseLayout from "@layouts/BaseLayout.astro";
|
||||
import Reveal from "@components/motion/Reveal.astro";
|
||||
import InterestHero from "@components/layout/InterestHero.astro";
|
||||
import MediaCard from "@components/cards/MediaCard.astro";
|
||||
import CategoryList from "@components/media/CategoryList.astro";
|
||||
import SectionHeader from "@components/layout/SectionHeader.astro";
|
||||
import RelatedInterests from "@components/layout/RelatedInterests.astro";
|
||||
import Placeholder from "@components/layout/Placeholder.astro";
|
||||
import { getFavorites, getGrouped, mediaKinds, getCount } from "@lib/media";
|
||||
|
||||
const meta = mediaKinds.anime;
|
||||
const favorites = getFavorites("anime");
|
||||
const groups = getGrouped("anime");
|
||||
const count = getCount("anime");
|
||||
|
||||
const related = [
|
||||
{
|
||||
href: "/interests/tv",
|
||||
title: "TV Shows",
|
||||
icon: "television-classic",
|
||||
blurb: "When I step away from animation, the live-action backlog is waiting.",
|
||||
},
|
||||
{
|
||||
href: "/interests/reading",
|
||||
title: "Reading",
|
||||
icon: "book-open-page-variant",
|
||||
blurb: "Half of these have light novels and manga I'm reading too.",
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
surface="interest"
|
||||
title={meta.title}
|
||||
description={`${count}+ completed anime series. Shonen, isekai, dark psychological. The primary pillar of my watchlist, not a side shelf.`}
|
||||
>
|
||||
<InterestHero
|
||||
icon={meta.icon}
|
||||
title={meta.title}
|
||||
tagline={meta.tagline}
|
||||
image={meta.heroImage}
|
||||
imageAlt={meta.heroImageAlt}
|
||||
/>
|
||||
|
||||
<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>{meta.overview}</p></Reveal>
|
||||
</section>
|
||||
|
||||
{/* Favorites */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal
|
||||
><SectionHeader
|
||||
kicker="The rewatch pile"
|
||||
icon="star"
|
||||
title="Favorites"
|
||||
/></Reveal
|
||||
>
|
||||
{
|
||||
favorites.length > 0 ? (
|
||||
<div class="grid gap-5 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{favorites.map((item, i) => (
|
||||
<Reveal delay={i * 70}>
|
||||
<MediaCard
|
||||
kind="anime"
|
||||
title={item.title}
|
||||
image={item.image}
|
||||
status={item.status}
|
||||
blurb={item.blurb}
|
||||
/>
|
||||
</Reveal>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Placeholder
|
||||
label="Anime favorites"
|
||||
hint='Mark items with "favorite": true in src/data/media/anime.json.'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</section>
|
||||
|
||||
{/* Full library */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal
|
||||
><SectionHeader
|
||||
kicker={`${count}+ and counting`}
|
||||
icon="format-list-bulleted"
|
||||
title="The full watch history"
|
||||
/></Reveal
|
||||
>
|
||||
<Reveal delay={80}>
|
||||
{groups.length > 0 ? (
|
||||
<CategoryList groups={groups} pillIcon="play-circle" />
|
||||
) : (
|
||||
<Placeholder
|
||||
label="Anime library"
|
||||
hint="Add categorized items to src/data/media/anime.json."
|
||||
/>
|
||||
)}
|
||||
</Reveal>
|
||||
</section>
|
||||
|
||||
<RelatedInterests items={related} />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,227 @@
|
||||
---
|
||||
/**
|
||||
* interests/index.astro — Interests Hub
|
||||
* ---------------------------------------------------------------------------
|
||||
* The "fun wing" landing page. A warm grid of cards linking into each interest
|
||||
* sub-page, using the spec's taglines. Sets surface="interest" so the whole
|
||||
* page reads as the craft-side night sky regardless of the global light/dark toggle.
|
||||
*/
|
||||
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 AnimatedBackground from "@components/hero/AnimatedBackground.tsx";
|
||||
import Motif from "@components/brand/Motif.astro";
|
||||
import ConstellationField from "@components/mystic/ConstellationField.tsx";
|
||||
|
||||
const interests = [
|
||||
{
|
||||
href: "/interests/ttrpgs",
|
||||
icon: "dice-multiple",
|
||||
title: "TTRPGs & Tabletop",
|
||||
tagline: "Have dice, will roll.",
|
||||
blurb:
|
||||
"Card Games, Board Games, and Long-running D&D and Daggerheart campaigns on Foundry VTT. Worldbuilding, NPC relationships, and player-driven narrative.",
|
||||
image: "/images/ttrpg-dice.jpg",
|
||||
alt: "Boardgame Shelf",
|
||||
},
|
||||
{
|
||||
href: "/interests/gaming",
|
||||
icon: "gamepad-variant",
|
||||
title: "Gaming",
|
||||
tagline: "We do not talk about my backlog.",
|
||||
blurb:
|
||||
"PlayStation 5, Nintendo Switch 2, and a politely out-of-hand Steam library. RPGs, strategy, and anything with a deeply layered world.",
|
||||
image: "/images/gaming-setup.jpg",
|
||||
alt: "Gaming setup desk",
|
||||
},
|
||||
{
|
||||
href: "/interests/reading",
|
||||
icon: "book-open-page-variant",
|
||||
title: "Reading",
|
||||
tagline: "Perpetually three books ahead of my to-read list.",
|
||||
blurb:
|
||||
"The Cosmere, Dune, the Pendergast saga, and Throne of Glass. Plus light novels, manga, manhwa, and webtoons.",
|
||||
image: "/images/fantasy-books.jpg",
|
||||
alt: "Fantasy bookshelf",
|
||||
},
|
||||
{
|
||||
href: "/interests/anime",
|
||||
icon: "animation-play",
|
||||
title: "Anime",
|
||||
tagline: "Subbed, dubbed, and somehow still behind.",
|
||||
blurb:
|
||||
"150+ completed series. The shonen classics, the modern hits, the isekai rabbit hole, and Frieren living rent-free in my head.",
|
||||
image: "/images/anime-hero.jpg",
|
||||
alt: "A cosplayer in a colorful anime-inspired costume at a convention",
|
||||
},
|
||||
{
|
||||
href: "/interests/tv",
|
||||
icon: "television-classic",
|
||||
title: "TV Shows",
|
||||
tagline: "My rewatch rotation has a rewatch rotation.",
|
||||
blurb:
|
||||
"200+ series across every prestige drama, NBC comedy, streaming original, and the full Marvel and Star Wars rosters.",
|
||||
image: "/images/tv-hero.jpg",
|
||||
alt: "A cozy living room with a couch facing a TV",
|
||||
},
|
||||
{
|
||||
href: "/interests/movies",
|
||||
icon: "movie-open",
|
||||
title: "Movies",
|
||||
tagline: "I stayed for the post-credits scene.",
|
||||
blurb:
|
||||
"160+ films across 17+ franchises. When I love a series I commit all the way, and The Princess Bride is always within reach.",
|
||||
image: "/images/movies-hero.jpg",
|
||||
alt: "Empty red seats in a vintage movie theater",
|
||||
},
|
||||
{
|
||||
href: "/interests/making",
|
||||
icon: "palette",
|
||||
title: "Making & Creating",
|
||||
tagline: "A magnet for super glue and nice cardboard.",
|
||||
blurb:
|
||||
"Resin dice in a pressure pot, fun and functional prints on the P1s, custom TCG proxies and decks. Passion and utility.",
|
||||
image: "/images/making-resin.jpg",
|
||||
alt: "Polyhedral dice on a dark bag",
|
||||
},
|
||||
{
|
||||
href: "/interests/tcgs",
|
||||
icon: "cards",
|
||||
title: "Trading Card Games",
|
||||
tagline:
|
||||
"Cardboard, deckbuilding, and the eternal question: one more pack?",
|
||||
blurb:
|
||||
"Magic: The Gathering Commander decks, Pokémon collecting, Riftbound, and the Gundam card game. Plus the proxies and props I make to play them.",
|
||||
image: "/images/cards.jpg",
|
||||
alt: "Trading cards and dice on a playmat",
|
||||
} /*
|
||||
{
|
||||
href: '/interests/pokemon',
|
||||
icon: 'pokeball',
|
||||
title: 'Pokémon',
|
||||
tagline: 'Metagross supremacy. This is not up for debate.',
|
||||
blurb: 'A lifelong obsession from Red and Blue to modern competitive. Steel/Psychic, beautiful design, absolutely devastating in battle.',
|
||||
image: '/images/pokemon-cards.jpg',
|
||||
alt: 'Trading card collection',
|
||||
},*/,
|
||||
];
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
surface="interest"
|
||||
title="Interests"
|
||||
description="The fun wing: tabletop RPGs, gaming, reading, resin dice and 3D printing, and Pokémon. Where Greg's passions live as stories and collections."
|
||||
>
|
||||
<!-- Hero -->
|
||||
<section class="on-sky relative overflow-hidden pb-8 pt-28 sm:pt-32">
|
||||
{
|
||||
/* Full animated Milky Way field + stars, matching the Home hero treatment. */
|
||||
}
|
||||
<AnimatedBackground client:idle className="vt-sky" />
|
||||
<div
|
||||
class="container-prose relative z-10 flex flex-col items-start gap-6 py-8"
|
||||
>
|
||||
<Reveal>
|
||||
<SectionHeader
|
||||
tone="hero"
|
||||
kicker="The personal wing"
|
||||
icon="heart"
|
||||
title="Interests"
|
||||
subtitle="Warmer, more visual, personality-driven. Not a list of hobbies but a collection of stories."
|
||||
/>
|
||||
</Reveal>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Interest grid -->
|
||||
<section class="relative overflow-hidden py-12 sm:py-16">
|
||||
<ConstellationField client:idle className="opacity-45" tint="teal" />
|
||||
<Motif
|
||||
name="tria-prima-array-seal-light"
|
||||
class="absolute -left-48 bottom-0 z-0 h-[38rem] w-[38rem]"
|
||||
style="opacity:.12;"
|
||||
/>
|
||||
<div class="container-prose relative z-10">
|
||||
<div class="grid gap-5 md:grid-cols-2 lg:grid-cols-3">
|
||||
{
|
||||
interests.map((item, i) => (
|
||||
<Reveal delay={i * 80}>
|
||||
<a
|
||||
href={item.href}
|
||||
class="card card-interactive group flex h-full flex-col gap-4 overflow-hidden p-0"
|
||||
>
|
||||
<div class="aspect-[16/10] w-full overflow-hidden">
|
||||
<img
|
||||
src={item.image}
|
||||
alt={item.alt}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
class="h-full w-full object-cover transition-transform duration-700 group-hover:scale-105"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 px-5 pb-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<span
|
||||
class="inline-flex h-12 w-12 items-center justify-center rounded-xl"
|
||||
style="background: color-mix(in srgb, var(--color-accent) 16%, transparent); color: var(--color-accent);"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Icon name={item.icon} class="h-6 w-6" />
|
||||
</span>
|
||||
<Icon
|
||||
name="arrow-right"
|
||||
class="h-5 w-5 transition-transform group-hover:translate-x-1"
|
||||
style="color: var(--color-accent);"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<h3 class="font-display" style="color: var(--color-text);">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p
|
||||
class="font-sans text-sm italic"
|
||||
style="color: var(--color-secondary);"
|
||||
>
|
||||
{item.tagline}
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
class="grow text-sm leading-relaxed"
|
||||
style="color: var(--color-text-muted);"
|
||||
>
|
||||
{item.blurb}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</Reveal>
|
||||
))
|
||||
}
|
||||
|
||||
{/* Cross-link back to professional wing */}
|
||||
<Reveal delay={interests.length * 80}>
|
||||
<a
|
||||
href="/professional"
|
||||
class="card card-interactive group flex h-full flex-col justify-center gap-2 border-dashed p-5 text-center"
|
||||
style="border-color: color-mix(in srgb, var(--color-accent) 40%, transparent);"
|
||||
>
|
||||
<Icon
|
||||
name="briefcase"
|
||||
class="mx-auto h-7 w-7"
|
||||
style="color: var(--color-accent);"
|
||||
/>
|
||||
<p
|
||||
class="font-sans font-semibold"
|
||||
style="color: var(--color-text);"
|
||||
>
|
||||
The other wing
|
||||
</p>
|
||||
<p class="text-sm" style="color: var(--color-text-muted);">
|
||||
See where curiosity meets the day job.
|
||||
</p>
|
||||
</a>
|
||||
</Reveal>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
@@ -0,0 +1,240 @@
|
||||
---
|
||||
/**
|
||||
* making.astro — Making & Creating
|
||||
* ---------------------------------------------------------------------------
|
||||
* Per-interest template with equipment card grid, a gallery placeholder, and the
|
||||
* spec's connection-to-TTRPGs callout. Related links back to TTRPGs and Gaming.
|
||||
*/
|
||||
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 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 CelestialImage from "@components/mystic/CelestialImage.astro";
|
||||
|
||||
const equipment = [
|
||||
{
|
||||
icon: "dice-multiple",
|
||||
title: "Resin Dice Making",
|
||||
body: "Custom molds, pressure pot curing, pigment experimentation. Producing dice sets for personal use and gifts.",
|
||||
},
|
||||
{
|
||||
icon: "printer-3d",
|
||||
title: "Elegoo Mars 4",
|
||||
body: "Resin 3D printer. Miniatures, terrain, and high-detail props.",
|
||||
},
|
||||
{
|
||||
icon: "printer-3d-nozzle",
|
||||
title: "Bambu Labs P1S",
|
||||
body: "FDM 3D printer. Functional prints, terrain pieces, and larger prototypes.",
|
||||
},
|
||||
{
|
||||
icon: "scissors-cutting",
|
||||
title: "Silhouette Cameo 5a",
|
||||
body: "Multi material cutting machine. Silly stickers, custom vinyl decals, and custom/proxy TCG cards.",
|
||||
},
|
||||
{
|
||||
icon: "wrench",
|
||||
title: "General Making",
|
||||
body: "Always experimenting and hoarding nice cardboard. If there is a new technique or material, I want to try it.",
|
||||
},
|
||||
];
|
||||
|
||||
const related = [
|
||||
{
|
||||
href: "/interests/ttrpgs",
|
||||
title: "TTRPGs & Tabletop",
|
||||
icon: "dice-multiple",
|
||||
blurb: "Much of what I make ends up on the virtual table.",
|
||||
},
|
||||
{
|
||||
href: "/interests/reading",
|
||||
title: "Reading & Media",
|
||||
icon: "book-open-page-variant",
|
||||
blurb: "Displays and props for the stories I love.",
|
||||
},
|
||||
{
|
||||
href: "/interests/tcgs",
|
||||
title: "Trading Card Games",
|
||||
icon: "cards",
|
||||
blurb: "Proxies, tokens, and props are the TCG side of the workshop.",
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
surface="interest"
|
||||
title="Making & Creating"
|
||||
description="Resin dice in a pressure pot, miniatures and terrain on the Elegoo Mars 4 and Bambu Labs P1S. If it involves layers and precision, I am in."
|
||||
>
|
||||
<InterestHero
|
||||
icon="palette"
|
||||
title="Making & Creating"
|
||||
tagline="A magnet for super glue and nice cardboard."
|
||||
image="/images/making-print.jpg"
|
||||
imageAlt="A 3D printer producing a detailed model"
|
||||
/>
|
||||
|
||||
<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>
|
||||
Making is part of me. Ever since I can remember, I had been dreaming
|
||||
of making inventions and things from cardboard and clay. I took things
|
||||
apart to see how they worked, and rarely got them back together. But
|
||||
now I have the tools and the skills to take ideas from my imagination
|
||||
and physically produce them. I cast resin dice using pressure pots and
|
||||
custom molds, print miniatures and props on my Elegoo Mars 4 (resin)
|
||||
and Bambu Labs P1S (FDM), and generally fill my workspace with
|
||||
projects-in-progress. I make beatiful cards as gifts and even make
|
||||
proxy mockups of trading cards to enjoy games casually.
|
||||
</p></Reveal
|
||||
>
|
||||
<Reveal>
|
||||
<p>
|
||||
There's something deeply satisfying about taking raw material through
|
||||
a process and holding the finished piece, whether that's a perfect
|
||||
polyhedral set or a hook that fits just right in that weird spot in
|
||||
the bathroom. The ability to blend my technial skills and my creative
|
||||
impulses into tangible objects is a fantasy come true my child self
|
||||
only dreamed of.
|
||||
</p></Reveal
|
||||
>
|
||||
</section>
|
||||
|
||||
{/* Equipment */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal
|
||||
><SectionHeader
|
||||
kicker="Equipment"
|
||||
icon="toolbox"
|
||||
title="What is on the bench"
|
||||
/></Reveal
|
||||
>
|
||||
<div class="grid gap-5 sm:grid-cols-2 lg:grid-cols-2">
|
||||
{
|
||||
equipment.map((e, i) => (
|
||||
<Reveal delay={i * 70}>
|
||||
<InterestCard icon={e.icon} title={e.title}>
|
||||
{e.body}
|
||||
</InterestCard>
|
||||
</Reveal>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Gallery */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal
|
||||
><SectionHeader
|
||||
kicker="Gallery"
|
||||
icon="image-multiple"
|
||||
title="Finished pieces"
|
||||
/></Reveal
|
||||
>
|
||||
<Reveal delay={80}>
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<CelestialImage
|
||||
src="/images/making-resin.jpg"
|
||||
alt="Resin dice set teal with gold leaf inclusions"
|
||||
aspect="4/3"
|
||||
/>
|
||||
<CelestialImage
|
||||
src="/images/dice_golden_moss.jpg"
|
||||
alt="Resin dice set moss like pattern with gold lettering"
|
||||
aspect="4/3"
|
||||
/>
|
||||
<CelestialImage
|
||||
src="/images/dice_scarlet.jpg"
|
||||
alt="Resin dice set scarlet with gold lettering"
|
||||
aspect="4/3"
|
||||
/>
|
||||
<CelestialImage
|
||||
src="/images/dice_galaxy.jpg"
|
||||
alt="Resin dice set dark galaxy pattern with gold powder swirls"
|
||||
aspect="4/3"
|
||||
/>
|
||||
<CelestialImage
|
||||
src="/images/minis.jpg"
|
||||
alt="3D printed miniature"
|
||||
aspect="4/3"
|
||||
/>
|
||||
<CelestialImage
|
||||
src="/images/deckboxes.jpg"
|
||||
alt="Deckboxes (Design by
|
||||
@Iceenchanted on MakerWorld)"
|
||||
aspect="4/3"
|
||||
/>
|
||||
<CelestialImage
|
||||
src="/images/proxies.jpg"
|
||||
alt="Proxy cards for Kynaios and Tiro deck"
|
||||
aspect="4/3"
|
||||
/>
|
||||
<CelestialImage
|
||||
src="/images/proxies2.jpg"
|
||||
alt="Successful Foil Proxies MTG"
|
||||
aspect="4/3"
|
||||
/>
|
||||
<CelestialImage
|
||||
src="/images/xmasgifts.jpg"
|
||||
alt="CS:GO Cases, Nightblood, Runescape Helmet, ACOTAR drawers"
|
||||
aspect="4/3"
|
||||
/>
|
||||
<CelestialImage
|
||||
src="/images/resin_rings.jpg"
|
||||
alt="Resin and painted rings"
|
||||
aspect="4/3"
|
||||
/>
|
||||
</div>
|
||||
</Reveal>
|
||||
</section>
|
||||
|
||||
{/* Connection to TTRPGs */}
|
||||
<section class="flex flex-col gap-5">
|
||||
<Reveal
|
||||
><SectionHeader
|
||||
kicker="Cross-pollination"
|
||||
icon="link-variant"
|
||||
title="Where it all connects"
|
||||
/></Reveal
|
||||
>
|
||||
<Reveal delay={80}>
|
||||
<a
|
||||
href="/interests/ttrpgs"
|
||||
class="card card-interactive group flex items-center gap-4"
|
||||
>
|
||||
<Icon
|
||||
name="dice-multiple"
|
||||
class="h-8 w-8 shrink-0"
|
||||
style="color: var(--color-accent);"
|
||||
/>
|
||||
<span class="flex flex-col gap-1">
|
||||
<span
|
||||
class="font-sans font-semibold"
|
||||
style="color: var(--color-text);"
|
||||
>Much of what I make ends up on the virtual table.</span
|
||||
>
|
||||
<span class="text-sm" style="color: var(--color-text-muted);"
|
||||
>Custom tokens, terrain, and props for Foundry VTT campaigns.</span
|
||||
>
|
||||
</span>
|
||||
<Icon
|
||||
name="arrow-right"
|
||||
class="ml-auto h-5 w-5 shrink-0 transition-transform group-hover:translate-x-1"
|
||||
style="color: var(--color-accent);"
|
||||
/>
|
||||
</a>
|
||||
</Reveal>
|
||||
</section>
|
||||
|
||||
<RelatedInterests items={related} />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
@@ -0,0 +1,119 @@
|
||||
---
|
||||
/**
|
||||
* movies.astro — Movies
|
||||
* ---------------------------------------------------------------------------
|
||||
* 160+ films across 17+ franchises. Favorite films render as poster cards;
|
||||
* the full watch history below lists every film grouped by franchise (plus a
|
||||
* Standalone group for single films). Posters populated by
|
||||
* scripts/fetch-tmdb-art.mjs.
|
||||
*/
|
||||
import BaseLayout from "@layouts/BaseLayout.astro";
|
||||
import Reveal from "@components/motion/Reveal.astro";
|
||||
import InterestHero from "@components/layout/InterestHero.astro";
|
||||
import MediaCard from "@components/cards/MediaCard.astro";
|
||||
import CategoryList from "@components/media/CategoryList.astro";
|
||||
import SectionHeader from "@components/layout/SectionHeader.astro";
|
||||
import RelatedInterests from "@components/layout/RelatedInterests.astro";
|
||||
import Placeholder from "@components/layout/Placeholder.astro";
|
||||
import { getFavorites, getGrouped, mediaKinds, getCount } from "@lib/media";
|
||||
|
||||
const meta = mediaKinds.movies;
|
||||
const favorites = getFavorites("movies");
|
||||
const groups = getGrouped("movies");
|
||||
const count = getCount("movies");
|
||||
|
||||
const related = [
|
||||
{
|
||||
href: "/interests/tv",
|
||||
title: "TV Shows",
|
||||
icon: "television-classic",
|
||||
blurb: "The MCU and Star Wars sagas continue here.",
|
||||
},
|
||||
{
|
||||
href: "/interests/anime",
|
||||
title: "Anime",
|
||||
icon: "animation-play",
|
||||
blurb: "The animated half of my watchlist.",
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
surface="interest"
|
||||
title={meta.title}
|
||||
description={`${count}+ films across 17+ franchises. When I love a series I commit all the way, and The Princess Bride is always within reach.`}
|
||||
>
|
||||
<InterestHero
|
||||
icon={meta.icon}
|
||||
title={meta.title}
|
||||
tagline={meta.tagline}
|
||||
image={meta.heroImage}
|
||||
imageAlt={meta.heroImageAlt}
|
||||
/>
|
||||
|
||||
<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>{meta.overview}</p></Reveal>
|
||||
</section>
|
||||
|
||||
{/* Favorites */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal
|
||||
><SectionHeader
|
||||
kicker="Stop on these any time"
|
||||
icon="star"
|
||||
title="Favorites"
|
||||
/></Reveal
|
||||
>
|
||||
{
|
||||
favorites.length > 0 ? (
|
||||
<div class="grid gap-5 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{favorites.map((item, i) => (
|
||||
<Reveal delay={i * 70}>
|
||||
<MediaCard
|
||||
kind="movies"
|
||||
title={item.title}
|
||||
image={item.image}
|
||||
year={item.year}
|
||||
blurb={item.blurb}
|
||||
/>
|
||||
</Reveal>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Placeholder
|
||||
label="Movie favorites"
|
||||
hint='Mark items with "favorite": true in src/data/media/movies.json.'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</section>
|
||||
|
||||
{/* Full franchise watch history */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal
|
||||
><SectionHeader
|
||||
kicker={`${count}+ films across ${groups.length} franchises`}
|
||||
icon="filmstrip"
|
||||
title="By franchise"
|
||||
/></Reveal
|
||||
>
|
||||
<Reveal delay={80}>
|
||||
{groups.length > 0 ? (
|
||||
<CategoryList groups={groups} pillIcon="movie-open" />
|
||||
) : (
|
||||
<Placeholder
|
||||
label="Movie library"
|
||||
hint="Add categorized films to src/data/media/movies.json."
|
||||
/>
|
||||
)}
|
||||
</Reveal>
|
||||
</section>
|
||||
|
||||
<RelatedInterests items={related} />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
/**
|
||||
* pokemon.astro — Pokémon
|
||||
* ---------------------------------------------------------------------------
|
||||
* Per-interest template: favorite callout (Metagross), games overview, a
|
||||
* Showdown-format roster placeholder (spec calls for standard Showdown text),
|
||||
* and related links to Gaming.
|
||||
*/
|
||||
import BaseLayout from '@layouts/BaseLayout.astro';
|
||||
import Reveal from '@components/motion/Reveal.astro';
|
||||
import InterestHero from '@components/layout/InterestHero.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';
|
||||
|
||||
const related = [
|
||||
{ href: '/interests/gaming', title: 'Gaming', icon: 'gamepad-variant', blurb: 'Nintendo console overlap, from Game Boy to Switch 2.' },
|
||||
{ href: '/interests/making', title: 'Making & Creating', icon: 'palette', blurb: 'Displays and props for collections.' },
|
||||
];
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
surface="interest"
|
||||
title="Pokémon"
|
||||
description="A lifelong obsession from Red and Blue to modern competitive. Metagross supremacy: Steel/Psychic, beautiful design, absolutely devastating in battle."
|
||||
>
|
||||
<InterestHero
|
||||
icon="pokeball"
|
||||
title="Pokémon"
|
||||
tagline="Metagross supremacy. This is not up for debate."
|
||||
image="/images/pokemon-cards.jpg"
|
||||
imageAlt="A collection of trading cards"
|
||||
/>
|
||||
|
||||
<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>Pokémon has been a lifelong obsession. From Red and Blue on the original Game Boy to modern competitive formats, I've followed the franchise through every generation. My favorite? Metagross, the iron leg Pokémon. Steel/Psychic, beautiful design, absolutely devastating in battle. No further questions.</p></Reveal>
|
||||
</section>
|
||||
|
||||
{/* Favorite callout */}
|
||||
<Reveal>
|
||||
<div
|
||||
class="card relative flex flex-col items-center gap-4 overflow-hidden text-center sm:flex-row sm:items-center sm:gap-6 sm:text-left"
|
||||
style="background: color-mix(in srgb, var(--color-accent) 8%, var(--color-surface-raised));"
|
||||
>
|
||||
<figure
|
||||
class="relative aspect-square w-full shrink-0 overflow-hidden sm:w-28"
|
||||
>
|
||||
<img src="/images/pokemon-cards.jpg" alt="Trading card collection" loading="lazy" decoding="async" class="h-full w-full object-cover" />
|
||||
</figure>
|
||||
<span
|
||||
class="inline-flex h-20 w-20 items-center justify-center rounded-2xl"
|
||||
style="background: color-mix(in srgb, var(--color-accent) 22%, transparent); color: var(--color-accent);"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Icon name="robot" class="h-10 w-10" />
|
||||
</span>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="font-eyebrow text-xs uppercase tracking-[0.22em]" style="color: var(--color-accent);">Favorite Pokémon</span>
|
||||
<h2 style="color: var(--color-text);">Metagross</h2>
|
||||
<p class="text-sm" style="color: var(--color-text-muted);">Steel / Psychic. The iron leg Pokémon.</p>
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
{/* Roster */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal><SectionHeader kicker="Battle team" icon="sword-cross" title="Roster" /></Reveal>
|
||||
<Reveal delay={80}>
|
||||
<p class="text-sm" style="color: var(--color-text-subtle);">
|
||||
Rosters use standard Pokémon Showdown text format.
|
||||
</p>
|
||||
<Placeholder label="Pokémon Showdown roster" hint="Paste your preferred roster in Showdown text format.">
|
||||
<pre
|
||||
class="mt-3 overflow-x-auto rounded-lg p-4 font-mono text-xs"
|
||||
style="background: var(--color-surface-sunken); color: var(--color-text-muted);"
|
||||
>## Pokémon
|
||||
[Greg's preferred roster in Showdown format]</pre>
|
||||
</Placeholder>
|
||||
</Reveal>
|
||||
</section>
|
||||
|
||||
{/* Games */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal><SectionHeader kicker="Across the generations" icon="gamepad-variant" title="Games" /></Reveal>
|
||||
<Reveal delay={80}>
|
||||
<Placeholder label="Mainline & spinoff titles" hint="List the mainline and spinoff titles played, if desired." />
|
||||
</Reveal>
|
||||
</section>
|
||||
|
||||
<RelatedInterests items={related} />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
@@ -0,0 +1,119 @@
|
||||
---
|
||||
/**
|
||||
* reading.astro — Reading
|
||||
* ---------------------------------------------------------------------------
|
||||
* Books, comics, light novels, webtoons, and manga/manhwa. Favorites render as
|
||||
* cover cards up top (populated by scripts/fetch-book-covers.mjs); the full
|
||||
* library is grouped by category below. Follows the canonical interest-page
|
||||
* template. The anime and movies/TV content that used to live here now has its
|
||||
* own pages under /interests/anime, /tv, and /movies.
|
||||
*/
|
||||
import BaseLayout from "@layouts/BaseLayout.astro";
|
||||
import Reveal from "@components/motion/Reveal.astro";
|
||||
import InterestHero from "@components/layout/InterestHero.astro";
|
||||
import MediaCard from "@components/cards/MediaCard.astro";
|
||||
import CategoryList from "@components/media/CategoryList.astro";
|
||||
import SectionHeader from "@components/layout/SectionHeader.astro";
|
||||
import RelatedInterests from "@components/layout/RelatedInterests.astro";
|
||||
import Placeholder from "@components/layout/Placeholder.astro";
|
||||
import { getFavorites, getGrouped, mediaKinds } from "@lib/media";
|
||||
|
||||
const meta = mediaKinds.reading;
|
||||
const favorites = getFavorites("reading");
|
||||
const groups = getGrouped("reading");
|
||||
|
||||
const related = [
|
||||
{
|
||||
href: "/interests/anime",
|
||||
title: "Anime",
|
||||
icon: "animation-play",
|
||||
blurb: "A lot of these light novels and manga have adaptations I've watched.",
|
||||
},
|
||||
{
|
||||
href: "/interests/making",
|
||||
title: "Making & Creating",
|
||||
icon: "palette",
|
||||
blurb: "Props and displays for the stories I love.",
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
surface="interest"
|
||||
title={meta.title}
|
||||
description="The Cosmere, Dune, the Pendergast saga, Throne of Glass. Plus light novels, manga, manhwa, and webtoons. If a story exists, I am probably consuming it."
|
||||
>
|
||||
<InterestHero
|
||||
icon={meta.icon}
|
||||
title={meta.title}
|
||||
tagline={meta.tagline}
|
||||
image={meta.heroImage}
|
||||
imageAlt={meta.heroImageAlt}
|
||||
/>
|
||||
|
||||
<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>{meta.overview}</p></Reveal>
|
||||
</section>
|
||||
|
||||
{/* Favorites */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal
|
||||
><SectionHeader
|
||||
kicker="On the shelf"
|
||||
icon="bookshelf"
|
||||
title="Favorites"
|
||||
/></Reveal
|
||||
>
|
||||
{
|
||||
favorites.length > 0 ? (
|
||||
<div class="grid gap-5 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{favorites.map((item, i) => (
|
||||
<Reveal delay={i * 70}>
|
||||
<MediaCard
|
||||
kind="reading"
|
||||
title={item.title}
|
||||
image={item.image}
|
||||
status={item.status}
|
||||
blurb={item.blurb}
|
||||
/>
|
||||
</Reveal>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Placeholder
|
||||
label="Reading favorites"
|
||||
hint='Mark items with "favorite": true in src/data/reading.json.'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</section>
|
||||
|
||||
{/* Full library */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal
|
||||
><SectionHeader
|
||||
kicker="The rest of the shelf"
|
||||
icon="format-list-bulleted"
|
||||
title="What I'm reading"
|
||||
/></Reveal
|
||||
>
|
||||
<Reveal delay={80}>
|
||||
{groups.length > 0 ? (
|
||||
<CategoryList groups={groups} pillIcon="book-open-page-variant" />
|
||||
) : (
|
||||
<Placeholder
|
||||
label="Reading library"
|
||||
hint="Add categorized items to src/data/reading.json."
|
||||
/>
|
||||
)}
|
||||
</Reveal>
|
||||
</section>
|
||||
|
||||
<RelatedInterests items={related} />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
@@ -0,0 +1,231 @@
|
||||
---
|
||||
/**
|
||||
* tcgs.astro — Trading Card Games
|
||||
* ---------------------------------------------------------------------------
|
||||
* Per-interest template. Sections: overview narrative, the games I play
|
||||
* (MTG, Pokémon, Riftbound, Gundam), Magic: The Gathering decks pulled live
|
||||
* from Archidekt, and proxy making. Related links to Tabletop and Making.
|
||||
*/
|
||||
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 DeckCard from "@components/cards/DeckCard.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 {
|
||||
getDecks,
|
||||
archidektSearchUrl,
|
||||
ARCHIDEKT_USERNAME,
|
||||
} from "@lib/archidekt";
|
||||
|
||||
const games = [
|
||||
{
|
||||
icon: "cards-playing",
|
||||
title: "Magic: The Gathering",
|
||||
body: "The main event. Mostly Commander (EDH), with a deckbuilding obsession that Archidekt enables far too well.",
|
||||
},
|
||||
{
|
||||
icon: "pokeball",
|
||||
title: "Pokémon TCG",
|
||||
body: "Collector energy and casual play. Beautiful art pulls, deck drafts, and a soft spot for Steel/Psychic. Mostly on Pocket TCG.",
|
||||
},
|
||||
{
|
||||
icon: "sword-cross",
|
||||
title: "Riftbound",
|
||||
body: "Riot's League of Legends TCG. A new game with a lot of potential. Interested in exploring deckbuilding and the evolving meta.",
|
||||
},
|
||||
{
|
||||
icon: "robot",
|
||||
title: "Gundam Card Game",
|
||||
body: "Bandai's Gundam TCG. A new game with a lot of potential. Interested in how some of the figures will come into play with the other table top pieces for this entry.",
|
||||
},
|
||||
];
|
||||
|
||||
const proxy = [
|
||||
{
|
||||
icon: "content-cut",
|
||||
title: "Proxy TCG cards",
|
||||
body: "Designing and cutting proxy cards on the Silhouette Cameo 5a for casual play",
|
||||
},
|
||||
{
|
||||
icon: "scissors-cutting",
|
||||
title: "Custom stickers & decals",
|
||||
body: "Vinyl cut for sleeves, playmats, and storage, and precision for life counters, clan badges, and the occasional silly sticker.",
|
||||
},
|
||||
{
|
||||
icon: "diamond-stone",
|
||||
title: "Tokens & counters",
|
||||
body: "Resin-cast tokens, +1/+1 counters, and commander damage trackers cast in the pressure pot.",
|
||||
},
|
||||
{
|
||||
icon: "printer-3d-nozzle",
|
||||
title: "3D-printed props",
|
||||
body: "Life counters, dice towers, deck boxes, and display stands off.",
|
||||
},
|
||||
];
|
||||
|
||||
const related = [
|
||||
{
|
||||
href: "/interests/ttrpgs",
|
||||
title: "TTRPGs & Tabletop",
|
||||
icon: "dice-multiple",
|
||||
blurb: "Cards and dice share the same shelf and the same table.",
|
||||
},
|
||||
{
|
||||
href: "/interests/making",
|
||||
title: "Making & Creating",
|
||||
icon: "palette",
|
||||
blurb: "Proxies, tokens, and props are the TCG side of the workshop.",
|
||||
},
|
||||
];
|
||||
|
||||
// Live Archidekt decks (server-side, cached ~1h, never throws).
|
||||
const decks = await getDecks();
|
||||
const decksHref = archidektSearchUrl();
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
surface="interest"
|
||||
title="Trading Card Games"
|
||||
description="Magic: The Gathering Commander decks, Pokémon collecting, Riftbound, and the Gundam card game. Plus the proxies, tokens, and props I make to play them."
|
||||
>
|
||||
<InterestHero
|
||||
icon="cards"
|
||||
title="Trading Card Games"
|
||||
tagline="Cardboard, deckbuilding, and the eternal question: one more pack?"
|
||||
image="/images/cards.jpg"
|
||||
imageAlt="Trading cards and dice on a playmat"
|
||||
/>
|
||||
|
||||
<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>
|
||||
Trading card games are where my collecting, deckbuilding, and making
|
||||
instincts all collide. I primarily play Magic: The Gathering, mostly
|
||||
Commander (Elder Dragon Highlander), where a hundred cards becomes a
|
||||
personal expression and the brewing never really ends. But I keep a
|
||||
hand in the Pokémon TCG, and I'm watching Riftbound and the new Gundam
|
||||
card game closely. If it has nice cardboard, pretty art, and a
|
||||
decision tree, I'm interested.
|
||||
</p></Reveal
|
||||
>
|
||||
<Reveal delay={80}
|
||||
><p>
|
||||
The hobby overlaps the rest of the fun wing: the same workshop that
|
||||
produces resin dice and 3D prints also turns out proxies, tokens, and
|
||||
deckboxes and more. And the deckbuilding itch is a close relation of
|
||||
the RPG character-builder in me. Optimizing a list tells a story about
|
||||
how I want to play and the theme I build the deck around.
|
||||
</p></Reveal
|
||||
>
|
||||
</section>
|
||||
|
||||
{/* The games I play */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal
|
||||
><SectionHeader
|
||||
kicker="Collection"
|
||||
icon="cards-outline"
|
||||
title="The games I play"
|
||||
/></Reveal
|
||||
>
|
||||
<div class="grid gap-5 sm:grid-cols-2 lg:grid-cols-2">
|
||||
{
|
||||
games.map((g, i) => (
|
||||
<Reveal delay={i * 70}>
|
||||
<InterestCard icon={g.icon} title={g.title}>
|
||||
{g.body}
|
||||
</InterestCard>
|
||||
</Reveal>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Magic: The Gathering decks (live from Archidekt) */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal>
|
||||
<div class="flex flex-wrap items-end justify-between gap-4">
|
||||
<SectionHeader kicker="Magic" icon="magic-staff" title="MTG decks" />
|
||||
<a
|
||||
href={decksHref}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center gap-1.5 text-sm transition-colors hover:underline"
|
||||
style="color: var(--color-accent);"
|
||||
>
|
||||
Manage on Archidekt
|
||||
<Icon name="open-in-new" class="h-4 w-4" />
|
||||
</a>
|
||||
</div>
|
||||
</Reveal>
|
||||
<Reveal delay={40}>
|
||||
<p class="text-sm" style="color: var(--color-text-muted);">
|
||||
Pulled live from my Archidekt account ({ARCHIDEKT_USERNAME}). Newest
|
||||
decks first; click a card to open it.
|
||||
</p>
|
||||
</Reveal>
|
||||
{
|
||||
decks.length > 0 ? (
|
||||
<div class="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{decks
|
||||
.slice()
|
||||
.sort((a, b) => +new Date(b.updatedAt) - +new Date(a.updatedAt))
|
||||
.map((d, i) => (
|
||||
<Reveal delay={Math.min(i * 50, 400)}>
|
||||
<DeckCard
|
||||
name={d.name}
|
||||
href={d.href}
|
||||
featured={d.featured}
|
||||
colors={d.colors}
|
||||
size={d.size}
|
||||
formatLabel={d.formatLabel}
|
||||
updatedAt={d.updatedAt}
|
||||
/>
|
||||
</Reveal>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Reveal delay={80}>
|
||||
<Placeholder
|
||||
label="Archidekt decks"
|
||||
hint={`Couldn't reach Archidekt right now. View the full list at ${decksHref}.`}
|
||||
/>
|
||||
</Reveal>
|
||||
)
|
||||
}
|
||||
</section>
|
||||
|
||||
{/* Proxy making */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal
|
||||
><SectionHeader
|
||||
kicker="Workshop"
|
||||
icon="auto-fix"
|
||||
title="Proxy making & printed props"
|
||||
/></Reveal
|
||||
>
|
||||
<div class="grid gap-5 sm:grid-cols-2 lg:grid-cols-2">
|
||||
{
|
||||
proxy.map((p, i) => (
|
||||
<Reveal delay={i * 70}>
|
||||
<InterestCard icon={p.icon} title={p.title}>
|
||||
{p.body}
|
||||
</InterestCard>
|
||||
</Reveal>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<RelatedInterests items={related} />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
@@ -0,0 +1,204 @@
|
||||
---
|
||||
/**
|
||||
* ttrpgs.astro — TTRPGs & Tabletop
|
||||
* ---------------------------------------------------------------------------
|
||||
* The spec's per-interest template: Header (title + tagline), Overview
|
||||
* (narrative, verbatim), Highlights (card grid, spec emojis -> mdi), Currently,
|
||||
* then the Board Games sub-section, then RelatedInterests cross-links.
|
||||
*/
|
||||
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 SectionHeader from "@components/layout/SectionHeader.astro";
|
||||
import RelatedInterests from "@components/layout/RelatedInterests.astro";
|
||||
import Placeholder from "@components/layout/Placeholder.astro";
|
||||
import BoardGameShelf from "@components/ttrpgs/BoardGameShelf.tsx";
|
||||
import boardgames from "@/data/boardgames.json";
|
||||
|
||||
const highlights = [
|
||||
{
|
||||
icon: "castle",
|
||||
title: "Dungeons & Dragons",
|
||||
body: "Lately 5th Edition. Long-running campaigns on and off Foundry VTT. Focus on rich worldbuilding, meaningful NPC relationships, and player-driven narrative.",
|
||||
},
|
||||
{
|
||||
icon: "sword-cross",
|
||||
title: "Daggerheart",
|
||||
body: "Darrington Press's dice-building RPG. Currently running a campaign and exploring how its mechanics create different story rhythms than D&D.",
|
||||
},
|
||||
{
|
||||
icon: "target",
|
||||
title: "Draw Steel",
|
||||
body: "MCDM's fantasy tactical cinematic combat game. Interested in running it when I get a chance.",
|
||||
},
|
||||
{
|
||||
icon: "shield-moon-outline",
|
||||
title: "Cypher System",
|
||||
body: "Monte Cook's narrative-focused system. Played a few one-shots and short form campaigns.",
|
||||
},
|
||||
{
|
||||
icon: "knife-military",
|
||||
title: "Blades in the Dark",
|
||||
body: "A gritty, cinematic system for heists and criminal enterprises. Interested in playing short campaigns in it one day.",
|
||||
},
|
||||
{
|
||||
icon: "weather-windy",
|
||||
title: "Avatar the Last Airbender RPG",
|
||||
body: "A narrative-focused system set in the world of Avatar. Interested in exploring bending mechanics and character-driven stories.",
|
||||
},
|
||||
{
|
||||
icon: "monitor",
|
||||
title: "Foundry VTT",
|
||||
body: "Self-hosted on the home server. Building custom modules, maps, and automation for campaigns.",
|
||||
},
|
||||
];
|
||||
|
||||
const related = [
|
||||
{
|
||||
href: "/interests/making",
|
||||
title: "Making & Creating",
|
||||
icon: "palette",
|
||||
blurb: "Dice and terrain made for campaigns.",
|
||||
},
|
||||
{
|
||||
href: "/interests/gaming",
|
||||
title: "Gaming",
|
||||
icon: "gamepad-variant",
|
||||
blurb: "Digital RPGs as another expression of the same impulse.",
|
||||
},
|
||||
{
|
||||
href: "/interests/tcgs",
|
||||
title: "Trading Card Games",
|
||||
icon: "cards",
|
||||
blurb: "Cards and dice share the same shelf — and the same table.",
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
surface="interest"
|
||||
title="TTRPGs & Tabletop"
|
||||
description="Long-running D&D and Daggerheart campaigns on Foundry VTT, the MCDM Crows playtest, and a growing board game collection."
|
||||
>
|
||||
<InterestHero
|
||||
icon="dice-multiple"
|
||||
title="TTRPGs & Tabletop"
|
||||
tagline="Minis and dice sets all sat round the table, card games commander all gathered when able. Games about dragons and spanning their wings, These are a few of my favorite things."
|
||||
image="/images/ttrpg-mini.jpg"
|
||||
imageAlt="A tabletop RPG spread with miniatures and dice"
|
||||
/>
|
||||
|
||||
<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>
|
||||
I love playing tabletop games from trading card games to role-playing
|
||||
games. I run Dungeons & Dragons and Daggerheart campaigns that span
|
||||
years of real-time play, maintaining worlds, NPCs, and narrative
|
||||
threads that my players remember years later. I host everything on
|
||||
Foundry VTT, which lets me build the kind of dynamic, visually rich
|
||||
experiences that keep players invested session after session and lets
|
||||
me flex my technical skills.
|
||||
</p></Reveal
|
||||
>
|
||||
<Reveal delay={60}
|
||||
><p>
|
||||
I also love board games, from strategy to social deduction. My
|
||||
collection is growing, and I enjoy hosting game nights with friends. I
|
||||
have a particular fondness for games that challenge players to think
|
||||
critically and creatively, and I appreciate the social aspect of
|
||||
gaming as much as the gameplay itself. Although, I do love the ability
|
||||
to play some of the engine building and deck-building games solo.
|
||||
Spirit Island and Wyrmspan have been my favorites lately, and I am
|
||||
always on the lookout for new games to add to my collection.
|
||||
</p></Reveal
|
||||
>
|
||||
<Reveal delay={100}
|
||||
><p>
|
||||
I have played a few trading card games and collected many cards. I
|
||||
have been a lifelong Pokémon and Magic: The Gathering fan, and I enjoy
|
||||
the strategy and collection aspects of both games in different ways.
|
||||
While I mostly enjoy looking at Pokemon cards, I spend most of my time
|
||||
with Magic deck building and playing Elder Dragon Highlander (or
|
||||
Commander). I recently picked up Riftbound and have had my eye on the
|
||||
Gundam TCG game as well. The mechanical differences of these games are
|
||||
fascinating, and I enjoy exploring the different strategies and
|
||||
deck-building techniques that each game offers.
|
||||
</p></Reveal
|
||||
>
|
||||
</section>
|
||||
|
||||
{/* Highlights */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal
|
||||
><SectionHeader
|
||||
kicker="Highlights"
|
||||
icon="view-grid"
|
||||
title="Systems & tools"
|
||||
/></Reveal
|
||||
>
|
||||
<div class="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{
|
||||
highlights.map((h, i) => (
|
||||
<Reveal delay={i * 70}>
|
||||
<InterestCard icon={h.icon} title={h.title}>
|
||||
{h.body}
|
||||
</InterestCard>
|
||||
</Reveal>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{
|
||||
/* Currently
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal><SectionHeader kicker="Currently" icon="calendar-clock" title="On the schedule" /></Reveal>
|
||||
<div class="grid gap-4 sm:grid-cols-3">
|
||||
<Placeholder label="Campaign 1 name" hint="D&D, [day of week]s." />
|
||||
<Placeholder label="Campaign 2 name" hint="Daggerheart, [day of week]s." />
|
||||
<Placeholder label="Crows playtest" hint="Ongoing sessions." />
|
||||
</div>
|
||||
</section>
|
||||
*/
|
||||
}
|
||||
|
||||
{/* Board games */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal
|
||||
><SectionHeader
|
||||
kicker="Beyond RPGs"
|
||||
icon="cards-playing"
|
||||
title="Board games"
|
||||
/></Reveal
|
||||
>
|
||||
<Reveal delay={80}>
|
||||
<blockquote
|
||||
class="border-l-2 pl-5 text-lg leading-relaxed"
|
||||
style="border-color: var(--color-gold); color: var(--color-text-muted);"
|
||||
>
|
||||
Beyond RPGs, I maintain a growing board and card game collection.
|
||||
Favorites span strategy, deck-building, and social deduction. Game
|
||||
night is a regular event, and yes, there's always room for one more.
|
||||
Hover a title to preview its cover; click to open it on BoardGameGeek.
|
||||
</blockquote>
|
||||
</Reveal>
|
||||
<Reveal delay={140}>
|
||||
<BoardGameShelf
|
||||
client:idle
|
||||
games={boardgames}
|
||||
shelfImage="/images/ttrpg-boardgame-shelf.jpg"
|
||||
shelfAlt="Greg's board game shelf"
|
||||
shelfCaption="The shelf"
|
||||
/>
|
||||
</Reveal>
|
||||
</section>
|
||||
|
||||
<RelatedInterests items={related} />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
/**
|
||||
* tv.astro — TV Shows
|
||||
* ---------------------------------------------------------------------------
|
||||
* 200+ completed series across prestige drama, streaming originals, workplace
|
||||
* comedy, procedural, Marvel/Star Wars universes, animation, and reality.
|
||||
* Favorites as poster cards; full library grouped by category below.
|
||||
*/
|
||||
import BaseLayout from "@layouts/BaseLayout.astro";
|
||||
import Reveal from "@components/motion/Reveal.astro";
|
||||
import InterestHero from "@components/layout/InterestHero.astro";
|
||||
import MediaCard from "@components/cards/MediaCard.astro";
|
||||
import CategoryList from "@components/media/CategoryList.astro";
|
||||
import SectionHeader from "@components/layout/SectionHeader.astro";
|
||||
import RelatedInterests from "@components/layout/RelatedInterests.astro";
|
||||
import Placeholder from "@components/layout/Placeholder.astro";
|
||||
import { getFavorites, getGrouped, mediaKinds, getCount } from "@lib/media";
|
||||
|
||||
const meta = mediaKinds.tv;
|
||||
const favorites = getFavorites("tv");
|
||||
const groups = getGrouped("tv");
|
||||
const count = getCount("tv");
|
||||
|
||||
const related = [
|
||||
{
|
||||
href: "/interests/anime",
|
||||
title: "Anime",
|
||||
icon: "animation-play",
|
||||
blurb: "The other half of my watchlist, honestly.",
|
||||
},
|
||||
{
|
||||
href: "/interests/movies",
|
||||
title: "Movies",
|
||||
icon: "movie-open",
|
||||
blurb: "The MCU, the Matrix, and everything between.",
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
surface="interest"
|
||||
title={meta.title}
|
||||
description={`${count}+ completed series across prestige drama, workplace comedy, streaming originals, and the full Marvel and Star Wars rosters.`}
|
||||
>
|
||||
<InterestHero
|
||||
icon={meta.icon}
|
||||
title={meta.title}
|
||||
tagline={meta.tagline}
|
||||
image={meta.heroImage}
|
||||
imageAlt={meta.heroImageAlt}
|
||||
/>
|
||||
|
||||
<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>{meta.overview}</p></Reveal>
|
||||
</section>
|
||||
|
||||
{/* Favorites */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal
|
||||
><SectionHeader
|
||||
kicker="On heavy rotation"
|
||||
icon="star"
|
||||
title="Favorites"
|
||||
/></Reveal
|
||||
>
|
||||
{
|
||||
favorites.length > 0 ? (
|
||||
<div class="grid gap-5 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{favorites.map((item, i) => (
|
||||
<Reveal delay={i * 70}>
|
||||
<MediaCard
|
||||
kind="tv"
|
||||
title={item.title}
|
||||
image={item.image}
|
||||
status={item.status}
|
||||
blurb={item.blurb}
|
||||
/>
|
||||
</Reveal>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Placeholder
|
||||
label="TV favorites"
|
||||
hint='Mark items with "favorite": true in src/data/media/tv.json.'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</section>
|
||||
|
||||
{/* Full library */}
|
||||
<section class="flex flex-col gap-6">
|
||||
<Reveal
|
||||
><SectionHeader
|
||||
kicker={`${count}+ series`}
|
||||
icon="format-list-bulleted"
|
||||
title="Everything I've finished"
|
||||
/></Reveal
|
||||
>
|
||||
<Reveal delay={80}>
|
||||
{groups.length > 0 ? (
|
||||
<CategoryList groups={groups} pillIcon="television-classic" />
|
||||
) : (
|
||||
<Placeholder
|
||||
label="TV library"
|
||||
hint="Add categorized items to src/data/media/tv.json."
|
||||
/>
|
||||
)}
|
||||
</Reveal>
|
||||
</section>
|
||||
|
||||
<RelatedInterests items={related} />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
Reference in New Issue
Block a user