fix stripe configuration

This commit is contained in:
2026-05-26 13:47:43 -04:00
parent 72609755f8
commit 3bcbdae678
35 changed files with 1189 additions and 1727 deletions

View File

@@ -0,0 +1,79 @@
import { createSignal, onMount, Show } from "solid-js";
import type { Stripe, StripeEmbeddedCheckout } from "@stripe/stripe-js";
interface EmbeddedCheckoutProps {
clientSecret: string;
onCheckoutComplete?: () => void;
}
export default function EmbeddedCheckout(props: EmbeddedCheckoutProps) {
const [error, setError] = createSignal<string | null>(null);
const [loading, setLoading] = createSignal(true);
let container: HTMLDivElement | undefined;
onMount(async () => {
let embeddedCheckout: StripeEmbeddedCheckout | null = null;
try {
const mod = await import("@stripe/stripe-js");
const publishableKey = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY;
if (!publishableKey) {
setError("Stripe publishable key not configured");
setLoading(false);
return;
}
const stripe = await mod.loadStripe(publishableKey);
if (!stripe) {
setError("Failed to load Stripe");
setLoading(false);
return;
}
embeddedCheckout = await stripe.createEmbeddedCheckoutPage({
clientSecret: props.clientSecret,
onComplete: () => {
props.onCheckoutComplete?.();
},
});
if (container) {
embeddedCheckout.mount(container);
}
setLoading(false);
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to load checkout");
setLoading(false);
}
// Cleanup on unmount
return () => {
embeddedCheckout?.destroy();
};
});
return (
<div class="w-full">
<Show when={loading()}>
<div class="flex items-center justify-center min-h-[400px]">
<div class="text-center">
<div class="animate-spin h-8 w-8 border-2 border-[var(--color-brand-primary)] border-t-transparent rounded-full mx-auto mb-3" />
<p class="text-sm text-[var(--color-text-secondary)]">Loading checkout...</p>
</div>
</div>
</Show>
<Show when={error() && !loading()}>
<div class="text-center py-8">
<p class="text-[var(--color-error)] mb-4">{error()}</p>
</div>
</Show>
<div
ref={container}
class={loading() ? "hidden" : ""}
/>
</div>
);
}

View File

@@ -1,38 +0,0 @@
import { A } from "@solidjs/router";
import { cn } from "~/lib/utils";
import { Button } from "~/components/ui";
import PageContainer from "~/components/layout/PageContainer";
interface CTABannerSectionProps {
class?: string;
}
export default function CTABannerSection(props: CTABannerSectionProps) {
return (
<section id="cta" class={cn("py-20 md:py-28 scroll-mt-16", props.class)}>
<PageContainer py="py-8">
<div class="gradient-card border border-(--color-border)/50 rounded-2xl p-10 md:p-16 text-center">
<h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-[var(--color-text-primary)] mb-4">
Ready to protect your identity?
</h2>
<p class="text-lg text-[var(--color-text-secondary)] max-w-2xl mx-auto mb-10">
Join thousands of users who trust Kordant to keep their digital
identity safe from emerging threats.
</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<A href="/signup">
<Button variant="primary" size="lg">
Create Account
</Button>
</A>
<A href="/login">
<Button variant="secondary" size="lg">
Sign In
</Button>
</A>
</div>
</div>
</PageContainer>
</section>
);
}

View File

