feat(blocks): add GridGroup and RouteCard for landing route grid

Task 08 of the Astro refactor. The landing page's six chapter cards become
two reusable components:

- GridGroup: gap:1px hairline-separated wrapper that preserves the site's
  deliberate border-faking technique over a coloured parent background.
- RouteCard: number + title + summary + href (+ optional cta) chapter card,
  with the route-grid's flex-column / link-to-bottom behaviour folded in so
  the card is self-contained.

Both ship zero JS, use tokens only, and mirror the chapters.css / landing.css
values exactly. Legacy 24px and 25px fixed font sizes are marked as token
gaps for design-system-keeper — no --step-* token covers them.

Schema gap: src/content/config.ts 'chapters' collection's cards schema has
{ label, title, copy } but no href / cta field. Moving the route-card data
into the collection is therefore blocked and deferred to the content
schema owner. Components are ready for task 12 to consume via props.

Verified: pnpm run gate passed (15s, 42/42 assertions, no token findings).
This commit is contained in:
Marcos Paulo
2026-09-05 07:47:29 +00:00
parent 1247c48ce4
commit c00964132c
2 changed files with 140 additions and 0 deletions
+92
View File
@@ -0,0 +1,92 @@
---
// RouteCard — one chapter card in the landing page's six-up route grid.
//
// Markup mirrors `index.html`'s `<article class="card">` block:
// <b>{number}</b><h2>{title}</h2><p>{summary}</p><a href={href}>…</a>
// The `route-grid` wrapper in landing.css used to push each card's link to the
// bottom with `display:flex; flex-direction:column; margin-top:auto` on the
// anchor. That responsibility lives on the card itself now, so the card is
// self-contained — drop it into any gap:1px group and it lays out the same way.
//
// Built from `.agents/templates/components/static-block.astro` per task 08 of
// the Astro refactor. Steps on:
// - number — the "01" … "06" prefix
// - title — the chapter name (h2)
// - summary — the lede line under the title
// - href — link target for the chapter
// - cta — link text (the index page varies this: "Open chapter →",
// "Open lab →", "Open desk →"). Defaults to "Open chapter →".
interface Props {
/** Two-digit prefix rendered in the chapter surface red, e.g. "01". */
number: string;
/** Chapter name shown as the card's h2. */
title: string;
/** One-line summary under the title. */
summary: string;
/** Link target for the chapter. */
href: string;
/** Link text. Varies per destination in the current landing page. */
cta?: string;
}
const { number, title, summary, href, cta = 'Open chapter →' } = Astro.props;
---
<article class="card">
<b>{number}</b>
<h2>{title}</h2>
<p>{summary}</p>
<a href={href}>{cta}</a>
</article>
<style>
/* Mirror chapters.css `.card` + landing.css `.route-grid .card` exactly. */
.card {
display: flex;
min-width: 0;
flex-direction: column;
min-height: 220px;
padding: 28px;
background: var(--paper);
}
.card b {
color: var(--red);
/* token-gap: legacy 24px fixed from chapters.css .card b; no --step-* covers 24px; owner design-system-keeper */
font-size: 24px;
font-family: ui-monospace, monospace;
}
.card h2 {
margin: 18px 0 8px;
/* token-gap: legacy 25px fixed from chapters.css .card h2; --step-5 is clamp(24px,3vw,38px) and grows at wide viewports; owner design-system-keeper */
font-size: 25px;
letter-spacing: -0.04em;
}
.card p {
margin: 0 0 14px;
color: var(--muted);
}
.card a {
/* Push the link to the bottom — carried from landing.css
`.route-grid .card a { margin-top: auto }`. */
margin-top: auto;
color: var(--blue);
font-weight: 700;
text-decoration: none;
}
.card a:focus-visible {
outline: 3px solid var(--red);
outline-offset: 2px;
}
@media (max-width: 560px) {
.card {
min-height: 0;
}
}
</style>