feat(site): phase 1 — the foundation
Some checks failed
PR checks / checks (pull_request) Failing after 4m19s
Some checks failed
PR checks / checks (pull_request) Failing after 4m19s
Astro 7 with the Node adapter, Starlight mounted at /docs, the token file, both self-hosted typefaces, the layout shell, and the two build-time checks from §12. The palette's gold and cyan are sampled from runic-emblem.png rather than guessed, per §11: 494,059 opaque pixels binned by hue, each value annotated with its measured contrast against the ground, and restricted rather than brightened where a ratio fails. - checkTokens.mjs fails the build on any colour literal outside tokens.css, which is what keeps §7's "recolouring is a file copy" promise true. - checkFacts.mjs re-reads all 14 externally-sourced facts from their authorities over the Gitea API and fails on disagreement. It also enforces D13: no email address in the source outside brand-default/brand.json. - Both were negative-tested; neither has ever been allowed to pass by default. §6 asks for output:'server' with per-page prerender=true. Astro 7 expresses the same runtime shape as output:'static' with an adapter, opting individual routes out — so the default is static rather than accidentally server-rendered. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
67
src/layouts/Base.astro
Normal file
67
src/layouts/Base.astro
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
import '../styles/tokens.css';
|
||||
import '../styles/global.css';
|
||||
|
||||
import Header from '../components/Header.astro';
|
||||
import Footer from '../components/Footer.astro';
|
||||
import { brand } from '../lib/brand.mjs';
|
||||
import { token } from '../lib/tokens.mjs';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
/** Suppress the site name suffix — the homepage sets its own full title. */
|
||||
bareTitle?: boolean;
|
||||
}
|
||||
|
||||
const { title, description, bareTitle = false } = Astro.props;
|
||||
|
||||
const fullTitle = bareTitle ? title : `${title} — ${brand.siteName}`;
|
||||
const canonical = new URL(Astro.url.pathname, Astro.site);
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>{fullTitle}</title>
|
||||
<meta name="description" content={description} />
|
||||
<link rel="canonical" href={canonical} />
|
||||
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content={brand.siteName} />
|
||||
<meta property="og:title" content={fullTitle} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:url" content={canonical} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
|
||||
<!--
|
||||
No analytics, no third-party requests, no cookie banner (D9), and the fonts are
|
||||
self-hosted (§11) — so there is nothing here to preconnect to, and §6's
|
||||
`default-src 'self'` holds with no exception to argue about. The CSP header itself
|
||||
is set at the adapter in phase 10; this comment is here so nobody adds a CDN link
|
||||
in the meantime and quietly breaks the promise.
|
||||
|
||||
Favicons and the OG image are served from the brand mount (`/brand/*`) in phase 2.
|
||||
-->
|
||||
<meta name="theme-color" content={token('--bg')} />
|
||||
|
||||
<slot name="head" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
|
||||
<div class="site">
|
||||
<Header />
|
||||
|
||||
<main id="main">
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user