@@ -1,249 +0,0 @@
import { For } from "solid-js";
import type { JSX } from "solid-js";
import { cn } from "~/lib/utils";
import Card from "~/components/ui/Card";
import PageContainer from "~/components/layout/PageContainer";
interface Feature {
title: string;
description: string;
icon: () => JSX.Element;
}
function DarkWatchIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-[var(--color-brand-primary)]"
>
<path
d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="1.5" />
</svg>
);
}
function VoicePrintIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-[var(--color-brand-primary)]"
>
<path
d="M12 4a4 4 0 00-4 4v8a4 4 0 008 0V8a4 4 0 00-4-4z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
/>
<path
d="M4 11v2m16-2v2M8 18.5A6 6 0 0016 18.5"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
/>
</svg>
);
}
function SpamShieldIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-[var(--color-brand-primary)]"
>
<path
d="M12 2l9 4v6c0 5.55-3.84 10.74-9 12-5.16-1.26-9-6.45-9-12V6l9-4z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M10 10l4 4m0-4l-4 4"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
/>
</svg>
);
}
function HomeTitleIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-[var(--color-brand-primary)]"
>
<path
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
}
function RemoveBrokersIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-[var(--color-brand-primary)]"
>
<path
d="M3 6h18M8 6V4a1 1 0 011-1h6a1 1 0 011 1v2m-4 4v6m-4-6v6m-3-8v10a2 2 0 002 2h10a2 2 0 002-2V8H8z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
}
function FamilyPlansIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-[var(--color-brand-primary)]"
>
<path
d="M12 12a4 4 0 100-8 4 4 0 000 8zM5 22v-2a4 4 0 014-4h6a4 4 0 014 4v2"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
/>
<path
d="M20 9a3 3 0 100-6 3 3 0 000 6zM16 22v-2a3 3 0 00-3-3h-2a3 3 0 00-3 3v2"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
opacity="0.6"
/>
</svg>
);
}
const features: Feature[] = [
{
title: "DarkWatch",
description:
"Continuous dark web monitoring to detect your exposed credentials and personal data.",
icon: DarkWatchIcon,
},
{
title: "VoicePrint",
description:
"AI-powered voice clone detection to protect against deepfake voice scams.",
icon: VoicePrintIcon,
},
{
title: "SpamShield",
description:
"Intelligent spam and scam call blocking that learns your patterns over time.",
icon: SpamShieldIcon,
},
{
title: "HomeTitle",
description:
"Property fraud alerts that notify you of unauthorized changes to your home records.",
icon: HomeTitleIcon,
},
{
title: "RemoveBrokers",
description:
"Automatic data broker removal to minimize your personal data footprint online.",
icon: RemoveBrokersIcon,
},
{
title: "Family Plans",
description:
"Protect your whole household with shared monitoring, alerts, and management tools.",
icon: FamilyPlansIcon,
},
];
interface FeatureCardProps {
feature: Feature;
}
function FeatureCard(props: FeatureCardProps) {
const Icon = props.feature.icon;
return (
<Card class="hover:shadow-lg transition-shadow duration-300">
<div class="flex items-start gap-4">
<div class="flex-shrink-0 p-2 rounded-lg bg-[var(--color-bg-secondary)]">
<Icon />
</div>
<div>
<h3 class="text-lg font-semibold text-[var(--color-text-primary)] mb-1">
{props.feature.title}
</h3>
<p class="text-[var(--color-text-secondary)] leading-relaxed">
{props.feature.description}
</p>
</div>
</div>
</Card>
);
}
interface FeaturesGridSectionProps {
class?: string;
}
export default function FeaturesGridSection(props: FeaturesGridSectionProps) {
return (
<section
id="features"
class={cn("py-20 md:py-28 scroll-mt-16", props.class)}
>
<PageContainer py="py-8">
<div class="text-center mb-16">
<h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-[var(--color-text-primary)] mb-4">
Platform Features
</h2>
<p class="text-lg text-[var(--color-text-secondary)] max-w-2xl mx-auto">
Comprehensive protection powered by AI and real-time monitoring
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<For each={features}>
{(feature) => <FeatureCard feature={feature} />}
</For>
</div>
</PageContainer>
</section>
);
}

View File

@@ -1,154 +0,0 @@
import { For } from "solid-js";
import type { JSX } from "solid-js";
import { cn } from "~/lib/utils";
import Card from "~/components/ui/Card";
import PageContainer from "~/components/layout/PageContainer";
function CheckIcon() {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="flex-shrink-0"
>
<path
d="M4 9l3 3 7-7"
stroke="var(--color-success)"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
}
function IndividualIcon() {
return (
<svg
width="40"
height="40"
viewBox="0 0 40 40"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-10 h-10 text-[var(--color-brand-primary)]"
>
<circle cx="20" cy="14" r="6" fill="currentColor" />
<path
d="M8 32c0-6.6 5.4-12 12-12s12 5.4 12 12H8z"
fill="currentColor"
/>
</svg>
);
}
function FamilyIcon() {
return (
<svg
width="40"
height="40"
viewBox="0 0 40 40"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-10 h-10 text-[var(--color-brand-primary)]"
>
<circle cx="14" cy="12" r="5" fill="currentColor" />
<circle cx="26" cy="12" r="4" fill="currentColor" opacity="0.7" />
<path
d="M2 30c0-5 4-9 9-9 1.5 0 3 .4 4.2 1.1C16.5 21.5 18 21 20 21s3.5.5 4.8 1.1C26 21.4 27.5 21 29 21c5 0 9 4 9 9H2z"
fill="currentColor"
/>
</svg>
);
}
interface PanelProps {
title: string;
description: string;
items: string[];
icon: () => JSX.Element;
}
function Panel(props: PanelProps) {
const Icon = props.icon;
return (
<Card class="h-full">
<div class="flex flex-col h-full">
<div class="mb-4">
<Icon />
</div>
<h3 class="text-xl font-semibold text-[var(--color-text-primary)] mb-2">
{props.title}
</h3>
<p class="text-[var(--color-text-secondary)] mb-6">
{props.description}
</p>
<ul class="space-y-3 flex-1">
<For each={props.items}>
{(item) => (
<li class="flex items-start gap-2.5">
<CheckIcon />
<span class="text-[var(--color-text-secondary)] text-sm">
{item}
</span>
</li>
)}
</For>
</ul>
</div>
</Card>
);
}
interface ForUsersSectionProps {
class?: string;
}
export default function ForUsersSection(props: ForUsersSectionProps) {
return (
<section
id="for-users"
class={cn("py-20 md:py-28 scroll-mt-16", props.class)}
>
<PageContainer py="py-8">
<div class="text-center mb-16">
<h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-[var(--color-text-primary)] mb-4">
For Everyone
</h2>
<p class="text-lg text-[var(--color-text-secondary)] max-w-2xl mx-auto">
Whether you're protecting yourself or your whole family
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<Panel
title="For Individuals"
description="Personal identity protection tailored to your digital footprint."
icon={IndividualIcon}
items={[
"Monitor personal email and phone numbers",
"Dark web credential scanning",
"Voiceprint clone detection",
"Spam and scam call filtering",
"Data broker opt-out service",
]}
/>
<Panel
title="For Families"
description="Group management tools to keep every household member safe."
icon={FamilyIcon}
items={[
"Add unlimited family members",
"Shared alert dashboard",
"Child account monitoring",
"Family-wide dark web scans",
"Centralized threat notifications",
]}
/>
</div>
</PageContainer>
</section>
);
}

