From d4e6081c1630317b68992ed453664524007b09ee Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Thu, 24 Sep 2026 20:05:41 +0300 Subject: [PATCH 1/7] fix(copy): sentence-case body copy, trim hero lead, dedupe stack Co-Authored-By: Claude Opus 5.5 --- app/llms-full.txt/route.test.ts | 12 ++++------ app/llms-full.txt/route.ts | 6 ++--- components/site/Contact.tsx | 32 ++++++++++----------------- components/site/Method.tsx | 8 +------ lib/copy.test.ts | 10 ++++----- lib/copy.ts | 39 ++++++++++++++++----------------- 6 files changed, 43 insertions(+), 64 deletions(-) diff --git a/app/llms-full.txt/route.test.ts b/app/llms-full.txt/route.test.ts index 763ce36..5a8beed 100644 --- a/app/llms-full.txt/route.test.ts +++ b/app/llms-full.txt/route.test.ts @@ -10,7 +10,6 @@ import { COMPARE_ROWS, HERO_FIGURES, HERO_LEAD, - METHOD_FIGURES, METHOD_STEPS, STACK, statusBlock, @@ -141,15 +140,12 @@ describe("homepage mirror", () => { expect(body).toContain(text); }); - // These two lines were hand-written paraphrases of inline JSX until the + // This line was a hand-written paraphrase of inline JSX until the // figures moved into lib/copy.ts. Asserting the serialised form is what // makes the mirror provably the same data the page renders. - it.each([...HERO_FIGURES, ...METHOD_FIGURES])( - "mirrors the $label figure", - (figure) => { - expect(body).toContain(`${figure.label}: ${figure.value}`); - }, - ); + it.each([...HERO_FIGURES])("mirrors the $label figure", (figure) => { + expect(body).toContain(`${figure.label}: ${figure.value}`); + }); it("mirrors the stack chips in order", () => { expect(lines).toContain(`Stack: ${STACK.join(" · ")}`); diff --git a/app/llms-full.txt/route.ts b/app/llms-full.txt/route.ts index 5b6cc3c..35e77d7 100644 --- a/app/llms-full.txt/route.ts +++ b/app/llms-full.txt/route.ts @@ -3,12 +3,12 @@ import { COMPARE_HEADLINE, COMPARE_LEAD, COMPARE_ROWS, + CONTACT_HEADLINE, CONTACT_LEAD, type FigureCopy, HERO_FIGURES, HERO_HEADLINE, HERO_LEAD, - METHOD_FIGURES, METHOD_STEPS, SITE_DESCRIPTION, STACK, @@ -217,8 +217,6 @@ ${compareTable} Stack: ${STACK.join(" · ")} -${figureList(METHOD_FIGURES)} - ${methodSteps} ### FAQ @@ -227,6 +225,8 @@ ${faq} ### Contact +${CONTACT_HEADLINE.join(" ")} + ${CONTACT_LEAD} Email ${EMAIL} · GitHub ${links.github} · X ${links.x} · LinkedIn ${links.linkedin} diff --git a/components/site/Contact.tsx b/components/site/Contact.tsx index 0c5648b..a56f713 100644 --- a/components/site/Contact.tsx +++ b/components/site/Contact.tsx @@ -1,12 +1,10 @@ import { EMAIL, links } from "@/lib/links"; -import { CONTACT_LEAD } from "@/lib/copy"; +import { CONTACT_HEADLINE, CONTACT_LEAD } from "@/lib/copy"; import { highlightBrand } from "@/components/ui/brand"; import { Frame } from "@/components/ui/Frame"; import { SectionHeader } from "@/components/ui/SectionHeader"; import { FleetStatus } from "@/components/site/FleetStatus"; -const wordmarkH = "clamp(4.75rem, 14vw, 11.5rem)"; - // Solid monochrome glyphs leading each contact link. Decorative — every link // carries its own visible text label — so each is aria-hidden. iconClass // sizes them in em (1.05×), so each glyph scales with its link's font-size; @@ -83,24 +81,16 @@ export function Contact() {
-

- Contact -

- - {/* eslint-disable-next-line @next/next/no-img-element */} - decdn + id="contact-h" + className="hug flex flex-col text-h2 leading-[0.92] font-semibold tracking-[-0.04em]" + > + {CONTACT_HEADLINE[0]} + {CONTACT_HEADLINE[1]} +

- x (twitter) + X → diff --git a/components/site/Method.tsx b/components/site/Method.tsx index b833610..6c7702b 100644 --- a/components/site/Method.tsx +++ b/components/site/Method.tsx @@ -1,6 +1,5 @@ import { Fragment } from "react"; -import { METHOD_FIGURES, METHOD_STEPS, STACK } from "@/lib/copy"; -import { Figure } from "@/components/ui/Figure"; +import { METHOD_STEPS, STACK } from "@/lib/copy"; import { Frame } from "@/components/ui/Frame"; import { MethodRow } from "@/components/ui/MethodRow"; import { SectionHeader } from "@/components/ui/SectionHeader"; @@ -41,11 +40,6 @@ export function Method() { ))}

