Skip to content
AstroCraft Docs
On this theme

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 badgecomplete renders [Complete] in success green, in-progress renders [In Progress] in amber. The mapping is statusMeta in @js/projectCards.
  • The titlecardTitle if the entry sets one, otherwise title. The mock’s card titles are shorter than the detail H1s, which is exactly why cardTitle is optional.
  • The thumbnail, its alt text, the tech array 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:

  1. A back link to /projects/.
  2. The hero panel — an elevated pixel panel carrying the status badge, Module_ID: <moduleId>, the title as the H1, and the tagline as an intro line.
  3. 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 features array (each { lead, text } renders as a bulleted item with lead bolded). The right column holds SYS_SPECS, a label/value grid from the specs array with the values in success green, and ARC_MAP, a framed box showing a fixed diagram glyph plus your archCaption.
  4. Challenges & Solutions — a two-column panel from the challenge and solution objects, 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.

NEXT STEPBlog & RSS