diff --git a/site/build-site.mjs b/site/build-site.mjs index 10419ab..c19663f 100755 --- a/site/build-site.mjs +++ b/site/build-site.mjs @@ -866,6 +866,13 @@ const stylesheet = ` --captured-artifact-bg: #ffffff; --fixed-light-ink: #ffffff; --hero-art-bg: #08181d; + --homepage-film-control-ink: var(--accent-strong); + --homepage-film-control-surface: color-mix(in srgb, var(--panel) 88%, transparent); + --homepage-film-control-surface-hover: color-mix(in srgb, var(--panel) 96%, transparent); + --homepage-film-control-surface-solid: var(--panel); + --homepage-film-control-border: color-mix(in srgb, var(--accent-strong) 28%, transparent); + --homepage-film-control-border-hover: color-mix(in srgb, var(--accent-strong) 44%, transparent); + --homepage-film-control-shadow: color-mix(in srgb, var(--accent-strong) 16%, transparent); --hero-art-overlay: linear-gradient(180deg, rgba(4, 18, 23, 0) 58%, rgba(4, 18, 23, 0.3) 100%), linear-gradient(90deg, rgba(255, 255, 255, 0.07), transparent 24%); @@ -936,6 +943,13 @@ const stylesheet = ` --bar-track: #29312e; --status-success-bg: rgba(130, 201, 154, 0.14); --status-warning-bg: rgba(224, 177, 93, 0.16); + --homepage-film-control-ink: var(--fixed-light-ink); + --homepage-film-control-surface: color-mix(in srgb, var(--hero-art-bg) 78%, transparent); + --homepage-film-control-surface-hover: color-mix(in srgb, var(--hero-art-bg) 88%, transparent); + --homepage-film-control-surface-solid: var(--hero-art-bg); + --homepage-film-control-border: color-mix(in srgb, var(--fixed-light-ink) 48%, transparent); + --homepage-film-control-border-hover: color-mix(in srgb, var(--fixed-light-ink) 64%, transparent); + --homepage-film-control-shadow: color-mix(in srgb, var(--hero-art-bg) 18%, transparent); --report-toc-bg: rgba(24, 29, 27, 0.88); --report-video-bg: #141b19; --report-video-copy-bg: rgba(24, 29, 27, 0.88); @@ -1483,7 +1497,6 @@ h2 { max-width: 1440px; } .homepage-film-figure { - position: relative; margin: 0; } .homepage-film-frame { @@ -1557,7 +1570,7 @@ h2 { width: min(236px, calc(100% - 28px)); padding: 0; border: 0; - color: var(--fixed-light-ink); + color: var(--homepage-film-control-ink); background: transparent; box-shadow: none; transform: none; @@ -1587,12 +1600,12 @@ h2 { left: 50%; width: 36px; height: 36px; - border: 1px solid color-mix(in srgb, var(--fixed-light-ink) 48%, transparent); + border: 1px solid var(--homepage-film-control-border); border-radius: 999px; - background: color-mix(in srgb, var(--hero-art-bg) 78%, transparent); + background: var(--homepage-film-control-surface); box-shadow: - 0 5px 14px color-mix(in srgb, var(--hero-art-bg) 18%, transparent), - inset 0 1px color-mix(in srgb, var(--fixed-light-ink) 9%, transparent); + 0 5px 14px var(--homepage-film-control-shadow), + inset 0 1px color-mix(in srgb, var(--homepage-film-control-ink) 9%, transparent); -webkit-backdrop-filter: blur(16px) saturate(1.16); backdrop-filter: blur(16px) saturate(1.16); content: ""; @@ -1601,14 +1614,14 @@ h2 { transition: background-color 140ms ease, border-color 140ms ease; } .homepage-film-control-button:hover::before { - border-color: color-mix(in srgb, var(--fixed-light-ink) 64%, transparent); - background: color-mix(in srgb, var(--hero-art-bg) 88%, transparent); + border-color: var(--homepage-film-control-border-hover); + background: var(--homepage-film-control-surface-hover); } .homepage-film-control-button:focus-visible, .homepage-film-scrubber:focus-visible { - outline: 2px solid var(--fixed-light-ink); + outline: 2px solid var(--homepage-film-control-ink); outline-offset: 2px; - box-shadow: 0 0 0 5px var(--hero-art-bg); + box-shadow: 0 0 0 5px var(--homepage-film-control-surface-solid); } .homepage-film-control-button svg { position: relative; @@ -1620,82 +1633,6 @@ h2 { .homepage-film-control-button svg[hidden] { display: none; } -.homepage-film-scroll-cue { - position: absolute; - z-index: 4; - top: min( - calc(100svh - var(--site-navigation-height) - 108px), - calc(100% - 108px) - ); - left: 50%; - isolation: isolate; - display: flex; - align-items: center; - justify-content: center; - width: 44px; - min-width: 44px; - height: 44px; - min-height: 44px; - padding: 0; - border: 0; - border-radius: 999px; - color: var(--fixed-light-ink); - background: transparent; - box-shadow: none; - text-decoration: none; - transform: translateX(-50%); -} -.homepage-film-scroll-cue::before { - position: absolute; - top: 50%; - left: 50%; - width: 36px; - height: 36px; - border: 1px solid color-mix(in srgb, var(--fixed-light-ink) 48%, transparent); - border-radius: 999px; - background: color-mix(in srgb, var(--hero-art-bg) 78%, transparent); - box-shadow: - 0 5px 14px color-mix(in srgb, var(--hero-art-bg) 18%, transparent), - inset 0 1px color-mix(in srgb, var(--fixed-light-ink) 9%, transparent); - -webkit-backdrop-filter: blur(16px) saturate(1.16); - backdrop-filter: blur(16px) saturate(1.16); - content: ""; - pointer-events: none; - transform: translate(-50%, -50%); - transition: background-color 140ms ease, border-color 140ms ease; -} -.homepage-film-scroll-cue:hover::before { - border-color: color-mix(in srgb, var(--fixed-light-ink) 64%, transparent); - background: color-mix(in srgb, var(--hero-art-bg) 88%, transparent); -} -.homepage-film-scroll-cue:focus-visible { - outline: 2px solid var(--fixed-light-ink); - outline-offset: 2px; - box-shadow: 0 0 0 5px var(--hero-art-bg); -} -.homepage-film-scroll-cue svg { - position: relative; - z-index: 1; - width: 18px; - height: 18px; - animation: homepage-film-scroll-cue-bob 2.2s ease-in-out 3; -} -.homepage-film-scroll-cue:hover svg, -.homepage-film-scroll-cue:focus-visible svg { - animation-play-state: paused; -} -.homepage-hero { - scroll-margin-top: var(--site-navigation-height); -} -@keyframes homepage-film-scroll-cue-bob { - 0%, - 100% { - transform: translateY(-2px); - } - 50% { - transform: translateY(2px); - } -} .homepage-film-scrubber { box-sizing: border-box; width: 100%; @@ -4562,15 +4499,6 @@ pre { padding: 0; transform: none; } - .homepage-film-scroll-cue { - position: relative; - top: auto; - right: auto; - bottom: auto; - left: auto; - margin: 8px auto 0; - transform: none; - } .homepage-film-control-button { width: 44px; min-width: 44px; @@ -4642,14 +4570,9 @@ pre { transition: none; } .homepage-film-controls, - .homepage-film-control-button::before, - .homepage-film-scroll-cue::before { + .homepage-film-control-button::before { transition: none; } - .homepage-film-scroll-cue svg { - animation: none; - transform: none; - } .hero-action:hover { transform: none; } @@ -4669,10 +4592,9 @@ pre { box-shadow: none; opacity: 1; } - .homepage-film-control-button::before, - .homepage-film-scroll-cue::before { - border: 2px solid var(--fixed-light-ink); - background: var(--hero-art-bg); + .homepage-film-control-button::before { + border: 2px solid var(--homepage-film-control-ink); + background: var(--homepage-film-control-surface-solid); box-shadow: none; -webkit-backdrop-filter: none; backdrop-filter: none; @@ -4712,8 +4634,7 @@ pre { box-shadow: none; opacity: 1; } - .homepage-film-control-button::before, - .homepage-film-scroll-cue::before { + .homepage-film-control-button::before { border: 1px solid ButtonText; color: ButtonText; background: ButtonFace; @@ -4752,8 +4673,7 @@ pre { box-shadow: none; } .homepage-film-control-button:focus-visible, - .homepage-film-scrubber:focus-visible, - .homepage-film-scroll-cue:focus-visible { + .homepage-film-scrubber:focus-visible { outline-color: Highlight; } .homepage-film-scrubber { @@ -4856,13 +4776,10 @@ function homepage() { - - ${renderHomepageFilmControlIcon("chevron-down", "data-homepage-film-scroll-icon")} - -
+

