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>
101 lines
4.2 KiB
TypeScript
101 lines
4.2 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useReveal } from "@/hooks/use-reveal";
|
|
import { useT } from "@/components/locale-provider";
|
|
|
|
const MODELS = [
|
|
{
|
|
index: "01",
|
|
name: { en: "Per job", sv: "Per jobb" },
|
|
tagline: { en: "Pay for what you order.", sv: "Betala för det du beställer." },
|
|
points: [
|
|
{ en: "50 % to start. 50 % on approval.", sv: "50 % start. 50 % vid godkänt." },
|
|
{ en: "No subscriptions.", sv: "Inga abonnemang." },
|
|
{ en: "Provenance included.", sv: "Härkomst ingår." },
|
|
],
|
|
cta: { en: "Build order", sv: "Bygg beställning" },
|
|
featured: true,
|
|
},
|
|
{
|
|
index: "02",
|
|
name: { en: "Subscription", sv: "Prenumeration" },
|
|
tagline: { en: "Continuous feed.", sv: "Löpande feed." },
|
|
points: [
|
|
{ en: "Daily deltas.", sv: "Dagliga deltan." },
|
|
{ en: "Webhook on change.", sv: "Webhook vid ändring." },
|
|
{ en: "Freshness monitored.", sv: "Färskhet bevakas." },
|
|
],
|
|
cta: { en: "Configure feed", sv: "Konfigurera feed" },
|
|
featured: false,
|
|
},
|
|
{
|
|
index: "03",
|
|
name: { en: "Custom", sv: "Custom" },
|
|
tagline: { en: "Large engagements.", sv: "Stora uppdrag." },
|
|
points: [
|
|
{ en: "Quote before start.", sv: "Offert innan start." },
|
|
{ en: "ML, BI, documents.", sv: "ML, BI, dokument." },
|
|
{ en: "Dedicated API.", sv: "Dedikerad API." },
|
|
],
|
|
cta: { en: "Book a call", sv: "Boka samtal" },
|
|
featured: false,
|
|
},
|
|
];
|
|
|
|
export function PricingSection() {
|
|
const ref = useReveal<HTMLElement>();
|
|
const t = useT();
|
|
return (
|
|
<section ref={ref} id="priser" 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 border-b border-border pb-6">
|
|
<p className="reveal font-mono text-[11px] uppercase tracking-[0.18em] text-muted-foreground">{t({ en: "06 — Pricing", sv: "06 — Priser" })}</p>
|
|
<p className="reveal delay-1 font-mono text-[11px] uppercase tracking-[0.18em] text-muted-foreground">{t({ en: "Three models", sv: "Tre modeller" })}</p>
|
|
</div>
|
|
|
|
<h2 className="reveal delay-1 mt-12 max-w-[20ch] text-[clamp(2.4rem,6vw,5rem)] font-medium leading-[0.96] tracking-[-0.03em]">
|
|
{t({ en: "Three ways to buy.", sv: "Tre sätt att köpa." })}
|
|
<br />
|
|
{t({ en: "No surprises.", sv: "Inga överraskningar." })}
|
|
</h2>
|
|
|
|
<div className="mt-20 grid grid-cols-1 gap-px border border-border bg-border md:grid-cols-3">
|
|
{MODELS.map((m, i) => (
|
|
<div key={m.index} className={`reveal delay-${i + 1} group relative flex flex-col bg-background p-8`}>
|
|
<div className="flex items-start justify-between">
|
|
<span className="font-mono text-[11px] uppercase tracking-[0.18em] text-muted-foreground">{m.index}</span>
|
|
{m.featured ? <span className="mt-1 inline-block h-2 w-2" style={{ backgroundColor: "var(--accent-red)" }} /> : null}
|
|
</div>
|
|
|
|
<h3 className="mt-10 text-2xl font-semibold tracking-[-0.02em]">{t(m.name)}</h3>
|
|
<p className="mt-2 text-sm text-muted-foreground">{t(m.tagline)}</p>
|
|
|
|
<ul className="mt-10 flex-1 space-y-px border-t border-border">
|
|
{m.points.map((p) => (
|
|
<li key={p.sv} className="flex items-center gap-3 border-b border-border py-3 text-sm">
|
|
<span className="inline-block h-1 w-1 shrink-0 bg-foreground" />
|
|
<span>{t(p)}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
|
|
<Link
|
|
href="/bestall"
|
|
className="mt-10 block py-4 text-center font-mono text-[11px] uppercase tracking-[0.14em] transition-all duration-200 hover:-translate-y-px"
|
|
style={
|
|
m.featured
|
|
? { backgroundColor: "var(--foreground)", color: "var(--background)" }
|
|
: { border: "1px solid var(--foreground)", color: "var(--foreground)" }
|
|
}
|
|
>
|
|
{t(m.cta)}
|
|
</Link>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|