diff --git a/bun.lock b/bun.lock index f75d778c..f2c7684f 100644 --- a/bun.lock +++ b/bun.lock @@ -19,7 +19,7 @@ "typescript": "6.0.3", }, "devDependencies": { - "@hraness/design-kit": "github:hraness/design-kit#v0.19.0", + "@hraness/design-kit": "github:hraness/design-kit#v0.21.0", "@hraness/direct": "https://github.com/hraness/direct/releases/download/v0.7.21/hraness-direct-0.7.21.tgz", "@hraness/site-footer": "github:hraness/site-footer#v0.19.2", "@hraness/ui": "github:hraness/ui#v0.5.18", @@ -126,7 +126,7 @@ "@hraness/accounts-cli": ["@hraness/accounts-cli@github:hraness/accounts-cli#eaad8cb", { "dependencies": { "@hraness/suite-accounts": "github:hraness/suite-accounts#v0.9.13" } }, "hraness-accounts-cli-eaad8cb", "sha512-/r6nTHa9buZj9dLP4dgRtPkrKpfhs3/7gwcR/r3aAFnNmLXtUBms60NPP7O4EOel524638UhEnvYuZ1TSlgtAA=="], - "@hraness/design-kit": ["@hraness/design-kit@github:hraness/design-kit#68374d3", { "dependencies": { "@hugeicons/core-free-icons": "4.2.3", "@stylexjs/stylex": "0.19.0", "motion": "12.42.0", "next-themes": "0.4.6", "react-aria-components": "1.19.0", "recharts": "3.8.0", "sugar-high": "^1.2.1", "web-haptics": "0.0.6" }, "peerDependencies": { "@hraness/ui": ">=0.5.16 <0.6.0", "react": ">=18 <20", "react-dom": ">=18 <20" }, "optionalPeers": ["@hraness/ui"] }, "hraness-design-kit-68374d3", "sha512-GIiSZpgAUN8dF4LU6vWsB9SNpZcP9Z/U/0T+YeUR9Y3fkwQkG58GA69X9agi8ZMaGrxWMAXb77BrGRABDVcNeQ=="], + "@hraness/design-kit": ["@hraness/design-kit@github:hraness/design-kit#4c3f7c9", { "dependencies": { "@hugeicons/core-free-icons": "4.2.3", "@stylexjs/stylex": "0.19.0", "motion": "12.42.0", "next-themes": "0.4.6", "react-aria-components": "1.19.0", "recharts": "3.8.0", "sugar-high": "^1.2.1", "web-haptics": "0.0.6" }, "peerDependencies": { "@hraness/ui": ">=0.5.16 <0.6.0", "react": ">=18 <20", "react-dom": ">=18 <20" }, "optionalPeers": ["@hraness/ui"] }, "hraness-design-kit-4c3f7c9", "sha512-7SfYsa39IMWkb67vXWBK5xJ5l7nrotVERD/KnhogvJObxLBfvxLLl6qBY9Q6QK0uxBE5LTzRT/huciboQf91nw=="], "@hraness/desktop-foundation": ["@hraness/desktop-foundation@https://github.com/hraness/desktop-foundation/releases/download/v0.7.0/hraness-desktop-foundation-0.7.0.tgz", { "bin": { "companion": "./dist/src/cli.js" } }, "sha512-lmGe/kJJqtVcbEIPKNGz0iWqVIb3jM4se1v5wR8vY6dA+haZ5u2XNBPL6n1zbLlMupZ7/Ue5/3SDU65x/yJ7SA=="], diff --git a/docs/assurance.md b/docs/assurance.md index 49fc9785..3352e391 100644 --- a/docs/assurance.md +++ b/docs/assurance.md @@ -8,7 +8,7 @@ A claim is *evidenced* when its layer runs in CI, *planned* when a plan phase sc ## Summary -The register holds 246 claims: 227 evidenced, 0 planned, and 19 not verified. It maps 91 guidelines from 5 guides; 70 list claims and 21 are exempt. +The register holds 246 claims: 227 evidenced, 0 planned, and 19 not verified. It maps 92 guidelines from 5 guides; 70 list claims and 22 are exempt. | Layer | Evidenced | Planned | Not verified | | --- | ---: | ---: | ---: | @@ -76,6 +76,7 @@ These guidelines have no claim in the register, and no automated check covers th | `website/AGENTS.md` | Keep every product… | Editorial accuracy rule for website copy; no automated check covers it. | | `website/AGENTS.md` | Keep canonical metadata,… | Search-metadata presentation rule; it states no safety or integrity property. | | `website/AGENTS.md` | Keep ordinary reference… | Presentation rule for website guides and editorial images; it states no safety or integrity property. | +| `website/AGENTS.md` | The HTML 404 renders… | Presentation rule for the informational website; it states no safety or integrity property. | | `website/AGENTS.md` | Preserve semantic headings,… | Accessibility presentation rule; it states no safety or integrity property. | | `website/AGENTS.md` | Public copy (page… | Public-copy style rule; it states no safety or integrity property. | | `website/AGENTS.md` | The one-line description… | Public-copy consistency rule that the website build enforces; it states no safety or integrity property. | diff --git a/package.json b/package.json index ea4b77b0..5b007602 100644 --- a/package.json +++ b/package.json @@ -682,7 +682,7 @@ "@hraness/desktop-foundation": "https://github.com/hraness/desktop-foundation/releases/download/v0.7.0/hraness-desktop-foundation-0.7.0.tgz" }, "devDependencies": { - "@hraness/design-kit": "github:hraness/design-kit#v0.19.0", + "@hraness/design-kit": "github:hraness/design-kit#v0.21.0", "@hraness/direct": "https://github.com/hraness/direct/releases/download/v0.7.21/hraness-direct-0.7.21.tgz", "@hraness/site-footer": "github:hraness/site-footer#v0.19.2", "@hraness/ui": "github:hraness/ui#v0.5.18", diff --git a/verification/claims.json b/verification/claims.json index cd034cfc..41043331 100644 --- a/verification/claims.json +++ b/verification/claims.json @@ -897,6 +897,12 @@ "digest": "565a9105cb81bd4643ec63da534bf4efebbb4506d1a519638a05f56797818b63", "exempt": "Presentation rule for website guides and editorial images; it states no safety or integrity property." }, + { + "guide": "website/AGENTS.md", + "anchor": "The HTML 404 renders", + "digest": "c34425a70895c13e96541c1283f008b887acf1aeb5c5b848209ca84c603964d9", + "exempt": "Presentation rule for the informational website; it states no safety or integrity property." + }, { "guide": "website/AGENTS.md", "anchor": "Public content pages", diff --git a/website/AGENTS.md b/website/AGENTS.md index 39aa4787..3b64fddb 100644 --- a/website/AGENTS.md +++ b/website/AGENTS.md @@ -33,6 +33,7 @@ - Require a production Git checkout with a resolvable `HEAD`; never substitute a deployment environment SHA or exclude `.git` through `.vercelignore`. For v0.16.13 and later, production verifies immutable GitHub metadata, exact five descriptors, bot/source receipt, manifest/archive digests, HEAD/tag, and Latest. It trusts the authenticated canonical release admission; independent promotion CI runs the cryptographic Sigstore verifier. Do not claim Vercel verifies Sigstore locally. Historical releases retain their prior npm manifest check. - Keep canonical metadata, robots, sitemap, llms.txt, Open Graph, X metadata, and the linked JSON-LD graph aligned to `https://ghostget.com/`. - Keep ordinary reference and task guides typographic and preserve the real getting-started video and poster. For the small set of live editorial argument or comparison pages, treat `editorial-images.ts` as the single image registry and use the `editorial-image-seo` skill. For new imagery, use a reviewed Slopcamera build from its [source-install guide](https://github.com/hraness/slopcamera/blob/main/docs/how-to/use-current-source.md), retain its exact source commit, invoke `bun "$SLOPCAMERA_SOURCE_ROOT/apps/desktop/dist/cli/main.js"` inside the intended Vercel environment, and never retry an ambiguous paid result. Preserve historical Atet credits and completed provenance bytes; extend the registry truthfully before accepting a new generator. For each new image, review the original and card thumbnail, then keep that provenance-bound 1536-pixel original canonical for social, schema, and sitemap use while serving locally derived 384- and 768-pixel WebP files through `srcset` in visible figures and cards. Synchronize the visible figure and credit, homepage card, Open Graph and X metadata, `TechArticle.image`, and image sitemap. Retain prompt, completed job, and receipt under `editorial-provenance//` without exposing those internal paths publicly. Do not register a draft route, including the PayPal/GrapheneOS page, until that route is live. Use semantic editorial metaphors rather than generated factual, capability, or security diagrams. +- The HTML 404 renders the shared design-kit status page through `renderGhostgetStatusPage` in `build.ts`: the homepage primary action, at most three next links, and every public page and listed post as "Did you mean" routes. `source/status-page.ts` bundles `attachStatusPage` as its only enhancement. Keep 404 presentation in design-kit, not in `styles.css`. - Public content pages keep one URL for HTML and Markdown. Honor `Accept` q-values, set `Vary: Accept`, and return `406` only when no owned representation remains. Unknown paths stay HTTP 404 and point agents at llms.txt, the sitemap, and getting started. - Keep analytics canonical-host-only, cookieless, personless, memory-only, DNT-aware, query-free, and restricted to page lifecycle plus the two explicit GitHub links. - Do not add private packages, private assets, cookies, replay, identity, feature flags, broad autocapture, console capture, or application behavior. diff --git a/website/appearance.test.ts b/website/appearance.test.ts index 13c3ae7a..920cacc8 100644 --- a/website/appearance.test.ts +++ b/website/appearance.test.ts @@ -19,9 +19,7 @@ test("one final header menu preserves content and precedes first stylesheet for expect(html).toContain('data-ready="false"'); expect(html).toContain('aria-label="Appearance: System" disabled'); } - const recovery = addDocumentAppearance(head + '

Missing

', asset); - expect(recovery).toContain('
'); - expect(recovery.match(/data-hraness-appearance-menu/gu)).toHaveLength(1); + expect(() => addDocumentAppearance(head + '

Missing

', asset)).toThrow("ordinary header"); }); test("every ordinary authored template has one accepted composition; preview remains inert", async () => { diff --git a/website/appearance.ts b/website/appearance.ts index f763902b..b68eb6d1 100644 --- a/website/appearance.ts +++ b/website/appearance.ts @@ -26,10 +26,6 @@ export function addDocumentAppearance(html: string, asset: string, format: "html ? existing.replace(actions, `$1\n${menu}\n$2`) : existing.replace("", `${menu}\n`); result = result.replace(existing, amended); - } else if (headers === null && /
/u.test(result)) { - result = result.replace("", `\n -
Ghostget${menu}
`); - result = result.replace('
', '
'); } else throw new Error("Appearance requires exactly one ordinary header."); const targets = result.match(/]*\bid="main"[^>]*>/gu); if (targets?.length !== 1 || /\btabindex=/iu.test(targets[0]!)) throw new Error("Expected one owned skip-link target."); diff --git a/website/build.ts b/website/build.ts index 13b1c4d7..9ef69a13 100644 --- a/website/build.ts +++ b/website/build.ts @@ -24,6 +24,7 @@ import { ghostgetSupportProfile } from "../src/support-profile"; import { AskAiAboutThis } from "./ask-ai-runtime.js"; import { product, type PortfolioProductId } from "@hraness/design-kit/portfolio"; +import { renderStatusPageHtml, type StatusPageLink } from "@hraness/design-kit"; import { EDITORIAL_ARTICLE_IMAGE_SIZES, EDITORIAL_CARD_IMAGE_SIZES, @@ -50,6 +51,7 @@ import { renderBlogIndexMain, renderBlogLlmsEntries, renderBlogPostMain, + type BlogPost, type BlogSite, } from "./blog"; import type { SitemapPath } from "@hraness/web-discovery"; @@ -964,6 +966,67 @@ function renderTemplate( return addDocumentAppearance(rendered, options.appearanceAsset, format); } +/** + * The shared design-kit 404 body. The primary action matches the homepage + * hero; the three next links cover what Ghostget is, the install guide, and + * the provider reference. `routes` feeds "Did you mean" and is never listed. + */ +export function renderGhostgetStatusPage(routes: readonly StatusPageLink[]): string { + return renderStatusPageHtml({ + agentIndexHref: "/llms.txt", + next: [ + { + description: "Install the CLI, read your first page, and watch the recorded demo.", + href: "/docs/tutorials/getting-started/", + label: "Getting started", + }, + { + description: "Every site and named action your agent can call, and how each one runs.", + href: "/docs/reference/provider-capabilities/", + label: "Supported sites and actions", + }, + { + description: "What the CLI and SDK do, and what they leave to your agent.", + href: "/about/", + label: "About Ghostget", + }, + ], + primaryAction: { href: "/#start", label: "Install Ghostget" }, + rootElement: "div", + routes, + siteName: "Ghostget", + }); +} + +const STATUS_ROUTE_LABEL_LIMIT = 48; + +/** A page title cut to the lead clause the "Did you mean" hint can show. */ +export function statusRouteLabel(title: string): string { + const lead = title.split(":")[0]!.split(", and ")[0]!.trim(); + if (lead.length <= STATUS_ROUTE_LABEL_LIMIT) return lead; + const cut = lead.slice(0, STATUS_ROUTE_LABEL_LIMIT - 1); + return `${cut.slice(0, cut.lastIndexOf(" ")).trimEnd()}…`; +} + +/** Known pages for "Did you mean": every public page, the blog, and each listed post. */ +export function statusPageRoutes( + pages: readonly Pick[], + posts: readonly Pick[], +): StatusPageLink[] { + return [ + ...pages.map((page) => { + // WebMCP registry pages carry long registry titles; the domain reads better. + const provider = /^\/providers\/([^/]+)\/$/u.exec(page.canonicalPath)?.[1]; + return { + href: page.canonicalPath, + label: statusRouteLabel(provider === undefined ? page.title : `${provider} on Ghostget`), + }; + }), + { href: BLOG_PATH, label: statusRouteLabel(BLOG_TITLE) }, + ...posts.map((post) => ({ href: blogPostPath(post.slug), label: statusRouteLabel(post.title) })), + ]; +} + export function renderPreview(template: string, cssAsset: string): string { const rendered = replaceRequired(template, "{{CSS_ASSET}}", escapeHtml(cssAsset)); if (/\{\{[A-Z0-9_]+\}\}/u.test(rendered)) { @@ -1128,6 +1191,7 @@ export async function buildWebsite( designKitProductMarketingCss, designKitPlainSiteCss, designKitPlainPublicationCss, + designKitStatusPageCss, hranessSiteFooterCss, blogShell, blogFragments, @@ -1156,6 +1220,7 @@ export async function buildWebsite( })), readFile(designKitPlainSiteStylesPath, "utf8"), readFile(designKitPlainPublicationStylesPath, "utf8"), + readFile(fileURLToPath(import.meta.resolve("@hraness/design-kit/status-page.css")), "utf8"), readFile( fileURLToPath(import.meta.resolve("@hraness/site-footer/stylex.css")), "utf8", @@ -1175,6 +1240,7 @@ export async function buildWebsite( join(sourceRoot, "foil.ts"), join(sourceRoot, "ghostget-field.ts"), join(sourceRoot, "appearance.ts"), + join(sourceRoot, "status-page.ts"), ], format: "esm", minify: true, @@ -1192,7 +1258,7 @@ export async function buildWebsite( ); const webmcpIndexValues = webmcpIndexTemplateValues(webmcpSnapshot); const allPages: readonly PublicPage[] = [...PUBLIC_PAGES, ...webmcpPages]; - if (!browserBuild.success || browserBuild.outputs.length !== 5) { + if (!browserBuild.success || browserBuild.outputs.length !== 6) { const messages = browserBuild.logs.map((log) => log.message).join("\n"); throw new Error(`Browser script build failed: ${messages || "no browser output"}`); } @@ -1206,6 +1272,7 @@ export async function buildWebsite( const skillInstall = new Uint8Array(await scriptAsset("skill-install-command")); const foil = new Uint8Array(await scriptAsset("foil")); const field = new Uint8Array(await scriptAsset("ghostget-field")); + const statusPage = new Uint8Array(await scriptAsset("status-page")); // The blocking head script is a classic script: wrap the shared ESM output in // one strict-mode IIFE so it never leaks a top-level binding. const appearance = new TextEncoder().encode(`(() => { "use strict";\n${new TextDecoder().decode(await scriptAsset("appearance"))}\n})();`); @@ -1221,13 +1288,14 @@ export async function buildWebsite( const lanternCss = lanternMaterial.files.get("lantern-material.css"); const lanternLicense = lanternMaterial.files.get("LICENSE"); if (lanternCss === undefined || lanternLicense === undefined) throw new Error("The complete Lantern build snapshot is required."); - const compiledCss = `${uiCss}\n\n${designKitFontsCss.trim()}\n\n${designKitTypographyCss.trim()}\n\n${designKitProductMarketingCss.trim()}\n\n${designKitPlainSiteCss.trim()}\n\n${designKitPlainPublicationCss.trim()}\n\n${hranessSiteFooterCss.trim()}\n\n${paperThemeCss.trim()}\n\n${paletteSystemCss.trim()}\n\n${paletteBridgeCss.replace('@import "./palette-system.css";', "").trim()}\n\n${appearanceCss.trim()}\n\n${css.trimEnd()}\n\n${marketingPreset.files.get("product-marketing-preset.css")!.toString("utf8")}\n\n${lanternCss.toString("utf8")}\n`; + const compiledCss = `${uiCss}\n\n${designKitFontsCss.trim()}\n\n${designKitTypographyCss.trim()}\n\n${designKitProductMarketingCss.trim()}\n\n${designKitPlainSiteCss.trim()}\n\n${designKitPlainPublicationCss.trim()}\n\n${designKitStatusPageCss.trim()}\n\n${hranessSiteFooterCss.trim()}\n\n${paperThemeCss.trim()}\n\n${paletteSystemCss.trim()}\n\n${paletteBridgeCss.replace('@import "./palette-system.css";', "").trim()}\n\n${appearanceCss.trim()}\n\n${css.trimEnd()}\n\n${marketingPreset.files.get("product-marketing-preset.css")!.toString("utf8")}\n\n${lanternCss.toString("utf8")}\n`; const cssAsset = `/assets/styles-${contentHash(compiledCss)}.css`; const analyticsAsset = `/assets/analytics-${contentHash(analytics)}.js`; const appearanceAsset = `/assets/appearance-${contentHash(appearance)}.js`; const skillInstallAsset = `/assets/skill-install-${contentHash(skillInstall)}.js`; const foilAsset = `/assets/foil-${contentHash(foil)}.js`; const fieldAsset = `/assets/field-${contentHash(field)}.js`; + const statusPageAsset = `/assets/status-page-${contentHash(statusPage)}.js`; const providerDirectory = createProviderDirectory(attestation); const beeperFacts = createBeeperPresentationFacts(providerDirectory); const whatsappFacts = createWhatsAppPresentationFacts(providerDirectory, attestation); @@ -1313,7 +1381,14 @@ export async function buildWebsite( )), writeFile(join(outputRoot, BLOG_FEED_PATH.slice(1)), renderBlogAtomFeed(BLOG_SITE)), writeFile(join(outputRoot, "preview/index.html"), renderPreview(previewTemplate, cssAsset)), - writeFile(join(outputRoot, "404.html"), renderTemplate(notFoundTemplate, renderOptions)), + writeFile(join(outputRoot, "404.html"), renderTemplate( + // A function replacement keeps "$" in route labels literal. + replaceHtmlRequired(notFoundTemplate, "{{STATUS_PAGE_ASSET}}", escapeHtml(statusPageAsset)).replace( + "{{STATUS_PAGE}}", + () => renderGhostgetStatusPage(statusPageRoutes(allPages, BLOG_POSTS.filter(isIndexablePost))), + ), + renderOptions, + )), writeFile(join(outputRoot, "404.md"), renderTemplate(notFoundMarkdown, renderOptions, undefined, "text")), writeFile(join(outputRoot, "llms.txt"), renderTemplate( replaceRequired(llmsTemplate, "{{BLOG_LLMS_ENTRIES}}", renderBlogLlmsEntries(BLOG_SITE)), @@ -1327,6 +1402,7 @@ export async function buildWebsite( writeFile(join(outputRoot, skillInstallAsset.slice(1)), skillInstall), writeFile(join(outputRoot, foilAsset.slice(1)), foil), writeFile(join(outputRoot, fieldAsset.slice(1)), field), + writeFile(join(outputRoot, statusPageAsset.slice(1)), statusPage), writeFile( join(outputRoot, "robots.txt"), `User-agent: *\nAllow: /\n\nSitemap: ${SITE_ORIGIN}/sitemap.xml\n`, diff --git a/website/site.test.ts b/website/site.test.ts index 3d42a981..40922c58 100644 --- a/website/site.test.ts +++ b/website/site.test.ts @@ -183,13 +183,13 @@ describe("ghostget.com static site", () => { expect(packageFiles).not.toContain("vercel.json"); expect(manifest).toMatchObject({ devDependencies: { - "@hraness/design-kit": "github:hraness/design-kit#v0.19.0", + "@hraness/design-kit": "github:hraness/design-kit#v0.21.0", "@hraness/site-footer": "github:hraness/site-footer#v0.19.2", "@hraness/ui": "github:hraness/ui#v0.5.18", }, }); - expect(lockfile).toContain('"@hraness/design-kit": "github:hraness/design-kit#v0.19.0"'); + expect(lockfile).toContain('"@hraness/design-kit": "github:hraness/design-kit#v0.21.0"'); expect(lockfile).toContain('"@hraness/ui": "github:hraness/ui#v0.5.18"'); expect(lockfile).toContain('"@hraness/site-footer": "github:hraness/site-footer#v0.19.2"'); @@ -695,13 +695,21 @@ describe("ghostget.com static site", () => { expect(notFound).toContain( '', ); - expect(notFound).toContain("Privacy: this page uses cookieless, personless PostHog analytics"); + expect(notFound).toContain("Privacy: cookieless PostHog analytics"); + // The shared design-kit status page: the homepage's primary action, three + // next links, one quiet agent line, and known pages for "Did you mean". + expect(notFound).toContain('
We can’t find that page'); + expect(notFound).toContain('data-emphasis="primary" data-foil="" href="/#start">Install Ghostget'); + expect(notFound.match(/class="hraness-status-page__next-link"/gu)).toHaveLength(3); + expect(notFound).toContain('

'); expect(notFound).toContain('href="/llms.txt"'); - expect(notFound).toContain('href="/sitemap.xml"'); - expect(notFound).toContain('href="/docs/tutorials/getting-started/"'); + expect(notFound).toContain("["/docs/tutorials/getting-started/","Get started with Ghostget"]"); + expect(notFound).not.toContain('href="/sitemap.xml"'); + expect(notFound).toMatch(/ + -

-

404 · Ghostget {{GHOSTGET_VERSION}}

-

Page not found

-

This page may have moved. The Ghostget {{GHOSTGET_RELEASE}} install guide and the rest of the site are still available. Start from one of these pages:

- - Return to ghostget.com -

Privacy: this page uses cookieless, personless PostHog analytics. No replay, broad autocapture, or URL queries; Do Not Track is honored.

-

Current install command: {{GHOSTGET_INSTALL_COMMAND}}

+ +
+ Ghostget + +
+
+ {{STATUS_PAGE}}
+ {{HRANESS_SITE_FOOTER}} diff --git a/website/source/404.md b/website/source/404.md index 26ed6ada..1f4d0e48 100644 --- a/website/source/404.md +++ b/website/source/404.md @@ -1,13 +1,7 @@ # Page not found -This page may have moved. The Ghostget {{GHOSTGET_RELEASE}} install guide and the rest of the site are still available. - -Start from one of these pages: +This address doesn’t exist on ghostget.com. The link may be out of date or mistyped. +- [Install Ghostget {{GHOSTGET_RELEASE}}](https://ghostget.com/docs/tutorials/getting-started/) - [Agent site guide (llms.txt)](https://ghostget.com/llms.txt) - [XML sitemap](https://ghostget.com/sitemap.xml) -- [Install and Ghostget developer resources](https://ghostget.com/docs/tutorials/getting-started/) -- [Ghostget home](https://ghostget.com/) -- [About Ghostget](https://ghostget.com/about/) -- [Contact and security reporting](https://ghostget.com/contact/) -- [Privacy and data custody](https://ghostget.com/privacy/) diff --git a/website/source/status-page.ts b/website/source/status-page.ts new file mode 100644 index 00000000..d521b28e --- /dev/null +++ b/website/source/status-page.ts @@ -0,0 +1,6 @@ +import { attachStatusPage } from "@hraness/design-kit/browser"; + +if (typeof document !== "undefined") { + const root = document.querySelector(".hraness-status-page"); + if (root !== null) attachStatusPage(root); +} diff --git a/website/source/styles.css b/website/source/styles.css index f9f30aed..a2442fb2 100644 --- a/website/source/styles.css +++ b/website/source/styles.css @@ -618,8 +618,7 @@ code:not(pre code):not(.hraness-marketing-flow__code) { } .section-heading h2, -.final-section h2, -.route-state h1 { +.final-section h2 { letter-spacing: -0.068em; line-height: 0.96; margin: 0; @@ -1480,45 +1479,6 @@ code:not(pre code):not(.hraness-marketing-flow__code) { max-width: 50rem; } -.route-state { - align-content: center; - display: grid; - min-height: 100svh; - padding: var(--section-block) var(--page-inline); -} - -.route-state > * { - max-width: 50rem; -} - -.route-state > p:not(.eyebrow, .visually-hidden) { - color: var(--muted); - font-size: clamp(1rem, 1.8vw, 1.2rem); - line-height: 1.65; - margin: 1.5rem 0 1.25rem; -} - -.route-state .recovery-links { - color: var(--muted); - line-height: 1.65; - margin: 0 0 2rem; - padding-left: 1.2rem; -} - -.route-state .recovery-links a { - color: var(--foreground); -} - -.route-state .button { - justify-self: start; -} - -.route-state > .privacy-note { - font-size: 0.78rem; - margin: 1.25rem 0 0; - max-width: 40rem; -} - .visually-hidden { clip: rect(0 0 0 0); clip-path: inset(50%); @@ -2062,8 +2022,7 @@ body { } .section-heading .eyebrow, -.guide-hero .eyebrow, -.route-state .eyebrow { +.guide-hero .eyebrow { margin: 0 0 0.75rem; } @@ -2085,7 +2044,6 @@ body { * text face. The inert preview keeps its larger hero size below. */ .hero h1, .guide-hero h1, -.route-state h1, .preview-copy h1 { font-family: var(--hraness-type-h1-font); font-weight: var(--hraness-type-h1-weight); @@ -2105,8 +2063,7 @@ body { text-wrap: balance; } -.guide-hero h1, -.route-state h1 { +.guide-hero h1 { font-size: var(--hraness-type-h1-size); margin: 0 0 1.25rem; max-width: 22ch;