From 79b8d53e4de802ee96b1f047049d90a04e3f9975 Mon Sep 17 00:00:00 2001 From: callumalpass Date: Tue, 15 Sep 2026 12:52:39 +1000 Subject: [PATCH] Use shared documentation layout for spec and add global signup link --- .github/workflows/ci.yml | 2 +- .github/workflows/deploy.yml | 2 +- README.md | 6 + package.json | 3 +- public/mdbase-shell.css | 430 +++++++++--------------------- scripts/import-spec.mjs | 53 +--- scripts/spec-page.mjs | 18 ++ scripts/spec-page.test.mjs | 25 ++ src/components/SiteHeader.astro | 5 +- src/pages/spec/[...version].astro | 45 ++++ src/styles/shell.css | 22 ++ src/styles/spec.css | 52 ++++ 12 files changed, 315 insertions(+), 348 deletions(-) create mode 100644 scripts/spec-page.mjs create mode 100644 scripts/spec-page.test.mjs create mode 100644 src/pages/spec/[...version].astro create mode 100644 src/styles/spec.css diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5205f3f..80d9d6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,7 +104,7 @@ jobs: pnpm check:release-order "$BASE_SHA" - name: Type and content checks - run: pnpm test:home && pnpm test:deploy:dev && pnpm test:release-update && pnpm check:release && pnpm check + run: pnpm test:spec && pnpm test:home && pnpm test:deploy:dev && pnpm test:release-update && pnpm check:release && pnpm check env: GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 201091e..a5a7525 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -103,7 +103,7 @@ jobs: MDBASE_TS_DIR: .sources/mdbase - name: Check and build website - run: pnpm test:home && pnpm check:release && pnpm check && pnpm build + run: pnpm test:spec && pnpm test:home && pnpm check:release && pnpm check && pnpm build env: GH_TOKEN: ${{ github.token }} diff --git a/README.md b/README.md index 017f3c4..c0836e4 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,12 @@ pnpm check:links The website repository owns the ecosystem pages and deployment cadence. The normative specification is built by `mdbase-spec` and imported under `/spec/`. +The import preserves its content and section anchors, inserting them into the +Astro specification shells (current and v0.2 archive). Those shells use the same +`BaseLayout`, fonts, theme control, and documentation styles as `/sdk/`; the +upstream site's header, scripts, and stylesheet are deliberately not imported. +Run `pnpm import:spec` after building to populate these shells; `pnpm dev` alone +shows the shell without the normative content. Connect schemas, theme roles, the homepage particle field, and implementation claims are synchronized from their canonical repositories. Generated assets remain traceable to the release artifacts they document. diff --git a/package.json b/package.json index 004dc13..fd32c89 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "test:deploy:dev": "node --test scripts/deploy-pages-dev.test.mjs", "test:release-update": "node --test scripts/connect-release-record.test.mjs", "test:home": "node --test scripts/home-animation.test.mjs", - "test": "pnpm test:home && pnpm test:deploy:dev && pnpm test:release-update && pnpm check:release && pnpm check && pnpm build && pnpm import:spec && pnpm check:links" + "test:spec": "node --test scripts/spec-page.test.mjs", + "test": "pnpm test:spec && pnpm test:home && pnpm test:deploy:dev && pnpm test:release-update && pnpm check:release && pnpm check && pnpm build && pnpm import:spec && pnpm check:links" }, "dependencies": { "@astrojs/sitemap": "^3.7.3", diff --git a/public/mdbase-shell.css b/public/mdbase-shell.css index c5071ab..4b34065 100644 --- a/public/mdbase-shell.css +++ b/public/mdbase-shell.css @@ -2,7 +2,29 @@ --sans: "Atkinson Hyperlegible", "Segoe UI", sans-serif; --mono: "Azeret Mono", "SFMono-Regular", "Cascadia Code", monospace; --site-left: clamp(24px, 8vw, 120px); - --shell-width: 1080px; + --shell-width: 760px; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +html { + font-size: 17px; } body { @@ -12,385 +34,199 @@ body { line-height: 1.5; } -.spec-header { +.site-body--wide { + --shell-width: 1080px; +} + +.site-header { position: relative; - z-index: 100; - top: auto; - right: auto; - left: auto; - display: block; + z-index: 20; width: min(calc(100% - 48px), var(--shell-width)); - height: auto; margin-left: var(--site-left); - padding: 22px 0 0; + padding-top: 22px; color: var(--color-text); background: var(--color-canvas); - border: 0; } -.spec-brand { - min-height: 0; - margin-bottom: 18px; - gap: 8px; -} - -.landing-logo { - gap: 0; - color: var(--color-text); - font: 400 1rem/1.5 var(--sans); - letter-spacing: 0; +.home-body > .site-header { + position: fixed; + top: 0; + left: var(--site-left); + margin-left: 0; + background: color-mix(in srgb, var(--color-canvas) 92%, transparent); } -.landing-logo .mdbase-mark { - display: none; -} - -.spec-version { - color: var(--color-text-muted); - font: 400 1rem/1.5 var(--sans); -} - -.spec-header-actions { - display: flex; +.site-header__inner { width: 100%; - height: auto; - min-height: 0; - margin: 0; - padding: 10px 0; - align-items: center; - gap: 22px; - border-top: 1px solid var(--color-text); - border-bottom: 1px solid var(--color-text); } -.landing-nav { +.site-header__identity { display: flex; - height: auto; - flex-wrap: wrap; - align-items: center; - gap: 6px 22px; + align-items: baseline; + justify-content: space-between; + gap: 1rem; } -.landing-nav a { - display: inline; - min-height: 0; - padding: 0; +.site-header__signup { color: var(--color-text); - font: 400 1rem/1.5 var(--sans); - text-decoration: underline; + white-space: nowrap; text-underline-offset: 0.2em; } -.landing-nav a::after { - display: none; +.site-header__signup:hover { + color: var(--color-text-soft); } -.landing-nav a:hover { - color: var(--color-text-soft); +.site-header__signup:focus-visible { + outline: 2px solid currentColor; + outline-offset: 3px; } -.landing-nav a.nav-active { +.wordmark { + display: inline-block; + margin-bottom: 18px; color: var(--color-text); font-weight: 400; text-decoration: none; } -.theme-menu { - margin-left: auto; -} - -.theme-menu > summary { - width: auto; - height: auto; - min-height: 0; - color: var(--color-text); - border-radius: 0; -} - -.theme-menu > summary:hover, -.theme-menu[open] > summary { - color: var(--color-text-soft); - background: transparent; -} - -.theme-menu__options { - border-radius: 0; - background: var(--color-canvas); -} - -.theme-menu__options button { - min-height: 2rem; - border-radius: 0; - font: 400 1rem/1.5 var(--sans); +.site-nav { + display: flex; + padding: 10px 0; + align-items: center; + flex-wrap: wrap; + gap: 6px 22px; + border-top: 1px solid var(--color-text); + border-bottom: 1px solid var(--color-text); } -.spec-layout { - display: grid; - width: min(calc(100vw - var(--site-left) - 24px), var(--shell-width)); - min-height: 0; - margin: 64px 0 0 var(--site-left); +.site-nav a, +.theme-toggle { padding: 0; - grid-template-columns: 180px minmax(0, 660px) minmax(0, 1fr); - gap: 32px; -} - -.spec-sidebar { - position: sticky; - z-index: 20; - inset: auto; - top: 32px; - width: auto; - max-height: calc(100vh - 64px); - align-self: start; - overflow-y: auto; - padding: 0 0 0 16px; + color: var(--color-text); background: transparent; border: 0; - border-left: 1px solid var(--color-text); -} - -.sidebar-group { - margin: 0; + font: inherit; + text-underline-offset: 0.2em; } -.sidebar-group-label { - margin: 0 0 10px; - padding: 0; - color: var(--color-text); - font: 400 1rem/1.5 var(--sans); +.site-nav a[aria-current="page"] { + font-weight: 400; + text-decoration: none; } -.sidebar-link { +.theme-toggle { + width: 24px; + height: 24px; + margin-left: auto; display: grid; - min-height: 0; - padding: 3px 0; - grid-template-columns: minmax(0, 1fr); - color: var(--color-text-soft); - border-radius: 0; - font: 400 1rem/1.4 var(--sans); - text-decoration: none; + place-items: center; + cursor: pointer; } -.sidebar-link .num { +.theme-toggle[hidden] { display: none; } -.sidebar-link:hover, -.sidebar-link.active { - color: var(--color-text); - background: transparent; - font-weight: 400; - text-decoration: none; +.theme-toggle__icon { + width: 14px; + height: 14px; } -.spec-content { - width: auto; - min-width: 0; - padding: 0 0 96px; - grid-column: 2; - justify-self: stretch; +.theme-toggle__icon circle { + fill: none; + stroke: currentColor; + stroke-width: 1.25; } -.spec-section { - margin-bottom: 64px; +.theme-toggle__icon path { + fill: currentColor; } -.spec-section > h1:first-child, -.spec-section h1, -.spec-section h2, -.spec-section h3, -.spec-section h4, -.spec-section h5, -.spec-section h6, -.spec-content strong { +.site-footer { + position: relative; + z-index: 3; + width: min(calc(100% - 48px), var(--shell-width)); + margin: 40px 0 0 var(--site-left); + padding: 12px 0 48px; color: var(--color-text); - font-family: var(--sans); - font-size: 1rem; - font-weight: 400; - line-height: 1.35; - letter-spacing: 0; - text-wrap: pretty; -} - -.spec-section > h1:first-child { - margin: 0 0 24px; - padding: 0; - border: 0; -} - -.spec-section h2 { - margin: 40px 0 16px; - padding-top: 16px; - border-top: 1px solid var(--color-border); -} - -.spec-section h3, -.spec-section h4, -.spec-section h5, -.spec-section h6 { - margin: 28px 0 12px; + background: var(--color-canvas); + border-top: 1px solid var(--color-text); } -.spec-content p, -.spec-content li { - max-width: 660px; +.site-footer__inner { + display: grid; + align-items: start; + grid-template-columns: minmax(9rem, 0.6fr) minmax(0, 1.4fr); + gap: 2rem; } -.spec-content a { - color: var(--color-text); - text-decoration-color: currentColor; +.site-footer strong { + font-weight: 400; } -.spec-content a:hover { +.site-footer p { + margin: 0; color: var(--color-text-soft); } -.spec-content code { - color: var(--color-text); - border-radius: 0; -} - -.spec-content pre, -.spec-content blockquote, -.spec-content table { - border-radius: 0; - box-shadow: none; +.site-footer__nav { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 1.5rem; } -.spec-content pre, -.spec-content blockquote { - background: var(--color-canvas); - border: 1px solid var(--color-text); +.site-footer__nav > div { + display: grid; + align-content: start; + gap: 0.2rem; } -.anchor { - color: var(--color-text-muted); - font-weight: 400; +.site-footer__nav > div > strong { + margin-bottom: 0.25rem; } -.sidebar-toggle { - min-height: 0; - padding: 0; +.site-footer a { color: var(--color-text); - background: transparent; - border: 0; - border-radius: 0; - font: 400 1rem/1.5 var(--sans); - text-decoration: underline; - text-underline-offset: 0.2em; + text-decoration: none; } -@media (max-width: 1024px) { - .spec-header { - display: flex; - width: calc(100% - 32px); - margin-left: 16px; - padding: 16px 0 10px; - align-items: center; - gap: 12px; - border-bottom: 1px solid var(--color-text); - } - - .spec-brand { - display: flex; - flex: 1; - margin: 0; - } - - .sidebar-toggle { - display: inline-flex; - order: 3; - margin-left: auto; - } - - .spec-header .landing-nav { - display: none; - } - - .spec-header-actions { - width: auto; - margin: 0; - padding: 0; - justify-content: flex-end; - border: 0; - } +.site-footer a:hover { + color: var(--color-text-soft); +} - .theme-menu { - margin: 0; +@media (max-width: 820px) { + :root { + --site-left: 16px; } - .spec-layout { - display: block; + .site-header, + .home-body > .site-header, + .site-footer { width: calc(100% - 32px); - margin: 48px 0 0 16px; } - .spec-sidebar { - position: fixed; - z-index: 120; - inset: 0 auto 0 0; - width: min(320px, calc(100vw - 48px)); - max-height: none; - padding: 24px 18px; - background: var(--color-canvas); - border: 0; - border-right: 1px solid var(--color-text); - transform: translateX(-105%); + .site-header { + padding-top: 16px; } - .spec-sidebar.open { - transform: translateX(0); - } - - .sidebar-scrim { - inset: 0; - z-index: 110; - } - - .spec-mobile-links { - display: block; - } - - .spec-content { - width: min(660px, 100%); - padding-bottom: 72px; + .site-nav { + gap: 6px 14px; } } -@media (max-width: 560px) { - .spec-version { - display: none; - } - - .spec-header-actions { - gap: 12px; - } - - .spec-content { - width: 100%; - } - - .spec-section { - margin-bottom: 48px; - } - - .anchor { - display: none; +@media (max-width: 42rem) { + .site-footer__inner { + grid-template-columns: 1fr; } } -@media print { - .spec-header, - .spec-sidebar, - .sidebar-scrim { - display: none !important; +@media (max-width: 35rem) { + .site-footer__identity p { + display: none; } - .spec-layout, - .spec-content { - display: block; - width: auto; - margin: 0; - padding: 0; + .site-footer__nav { + grid-template-columns: repeat(2, minmax(0, 1fr)); } } diff --git a/scripts/import-spec.mjs b/scripts/import-spec.mjs index a833a34..25ec3c9 100644 --- a/scripts/import-spec.mjs +++ b/scripts/import-spec.mjs @@ -7,6 +7,7 @@ import { } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import { renderSpecPage } from "./spec-page.mjs"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const specDir = resolve(process.env.MDBASE_SPEC_DIR ?? join(root, "..", "mdbase-spec")); @@ -28,11 +29,6 @@ writeFileSync( rewrite(readFileSync(join(source, "spec-v0.2.html"), "utf8"), true) ); -for (const asset of ["style.css", "theme.js", "mdbase-favicon.svg"]) { - const path = join(source, asset); - if (existsSync(path)) copyFileSync(path, join(destination, asset)); -} - for (const support of ["IMPLEMENTING.md", "REFERENCE-RUNNER.md", "QUICK-REFERENCE.md"]) { const path = join(specDir, "v0.2", support); if (existsSync(path)) copyFileSync(path, join(destination, "v0.2", support)); @@ -43,36 +39,10 @@ addSitemapRoutes(); console.log(`Imported specification pages from ${source}`); function rewrite(html, archive) { - const pageUrl = archive - ? `${siteOrigin}/spec/v0.2/` - : `${siteOrigin}/spec/`; - const pageTitle = archive - ? "mdbase specification v0.2 archive" - : "mdbase specification v0.3"; - return html - .replaceAll('src="theme.js', 'src="/spec/theme.js') - .replaceAll('href="style.css', 'href="/spec/style.css') - .replaceAll('href="mdbase-favicon.svg"', 'href="/spec/mdbase-favicon.svg"') + const shellPath = join(destination, archive ? "v0.2/index.html" : "index.html"); + required(shellPath, "Build the shared specification shell before importing"); + const content = html .replaceAll('href="runtime.html"', 'href="/runtime/"') - .replaceAll('Implementations', '') - .replace( - /