View File

@@ -1,125 +0,0 @@
import { onMount } from "solid-js";
import { A } from "@solidjs/router";
import { cn } from "~/lib/utils";
import { Button } from "~/components/ui";
import { Typewriter } from "~/components/ui/Typewriter";
import PageContainer from "~/components/layout/PageContainer";
function ShieldIcon() {
return (
<svg
width="48"
height="48"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-12 h-12 md:w-16 md:h-16"
>
<circle cx="24" cy="24" r="24" fill="var(--color-brand-primary)" />
<path
d="M24 10L16 14v6.5c0 5.1 3.4 9.9 8 11.5 4.6-1.6 8-6.4 8-11.5V14l-8-4z"
fill="white"
fill-opacity="0.9"
/>
<path
d="M20 24l3 2.5 5-5"
stroke="var(--color-brand-primary)"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
}
interface HeroSectionProps {
class?: string;
}
export default function HeroSection(props: HeroSectionProps) {
let heroRef: HTMLDivElement | undefined;
onMount(() => {
if (heroRef) {
heroRef.style.opacity = "1";
heroRef.style.transform = "translateY(0)";
}
});
return (
<section class={cn("relative", props.class)}>
<PageContainer>
<div
ref={heroRef}
class="flex flex-col items-center text-center py-20 md:py-32 transition-all duration-700 ease-out"
style="opacity: 0; transform: translateY(20px);"
>
<div class="mb-6 shadow-glow-primary rounded-full p-3">
<ShieldIcon />
</div>
<h1 class="text-4xl md:text-6xl lg:text-7xl font-bold tracking-tight mb-6 max-w-4xl">
<Typewriter speed={50} delay={400} keepAlive={false}>
<span class="text-text-primary">AI-Powered </span>
<span class="text-gradient-primary">Identity Protection</span>
<br />
<span class="text-text-primary">for Everyone</span>
</Typewriter>
</h1>
<p class="text-xl md:text-2xl text-text-secondary max-w-2xl mb-10 leading-relaxed">
Threat actors are using AI in multifaceted attacks. Kordant evens
the playing field using advanced AI to monitor, detect, and prevent
identity threats in real-time.
</p>
<div class="flex flex-col sm:flex-row gap-4 mb-8">
<A href="/signup">
<Button variant="primary" size="lg">
Get Started
</Button>
</A>
<A href="#features">
<Button variant="ghost" size="lg">
Learn More
</Button>
</A>
</div>
<div class="flex flex-wrap items-center justify-center gap-x-6 gap-y-2 text-sm text-text-tertiary">
<span class="flex items-center gap-1.5">
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.5 11.5L3 8L4.1 6.9L6.5 9.3L12.4 3.4L13.5 4.5L6.5 11.5Z"
fill="var(--color-success)"
/>
</svg>
No credit card required
</span>
<span class="flex items-center gap-1.5">
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.5 11.5L3 8L4.1 6.9L6.5 9.3L12.4 3.4L13.5 4.5L6.5 11.5Z"
fill="var(--color-success)"
/>
</svg>
Free tier available
</span>
</div>
</div>
</PageContainer>
</section>
);
}

View File

@@ -1,197 +0,0 @@
import { For } from "solid-js";
import type { JSX } from "solid-js";
import { cn } from "~/lib/utils";
import PageContainer from "~/components/layout/PageContainer";
interface Step {
number: number;
title: string;
description: string;
icon: () => JSX.Element;
}
function EnrollIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-8 h-8 text-white"
>
<path
d="M12 4a4 4 0 100 8 4 4 0 000-8zM6 21v-2a4 4 0 014-4h4a4 4 0 014 4v2"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
/>
<path
d="M17 8l2 2 4-4"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
}
function MonitorIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-8 h-8 text-white"
>
<path
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
stroke="currentColor"
stroke-width="1.5"
/>
<path
d="M12 8v4l3 3"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M3 12h3m15 0h3M12 3v3m0 15v3"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
opacity="0.5"
/>
</svg>
);
}
function AlertIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-8 h-8 text-white"
>
<path
d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M12 22a2 2 0 01-2-2h4a2 2 0 01-2 2z"
fill="currentColor"
/>
<path
d="M12 11v3m0-6v1"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
/>
</svg>
);
}
const steps: Step[] = [
{
number: 1,
title: "Enroll Your Identity",
description:
"Sign up and add your emails, phone numbers, and family members to create your protection profile.",
icon: EnrollIcon,
},
{
number: 2,
title: "We Monitor 24/7",
description:
"Our system runs continuous dark web scans, voiceprint detection, and spam filtering to catch threats early.",
icon: MonitorIcon,
},
{
number: 3,
title: "Get Instant Alerts",
description:
"Receive real-time notifications the moment a threat is detected, with clear guidance on what to do next.",
icon: AlertIcon,
},
];
interface StepBlockProps {
step: Step;
index: number;
}
function StepBlock(props: StepBlockProps) {
const isEven = props.index % 2 === 0;
const Icon = props.step.icon;
return (
<div
class={cn(
"flex gap-8 md:flex-row flex-col",
isEven ? "" : "md:flex-row-reverse",
)}
>
<div class="flex-1">
<div class="flex items-start gap-5">
<div class="w-14 h-14 rounded-full gradient-primary shadow-glow-primary flex items-center justify-center shrink-0">
<Icon />
</div>
<div>
<div class="inline-flex items-center gap-2 mb-1.5">
<span class="text-sm font-semibold text-[var(--color-brand-primary)]">
Step {props.step.number}
</span>
</div>
<h3 class="text-xl md:text-2xl font-bold text-[var(--color-text-primary)] mb-2">
{props.step.title}
</h3>
<p class="text-base text-[var(--color-text-secondary)] leading-relaxed">
{props.step.description}
</p>
</div>
</div>
</div>
<div class="flex-1 hidden md:block" />
</div>
);
}
interface HowItWorksSectionProps {
class?: string;
}
export default function HowItWorksSection(props: HowItWorksSectionProps) {
return (
<section
id="how-it-works"
class={cn("py-20 md:py-28 scroll-mt-16", props.class)}
>
<PageContainer py="py-8">
<div class="text-center mb-16">
<h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-[var(--color-text-primary)] mb-4">
How It Works
</h2>
<p class="text-lg text-[var(--color-text-secondary)] max-w-2xl mx-auto">
Three simple steps to full identity protection
</p>
</div>
<div class="flex flex-col gap-12 md:gap-16">
<For each={steps}>
{(step, index) => <StepBlock step={step} index={index()} />}
</For>
</div>
</PageContainer>
</section>
);
}

