mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-08 04:08:24 +00:00
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.
|