Product judgment for AI-generated UI

diff --git a/tests/site-local-server.test.mjs b/tests/site-local-server.test.mjs index 24b876f..e52221f 100644 --- a/tests/site-local-server.test.mjs +++ b/tests/site-local-server.test.mjs @@ -449,6 +449,11 @@ try { await assertStaticGetAndHead(url, "/assets/site.css", "text/css; charset=utf-8", (body) => { const css = body.toString("utf8"); const controlsCss = cssRuleBody(css, ".homepage-film-controls"); + assert.match( + controlsCss, + /color:\s*var\(--homepage-film-control-ink\);/, + "served player icons should follow the current site theme", + ); for (const property of [ "border", @@ -498,6 +503,8 @@ try { const buttonSurfaceCss = cssRuleBody(css, ".homepage-film-control-button::before"); assert.match(buttonSurfaceCss, /width:\s*36px;/); assert.match(buttonSurfaceCss, /height:\s*36px;/); + assert.match(buttonSurfaceCss, /background:\s*var\(--homepage-film-control-surface\);/); + assert.match(buttonSurfaceCss, /border:\s*1px solid var\(--homepage-film-control-border\);/); const iconCss = cssRuleBody(css, ".homepage-film-control-button svg"); assert.match(iconCss, /width:\s*16px;/); assert.match(iconCss, /height:\s*16px;/); @@ -530,27 +537,22 @@ try { const sectionCss = cssRuleBody(css, ".homepage-film-section"); const shellCss = cssRuleBody(css, ".homepage-film-shell"); - const figureCss = cssRuleBody(css, ".homepage-film-figure"); const frameCss = cssRuleBody(css, ".homepage-film-frame"); - const scrollCueCss = cssRuleBody(css, ".homepage-film-scroll-cue"); - const scrollCueSurfaceCss = cssRuleBody(css, ".homepage-film-scroll-cue::before"); - const scrollCueIconCss = cssRuleBody(css, ".homepage-film-scroll-cue svg"); - assert.match(figureCss, /position:\s*relative;/); - assert.match(scrollCueCss, /position:\s*absolute;/); - assert.match(scrollCueCss, /width:\s*44px;/); - assert.match(scrollCueCss, /height:\s*44px;/); - assert.match(scrollCueCss, /border-radius:\s*999px;/); - assert.match(scrollCueSurfaceCss, /width:\s*36px;/); - assert.match(scrollCueSurfaceCss, /height:\s*36px;/); - assert.match(scrollCueIconCss, /animation:\s*homepage-film-scroll-cue-bob/); - assert.match(css, /@keyframes homepage-film-scroll-cue-bob/); - assert.match( - css, - /@media \(prefers-reduced-motion: reduce\)\s*\{[\s\S]*?\.homepage-film-scroll-cue svg\s*\{[\s\S]*?animation:\s*none;/, - ); - assert.match( + for (const token of [ + "--homepage-film-control-ink", + "--homepage-film-control-surface", + "--homepage-film-control-surface-solid", + "--homepage-film-control-border", + ]) { + assert.match(css, new RegExp(`:root\\s*\\{[^}]*${token}:`, "s")); + assert.match( + css, + new RegExp(`@media \\(prefers-color-scheme: dark\\)\\s*\\{\\s*:root\\s*\\{[^}]*${token}:`, "s"), + ); + } + assert.doesNotMatch( css, - /@media \(max-width: 560px\)\s*\{[\s\S]*?\.homepage-film-scroll-cue\s*\{[\s\S]*?position:\s*relative;[\s\S]*?margin:\s*8px auto 0;/, + /\.homepage-film-scroll-cue|homepage-film-scroll-cue-bob/, ); for (const property of ["padding", "border", "background", "box-shadow"]) { const value = cssDeclarationValue(frameCss, property); @@ -741,32 +743,16 @@ try { assert.match(iconTag("volume-x"), /(?:\s|^)hidden(?:\s|>)/); assert.doesNotMatch(playButtonMarkup + muteButtonMarkup, /]*class="[^"]*\bhomepage-film-scroll-cue\b[^"]*"[^>]*>[\s\S]*?<\/a>/i, - )?.[0] ?? ""; - assert.ok(scrollCueMarkup, "homepage should serve a down-chevron content cue"); - assert.match(scrollCueMarkup, /href="#homepage-overview"/); - assert.match(scrollCueMarkup, /aria-label="Continue to JudgmentKit overview"/); - assert.match(scrollCueMarkup, /data-homepage-film-scroll-cue(?:\s|=|>)/); - assert.deepEqual( - [...scrollCueMarkup.matchAll(/data-icon-id="([^"]+)"/g)].map((match) => match[1]), - ["chevron-down"], - ); assert.equal(controls.includes("data-homepage-film-scroll-cue"), false); - const scrollCueIndex = html.search(/class="[^"]*\bhomepage-film-scroll-cue\b[^"]*"/); + assert.doesNotMatch( + html, + /homepage-film-scroll-cue|data-homepage-film-scroll-icon|Continue to JudgmentKit overview|id="homepage-overview"/, + ); + const filmSectionIndex = html.search(/class="[^"]*\bhomepage-film-section\b[^"]*"/); const heroIndex = html.search(/class="[^"]*\bhomepage-hero\b[^"]*"/); assert.ok( - filmIndex >= 0 && - scrollCueIndex >= 0 && - heroIndex >= 0 && - filmIndex < scrollCueIndex && - scrollCueIndex < heroIndex, - "homepage scroll cue should bridge the film and the existing hero", - ); - assert.match( - html.match(/]*class="[^"]*\bhomepage-hero\b[^"]*"[^>]*>/)?.[0] ?? "", - /id="homepage-overview"/, + filmSectionIndex >= 0 && heroIndex >= 0 && filmSectionIndex < heroIndex, + "served homepage should keep the live film ahead of the existing hero", ); assert.doesNotMatch(frame, /<(?:h[1-6]|p|figcaption)\b/i); const filmScripts = [...html.matchAll(/]*>([\s\S]*?)<\/script>/gi)] diff --git a/tests/site.test.mjs b/tests/site.test.mjs index e173b36..a1d3987 100644 --- a/tests/site.test.mjs +++ b/tests/site.test.mjs @@ -742,55 +742,25 @@ assert.equal( "the live HTML renderer should replace the canvas presentation surface", ); assert.equal((homepageMain.match(/]*class="[^"]*\bhomepage-film-scroll-cue\b[^"]*"[^>]*>[\s\S]*?<\/a>/gi, - ), -].map((match) => match[0]); -assert.equal( - homepageFilmScrollCueMatches.length, - 1, - "homepage should expose one visual cue that more content follows the film", -); -const homepageFilmScrollCue = homepageFilmScrollCueMatches[0]; -const homepageFilmScrollCueOpenTag = - homepageFilmScrollCue.match(/]*>/i)?.[0] ?? ""; -assert.match(homepageFilmScrollCueOpenTag, /href="#homepage-overview"/); -assert.match( - homepageFilmScrollCueOpenTag, - /aria-label="Continue to JudgmentKit overview"/, -); -assert.match( - homepageFilmScrollCueOpenTag, - /data-homepage-film-scroll-cue(?:\s|=|>)/, +assert.doesNotMatch( + homepageMain, + /homepage-film-scroll-cue|data-homepage-film-scroll-icon|Continue to JudgmentKit overview/, + "the homepage should not place a jump control over or after the live demo", ); -assert.deepEqual( - [...homepageFilmScrollCue.matchAll(/data-icon-id="([^"]+)"/g)].map( - (match) => match[1], - ), - ["chevron-down"], - "the scroll cue should use JudgmentKit's canonical down-chevron icon", +assert.doesNotMatch( + homepageMain, + /id="homepage-overview"/, + "the retired jump control should not leave an unused overview target", ); -assert.match(homepageFilmScrollCue, /]*aria-hidden="true"/i); -const homepageFilmScrollCueIndex = homepageMain.search( - /class="[^"]*\bhomepage-film-scroll-cue\b[^"]*"/, +const homepageFilmSectionIndex = homepageMain.search( + /class="[^"]*\bhomepage-film-section\b[^"]*"/, ); const homepageHeroIndex = homepageMain.search(/class="[^"]*\bhomepage-hero\b[^"]*"/); assert.ok( - homepageFilmFrameIndex >= 0 && - homepageFilmScrollCueIndex >= 0 && + homepageFilmSectionIndex >= 0 && homepageHeroIndex >= 0 && - homepageFilmFrameIndex < homepageFilmScrollCueIndex && - homepageFilmScrollCueIndex < homepageHeroIndex, - "homepage scroll cue should bridge the film and the existing hero", -); -assert.match( - homepageMain.match(/]*class="[^"]*\bhomepage-hero\b[^"]*"[^>]*>/)?.[0] ?? "", - /id="homepage-overview"/, - "the scroll cue should target the product overview that follows the film", + homepageFilmSectionIndex < homepageHeroIndex, + "the live film should remain immediately ahead of the existing homepage story", ); assert.match( homepageFilmMediaOpenTag, @@ -1401,6 +1371,11 @@ assert.match( "hidden custom controls should not flash before enhancement", ); const homepageFilmControlsCss = cssRuleBody(siteCss, ".homepage-film-controls"); +assert.match( + homepageFilmControlsCss, + /color:\s*var\(--homepage-film-control-ink\);/, + "player icons should inherit the current light or dark site theme instead of fixed light ink", +); for (const property of [ "border", "background", @@ -1457,7 +1432,12 @@ assert.match(homepageFilmControlButtonSurfaceCss, /width:\s*36px;/); assert.match(homepageFilmControlButtonSurfaceCss, /height:\s*36px;/); assert.match( homepageFilmControlButtonSurfaceCss, - /background:\s*color-mix\(in srgb, var\(--hero-art-bg\) 78%, transparent\);/, + /background:\s*var\(--homepage-film-control-surface\);/, + "player button surfaces should use the theme-aware film-control surface", +); +assert.match( + homepageFilmControlButtonSurfaceCss, + /border:\s*1px solid var\(--homepage-film-control-border\);/, ); assert.ok( !/border:\s*(?:0|none|transparent)\s*;/i.test(homepageFilmControlButtonSurfaceCss), @@ -1489,7 +1469,7 @@ assert.match(homepageFilmWebkitTrackCss, /height:\s*4px;/); assert.match( homepageFilmWebkitTrackCss, /box-shadow:\s*0 0 0 1px color-mix\(in srgb, var\(--hero-art-bg\) 58%, transparent\)/, - "the thin scrubber track should retain a subtle dual-contrast edge", + "the thin scrubber track should retain its established dual-contrast edge", ); const homepageFilmWebkitThumbCss = cssRuleBody( siteCss, @@ -1516,8 +1496,8 @@ assert.match(homepageFilmMozThumbCss, /width:\s*(?:14|15)px;/); assert.match(homepageFilmMozThumbCss, /height:\s*(?:14|15)px;/); assert.match( siteCss, - /\.homepage-film-control-button:focus-visible,\s*\n\.homepage-film-scrubber:focus-visible\s*\{[^}]*outline:\s*2px solid var\(--fixed-light-ink\);[^}]*box-shadow:\s*0 0 0 5px var\(--hero-art-bg\);/s, - "both floating control types should retain a dual-contrast keyboard-focus treatment", + /\.homepage-film-control-button:focus-visible,\s*\n\.homepage-film-scrubber:focus-visible\s*\{[^}]*outline:\s*2px solid var\(--homepage-film-control-ink\);[^}]*box-shadow:\s*0 0 0 5px var\(--homepage-film-control-surface-solid\);/s, + "both floating control types should retain a theme-aware dual-contrast keyboard-focus treatment", ); assert.match( siteCss, @@ -1526,61 +1506,34 @@ assert.match( ); const homepageFilmSectionCss = cssRuleBody(siteCss, ".homepage-film-section"); const homepageFilmShellCss = cssRuleBody(siteCss, ".homepage-film-shell"); -const homepageFilmFigureCss = cssRuleBody(siteCss, ".homepage-film-figure"); const homepageFilmFrameCss = cssRuleBody(siteCss, ".homepage-film-frame"); -const homepageFilmScrollCueCss = cssRuleBody(siteCss, ".homepage-film-scroll-cue"); -const homepageFilmScrollCueSurfaceCss = cssRuleBody( - siteCss, - ".homepage-film-scroll-cue::before", -); -const homepageFilmScrollCueIconCss = cssRuleBody( - siteCss, - ".homepage-film-scroll-cue svg", -); -assert.match(homepageFilmFigureCss, /position:\s*relative;/); -assert.match(homepageFilmScrollCueCss, /position:\s*absolute;/); -assert.match(homepageFilmScrollCueCss, /z-index:\s*[34];/); -assert.match(homepageFilmScrollCueCss, /left:\s*50%;/); -assert.match(homepageFilmScrollCueCss, /width:\s*44px;/); -assert.match(homepageFilmScrollCueCss, /min-width:\s*44px;/); -assert.match(homepageFilmScrollCueCss, /height:\s*44px;/); -assert.match(homepageFilmScrollCueCss, /min-height:\s*44px;/); -assert.match(homepageFilmScrollCueCss, /border-radius:\s*999px;/); -assert.match(homepageFilmScrollCueCss, /transform:\s*translateX\(-50%\);/); -assert.match( - homepageFilmScrollCueCss, - /top:\s*min\(\s*calc\(100svh - var\(--site-navigation-height\) - 108px\),\s*calc\(100% - 108px\)\s*\);/, - "the scroll cue should remain visible within a desktop viewport before scrolling", -); -assert.match(homepageFilmScrollCueSurfaceCss, /width:\s*36px;/); -assert.match(homepageFilmScrollCueSurfaceCss, /height:\s*36px;/); -assert.match(homepageFilmScrollCueSurfaceCss, /border-radius:\s*999px;/); -assert.match(homepageFilmScrollCueSurfaceCss, /backdrop-filter:\s*blur\(/); -assert.match(homepageFilmScrollCueIconCss, /width:\s*(?:16|18)px;/); -assert.match(homepageFilmScrollCueIconCss, /height:\s*(?:16|18)px;/); -assert.match( - homepageFilmScrollCueIconCss, - /animation:\s*homepage-film-scroll-cue-bob\s+(?:2|2\.[0-9]+)s\s+ease-in-out\s+3;/, -); -assert.match( - siteCss, - /@keyframes homepage-film-scroll-cue-bob\s*\{[\s\S]*?translateY\(-2px\)[\s\S]*?translateY\(2px\)[\s\S]*?\}/, - "the down chevron should use a restrained four-pixel vertical motion range", -); -assert.match( - siteCss, - /\.homepage-film-scroll-cue:focus-visible\s*\{[^}]*outline:\s*2px solid var\(--fixed-light-ink\);[^}]*box-shadow:\s*0 0 0 5px var\(--hero-art-bg\);/s, - "the scroll cue should retain a dual-contrast keyboard focus treatment", -); -assert.match( - siteCss, - /@media \(max-width: 560px\)\s*\{[\s\S]*?\.homepage-film-scroll-cue\s*\{[\s\S]*?position:\s*relative;[\s\S]*?top:\s*auto;[\s\S]*?left:\s*auto;[\s\S]*?margin:\s*8px auto 0;[\s\S]*?transform:\s*none;/, - "the cue should enter normal flow on narrow screens instead of covering the player", -); -assert.match( +for (const token of [ + "--homepage-film-control-ink", + "--homepage-film-control-surface", + "--homepage-film-control-surface-hover", + "--homepage-film-control-surface-solid", + "--homepage-film-control-border", + "--homepage-film-control-border-hover", + "--homepage-film-control-shadow", +]) { + assert.ok( + lightAppearanceTokens.has(token), + `the light appearance should define ${token}`, + ); + assert.ok( + darkAppearanceTokens.has(token), + `the dark appearance should define ${token}`, + ); + assert.notEqual( + lightAppearanceTokens.get(token), + darkAppearanceTokens.get(token), + `${token} should adapt between light and dark appearance`, + ); +} +assert.doesNotMatch( siteCss, - /@media \(prefers-reduced-motion: reduce\)\s*\{[\s\S]*?\.homepage-film-scroll-cue svg\s*\{[\s\S]*?animation:\s*none;/, - "the scroll cue should stop moving when reduced motion is requested", + /\.homepage-film-scroll-cue|homepage-film-scroll-cue-bob/, + "retired jump-control styling and motion should be removed from the site bundle", ); const homepageFilmStageCss = cssRuleBody(siteCss, ".homepage-film-stage"); const homepageFilmSurfaceCss = cssRuleBody(