596 lines
26 KiB
TypeScript
596 lines
26 KiB
TypeScript
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 didn’t 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 isn’t finding options.
|
||
</h3>
|
||
<p
|
||
class="about-problem-body"
|
||
style={{
|
||
opacity: chapterOneBodyIn(),
|
||
filter: `blur(${(1 - chapterOneBodyIn()) * 3}px)`,
|
||
}}
|
||
>
|
||
It’s knowing which one deserves your time.
|
||
<br />
|
||
Scrolling. Comparing. Second-guessing. Starting over.
|
||
<br />
|
||
The real cost isn’t money.
|
||
<br />
|
||
It’s 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 didn’t 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>
|
||
);
|
||
}
|