nxtgauge-frontend-solid/src/components/PublicLanding.tsx

736 lines
33 KiB
TypeScript

import { A } from '@solidjs/router';
import { createEffect, createMemo, createSignal, For, onCleanup, onMount, Show } from 'solid-js';
import OpportunityGraph from '~/components/OpportunityGraph';
import PublicHeader from '~/components/PublicHeader';
import PublicFooter from '~/components/PublicFooter';
type PathCard = {
title: string;
description: string;
button: string;
chip: string;
icon: 'briefcase' | 'user' | 'users' | 'code' | 'camera' | 'sparkles' | 'grad' | 'film' | 'pen' | 'share' | 'dumbbell' | 'utensils';
href: string;
image: string;
audience: 'customer' | 'professional' | 'company' | 'job_seeker';
};
type Flow = {
label: string;
title: string;
description: string;
image: string;
steps: Array<{ title: string; description: string }>;
};
const pathCards: PathCard[] = [
{
title: 'Post a Job',
description: 'Create verified job openings and find the right talent faster.',
button: 'Start as Company',
chip: 'Company',
icon: 'briefcase',
href: '/auth/register?intent=company&redirect=/users/onboarding/company',
audience: 'company',
image: 'https://images.unsplash.com/photo-1484480974693-6ca0a78fb36b?q=80&w=800&auto=format&fit=crop',
},
{
title: 'Apply for Jobs',
description: 'Build your profile and apply to approved opportunities quickly.',
button: 'Start as Job Seeker',
chip: 'Job Seeker',
icon: 'user',
href: '/auth/register?intent=job_seeker&redirect=/users/onboarding/job-seeker',
audience: 'job_seeker',
image: 'https://images.unsplash.com/photo-1586281380349-632531db7ed4?q=80&w=800&auto=format&fit=crop',
},
{
title: 'Hire a Professional',
description: 'Post your requirement and discover trusted specialists.',
button: 'Start as Customer',
chip: 'Customer',
icon: 'users',
href: '/auth/register?intent=customer&redirect=/users/onboarding/customer',
audience: 'customer',
image: 'https://images.unsplash.com/photo-1450101499163-c8848c66ca85?q=80&w=800&auto=format&fit=crop',
},
{
title: 'Join as Developer',
description: 'Build products and grow with verified client demand.',
button: 'Join Developer',
chip: 'Professional',
icon: 'code',
href: '/auth/register?intent=professional&redirect=/users/onboarding/professional&profession=developer',
audience: 'professional',
image: 'https://images.unsplash.com/photo-1498050108023-c5249f4df085?q=80&w=800&auto=format&fit=crop',
},
{
title: 'Join as Photographer',
description: 'Capture events and campaigns with trusted bookings.',
button: 'Join Photographer',
chip: 'Professional',
icon: 'camera',
href: '/auth/register?intent=professional&redirect=/users/onboarding/professional&profession=photographer',
audience: 'professional',
image: 'https://images.unsplash.com/photo-1516035069371-29a1b244cc32?q=80&w=800&auto=format&fit=crop',
},
{
title: 'Join as Makeup Artist',
description: 'Offer styling services with profile-based trust signals.',
button: 'Join Makeup Artist',
chip: 'Professional',
icon: 'sparkles',
href: '/auth/register?intent=professional&redirect=/users/onboarding/professional&profession=makeup_artist',
audience: 'professional',
image: 'https://images.unsplash.com/photo-1522335789203-aabd1fc54bc9?q=80&w=800&auto=format&fit=crop',
},
{
title: 'Join as Tutor',
description: 'Teach learners and build your reputation with verified profiles.',
button: 'Join Tutor',
chip: 'Professional',
icon: 'grad',
href: '/auth/register?intent=professional&redirect=/users/onboarding/professional&profession=tutor',
audience: 'professional',
image: 'https://images.unsplash.com/photo-1497633762265-9d179a990aa6?q=80&w=800&auto=format&fit=crop',
},
{
title: 'Join as Video Editor',
description: 'Create compelling edits and work with quality clients.',
button: 'Join Video Editor',
chip: 'Professional',
icon: 'film',
href: '/auth/register?intent=professional&redirect=/users/onboarding/professional&profession=video_editor',
audience: 'professional',
image: 'https://images.unsplash.com/photo-1574717024653-61fd2cf4d44d?q=80&w=800&auto=format&fit=crop',
},
{
title: 'Join as Graphic Designer',
description: 'Design brand-ready visuals and collaborate with growing businesses.',
button: 'Join Graphic Designer',
chip: 'Professional',
icon: 'pen',
href: '/auth/register?intent=professional&redirect=/users/onboarding/professional&profession=graphic_designer',
audience: 'professional',
image: 'https://images.unsplash.com/photo-1558655146-d09347e92766?q=80&w=800&auto=format&fit=crop',
},
{
title: 'Join as Social Media Manager',
description: 'Plan campaigns and scale audience growth for clients.',
button: 'Join Social Manager',
chip: 'Professional',
icon: 'share',
href: '/auth/register?intent=professional&redirect=/users/onboarding/professional&profession=social_media_manager',
audience: 'professional',
image: 'https://images.unsplash.com/photo-1611162618071-b39a2ec055fb?q=80&w=800&auto=format&fit=crop',
},
{
title: 'Join as Fitness Trainer',
description: 'Coach clients with structured plans and trusted profiles.',
button: 'Join Trainer',
chip: 'Professional',
icon: 'dumbbell',
href: '/auth/register?intent=professional&redirect=/users/onboarding/professional&profession=fitness_trainer',
audience: 'professional',
image: 'https://images.unsplash.com/photo-1517836357463-d25dfeac3438?q=80&w=800&auto=format&fit=crop',
},
{
title: 'Join as Catering Services',
description: 'Showcase event-ready menus to customers and companies.',
button: 'Join Catering',
chip: 'Professional',
icon: 'utensils',
href: '/auth/register?intent=professional&redirect=/users/onboarding/professional&profession=catering_services',
audience: 'professional',
image: 'https://images.unsplash.com/photo-1555244162-803834f70033?q=80&w=800&auto=format&fit=crop',
},
];
const benefits = [
{ title: 'Verified profiles & businesses', body: 'Identity and profile checks reduce fake submissions and spam.', icon: 'shield-check' },
{ title: 'Approval-based quality (24-48 hours)', body: 'Profiles, requirements, and jobs are reviewed before visibility.', icon: 'zap' },
{ title: 'Smart matching using tags/skills', body: 'Tag-based relevance helps surface better opportunities faster.', icon: 'hash' },
{ title: 'Focused discovery with filters', body: 'Search and filter tools keep opportunity discovery focused.', icon: 'search' },
{ title: 'Controlled contact visibility', body: 'Sensitive contact flow remains controlled by role and workflow.', icon: 'lock' },
{ title: 'Notifications & updates', body: 'Track approvals, responses, applications, and key updates.', icon: 'bell' },
] as const;
const flows: Flow[] = [
{
label: 'Customers',
title: 'Need to solution, with verified trust in the middle',
description: 'Post your requirement, pass quality checks, and receive focused responses.',
image: 'https://images.unsplash.com/photo-1454165804606-c3d57bc86b40?q=80&w=1200&auto=format&fit=crop',
steps: [
{ title: 'Create account', description: 'Sign up quickly with customer intent.' },
{ title: 'Share requirement', description: 'Add budget, scope, and timing.' },
{ title: 'Review and verify', description: 'Quality checks run before visibility.' },
{ title: 'Track responses', description: 'Monitor replies and status updates.' },
],
},
{
label: 'Professionals',
title: 'Profile first, opportunity next',
description: 'Build profile quality once, then receive better-fit lead discovery.',
image: 'https://images.unsplash.com/photo-1461749280684-dccba630e2f6?q=80&w=1200&auto=format&fit=crop',
steps: [
{ title: 'Pick role and skills', description: 'Choose service category and specialization.' },
{ title: 'Build profile', description: 'Add portfolio, strengths, and proof.' },
{ title: 'Pass verification', description: 'Approval protects platform trust.' },
{ title: 'Respond to opportunities', description: 'Manage matching leads in one flow.' },
],
},
{
label: 'Companies',
title: 'Hiring workflows without chaos',
description: 'Publish requirements, keep quality high, and track applicants clearly.',
image: 'https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?q=80&w=1200&auto=format&fit=crop',
steps: [
{ title: 'Create company profile', description: 'Set hiring context and org details.' },
{ title: 'Post requirements', description: 'Publish role details and expectations.' },
{ title: 'Approval before publish', description: 'Listings are reviewed first.' },
{ title: 'Manage pipeline', description: 'Track candidates and responses.' },
],
},
{
label: 'Job Seekers',
title: 'Onboard once, apply with confidence',
description: 'Get visible only after approval, then discover jobs with targeted filters.',
image: 'https://images.unsplash.com/photo-1450101499163-c8848c66ca85?q=80&w=1200&auto=format&fit=crop',
steps: [
{ title: 'Set up profile', description: 'Complete essentials and work preferences.' },
{ title: 'Approval check', description: 'Review keeps quality consistent.' },
{ title: 'Explore matching jobs', description: 'Filter by practical criteria.' },
{ title: 'Apply and track', description: 'Submit applications and watch status.' },
],
},
];
const faqs = [
{
q: 'What is Nxtgauge?',
a: 'Nxtgauge connects customers, professionals, companies, and job seekers in one trusted platform.',
},
{
q: 'Who can join Nxtgauge?',
a: 'Customers, service professionals, companies, and job seekers can create an account and choose their path.',
},
{
q: 'Why does Nxtgauge require verification?',
a: 'Verification helps reduce fake profiles and improves trust across the platform.',
},
{
q: 'How long does account approval take?',
a: 'Most profile approvals are completed within 24-48 hours.',
},
{
q: 'Do I need to choose my role during signup?',
a: 'No. You can sign up once and continue with the role flow that fits you best.',
},
] as const;
const chipNodes = [
{ kind: 'code', left: '2%', top: '14%', size: 44, cls: 'lp-chip-slow' },
{ kind: 'camera', left: '94%', top: '22%', size: 46, cls: 'lp-chip-mid' },
{ kind: 'briefcase', left: '3%', top: '76%', size: 46, cls: 'lp-chip-fast' },
{ kind: 'bell', left: '93%', top: '80%', size: 42, cls: 'lp-chip-slow' },
{ kind: 'sparkles', left: '50%', top: '6%', size: 40, cls: 'lp-chip-mid' },
] as const;
function ChipIcon(props: { kind: (typeof chipNodes)[number]['kind'] }) {
const common = { fill: 'none', stroke: 'currentColor', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' } as const;
if (props.kind === 'camera') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" {...common}>
<path d="M14 4a2 2 0 0 1 1.76 1.05l.49.9A2 2 0 0 0 18 7h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h2a2 2 0 0 0 1.76-1.05l.49-.9A2 2 0 0 1 10 4z" />
<circle cx="12" cy="13" r="3" />
</svg>
);
}
if (props.kind === 'briefcase') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" {...common}>
<path d="M16 6V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2" />
<path d="M22 13a18 18 0 0 1-20 0" />
<rect x="2" y="6" width="20" height="14" rx="2" />
</svg>
);
}
if (props.kind === 'bell') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" {...common}>
<path d="M10.3 21a2 2 0 0 0 3.4 0" />
<path d="M3.3 15.3A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.67C19.4 14 18 12.5 18 8A6 6 0 0 0 6 8c0 4.5-1.4 6-2.7 7.3" />
</svg>
);
}
if (props.kind === 'sparkles') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" {...common}>
<path d="M11 2.8a1 1 0 0 1 2 0l1 5.6a2 2 0 0 0 1.6 1.6l5.6 1a1 1 0 0 1 0 2l-5.6 1a2 2 0 0 0-1.6 1.6l-1 5.6a1 1 0 0 1-2 0l-1-5.6A2 2 0 0 0 8.4 14l-5.6-1a1 1 0 0 1 0-2l5.6-1A2 2 0 0 0 10 8.4z" />
<path d="M20 2v4M22 4h-4" />
</svg>
);
}
return (
<svg viewBox="0 0 24 24" aria-hidden="true" {...common}>
<path d="m18 16 4-4-4-4" />
<path d="m6 8-4 4 4 4" />
<path d="m14.5 4-5 16" />
</svg>
);
}
function PathRoleIcon(props: { kind: PathCard['icon'] }) {
const common = { fill: 'none', stroke: 'currentColor', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' } as const;
if (props.kind === 'briefcase') return <svg viewBox="0 0 24 24" aria-hidden="true" {...common}><path d="M16 6V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2" /><path d="M22 13a18 18 0 0 1-20 0" /><rect x="2" y="6" width="20" height="14" rx="2" /></svg>;
if (props.kind === 'user') return <svg viewBox="0 0 24 24" aria-hidden="true" {...common}><circle cx="12" cy="8" r="4" /><path d="M6 20c1.4-3.2 3.4-5 6-5s4.6 1.8 6 5" /></svg>;
if (props.kind === 'users') return <svg viewBox="0 0 24 24" aria-hidden="true" {...common}><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M22 21v-2a4 4 0 0 0-3-3.87" /><path d="M16 3.13a4 4 0 0 1 0 7.75" /></svg>;
if (props.kind === 'code') return <svg viewBox="0 0 24 24" aria-hidden="true" {...common}><path d="m18 16 4-4-4-4" /><path d="m6 8-4 4 4 4" /><path d="m14.5 4-5 16" /></svg>;
if (props.kind === 'camera') return <svg viewBox="0 0 24 24" aria-hidden="true" {...common}><path d="M14 4a2 2 0 0 1 1.76 1.05l.49.9A2 2 0 0 0 18 7h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h2a2 2 0 0 0 1.76-1.05l.49-.9A2 2 0 0 1 10 4z" /><circle cx="12" cy="13" r="3" /></svg>;
if (props.kind === 'sparkles') return <svg viewBox="0 0 24 24" aria-hidden="true" {...common}><path d="M11 2.8a1 1 0 0 1 2 0l1 5.6a2 2 0 0 0 1.6 1.6l5.6 1a1 1 0 0 1 0 2l-5.6 1a2 2 0 0 0-1.6 1.6l-1 5.6a1 1 0 0 1-2 0l-1-5.6A2 2 0 0 0 8.4 14l-5.6-1a1 1 0 0 1 0-2l5.6-1A2 2 0 0 0 10 8.4z" /><path d="M20 2v4M22 4h-4" /></svg>;
if (props.kind === 'grad') return <svg viewBox="0 0 24 24" aria-hidden="true" {...common}><path d="m22 10-10-5-10 5 10 5 10-5z" /><path d="M6 12v5c3 2 9 2 12 0v-5" /></svg>;
if (props.kind === 'film') return <svg viewBox="0 0 24 24" aria-hidden="true" {...common}><rect x="2" y="2" width="20" height="20" rx="2" /><path d="M7 2v20M17 2v20M2 7h20M2 17h20" /></svg>;
if (props.kind === 'pen') return <svg viewBox="0 0 24 24" aria-hidden="true" {...common}><path d="M12 20h9" /><path d="m16.5 3.5 4 4L7 21H3v-4z" /></svg>;
if (props.kind === 'share') return <svg viewBox="0 0 24 24" aria-hidden="true" {...common}><circle cx="18" cy="5" r="3" /><circle cx="6" cy="12" r="3" /><circle cx="18" cy="19" r="3" /><path d="M8.6 13.4 15.4 17M15.4 7 8.6 10.6" /></svg>;
if (props.kind === 'dumbbell') return <svg viewBox="0 0 24 24" aria-hidden="true" {...common}><path d="M6 10v4M4 9v6M2 8v8M18 10v4M20 9v6M22 8v8M6 12h12" /></svg>;
return <svg viewBox="0 0 24 24" aria-hidden="true" {...common}><path d="M3 4h18" /><path d="M6 4v16h12V4" /><path d="M9 10h6M9 14h6" /></svg>;
}
function CheckBadgeIcon() {
return (
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M9 12.4l2 2 4-4" />
<circle cx="12" cy="12" r="9" />
</svg>
);
}
function WhyBenefitIcon(props: { kind: (typeof benefits)[number]['icon'] }) {
const common = { fill: 'none', stroke: 'currentColor', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' } as const;
if (props.kind === 'shield-check') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" {...common}>
<path d="M12 2 4 6v6c0 5 3.5 8.5 8 10 4.5-1.5 8-5 8-10V6z" />
<path d="m9 12 2 2 4-4" />
</svg>
);
}
if (props.kind === 'zap') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" {...common}>
<path d="M13 2 3 14h7l-1 8 10-12h-7z" />
</svg>
);
}
if (props.kind === 'hash') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" {...common}>
<path d="M4 9h16M3 15h16M10 3 8 21M16 3l-2 18" />
</svg>
);
}
if (props.kind === 'search') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" {...common}>
<circle cx="11" cy="11" r="7" />
<path d="m21 21-4.35-4.35" />
</svg>
);
}
if (props.kind === 'lock') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" {...common}>
<rect x="4" y="11" width="16" height="10" rx="2" />
<path d="M8 11V7a4 4 0 0 1 8 0v4" />
</svg>
);
}
return (
<svg viewBox="0 0 24 24" aria-hidden="true" {...common}>
<path d="M10.3 21a2 2 0 0 0 3.4 0" />
<path d="M3.3 15.3A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.67C19.4 14 18 12.5 18 8A6 6 0 0 0 6 8c0 4.5-1.4 6-2.7 7.3" />
</svg>
);
}
export default function PublicLanding() {
const [reduceMotion, setReduceMotion] = createSignal(false);
const [scrollY, setScrollY] = createSignal(0);
const [heroTilt, setHeroTilt] = createSignal({ x: 0, y: 0 });
const [pathPage, setPathPage] = createSignal(0);
const [cardsPerPage, setCardsPerPage] = createSignal(3);
const [pathPaused, setPathPaused] = createSignal(false);
const [pathTouchStartX, setPathTouchStartX] = createSignal<number | null>(null);
const [benefitIdx, setBenefitIdx] = createSignal(0);
const [flowIndex, setFlowIndex] = createSignal(0);
const [flowStepIndex, setFlowStepIndex] = createSignal(0);
const [openFaq, setOpenFaq] = createSignal(0);
const [showBackToTop, setShowBackToTop] = createSignal(false);
onMount(() => {
const media = window.matchMedia('(prefers-reduced-motion: reduce)');
const syncMotion = () => setReduceMotion(media.matches);
syncMotion();
let ticking = false;
const onScroll = () => {
setShowBackToTop(window.scrollY > 500);
if (media.matches) return;
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
setScrollY(window.scrollY || 0);
ticking = false;
});
};
const syncCardsPerPage = () => {
const w = window.innerWidth;
if (w < 640) {
setCardsPerPage(1);
return;
}
if (w < 1024) {
setCardsPerPage(2);
return;
}
setCardsPerPage(3);
};
onScroll();
syncCardsPerPage();
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', syncCardsPerPage);
media.addEventListener('change', syncMotion);
const benefitTimer = window.setInterval(() => setBenefitIdx((x) => (x + 1) % benefits.length), 4200);
const flowTimer = window.setInterval(() => {
const active = flows[flowIndex()];
setFlowStepIndex((prev) => {
const next = prev + 1;
if (next < active.steps.length) return next;
setFlowIndex((f) => (f + 1) % flows.length);
return 0;
});
}, 3000);
onCleanup(() => {
window.removeEventListener('scroll', onScroll);
window.removeEventListener('resize', syncCardsPerPage);
media.removeEventListener('change', syncMotion);
window.clearInterval(benefitTimer);
window.clearInterval(flowTimer);
});
});
const pagedPaths = createMemo(() => {
const cards = pathCards;
const per = cardsPerPage();
const pages: PathCard[][] = [];
for (let i = 0; i < cards.length; i += per) pages.push(cards.slice(i, i + per));
return pages;
});
const activePathPage = createMemo(() => {
const pages = pagedPaths();
if (pages.length === 0) return 0;
return pathPage() % pages.length;
});
createEffect(() => {
const pagesLen = pagedPaths().length;
if (reduceMotion() || pathPaused() || pagesLen <= 1) return;
const timer = window.setInterval(() => {
setPathPage((prev) => {
const next = prev + 1;
return next >= pagesLen ? 0 : next;
});
}, 4200);
onCleanup(() => window.clearInterval(timer));
});
return (
<main class="lp-main">
<div class="lp-bg" aria-hidden="true">
<div class="lp-dark-base" />
<div class="lp-mesh" style={{ transform: `translate3d(0, ${reduceMotion() ? 0 : Math.min(36, scrollY() * 0.1)}px, 0)` }} />
<div class="lp-ribbon" style={{ transform: `translate3d(0, ${reduceMotion() ? 0 : Math.min(58, scrollY() * 0.18)}px, 0)` }} />
<div class="lp-chips" style={{ transform: `translate3d(0, ${reduceMotion() ? 0 : Math.min(80, scrollY() * 0.24)}px, 0)` }}>
<For each={chipNodes}>
{(chip) => (
<span class={`lp-chip ${chip.cls}`} style={{ left: chip.left, top: chip.top, width: `${chip.size}px`, height: `${chip.size}px` }}>
<ChipIcon kind={chip.kind} />
</span>
)}
</For>
</div>
<div class="lp-noise" />
</div>
<div class="lp-content">
<PublicHeader />
<section
class="public-hero scene-dark lp-section-hero"
onMouseMove={(event) => {
if (reduceMotion()) return;
const rect = event.currentTarget.getBoundingClientRect();
const px = (event.clientX - rect.left) / rect.width - 0.5;
const py = (event.clientY - rect.top) / rect.height - 0.5;
setHeroTilt({ x: px * 14, y: py * 12 });
}}
onMouseLeave={() => setHeroTilt({ x: 0, y: 0 })}
>
<div class="container lp-hero-grid">
<div>
<h1 class="lp-hero-title">Hire verified professionals. Post jobs. Get approvals in 24-48 hours.</h1>
<p class="lp-hero-copy">
Nxtgauge connects customers, companies, job seekers, and professionals through a trusted approval workflow.
</p>
<div class="hero-actions">
<A class="lp-primary-btn" href="/auth/register?intent=customer&redirect=/users/onboarding/customer">Hire a Professional</A>
<A class="lp-ghost-btn lp-ghost-btn-dark" href="/auth/register?intent=job_seeker&redirect=/users/onboarding/job-seeker">Apply for Jobs</A>
</div>
</div>
<div
class="lp-hero-graph"
style={{
transform: reduceMotion() ? 'none' : `translate3d(${heroTilt().x}px, ${heroTilt().y}px, 0)`,
}}
>
<OpportunityGraph reduceMotion={reduceMotion()} />
</div>
</div>
</section>
<section id="choose-path" class="public-section scene-dark lp-section">
<div class="container panel choose-path-panel">
<div class="section-head">
<div>
<h2>Choose Your Path</h2>
<p class="sub">One account, multiple journeys. Pick your goal and get started.</p>
</div>
<div class="lp-path-controls desktop-only">
<button
class="lp-path-arrow"
onClick={() => setPathPage((x) => Math.max(0, x - 1))}
aria-label="Previous cards"
disabled={activePathPage() === 0}
>
</button>
<button
class="lp-path-arrow"
onClick={() => setPathPage((x) => Math.min(Math.max(0, pagedPaths().length - 1), x + 1))}
aria-label="Next cards"
disabled={activePathPage() >= Math.max(0, pagedPaths().length - 1)}
>
</button>
</div>
</div>
<div class="path-carousel-shell">
<div
class={`path-carousel-track ${pathPaused() ? 'path-carousel-track-hover' : ''}`}
style={{ transform: `translate3d(-${activePathPage() * 100}%, 0, 0)` }}
onMouseEnter={() => setPathPaused(true)}
onMouseLeave={() => setPathPaused(false)}
onTouchStart={(event) => {
setPathPaused(true);
setPathTouchStartX(event.changedTouches[0]?.clientX ?? null);
}}
onTouchEnd={(event) => {
setPathPaused(false);
const startX = pathTouchStartX();
const endX = event.changedTouches[0]?.clientX ?? null;
if (startX == null || endX == null) return;
const deltaX = endX - startX;
if (Math.abs(deltaX) < 40) return;
if (deltaX < 0) {
setPathPage((x) => Math.min(Math.max(0, pagedPaths().length - 1), x + 1));
return;
}
setPathPage((x) => Math.max(0, x - 1));
}}
>
<For each={pagedPaths()}>
{(group) => (
<div class="path-page">
<div class={`path-grid path-grid-${cardsPerPage()}`}>
<For each={group}>
{(card) => (
<article class="path-card path-card-hero">
<div class="path-media">
<img src={card.image} alt={card.title} loading="lazy" />
<div class="path-media-overlay" />
</div>
<div class="path-body">
<div class="path-head-row">
<span class="path-icon"><PathRoleIcon kind={card.icon} /></span>
<span class="path-chip"><CheckBadgeIcon />{card.chip}</span>
</div>
<h3>{card.title}</h3>
<p>{card.description}</p>
<A class="path-secondary-btn" href={card.href}>{card.button}</A>
</div>
</article>
)}
</For>
</div>
</div>
)}
</For>
</div>
</div>
<div class="lp-path-dots">
<For each={pagedPaths()}>
{(_, idx) => (
<button
class={`lp-path-dot ${idx() === activePathPage() ? 'active' : ''}`}
onClick={() => setPathPage(idx())}
aria-label={`Go to page ${idx() + 1}`}
/>
)}
</For>
</div>
</div>
</section>
<section id="why-nxtgauge" class="public-section scene-dark lp-section">
<div class="container panel panel-dark">
<div class="center">
<p class="eyebrow">Why Nxtgauge</p>
<h2>Trust, approvals, and better matching in one flow.</h2>
</div>
<div class="whySliderWrap">
<article class="whyHeroCard whyHeroCardPulse">
<div class="whyCardGlow" />
<div class="whyHaloA" />
<div class="whyHaloB" />
<div class="whyCardContent whyContentSwap">
<p class="whyMetaKicker">Capability {benefitIdx() + 1} of {benefits.length}</p>
<span class="whyIconShell"><WhyBenefitIcon kind={benefits[benefitIdx()].icon} /></span>
<h3 style={{ margin: '12px 0 0', color: '#fff', 'font-size': '30px' }}>{benefits[benefitIdx()].title}</h3>
<p style={{ margin: '8px 0 0', color: 'rgba(255,255,255,0.9)', 'font-size': '17px' }}>{benefits[benefitIdx()].body}</p>
</div>
<span class="whyAutoTrack" />
</article>
<div class="whyOrbitalNav">
<For each={benefits}>
{(item, idx) => (
<button
class={`whyOrbitalBtn ${idx() === benefitIdx() ? 'whyOrbitalBtnActive' : ''}`}
onClick={() => setBenefitIdx(idx())}
aria-label={`Show ${item.title}`}
title={item.title}
>
<WhyBenefitIcon kind={item.icon} />
</button>
)}
</For>
</div>
<div class="whyControls">
<button class="whyControlBtn" onClick={() => setBenefitIdx((benefitIdx() - 1 + benefits.length) % benefits.length)} aria-label="Previous slide"></button>
<button class="whyControlBtn" onClick={() => setBenefitIdx((benefitIdx() + 1) % benefits.length)} aria-label="Next slide"></button>
</div>
</div>
</div>
</section>
<section id="how-it-works" class="public-section scene-dark lp-section landing-hiw-section">
<div class="container panel panel-light">
<div class="center">
<p class="eyebrow">How it works</p>
<h2>Clear journey, zero confusion</h2>
</div>
<article class="hiwCodeCard">
<div class="hiwCodeMedia">
<div class="hiwCodeBigRect">
<img class="hiwCodePhoto" src={flows[flowIndex()].image} alt={`${flows[flowIndex()].label} role`} />
</div>
<div class="hiwCodeMediaCopy">
<p class="hiwCodeKicker">{flows[flowIndex()].label}</p>
<h3 class="hiwCodeTitle">{flows[flowIndex()].title}</h3>
<p class="hiwCodeDesc">{flows[flowIndex()].description}</p>
</div>
</div>
<div class="hiwCodeBody">
<p class="hiwCodeStepsHeading">Step Flow</p>
<div class="hiwCodeSteps">
<For each={flows[flowIndex()].steps}>
{(step, idx) => (
<div class={`hiwCodeStep ${idx() === flowStepIndex() ? 'hiwCodeStepActive' : ''}`}>
<span class="hiwCodeStepNum">{idx() + 1}</span>
<div>
<h4 class="hiwCodeStepTitle">{step.title}</h4>
<p class="hiwCodeStepDesc">{step.description}</p>
</div>
</div>
)}
</For>
</div>
<div class="hiwCodeFooter">
<div class="hiwCodeArrows">
<button class="hiwCodeArrow" onClick={() => { setFlowIndex((flowIndex() - 1 + flows.length) % flows.length); setFlowStepIndex(0); }} aria-label="Previous role"></button>
<button class="hiwCodeArrow" onClick={() => { setFlowIndex((flowIndex() + 1) % flows.length); setFlowStepIndex(0); }} aria-label="Next role"></button>
</div>
<div class="hiwCodeDots">
<For each={flows}>
{(_, idx) => <span class={`hiwCodeDot ${idx() === flowIndex() ? 'hiwCodeDotActive' : ''}`} />}
</For>
</div>
</div>
</div>
</article>
</div>
</section>
<section id="faqs" class="public-section scene-dark lp-section landing-faq-section">
<div class="container panel panel-dark faq-wrap">
<h2 class="center">Frequently asked questions</h2>
<p class="center sub">Quick answers before you create your account.</p>
<div class="faq-list">
<For each={faqs}>
{(item, idx) => (
<article class={`faq-item ${openFaq() === idx() ? 'open' : ''}`}>
<button class="faq-q" onClick={() => setOpenFaq(openFaq() === idx() ? -1 : idx())}>
<span>{item.q}</span>
<span class={`faq-q-icon ${openFaq() === idx() ? 'open' : ''}`}></span>
</button>
<Show when={openFaq() === idx()}>
<p class="faq-a">{item.a}</p>
</Show>
</article>
)}
</For>
</div>
</div>
</section>
<section class="public-section scene-dark lp-section">
<div class="container panel panel-dark cta-panel">
<div class="cta-glow" />
<div class="cta-copy">
<p class="eyebrow">Quick Actions</p>
<h2>Ready to get started?</h2>
<p class="sub">Pick your next action and continue with the correct role flow.</p>
</div>
<div class="hero-actions cta-actions">
<A class="lp-primary-btn pulse" href="/auth/register?intent=customer&redirect=/users/onboarding/customer">Hire a Professional</A>
<A class="lp-ghost-btn lp-ghost-btn-dark" href="/auth/register?intent=job_seeker&redirect=/users/onboarding/job-seeker">Apply for Jobs</A>
<A class="lp-ghost-btn lp-ghost-btn-dark" href="/auth/register?intent=company&redirect=/users/onboarding/company">Post a Job</A>
</div>
</div>
</section>
<PublicFooter />
<Show when={showBackToTop()}>
<button class="back-top" onClick={() => window.scrollTo({ top: 0, behavior: reduceMotion() ? 'auto' : 'smooth' })}>
</button>
</Show>
</div>
</main>
);
}