View File

@@ -1,217 +0,0 @@
import { For } from "solid-js";
import type { JSX } from "solid-js";
import { cn } from "~/lib/utils";
import Card from "~/components/ui/Card";
import PageContainer from "~/components/layout/PageContainer";
function CheckIcon() {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="flex-shrink-0"
>
<path
d="M4 9l3 3 7-7"
stroke="var(--color-success)"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
}
function ProactiveIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-[var(--color-brand-primary)]"
>
<path
d="M13 3l-2 6h5l-3 8"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M4 14l5-5m0 0l5 5m-5-5v12"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
}
function AIIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-[var(--color-brand-primary)]"
>
<path
d="M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5L12 3z"
stroke="currentColor"
stroke-width="1.5"
stroke-linejoin="round"
/>
<path
d="M8 16l2 2-2 2M16 16l-2 2 2 2"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
}
function PrivacyIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-[var(--color-brand-primary)]"
>
<path
d="M12 2l9 4v6c0 5.55-3.84 10.74-9 12-5.16-1.26-9-6.45-9-12V6l9-4z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M9 12l2 2 4-4"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
}
interface ValueProp {
title: string;
description: string;
items: string[];
icon: () => JSX.Element;
}
const valueProps: ValueProp[] = [
{
title: "Proactive, Not Reactive",
description:
"We detect threats before they cause damage, so you can act early.",
icon: ProactiveIcon,
items: [
"Real-time dark web scanning",
"Pre-breach alerts and warnings",
"Automated threat response",
],
},
{
title: "AI-Powered Detection",
description:
"Machine learning models trained on real scam data to catch the latest threats.",
icon: AIIcon,
items: [
"Deepfake voice identification",
"Pattern-based scam detection",
"Continuous model improvement",
],
},
{
title: "Privacy First",
description:
"Your data stays encrypted and private. We never sell your information.",
icon: PrivacyIcon,
items: [
"End-to-end encrypted data",
"GDPR and CCPA compliant",
"Zero data selling policy",
],
},
];
interface ValueCardProps {
prop: ValueProp;
}
function ValueCard(props: ValueCardProps) {
const Icon = props.prop.icon;
return (
<Card class="backdrop-blur-2xl">
<div class="flex flex-col h-full">
<div class="mb-3 p-2 rounded-lg bg-[var(--color-bg-secondary)] w-fit">
<Icon />
</div>
<h3 class="text-lg font-semibold text-[var(--color-text-primary)] mb-2">
{props.prop.title}
</h3>
<p class="text-[var(--color-text-secondary)] mb-4 leading-relaxed">
{props.prop.description}
</p>
<ul class="space-y-2 flex-1">
<For each={props.prop.items}>
{(item) => (
<li class="flex items-start gap-2.5">
<CheckIcon />
<span class="text-[var(--color-text-secondary)] text-sm">
{item}
</span>
</li>
)}
</For>
</ul>
</div>
</Card>
);
}
interface WhyKordantSectionProps {
class?: string;
}
export default function WhyKordantSection(props: WhyKordantSectionProps) {
return (
<section
id="why-kordant"
class={cn("py-20 md:py-28 scroll-mt-16", props.class)}
>
<PageContainer py="py-8">
<div class="text-center mb-16">
<h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-[var(--color-text-primary)] mb-4">
Why Kordant
</h2>
<p class="text-lg text-[var(--color-text-secondary)] max-w-2xl mx-auto">
Built on cutting-edge technology with your privacy at the core
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<For each={valueProps}>
{(prop) => <ValueCard prop={prop} />}
</For>
</div>
</PageContainer>
</section>
);
}

