feat: a red thread through the homepage, in words and as a line
The rail and the "next, you are wondering" links are gone (owner: flat, and the question stood twice in a row). The page now reads from top to bottom: Every chapter opens with one sentence, and the sentences follow each other as the way of a project: you need someone who builds it, first we find out what you need, sometimes it is less work by hand, sometimes a meeting, then it always runs the same way, in the end something runs, a few questions remain, and it starts with an email. One line runs down the whole page and draws itself as the visitor scrolls. Under each sentence it crosses the page, stands a figure on the line (diamond, hexagon, screen, circle, square, triangle) and continues down the other side; a glowing dot travels at its head and comes to rest in front of the last sentence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,17 +4,16 @@ 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";
|
||||
import { ChapterIntro } from "@/src/components/story/ChapterIntro";
|
||||
import type { Figure } from "@/src/components/story/threadPath";
|
||||
|
||||
interface BlockProps {
|
||||
/** Two-digit section index, shown with its binary form. */
|
||||
index: string;
|
||||
label: string;
|
||||
title: React.ReactNode;
|
||||
/** 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 };
|
||||
/** Makes the section a chapter of the story: its sentence and the figure the thread forms. */
|
||||
chapter?: { line: string; figure: Figure; flip: boolean };
|
||||
id?: string;
|
||||
/** `dark` turns the section into a dark stage. */
|
||||
tone?: "light" | "dark";
|
||||
@@ -30,7 +29,6 @@ export const Block: React.FC<BlockProps> = ({
|
||||
label,
|
||||
title,
|
||||
chapter,
|
||||
next,
|
||||
id,
|
||||
tone = "light",
|
||||
children,
|
||||
@@ -38,8 +36,7 @@ export const Block: React.FC<BlockProps> = ({
|
||||
const dark = tone === "dark";
|
||||
return (
|
||||
<section
|
||||
id={chapter?.id ?? id}
|
||||
data-chapter={chapter?.id}
|
||||
id={id}
|
||||
className={`relative scroll-mt-24 border-t ${
|
||||
dark
|
||||
? "overflow-clip border-slate-800 bg-slate-900"
|
||||
@@ -60,7 +57,7 @@ export const Block: React.FC<BlockProps> = ({
|
||||
<div className="relative mx-auto w-full max-w-[1400px] px-5 py-14 md:px-12 md:py-36">
|
||||
{chapter && (
|
||||
<div className="-mt-14 md:-mt-36">
|
||||
<ChapterThread dark={dark} />
|
||||
<ChapterIntro {...chapter} dark={dark} />
|
||||
</div>
|
||||
)}
|
||||
<Reveal>
|
||||
@@ -92,14 +89,6 @@ 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>
|
||||
);
|
||||
|
||||
@@ -8,12 +8,13 @@ import { BitHeadline } from "./BitHeadline";
|
||||
import { ContactComposer } from "./ContactComposer";
|
||||
|
||||
/** ClosingCta: the contact section. One address and a message starter, no form to submit. */
|
||||
export const ClosingCta: React.FC<{ contact: Content["contact"] }> = ({
|
||||
contact,
|
||||
}) => (
|
||||
export const ClosingCta: React.FC<{
|
||||
contact: Content["contact"];
|
||||
/** The last sentence of the story; the story thread ends in front of it. */
|
||||
story?: string;
|
||||
}> = ({ contact, story }) => (
|
||||
<section
|
||||
id="contact"
|
||||
data-chapter="contact"
|
||||
data-glow
|
||||
className="relative overflow-clip border-t border-slate-200 bg-slate-900 text-white"
|
||||
>
|
||||
@@ -29,6 +30,16 @@ export const ClosingCta: React.FC<{ contact: Content["contact"] }> = ({
|
||||
<BitSeam />
|
||||
<div className="relative mx-auto grid w-full max-w-[1400px] grid-cols-1 gap-12 px-5 py-16 md:px-12 md:py-40 lg:grid-cols-12 lg:items-center lg:gap-12">
|
||||
<Reveal className="lg:col-span-6">
|
||||
{story && (
|
||||
<p className="relative m-0 mb-10 max-w-xl font-sans text-2xl font-bold leading-[1.1] tracking-tight text-white md:mb-14 md:text-5xl">
|
||||
<span
|
||||
data-thread-end=""
|
||||
className="absolute -left-2.5 top-[0.55em] md:-left-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{story}
|
||||
</p>
|
||||
)}
|
||||
<BitHeadline
|
||||
as="h2"
|
||||
invert
|
||||
|
||||
@@ -18,11 +18,13 @@ export const PortraitSection: React.FC<{
|
||||
label: string;
|
||||
title: React.ReactNode;
|
||||
portraitAlt: string;
|
||||
/** The sentence of the story this chapter opens with. */
|
||||
story: string;
|
||||
children: React.ReactNode;
|
||||
}> = ({ id, index, label, title, portraitAlt, children }) => (
|
||||
}> = ({ id, index, label, title, portraitAlt, story, children }) => (
|
||||
<section
|
||||
id={id}
|
||||
data-chapter={id}
|
||||
data-thread-start=""
|
||||
className="relative scroll-mt-24 overflow-hidden bg-slate-900 text-white"
|
||||
>
|
||||
{/* Portrait: right side on wide screens, top on small ones. */}
|
||||
@@ -59,6 +61,9 @@ export const PortraitSection: React.FC<{
|
||||
viewport={{ once: true, margin: "-10%" }}
|
||||
transition={{ duration: 0.8, delay: 0.2, ease: [0.16, 1, 0.3, 1] }}
|
||||
>
|
||||
<p className="m-0 mb-10 max-w-2xl font-sans text-2xl font-bold leading-[1.1] tracking-tight text-white md:mb-16 md:text-5xl">
|
||||
{story}
|
||||
</p>
|
||||
<div className="mb-8 flex items-center gap-4 md:mb-12">
|
||||
<span className="font-mono text-[11px] font-bold tracking-[0.2em] text-white">
|
||||
{index}
|
||||
|
||||
Reference in New Issue
Block a user