Files
com.gregkilmer.www/docs/CONTENT.md
T
2026-07-06 21:50:41 -04:00

9.1 KiB

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:

---
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:

---
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 or search the installed set:

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):

---
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:

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 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):
    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:

[
  { "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):

# 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.


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:

grep -rn "Placeholder label" src/
grep -rn "Greg to fill\|\[Greg" src/

Replace each with real content as you gather it.