diff --git a/blume.config.ts b/blume.config.ts index c840389..eb4cee8 100644 --- a/blume.config.ts +++ b/blume.config.ts @@ -64,6 +64,8 @@ export default defineConfig({ title: "Spacefast Docs", variables: pricingVariables(await loadPricing({ fixture: new URL("./content/_pricing/document.json", import.meta.url) })), description: "Publish and host the sites your agents build.", + // Two links (components/Logo.astro): the mark goes to the Website home at the + // origin root, outside the /docs base; "Docs" goes to the Docs home. logo: { href: "/", image: { light: "/spacefast-wordmark.svg", alt: "Spacefast" }, text: "Docs" }, // The 2026-10 brand: neutral greys, one green, dark first like the Website. // The green is a ground with ink on it, never type on white, so light mode diff --git a/components.ts b/components.ts index 1ab402d..3eca02e 100644 --- a/components.ts +++ b/components.ts @@ -1,12 +1,13 @@ import { defineComponents } from "blume"; import Breadcrumbs from "./components/Breadcrumbs.astro"; +import Logo from "./components/Logo.astro"; import Pagination from "./components/Pagination.astro"; import Sidebar from "./components/Sidebar.astro"; import PricingTable from "./components/PricingTable.astro"; import WpCloudDocs from "./components/WpCloudDocs.astro"; export default defineComponents({ - layout: { Breadcrumbs, Pagination, Sidebar }, + layout: { Breadcrumbs, Logo, Pagination, Sidebar }, mdx: { PricingTable, WpCloudDocs }, }); diff --git a/components/Logo.astro b/components/Logo.astro new file mode 100644 index 0000000..46942b2 --- /dev/null +++ b/components/Logo.astro @@ -0,0 +1,41 @@ +--- +// The brand lockup: the Spacefast mark links to the Website home, "Docs" links +// to the Docs home. Docs are always served inside the Website artifact at +// /docs, so the configured root-relative `logo.href` ("/") is emitted as is, +// without the deployment base, and reaches the Website home on whatever origin +// serves the page. The Website is not part of the Docs' client router, so the +// mark's link does a plain page load instead of a router fetch first. +import { withBase } from "blume/components/islands/base-path.ts"; +import type { BlumeLogo } from "blume/core/data.ts"; + +interface Props { + site: { title: string }; + logo?: BlumeLogo | null; + locale?: string; +} + +const { site, logo } = Astro.props; +const label = logo?.text ?? site.title; +const link = "inline-flex items-center transition-opacity hover:opacity-80"; +// The config inlines the wordmark so it follows the theme. Fail the build +// rather than ship a header whose mark silently disappeared. +if (!logo?.svg) throw new Error("components/Logo.astro needs blume.config.ts `logo` to resolve to an inline SVG."); +--- + +
+ + + { + label && ( + + {label} + + ) + } +
diff --git a/scripts/verify-routes.mjs b/scripts/verify-routes.mjs index acf634d..9a06bb9 100644 --- a/scripts/verify-routes.mjs +++ b/scripts/verify-routes.mjs @@ -16,6 +16,7 @@ import { } from "./redirect-rules.mjs"; import { discoveryFileForUrl } from "./build-llms-index.mjs"; import { readSidebarRoutes } from "./sidebar-routes.mjs"; +import { websiteDependencies } from "./website-dependencies.mjs"; const root = path.resolve(process.cwd()); const dist = path.join(root, "dist"); @@ -289,6 +290,21 @@ for (const [file, present, absent] of sidebarExpectations) { ); } } +// The header lockup (components/Logo.astro) is two links: the mark to the +// Website home at the origin root, the label to the Docs home. +for (const file of ["index.html", "quickstart/index.html", "api/reference/index.html"]) { + const html = await readBuilt(file); + const start = html.indexOf("data-sf-brand"); + const brand = start < 0 ? "" : html.slice(start, html.indexOf("", start)); + const links = [...brand.matchAll(/]*)>([\s\S]*?)<\/a>/gu)].map(([, attributes, body]) => ({ + href: /\bhref="([^"]*)"/u.exec(attributes)?.[1], + name: /\baria-label="([^"]*)"/u.exec(attributes)?.[1] ?? body.replace(/<[^>]*>/gu, "").trim(), + })); + const want = [{ href: "/", name: "Spacefast home" }, { href: `${deploymentBase}/`, name: "Docs" }]; + if (JSON.stringify(links) !== JSON.stringify(want)) { + throw new Error(`dist/${file} header lockup is wrong: ${JSON.stringify(links)}.`); + } +} // Docs previous/next skip the generated /setup pages; /setup keeps Blume's order. for (const [file, prev, next] of [ ["troubleshooting/index.html", "/quickstart", "/spaces"], @@ -555,7 +571,7 @@ for (const file of htmlFiles) { const html = await readFile(path.join(dist, file), "utf8"); for (const match of html.matchAll(/\b(?:action|href|src)=["'](\/[^"']*)["']/giu)) { const url = match[1]; - if (url.startsWith("//")) continue; + if (url.startsWith("//") || websiteDependencies.includes(url)) continue; if (url !== deploymentBase && !url.startsWith(`${deploymentBase}/`)) { throw new Error(`Built URL misses the ${deploymentBase} base in dist/${file}: ${url}`); } diff --git a/theme.css b/theme.css index dc9a99d..fcae942 100644 --- a/theme.css +++ b/theme.css @@ -39,17 +39,6 @@ color: var(--sf-ink); } -/* The wordmark beside the mark reads as part of the logo, not a link. */ -[data-blume-header] > a:first-of-type { - gap: 0.625rem; - font-weight: 500; - letter-spacing: -0.02em; -} - -[data-blume-header] > a:first-of-type > span:last-child { - color: var(--blume-muted-foreground); -} - /* * Headings follow the brand's type scale: Medium for display sizes, slight * negative tracking, no serif.