Skip to content

Commit 18f71b7

Browse files
angusbezzinaclaude
andcommitted
design(web): the hero display sets one sentence per line
The copy track takes 55 %, the display's measure widens to 22ch, and each sentence is a no-wrap span from the span breakpoint up, so the only break is between them. text-wrap: balance could not do it: the two candidate splits are equally balanced and the browser kept the wrong one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d48e438 commit 18f71b7

2 files changed

Lines changed: 31 additions & 4 deletions

File tree

web/src/sections/Hero.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Fragment } from "react";
12
import { ArrowDown, ArrowUpRight } from "lucide-react";
23
import { Icon } from "../components/Icon";
34
import { hero } from "../copy";
@@ -19,7 +20,17 @@ export function Hero() {
1920
<div className="u-shell hero__grid">
2021
<div className="hero__copy">
2122
<h1 className="hero__display reveal" id="hero-display" style={{ "--i": 0 } as React.CSSProperties}>
22-
{hero.display}
23+
{/* One sentence per line from the span breakpoint up. Each sentence
24+
* is a no-wrap span, so the only place the line can break is
25+
* between them; the text content is the one string, unchanged. */}
26+
{hero.display.split(/(?<=\.) /).map((sentence, i) => (
27+
<Fragment key={sentence}>
28+
{/* The space sits BETWEEN the spans: inside a no-wrap span it
29+
* would be a space the line cannot break at. */}
30+
{i > 0 ? " " : null}
31+
<span className="hero__sentence">{sentence}</span>
32+
</Fragment>
33+
))}
2334
</h1>
2435
<p className="hero__standfirst reveal" style={{ "--i": 1 } as React.CSSProperties}>
2536
{hero.standfirst}

web/src/styles/base.css

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -635,10 +635,24 @@ pre {
635635
.hero__display {
636636
font-size: var(--text-display-s);
637637
line-height: var(--lh-display);
638-
max-width: 18ch;
638+
/* 22ch, not 18: `ch` is the width of Fraunces's zero, and its bold
639+
* lowercase runs wider than that. 18ch broke "Point at the view." before
640+
* the full stop; 22ch holds the sentence on one line. */
641+
max-width: 22ch;
639642
margin-block-end: var(--space-lg);
640643
}
641644

645+
/* Two sentences, two lines. `text-wrap: balance` could not be relied on:
646+
* "…view. Hand / the agent…" and "…view. / Hand the agent…" are equally
647+
* balanced, and the browser kept the first. So the sentences are spans that
648+
* do not wrap inside themselves. Only from the span breakpoint: at 320 px a
649+
* 23-character no-wrap line at the display size would run off the page. */
650+
@media (min-width: 60rem) {
651+
.hero__sentence {
652+
white-space: nowrap;
653+
}
654+
}
655+
642656
.hero__standfirst {
643657
font-size: var(--text-md);
644658
color: var(--color-ink-2);
@@ -666,8 +680,10 @@ pre {
666680

667681
@media (min-width: 60rem) {
668682
.hero__grid {
669-
/* An even split: the copy lost a display step, the loop gains the width. */
670-
grid-template-columns: repeat(2, minmax(0, 1fr));
683+
/* A fraction past even, towards the copy: the display's first sentence
684+
* wants one line, and 55 % is where it gets it without the loop shrinking
685+
* back to where it was. */
686+
grid-template-columns: minmax(0, 11fr) minmax(0, 9fr);
671687
gap: var(--space-2xl);
672688
}
673689
}

0 commit comments

Comments
 (0)