Error 403
Original · free403 access denied page
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/error-403.json"use client";
import { type FormEvent, useId, useRef, useState } from "react";
import { motion, useReducedMotion } from "motion/react";
type CopyKey = "reference" | "ticket";
type AccessLevel = "read" | "export" | "admin";
const LEVELS: { value: AccessLevel; label: string; hint: string }[] = [
{ value: "read", label: "Read-only", hint: "View reports and exports" },
{ value: "export", label: "Read & export", hint: "Download billing exports" },
{ value: "admin", label: "Admin", hint: "Manage members and scopes" },
];
const REASONS: string[] = [
"Your role (Analyst) doesn’t include the billing:read scope.",
"This resource is limited to members of the Finance group.",
"Elevated access needs sign-off from a workspace admin.",
];
const REFERENCE = "ACL-403-7F2A91";
export default function Error403() {
const reduce = useReducedMotion();
const uid = useId();
const liveRef = useRef<HTMLParagraphElement | null>(null);
const [copied, setCopied] = useState<CopyKey | null>(null);
const [showWhy, setShowWhy] = useState(false);
const [email, setEmail] = useState("");
const [level, setLevel] = useState<AccessLevel>("export");
const [reason, setReason] = useState("");
const [errors, setErrors] = useState<{ email?: string; reason?: string }>({});
const [ticket, setTicket] = useState<string | null>(null);
const emailId = `${uid}-email`;
const levelId = `${uid}-level`;
const reasonId = `${uid}-reason`;
const emailErrId = `${uid}-email-err`;
const reasonErrId = `${uid}-reason-err`;
const whyId = `${uid}-why`;
async function copy(text: string, key: CopyKey) {
try {
if (typeof navigator !== "undefined" && navigator.clipboard) {
await navigator.clipboard.writeText(text);
}
setCopied(key);
window.setTimeout(() => setCopied((c) => (c === key ? null : c)), 1800);
} catch {
setCopied(null);
}
}
function submit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
const next: { email?: string; reason?: string } = {};
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
next.email = "Enter the work email tied to your account.";
}
if (reason.trim().length < 12) {
next.reason = "Add at least a sentence so the admin can review quickly.";
}
setErrors(next);
if (Object.keys(next).length > 0) return;
const id = "REQ-" + Math.random().toString(36).slice(2, 8).toUpperCase();
setTicket(id);
}
function reset() {
setTicket(null);
setErrors({});
}
const fade = (delay: number) =>
reduce
? { initial: false as const, animate: { opacity: 1, y: 0 } }
: {
initial: { opacity: 0, y: 14 },
animate: { opacity: 1, y: 0 },
transition: { duration: 0.55, delay, ease: "easeOut" as const },
};
return (
<section className="relative w-full overflow-hidden bg-slate-50 px-4 py-20 text-slate-900 sm:py-28 dark:bg-slate-950 dark:text-slate-100">
<style>{`
.e403-scan{animation:e403-scan 3.4s ease-in-out infinite}
@keyframes e403-scan{0%{transform:translateY(6px);opacity:0}12%{opacity:.85}50%{transform:translateY(104px);opacity:.85}88%{opacity:.85}100%{transform:translateY(6px);opacity:0}}
.e403-glow{animation:e403-glow 3.2s ease-in-out infinite}
@keyframes e403-glow{0%,100%{opacity:.3}50%{opacity:.7}}
.e403-float{animation:e403-float 6.5s ease-in-out infinite}
@keyframes e403-float{0%,100%{transform:translateY(0)}50%{transform:translateY(-7px)}}
@media (prefers-reduced-motion: reduce){.e403-scan,.e403-glow,.e403-float{animation:none!important}}
`}</style>
{/* Decorative backdrop */}
<div aria-hidden className="pointer-events-none absolute inset-0">
<svg className="absolute inset-0 h-full w-full text-slate-300/60 dark:text-slate-700/40" width="100%" height="100%">
<defs>
<pattern id={`${uid}-grid`} width="34" height="34" patternUnits="userSpaceOnUse">
<path d="M34 0H0V34" fill="none" stroke="currentColor" strokeWidth="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill={`url(#${uid}-grid)`} />
</svg>
<div className="e403-glow absolute -top-24 left-1/2 h-72 w-[42rem] -translate-x-1/2 rounded-full bg-rose-400/20 blur-3xl dark:bg-rose-500/20" />
<div className="e403-glow absolute bottom-0 right-0 h-64 w-96 translate-x-1/4 translate-y-1/4 rounded-full bg-indigo-400/20 blur-3xl dark:bg-indigo-500/15" />
</div>
<div className="relative mx-auto max-w-5xl">
{/* Header / hero */}
<div className="flex flex-col items-center text-center">
<motion.span
{...fade(0)}
className="inline-flex items-center gap-2 rounded-full border border-rose-300/70 bg-rose-100/70 px-3.5 py-1.5 text-xs font-semibold uppercase tracking-widest text-rose-700 dark:border-rose-500/40 dark:bg-rose-500/10 dark:text-rose-300"
>
<span className="relative flex h-2 w-2">
<span className="e403-glow absolute inline-flex h-full w-full rounded-full bg-rose-500/70" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-rose-500" />
</span>
Error 403 · Access denied
</motion.span>
<motion.div {...fade(0.06)} className="e403-float mt-8">
<ShieldMark uid={uid} />
</motion.div>
<motion.h1
{...fade(0.12)}
className="mt-7 bg-gradient-to-b from-slate-900 to-slate-600 bg-clip-text text-5xl font-black tracking-tight text-transparent sm:text-6xl dark:from-white dark:to-slate-400"
>
Access denied
</motion.h1>
<motion.p {...fade(0.18)} className="mt-4 max-w-xl text-balance text-base leading-relaxed text-slate-600 dark:text-slate-400">
You’re signed in, but your account doesn’t have permission to open{" "}
<span className="font-semibold text-slate-800 dark:text-slate-200">Production Billing Exports</span>. Request
access below or head back to where you belong.
</motion.p>
<motion.div {...fade(0.24)} className="mt-7 flex flex-col items-center gap-3 sm:flex-row">
<div className="inline-flex items-center gap-2 rounded-xl border border-slate-200 bg-white/80 px-3 py-2 text-sm shadow-sm dark:border-slate-800 dark:bg-slate-900/70">
<span className="text-slate-500 dark:text-slate-500">Reference</span>
<code className="font-mono font-semibold tracking-tight text-slate-800 dark:text-slate-200">{REFERENCE}</code>
<button
type="button"
onClick={() => copy(REFERENCE, "reference")}
className="ml-1 inline-flex items-center gap-1 rounded-md px-1.5 py-1 text-xs font-medium text-indigo-600 transition-colors hover:bg-indigo-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:text-indigo-300 dark:hover:bg-indigo-500/10 dark:focus-visible:ring-offset-slate-950"
>
{copied === "reference" ? <CheckIcon /> : <CopyIcon />}
{copied === "reference" ? "Copied" : "Copy"}
</button>
</div>
<div className="flex items-center gap-3">
<button
type="button"
onClick={() => {
if (typeof window !== "undefined") window.history.back();
}}
className="inline-flex items-center gap-2 rounded-xl bg-slate-900 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-transform hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:bg-white dark:text-slate-900 dark:focus-visible:ring-offset-slate-950"
>
<ArrowLeftIcon />
Go back
</button>
<a
href="/dashboard"
className="inline-flex items-center gap-2 rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm font-semibold text-slate-700 shadow-sm transition-colors hover:bg-slate-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800 dark:focus-visible:ring-offset-slate-950"
>
Dashboard
</a>
</div>
</motion.div>
</div>
{/* Two-column body */}
<div className="mt-14 grid gap-6 lg:grid-cols-[1.05fr_0.95fr]">
{/* Request access card */}
<motion.div
{...fade(0.3)}
className="rounded-2xl border border-slate-200 bg-white/80 p-6 shadow-sm backdrop-blur sm:p-8 dark:border-slate-800 dark:bg-slate-900/60"
>
<div className="flex items-center gap-3">
<span className="flex h-10 w-10 items-center justify-center rounded-xl bg-indigo-100 text-indigo-600 dark:bg-indigo-500/15 dark:text-indigo-300">
<KeyIcon />
</span>
<div>
<h2 className="text-lg font-bold tracking-tight">Request access</h2>
<p className="text-sm text-slate-500 dark:text-slate-400">Goes to your workspace admin for review.</p>
</div>
</div>
{ticket ? (
<div className="mt-6 rounded-xl border border-emerald-300/70 bg-emerald-50 p-5 text-sm dark:border-emerald-500/30 dark:bg-emerald-500/10">
<div className="flex items-center gap-2 font-semibold text-emerald-700 dark:text-emerald-300">
<CheckIcon />
Request sent
</div>
<p className="mt-2 text-emerald-800/90 dark:text-emerald-200/80">
We routed your request to the Finance admins. Most reviews finish within one business day.
</p>
<div className="mt-4 flex items-center justify-between gap-3 rounded-lg border border-emerald-300/60 bg-white/70 px-3 py-2 dark:border-emerald-500/20 dark:bg-slate-900/50">
<span className="font-mono text-sm font-semibold text-emerald-800 dark:text-emerald-200">{ticket}</span>
<button
type="button"
onClick={() => copy(ticket, "ticket")}
className="inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-semibold text-emerald-700 transition-colors hover:bg-emerald-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-emerald-50 dark:text-emerald-300 dark:hover:bg-emerald-500/10 dark:focus-visible:ring-offset-slate-950"
>
{copied === "ticket" ? <CheckIcon /> : <CopyIcon />}
{copied === "ticket" ? "Copied" : "Copy ID"}
</button>
</div>
<button
type="button"
onClick={reset}
className="mt-4 text-sm font-semibold text-emerald-700 underline-offset-4 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-emerald-50 dark:text-emerald-300 dark:focus-visible:ring-offset-slate-950"
>
Submit another request
</button>
</div>
) : (
<form onSubmit={submit} noValidate className="mt-6 space-y-5">
<div>
<label htmlFor={emailId} className="block text-sm font-medium text-slate-700 dark:text-slate-300">
Work email
</label>
<input
id={emailId}
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
aria-invalid={Boolean(errors.email)}
aria-describedby={errors.email ? emailErrId : undefined}
placeholder="you@northwind.example"
className="mt-1.5 w-full rounded-xl border border-slate-300 bg-white px-3.5 py-2.5 text-sm text-slate-900 shadow-sm placeholder:text-slate-400 focus-visible:border-indigo-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500/40 dark:border-slate-700 dark:bg-slate-950/50 dark:text-slate-100 dark:placeholder:text-slate-500"
/>
{errors.email && (
<p id={emailErrId} role="alert" className="mt-1.5 text-xs font-medium text-rose-600 dark:text-rose-400">
{errors.email}
</p>
)}
</div>
<div>
<label htmlFor={levelId} className="block text-sm font-medium text-slate-700 dark:text-slate-300">
Access level
</label>
<div className="relative mt-1.5">
<select
id={levelId}
value={level}
onChange={(e) => setLevel(e.target.value as AccessLevel)}
className="w-full appearance-none rounded-xl border border-slate-300 bg-white px-3.5 py-2.5 pr-10 text-sm text-slate-900 shadow-sm focus-visible:border-indigo-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500/40 dark:border-slate-700 dark:bg-slate-950/50 dark:text-slate-100"
>
{LEVELS.map((l) => (
<option key={l.value} value={l.value}>
{l.label} — {l.hint}
</option>
))}
</select>
<span aria-hidden className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-slate-400">
<ChevronIcon />
</span>
</div>
</div>
<div>
<label htmlFor={reasonId} className="block text-sm font-medium text-slate-700 dark:text-slate-300">
Why do you need it?
</label>
<textarea
id={reasonId}
value={reason}
onChange={(e) => setReason(e.target.value)}
rows={3}
aria-invalid={Boolean(errors.reason)}
aria-describedby={errors.reason ? reasonErrId : undefined}
placeholder="Reconciling Q3 vendor invoices before the board review on Friday."
className="mt-1.5 w-full resize-none rounded-xl border border-slate-300 bg-white px-3.5 py-2.5 text-sm text-slate-900 shadow-sm placeholder:text-slate-400 focus-visible:border-indigo-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500/40 dark:border-slate-700 dark:bg-slate-950/50 dark:text-slate-100 dark:placeholder:text-slate-500"
/>
{errors.reason && (
<p id={reasonErrId} role="alert" className="mt-1.5 text-xs font-medium text-rose-600 dark:text-rose-400">
{errors.reason}
</p>
)}
</div>
<button
type="submit"
className="inline-flex w-full items-center justify-center gap-2 rounded-xl bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-indigo-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950"
>
<SendIcon />
Send access request
</button>
</form>
)}
<p ref={liveRef} aria-live="polite" className="sr-only">
{copied ? "Copied to clipboard." : ""}
</p>
</motion.div>
{/* Options + disclosure */}
<motion.div {...fade(0.36)} className="flex flex-col gap-6">
<div className="rounded-2xl border border-slate-200 bg-white/80 p-6 shadow-sm backdrop-blur sm:p-8 dark:border-slate-800 dark:bg-slate-900/60">
<h2 className="text-lg font-bold tracking-tight">What you can do</h2>
<ul className="mt-4 space-y-1">
{[
{
icon: <GridIcon />,
title: "Return to your dashboard",
body: "Everything your Analyst role can open, in one place.",
href: "/dashboard",
cta: "Open dashboard",
},
{
icon: <SwapIcon />,
title: "Switch account",
body: "Sign in with an account that has Finance access.",
href: "/login",
cta: "Switch account",
},
{
icon: <BookIcon />,
title: "Read the access policy",
body: "See how workspace permissions and scopes are granted.",
href: "/docs/permissions",
cta: "View policy",
},
].map((it) => (
<li key={it.title}>
<a
href={it.href}
className="group -mx-2 flex items-start gap-3 rounded-xl px-2 py-3 transition-colors hover:bg-slate-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:hover:bg-slate-800/60 dark:focus-visible:ring-offset-slate-950"
>
<span className="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-slate-100 text-slate-600 transition-colors group-hover:bg-indigo-100 group-hover:text-indigo-600 dark:bg-slate-800 dark:text-slate-300 dark:group-hover:bg-indigo-500/15 dark:group-hover:text-indigo-300">
{it.icon}
</span>
<span className="min-w-0 flex-1">
<span className="flex items-center justify-between gap-2">
<span className="text-sm font-semibold text-slate-800 dark:text-slate-100">{it.title}</span>
<span className="translate-x-0 text-slate-400 transition-transform group-hover:translate-x-0.5 dark:text-slate-500">
<ArrowRightIcon />
</span>
</span>
<span className="mt-0.5 block text-sm text-slate-500 dark:text-slate-400">{it.body}</span>
</span>
</a>
</li>
))}
</ul>
</div>
<div className="rounded-2xl border border-slate-200 bg-white/80 p-6 shadow-sm backdrop-blur sm:p-8 dark:border-slate-800 dark:bg-slate-900/60">
<button
type="button"
onClick={() => setShowWhy((v) => !v)}
aria-expanded={showWhy}
aria-controls={whyId}
className="flex w-full items-center justify-between gap-3 rounded-lg text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950"
>
<span className="text-sm font-bold tracking-tight text-slate-800 dark:text-slate-100">
Why am I seeing this?
</span>
<span
aria-hidden
className={`text-slate-400 transition-transform duration-200 ${showWhy ? "rotate-180" : ""}`}
>
<ChevronIcon />
</span>
</button>
<div id={whyId} hidden={!showWhy} className="mt-4">
<ul className="space-y-2.5">
{REASONS.map((r) => (
<li key={r} className="flex items-start gap-2.5 text-sm text-slate-600 dark:text-slate-400">
<span className="mt-0.5 text-amber-500 dark:text-amber-400">
<DotIcon />
</span>
{r}
</li>
))}
</ul>
</div>
</div>
</motion.div>
</div>
{/* Support footer */}
<motion.p
{...fade(0.42)}
className="mt-10 text-center text-sm text-slate-500 dark:text-slate-500"
>
Still blocked? Reach your workspace admin at{" "}
<a
href="mailto:access@northwind.example"
className="font-semibold text-indigo-600 underline-offset-4 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:text-indigo-300 dark:focus-visible:ring-offset-slate-950"
>
access@northwind.example
</a>
.
</motion.p>
</div>
</section>
);
}
function ShieldMark({ uid }: { uid: string }) {
const clip = `${uid}-shield-clip`;
const grad = `${uid}-shield-grad`;
return (
<div className="relative flex h-32 w-32 items-center justify-center rounded-3xl border border-slate-200 bg-white shadow-lg shadow-rose-500/10 dark:border-slate-800 dark:bg-slate-900">
<div className="e403-glow absolute inset-0 rounded-3xl bg-rose-400/25 blur-xl dark:bg-rose-500/20" />
<svg viewBox="0 0 120 140" className="relative h-24 w-24" role="img" aria-label="Locked security shield">
<defs>
<linearGradient id={grad} x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stopColor="#f43f5e" />
<stop offset="1" stopColor="#e11d48" />
</linearGradient>
<clipPath id={clip}>
<path d="M60 8 L108 26 V64 C108 96 88 120 60 132 C32 120 12 96 12 64 V26 Z" />
</clipPath>
</defs>
<path
d="M60 8 L108 26 V64 C108 96 88 120 60 132 C32 120 12 96 12 64 V26 Z"
className="fill-rose-50 stroke-rose-300 dark:fill-rose-500/10 dark:stroke-rose-500/40"
strokeWidth="3"
/>
<g clipPath={`url(#${clip})`}>
<rect x="0" y="0" width="120" height="10" className="e403-scan fill-rose-400/70 dark:fill-rose-400/60" />
</g>
{/* Padlock */}
<path
d="M48 64 V56 a12 12 0 0 1 24 0 V64"
fill="none"
stroke={`url(#${grad})`}
strokeWidth="6"
strokeLinecap="round"
/>
<rect x="42" y="64" width="36" height="30" rx="6" fill={`url(#${grad})`} />
<circle cx="60" cy="76" r="4.5" className="fill-white" />
<rect x="57.8" y="78" width="4.4" height="9" rx="2.2" className="fill-white" />
</svg>
</div>
);
}
/* --- Inline icons --- */
function CopyIcon() {
return (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<rect x="9" y="9" width="13" height="13" rx="2" />
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
</svg>
);
}
function CheckIcon() {
return (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<path d="M20 6 9 17l-5-5" />
</svg>
);
}
function ArrowLeftIcon() {
return (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<path d="M19 12H5" />
<path d="M12 19l-7-7 7-7" />
</svg>
);
}
function ArrowRightIcon() {
return (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<path d="M5 12h14" />
<path d="M12 5l7 7-7 7" />
</svg>
);
}
function ChevronIcon() {
return (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<path d="M6 9l6 6 6-6" />
</svg>
);
}
function KeyIcon() {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<circle cx="7.5" cy="15.5" r="4.5" />
<path d="M10.5 12.5 21 2" />
<path d="M16 7l3 3" />
</svg>
);
}
function SendIcon() {
return (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<path d="M22 2 11 13" />
<path d="M22 2 15 22l-4-9-9-4 20-7Z" />
</svg>
);
}
function GridIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<rect x="3" y="3" width="7" height="7" rx="1.5" />
<rect x="14" y="3" width="7" height="7" rx="1.5" />
<rect x="3" y="14" width="7" height="7" rx="1.5" />
<rect x="14" y="14" width="7" height="7" rx="1.5" />
</svg>
);
}
function SwapIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<path d="M17 4l3 3-3 3" />
<path d="M20 7H9a5 5 0 0 0-5 5" />
<path d="M7 20l-3-3 3-3" />
<path d="M4 17h11a5 5 0 0 0 5-5" />
</svg>
);
}
function BookIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2Z" />
</svg>
);
}
function DotIcon() {
return (
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden>
<circle cx="12" cy="12" r="5" />
</svg>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Error 404
Originalfriendly 404 page with illustration and CTA

Error 500
Original500 server error page

Error Offline
Originaloffline / no-connection state

Error Maintenance
Originalscheduled maintenance page

Error Coming Soon
Originalcoming soon page with countdown and email

Error Under Construction
Originalunder construction page

Error Search Empty
Originalno search results page

Error Expired
Originallink/session expired page

Error Generic
Originalgeneric error page with retry

Spotlight Hero
OriginalA centred hero with a soft radial spotlight, badge and dual call-to-action.

Split Hero
OriginalA two-column hero pairing a headline and CTAs with a product mock and social proof.

Gradient Spotlight Hero
OriginalA minimal centred hero with a soft gradient-mesh backdrop, announcement pill and dual call-to-action buttons.

