Add educational censorship-resistance quiz as a native Astro page
CI / ci (push) Failing after 6m53s
CI / ci (push) Failing after 6m53s
Reworks the standalone-HTML quiz from PR #7 into the site's real architecture so it actually ships and respects every project rule. - /quiz, /fr/quiz, /nl/quiz — prerendered per-locale routes with correct canonical + hreflang (new optional `path` prop on Base.astro). - Progressive enhancement: the twelve questions and the full scoring key (bands + per-area guidance with deep links into the guide) are server-rendered and readable with JavaScript disabled; the client script only computes the live score and reveals the result panel. - CSP-clean: bundled same-origin script (no inline handlers), bar widths set via a CSS custom property through the CSSOM (no inline styles), no third-party request. docker/csp.conf needs no new hash. - i18n via the existing core+overlay model: src/data/quiz.json (neutral scoring) + src/i18n/content/<locale>/quiz.json (prose), loaded by a dedicated loadQuiz() that checks question/option/band parity in all three locales. Page chrome added to the `quiz` namespace (key parity tested). - Share is copy-link + native Web Share + X (already allowlisted); the third-party mastodonshare.com redirector from PR #7 is dropped. - TopBar "Contents" link made absolute (/#toc) so it works off the guide, plus a new "Quiz" nav link. New tests/e2e/quiz.spec.ts covers no-JS readability, scoring, the TOC link and zero third-party requests. Co-Authored-By: arnaudom <1535627+arnaudom@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+12
-7
@@ -8,17 +8,22 @@ import '../styles/components.css'
|
||||
|
||||
interface Props {
|
||||
locale: Locale
|
||||
// Subpage slug appended after the locale prefix ('' = home). Drives the
|
||||
// canonical + hreflang URLs so /quiz and /fr/quiz point at themselves.
|
||||
path?: string
|
||||
}
|
||||
|
||||
const { locale } = Astro.props
|
||||
const { locale, path = '' } = Astro.props
|
||||
const t = useT(locale)
|
||||
const site = Astro.site!
|
||||
|
||||
const canonical = new URL(`${localePath(locale)}/`.replace('//', '/'), site)
|
||||
const alternates = locales.map((l) => ({
|
||||
hreflang: l,
|
||||
href: new URL(`${localePath(l)}/`.replace('//', '/'), site).href,
|
||||
}))
|
||||
const localeRoot = (l: Locale) => `${localePath(l)}/`.replace('//', '/')
|
||||
const localeHref = (l: Locale) =>
|
||||
new URL(path ? `${localeRoot(l)}${path}` : localeRoot(l), site).href
|
||||
|
||||
const canonical = new URL(localeHref(locale))
|
||||
const alternates = locales.map((l) => ({ hreflang: l, href: localeHref(l) }))
|
||||
const xDefaultHref = new URL(path ? `/${path}` : '/', site).href
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
@@ -30,7 +35,7 @@ const alternates = locales.map((l) => ({
|
||||
<meta name="description" content={t('site.description')} />
|
||||
<link rel="canonical" href={canonical.href} />
|
||||
{alternates.map((a) => <link rel="alternate" hreflang={a.hreflang} href={a.href} />)}
|
||||
<link rel="alternate" hreflang="x-default" href={new URL('/', site).href} />
|
||||
<link rel="alternate" hreflang="x-default" href={xDefaultHref} />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="apple-touch-icon" href="/favicon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
Reference in New Issue
Block a user