View File

@@ -1,120 +0,0 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render } from "solid-js/web";
import type { JSX } from "solid-js";
vi.mock("@solidjs/router", () => ({
A: (props: { href?: string; children?: JSX.Element }) => {
const href = props.href || "#";
return (
<a href={href}>
{props.children}
</a>
);
},
}));
import HeroSection from "./HeroSection";
function mount(comp: () => JSX.Element): HTMLDivElement {
const container = document.createElement("div");
document.body.appendChild(container);
render(() => comp(), container);
return container;
}
beforeEach(() => {
document.body.innerHTML = "";
});
afterEach(() => {
document.body.innerHTML = "";
});
describe("HeroSection", () => {
it("renders the headline with AI-Powered Identity Protection", () => {
mount(() => <HeroSection />);
expect(document.body.textContent).toContain("AI-Powered");
expect(document.body.textContent).toContain("Identity Protection");
expect(document.body.textContent).toContain("for Everyone");
});
it("renders the subheadline", () => {
mount(() => <HeroSection />);
expect(document.body.textContent).toContain("Kordant uses advanced AI");
});
it("renders the Get Started CTA", () => {
mount(() => <HeroSection />);
expect(document.body.textContent).toContain("Get Started");
const primaryBtn = document.querySelector("button.gradient-primary");
expect(primaryBtn).toBeTruthy();
});
it("renders the Learn More CTA", () => {
mount(() => <HeroSection />);
expect(document.body.textContent).toContain("Learn More");
});
it("renders trust indicators", () => {
mount(() => <HeroSection />);
expect(document.body.textContent).toContain("No credit card required");
expect(document.body.textContent).toContain("Free tier available");
});
it("renders the shield icon SVG", () => {
mount(() => <HeroSection />);
const svg = document.querySelector("svg");
expect(svg).toBeTruthy();
});
it("wraps content in PageContainer", () => {
mount(() => <HeroSection />);
const container = document.querySelector(".max-w-7xl");
expect(container).toBeTruthy();
});
it("renders two buttons for CTAs", () => {
mount(() => <HeroSection />);
const buttons = document.querySelectorAll("button");
expect(buttons.length).toBe(2);
});
it("has Get Started button wrapped in link to /signup", () => {
mount(() => <HeroSection />);
const links = document.querySelectorAll("a");
const signupLink = Array.from(links).find(
(a) => a.getAttribute("href") === "/signup",
);
expect(signupLink).toBeTruthy();
expect(signupLink!.textContent).toContain("Get Started");
});
it("has Learn More button wrapped in link to #features", () => {
mount(() => <HeroSection />);
const links = document.querySelectorAll("a");
const featuresLink = Array.from(links).find(
(a) => a.getAttribute("href") === "#features",
);
expect(featuresLink).toBeTruthy();
expect(featuresLink!.textContent).toContain("Learn More");
});
it("applies custom class prop", () => {
mount(() => <HeroSection class="custom-hero" />);
const section = document.querySelector("section.custom-hero");
expect(section).toBeTruthy();
});
it("has centered text layout", () => {
mount(() => <HeroSection />);
const inner = document.querySelector(".text-center");
expect(inner).toBeTruthy();
});
it("has responsive vertical padding", () => {
mount(() => <HeroSection />);
const inner = document.querySelector(".py-20");
expect(inner).toBeTruthy();
expect(inner!.className).toContain("md:py-32");
});
});

View File

