CI (SIAX Cloud) / integration (push) Skipped
masterplan-lock / check (push) Successful in 7s
Security Scan (SIAX Cloud) / security (push) Successful in 20s
CI (SIAX Cloud) / security (push) Successful in 47s
masterplan-lock / check (pull_request) Successful in 10s
CI (SIAX Cloud) / quality (push) Successful in 1m11s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 32s
CI (SIAX Cloud) / security (pull_request) Successful in 42s
CI (SIAX Cloud) / integration (pull_request) Successful in 56s
CI (SIAX Cloud) / quality (pull_request) Successful in 59s
axe-core 4.10.2 mot 0pt.app 2026-09-26: en enda regel, color-contrast, impact=serious, 82 noder. Estatets största enskilda a11y-defekt. Alla 82 har samma form: en Tailwind-alfamodifierare på en textfärg. Uppmätt WCAG 2.2-kontrast mot bägge temabakgrunderna i styles/globals.css: text-muted-foreground 4.73:1 ljust 7.63:1 mörkt GODKÄNT text-muted-foreground/70 2.71:1 4.20:1 faller text-muted-foreground/60 2.29:1 3.37:1 faller text-muted-foreground/50 1.96:1 2.67:1 faller text-muted-foreground/40 1.69:1 2.11:1 faller text-foreground/20 1.57:1 1.74:1 faller Den omodifierade token klarar AA i bägge teman, så åtgärden är helt enkelt att aldrig tona den. Elva textställen bytte till text-muted-foreground. Kopieringsknappen i app/portal går från 1.57:1 till 4.73:1 i viloläge och 19.79:1 vid hover — den föll även 1.4.11 för icke-textkomponenter. Toastens stängknapp klarade redan 1.4.11 (3.71:1) men blir 4.73:1. Kvar med alfa är endast den dekorativa masthead-siffran i app/bestall, som redan bär aria-hidden + select-none + pointer-events-none. Den är undantagen både av WCAG och av axe, och guarden nedan tillåter den på just den grunden. scripts/ci/check-text-contrast.mjs är en blockerande källkodsgrind i quality-jobbet. Den är avsiktligt inte en axe-körning: den fullständiga axe-jobb som UI-09 också begär kräver build + serve i CI och spåras separat. Tills det finns stoppar den här grinden regressionen vid den form som orsakade defekten. Refs: UI-09 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
82 lines
3.8 KiB
TypeScript
82 lines
3.8 KiB
TypeScript
"use client";
|
|
|
|
import { useReveal } from "@/hooks/use-reveal";
|
|
import { useT } from "@/components/locale-provider";
|
|
import { MARKETS } from "@/lib/data";
|
|
|
|
const STATS: [string, { en: string; sv: string }][] = [
|
|
["4 980", { en: "records in the sample", sv: "poster i exempel" }],
|
|
["20", { en: "omitted. Never guessed", sv: "utelämnade. Ej gissade" }],
|
|
["3,2", { en: "sources per field", sv: "källor per fält" }],
|
|
];
|
|
|
|
export function CollectionSection() {
|
|
const t = useT();
|
|
const ref = useReveal<HTMLElement>();
|
|
const liveCount = MARKETS.filter((m) => m.live).length;
|
|
return (
|
|
<section ref={ref} id="tackning" className="border-b border-border bg-background">
|
|
<div className="mx-auto max-w-[80rem] px-6 py-32 lg:py-40">
|
|
<div className="flex items-baseline justify-between gap-6 border-b border-border pb-6">
|
|
<p className="reveal font-mono text-[11px] uppercase tracking-[0.18em] text-muted-foreground">{t({ en: "04 — Coverage", sv: "04 — Täckning" })}</p>
|
|
<p className="reveal delay-1 font-mono text-[11px] uppercase tracking-[0.18em] text-muted-foreground">{liveCount}/{MARKETS.length} live</p>
|
|
</div>
|
|
|
|
<div className="mt-12 grid items-end gap-y-8 lg:grid-cols-12 lg:gap-x-10">
|
|
<h2 className="reveal delay-1 col-span-8 text-[clamp(2.4rem,6vw,4.75rem)] font-medium leading-[0.95] tracking-[-0.03em]">
|
|
{t({ en: "Twenty markets.", sv: "Tjugo marknader." })}
|
|
<br />
|
|
{t({ en: "One model.", sv: "En modell." })}
|
|
</h2>
|
|
<p className="reveal-fade delay-2 col-span-4 font-mono text-[13px] leading-relaxed text-muted-foreground lg:text-right">
|
|
{t({ en: "Same engine. Same proof.", sv: "Samma motor. Samma bevis." })}
|
|
<br />
|
|
{t({ en: "Every country.", sv: "Varje land." })}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="reveal delay-2 mt-16 grid grid-cols-[repeat(auto-fill,minmax(108px,1fr))] gap-px border border-border bg-border">
|
|
{MARKETS.map((m, i) => (
|
|
<div
|
|
key={m.code}
|
|
className="group relative bg-background p-4 transition-opacity duration-300"
|
|
style={{ opacity: m.live ? 1 : 0.4 }}
|
|
>
|
|
<div className="flex items-baseline justify-between">
|
|
<p className="font-mono text-sm font-semibold tracking-tight">{m.code}</p>
|
|
<span className="font-mono text-[9px] text-muted-foreground">{String(i + 1).padStart(2, "0")}</span>
|
|
</div>
|
|
<p className="mt-1 font-mono text-[10px] uppercase tracking-[0.08em] text-muted-foreground">
|
|
{m.live ? `${m.coverage}%` : t({ en: "coming", sv: "kommande" })}
|
|
</p>
|
|
<div className="mt-3 h-px w-full bg-secondary">
|
|
<div
|
|
className="h-full transition-[width] duration-500"
|
|
style={{
|
|
width: `${m.coverage}%`,
|
|
backgroundColor: m.coverage >= 95 ? "var(--prov-3)" : m.coverage >= 85 ? "var(--prov-2)" : "var(--muted-foreground)",
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="reveal delay-3 mt-px grid grid-cols-1 gap-px border border-border bg-border sm:grid-cols-3">
|
|
{STATS.map(([n, l], i) => (
|
|
<div key={l.sv} className="bg-background p-6">
|
|
<p
|
|
className="text-[clamp(2.25rem,4vw,3rem)] font-medium leading-none tracking-[-0.03em]"
|
|
style={i === 1 ? { color: "var(--accent-red)" } : undefined}
|
|
>
|
|
{n}
|
|
</p>
|
|
<p className="mt-3 font-mono text-[10px] uppercase tracking-[0.1em] text-muted-foreground">{t(l)}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|