Projects & Case Studies
The project log is 8-BitQuest’s portfolio surface — a listing at /projects/ and a fixed-layout detail page at /projects/<slug>/ for each entry. Unlike a blog post, a project’s detail page is not free-form prose. It is a designed layout with named slots, and almost everything in it comes from structured frontmatter that the build validates. The one free-form part is the “Project Overview” narrative, which is the MDX body.
The listing
src/pages/projects/index.astro reads the collection through @js/projectData’s getSortedProjects() — non-draft entries, sorted by the order field ascending — maps each to a card with toProjectCard, and hands the cards to the shared Global/CardGrid. The page composes two sections: Project/ProjectsHero (the “Project Log” header) and the grid (“All Systems”).
The same toProjectCard mapping feeds the home page’s Featured Projects section, so the two grids can never show a project differently. Each card carries:
- A status badge —
completerenders[Complete]in success green,in-progressrenders[In Progress]in amber. The mapping isstatusMetain@js/projectCards. - The title —
cardTitleif the entry sets one, otherwisetitle. The mock’s card titles are shorter than the detail H1s, which is exactly whycardTitleis optional. - The thumbnail, its alt text, the
techarray as flat tag pills, and a “View →” CTA.
Because the card’s href and the detail route both derive from entry.id, the grid link and the generated page stay in lockstep.
The detail page
src/pages/projects/[slug].astro emits one static page per entry via getStaticPaths, and composes Project/ProjectArticle.astro with the rendered MDX body in its default slot. The article lays out, top to bottom:
- A back link to
/projects/. - The hero panel — an elevated pixel panel carrying the status badge,
Module_ID: <moduleId>, thetitleas the H1, and thetaglineas an intro line. - A two-thirds / one-third content split. The left column holds the Project Overview (the rendered MDX body) and the System Features list, built from the
featuresarray (each{ lead, text }renders as a bulleted item withleadbolded). The right column holds SYS_SPECS, a label/value grid from thespecsarray with the values in success green, and ARC_MAP, a framed box showing a fixed diagram glyph plus yourarchCaption. - Challenges & Solutions — a two-column panel from the
challengeandsolutionobjects, each a{ title, body }pair. The challenge title reads in the theme’s pink, the solution title in success green.
The whole page is built from ui/pixel-panel, ui/badge (the pixel variant), the pixel-icon registry and the shared heading classes — tokens only, so the light theme flips for free.
What a project entry can contain
Every field is required unless the schema marks it optional. A complete entry:
---
title: "Realtime Chat Engine"
cardTitle: "Retro Chat" # optional — the shorter listing title
description: "A websocket chat with a pixel-art UI."
tagline: "Low-latency messaging with an 8-bit soul."
status: "complete" # complete | in-progress
moduleId: "#01_CHAT"
order: 1
thumbnail: "./thumb.png"
thumbnailAlt: "Retro chat interface screenshot"
tech: ["TypeScript", "WebSockets", "Astro"]
specs:
- { label: "Stack", value: "Node + WS" }
- { label: "Latency", value: "< 40ms" }
features:
- { lead: "Presence", text: "Live typing indicators and online counts." }
- { lead: "History", text: "Server-persisted message log." }
archCaption: "[Packet Switching Engine]"
challenge: { title: "Backpressure", body: "…" }
solution: { title: "Ring Buffer", body: "…" }
---
The free-form Project Overview prose goes here, as MDX.
What it cannot contain
The detail layout has no slot for anything the schema does not name. There is no gallery, no video embed field, no chapter list — the structured slots are specs, features, challenge, solution, and the free-form body. If a project needs a section the layout does not provide, add it to ProjectArticle.astro and to the schema in src/content.config.ts together; a field in one without the other either fails to validate or never renders.
Draft and ordering
Set draft: true to keep an entry out of the build — getSortedProjects() filters drafts, so a draft never reaches the listing or gets a route. order is the listing sort key, ascending, and it is the only thing that decides position; there is no date on a project. Renumbering is a frontmatter edit, no code change.