Skip to content

Per-card shareable pages with card-art link previews, plus consistency fixes - #9

Merged
ralyodio merged 1 commit into
masterfrom
worktree-card-pages-and-consistency
Aug 17, 2026
Merged

Per-card shareable pages with card-art link previews, plus consistency fixes#9
ralyodio merged 1 commit into
masterfrom
worktree-card-pages-and-consistency

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Addresses the review feedback: shareable per-card pages first, then the consistency issues.

Per-card pages (the headline item)

Every card has its own page — /cards/<slug> for Series One, /hacking-legends/<slug> for Series Two. Card faces, full scouting report, stat bars, projects/domains, prev/next, copy-link + share-on-X + download-the-art.

Link previews use the card art. The card faces are 500×745 portrait, which X and LinkedIn refuse to render as a large image — they'd fall back to a small square crop. So each page builds a 1200×630 composite with the card on the left and the stat line on the right:

Series One Hacking Legends
/cards/linus-torvalds /hacking-legends/kevin-mitnick

All 77 OG images are prerendered at build via generateStaticParams — no runtime image work.

SEO: canonical URLs, Person JSON-LD per card, sitemap.xml (82 URLs) and robots.txt. The grids now link to the detail pages, so all 47 Series One permalinks are crawlable from /cards.

Consistency fixes

Every count is now derived from the data instead of hardcoded:

  • 47 vs 50 — the stat strip read '50'; it now reads cards.length, and gained the Series Two roster count.
  • Collector's Box "All 16 cards"All 47 cards.
  • "no artwork yet" vs 30/30 rendered — the homepage teaser now reports illustrated and locked counts from the roster. The "what ships next" list also no longer promises illustration that already shipped in Add all 30 Hacking Legends portraits #7/Cut all 30 Hacking Legends cards #8.

Source links on Hacking Legends

Hacker gains a required sources field, populated for all 30 entries. Every URL was fetched and returned 200 before being written in. They render on the card page and in the expanded roster report.

RosterCard's root changed from <button> to <article> — source links and the card permalink can't live inside a button. Expand/collapse moved to a button in the footer.

Contribution guide

It pointed at src/data/legends.ts (doesn't exist) and showed a card shape with signatureProjects/skills that matches neither series. Now documents the real pipeline: src/data/hacking.ts as the Series Two source of truth, data/roster.locked.json → generated src/data/cards.ts for Series One, and the render step that writes the front/back fields.

Not included

The "make your own contributor card from your GitHub handle" idea — deferred as suggested.

⚠️ Separate issue found while sourcing

The image-to-image enhance pass has corrupted text on several shipped Series Two fronts. The prompt tells it not to touch text; it did anyway:

  • Fabricated quotes attributed to living people — #30 Moxie Marlinspike, #10 Cliff Stoll, #11 Tsutomu Shimomura, and #19 Joanna Rutkowska (whose card quotes a line from WarGames (1983)).
  • Invented stat panels — #10, #11 and #19 have stat bars on the front, which the front template has no stat block at all; the numbers (10, 11, 92/85/88/90/95) are not the card's real stats.
  • Misspelled name — #14 renders as "ADRIAN LAMU".
  • Handle drift — #20 renders "Lcamtuf" instead of "lcamtuf".

This undercuts exactly the credibility the source links are meant to add, so it's worth a decision. All 30 approved portraits are committed under assets/portraits/hacking-legends/, so pnpm hacking render can regenerate text-accurate faces with no API calls — but that drops the premium finish on every card, which is an art-direction call rather than mine to make. Not touched in this PR.

Separately, buildBack renders the note field inside quote marks, which reads as a quotation from the subject even though hacking.ts states the field is editorial voice. Worth a look on the same pass.

🤖 Generated with Claude Code

Every card now has its own page — /cards/<slug> for Series One and
/hacking-legends/<slug> for Series Two — so a legend can be shared as a
link instead of a screenshot.

Sharing:
- Each page generates a 1200x630 opengraph-image at build time with the
  card art on the left and the stat line on the right. The card faces are
  500x745 portrait, which X and LinkedIn refuse to render large, so a
  landscape composite is what actually shows the card in a preview.
- All 77 OG images are prerendered by generateStaticParams; no runtime
  image work.

SEO:
- Canonical URLs on every page, Person JSON-LD on card pages, and
  sitemap.xml covering all 82 URLs plus robots.txt.
- The card grids now link to the detail pages, so all 47 Series One
  permalinks are crawlable from /cards.

Consistency, all now derived from the data rather than hardcoded:
- Homepage said both 47 and 50 legends. The stat strip read "50"; it now
  reads cards.length, and gained the Series Two roster count.
- Collector's Box advertised "All 16 cards" -> All 47 cards.
- Homepage said Hacking Legends had no artwork while its own page showed
  30/30 rendered. It now reports illustrated and locked counts from the
  roster. The "what ships next" list no longer promises illustration
  that already happened.

Sourcing:
- Hacker gains a required `sources` field, populated for all 30 entries.
  Every URL was fetched and returned 200 before being written in.
- Sources render on the card page and in the expanded roster report, so
  the historical claims can be checked. RosterCard is an <article> rather
  than a <button> now, because those links cannot live inside a button.

Contribution guide:
- It pointed at src/data/legends.ts, which does not exist, and showed a
  card shape with signatureProjects/skills that no longer matches either
  series. It now documents the real pipeline: src/data/hacking.ts as the
  Series Two source of truth, data/roster.locked.json for Series One with
  src/data/cards.ts generated from it, and the render step that writes
  the front/back fields.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

3 finding(s)

MEDIUM: 3

Severity Rule Location
MEDIUM js-credential-logged scripts/migrate.mjs:16
MEDIUM js-unescaped-html-sink src/app/cards/[slug]/page.tsx:60
MEDIUM js-unescaped-html-sink src/app/hacking-legends/[slug]/page.tsx:68

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit fef2684 into master Aug 17, 2026
4 checks passed
@ralyodio
ralyodio deleted the worktree-card-pages-and-consistency branch August 17, 2026 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants