nxtgauge-frontend-solid/src/routes/about.tsx

596 lines
26 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { A } from '@solidjs/router';
import { For, Show, createMemo, createSignal, onCleanup, onMount } from 'solid-js';
import PublicBackground from '~/components/PublicBackground';
import PublicFooter from '~/components/PublicFooter';
import PublicHeader from '~/components/PublicHeader';
const chapters = [
{ id: 'chapter-problem', title: '01 The problem' },
{ id: 'chapter-built', title: '02 What we built' },
{ id: 'chapter-trust', title: '03 How trust works' },
{ id: 'chapter-principles', title: '04 Principles' },
{ id: 'chapter-timeline', title: '05 Timeline' },
] as const;
const trustSequence = [
{ num: '01', title: 'Submission', body: 'A user submits profile, job, or requirement details with required context.' },
{ num: '02', title: 'Human Review', body: 'A reviewer checks quality, relevance, and verification requirements.' },
{ num: '03', title: 'Approval', body: 'Validated submissions are approved and moved to publish-ready state.' },
{ num: '04', title: 'Visible to Marketplace', body: 'Approved entities become discoverable and can receive responses.' },
] as const;
const milestones = [
{ title: 'Research', body: 'Mapped trust breakdowns in hiring and service discovery journeys.' },
{ title: 'MVP', body: 'Built a marketplace core with structured onboarding and review signals.' },
{ title: 'Private pilot', body: 'Tested operational workflows with role-specific submissions.' },
{ title: 'Launch', body: 'Released trust-layered marketplace experience to wider users.' },
{ title: 'Next: expanding categories', body: 'Adding more categories while keeping quality controls strong.' },
] as const;
const chapterFourNarrative = [
'We didnt build another marketplace.',
'We built a filter.',
'A review layer.',
'Clarity replaces noise.',
] as const;
const chapterTwoRows = ['Profile status', 'Requirement status', 'Activity transparency'] as const;
export default function AboutPage() {
const [showBackToTop, setShowBackToTop] = createSignal(false);
const [reduceMotion, setReduceMotion] = createSignal(false);
const [activeChapter, setActiveChapter] = createSignal(0);
const [scrollY, setScrollY] = createSignal(0);
// Initialize visible for SSR - all content renders on server, client hydration just updates visibility for animations
const [heroVisible, setHeroVisible] = createSignal(true);
const [problemVisible, setProblemVisible] = createSignal(true);
const [builtVisible, setBuiltVisible] = createSignal(true);
const [trustVisible, setTrustVisible] = createSignal(true);
const [principlesVisible, setPrinciplesVisible] = createSignal(true);
const [timelineVisible, setTimelineVisible] = createSignal(true);
const [closingVisible, setClosingVisible] = createSignal(true);
const [problemProgress, setProblemProgress] = createSignal(0);
const [trustProgress, setTrustProgress] = createSignal(0);
const [principleProgress, setPrincipleProgress] = createSignal(0);
const [builtTilt, setBuiltTilt] = createSignal({ x: 0, y: 0 });
let heroRef: HTMLElement | undefined;
let chapterProblemRef: HTMLElement | undefined;
let chapterBuiltRef: HTMLElement | undefined;
let chapterTrustRef: HTMLElement | undefined;
let chapterPrinciplesRef: HTMLElement | undefined;
let chapterTimelineRef: HTMLElement | undefined;
let closingRef: HTMLElement | undefined;
onMount(() => {
const media = window.matchMedia('(prefers-reduced-motion: reduce)');
const syncMotion = () => setReduceMotion(media.matches);
syncMotion();
const chapterRefs = () => [chapterProblemRef, chapterBuiltRef, chapterTrustRef, chapterPrinciplesRef, chapterTimelineRef];
const revealMap = new Map<HTMLElement, (visible: boolean) => void>();
if (heroRef) revealMap.set(heroRef, setHeroVisible);
if (chapterProblemRef) revealMap.set(chapterProblemRef, setProblemVisible);
if (chapterBuiltRef) revealMap.set(chapterBuiltRef, setBuiltVisible);
if (chapterTrustRef) revealMap.set(chapterTrustRef, setTrustVisible);
if (chapterPrinciplesRef) revealMap.set(chapterPrinciplesRef, setPrinciplesVisible);
if (chapterTimelineRef) revealMap.set(chapterTimelineRef, setTimelineVisible);
if (closingRef) revealMap.set(closingRef, setClosingVisible);
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const setter = revealMap.get(entry.target as HTMLElement);
if (setter) setter(true);
});
},
{ threshold: 0.12 }
);
revealMap.forEach((_, el) => observer.observe(el));
const onScroll = () => {
setShowBackToTop(window.scrollY > 500);
setScrollY(window.scrollY || 0);
const middle = window.innerHeight * 0.42;
let nextActive = 0;
chapterRefs().forEach((section, index) => {
if (!section) return;
const rect = section.getBoundingClientRect();
if (rect.top <= middle) nextActive = index;
});
setActiveChapter(nextActive);
if (chapterProblemRef && !reduceMotion()) {
const rect = chapterProblemRef.getBoundingClientRect();
const viewport = window.innerHeight;
const start = viewport * 0.9;
const end = viewport * 0.18;
const range = rect.height + (start - end);
const raw = (start - rect.top) / range;
setProblemProgress(Math.max(0, Math.min(1, raw)));
} else if (reduceMotion()) {
setProblemProgress(1);
}
if (chapterTrustRef && !reduceMotion()) {
const rect = chapterTrustRef.getBoundingClientRect();
const viewport = window.innerHeight;
const start = viewport * 0.75;
const end = viewport * 0.18;
const range = rect.height + (start - end);
const raw = (start - rect.top) / range;
setTrustProgress(Math.max(0, Math.min(1, raw)));
} else if (reduceMotion()) {
setTrustProgress(1);
}
if (chapterPrinciplesRef && !reduceMotion()) {
const rect = chapterPrinciplesRef.getBoundingClientRect();
const viewport = window.innerHeight;
const sectionTopAbs = window.scrollY + rect.top;
const start = sectionTopAbs - viewport * 0.9;
const end = sectionTopAbs + rect.height - viewport * 0.52;
const range = Math.max(1, end - start);
const raw = (window.scrollY - start) / range;
setPrincipleProgress(Math.max(0, Math.min(1, raw)));
} else if (reduceMotion()) {
setPrincipleProgress(1);
}
};
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
media.addEventListener('change', syncMotion);
onCleanup(() => {
window.removeEventListener('scroll', onScroll);
media.removeEventListener('change', syncMotion);
observer.disconnect();
});
});
const progress = createMemo(() => {
if (chapters.length <= 1) return 0;
return (activeChapter() / (chapters.length - 1)) * 100;
});
const progressBetween = (value: number, start: number, end: number) => {
const span = Math.max(0.001, end - start);
return Math.max(0, Math.min(1, (value - start) / span));
};
const effectiveProblemProgress = createMemo(() => (reduceMotion() ? 1 : problemProgress()));
const effectiveTrustProgress = createMemo(() => (reduceMotion() ? 1 : trustProgress()));
const effectivePrincipleProgress = createMemo(() => (reduceMotion() ? 1 : principleProgress()));
const chapterOneHeadlineIn = createMemo(() => progressBetween(effectiveProblemProgress(), 0.12, 0.42));
const chapterOneBodyIn = createMemo(() => progressBetween(effectiveProblemProgress(), 0.24, 0.56));
const chapterOneShapeFade = createMemo(() => (reduceMotion() ? 0 : 0.08 * (1 - effectiveProblemProgress())));
const principleStage = createMemo(() => {
const p = effectivePrincipleProgress();
if (p < 0.25) return 0;
if (p < 0.5) return 1;
if (p < 0.75) return 2;
return 3;
});
const stateTwoUnderline = createMemo(() => progressBetween(effectivePrincipleProgress(), 0.26, 0.46));
const stateThreeLine = createMemo(() => progressBetween(effectivePrincipleProgress(), 0.52, 0.74));
const chapterFourMotion = (idx: number) => {
const p = effectivePrincipleProgress();
const center = (idx + 0.5) / chapterFourNarrative.length;
const preWindow = 0.14;
// Once a line has crossed center, keep it bright and glowing.
if (p >= center) {
return {
opacity: 1,
y: 0,
glow: 52,
};
}
// Before center: approach from dim to bright, but no glow yet.
const distanceToCenter = Math.max(0, center - p);
const t = Math.max(0, Math.min(1, 1 - distanceToCenter / preWindow));
return {
opacity: 0.46 + t * 0.54,
y: 0,
glow: 0,
};
};
const chapterFourBackdropGlow = createMemo(() => {
// Keep chapter backdrop glow once the first narrative line reaches center.
const firstCenter = 0.5 / chapterFourNarrative.length;
return effectivePrincipleProgress() >= firstCenter ? 1 : 0;
});
const scrollToChapter = (chapterId: string) => {
const target = document.getElementById(chapterId);
if (!target) return;
const offset = window.innerWidth >= 1280 ? 180 : 150;
const top = target.getBoundingClientRect().top + window.scrollY - offset;
window.scrollTo({ top: Math.max(0, top), behavior: reduceMotion() ? 'auto' : 'smooth' });
};
return (
<main class="lp-main about-page-root">
<PublicBackground scrollY={scrollY()} reduceMotion={reduceMotion()} meshFactor={0.12} ribbonFactor={0.22} meshCap={40} ribbonCap={55} />
<div class="lp-content about-content about-with-rail">
<PublicHeader />
<aside class="about-chapter-rail">
<div class="about-chapter-track">
<span class="about-chapter-progress" style={{ height: `${progress()}%` }} />
</div>
<ul class="about-chapter-list">
<For each={chapters}>
{(chapter, idx) => (
<li class={idx() === activeChapter() ? 'about-chapter-item-active' : 'about-chapter-item'}>
<a
href={`#${chapter.id}`}
onClick={(event) => {
event.preventDefault();
scrollToChapter(chapter.id);
}}
>
{chapter.title}
</a>
</li>
)}
</For>
</ul>
</aside>
<section ref={heroRef} class="about-hero">
<div class="container">
<div class={`about-hero-inner about-reveal-init ${heroVisible() ? 'about-reveal-show' : ''}`}>
<div>
<p class="about-kicker">Brand Story</p>
<h1 class="about-title">Trust-first hiring and services.</h1>
<p class="about-copy">
Nxtgauge is built to reduce noise, improve quality, and help people connect with confidence.
</p>
<div class="hero-actions">
<A class="lp-primary-btn" href="/contact">Contact us</A>
</div>
</div>
<aside class="about-manifesto-card">
<h2>Our manifesto</h2>
<ul>
<li>Verify what matters</li>
<li>Reduce spam and guesswork</li>
<li>Match people faster</li>
</ul>
<span class="about-sheen-sweep" />
</aside>
</div>
</div>
</section>
<section
id="chapter-problem"
ref={chapterProblemRef}
class="about-section-tight about-chapter-problem-section"
>
<div class="container">
<article class={`about-problem-stage about-reveal-init ${problemVisible() ? 'about-reveal-show' : ''}`}>
<span class="about-problem-halo" style={{ opacity: 0.16 + effectiveProblemProgress() * 0.32 }} />
<span
class="about-problem-shape-a"
style={{
opacity: chapterOneShapeFade(),
transform: `translate3d(${-14 * effectiveProblemProgress()}px, ${10 * effectiveProblemProgress()}px, 0)`,
}}
/>
<span
class="about-problem-shape-b"
style={{
opacity: chapterOneShapeFade(),
transform: `translate3d(${16 * effectiveProblemProgress()}px, ${-8 * effectiveProblemProgress()}px, 0)`,
}}
/>
<span
class="about-problem-shape-c"
style={{
opacity: chapterOneShapeFade(),
transform: `translate3d(${12 * effectiveProblemProgress()}px, ${11 * effectiveProblemProgress()}px, 0)`,
}}
/>
<span
class="about-problem-shape-d"
style={{
opacity: chapterOneShapeFade(),
transform: `translate3d(${-12 * effectiveProblemProgress()}px, ${-10 * effectiveProblemProgress()}px, 0)`,
}}
/>
<p class="about-chapter-label about-chapter-label-light">Chapter 01</p>
<h2 class="about-chapter-title about-chapter-title-dark">The Problem</h2>
<h3
class="about-problem-headline"
style={{
opacity: chapterOneHeadlineIn(),
filter: `blur(${(1 - chapterOneHeadlineIn()) * 5}px)`,
}}
>
The hardest part isnt finding options.
</h3>
<p
class="about-problem-body"
style={{
opacity: chapterOneBodyIn(),
filter: `blur(${(1 - chapterOneBodyIn()) * 3}px)`,
}}
>
Its knowing which one deserves your time.
<br />
Scrolling. Comparing. Second-guessing. Starting over.
<br />
The real cost isnt money.
<br />
Its momentum.
</p>
</article>
</div>
</section>
<section
id="chapter-built"
ref={chapterBuiltRef}
class="about-section-tight"
>
<div class="container">
<article class={`about-glass-light about-chapter-two-shell about-reveal-init ${builtVisible() ? 'about-reveal-show' : ''}`}>
<p class="about-chapter-label about-chapter-label-orange">Chapter 02</p>
<h2 class="about-chapter-title about-chapter-title-light">What We Built</h2>
<div class="about-chapter-two-grid">
<div
class="about-chapter-two-text"
style={{
opacity: builtVisible() ? 1 : 0,
transform: builtVisible() ? 'translate3d(0,0,0)' : 'translate3d(0,8px,0)',
}}
>
<h2 class="about-chapter-two-heading">We wanted to reduce hesitation.</h2>
<p class="about-chapter-two-body">
Not by adding more choices.
<br />
But by designing fewer, stronger ones.
<br />
Nxtgauge is built around one idea:
<br />
Confidence should come before commitment.
</p>
</div>
<aside
class="about-chapter-two-panel"
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;
setBuiltTilt({ x: -(py * 2), y: px * 2 });
}}
onMouseLeave={() => setBuiltTilt({ x: 0, y: 0 })}
style={{
opacity: builtVisible() ? 1 : 0,
transform: reduceMotion()
? builtVisible()
? 'translate3d(0,0,0)'
: 'translate3d(0,8px,0)'
: builtVisible()
? `perspective(980px) rotateX(${builtTilt().x}deg) rotateY(${builtTilt().y}deg) scale(1)`
: 'perspective(980px) rotateX(0deg) rotateY(0deg) scale(0.95)',
filter: builtVisible() && !reduceMotion() ? 'blur(0px)' : 'blur(3px)',
transitionDelay: builtVisible() ? '150ms' : '0ms',
}}
>
<span class="about-chapter-two-panel-glow" />
<span class="about-chapter-two-reflection" style={{ animation: builtVisible() && !reduceMotion() ? 'chapterTwoSweep 900ms ease-out 1' : 'none' }} />
<div class="about-chapter-two-panel-inner">
<p class="about-chapter-two-panel-label">HOW IT SHOWS UP</p>
<h3 class="about-chapter-two-panel-title">
Before you decide,
<br />
you see signals.
</h3>
<span class="about-chapter-two-divider" />
<div class="about-chapter-two-rows">
<For each={chapterTwoRows}>
{(row, idx) => (
<div
class="about-chapter-two-row"
style={{
opacity: builtVisible() ? 1 : 0,
transform: builtVisible() ? 'translate3d(0,0,0)' : 'translate3d(0,8px,0)',
'transition-delay': builtVisible() ? `${200 + idx() * 100}ms` : '0ms',
}}
>
<span class="about-chapter-two-row-dot" />
{row}
</div>
)}
</For>
</div>
<p class="about-chapter-two-closing">Clarity before commitment.</p>
</div>
</aside>
</div>
</article>
</div>
</section>
<section
id="chapter-trust"
ref={chapterTrustRef}
class="about-section-tight"
>
<div class="container">
<article class={`about-glass-dark about-trust-shell about-reveal-init ${trustVisible() ? 'about-reveal-show' : ''}`}>
<p class="about-chapter-label about-chapter-label-light">Chapter 03</p>
<h2 class="about-chapter-title about-chapter-title-dark">The Trust Layer</h2>
<p class="about-trust-sub">Most reviews complete within 24-48 hours.</p>
<div class="about-trust-sequence">
<div class="about-trust-sequence-list">
<For each={trustSequence}>
{(step, idx) => (
<article
class="about-trust-sequence-card"
style={{
opacity: effectiveTrustProgress() >= (idx() / trustSequence.length) * 0.85 ? 1 : 0.3,
transform: effectiveTrustProgress() >= (idx() / trustSequence.length) * 0.85 ? 'translate3d(0,0,0)' : 'translate3d(0,10px,0)',
}}
>
<div class="about-trust-sequence-row">
<span class="about-trust-sequence-icon">
<span class="about-trust-sequence-dot" />
</span>
<div>
<p class="about-trust-num">{step.num}</p>
<h3>{step.title}</h3>
<p>{step.body}</p>
</div>
</div>
</article>
)}
</For>
</div>
</div>
</article>
</div>
</section>
<section
id="chapter-principles"
ref={chapterPrinciplesRef}
class="about-section-tight about-principles-section"
>
<div class="container">
<article class={`about-glass-dark about-trust-shell about-principle-narrative-section about-reveal-init ${principlesVisible() ? 'about-reveal-show' : ''}`}>
<p class="about-chapter-label about-chapter-label-light">Chapter 04</p>
<h2 class="about-chapter-title about-chapter-title-dark">Principles</h2>
<div class="about-narrative-stage-root">
<Show
when={!reduceMotion()}
fallback={
<div class="about-narrative-stack">
<p class="about-narrative-headline about-narrative-item-active">We didnt build another marketplace.</p>
<div>
<p class="about-narrative-headline about-narrative-item-active">We built a <span class="about-orange-word">filter</span>.</p>
<span class="about-filter-underline-static" />
</div>
<div>
<p class="about-narrative-headline about-narrative-item-active">
A review layer.
<span class="about-principles-subline-inline">Profiles. Jobs. Requirements.</span>
</p>
<span class="about-review-line-static" />
</div>
<p class="about-narrative-headline about-narrative-item-active">Clarity replaces noise.</p>
</div>
}
>
<div class="about-narrative-viewport">
<span
class="about-narrative-glow"
style={{
opacity: chapterFourBackdropGlow() * 0.78,
}}
/>
<div class="about-narrative-stack">
<For each={chapterFourNarrative}>
{(line, idx) => (
<div
class={principleStage() === idx() ? 'about-narrative-item-active' : 'about-narrative-item-inactive'}
style={{
opacity: chapterFourMotion(idx()).opacity,
transform: `translate3d(0, ${chapterFourMotion(idx()).y}px, 0)`,
'text-shadow': chapterFourMotion(idx()).glow > 0
? `0 0 ${chapterFourMotion(idx()).glow}px rgba(253, 98, 22, 0.34)`
: 'none',
}}
>
<p class="about-narrative-headline">
{idx() === 1 ? (
<>
We built a <span class="about-orange-word">filter</span>.
</>
) : (
line
)}
</p>
<Show when={idx() === 1}>
<span class="about-filter-underline" style={{ transform: `scaleX(${stateTwoUnderline()})` }} />
</Show>
<Show when={idx() === 2}>
<>
<span class="about-principles-subline-inline">Profiles. Jobs. Requirements.</span>
<span class="about-review-line" style={{ transform: `scaleX(${stateThreeLine()})` }} />
</>
</Show>
</div>
)}
</For>
</div>
</div>
</Show>
</div>
</article>
</div>
</section>
<section
id="chapter-timeline"
ref={chapterTimelineRef}
class="about-section-tight about-timeline-section-tight"
>
<div class="container">
<div class={`about-glass-light about-timeline-mask-init ${timelineVisible() ? 'about-timeline-mask-show' : ''}`}>
<p class="about-kicker about-kicker-orange">Chapter 05</p>
<h2 class="about-section-title-light">Timeline</h2>
<div class="about-timeline-wrap">
<span class="about-timeline-spine-glow" />
<For each={milestones}>
{(milestone, index) => (
<article
class={`about-timeline-milestone ${timelineVisible() ? 'about-timeline-milestone-visible' : ''}`}
style={{ 'transition-delay': `${index() * 90}ms` }}
>
<span class="about-timeline-index">{index() + 1}</span>
<h3>{milestone.title}</h3>
<p>{milestone.body}</p>
</article>
)}
</For>
</div>
</div>
</div>
</section>
<section ref={closingRef} class="about-section-mid">
<div class="container">
<div class={`about-glass-dark about-closing-card about-reveal-init ${closingVisible() ? 'about-reveal-show' : ''}`}>
<h2>Have a question or want to partner?</h2>
<div class="hero-actions">
<A class="lp-primary-btn" href="/contact">Contact us</A>
<A class="lp-ghost-btn lp-ghost-btn-dark" href="/">Back to home</A>
</div>
</div>
</div>
</section>
<Show when={showBackToTop()}>
<button class="back-top" onClick={() => window.scrollTo({ top: 0, behavior: reduceMotion() ? 'auto' : 'smooth' })}>
</button>
</Show>
<PublicFooter />
</div>
</main>
);
}