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
+27 -10
View File
@@ -1,7 +1,6 @@
import type { Metadata } from "next";
import Link from "next/link";
import { BitAura } from "@/src/components/bits/BitAura";
import { OrderFilm } from "@/src/components/film/OrderFilm";
import { Block } from "@/src/components/home/Block";
import { ClosingCta } from "@/src/components/home/ClosingCta";
import { Faq } from "@/src/components/home/Faq";
@@ -19,9 +18,11 @@ import {
} from "@/src/components/illustrations/WebPresentation";
import { WorkShowcase } from "@/src/components/illustrations/WorkShowcase";
import { Reveal } from "@/src/components/Reveal";
import { NextChapter } from "@/src/components/story/Chapter";
import { StoryGuide } from "@/src/components/story/StoryGuide";
import { SwipeRow } from "@/src/components/SwipeRow";
import { contentFor } from "@/src/content";
import type { SplitTitle } from "@/src/content/types";
import type { ChapterId, SplitTitle } from "@/src/content/types";
import { DEFAULT_LOCALE, parseLocale } from "@/src/domain/locale";
import { languageAlternates, pathFor } from "@/src/domain/routes";
@@ -69,11 +70,28 @@ export default async function LandingPage({ params }: Props) {
faq,
} = content;
/** What makes a section a chapter, and the hand-over to the one after it. */
const chapter = (id: ChapterId) => {
const at = story.chapters.findIndex((item) => item.id === id);
const own = story.chapters[at];
const after = story.chapters[at + 1];
return {
chapter: own && { id: own.id },
next: after && {
id: after.id,
label: story.next,
question: after.question,
},
};
};
const afterHello = chapter("hello").next;
return (
<div className="relative flex flex-col bg-white">
<HomeJsonLd locale={locale} />
<HeroStatement hero={content.hero} />
<StickyBook />
<StoryGuide />
<PortraitSection
id="hello"
@@ -101,13 +119,14 @@ export default async function LandingPage({ params }: Props) {
{hello.careerLabel}
</p>
<CareerLine stations={hello.career} />
{afterHello && <NextChapter {...afterHello} dark />}
</div>
</section>
<Block
index="02"
label={services.label}
bridge={story.services}
{...chapter("services")}
title={<Title title={services.title} />}
>
<Reveal>
@@ -115,12 +134,10 @@ export default async function LandingPage({ params }: Props) {
</Reveal>
</Block>
<OrderFilm />
<Block
index="03"
label={automation.label}
bridge={story.automation}
{...chapter("automation")}
title={<Title title={automation.title} />}
>
<Reveal>
@@ -153,7 +170,7 @@ export default async function LandingPage({ params }: Props) {
<Block
index="04"
label={presentations.label}
bridge={story.presentations}
{...chapter("presentations")}
tone="dark"
title={<Title title={presentations.title} muted="text-slate-500" />}
>
@@ -226,7 +243,7 @@ export default async function LandingPage({ params }: Props) {
<Block
index="05"
label={working.label}
bridge={story.working}
{...chapter("working")}
title={<Title title={working.title} />}
>
<Reveal>
@@ -277,7 +294,7 @@ export default async function LandingPage({ params }: Props) {
<Block
index="06"
label={work.label}
bridge={story.work}
{...chapter("work")}
tone="dark"
title={<Title title={work.title} muted="text-slate-500" />}
>
@@ -292,7 +309,7 @@ export default async function LandingPage({ params }: Props) {
<Block
index="07"
label={faq.label}
bridge={story.faq}
{...chapter("faq")}
title={<Title title={faq.title} />}
>
<Reveal>
+17
View File
@@ -1339,6 +1339,23 @@ pre:has(code[class*="language-"]) {
}
}
/* The story guide: the dot that travels along the chapter rail, and the header line. */
.story-dot {
box-shadow:
0 0 0 3px rgba(253, 230, 138, 0.35),
0 0 14px 3px rgba(251, 191, 36, 0.8);
transition: top 200ms linear;
}
.story-now {
animation: story-now-in 400ms cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes story-now-in {
from {
opacity: 0;
transform: translateY(6px);
}
}
/*
* Native feel: the page should behave like a well-made app on every input type.
*/