From a791782ee8dc59598c7ff25937bcebee969beedb Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 22:16:06 +0000 Subject: [PATCH 01/14] Give the site a display face, a shape rule and a focus ring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Typography: Inter and Sora out. Inter is a banned display face and Sora was a flat 500/600/700 ladder — three preloaded families (105 KB) buying nothing distinctive. In: Bricolage Grotesque for display and Instrument Sans for text, both single-axis variable faces, so two files now cover the whole weight ladder and the preloaded font payload drops to 71 KB. Geist Mono stays for code but is no longer preloaded: it only ever appears below or beside the LCP element. Shape: brand.css gains a radius scale where every step has one squared "tail" corner at bottom-left. Agent Relay is a messaging layer, so every interactive element and every surface on the site is shaped like a message. `-in` variants mirror the tail for the cases where direction means something. `.btn` adopts it, along with a 48px min-height so no button on any of the ten pages that compose it is an uncomfortable target. Focus: there was no global :focus-visible rule at all, so most controls fell back to the UA outline on a dark ground. One rule now covers the site — 3px, 2px offset, so the ring lands on the page background it is measured against — plus a skip link for keyboard users. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Ew9DgXvSa3cozTLoLMNaTN --- web/app/globals.css | 52 +++++++++++++++++++++++++++++++++++++++----- web/app/layout.tsx | 22 +++++++++++++++---- web/public/brand.css | 21 ++++++++++++++++++ 3 files changed, 85 insertions(+), 10 deletions(-) diff --git a/web/app/globals.css b/web/app/globals.css index 5411fa8..e49a309 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -32,20 +32,60 @@ a { color: inherit; } +/* ── Focus ── + One rule for the whole site. The 2px offset puts the ring on the page + ground rather than on the control, so it is measured against the + background: >= 12:1 on every surface the site uses. */ +:focus-visible { + outline: 3px solid var(--focus-ring); + outline-offset: 2px; +} + +/* Keyboard-only skip target, revealed on focus. */ +.skip-link { + position: absolute; + top: 10px; + left: 10px; + z-index: 200; + padding: 12px 20px; + min-height: 44px; + display: inline-flex; + align-items: center; + background: var(--fg); + color: var(--bg); + font-family: var(--font-geist-sans), sans-serif; + font-size: 0.9375rem; + font-weight: 600; + text-decoration: none; + border-radius: var(--r-md); + transform: translateY(-200%); + transition: transform 0.18s var(--ease-out-quint); +} + +.skip-link:focus-visible { + transform: none; +} + /* ── Buttons ── Canonical button design, shared across the site. Landing-page CSS modules pull these in with `composes: btn btn-primary from global;` - so the primary button looks identical everywhere. */ + so the primary button looks identical everywhere. + + Shape comes from --r-md: the tail-corner message radius that every + interactive element on the site shares. min-height keeps every button a + comfortable target on touch (>= 44px) without per-page overrides. */ .btn { display: inline-flex; align-items: center; justify-content: center; - gap: 6px; - font-size: 0.92rem; + gap: 8px; + font-family: var(--font-geist-sans), sans-serif; + font-size: 0.9375rem; font-weight: 600; - letter-spacing: 0.01em; - padding: 12px 28px; - border-radius: 100px; + letter-spacing: -0.005em; + padding: 13px 26px; + min-height: 48px; + border-radius: var(--r-md); text-decoration: none; cursor: pointer; transition: diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 0dd8e93..30867ac 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -1,6 +1,6 @@ import type { Metadata } from 'next'; import { PostHogProvider } from '@posthog/next'; -import { Geist_Mono, Inter, Sora } from 'next/font/google'; +import { Bricolage_Grotesque, Geist_Mono, Instrument_Sans } from 'next/font/google'; import type { ReactNode } from 'react'; import { defaultOgImage } from '../lib/og-meta'; @@ -8,19 +8,30 @@ import { absoluteUrl, POSTHOG_HOST, SITE_EMAIL, SITE_NAME, SITE_URL } from '../l import { WebsitePostHogPageView } from './PostHogPageView'; import './globals.css'; -const inter = Inter({ +// Two families carry the whole site: a characterful display grotesque set very +// large and very tight, and a quiet text grotesque for everything else. Both are +// single-axis (wght) variable faces, so two files cover the full weight ladder. +// The variable names stay `--font-geist-sans` / `--font-heading` because ~25 CSS +// modules reference them by those names. +const instrumentSans = Instrument_Sans({ variable: '--font-geist-sans', subsets: ['latin'], + display: 'swap', }); -const sora = Sora({ +const bricolage = Bricolage_Grotesque({ variable: '--font-heading', subsets: ['latin'], + display: 'swap', }); +// Mono only ever appears below or beside the LCP element, so it stays off the +// critical path: two preloaded families instead of three. const geistMono = Geist_Mono({ variable: '--font-geist-mono', subsets: ['latin'], + display: 'swap', + preload: false, }); export const metadata: Metadata = { @@ -147,7 +158,10 @@ export default function RootLayout({ children }: { children: ReactNode }) { title="Agent Relay blog" /> - +