## Purpose Defines the `BaseLayout.astro` component that provides the complete HTML document shell for every page of the Qumo website, including all `` meta tags, font preloading, hreflang alternates, JSON-LD support, and the Nav/Footer stub components. ## Requirements ### Requirement: BaseLayout provides complete HTML document shell `src/layouts/BaseLayout.astro` SHALL render a complete HTML document (``, ``, ``, ``) that every page can wrap its content in. It SHALL accept the following props: - `title: string` — page-specific `` and OG title - `description: string` — page-specific meta description and OG description - `locale?: string` — BCP 47 language tag, default `"en"` - `canonicalUrl: string` — absolute canonical URL for this page - `alternateUrl: string` — absolute URL of the alternate-locale version of this page - `ogImage?: string` — absolute URL for `og:image`; tag is omitted when falsy - `jsonLd?: Record<string, unknown>` — structured data object to serialize as JSON-LD #### Scenario: Page renders with all head meta tags - **WHEN** a page wraps its content in `<BaseLayout>` with all props provided - **THEN** the rendered HTML `<head>` SHALL contain: `<meta charset="utf-8">`, `<meta name="viewport">`, `<link rel="icon">` for both SVG and ICO favicons, `<link rel="canonical">`, `<link rel="alternate" hreflang>` for both locales, `<meta property="og:*">` tags, `<meta name="twitter:*">` tags, and `<title>` #### Scenario: OG image tag omitted when ogImage is falsy - **WHEN** `ogImage` prop is an empty string or not provided - **THEN** no `<meta property="og:image">` tag SHALL appear in the rendered HTML #### Scenario: Body has brand defaults - **WHEN** any page uses BaseLayout - **THEN** the `<body>` element SHALL have Tailwind classes `bg-midnight text-snow font-archia` applied ### Requirement: BaseLayout preloads critical Archia font weights The `<head>` SHALL contain `<link rel="preload" as="font" type="font/woff2" crossorigin>` for Archia Regular (400), SemiBold (600), and Bold (700) woff2 files. Thin (100) and Light (300) SHALL NOT be preloaded. #### Scenario: Correct preload tags rendered - **WHEN** the page HTML is rendered - **THEN** exactly three font preload links SHALL appear in `<head>`: one each for `archia-regular-webfont.woff2`, `archia-semibold-webfont.woff2`, and `archia-bold-webfont.woff2` ### Requirement: BaseLayout renders hreflang alternates The `<head>` SHALL contain `<link rel="alternate" hreflang>` tags for both `en` and `nl` locales, plus an `x-default` pointing to the EN URL. #### Scenario: EN page hreflang output - **WHEN** a page passes `locale="en"`, `canonicalUrl="https://qumo.io/about"`, `alternateUrl="https://qumo.io/nl/about"` - **THEN** the head SHALL contain: - `<link rel="alternate" hreflang="en" href="https://qumo.io/about">` - `<link rel="alternate" hreflang="nl" href="https://qumo.io/nl/about">` - `<link rel="alternate" hreflang="x-default" href="https://qumo.io/about">` #### Scenario: NL page hreflang output - **WHEN** a page passes `locale="nl"`, `canonicalUrl="https://qumo.io/nl/about"`, `alternateUrl="https://qumo.io/about"` - **THEN** the head SHALL contain the same three hreflang tags with EN/NL values swapped appropriately, and `x-default` SHALL always point to the EN URL (the `alternateUrl` on NL pages) ### Requirement: BaseLayout supports JSON-LD injection via prop When the `jsonLd` prop is provided, BaseLayout SHALL render a `<script type="application/ld+json">` tag in `<head>` containing the JSON-serialized value. #### Scenario: JSON-LD rendered from prop - **WHEN** `jsonLd={{ "@context": "https://schema.org", "@type": "WebPage", "name": "About" }}` is passed - **THEN** the head SHALL contain `<script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","name":"About"}</script>` #### Scenario: No JSON-LD script when prop omitted - **WHEN** `jsonLd` prop is not provided - **THEN** no `<script type="application/ld+json">` tag SHALL appear in the rendered head (beyond any injected via the named slot) ### Requirement: BaseLayout supports additional JSON-LD via named slot A `<slot name="jsonld" />` SHALL be provided inside `<head>` for pages that need additional or multiple structured data blocks beyond the `jsonLd` prop. #### Scenario: Named slot accepts additional script - **WHEN** a page passes `<script slot="jsonld" type="application/ld+json">{"@type":"BreadcrumbList"}</script>` - **THEN** that script tag SHALL appear in the rendered `<head>` ### Requirement: BaseLayout includes Nav and Footer stub components `BaseLayout.astro` SHALL import `src/components/Nav.astro` and `src/components/Footer.astro` and render them at the top and bottom of `<body>` respectively. Both components are stubs that render nothing in this step. #### Scenario: Nav and Footer positions in body - **WHEN** any page uses BaseLayout - **THEN** the `<body>` structure SHALL be: Nav stub output → `<slot />` (page content) → Footer stub output ### Requirement: BaseLayout content slot renders page content A default `<slot />` SHALL be provided between Nav and Footer for page-specific content. #### Scenario: Page content appears in slot - **WHEN** a page places markup inside `<BaseLayout>...</BaseLayout>` - **THEN** that markup SHALL appear in the rendered body between Nav and Footer