- Catalog IDs: synced /api/catalog and /katalog/[id] to consistent English slugs
(nordic-companies, tech-decisionmakers, vehicles-fleet, financial-series,
buying-signals, sanctions-pep); View links from catalog page were all broken
- Added 2 missing catalog detail entries: buying-signals, sanctions-pep
- featured-products-section: link cards to /katalog/{id} instead of hardcoded demo URL
- hero-section: 'See a dataset' now links to /katalog/nordic-companies
- layout.tsx: mount <Toaster> (sonner was installed but never mounted)
- bestall/forskott: pay() tries Stripe checkout first, falls back to demo mode
- api/payments/[order_id]/deposit: Stripe Checkout Session creation (501 if unconfigured)
- app/not-found.tsx: custom 404 page matching mono design system
- philosophy-section: fix TS type error on PRINCIPLES array
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
126 lines
6.3 KiB
TypeScript
126 lines
6.3 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useReveal } from "@/hooks/use-reveal";
|
|
import { useT } from "@/components/locale-provider";
|
|
|
|
type Loc = { en: string; sv: string };
|
|
type LocOrStr = string | Loc;
|
|
|
|
const STATS: [LocOrStr, Loc][] = [
|
|
["20", { en: "markets", sv: "marknader" }],
|
|
[{ en: "Per field", sv: "Per fält" }, { en: "provenance & confidence", sv: "härkomst & konfidens" }],
|
|
["50/50", { en: "start & approved", sv: "start & godkänt" }],
|
|
["GDPR", { en: "native, with DPA", sv: "native, med DPA" }],
|
|
];
|
|
|
|
// Data-native hero artifact: a miniature company table that "is" the product.
|
|
const ROWS: [string, LocOrStr, LocOrStr, 0 | 1 | 2 | 3][] = [
|
|
["556016-0680", "Volvo Group", "GLEIF", 3],
|
|
["915933149", "Equinor ASA", "Brreg", 3],
|
|
["25052943", "Maersk A/S", "CVR", 2],
|
|
["0112038-9", "Nokia Oyj", "PRH", 2],
|
|
["—", { en: "Email (FI)", sv: "E-post (FI)" }, { en: "license", sv: "licens" }, 0],
|
|
];
|
|
const PROV = ["var(--prov-0)", "var(--prov-1)", "var(--prov-2)", "var(--prov-3)"];
|
|
|
|
export function HeroSection() {
|
|
const ref = useReveal<HTMLElement>();
|
|
const t = useT();
|
|
// Resolve a value that may be a literal string (brand/number) or a localized {en,sv} pair.
|
|
const tx = (v: LocOrStr) => (typeof v === "string" ? v : t(v));
|
|
|
|
return (
|
|
<section ref={ref} className="grain-overlay relative overflow-hidden border-b border-border bg-background">
|
|
{/* graph-paper grid */}
|
|
<div
|
|
aria-hidden
|
|
className="pointer-events-none absolute inset-0 opacity-[0.5]"
|
|
style={{
|
|
backgroundImage:
|
|
"linear-gradient(to right, var(--border) 1px, transparent 1px), linear-gradient(to bottom, var(--border) 1px, transparent 1px)",
|
|
backgroundSize: "32px 32px",
|
|
maskImage: "radial-gradient(120% 90% at 30% 20%, #000 30%, transparent 75%)",
|
|
WebkitMaskImage: "radial-gradient(120% 90% at 30% 20%, #000 30%, transparent 75%)",
|
|
}}
|
|
/>
|
|
{/* faint oversized wordmark */}
|
|
<div aria-hidden className="pointer-events-none absolute -right-10 top-1/2 -translate-y-1/2 select-none">
|
|
<span className="text-[34vw] font-semibold leading-none tracking-tighter text-border/50 md:text-[22vw]">0pt</span>
|
|
</div>
|
|
{/* single red marker — the only red at rest */}
|
|
<span aria-hidden className="animate-float pointer-events-none absolute right-[8%] top-[20%] inline-block h-3 w-3" style={{ backgroundColor: "var(--accent-red)" }} />
|
|
|
|
<div className="relative mx-auto grid max-w-[80rem] grid-cols-1 gap-16 px-6 py-28 lg:grid-cols-[1.1fr_0.9fr] lg:py-40">
|
|
<div className="flex flex-col justify-center">
|
|
<p className="reveal delay-1 font-mono text-[11px] uppercase tracking-[0.18em] text-muted-foreground">
|
|
{t({ en: "00 — Data services on demand", sv: "00 — Datatjänster på beställning" })}
|
|
</p>
|
|
<h1 className="reveal delay-2 mt-8 text-[clamp(3rem,8vw,7rem)] font-medium leading-[0.92] tracking-[-0.035em]">
|
|
{t({ en: "Order data.", sv: "Beställ data." })}
|
|
<br />
|
|
{t({ en: "Validate fields.", sv: "Validera fält." })}
|
|
</h1>
|
|
<p className="reveal delay-3 mt-8 max-w-[32ch] text-xl leading-snug text-muted-foreground">
|
|
{t({
|
|
en: "Build the job. Pay half. Track production. Approve. The source is always visible.",
|
|
sv: "Bygg jobbet. Betala hälften. Följ produktionen. Godkänn. Källan syns — alltid.",
|
|
})}
|
|
</p>
|
|
<div className="reveal delay-4 mt-12 flex flex-wrap gap-3">
|
|
<Link
|
|
href="/bestall"
|
|
className="bg-foreground px-7 py-4 font-mono text-[12px] uppercase tracking-[0.14em] text-background transition-colors hover:bg-[var(--accent-red)]"
|
|
>
|
|
{t({ en: "Build an order", sv: "Bygg beställning" })}
|
|
</Link>
|
|
<Link
|
|
href="/katalog/nordic-companies"
|
|
className="border border-foreground px-7 py-4 font-mono text-[12px] uppercase tracking-[0.14em] transition-colors hover:bg-foreground hover:text-background"
|
|
>
|
|
{t({ en: "See a dataset", sv: "Se ett dataset" })}
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="reveal delay-5 mt-16 grid grid-cols-2 gap-px border border-border bg-border sm:grid-cols-4">
|
|
{STATS.map(([n, l], i) => (
|
|
<div key={i} className="group bg-background p-4 transition-colors hover:bg-muted/40">
|
|
<p className="text-2xl font-semibold tracking-[-0.03em]">{tx(n)}</p>
|
|
<p className="mt-2 font-mono text-[10px] uppercase tracking-[0.1em] text-muted-foreground">{t(l)}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* the artifact */}
|
|
<div className="reveal-fade flex items-center">
|
|
<div className="w-full border border-border bg-background">
|
|
<div className="flex items-center justify-between border-b border-border px-4 py-2.5">
|
|
<span className="font-mono text-[10px] uppercase tracking-[0.14em] text-muted-foreground">dim_company</span>
|
|
<span className="font-mono text-[10px] uppercase tracking-[0.14em] text-muted-foreground">{t({ en: "provenance", sv: "härkomst" })}</span>
|
|
</div>
|
|
{ROWS.map((r, i) => (
|
|
<div
|
|
key={i}
|
|
className={`reveal delay-${i + 1} group flex items-center justify-between border-b border-border px-4 py-3.5 transition-colors last:border-b-0 hover:bg-muted/40`}
|
|
>
|
|
<div className="min-w-0">
|
|
<p className="truncate text-sm transition-transform duration-200 group-hover:translate-x-0.5">{tx(r[1])}</p>
|
|
<p className="font-mono text-[10.5px] text-muted-foreground">{r[0]}</p>
|
|
</div>
|
|
<span className="ml-3 inline-flex items-center gap-1.5 border border-border px-1.5 py-0.5 font-mono text-[10px] uppercase text-muted-foreground">
|
|
<span
|
|
className="inline-block h-[7px] w-[7px]"
|
|
style={{ backgroundColor: r[3] === 0 ? "transparent" : PROV[r[3]], border: r[3] === 0 ? `1px solid ${PROV[0]}` : "none" }}
|
|
/>
|
|
{tx(r[2])}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|