diff --git a/public/css/scangov.css b/public/css/scangov.css index 3b6cdbd9..2b45dd9e 100644 --- a/public/css/scangov.css +++ b/public/css/scangov.css @@ -498,15 +498,19 @@ button:not(.card *) { } } -/* Skip link — visible on focus for keyboard users (WCAG 2.4.1) */ +/* Skip link — visible on focus for keyboard users (WCAG 2.4.1). Hidden by + Bootstrap's .visually-hidden-focusable until it takes focus; never pair it + with .visually-hidden, which stays clipped on focus. */ .skip-link { position: absolute; - top: -100%; - left: 0; -} - -.skip-link:focus { top: 0; + left: 0; + z-index: 1100; + padding: 0.5rem 1rem; + background-color: var(--bs-body-bg); + color: var(--bs-link-color); + border: 1px solid var(--bs-border-color); + border-top: 0; } /* Navbar */ @@ -1933,6 +1937,19 @@ iframe { border: 1px solid var(--bs-border-color) !important; } +/* Video embeds (lite-youtube-embed, see the youtube shortcode). The + component caps itself at 720px; fill the column like the old iframe did. */ +lite-youtube { + max-width: 100%; +} + +/* The play button covers the whole poster and the component clips paint at + its edge, so the site's offset focus ring would be cut off. Draw it inset. */ +lite-youtube > .lyt-playbtn:focus-visible { + outline: 3px solid #fff; + outline-offset: -5px; +} + /* News posts */ .post-content h2 { diff --git a/scripts/bundle.css b/scripts/bundle.css index 0589033a..916e5ee7 100644 --- a/scripts/bundle.css +++ b/scripts/bundle.css @@ -713,15 +713,19 @@ button:not(.card *) { } } -/* Skip link — visible on focus for keyboard users (WCAG 2.4.1) */ +/* Skip link — visible on focus for keyboard users (WCAG 2.4.1). Hidden by + Bootstrap's .visually-hidden-focusable until it takes focus; never pair it + with .visually-hidden, which stays clipped on focus. */ .skip-link { position: absolute; - top: -100%; - left: 0; -} - -.skip-link:focus { top: 0; + left: 0; + z-index: 1100; + padding: 0.5rem 1rem; + background-color: var(--bs-body-bg); + color: var(--bs-link-color); + border: 1px solid var(--bs-border-color); + border-top: 0; } /* Navbar */ @@ -2148,6 +2152,19 @@ iframe { border: 1px solid var(--bs-border-color) !important; } +/* Video embeds (lite-youtube-embed, see the youtube shortcode). The + component caps itself at 720px; fill the column like the old iframe did. */ +lite-youtube { + max-width: 100%; +} + +/* The play button covers the whole poster and the component clips paint at + its edge, so the site's offset focus ring would be cut off. Draw it inset. */ +lite-youtube > .lyt-playbtn:focus-visible { + outline: 3px solid #fff; + outline-offset: -5px; +} + /* News posts */ .post-content h2 { diff --git a/scripts/check-sitemap-completeness.js b/scripts/check-sitemap-completeness.js index b89207fb..f7a82fae 100644 --- a/scripts/check-sitemap-completeness.js +++ b/scripts/check-sitemap-completeness.js @@ -177,6 +177,22 @@ const sitemapPaths = new Set( [...sitemapXml.matchAll(/(.*?)<\/loc>/g)].map((m) => new URL(m[1]).pathname) ); +// The readability pages must stay in the sitemap. The checks below only catch a +// built page that is missing, so a removed or renamed page would go unnoticed. +const REQUIRED_READABILITY_PATHS = [ + '/readability/', + '/reading-level/', + '/automated-readability-index/', + '/flesch-kincaid/', + '/guidance/readability/', +]; +const requiredMissing = REQUIRED_READABILITY_PATHS.filter((p) => !sitemapPaths.has(p)); +if (requiredMissing.length > 0) { + console.error(`check-sitemap-completeness: ${requiredMissing.length} readability page(s) missing from sitemap.xml:\n`); + for (const p of requiredMissing) console.error(` ${p}`); + process.exit(1); +} + let missing, total; try { ({ missing, total } = await checkViaNdjson(siteRoot, sitemapPaths));