Skip to content

Commit 83ef321

Browse files
committed
Merge remote-tracking branch 'origin/main' into blove/interrupt-durability
2 parents 8bb7539 + 5e2eafd commit 83ef321

8 files changed

Lines changed: 689 additions & 103 deletions

File tree

apps/website/src/app/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ export default function HomePage() {
4040
(live-stage spec §3, §8). Copy lives in STAGE_RAIL (positioning.ts). */}
4141
<Stage proof={STAGE_PROOF} />
4242

43-
{/* The open-source beat: a slim dark strip, not a second CTA. Copy lives
44-
in OPEN_SOURCE_STRIP (positioning.ts). */}
43+
{/* The open-source full stop: a loud dark band with one fork CTA. Copy
44+
lives in OPEN_SOURCE_STRIP (positioning.ts). */}
4545
<OpenSourceStrip />
4646
<TeamsBlock formPolicy={formPolicy} />
4747
<HomeFAQ />

apps/website/src/components/landing/OpenSourceStrip.spec.tsx

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@ vi.mock('../../lib/analytics/client', () => ({
1414
beforeEach(() => trackCtaClickMock.mockClear());
1515

1616
describe('OpenSourceStrip', () => {
17-
it('renders the whole sentence as the section heading, emphasis included', () => {
18-
render(<OpenSourceStrip />);
17+
it('makes the headline alone the section heading', () => {
18+
const { container } = render(<OpenSourceStrip />);
1919
const heading = screen.getByRole('heading', { level: 2 });
20-
expect(heading.textContent).toBe(
21-
`${OPEN_SOURCE_STRIP.lead} ${OPEN_SOURCE_STRIP.emphasis}`,
20+
// Two words, not a sentence: the section's accessible name is the offer.
21+
expect(heading.textContent).toBe(OPEN_SOURCE_STRIP.headline);
22+
expect(heading.querySelector('em')).toBeNull();
23+
// The eyebrow stays readable rather than aria-hidden, matching how
24+
// SectionHeader treats its own eyebrows.
25+
expect(container.querySelector('.open-source-strip-eyebrow')?.textContent).toBe(
26+
OPEN_SOURCE_STRIP.eyebrow,
2227
);
23-
expect(heading.querySelector('em')?.textContent).toBe(OPEN_SOURCE_STRIP.emphasis);
2428
});
2529

2630
it('names the section by that heading', () => {
@@ -30,13 +34,15 @@ describe('OpenSourceStrip', () => {
3034
expect(container.querySelector('#open-source-heading')).toBeTruthy();
3135
});
3236

33-
it('sits on the dark surface at the tight rhythm', () => {
37+
it('sits on the dark surface at the FULL rhythm, not the tight one', () => {
3438
const { container } = render(<OpenSourceStrip />);
3539
const section = container.querySelector('[data-ui="section"]');
3640
expect(section?.getAttribute('data-surface')).toBe('dark');
37-
// The strip's own padding override keys off [data-tight]; dropping the
38-
// prop silently restores the full 48-80px band this replaced.
39-
expect(section?.getAttribute('data-tight')).toBe('true');
41+
// Deliberately absent. This band used to be the page's quiet beat and a
42+
// guard here asserted data-tight="true" to keep it that way; the section
43+
// is now a full stop, and it gets its ~461px from the standard section
44+
// padding rather than from any override of its own.
45+
expect(section?.getAttribute('data-tight')).toBeNull();
4046
expect(section?.classList.contains('open-source-strip')).toBe(true);
4147
});
4248

@@ -56,7 +62,7 @@ describe('OpenSourceStrip', () => {
5662
);
5763
});
5864

59-
it('is the page’s quiet beat: one action, no second CTA', () => {
65+
it('offers one action and no second CTA', () => {
6066
const { container } = render(<OpenSourceStrip />);
6167
expect(container.querySelectorAll('a').length).toBe(1);
6268
});
@@ -71,4 +77,15 @@ describe('OpenSourceStrip', () => {
7177
destination_url: GITHUB_REPO_URL,
7278
});
7379
});
80+
81+
it('marks the runway decorative and puts it outside the container', () => {
82+
const { container } = render(<OpenSourceStrip />);
83+
const runway = container.querySelector('.open-source-strip-runway');
84+
expect(runway?.getAttribute('aria-hidden')).toBe('true');
85+
expect(runway?.textContent).toBe('');
86+
// It spans the section, not the container, so it must not be nested in
87+
// one — inside, the container's gutters would clip the marking short.
88+
expect(runway?.closest('[data-ui="container"]')).toBeNull();
89+
expect(runway?.parentElement?.getAttribute('data-ui')).toBe('section');
90+
});
7491
});

apps/website/src/components/landing/OpenSourceStrip.tsx

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,55 +8,60 @@ import { trackCtaClick } from '../../lib/analytics/client';
88
import { GITHUB_REPO_URL, OPEN_SOURCE_STRIP } from '../../lib/positioning';
99

1010
/**
11-
* The homepage's open-source beat: one dark strip between the stage and the
12-
* teams block.
11+
* The homepage's open-source beat: a dark full-stop band between the stage
12+
* and the teams block.
1313
*
14-
* It replaced a full dark `FinalCTA` (the "prove the Angular UI" closer). The
15-
* point of the section is that there is no catch and no upsell, so it is
16-
* deliberately the quietest band on the page: one sentence at the left edge of
17-
* the page container, the licence and the repo at the right, and no second
18-
* pitch. The four library pages still close on `FinalCTA variant="dark"` —
19-
* that component is untouched.
14+
* It is deliberately loud. An earlier version was the quietest band on the
15+
* page — the reasoning was that the offer is "no catch, no upsell," so it
16+
* should not read as a second pitch. That restraint is now spent on purpose:
17+
* the open-source offer is one of the strongest things the product has to
18+
* say, and it is said here in two words over an aviation code.
19+
*
20+
* The band takes its ~461px from the standard section rhythm; there is no
21+
* padding override, which is why `tight` is absent rather than false.
22+
*
23+
* The four library pages still close on `FinalCTA variant="dark"` — that
24+
* component is untouched.
2025
*/
2126
export function OpenSourceStrip() {
2227
return (
2328
<Section
2429
surface="dark"
25-
tight
2630
ariaLabelledBy="open-source-heading"
2731
className="open-source-strip"
2832
>
2933
<Container>
30-
<div className="open-source-strip-inner">
31-
<h2 id="open-source-heading" className="open-source-strip-line">
32-
{OPEN_SOURCE_STRIP.lead}{' '}
33-
<em>{OPEN_SOURCE_STRIP.emphasis}</em>
34-
</h2>
35-
<div className="open-source-strip-actions">
36-
<span className="open-source-strip-licence">
37-
{OPEN_SOURCE_STRIP.licence}
38-
</span>
39-
<Button
40-
variant="secondary"
41-
size="md"
42-
href={GITHUB_REPO_URL}
43-
target="_blank"
44-
rel="noopener noreferrer"
45-
leadingIcon={<GitHubIcon />}
46-
onClick={() =>
47-
trackCtaClick({
48-
cta_id: 'hero_github',
49-
track: 'developer',
50-
surface: 'home',
51-
destination_url: GITHUB_REPO_URL,
52-
})
53-
}
54-
>
55-
{OPEN_SOURCE_STRIP.cta}
56-
</Button>
57-
</div>
34+
<p className="open-source-strip-eyebrow">{OPEN_SOURCE_STRIP.eyebrow}</p>
35+
<h2 id="open-source-heading" className="open-source-strip-headline">
36+
{OPEN_SOURCE_STRIP.headline}
37+
</h2>
38+
<div className="open-source-strip-actions">
39+
<Button
40+
variant="primary"
41+
size="md"
42+
href={GITHUB_REPO_URL}
43+
target="_blank"
44+
rel="noopener noreferrer"
45+
leadingIcon={<GitHubIcon />}
46+
onClick={() =>
47+
trackCtaClick({
48+
cta_id: 'hero_github',
49+
track: 'developer',
50+
surface: 'home',
51+
destination_url: GITHUB_REPO_URL,
52+
})
53+
}
54+
>
55+
{OPEN_SOURCE_STRIP.cta}
56+
</Button>
57+
<span className="open-source-strip-licence">
58+
{OPEN_SOURCE_STRIP.licence}
59+
</span>
5860
</div>
5961
</Container>
62+
{/* Spans the section rather than the container, so the marking runs
63+
edge to edge like paint on a runway. */}
64+
<div className="open-source-strip-runway" aria-hidden="true" />
6065
</Section>
6166
);
6267
}

apps/website/src/lib/positioning.spec.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,16 @@ describe('positioning: coding-agent prompt', () => {
157157
});
158158

159159
describe('homepage restructure copy (live-stage spec §3)', () => {
160-
it('closes on the open-source offer: one sentence, the licence, the fork CTA', async () => {
160+
it('closes on the open-source offer: an aviation eyebrow, two words, the licence and the CTA', async () => {
161161
const { OPEN_SOURCE_STRIP } = await import('./positioning');
162-
expect(`${OPEN_SOURCE_STRIP.lead} ${OPEN_SOURCE_STRIP.emphasis}`).toBe(
163-
'Yes, this is all free. Don’t like something? Fork us.',
164-
);
165-
// The strip is one line on a wide viewport; long copy would wrap into the
166-
// action group and undo the whole point of the shape.
167-
expect(`${OPEN_SOURCE_STRIP.lead} ${OPEN_SOURCE_STRIP.emphasis}`.length).toBeLessThanOrEqual(60);
162+
expect(OPEN_SOURCE_STRIP.eyebrow).toBe('Squawk 1200');
163+
expect(OPEN_SOURCE_STRIP.headline).toBe('Fork us.');
164+
// A length budget, not a word count: 12 characters is about what fits
165+
// on one line at 116px in Archivo Black inside the 1200px container.
166+
// A wrapped headline stops reading as a full stop.
167+
expect(OPEN_SOURCE_STRIP.headline.length).toBeLessThanOrEqual(12);
168+
// The eyebrow sits on one line at 0.18em tracking beside nothing else.
169+
expect(OPEN_SOURCE_STRIP.eyebrow.length).toBeLessThanOrEqual(14);
168170
expect(OPEN_SOURCE_STRIP.licence).toBe('MIT');
169171
expect(OPEN_SOURCE_STRIP.cta).toBe('Fork on GitHub');
170172
});

apps/website/src/lib/positioning.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,22 @@ export const HERO_TRUST_LINE = `MIT · ${formatAngularRange(WEBSITE_SUPPORTED_AN
6262
*/
6363
export const GITHUB_REPO_URL = 'https://github.com/cacheplane/angular-agent-framework';
6464

65-
// ── The open-source strip (the slim dark band after the stage). One sentence,
66-
// the licence, the repo. Bold in what it says, quiet in how it looks: the
67-
// point is that there is no catch, and the remedy for disagreeing with us is
68-
// a fork — so it must not read as another sales pitch. ──────────────────────
65+
// ── The open-source band (the dark full stop after the stage). An eyebrow,
66+
// two words, the licence and the repo. It used to be the quietest band on the
67+
// page; it is now loud on purpose, because the offer — no catch, and the
68+
// remedy for disagreeing with us is a fork — is one of the strongest things
69+
// the product has to say. ───────────────────────────────────────────────────
6970
export const OPEN_SOURCE_STRIP = {
70-
/** The sentence, up to the part that carries the emphasis. */
71-
lead: 'Yes, this is all free. Don’t like something?',
72-
/** Rendered italic at the end of the same line. */
73-
emphasis: 'Fork us.',
74-
/** The licence tag beside the action. */
71+
/**
72+
* The US transponder code for VFR flight not receiving ATC services —
73+
* flying with nobody controlling you, which is the offer exactly. It is
74+
* texture, not information: the headline carries the whole meaning, so a
75+
* reader who does not fly loses nothing.
76+
*/
77+
eyebrow: 'Squawk 1200',
78+
/** Two words at up to 116px. The band's entire argument. */
79+
headline: 'Fork us.',
80+
/** The bare licence, set in mono after the action. */
7581
licence: 'MIT',
7682
cta: 'Fork on GitHub',
7783
} as const;

apps/website/src/styles/landing.css

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -616,58 +616,77 @@
616616
}
617617
}
618618

619-
/* OpenSourceStrip — components/landing/OpenSourceStrip.tsx
620-
*
621-
* A strip, not a section: the sentence sits at the left edge of the page
622-
* container and the action at the right, so it reads as an aside between the
623-
* stage and the teams block. It deliberately undercuts the tight section
624-
* rhythm (48–80px) — at that height the band still reads as a full closing
625-
* beat, which is the volume this replaced. The [data-tight] selector is two
626-
* attributes, so the override has to carry both to win. */
627-
[data-ui="section"][data-tight].open-source-strip {
628-
padding-top: clamp(28px, 3.2vw, 44px);
629-
padding-bottom: clamp(28px, 3.2vw, 44px);
630-
}
631-
.open-source-strip-inner {
632-
display: flex;
633-
align-items: center;
634-
justify-content: space-between;
635-
gap: 20px 32px;
636-
flex-wrap: wrap;
619+
/* The Fork us band — components/landing/OpenSourceStrip.tsx.
620+
* No padding override: the band takes the standard --spacing-section-y, which
621+
* is where its ~461px comes from. The dark surface already sets position:
622+
* relative, but the runway depends on it, so it is set here too rather than
623+
* inherited from a rule that could change for unrelated reasons. */
624+
.open-source-strip {
625+
position: relative;
637626
}
638-
.open-source-strip-line {
639-
/* Body-scale editorial copy, not display type: it carries an italic <em>,
640-
and Archivo Black has no italic. --font-sans (Archivo) does. */
641-
font-family: var(--font-sans);
642-
font-size: 23px;
643-
line-height: 1.35;
627+
.open-source-strip-eyebrow {
628+
font-family: var(--font-mono);
629+
font-size: 11px;
630+
font-weight: 700;
631+
letter-spacing: 0.18em;
632+
text-transform: uppercase;
633+
/* The Eyebrow primitive pins 1.4; this rule is hand-rolled for the tighter
634+
11px/.18em the band wants, so it has to carry the line-height too rather
635+
than inherit the body's. */
636+
line-height: 1.4;
637+
color: var(--color-signal);
638+
margin: 0 0 22px;
639+
}
640+
.open-source-strip-headline {
641+
/* --font-display is available here now: the italic <em> this line used to
642+
carry is gone, and Archivo Black has no italic. */
643+
font-family: var(--font-display);
644644
font-weight: 400;
645-
letter-spacing: -0.005em;
645+
font-size: clamp(56px, 8.5vw, 116px);
646+
line-height: 0.9;
647+
letter-spacing: -0.04em;
646648
color: var(--color-text-primary);
647649
margin: 0;
648650
}
649-
.open-source-strip-line em {
650-
font-style: italic;
651-
}
652651
.open-source-strip-actions {
653652
display: flex;
654653
align-items: center;
655-
gap: 18px;
656-
flex: none;
654+
gap: 20px;
655+
margin-top: 38px;
657656
}
658657
.open-source-strip-licence {
659-
font-family: var(--font-sans);
660-
font-size: 12px;
661-
letter-spacing: 0.02em;
658+
font-family: var(--font-mono);
659+
font-size: 11px;
660+
letter-spacing: 0.12em;
661+
text-transform: uppercase;
662662
color: var(--color-text-muted);
663-
border: 1px solid var(--color-border);
664-
border-radius: var(--radius-pill, 999px);
665-
padding: 4px 10px;
666663
white-space: nowrap;
667664
}
665+
/* The runway centreline. A fixed 46px dash on a 92px period rather than a
666+
* proportional one, so it reads as the same painted marking at every width. */
667+
.open-source-strip-runway {
668+
/* Decorative and layered over the band's bottom padding, so it must not
669+
swallow a text-selection drag — the dark surface's top seam
670+
(ui.css, [data-surface="dark"]::before) sets this for the same reason. */
671+
pointer-events: none;
672+
position: absolute;
673+
left: 0;
674+
right: 0;
675+
bottom: 30px;
676+
height: 6px;
677+
background: repeating-linear-gradient(
678+
90deg,
679+
rgba(255, 175, 0, 0.5) 0 46px,
680+
transparent 46px 92px
681+
);
682+
}
668683
@media (max-width: 640px) {
669-
.open-source-strip-line {
670-
font-size: 21px;
684+
.open-source-strip-headline {
685+
font-size: clamp(44px, 13vw, 56px);
686+
}
687+
.open-source-strip-actions {
688+
flex-wrap: wrap;
689+
gap: 14px;
671690
}
672691
}
673692

0 commit comments

Comments
 (0)