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

458 lines
20 KiB
TypeScript

import { Meta, Title } from '@solidjs/meta';
import { A } from '@solidjs/router';
import { For, Show, createMemo, createSignal, onCleanup, onMount } from 'solid-js';
import PublicFooter from '~/components/PublicFooter';
import PublicHeader from '~/components/PublicHeader';
import {
getRoleLandingBySlug,
roleChecklist,
roleOutcomes,
roleTrustChips,
type RoleLandingAudience,
} from '~/lib/role-landings';
const chipNodes = [
{ left: '8%', top: '20%', size: 34, cls: 'lp-chip-slow', label: '★' },
{ left: '22%', top: '9%', size: 40, cls: 'lp-chip-mid', label: '✓' },
{ left: '88%', top: '16%', size: 36, cls: 'lp-chip-fast', label: '↗' },
{ left: '76%', top: '38%', size: 30, cls: 'lp-chip-mid', label: '✦' },
{ left: '14%', top: '58%', size: 38, cls: 'lp-chip-fast', label: '⚑' },
{ left: '62%', top: '72%', size: 34, cls: 'lp-chip-slow', label: '⎈' },
{ left: '42%', top: '84%', size: 30, cls: 'lp-chip-mid', label: '✓' },
] as const;
type Props = {
slug: string;
pathBase: '/professionals' | '/roles';
audience?: RoleLandingAudience;
};
function splitLine(line: string): { title: string; body: string } {
const [title, ...rest] = line.split(' - ');
return { title, body: rest.join(' - ') };
}
function SectionGlyph(props: { kind: 'trust' | 'visibility' | 'tracking' | 'benefit' | 'work' | 'expectation' | 'why' | 'check' | 'outcome' }) {
const stroke = 'currentColor';
if (props.kind === 'tracking') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 6v6l4 2" fill="none" stroke={stroke} stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<circle cx="12" cy="12" r="9" fill="none" stroke={stroke} stroke-width="2" />
</svg>
);
}
if (props.kind === 'visibility') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6Z" fill="none" stroke={stroke} stroke-width="2" />
<circle cx="12" cy="12" r="3" fill="none" stroke={stroke} stroke-width="2" />
</svg>
);
}
if (props.kind === 'work') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M4 7h16v11H4z" fill="none" stroke={stroke} stroke-width="2" />
<path d="M8 7V5h8v2" fill="none" stroke={stroke} stroke-width="2" />
</svg>
);
}
if (props.kind === 'expectation') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 3 4 7v6c0 4 3 7 8 8 5-1 8-4 8-8V7Z" fill="none" stroke={stroke} stroke-width="2" />
<path d="m9 12 2 2 4-4" fill="none" stroke={stroke} stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
);
}
if (props.kind === 'why') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 2 14.8 8l6.2.7-4.6 4.3 1.2 6.1L12 16l-5.6 3.1 1.2-6.1L3 8.7 9.2 8Z" fill="none" stroke={stroke} stroke-width="2" />
</svg>
);
}
if (props.kind === 'check') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="12" r="9" fill="none" stroke={stroke} stroke-width="2" />
<path d="m8 12 2.5 2.5L16 9" fill="none" stroke={stroke} stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
);
}
if (props.kind === 'outcome') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 17 18 5" fill="none" stroke={stroke} stroke-width="2" stroke-linecap="round" />
<path d="M11 5h7v7" fill="none" stroke={stroke} stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
);
}
if (props.kind === 'benefit') {
return (
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 3 21 12l-9 9-9-9Z" fill="none" stroke={stroke} stroke-width="2" />
</svg>
);
}
return (
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 3 4 7v6c0 4 3 7 8 8 5-1 8-4 8-8V7Z" fill="none" stroke={stroke} stroke-width="2" />
</svg>
);
}
export default function RoleLandingPage(props: Props) {
const [scrollY, setScrollY] = createSignal(0);
const [reduceMotion, setReduceMotion] = createSignal(false);
const [activeHowStep, setActiveHowStep] = createSignal(0);
const [openFaqIdx, setOpenFaqIdx] = createSignal(0);
const content = createMemo(() => {
const item = getRoleLandingBySlug(String(props.slug || ''));
if (!item) return undefined;
if (props.audience && item.audience !== props.audience) return undefined;
return item;
});
const canonical = createMemo(() => `https://test111.nxtgauge.com${props.pathBase}/${encodeURIComponent(String(props.slug || ''))}`);
const pageTitle = createMemo(() => (content() ? `${content()!.shortTitle} | Nxtgauge` : 'Role | Nxtgauge'));
const pageDescription = createMemo(() =>
content() ? `${content()!.heroDescription} Most role reviews complete in 24-48 hours.` : 'Role landing page on Nxtgauge.'
);
onMount(() => {
const media = window.matchMedia('(prefers-reduced-motion: reduce)');
const sync = () => setReduceMotion(media.matches);
sync();
const onScroll = () => setScrollY(window.scrollY || 0);
window.addEventListener('scroll', onScroll, { passive: true });
media.addEventListener('change', sync);
const timer = window.setInterval(() => {
if (media.matches) return;
const count = content()?.howItWorksSteps.length || 0;
if (count <= 1) return;
setActiveHowStep((prev) => (prev + 1) % count);
}, 2600);
onCleanup(() => {
window.removeEventListener('scroll', onScroll);
media.removeEventListener('change', sync);
window.clearInterval(timer);
});
});
return (
<main class="lp-main">
<Title>{pageTitle()}</Title>
<Meta name="description" content={pageDescription()} />
<Meta property="og:title" content={pageTitle()} />
<Meta property="og:description" content={pageDescription()} />
<Meta property="og:type" content="website" />
<Meta property="og:url" content={canonical()} />
<Meta name="twitter:card" content="summary_large_image" />
<Meta name="twitter:title" content={pageTitle()} />
<Meta name="twitter:description" content={pageDescription()} />
<link rel="canonical" href={canonical()} />
<div class="lp-bg" aria-hidden="true">
<div class="lp-dark-base" />
<div class="lp-mesh" style={{ transform: `translate3d(0, ${reduceMotion() ? 0 : Math.min(34, scrollY() * 0.1)}px, 0)` }} />
<div class="lp-ribbon" style={{ transform: `translate3d(0, ${reduceMotion() ? 0 : Math.min(56, scrollY() * 0.16)}px, 0)` }} />
<div class="lp-chips" style={{ transform: `translate3d(0, ${reduceMotion() ? 0 : Math.min(80, scrollY() * 0.22)}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` }}>
<span class="pro-detail-bg-chip-icon">{chip.label}</span>
</span>
)}
</For>
</div>
<div class="lp-noise" />
</div>
<div class="lp-content">
<PublicHeader />
<Show
when={content()}
fallback={
<section class="public-section lp-section">
<div class="container panel choose-path-panel">
<h2>Role landing page not found</h2>
<p class="sub">The requested role page is unavailable.</p>
<A class="lp-primary-btn" href="/professionals">Back to Professionals</A>
</div>
</section>
}
>
{(item) => (
<>
<section class="public-section lp-section-hero">
<div class="container lp-hero-grid">
<div>
<p class="eyebrow">{item().heroEyebrow}</p>
<h1 class="lp-hero-title">{item().heroTitle}</h1>
<p class="lp-hero-copy">{item().heroDescription}</p>
<div class="hero-actions">
<A class="lp-primary-btn" href={item().primaryCta.href}>{item().primaryCta.label}</A>
<A class="lp-ghost-btn lp-ghost-btn-dark" href={item().secondaryCta.href}>{item().secondaryCta.label}</A>
</div>
<div class="pro-trust-chips">
<For each={roleTrustChips}>
{(line) => <span>{line}</span>}
</For>
</div>
</div>
<div class="lp-hero-graph">
<img src={item().heroImage} alt={item().shortTitle} class="role-hero-photo" />
</div>
</div>
</section>
<section class="public-section lp-section">
<div class="container panel choose-path-panel">
<div class="section-head">
<div>
<h2>{item().whatYouGetTitle}</h2>
<p class="sub">{item().whatYouGetSubtitle}</p>
</div>
</div>
<div class="path-grid">
<For each={item().whatYouGetCards}>
{(line, idx) => (
<article class="path-card role-copy-card">
<div class="path-body">
<div class="path-head-row">
<span class="role-chip-icon"><SectionGlyph kind="benefit" /></span>
<span class="path-chip">Benefit {String(idx() + 1).padStart(2, '0')}</span>
</div>
<h3>{splitLine(line).title}</h3>
<p>{splitLine(line).body}</p>
</div>
</article>
)}
</For>
</div>
</div>
</section>
<section class="public-section lp-section role-typical-section">
<div class="container panel choose-path-panel role-typical-shell">
<div class="section-head">
<div>
<h2>Typical work and expectations</h2>
<p class="sub">Show clients exactly what you handle and what they should confirm before first contact.</p>
</div>
</div>
<div class="pro-real-grid">
<article class="pro-real-card role-typical-card">
<div class="role-section-headline">
<span class="role-card-icon role-card-icon-light"><SectionGlyph kind="work" /></span>
<div>
<p class="role-section-kicker">Project Scope</p>
<h3>{item().typicalLeftTitle}</h3>
</div>
</div>
<ul class="role-typical-list">
<For each={item().typicalLeftItems}>
{(line) => (
<li>
<span class="role-typical-bullet"></span>
<span>{line}</span>
</li>
)}
</For>
</ul>
</article>
<article class="pro-real-card role-typical-card">
<div class="role-section-headline">
<span class="role-card-icon role-card-icon-light"><SectionGlyph kind="expectation" /></span>
<div>
<p class="role-section-kicker">Buyer Evaluation</p>
<h3>{item().typicalRightTitle}</h3>
</div>
</div>
<ul class="role-typical-list">
<For each={item().typicalRightItems}>
{(line) => (
<li>
<span class="role-typical-bullet"></span>
<span>{line}</span>
</li>
)}
</For>
</ul>
</article>
</div>
<div class="pro-real-note-card">
<span class="role-card-icon role-card-icon-light"><SectionGlyph kind="check" /></span>
<p>
Profiles that clearly explain who you help, how your process works, and what clients receive tend to get faster replies and better-fit inquiries.
</p>
</div>
</div>
</section>
<section class="public-section lp-section">
<div class="container panel choose-path-panel">
<div class="section-head">
<div>
<h2>{item().howItWorksTitle}</h2>
</div>
</div>
<article class="hiwCodeCard">
<div class="hiwCodeMedia">
<div class="hiwCodeBigRect">
<img class="hiwCodePhoto" src={item().heroImage} alt={item().shortTitle} />
</div>
<div class="hiwCodeMediaCopy">
<p class="hiwCodeKicker">{item().shortTitle}</p>
<h3 class="hiwCodeTitle">{item().howItWorksTitle}</h3>
<p class="hiwCodeDesc">Simple four-step onboarding with trust checkpoints and clearer status visibility.</p>
</div>
</div>
<div class="hiwCodeBody">
<p class="hiwCodeStepsHeading">Step Flow</p>
<div class="hiwCodeSteps">
<For each={item().howItWorksSteps}>
{(step, idx) => (
<div class={`hiwCodeStep ${idx() === activeHowStep() ? '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>
</article>
</div>
</section>
<section class="public-section lp-section">
<div class="container panel panel-dark">
<div class="section-head">
<div>
<h2>{item().whyChooseTitle}</h2>
<p class="sub">Trust-led workflow advantages that improve consistency and first-contact confidence.</p>
</div>
</div>
<div class="benefit-grid">
<For each={item().whyChooseCards}>
{(line, idx) => (
<article class="benefit-card">
<div class="benefit-head">
<span class="role-card-icon role-card-icon-dark"><SectionGlyph kind="why" /></span>
<span class="benefit-index">Advantage {String(idx() + 1).padStart(2, '0')}</span>
</div>
<h3>{splitLine(line).title}</h3>
<span class="benefit-divider" />
<p>{splitLine(line).body}</p>
</article>
)}
</For>
</div>
</div>
</section>
<section class="public-section lp-section">
<div class="container panel choose-path-panel">
<div class="section-head">
<div>
<h2>Getting started checklist</h2>
<p class="sub">Use this launch sequence to improve profile quality and conversion outcomes from day one.</p>
</div>
</div>
<div class="launch-grid">
<article class="launch-panel">
<div class="launch-panel-head">
<span class="role-card-icon role-card-icon-light"><SectionGlyph kind="check" /></span>
<div>
<p class="launch-kicker">Checklist</p>
<h3>Complete these first</h3>
</div>
</div>
<ol class="launch-list">
<For each={roleChecklist}>
{(line, idx) => (
<li>
<span class="launch-step">{String(idx() + 1).padStart(2, '0')}</span>
<p>{line}</p>
</li>
)}
</For>
</ol>
</article>
<article class="launch-panel launch-panel-outcomes">
<div class="launch-panel-head">
<span class="role-card-icon role-card-icon-light"><SectionGlyph kind="outcome" /></span>
<div>
<p class="launch-kicker">Outcomes</p>
<h3>Platform outcomes you can expect</h3>
</div>
</div>
<div class="outcome-grid">
<For each={roleOutcomes}>
{(line) => (
<article class="outcome-tile">
<span class="outcome-dot" />
<p>{line}</p>
</article>
)}
</For>
</div>
</article>
</div>
</div>
</section>
<section class="public-section lp-section landing-faq-section">
<div class="container panel panel-dark faq-wrap">
<h2 class="center">{item().shortTitle} FAQs</h2>
<div class="faq-list">
<For each={item().faqs}>
{(faq, idx) => (
<article class={`faq-item ${openFaqIdx() === idx() ? 'open' : ''}`}>
<button class="faq-q" type="button" onClick={() => setOpenFaqIdx((prev) => (prev === idx() ? -1 : idx()))}>
<span>{faq.q}</span>
<span class={`faq-q-icon ${openFaqIdx() === idx() ? 'open' : ''}`}></span>
</button>
<Show when={openFaqIdx() === idx()}>
<p class="faq-a">{faq.a}</p>
</Show>
</article>
)}
</For>
</div>
</div>
</section>
<section class="public-section lp-section">
<div class="container panel panel-dark cta-panel">
<div class="cta-copy">
<p class="eyebrow">Start now</p>
<h2>{item().finalCtaHeading}</h2>
<p class="sub">{item().finalCtaSupport}</p>
<div class="pro-detail-cta-points">
<span>Single account</span>
<span>Role visibility</span>
<span>Trust approvals</span>
</div>
</div>
<div class="hero-actions cta-actions">
<A class="lp-primary-btn pulse" href={item().finalPrimaryCta.href}>{item().finalPrimaryCta.label}</A>
<A class="lp-ghost-btn lp-ghost-btn-dark" href={item().finalSecondaryCta.href}>{item().finalSecondaryCta.label}</A>
</div>
</div>
</section>
</>
)}
</Show>
<PublicFooter />
</div>
</main>
);
}