diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 05ed9df..941df82 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -37,6 +37,13 @@ jobs: run: pnpm install --frozen-lockfile - name: Typecheck (tsc over src/ + tests/ -- blocking) run: pnpm run typecheck + # UI-09: 0pt carried 82 axe color-contrast violations (impact=serious), the + # estate's largest single a11y defect, and every one came from a Tailwind + # alpha modifier on a text colour. The unmodified token measures 4.73:1 + # light / 7.63:1 dark and passes AA; every faded variant fails. Blocking, + # because a contrast regression is invisible in review. + - name: Text contrast shape (WCAG 2.2 AA 1.4.3 -- blocking) + run: pnpm run check:contrast - name: Test (vitest -- blocking) run: pnpm run test - name: Lint (eslint -- advisory; legacy tree has pre-existing noise) diff --git a/app/admin/verktyg/page.tsx b/app/admin/verktyg/page.tsx index db0e2a2..cddfa8e 100644 --- a/app/admin/verktyg/page.tsx +++ b/app/admin/verktyg/page.tsx @@ -88,7 +88,7 @@ function ScreenTool() { ))} )} -
{t({ en: "Requires verified OpenSanctions terms/key for full matches.", sv: "Kräver verifierade OpenSanctions-villkor/nyckel för fullständiga träffar." })}
+{t({ en: "Requires verified OpenSanctions terms/key for full matches.", sv: "Kräver verifierade OpenSanctions-villkor/nyckel för fullständiga träffar." })}
); diff --git a/app/bestall/beskriv/page.tsx b/app/bestall/beskriv/page.tsx index f688ff7..29440d9 100644 --- a/app/bestall/beskriv/page.tsx +++ b/app/bestall/beskriv/page.tsx @@ -79,7 +79,7 @@ export default function DescribePage() { ))} - {svc ?{t(svc.covers)} → {t(svc.deliverable)}
: null} + {svc ?{t(svc.covers)} → {t(svc.deliverable)}
: null}{rows.length} {t({ en: "sources · every field carries its provenance", sv: "källor · varje fält bär sin härkomst" })}
-+
{t({ en: "Licence terms verified before use", sv: "Licensvillkor verifieras innan användning" })}
{t(s.tagline)}
{t(s.covers)}
-→ {t(s.deliverable)}
+→ {t(s.deliverable)}
{t(cta.label)} → diff --git a/components/header.tsx b/components/header.tsx index 65831b2..2ad979d 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -123,7 +123,7 @@ export function Header() { className="flex items-baseline justify-between border-b border-border py-4 font-mono text-[12px] uppercase tracking-[0.12em] text-muted-foreground transition-colors hover:text-foreground" > {t(n.label)} - {String(i + 1).padStart(2, "0")} + {String(i + 1).padStart(2, "0")} ))}{m.code}
- {String(i + 1).padStart(2, "0")} + {String(i + 1).padStart(2, "0")}{m.live ? `${m.coverage}%` : t({ en: "coming", sv: "kommande" })} diff --git a/components/sections/featured-products-section.tsx b/components/sections/featured-products-section.tsx index 47807f9..2f3cdd3 100644 --- a/components/sections/featured-products-section.tsx +++ b/components/sections/featured-products-section.tsx @@ -91,7 +91,7 @@ export function FeaturedProductsSection() {
{d.markets}
- {String(i + 1).padStart(2, "0")} + {String(i + 1).padStart(2, "0")}{t(d.desc)}
diff --git a/components/sections/philosophy-section.tsx b/components/sections/philosophy-section.tsx index a04be34..525ef26 100644 --- a/components/sections/philosophy-section.tsx +++ b/components/sections/philosophy-section.tsx @@ -33,7 +33,7 @@ export function PhilosophySection() { className={`reveal delay-${i + 1} group relative grid grid-cols-[3.5rem_1fr] items-baseline gap-8 border-b border-border py-8 transition-[padding] duration-300 ease-out hover:pl-2`} > - + {n}0{i + 1}
+0{i + 1}
{ + for (const m of line.matchAll(PATTERN)) { + // Decorative text is exempt when the element declares itself hidden to AT. + // The attribute may sit on an earlier line of the same JSX element. + const window = lines.slice(Math.max(0, i - 6), i + 1).join('\n'); + if (/\baria-hidden\b/.test(window)) continue; + violations.push({ file: relative(ROOT, file), line: i + 1, token: m[0], text: line.trim() }); + } + }); +} + +if (violations.length) { + console.error(`\n✗ ${violations.length} alpha-faded text colour(s) found — WCAG 2.2 AA 1.4.3 (finding UI-09).\n`); + for (const v of violations) { + console.error(` ${v.file}:${v.line} ${v.token}`); + console.error(` ${v.text.slice(0, 160)}`); + } + console.error('\n Use the unmodified token (text-muted-foreground = 4.73:1 light / 7.63:1 dark).'); + console.error(' If the text really is decoration, put aria-hidden on the element.\n'); + process.exit(1); +} +console.log('✓ no alpha-faded text colours — WCAG 2.2 AA 1.4.3 contrast shape is clean (UI-09)');