@@ -1,379 +0,0 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render } from "solid-js/web";
import type { JSX } from "solid-js";
vi.mock("@solidjs/router", () => ({
A: (props: { href?: string; children?: JSX.Element }) => {
const href = props.href || "#";
return (
<a href={href}>
{props.children}
</a>
);
},
}));
import HowItWorksSection from "./HowItWorksSection";
import FeaturesGridSection from "./FeaturesGridSection";
import ForUsersSection from "./ForUsersSection";
import WhyKordantSection from "./WhyKordantSection";
import CTABannerSection from "./CTABannerSection";
function mount(comp: () => JSX.Element): HTMLDivElement {
const container = document.createElement("div");
document.body.appendChild(container);
render(() => comp(), container);
return container;
}
beforeEach(() => {
document.body.innerHTML = "";
});
afterEach(() => {
document.body.innerHTML = "";
});
describe("HowItWorksSection", () => {
it("renders the section heading", () => {
mount(() => <HowItWorksSection />);
expect(document.body.textContent).toContain("How It Works");
});
it("renders the section subheading", () => {
mount(() => <HowItWorksSection />);
expect(document.body.textContent).toContain(
"Three simple steps to full identity protection",
);
});
it("renders all 3 steps", () => {
mount(() => <HowItWorksSection />);
expect(document.body.textContent).toContain("Enroll Your Identity");
expect(document.body.textContent).toContain("We Monitor 24/7");
expect(document.body.textContent).toContain("Get Instant Alerts");
});
it("renders step descriptions", () => {
mount(() => <HowItWorksSection />);
expect(document.body.textContent).toContain(
"Sign up and add your emails",
);
expect(document.body.textContent).toContain("dark web scans");
expect(document.body.textContent).toContain("real-time notifications");
});
it("renders 3 numbered circles with gradient-primary", () => {
mount(() => <HowItWorksSection />);
const circles = document.querySelectorAll(".gradient-primary");
expect(circles.length).toBe(3);
});
it("has the anchor ID for smooth scrolling", () => {
mount(() => <HowItWorksSection />);
const section = document.querySelector('#how-it-works');
expect(section).toBeTruthy();
});
it("applies custom class prop", () => {
mount(() => <HowItWorksSection class="custom-how" />);
const section = document.querySelector("section.custom-how");
expect(section).toBeTruthy();
});
it("wraps content in PageContainer", () => {
mount(() => <HowItWorksSection />);
const container = document.querySelector(".max-w-7xl");
expect(container).toBeTruthy();
});
});
describe("FeaturesGridSection", () => {
it("renders the section heading", () => {
mount(() => <FeaturesGridSection />);
expect(document.body.textContent).toContain("Platform Features");
});
it("renders the section subheading", () => {
mount(() => <FeaturesGridSection />);
expect(document.body.textContent).toContain("Comprehensive protection");
});
it("renders all 6 feature cards", () => {
mount(() => <FeaturesGridSection />);
expect(document.body.textContent).toContain("DarkWatch");
expect(document.body.textContent).toContain("VoicePrint");
expect(document.body.textContent).toContain("SpamShield");
expect(document.body.textContent).toContain("HomeTitle");
expect(document.body.textContent).toContain("RemoveBrokers");
expect(document.body.textContent).toContain("Family Plans");
});
it("renders 6 Card components", () => {
mount(() => <FeaturesGridSection />);
const cards = document.querySelectorAll(".gradient-card");
expect(cards.length).toBe(6);
});
it("renders feature descriptions", () => {
mount(() => <FeaturesGridSection />);
expect(document.body.textContent).toContain("dark web monitoring");
expect(document.body.textContent).toContain("voice clone detection");
expect(document.body.textContent).toContain("scam call blocking");
});
it("has the anchor ID for smooth scrolling", () => {
mount(() => <FeaturesGridSection />);
const section = document.querySelector('#features');
expect(section).toBeTruthy();
});
it("applies custom class prop", () => {
mount(() => <FeaturesGridSection class="custom-features" />);
const section = document.querySelector("section.custom-features");
expect(section).toBeTruthy();
});
it("uses responsive grid layout", () => {
mount(() => <FeaturesGridSection />);
const grid = document.querySelector(".grid-cols-1");
expect(grid).toBeTruthy();
expect(grid!.className).toContain("md:grid-cols-2");
expect(grid!.className).toContain("lg:grid-cols-3");
});
});
describe("ForUsersSection", () => {
it("renders the section heading", () => {
mount(() => <ForUsersSection />);
expect(document.body.textContent).toContain("For Everyone");
});
it("renders the section subheading", () => {
mount(() => <ForUsersSection />);
expect(document.body.textContent).toContain(
"Whether you're protecting yourself",
);
});
it("renders both panels", () => {
mount(() => <ForUsersSection />);
expect(document.body.textContent).toContain("For Individuals");
expect(document.body.textContent).toContain("For Families");
});
it("renders individual panel description", () => {
mount(() => <ForUsersSection />);
expect(document.body.textContent).toContain(
"Personal identity protection",
);
});
it("renders family panel description", () => {
mount(() => <ForUsersSection />);
expect(document.body.textContent).toContain("Group management tools");
});
it("renders bullet items for individuals", () => {
mount(() => <ForUsersSection />);
expect(document.body.textContent).toContain(
"Monitor personal email and phone numbers",
);
expect(document.body.textContent).toContain(
"Dark web credential scanning",
);
});
it("renders bullet items for families", () => {
mount(() => <ForUsersSection />);
expect(document.body.textContent).toContain(
"Add unlimited family members",
);
expect(document.body.textContent).toContain("Shared alert dashboard");
});
it("renders checkmark icons", () => {
mount(() => <ForUsersSection />);
const checkmarks = document.querySelectorAll(
'svg path[fill="var(--color-success)"]',
);
expect(checkmarks.length).toBeGreaterThan(0);
});
it("renders 2 Card components for panels", () => {
mount(() => <ForUsersSection />);
const cards = document.querySelectorAll(".gradient-card");
expect(cards.length).toBe(2);
});
it("has the anchor ID for smooth scrolling", () => {
mount(() => <ForUsersSection />);
const section = document.querySelector('#for-users');
expect(section).toBeTruthy();
});
it("applies custom class prop", () => {
mount(() => <ForUsersSection class="custom-users" />);
const section = document.querySelector("section.custom-users");
expect(section).toBeTruthy();
});
it("uses two-column grid on desktop", () => {
mount(() => <ForUsersSection />);
const grid = document.querySelector(".grid-cols-1");
expect(grid).toBeTruthy();
expect(grid!.className).toContain("md:grid-cols-2");
});
});
describe("WhyKordantSection", () => {
it("renders the section heading", () => {
mount(() => <WhyKordantSection />);
expect(document.body.textContent).toContain("Why Kordant");
});
it("renders the section subheading", () => {
mount(() => <WhyKordantSection />);
expect(document.body.textContent).toContain(
"Built on cutting-edge technology",
);
});
it("renders all 3 value prop cards", () => {
mount(() => <WhyKordantSection />);
expect(document.body.textContent).toContain("Proactive, Not Reactive");
expect(document.body.textContent).toContain("AI-Powered Detection");
expect(document.body.textContent).toContain("Privacy First");
});
it("renders value prop descriptions", () => {
mount(() => <WhyKordantSection />);
expect(document.body.textContent).toContain(
"detect threats before they cause damage",
);
expect(document.body.textContent).toContain(
"Machine learning models trained",
);
expect(document.body.textContent).toContain("encrypted and private");
});
it("renders bullet items for each card", () => {
mount(() => <WhyKordantSection />);
expect(document.body.textContent).toContain(
"Real-time dark web scanning",
);
expect(document.body.textContent).toContain(
"Deepfake voice identification",
);
expect(document.body.textContent).toContain("End-to-end encrypted data");
});
it("renders 3 Card components", () => {
mount(() => <WhyKordantSection />);
const cards = document.querySelectorAll(".gradient-card");
expect(cards.length).toBe(3);
});
it("has the anchor ID for smooth scrolling", () => {
mount(() => <WhyKordantSection />);
const section = document.querySelector('#why-kordant');
expect(section).toBeTruthy();
});
it("applies custom class prop", () => {
mount(() => <WhyKordantSection class="custom-why" />);
const section = document.querySelector("section.custom-why");
expect(section).toBeTruthy();
});
it("uses three-column grid on desktop", () => {
mount(() => <WhyKordantSection />);
const grid = document.querySelector(".grid-cols-1");
expect(grid).toBeTruthy();
expect(grid!.className).toContain("md:grid-cols-3");
});
});
describe("CTABannerSection", () => {
it("renders the CTA headline", () => {
mount(() => <CTABannerSection />);
expect(document.body.textContent).toContain(
"Ready to protect your identity?",
);
});
it("renders the CTA subtext", () => {
mount(() => <CTABannerSection />);
expect(document.body.textContent).toContain(
"Join thousands of users",
);
});
it("renders Create Account button", () => {
mount(() => <CTABannerSection />);
expect(document.body.textContent).toContain("Create Account");
const primaryBtn = document.querySelector("button.gradient-primary");
expect(primaryBtn).toBeTruthy();
});
it("renders Sign In button", () => {
mount(() => <CTABannerSection />);
expect(document.body.textContent).toContain("Sign In");
});
it("has Create Account link to /signup", () => {
mount(() => <CTABannerSection />);
const links = document.querySelectorAll("a");
const signupLink = Array.from(links).find(
(a) => a.getAttribute("href") === "/signup",
);
expect(signupLink).toBeTruthy();
expect(signupLink!.textContent).toContain("Create Account");
});
it("has Sign In link to /login", () => {
mount(() => <CTABannerSection />);
const links = document.querySelectorAll("a");
const loginLink = Array.from(links).find(
(a) => a.getAttribute("href") === "/login",
);
expect(loginLink).toBeTruthy();
expect(loginLink!.textContent).toContain("Sign In");
});
it("renders 2 buttons", () => {
mount(() => <CTABannerSection />);
const buttons = document.querySelectorAll("button");
expect(buttons.length).toBe(2);
});
it("has the anchor ID for smooth scrolling", () => {
mount(() => <CTABannerSection />);
const section = document.querySelector('#cta');
expect(section).toBeTruthy();
});
it("applies custom class prop", () => {
mount(() => <CTABannerSection class="custom-cta" />);
const section = document.querySelector("section.custom-cta");
expect(section).toBeTruthy();
});
it("uses centered text layout", () => {
mount(() => <CTABannerSection />);
const inner = document.querySelector(".text-center");
expect(inner).toBeTruthy();
});
it("wraps content in PageContainer", () => {
mount(() => <CTABannerSection />);
const container = document.querySelector(".max-w-7xl");
expect(container).toBeTruthy();
});
it("uses gradient card for CTA banner", () => {
mount(() => <CTABannerSection />);
const card = document.querySelector(".gradient-card");
expect(card).toBeTruthy();
});
});

