458 lines
20 KiB
TypeScript
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>
|
|
);
|
|
}
|