feat(landing): migrate / to astro composed from RouteCard+GridGroup

This commit is contained in:
Marcos Paulo
2026-09-05 17:24:34 +00:00
parent d234f40134
commit 66c7c0b014
+158
View File
@@ -0,0 +1,158 @@
---
// / — the landing route map. The smallest page migration in the refactor:
// six cards rendered through `RouteCard` inside a `GridGroup`, copy lifted
// from the `chapters` collection (`src/content/chapters/landing.json`,
// id `landing`).
//
// Migrated from `index.html` per plans/astro-refactor/task-12-page-landing.md.
// The page is the project's static entry point — it ships zero JavaScript.
// Wrapped in `ChapterLayout` so the chapters.css palette (the drifted
// chapter surface this page has always rendered with) loads before the
// components resolve any colour, and so the surrounding chrome (TopBar,
// SiteFooter, shared CSS) stays in lock-step with the chapter pages that
// already migrated.
import { getEntry } from 'astro:content';
import ChapterLayout from '../layouts/ChapterLayout.astro';
import ChapterHero from '../components/blocks/ChapterHero.astro';
import GridGroup from '../components/blocks/GridGroup.astro';
import RouteCard from '../components/blocks/RouteCard.astro';
const landing = await getEntry('chapters', 'landing');
if (!landing) throw new Error('landing page entry missing from chapters collection');
const base = import.meta.env.BASE_URL;
// Landing-page copy is English-only today: the existing index.html always
// rendered English, and wiring the full bilingual toggle is a separate task
// (the language-toggle island in `.agents/rules/astro.md`). When that ship
// lands, the `lang` value flows through the same pattern as the other
// chapter pages and every `.en` access below becomes a `[lang]` lookup.
const lang = 'en';
const data = landing.data;
const cards = data.cards ?? [];
---
<ChapterLayout
title="AI For Dummies — Start here"
description="AI For Dummies: a practical route map for models, agents, worktrees, skills, rules, and verification."
>
<a slot="top-previous" href={base} aria-current="page">AI FOR DUMMIES</a>
<span slot="top-center">00 / START HERE</span>
<a slot="top-next" href={`${base}skills-review/`}>review desk </a>
<ChapterHero eyebrow={data.eyebrow[lang]} tone="red">
<span slot="title" set:html={data.title[lang]} />
<p>{data.lede[lang]}</p>
<a slot="foot" class="guide-launch" href={`${base}full-guide/`}
>Take the full field guide <span></span></a
>
</ChapterHero>
<GridGroup label="Guide chapters" columns={3}>
{
cards.map((card) => (
<RouteCard
number={card.label[lang]}
title={card.title[lang]}
summary={card.copy[lang]}
href={`${base}${card.href}`}
cta={card.cta?.[lang] ?? 'Open chapter →'}
/>
))
}
</GridGroup>
<section class="landing-note">
<span>{data.threadLabel?.[lang]}</span>
<strong>{data.threadText?.[lang]}</strong>
</section>
<span slot="footer-text">{data.footer?.[lang]}</span>
</ChapterLayout>
<style>
/* Page-level styles only. Anything reusable belongs in a component.
`.guide-launch` and `.landing-note` lived in `landing.css` and were
unique to this route, so they stay inline rather than earning a block
of their own. See `.agents/rules/componentization.md`. */
.guide-launch {
display: inline-flex;
gap: 14px;
align-items: center;
margin-top: 20px;
padding: 12px 16px;
background: var(--ink);
color: var(--paper);
/* token-gap: legacy 12px fixed from landing.css; --step-0 is 11px;
no token matches 12px; owner design-system-keeper */
font:
700 12px ui-monospace,
monospace;
letter-spacing: 0.06em;
text-decoration: none;
text-transform: uppercase;
transition:
transform 0.2s ease,
background 0.2s ease;
}
.guide-launch:hover {
transform: translateY(-3px);
background: var(--blue);
}
.guide-launch span {
color: var(--gold);
/* token-gap: legacy 22px arrow from landing.css; no --step-* covers 22px; owner design-system-keeper */
font-size: 22px;
line-height: 0;
}
.landing-note {
display: grid;
grid-template-columns: 170px minmax(0, 1fr);
gap: 30px;
margin: 0 0 70px;
padding: 25px 0;
border-top: 1px solid var(--ink);
border-bottom: 1px solid var(--ink);
}
.landing-note span {
color: var(--red);
/* token-gap: legacy 11px monospace eyebrow (no DM Mono in stack);
--step-0 also resolves to 11px but with different font weight;
preserved verbatim from landing.css; owner design-system-keeper */
font:
700 11px ui-monospace,
monospace;
letter-spacing: 0.1em;
}
.landing-note strong {
/* token-gap: clamp(22px,3.2vw,42px) is specific to this strip; no
--step-* ramp covers both endpoints; owner design-system-keeper */
font-size: clamp(22px, 3.2vw, 42px);
line-height: 1.08;
letter-spacing: -0.04em;
}
/* token-gap: 520px collapse from landing.css (was bottom-bar threshold); owner design-system-keeper */
@media (max-width: 520px) {
.landing-note {
grid-template-columns: 1fr;
gap: 9px;
margin-bottom: 45px;
}
}
@media (prefers-reduced-motion: reduce) {
.guide-launch {
transition: none;
}
.guide-launch:hover {
transform: none;
}
}
</style>