Files
crashtestdev/src/layouts/BaseLayout.astro
T
Ken 4455a5ed01 feat: create BaseLayout component with global styles and skip navigation
- Created src/layouts/BaseLayout.astro as the site's base layout component
- Imports tokens.css and fonts.css for design system and typography
- Accepts Props {title: string, description?: string}
- Computes page title (appends ' — Crash Test Dev' unless title is already 'Crash Test Dev')
- Reads import.meta.env.BASE_URL for RSS link generation
- HTML structure: doctype, html lang=en, head with charset/viewport/description meta, computed title, RSS link alternate
- Body: skip-to-content link, named nav slot, main#main-content with default slot
- Global styles: CSS reset, body typography, link styles, skip-link, main container, ::selection
- Updated src/pages/index.astro to use BaseLayout with inline style placeholders demonstrating tokens/fonts
- Added tests/check-base-layout.mjs build-output test with 23 assertions, all passing
2026-05-26 22:43:02 +00:00

101 lines
2.1 KiB
Plaintext

---
import '../styles/tokens.css';
import '../styles/fonts.css';
interface Props {
title: string;
description?: string;
}
const { title, description } = Astro.props;
const siteTitle = title === 'Crash Test Dev' ? title : `${title} — Crash Test Dev`;
const base = import.meta.env.BASE_URL;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{description && <meta name="description" content={description} />}
<title>{siteTitle}</title>
<link rel="alternate" type="application/rss+xml" title="Crash Test Dev" href={`${base.replace(/\/?$/, '/')}rss.xml`} />
</head>
<body>
<a href="#main-content" class="skip-link">Skip to content</a>
<slot name="nav" />
<main id="main-content">
<slot />
</main>
</body>
</html>
<style is:global>
/* CSS Reset */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 100%;
scroll-behavior: smooth;
}
body {
font-family: var(--font-prose);
font-size: var(--type-base);
line-height: var(--leading-prose);
color: var(--text-primary);
background-color: var(--surface-base);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
color: var(--accent-primary);
text-decoration: none;
}
a:hover {
text-decoration: underline;
color: var(--accent-primary-hover);
}
a:focus-visible {
outline: 2px solid var(--focus-ring);
outline-offset: 2px;
}
.skip-link {
position: absolute;
top: -100%;
left: var(--space-1);
z-index: 100;
padding: var(--space-1) var(--space-2);
background: var(--surface-card);
font-family: var(--font-structure);
font-size: var(--type-sm);
}
.skip-link:focus {
top: var(--space-1);
}
main {
max-width: var(--width-container);
margin-left: auto;
margin-right: auto;
padding-left: var(--gutter);
padding-right: var(--gutter);
}
::selection {
background-color: var(--accent-primary);
color: var(--surface-card);
}
</style>