-
- {METHOD_FIGURES.map((figure) => ( -
- ))} -
); diff --git a/lib/copy.test.ts b/lib/copy.test.ts index 88dcef1..4999cee 100644 --- a/lib/copy.test.ts +++ b/lib/copy.test.ts @@ -3,13 +3,13 @@ import { COMPARE_HEADLINE, COMPARE_LEAD, COMPARE_ROWS, + CONTACT_HEADLINE, CONTACT_LEAD, type CompareRow, DEMO_CAPTIONS, HERO_FIGURES, HERO_HEADLINE, HERO_LEAD, - METHOD_FIGURES, METHOD_STEPS, SITE_DESCRIPTION, SITE_TITLE, @@ -55,12 +55,12 @@ const ALL_STRINGS: readonly [string, string][] = [ [`METHOD_STEPS.${s.n}.body`, s.body], ]), ...STACK.map((s, i): [string, string] => [`STACK[${i}]`, s]), - ...METHOD_FIGURES.flatMap((f): [string, string][] => [ - [`METHOD_FIGURES.${f.label}.label`, f.label], - [`METHOD_FIGURES.${f.label}.value`, f.value], - ]), ["DEMO_CAPTIONS.terminal", DEMO_CAPTIONS.terminal], ["DEMO_CAPTIONS.fleet", DEMO_CAPTIONS.fleet], + ...CONTACT_HEADLINE.map((s, i): [string, string] => [ + `CONTACT_HEADLINE[${i}]`, + s, + ]), ["CONTACT_LEAD", CONTACT_LEAD], ]; diff --git a/lib/copy.ts b/lib/copy.ts index 39fe237..b84eb63 100644 --- a/lib/copy.ts +++ b/lib/copy.ts @@ -34,7 +34,7 @@ export const HERO_HEADLINE = [ ] as const; export const HERO_LEAD = - "the first bytes of a 14-gigabyte file posted in berlin reach a client in tokyo in under a second. the client streams from three peers at once, verifies every chunk with blake3, and pays per megabyte in usdc — whether the payload is a linux iso, a dataset, a game patch, a media library, or an ai model. deCDN is demand-shaped, locality-optimised delivery for large files at scale: supply forms around demand, cost collapses as regional traffic concentrates. the code is open. the network is open. the price is posted."; + "The first bytes of a 14-gigabyte file posted in Berlin reach a client in Tokyo in under a second — streamed from three peers at once, every chunk verified with BLAKE3, paid for per megabyte in USDC. The code is open. The network is open. The price is posted."; /** A label/value pair rendered by `components/ui/Figure`. Named `FigureCopy` * rather than `Figure` so a call site can import both the copy and the @@ -50,7 +50,7 @@ export type FigureCopy = { label: string; value: string }; export const HERO_FIGURES = [ { label: "target price", value: "$0.01/GB" }, { label: "p50 latency", value: "50–100 ms" }, - { label: "settlement", value: "per-MB · usdc" }, + { label: "settlement", value: "per-MB · USDC" }, { label: "gas overhead", value: "<1%" }, ] as const satisfies readonly FigureCopy[]; @@ -60,7 +60,7 @@ export const COMPARE_HEADLINE = [ ] as const; export const COMPARE_LEAD = - "the pattern repeats whenever something big ships: mirrors fork, cdns rate-limit, small teams burn tens of thousands hosting bytes they don't own. deCDN inverts every axis — supply forms around demand, not allocated to it."; + "The pattern repeats whenever something big ships: mirrors fork, CDNs rate-limit, small teams burn tens of thousands hosting bytes they don't own. deCDN inverts every axis — supply forms around demand instead of being allocated to it."; export type CompareRow = { /** Row header — the `` text. */ @@ -97,7 +97,7 @@ export const COMPARE_ROWS = [ { axis: "billing", traditional: "monthly minimums, annual contracts", - decdn: "per megabyte, in usdc", + decdn: "per megabyte, in USDC", }, { axis: "operators", @@ -107,11 +107,11 @@ export const COMPARE_ROWS = [ { axis: "integrity", traditional: "trust the origin", - decdn: "blake3, verify every chunk", + decdn: "BLAKE3, verify every chunk", }, { axis: "failure", - traditional: "pop dies, region 503s", + traditional: "PoP dies, region 503s", decdn: "peer drops, stream continues", }, { @@ -136,31 +136,24 @@ export const METHOD_STEPS: readonly MethodStep[] = [ { n: "01", word: "probe", - body: "in a single handshake, the client asks nearby peers who has the file. peers answer with what they've cached, their rate per megabyte, and how fast they can serve — the roundtrip averages under 100 milliseconds. the client ranks the answers by price, latency, and reputation; the best-priced, fastest, most-reputable peer wins, or several win in parallel for a large file.", + body: "In a single handshake, the client asks nearby peers who has the file. Peers answer with what they've cached, their rate per megabyte, and how fast they can serve — the round trip averages under 100 milliseconds. The client ranks the answers by price, latency, and reputation; the best-priced, fastest, most-reputable peer wins, or several win in parallel for a large file.", }, { n: "02", word: "swarm", - body: "bytes flow directly from the chosen node; for files over ten gigabytes the client opens parallel streams to several peers at once and aggregates their throughput — a 1 gbps origin turns into multi-gigabit delivery to the client. every chunk is verified against the blake3 tree hash the instant it lands; tampered bytes trigger immediate disconnect and a fraud proof against the node's stake. trust no node — verify every byte.", + body: "Bytes flow directly from the chosen node; for files over ten gigabytes the client opens parallel streams to several peers at once and aggregates their throughput — a 1 Gbps origin turns into multi-gigabit delivery to the client. Every chunk is verified against the BLAKE3 tree hash the instant it lands; tampered bytes trigger immediate disconnect and a fraud proof against the node's stake. Trust no node — verify every byte.", }, { n: "03", word: "settle", - body: "you pay per megabyte in usdc, automatically, as the bytes arrive — no monthly invoice, no subscription, no whole-file minimum. pay for what you pulled, nothing more.", + body: "You pay per megabyte in USDC, automatically, as the bytes arrive — no monthly invoice, no subscription, no whole-file minimum. Pay for what you pulled, nothing more.", }, ]; /** The stack chips under the method steps, rendered with an aria-hidden `·` - * between each. Order is the render order. */ -export const STACK = ["blake3", "quic", "iroh", "usdc", "evm"] as const; - -/** The stat strip beside the stack chips. */ -export const METHOD_FIGURES = [ - { label: "language", value: "rust" }, - { label: "transport", value: "quic / iroh" }, - { label: "settlement", value: "chain-agnostic" }, - { label: "currency", value: "usdc · token" }, -] as const satisfies readonly FigureCopy[]; + * between each. Order is the render order. This is the section's only stack + * summary — a figure strip beside it used to restate QUIC, iroh and USDC. */ +export const STACK = ["Rust", "BLAKE3", "QUIC", "iroh", "USDC", "EVM"] as const; /** The target price, as the page and both machine surfaces quote it. */ export const TARGET_RATE = "$0.01/GB"; @@ -203,5 +196,11 @@ export const DEMO_CAPTIONS = { export const BLOG_TITLE = "field notes"; export const BLOG_DESCRIPTION = "long-form posts on the deCDN protocol."; +/** The Contact section's display h2, one line per span. */ +export const CONTACT_HEADLINE = [ + "the network is open.", + "so is our inbox.", +] as const; + export const CONTACT_LEAD = - "the network is open. so is our inbox. write us with questions, partnerships, or anything you'd run on a fleet of idle machines. we read everything."; + "Write to us with questions, partnerships, or anything you'd run on a fleet of idle machines. We read everything."; From 823ba305de8506204e8b46d5739d83ac789fdc49 Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Thu, 24 Sep 2026 20:05:44 +0300 Subject: [PATCH 2/7] feat(hero): single primary cta and terminal above the fold Co-Authored-By: Claude Opus 5.5 --- app/globals.css | 47 ++++++++++++++++++++++++++++++++ components/site/Faq.tsx | 8 +++++- components/site/Hero.tsx | 48 ++++++++++++++++++--------------- components/ui/SectionHeader.tsx | 16 ++++++----- 4 files changed, 90 insertions(+), 29 deletions(-) diff --git a/app/globals.css b/app/globals.css index 61eb4be..6609b9f 100644 --- a/app/globals.css +++ b/app/globals.css @@ -253,6 +253,53 @@ a.underline-brutal:hover .arrow { color: var(--whisper); } +/* The hero's single primary action: a solid ink block with the same hard + offset shadow as the tool panels, so it reads as the page's one button + rather than a fourth underlined link. */ +a.cta-primary { + display: inline-flex; + align-items: center; + gap: 0.75rem; + padding: 0.9rem 1.25rem; + background: var(--ink); + color: var(--paper); + font-size: var(--text-cta); + font-weight: 600; + letter-spacing: 0.14em; + text-transform: uppercase; + text-decoration: none; + box-shadow: 5px 5px 0 0 var(--whisper); + transition: + transform 260ms cubic-bezier(0.16, 1, 0.3, 1), + box-shadow 260ms cubic-bezier(0.16, 1, 0.3, 1); +} + +a.cta-primary .arrow { + color: var(--whisper); + transition: transform 260ms cubic-bezier(0.16, 1, 0.3, 1); +} + +a.cta-primary:hover { + transform: translate(-2px, -2px); + box-shadow: 7px 7px 0 0 var(--whisper); +} + +a.cta-primary:hover .arrow { + transform: translateX(6px); +} + +a.cta-primary:focus-visible { + outline: 1px dashed var(--ink); + outline-offset: 9px; +} + +@media (prefers-reduced-motion: reduce) { + a.cta-primary, + a.cta-primary .arrow { + transition: none; + } +} + /* --- hero terminal ----------------------------------------------------- Mocks a single decdn CLI session: probe → stream → verify → settle. All lines share one 4.5s loop; each line fades in at its own delay. diff --git a/components/site/Faq.tsx b/components/site/Faq.tsx index 7cc6f4c..8fd62ef 100644 --- a/components/site/Faq.tsx +++ b/components/site/Faq.tsx @@ -1,4 +1,5 @@ import { FaqList } from "@/components/site/FaqList"; +import { FAQ_ITEMS } from "@/lib/faq"; import { Frame } from "@/components/ui/Frame"; import { SectionHeader } from "@/components/ui/SectionHeader"; @@ -9,7 +10,12 @@ const SECTION = "04"; export function Faq() { return ( - + {/* Not "field notes": that is the blog's name, and this is not the blog. */} +

- +

{HERO_HEADLINE[2]}

-
+

{highlightBrand(HERO_LEAD)}

-
+ {/* One primary action; docs and source sit beside it as quieter + text links so the eye lands on the litepaper first. */} +
diff --git a/components/ui/SectionHeader.tsx b/components/ui/SectionHeader.tsx index 45de483..33bb318 100644 --- a/components/ui/SectionHeader.tsx +++ b/components/ui/SectionHeader.tsx @@ -3,18 +3,22 @@ export function SectionHeader({ label, timestamp, }: { - index: string; - label: string; + /** Omit both on the hero: it is the page's cover, not a numbered section, + * and "§ 01 / Hero" only named the layout slot. */ + index?: string; + label?: string; timestamp: string; }) { return (
- - § {index} / {label} - - {timestamp} + {index && label && ( + + § {index} / {label} + + )} + {timestamp}
); From c72ddb77cf3322a18443e5f63d92b09f23d807c5 Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Thu, 24 Sep 2026 20:05:46 +0300 Subject: [PATCH 3/7] style(panels): unify fleet and faq card with the terminal panel Co-Authored-By: Claude Opus 5.5 --- app/globals.css | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/app/globals.css b/app/globals.css index 6609b9f..d2ec3cb 100644 --- a/app/globals.css +++ b/app/globals.css @@ -494,10 +494,11 @@ a.cta-primary:focus-visible { } /* --- fleet status panel (§05) ----------------------------------------- - Sits on the black Close section. Shares the dashed-border "tool - panel" vocabulary with .terminal but with an ops-dashboard layout - instead of a CLI log: a list of nodes with pulsing serving - indicators plus two aggregate stat bars that breathe. */ + Sits on the paper Contact section as an inverted panel. Same "tool + panel" vocabulary as .terminal — square corners, dashed border, hard + offset shadow — with an ops-dashboard layout instead of a CLI log: a + list of nodes with pulsing serving indicators plus two aggregate stat + bars that breathe. Not interactive, so no hover transform. */ .fleet { font-family: @@ -506,25 +507,10 @@ a.cta-primary:focus-visible { font-size: clamp(11px, 1.1vw, 12px); line-height: 1.55; color: var(--paper); - /* soft top-to-bottom gradient on a mid-charcoal base so the panel - reads as a lit surface rather than a heavy ink slab. */ - background: linear-gradient( - 180deg, - color-mix(in srgb, var(--paper) 18%, var(--ink)) 0%, - color-mix(in srgb, var(--paper) 12%, var(--ink)) 100% - ); + background: color-mix(in srgb, var(--paper) 12%, var(--ink)); border: 1px dashed color-mix(in srgb, var(--paper) 26%, transparent); - border-radius: 14px; overflow: hidden; - box-shadow: - 7px 7px 0 0 color-mix(in srgb, var(--ink) 12%, transparent), - inset 0 1px 0 0 color-mix(in srgb, var(--paper) 8%, transparent); - transition: transform 320ms cubic-bezier(0.2, 0.8, 0.2, 1); - cursor: default; -} - -.fleet:hover { - transform: scale(1.03); + box-shadow: 7px 7px 0 0 color-mix(in srgb, var(--ink) 12%, transparent); } .fleet-head { @@ -1365,9 +1351,8 @@ a.cta-primary:focus-visible { display: none; margin: 0 0 1.5rem; padding: var(--faq-pad); - border: 1px solid var(--faq-edge); - /* box-shadow follows the border-box radius, so the offset block rounds too. */ - border-radius: 10px; + /* Square and dashed, matching .terminal and .fleet. */ + border: 1px dashed var(--faq-edge); box-shadow: 7px 7px 0 0 color-mix(in srgb, var(--paper) 12%, transparent); } .faq-card[data-selected="true"] { From 2a3f194f11bea59c527a1ff5f2bca1d09f0ddab0 Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Thu, 24 Sep 2026 20:05:47 +0300 Subject: [PATCH 4/7] style(motion): shorten scroll reveals and finish them on entry Co-Authored-By: Claude Opus 5.5 --- app/globals.css | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/globals.css b/app/globals.css index d2ec3cb..3757b51 100644 --- a/app/globals.css +++ b/app/globals.css @@ -178,7 +178,7 @@ body { [data-reveal] { opacity: 0; - transform: translateY(28px); + transform: translateY(16px); transition: opacity 800ms cubic-bezier(0.2, 0.8, 0.2, 1), transform 800ms cubic-bezier(0.2, 0.8, 0.2, 1); @@ -198,7 +198,10 @@ body { explicit non-zero duration for safety. */ animation: reveal-wipe 1s linear both; animation-timeline: view(); - animation-range: entry 8% cover 28%; + /* Keyed to the element's own entry, not to how far it covers the + viewport: a short table row finishes as soon as it is mostly in view + instead of staying dim while it is being read. */ + animation-range: entry 0% entry 70%; transition: none; } } @@ -206,7 +209,7 @@ body { @keyframes reveal-wipe { from { opacity: 0; - transform: translate3d(0, 72px, 0); + transform: translate3d(0, 24px, 0); } to { opacity: 1; From a93a03adabc0735e1901f5a606680bf397515f66 Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Thu, 24 Sep 2026 22:40:16 +0300 Subject: [PATCH 5/7] fix(hero): drop the third headline line Co-Authored-By: Claude Opus 5.5 --- components/site/Hero.tsx | 1 - lib/copy.ts | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/components/site/Hero.tsx b/components/site/Hero.tsx index e03b32b..a014106 100644 --- a/components/site/Hero.tsx +++ b/components/site/Hero.tsx @@ -23,7 +23,6 @@ export function Hero() { . - {HERO_HEADLINE[2]}

diff --git a/lib/copy.ts b/lib/copy.ts index b84eb63..270bec7 100644 --- a/lib/copy.ts +++ b/lib/copy.ts @@ -24,13 +24,12 @@ export const SITE_TITLE = "deCDN — decentralized CDN for bytes at scale"; export const SITE_DESCRIPTION = "A decentralized CDN. Anyone can serve bytes; clients pay per megabyte in USDC. An open market that gets cheaper as it grows, at ~$0.01/GB, up to 90% below legacy CDN list pricing."; -/** The three h1 lines, each its own span — lines 2 and 3 carry their own - * indent and line 2 an aria-hidden full stop, so they stay explicit at the - * call site rather than being mapped over. */ +/** The two h1 lines, each its own span — line 2 carries its own indent and + * an aria-hidden full stop, so they stay explicit at the call site rather + * than being mapped over. */ export const HERO_HEADLINE = [ "the delivery layer", "anyone can serve", - "priced, not quoted.", ] as const; export const HERO_LEAD = From 8f1394ae4f5fda527ced1c653743d5b6d338fdc2 Mon Sep 17 00:00:00 2001 From: Alper Gundogdu Date: Fri, 25 Sep 2026 10:24:28 +0100 Subject: [PATCH 6/7] =?UTF-8?q?fix(hero):=20restore=20the=20=C2=A7=2001=20?= =?UTF-8?q?/=20hero=20section=20label?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without it the page's first numbered label was § 02, which read as a missing section. Co-Authored-By: Claude Opus 5.5 --- components/site/Hero.tsx | 2 +- components/ui/SectionHeader.tsx | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/components/site/Hero.tsx b/components/site/Hero.tsx index a014106..7589c60 100644 --- a/components/site/Hero.tsx +++ b/components/site/Hero.tsx @@ -9,7 +9,7 @@ import { HeroTerminal } from "@/components/site/HeroTerminal"; export function Hero() { return ( - +

- {index && label && ( - - § {index} / {label} - - )} - {timestamp} + + § {index} / {label} + + {timestamp}
); From 95f3569cd72e7c73e2c0eaaab2646a094a862450 Mon Sep 17 00:00:00 2001 From: Alper Gundogdu Date: Fri, 25 Sep 2026 10:28:16 +0100 Subject: [PATCH 7/7] fix(layout): size every section to its content Frame's min(100svh, 54rem) floor left empty space under any section shorter than the screen, and how much depended on the viewport height. Sections are now content height plus the fixed vertical padding. Co-Authored-By: Claude Opus 5.5 --- app/globals.css | 1 - components/site/Compare.tsx | 2 +- components/site/Contact.tsx | 2 +- components/site/Method.tsx | 2 +- components/ui/Frame.tsx | 15 +-------------- components/ui/LegalDoc.tsx | 2 +- 6 files changed, 5 insertions(+), 19 deletions(-) diff --git a/app/globals.css b/app/globals.css index 3757b51..b403ebd 100644 --- a/app/globals.css +++ b/app/globals.css @@ -31,7 +31,6 @@ 9vw, 7rem ); /* floor coupled to nav height — see --nav-h note above */ - --spacing-frame-min-h: min(100svh, 54rem); /* Type scale: micro and CTA are fixed; body and headlines scale with viewport. Named under Tailwind v4's --text-* namespace so `text-body`, `text-h1`, … diff --git a/components/site/Compare.tsx b/components/site/Compare.tsx index 6794178..4f3512f 100644 --- a/components/site/Compare.tsx +++ b/components/site/Compare.tsx @@ -43,7 +43,7 @@ export function Compare() {
{CAPTION} diff --git a/components/site/Contact.tsx b/components/site/Contact.tsx index a56f713..641e41d 100644 --- a/components/site/Contact.tsx +++ b/components/site/Contact.tsx @@ -72,7 +72,7 @@ function XIcon() { export function Contact() { return ( - + -
+
stack
{/* The `·` is a separator between chips, not part of one, so it is diff --git a/components/ui/Frame.tsx b/components/ui/Frame.tsx index 02f1545..9c537ac 100644 --- a/components/ui/Frame.tsx +++ b/components/ui/Frame.tsx @@ -7,12 +7,6 @@ type FrameProps = { tone: Tone; /** Extra classes on the outer
. */ className?: string; - /** - * Full-viewport min-height (default true). Set false for a - * content-height section, e.g. the closing section directly above - * the footer, where the floor would just manufacture dead space. - */ - fill?: boolean; children: ReactNode; }; @@ -21,16 +15,9 @@ const TONE_CLASS: Record = { paper: "bg-paper text-ink", }; -export function Frame({ - id, - tone, - className = "", - fill = true, - children, -}: FrameProps) { +export function Frame({ id, tone, className = "", children }: FrameProps) { const sectionClass = [ "relative flex flex-col scroll-mt-[var(--nav-h)] px-frame-gutter py-frame-pad-y", - fill && "min-h-frame-min-h", TONE_CLASS[tone], className, ] diff --git a/components/ui/LegalDoc.tsx b/components/ui/LegalDoc.tsx index 8cff224..afc709c 100644 --- a/components/ui/LegalDoc.tsx +++ b/components/ui/LegalDoc.tsx @@ -11,7 +11,7 @@ export function LegalDoc({ slug }: { slug: LegalSlug }) { const doc = getLegalDoc(slug); return (
- +