feat: the homepage leads the visitor through as one story

The page was a row of sections to scroll past, and a film in one of them did
not change that. It now follows the questions a visitor has, in the order
they come up: who is this, can he do what I need, what does it look like,
what else, how does it go, has he done it, what is open, how do I start.

Each chapter ends by asking the next question as a large link that leads on,
and the next chapter answers it. A rail at the edge shows every chapter as a
station with a dot travelling along it (on phones: a pill with the current
chapter and one tap to the next), and the header names the current question.

Where the visitor is, is pure logic in src/domain/chapters.ts. The scroll
film section and the bridge sentences are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 10:24:52 +02:00
co-authored by Claude Opus 5.5
parent e6f00cbbb6
commit 90120b60db
19 changed files with 518 additions and 640 deletions
+21 -20
View File
@@ -4,14 +4,17 @@ import { BitSeam } from "@/src/components/bits/BitSeam";
import { BitWipe } from "@/src/components/bits/BitWipe";
import { DecodeText } from "@/src/components/bits/DecodeText";
import { Reveal } from "@/src/components/Reveal";
import { ChapterThread, NextChapter } from "@/src/components/story/Chapter";
interface BlockProps {
/** Two-digit section index, shown with its binary form. */
index: string;
label: string;
title: React.ReactNode;
/** One sentence that leads over from the section before (the thread of the page). */
bridge?: string;
/** Makes the section a chapter of the story, reachable under this id. */
chapter?: { id: string };
/** The chapter this one hands over to at its end. */
next?: { id: string; label: string; question: string };
id?: string;
/** `dark` turns the section into a dark stage. */
tone?: "light" | "dark";
@@ -26,7 +29,8 @@ export const Block: React.FC<BlockProps> = ({
index,
label,
title,
bridge,
chapter,
next,
id,
tone = "light",
children,
@@ -34,7 +38,8 @@ export const Block: React.FC<BlockProps> = ({
const dark = tone === "dark";
return (
<section
id={id}
id={chapter?.id ?? id}
data-chapter={chapter?.id}
className={`relative scroll-mt-24 border-t ${
dark
? "overflow-clip border-slate-800 bg-slate-900"
@@ -53,22 +58,10 @@ export const Block: React.FC<BlockProps> = ({
)}
{dark && <BitSeam />}
<div className="relative mx-auto w-full max-w-[1400px] px-5 py-14 md:px-12 md:py-36">
{bridge && (
<Reveal>
<div className="-mt-14 mb-10 md:-mt-36 md:mb-20">
<span
className={`block h-14 w-px md:h-28 ${dark ? "bg-slate-600" : "bg-slate-300"}`}
aria-hidden="true"
/>
<p
className={`m-0 mt-5 max-w-2xl font-sans text-xl leading-snug md:text-3xl ${
dark ? "text-slate-300" : "text-slate-500"
}`}
>
{bridge}
</p>
</div>
</Reveal>
{chapter && (
<div className="-mt-14 md:-mt-36">
<ChapterThread dark={dark} />
</div>
)}
<Reveal>
<div className="mb-8 flex items-center gap-4 md:mb-12">
@@ -99,6 +92,14 @@ export const Block: React.FC<BlockProps> = ({
</h2>
</BitWipe>
<div className="mt-10 md:mt-24">{children}</div>
{next && (
<NextChapter
label={next.label}
id={next.id}
question={next.question}
dark={dark}
/>
)}
</div>
</section>
);
@@ -13,6 +13,7 @@ export const ClosingCta: React.FC<{ contact: Content["contact"] }> = ({
}) => (
<section
id="contact"
data-chapter="contact"
data-glow
className="relative overflow-clip border-t border-slate-200 bg-slate-900 text-white"
>
@@ -22,6 +22,7 @@ export const PortraitSection: React.FC<{
}> = ({ id, index, label, title, portraitAlt, children }) => (
<section
id={id}
data-chapter={id}
className="relative scroll-mt-24 overflow-hidden bg-slate-900 text-white"
>
{/* Portrait: right side on wide screens, top on small ones. */}