mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-08 04:08:24 +00:00
a2f723ca2d
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
400 lines
14 KiB
Markdown
400 lines
14 KiB
Markdown
# Content Guide
|
|
|
|
How to add and edit content on the site. Four sections use **Content Collections**
|
|
(typed markdown/MDX/YAML); the other interest pages are hand-built `.astro` files.
|
|
|
|
---
|
|
|
|
## Projects (Professional page)
|
|
|
|
Projects render from `src/content/projects/*.md` (or `.mdx`). Each file is one
|
|
project card. The schema lives in `src/content.config.ts`.
|
|
|
|
### Add a project
|
|
|
|
Create `src/content/projects/my-project.md`:
|
|
|
|
```md
|
|
---
|
|
title: 'My Awesome Project'
|
|
role: 'Lead Developer' # optional, default: 'Developer'
|
|
tech: ['TypeScript', 'Node.js', 'SAP BTP'] # rendered as pills
|
|
summary: 'A 2-3 sentence description of what it does and your contribution.'
|
|
repo: 'https://github.com/you/repo' # optional
|
|
demo: 'https://demo.example.com' # optional
|
|
caseStudy: 'https://blog.example.com' # optional
|
|
cover: '/images/my-project.png' # optional, public path
|
|
order: 20 # lower = shown first, default 100
|
|
placeholder: false # true hides it from "real" styling
|
|
---
|
|
|
|
Optional longer writeup in markdown here. The card only shows `summary`, but you
|
|
can link to a case study for depth.
|
|
```
|
|
|
|
### Field reference
|
|
|
|
| Field | Type | Required | Notes |
|
|
|---|---|---|---|
|
|
| `title` | string | yes | Project name |
|
|
| `role` | string | no | e.g. "Lead Developer" |
|
|
| `tech` | string[] | no | Tag pills |
|
|
| `summary` | string | yes | Short card description |
|
|
| `repo` / `demo` / `caseStudy` | URL | no | Outbound links |
|
|
| `cover` | string | no | Image path in `public/` |
|
|
| `order` | number | no | Sort weight |
|
|
| `placeholder` | boolean | no | Marks it as a TODO card |
|
|
|
|
To remove the seed placeholder, delete `placeholder-project.md`. Aim for 3-6 real
|
|
entries per the spec.
|
|
|
|
---
|
|
|
|
## Experience (Professional timeline)
|
|
|
|
Experience renders from `src/content/experience/*.md`. Each file is one role on
|
|
the vertical timeline.
|
|
|
|
### Add a role
|
|
|
|
Create `src/content/experience/another-role.md`:
|
|
|
|
```md
|
|
---
|
|
company: 'Previous Co.'
|
|
title: 'Junior Developer'
|
|
period: '2016 - 2020' # free-form date label
|
|
focus: 'What you primarily did.' # optional
|
|
icon: 'briefcase' # mdi icon name for the marker
|
|
order: 20 # lower = more recent / first
|
|
---
|
|
|
|
- Bullet point of an accomplishment.
|
|
- Another bullet, ideally with a metric.
|
|
- One more, showing impact.
|
|
```
|
|
|
|
The body renders inside an expandable "Show details" panel. Use markdown lists
|
|
for bullets. Replace the bracketed placeholders in the seed `implico.md` with
|
|
real outcomes, metrics, and dates.
|
|
|
|
### Icon names
|
|
|
|
The `icon` field takes an mdi name without the `mdi:` prefix. Browse names at
|
|
[pictogrammers.com](https://pictogrammers.com/library/mdi/) or search the
|
|
installed set:
|
|
|
|
```bash
|
|
node -e "const m=require('./node_modules/@iconify-json/mdi/icons.json'); console.log(Object.keys(m.icons).filter(n=>n.includes('cloud')).join('\n'))"
|
|
```
|
|
|
|
---
|
|
|
|
## Games (Gaming page)
|
|
|
|
The Gaming page (`src/pages/interests/gaming.astro`) has two content-driven
|
|
sections that read from collections: **Now playing** and the **Gaming Hall of
|
|
Fame**. The platforms grid and favorite-genre cards are still edited inline on
|
|
the page.
|
|
|
|
### Hall of Fame — one file per game
|
|
|
|
Each game is a markdown file in `src/content/games/hall-of-fame/`. Create a new
|
|
file (e.g. `hades.md`):
|
|
|
|
```md
|
|
---
|
|
title: 'Hades'
|
|
platform: 'Steam' # free-form; drives the icon + platform pill
|
|
year: 2020 # optional, shown as a small detail
|
|
art: '/images/games/hades.jpg' # optional, public path (see fetch tool below)
|
|
artAlt: 'Hades key art' # optional, defaults to the title
|
|
icon: 'sword-cross' # optional, overrides the platform-derived icon
|
|
blurb: 'A 2-3 sentence case for why it earned a Hall of Fame slot.'
|
|
order: 15 # lower = shown first, default 100
|
|
---
|
|
```
|
|
|
|
| Field | Type | Required | Notes |
|
|
|---|---|---|---|
|
|
| `title` | string | yes | Game name |
|
|
| `platform` | string | yes | e.g. "PlayStation 5", "Steam", "Nintendo Switch" |
|
|
| `blurb` | string | yes | Why it's a favorite |
|
|
| `art` | string | no | `/images/games/<slug>.jpg` (public path) |
|
|
| `artAlt` | string | no | Alt text, defaults to title |
|
|
| `year` | number | no | Release year |
|
|
| `icon` | string | no | mdi icon override (else derived from platform) |
|
|
| `order` | number | no | Sort weight |
|
|
|
|
`platform` is matched case-insensitively to pick an icon: `playstation` →
|
|
`sony-playstation`, `switch` → `nintendo-switch`, `steam`/`pc` → `steam`,
|
|
`xbox` → `microsoft-xbox`, anything else → `gamepad-variant`. Override with
|
|
`icon` if you want something specific.
|
|
|
|
### Now playing — single list
|
|
|
|
Edit `src/content/games/now-playing.yaml`. This is the transient list of games
|
|
you're actively playing; each entry is a title + platform only:
|
|
|
|
```yaml
|
|
games:
|
|
- title: "Elden Ring"
|
|
platform: "PlayStation 5"
|
|
- title: "Balatro"
|
|
platform: "Steam"
|
|
```
|
|
|
|
If the list is empty, the page shows a TODO placeholder instead.
|
|
|
|
### Fetching box art (RAWG)
|
|
|
|
`scripts/fetch-box-art.mjs` downloads box/banner art from the
|
|
[RAWG API](https://rawg.io/apidocs) into `public/images/games/`, then prints the
|
|
public path to paste into a game file's `art:` field. RAWG covers Steam,
|
|
PlayStation, Xbox, and Nintendo in one place.
|
|
|
|
1. Get a free key at <https://rawg.io/login?forward=developer> and add it to
|
|
`.env` (copy from `.env.example`):
|
|
```
|
|
RAWG_API_KEY=your_key_here
|
|
```
|
|
2. Run (Node 20.6+ can auto-load `.env` via `--env-file`):
|
|
```bash
|
|
node --env-file=.env scripts/fetch-box-art.mjs "Elden Ring"
|
|
node --env-file=.env scripts/fetch-box-art.mjs "Hollow Knight" --platform pc
|
|
node --env-file=.env scripts/fetch-box-art.mjs --id 3328 # by RAWG id
|
|
node --env-file=.env scripts/fetch-box-art.mjs "Celeste" --out celeste.jpg
|
|
```
|
|
3. It prints something like:
|
|
```
|
|
✔ Saved: public/images/games/elden-ring.jpg
|
|
art: "/images/games/elden-ring.jpg"
|
|
```
|
|
Paste that `art:` value into the game file's frontmatter.
|
|
|
|
> RAWG serves **landscape** banner/screenshot art (not portrait Steam
|
|
> capsules), which matches the Hall of Fame cards (`aspect-[4/3]`).
|
|
|
|
---
|
|
|
|
## Board games (TTRPGs page)
|
|
|
|
The "Board games" section on the TTRPGs page reads from a JSON file and renders
|
|
an interactive shelf: a list of games on the left, and a tall image on the
|
|
right that cross-fades to a game's BGG cover when you hover its name. Clicking a
|
|
name opens the game on BoardGameGeek.
|
|
|
|
### The list — `src/data/boardgames.json`
|
|
|
|
A flat JSON array. Each entry needs a `name` and a `bggId`; `note` and `image`
|
|
are optional:
|
|
|
|
```json
|
|
[
|
|
{ "name": "Wingspan", "bggId": 266192, "note": "Engine builder" },
|
|
{ "name": "Brass: Birmingham", "bggId": 224517 }
|
|
]
|
|
```
|
|
|
|
| Field | Type | Required | Notes |
|
|
|---|---|---|---|
|
|
| `name` | string | yes | Display label + link text |
|
|
| `bggId` | number | yes | BoardGameGeek numeric id (from its URL) |
|
|
| `note` | string | no | One-line caption shown under the name |
|
|
| `image` | string | no | Cover path/URL; populated by the fetch tool |
|
|
|
|
Find a game's `bggId` in its BoardGameGeek URL:
|
|
`https://boardgamegeek.com/boardgame/<bggId>/<slug>`.
|
|
|
|
The shelf photo at rest is `/images/ttrpg-boardgame-shelf.jpg` — drop a real
|
|
photo of your shelf at that path. The image is a **progressive enhancement**:
|
|
without JS the list still works (links + shelf photo); games without an `image`
|
|
just keep the shelf photo on hover.
|
|
|
|
### Fetching covers (BoardGameGeek)
|
|
|
|
`scripts/fetch-bgg-covers.mjs` fills in the `image` field for any game missing
|
|
one, using BGG's public XML2 API (no key needed):
|
|
|
|
```bash
|
|
# Default: download covers to public/images/boardgames/<bggId>.jpg (recommended)
|
|
node scripts/fetch-bgg-covers.mjs
|
|
|
|
# Instead hotlink BGG's CDN URLs (no download, depends on their CDN)
|
|
node scripts/fetch-bgg-covers.mjs --remote
|
|
|
|
# Re-fetch even games that already have an image
|
|
node scripts/fetch-bgg-covers.mjs --force
|
|
|
|
# Slow down between BGG calls (they rate-limit; default 1200ms)
|
|
node scripts/fetch-bgg-covers.mjs --sleep 2000
|
|
```
|
|
|
|
It's idempotent — run it again any time you add games to the JSON.
|
|
|
|
---
|
|
|
|
## Media viewing history (Anime, TV, Movies, Reading pages)
|
|
|
|
Four interest pages — **Anime** (`/interests/anime`), **TV Shows**
|
|
(`/interests/tv`), **Movies** (`/interests/movies`), and **Reading**
|
|
(`/interests/reading`) — share one data pattern. Each reads a JSON file, shows
|
|
the **favorites** as poster/cover cards up top, then the **full library**
|
|
grouped by category as pills below.
|
|
|
|
The data files and their pages:
|
|
|
|
| Page | Data file |
|
|
|---|---|
|
|
| Anime | `src/data/media/anime.json` |
|
|
| TV Shows | `src/data/media/tv.json` |
|
|
| Movies | `src/data/media/movies.json` |
|
|
| Reading | `src/data/reading.json` |
|
|
|
|
### The shared item shape
|
|
|
|
A flat JSON array. Each entry has a `title` plus optional fields. The
|
|
`favorite` flag is the **single source of truth** for what appears in the top
|
|
grid — set it on the entry itself, no separate favorites list to keep in sync.
|
|
`category` drives the grouping of the full library below (items without a
|
|
category appear only in favorites, if favorited).
|
|
|
|
```json
|
|
[
|
|
{
|
|
"title": "Breaking Bad",
|
|
"favorite": true,
|
|
"blurb": "The character study of a decade."
|
|
},
|
|
{
|
|
"title": "Dark",
|
|
"category": "Streaming Originals",
|
|
"status": "Completed",
|
|
"note": "Optional short detail shown on hover."
|
|
}
|
|
]
|
|
```
|
|
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `title` | string | Display label (required) |
|
|
| `favorite` | boolean | True → rich card in the top "Favorites" grid |
|
|
| `category` | string | Grouping label for the full library, e.g. "Prestige Drama" |
|
|
| `status` | string | Pill text, e.g. "Completed", "Watching", "Caught up" |
|
|
| `year` | number | Release year; shown on movie/anime pills |
|
|
| `count` | string | Human span for a franchise, e.g. "All 4 films" |
|
|
| `blurb` | string | Long-form copy on favorite cards |
|
|
| `note` | string | Short detail on a full-list pill (hover/title attr) |
|
|
| `image` | string | Local art path; populated by the fetch tools below |
|
|
|
|
Adding an entry is just dropping another object in the right JSON file. The
|
|
page reads it via the helpers in `src/lib/media.ts` (`getFavorites`,
|
|
`getGrouped`). Categories appear in order of first occurrence, so order the
|
|
file the way you want the page grouped.
|
|
|
|
**Movies are film-level:** each entry is one film, with `category` naming the
|
|
franchise it belongs to (e.g. "The Matrix", "Marvel Cinematic Universe").
|
|
Single films with no sequel go under `category: "Standalone"`. A favorite film
|
|
carries `favorite: true` so it appears in the top grid **and** in its franchise
|
|
group.
|
|
|
|
### Fetching poster/cover art
|
|
|
|
Three idempotent scripts fill in the `image` field for **favorites only** (the
|
|
full library stays text pills), downloading to `public/images/<kind>/`:
|
|
|
|
```bash
|
|
# Anime — AniList GraphQL API (free, NO key required)
|
|
node scripts/fetch-anime-art.mjs
|
|
node scripts/fetch-anime-art.mjs --force # re-fetch existing
|
|
|
|
# Reading — Open Library Covers API (free, NO key required)
|
|
node scripts/fetch-book-covers.mjs
|
|
|
|
# Movies + TV — TMDB API (free key required: TMDB_API_KEY in .env)
|
|
node --env-file=.env scripts/fetch-tmdb-art.mjs
|
|
node --env-file=.env scripts/fetch-tmdb-art.mjs --kind movies
|
|
node --env-file=.env scripts/fetch-tmdb-art.mjs --kind tv
|
|
```
|
|
|
|
All three skip items that already have an `image` (use `--force` to re-fetch),
|
|
sleep briefly between requests, and write the local path back into the JSON.
|
|
Get a free TMDB key at <https://www.themoviedb.org/settings/api>.
|
|
|
|
Cards render the `image` when present and fall back to a kind-derived icon slab
|
|
otherwise, so partial coverage looks intentional.
|
|
|
|
---
|
|
|
|
## TCGs (Trading Card Games page)
|
|
|
|
The TCGs page (`src/pages/interests/tcgs.astro`) is hand-built, like the other
|
|
interest pages. Two parts are data-driven:
|
|
|
|
- **The games I play** and **Proxy making** are inline frontmatter arrays of
|
|
`{ icon, title, body }` — edit them directly on the page.
|
|
- **Magic: The Gathering decks** are pulled **live from Archidekt** on the
|
|
server (the site is SSR, so no CORS or API key is needed).
|
|
|
|
### Archidekt integration
|
|
|
|
`src/lib/archidekt.ts` fetches Greg's public decks from:
|
|
|
|
```
|
|
GET https://archidekt.com/api/decks/v3/?ownerUsername=<username>
|
|
```
|
|
|
|
- The username is the `ARCHIDEKT_USERNAME` export (currently `Patiun`). Change
|
|
it there to point at a different account.
|
|
- Results are cached to `node_modules/.cache/archidekt-<username>.json` for
|
|
`CACHE_TTL_MS` (default **1 hour**) so repeat visits are instant and we're
|
|
gentle on Archidekt. Tune the TTL via that export.
|
|
- Only public, non-unlisted decks are shown. If Archidekt is unreachable, the
|
|
section falls back to a "Manage on Archidekt" link — it never breaks the page.
|
|
|
|
Each deck renders as a `DeckCard` (featured card art, mana-color pips, card
|
|
count, format, last-updated) linking to its Archidekt page. To add or remove
|
|
decks, just change them on Archidekt — the page picks them up automatically
|
|
(within the cache window).
|
|
|
|
---
|
|
|
|
## Interest pages
|
|
|
|
Each interest is a hand-built page in `src/pages/interests/`. They follow the
|
|
spec's template: **Header** (title + tagline), **Overview** (narrative),
|
|
**Highlights** (card grid), **Currently** (active now), **Related** (cross-links).
|
|
|
|
To edit content, open the relevant `.astro` file and change the copy in the
|
|
frontmatter arrays or the section bodies. To add a new interest:
|
|
|
|
1. Create `src/pages/interests/my-interest.astro` (copy an existing page as a
|
|
template).
|
|
2. Use `<BaseLayout surface="interest">`, `<InterestHero>`, `<InterestCard>`, and
|
|
`<RelatedInterests>`.
|
|
3. Add a card for it in `src/pages/interests/index.astro` (the hub).
|
|
|
|
---
|
|
|
|
## Global content (nav, socials, tagline)
|
|
|
|
Edit `src/lib/site.ts`:
|
|
|
|
- `siteIdentity` — name, tagline, domain
|
|
- `navGroups` — the two-group top nav
|
|
- `socials` — footer + contact links (replace the `[Greg to fill]` handles)
|
|
|
|
---
|
|
|
|
## Finding placeholders
|
|
|
|
Every `[Greg to fill]` gap from the spec is rendered as a styled **Placeholder**
|
|
slot (dashed border + pencil badge + "TODO · label"). To find them all:
|
|
|
|
```bash
|
|
grep -rn "Placeholder label" src/
|
|
grep -rn "Greg to fill\|\[Greg" src/
|
|
```
|
|
|
|
Replace each with real content as you gather it.
|