$ErrorActionPreference = 'Stop' $repoRoot = Split-Path -Parent $PSScriptRoot $pagesJsonPath = Join-Path $repoRoot 'pages.json' $componentPath = Join-Path $repoRoot 'components/GenealogyPageBackground.vue' $assetRelativePath = 'static/assets/modules/genealogy/opaque/genealogy-page-background-long.png' $assetPath = Join-Path $repoRoot $assetRelativePath function Assert-Contract { param( [bool]$Condition, [string]$Message ) if (-not $Condition) { throw "G shared background contract failed: $Message" } } $pagesConfig = Get-Content -Raw -Encoding UTF8 $pagesJsonPath | ConvertFrom-Json $genealogyRoutes = @( $pagesConfig.pages | Where-Object { $_.path -match '^pages/genealogy/g\d{2}-' } | ForEach-Object { $_.path } ) Assert-Contract ($genealogyRoutes.Count -eq 9) "expected 9 active G routes from pages.json, got $($genealogyRoutes.Count)" Assert-Contract (Test-Path -LiteralPath $componentPath) 'missing components/GenealogyPageBackground.vue' Assert-Contract (Test-Path -LiteralPath $assetPath) "missing $assetRelativePath" $componentSource = Get-Content -Raw -Encoding UTF8 $componentPath Assert-Contract ($componentSource -match [regex]::Escape('/static/assets/modules/genealogy/opaque/genealogy-page-background-long.png')) 'component must own the approved long runtime asset path' Assert-Contract ($componentSource -match 'mode="widthFix"') 'shared artwork must use widthFix' Assert-Contract ($componentSource -match '(?s)\.genealogy-page-background\s*\{.*position:\s*fixed;.*inset:\s*0;.*background:\s*#[0-9a-fA-F]{6};.*pointer-events:\s*none;') 'shared background layer must be fixed, full-screen, paper-colored, and non-interactive' Assert-Contract ($componentSource -match '(?s)\.genealogy-page-background__art\s*\{.*width:\s*100%;') 'shared artwork must render at full page width' Assert-Contract ($componentSource -match '(?s)\.genealogy-page-background__art\s*\{.*bottom:\s*0;') 'shared artwork must stay bottom-aligned on tall viewports' Assert-Contract ($componentSource -notmatch '(?s)\.genealogy-page-background__art\s*\{[^}]*top:\s*0;') 'shared artwork must not remain top-aligned' Assert-Contract ($componentSource -match '(?s)\.genealogy-page-background__art\s*\{[^}]*opacity:\s*0\.28;') 'shared artwork must use the approved 28% opacity' foreach ($route in $genealogyRoutes) { $pagePath = Join-Path $repoRoot ($route + '.vue') Assert-Contract (Test-Path -LiteralPath $pagePath) "missing route file $route.vue" $pageSource = Get-Content -Raw -Encoding UTF8 $pagePath Assert-Contract ($pageSource -match 'import\s+GenealogyPageBackground\s+from\s+["'']@/components/GenealogyPageBackground\.vue["''];?') "$route must explicitly import GenealogyPageBackground" $componentCount = ([regex]::Matches($pageSource, '')).Count Assert-Contract ($componentCount -eq 1) "$route must render exactly one GenealogyPageBackground, got $componentCount" Assert-Contract ($pageSource -notmatch 'page-paper\.jpg') "$route still references page-paper.jpg" Assert-Contract ($pageSource -notmatch 'footer-mountain-bamboo\.png') "$route still references footer-mountain-bamboo.png" Assert-Contract ($pageSource -notmatch [regex]::Escape('/static/assets/modules/genealogy/opaque/genealogy-page-background-long.png')) "$route must consume the component instead of the asset directly" Assert-Contract ($pageSource -notmatch '(?s)\.genealogy-page-background__art\s*\{[^}]*opacity:') "$route must not override the shared background opacity" } Write-Output 'PASS G genealogy shared background contract'