## ADDED Requirements ### Requirement: Every public page emits title, description and canonical The system SHALL render a unique ``, a `<meta name="description">` and a `<link rel="canonical">` on every public route (SPEC §6.6, §19). Portfolio cases MUST use `meta_title`/`meta_description` when filled and fall back to title/summary otherwise. Pages without page-level metadata MUST fall back to `default_meta_title` and `default_meta_description` from `site_settings`. #### Scenario: Page-level metadata overrides defaults - **GIVEN** a published case with `meta_title` and `meta_description` filled - **WHEN** a visitor loads the case detail - **THEN** the rendered title and description MUST use the case values #### Scenario: Missing metadata falls back to site defaults - **GIVEN** a published case without `meta_title` - **WHEN** a visitor loads the case detail - **THEN** the rendered title MUST be derived from the case title - **AND** the description MUST fall back to the case summary or the site default #### Scenario: Canonical points to the absolute route URL - **WHEN** any public page is rendered - **THEN** the canonical URL MUST be the absolute URL of that route without query parameters ### Requirement: Open Graph metadata is emitted for sharing The system SHALL emit Open Graph tags (`og:title`, `og:description`, `og:type`, `og:url`, `og:image`) on public pages. The image MUST use the page cover image when available and `default_og_image_path` from `site_settings` otherwise. #### Scenario: Case detail uses its cover as OG image - **GIVEN** a published case with a cover image - **WHEN** the case detail is rendered - **THEN** `og:image` MUST reference the case cover image URL #### Scenario: Pages without cover use the default OG image - **WHEN** a page without its own image is rendered - **THEN** `og:image` MUST reference `default_og_image_path` ### Requirement: Sitemap and robots are served by the application The system SHALL serve `/sitemap.xml` listing the home, institutional routes, the services listing, the portfolio listing and every published case slug with its last modification date. `/robots.txt` MUST be served by an application route referencing the sitemap URL. #### Scenario: Sitemap contains only published slugs - **GIVEN** one published case and one draft case - **WHEN** `/sitemap.xml` is requested - **THEN** the response MUST include the published slug - **AND** MUST NOT include the draft slug #### Scenario: Newly published case enters the sitemap - **WHEN** an admin publishes a case - **THEN** the case slug MUST appear in `/sitemap.xml` on the next request #### Scenario: Robots references the sitemap - **WHEN** `/robots.txt` is requested - **THEN** the response MUST be `text/plain` - **AND** MUST contain the absolute `/sitemap.xml` URL ### Requirement: Basic structured data is emitted where applicable The system SHALL emit JSON-LD structured data: `Organization` on the home using `site_settings`, and `Article` or equivalent creative work on the case detail. #### Scenario: Home exposes organization data - **WHEN** the home is rendered - **THEN** a JSON-LD block of type `Organization` MUST be present with brand name and contact data #### Scenario: Structured data is valid JSON - **WHEN** any public page emits JSON-LD - **THEN** the script content MUST parse as valid JSON ### Requirement: Analytics script is injected only when explicitly enabled The system SHALL render the analytics snippet from `site_settings` only when `analytics_enabled` is true and the script field is non-empty (WEB-06). Analytics MUST be disabled by default. #### Scenario: Analytics disabled emits nothing - **GIVEN** `analytics_enabled` is false - **WHEN** any public page is rendered - **THEN** the analytics snippet MUST NOT appear in the HTML #### Scenario: Analytics enabled injects the configured snippet - **GIVEN** `analytics_enabled` is true and a snippet is configured - **WHEN** a public page is rendered - **THEN** the snippet MUST be present exactly once