View File

@@ -143,7 +143,7 @@ function RealtimeIndicator() {
<button
type="button"
onClick={clearUnread}
class="relative flex items-center justify-center w-6 h-6 rounded-full bg-[var(--color-error)] text-white text-[10px] font-bold leading-none transition-transform hover:scale-110"
class="relative flex items-center justify-center w-6 h-6 rounded-full bg-(--color-error) text-white text-[10px] font-bold leading-none transition-transform hover:scale-110"
aria-label={`${unreadCount()} unread alerts`}
>
{unreadCount() > 99 ? "99+" : unreadCount()}
@@ -152,17 +152,24 @@ function RealtimeIndicator() {
<Show
when={connectionStatus() === "connected"}
fallback={
connectionStatus() === "reconnecting" || connectionStatus() === "connecting" ? (
<div class="flex items-center gap-1 text-[10px] text-[var(--color-warning)]" aria-label="Reconnecting">
connectionStatus() === "reconnecting" ||
connectionStatus() === "connecting" ? (
<div
class="flex items-center gap-1 text-[10px] text-(--color-warning)"
aria-label="Reconnecting"
>
<span class="relative flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-[var(--color-warning)] opacity-75" />
<span class="relative inline-flex rounded-full h-2 w-2 bg-[var(--color-warning)]" />
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-(--color-warning) opacity-75" />
<span class="relative inline-flex rounded-full h-2 w-2 bg-(--color-warning)" />
</span>
<span class="hidden sm:inline">Reconnecting</span>
</div>
) : (
<div class="flex items-center gap-1 text-[10px] text-[var(--color-text-muted)]" aria-label="Offline">
<span class="inline-flex rounded-full h-2 w-2 bg-[var(--color-text-muted)]" />
<div
class="flex items-center gap-1 text-[10px] text-(--color-text-muted)"
aria-label="Offline"
>
<span class="inline-flex rounded-full h-2 w-2 bg-(--color-text-muted)" />
<span class="hidden sm:inline">Offline</span>
</div>
)
@@ -170,8 +177,8 @@ function RealtimeIndicator() {
>
<div class="flex items-center gap-1" aria-label="Connected">
<span class="relative flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-[var(--color-success)] opacity-75" />
<span class="relative inline-flex rounded-full h-2 w-2 bg-[var(--color-success)]" />
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-(--color-success) opacity-75" />
<span class="relative inline-flex rounded-full h-2 w-2 bg-(--color-success)" />
</span>
</div>
</Show>
@@ -197,7 +204,11 @@ export default function Navbar() {
return location.pathname.startsWith(href);
};
const NavLink = (props: { href: string; label: string; mobile?: boolean }) => (
const NavLink = (props: {
href: string;
label: string;
mobile?: boolean;
}) => (
<A
href={props.href}
class={cn(
@@ -205,9 +216,11 @@ export default function Navbar() {
? "block px-3 py-2 rounded-lg text-base font-medium transition-colors"
: "text-sm font-medium transition-colors",
isActive(props.href)
? "text-[var(--color-text-primary)]"
? "text-text-primary"
: "text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]",
props.mobile && !isActive(props.href) && "hover:bg-[var(--color-bg-secondary)]",
props.mobile &&
!isActive(props.href) &&
"hover:bg-[var(--color-bg-secondary)]",
)}
onClick={() => props.mobile && setMobileOpen(false)}
>
@@ -234,10 +247,14 @@ export default function Navbar() {
<div class="hidden md:flex items-center gap-6">
<SignedOut>
{marketingLinks.map(link => <NavLink href={link.href} label={link.label} />)}
{marketingLinks.map((link) => (
<NavLink href={link.href} label={link.label} />
))}
</SignedOut>
<SignedIn>
{productLinks.map(link => <NavLink href={link.href} label={link.label} />)}
{productLinks.map((link) => (
<NavLink href={link.href} label={link.label} />
))}
</SignedIn>
</div>
@@ -263,7 +280,7 @@ export default function Navbar() {
type="button"
aria-label="Toggle menu"
class="p-2 rounded-lg text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]"
onClick={() => setMobileOpen(v => !v)}
onClick={() => setMobileOpen((v) => !v)}
>
<Show
when={mobileOpen()}
@@ -304,12 +321,12 @@ export default function Navbar() {
<div class="md:hidden glass border-t border-[var(--color-border)]">
<div class="px-4 py-4 space-y-1">
<SignedOut>
{marketingLinks.map(link => (
{marketingLinks.map((link) => (
<NavLink href={link.href} label={link.label} mobile />
))}
</SignedOut>
<SignedIn>
{productLinks.map(link => (
{productLinks.map((link) => (
<NavLink href={link.href} label={link.label} mobile />
))}
</SignedIn>