From 2a2ee0e176a3a73a5a842c659d5d8ab08f64e28d Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Mon, 28 Sep 2026 05:03:04 +0300 Subject: [PATCH 1/9] feat(hero): add live status link under the terminal and loosen cta spacing Co-Authored-By: Claude Opus 5.5 --- app/globals.css | 76 +++++++++++++++++++++++ components/site/Hero.tsx | 22 ++++--- components/site/HeroStatus.test.ts | 96 ++++++++++++++++++++++++++++++ components/site/HeroStatus.tsx | 62 +++++++++++++++++++ lib/copy.ts | 7 +++ lib/links.ts | 3 + 6 files changed, 258 insertions(+), 8 deletions(-) create mode 100644 components/site/HeroStatus.test.ts create mode 100644 components/site/HeroStatus.tsx diff --git a/app/globals.css b/app/globals.css index 6a14141..b9cb2c4 100644 --- a/app/globals.css +++ b/app/globals.css @@ -298,6 +298,82 @@ a.cta-primary:focus-visible { outline-offset: 9px; } +/* The status link under the hero terminal. Text is a deepened whisper — + raw --whisper is ~3.4:1 on paper, short of AA at body size — while the + dot, arrow and strip keep the full accent. The strip is the last 24h of + settlement per hour (lib/stats.ts fleetView), styled like .fleet-spark. */ +a.hero-status { + color: color-mix(in srgb, var(--whisper) 68%, var(--ink)); + text-decoration: none; +} + +.hero-status-text { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding-bottom: 1px; + border-bottom: 1px solid transparent; + transition: border-bottom-color 260ms cubic-bezier(0.2, 0.8, 0.2, 1); +} + +a.hero-status:hover .hero-status-text { + border-bottom-color: currentColor; +} + +a.hero-status .arrow { + margin-right: 0; + transition: transform 260ms cubic-bezier(0.16, 1, 0.3, 1); +} + +a.hero-status:hover .arrow { + transform: translateX(6px); +} + +a.hero-status:focus-visible { + outline: 1px dashed var(--ink); + outline-offset: 6px; +} + +.status-live { + width: 7px; + height: 7px; + flex: none; + border-radius: 50%; + border: 1px solid var(--whisper); +} + +.status-live-active { + background: var(--whisper); + animation: fleet-pulse 2.4s ease-in-out infinite; +} + +.status-strip { + display: inline-flex; + align-items: flex-end; + gap: 2px; + height: 12px; + border-bottom: 1px solid color-mix(in srgb, var(--whisper) 35%, transparent); +} + +.status-strip > span { + width: 3px; + height: max(1px, calc(var(--level, 0) * 100%)); + background: var(--whisper); + transition: height 0.6s cubic-bezier(0.3, 0, 0.2, 1); +} + +@media (prefers-reduced-motion: reduce) { + .status-live-active { + animation: none; + opacity: 1; + } + .status-strip > span, + .hero-status-text, + a.hero-status .arrow { + transition: none; + } +} + @media (prefers-reduced-motion: reduce) { a.cta-primary, a.cta-primary .arrow { diff --git a/components/site/Hero.tsx b/components/site/Hero.tsx index 7589c60..d86ba9f 100644 --- a/components/site/Hero.tsx +++ b/components/site/Hero.tsx @@ -4,6 +4,7 @@ import { highlightBrand } from "@/components/ui/brand"; import { Figure } from "@/components/ui/Figure"; import { Frame } from "@/components/ui/Frame"; import { SectionHeader } from "@/components/ui/SectionHeader"; +import { HeroStatus } from "@/components/site/HeroStatus"; import { HeroTerminal } from "@/components/site/HeroTerminal"; export function Hero() { @@ -25,8 +26,11 @@ export function Hero() { -
-
+ {/* Stats and the status link share the grid's second row so they + baseline-align on desktop; on mobile the DOM order stacks the + link under the stats and the terminal last. */} +
+

{highlightBrand(HERO_LEAD)}

@@ -66,15 +70,17 @@ export function Hero() {
+
-
- {HERO_FIGURES.map((figure) => ( -
- ))} -
+
+ {HERO_FIGURES.map((figure) => ( +
+ ))}
- + + +
diff --git a/components/site/HeroStatus.test.ts b/components/site/HeroStatus.test.ts new file mode 100644 index 0000000..d42c19d --- /dev/null +++ b/components/site/HeroStatus.test.ts @@ -0,0 +1,96 @@ +import { describe, expect, it } from "vitest"; +import { HeroStatusLink } from "./HeroStatus"; +import { HERO_STATUS } from "@/lib/copy"; +import { STATUS_URL } from "@/lib/links"; +import { + FLEET_WINDOW_HOURS, + fleetView, + type Stats, + type StatsResult, +} from "@/lib/stats"; +import { attrs, findAll, findOne, textOf } from "@/test-utils/react-tree"; + +// HeroStatus itself calls useStats (useState), so the tree walker can't invoke +// it; HeroStatusLink is its render half. + +const stats: Stats = { + chainId: 421614, + updatedAt: "2026-09-27T12:00:00.000Z", + lastBlock: 100, + caughtUp: true, + totals: { + valueSettled: "1500000", + bytesServed: "3000000000", + settlementCount: 2, + }, + hourly: [ + { + hour: "2026-09-27T11", + valueSettled: "500000", + bytesServed: "1000000000", + settlementCount: 1, + registeredNodes: 1, + }, + { + hour: "2026-09-27T12", + valueSettled: "1000000", + bytesServed: "2000000000", + settlementCount: 1, + registeredNodes: 1, + }, + ], + settlements: [], + nodes: {}, +}; + +const fetchedAt = 1_790_000_000_000; + +const render = (result: StatsResult) => { + const tree = HeroStatusLink({ result }); + const spans = findAll(tree, "span"); + const strip = spans.find((el) => attrs(el).className === "status-strip"); + const dot = spans.find((el) => + String(attrs(el).className).startsWith("status-live"), + ); + if (!strip || !dot) throw new Error("strip or dot missing"); + const levels = findAll(strip, "span") + .filter((el) => el !== strip) + .map((el) => (attrs(el).style as Record)["--level"]); + return { tree, levels, dotClass: attrs(dot).className }; +}; + +describe("HeroStatusLink", () => { + it("links to the status site in a new tab", () => { + const a = findOne(render({ status: "loading" }).tree, "a"); + expect(attrs(a).href).toBe(STATUS_URL); + expect(attrs(a).target).toBe("_blank"); + expect(attrs(a).rel).toBe("noopener noreferrer"); + expect(textOf(a)).toContain(HERO_STATUS.value); + }); + + it.each([{ status: "loading" }, { status: "error" }] as const)( + "renders a flat strip and no live pulse while $status", + (result) => { + const { levels, dotClass } = render(result); + expect(levels).toHaveLength(FLEET_WINDOW_HOURS); + expect(levels.every((l) => l === 0)).toBe(true); + expect(dotClass).toBe("status-live"); + }, + ); + + it("renders the last 24h of settlement and pulses when current", () => { + const { levels, dotClass } = render({ status: "ok", stats, fetchedAt }); + expect(levels).toEqual(fleetView(stats).settledSpark); + expect(levels.at(-1)).toBe(1); + expect(dotClass).toBe("status-live status-live-active"); + }); + + it("does not claim live while the index is catching up", () => { + const { dotClass } = render({ + status: "ok", + stats: { ...stats, caughtUp: false }, + fetchedAt, + }); + expect(dotClass).toBe("status-live"); + }); +}); diff --git a/components/site/HeroStatus.tsx b/components/site/HeroStatus.tsx new file mode 100644 index 0000000..f0e04ca --- /dev/null +++ b/components/site/HeroStatus.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { HERO_STATUS } from "@/lib/copy"; +import { STATUS_URL } from "@/lib/links"; +import { FLEET_WINDOW_HOURS, fleetView, type StatsResult } from "@/lib/stats"; +import { useStats } from "@/lib/use-stats"; + +// Flat strip until the stats load. +const FLAT = Array(FLEET_WINDOW_HOURS).fill(0); + +/** + * The link itself, from a stats result. Split from `HeroStatus` so the + * rendering is a plain function a test can walk without a React renderer; + * `HeroStatus` only adds the fetch. Mirrors `Figure`'s label/value lines so it + * baseline-aligns with the hero stat strip beside it. + */ +export function HeroStatusLink({ + result, + className, +}: { + result: StatsResult; + className?: string; +}) { + const ok = result.status === "ok"; + // The last 24h of USDC settled, per hour — the same series as the fleet + // panel's settled strip. + const levels = ok ? fleetView(result.stats).settledSpark : FLAT; + // Only claim "live" when the indexer is current. + const live = ok && result.stats.caughtUp; + + return ( + + {HERO_STATUS.label} + + + + {HERO_STATUS.value} + + → + + + + {levels.map((level, i) => ( + + ))} + + + + ); +} + +export function HeroStatus({ className }: { className?: string }) { + return ; +} diff --git a/lib/copy.ts b/lib/copy.ts index 8b4c04d..54f46a0 100644 --- a/lib/copy.ts +++ b/lib/copy.ts @@ -53,6 +53,13 @@ export const HERO_FIGURES = [ { label: "gas overhead", value: "<1%" }, ] as const satisfies readonly FigureCopy[]; +/** The status link under the hero terminal, set in the stat strip's + * label/value type so it lines up with it. */ +export const HERO_STATUS = { + label: "see also", + value: "Status website", +} as const satisfies FigureCopy; + export const COMPARE_HEADLINE = [ "information scaled.", "supply didn't.", diff --git a/lib/links.ts b/lib/links.ts index 4b97526..ad4ea70 100644 --- a/lib/links.ts +++ b/lib/links.ts @@ -45,6 +45,9 @@ export const BLOG_URL = `${SITE_URL}blog/`; // (app/sitemap.xml, app/llms.txt, app/llms-full.txt); derive it once. export const DOCS_ORIGIN = new URL(links.docs).origin; +// The network status site, linked from under the hero terminal. +export const STATUS_URL = "https://status.decdn.org"; + // Twitter expects an `@handle`; derive from the X profile URL so the X // account is the single source of truth. export const X_HANDLE = `@${new URL(links.x).pathname.replace(/^\//, "")}`; From 2729aba09c89adba5243aa2d8f32a0fb3f6cfbcb Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Mon, 28 Sep 2026 05:06:05 +0300 Subject: [PATCH 2/9] refactor(hero): drop the bar strip from the status link Co-Authored-By: Claude Opus 5.5 --- app/globals.css | 19 +--------- components/site/HeroStatus.test.ts | 57 +++++++----------------------- components/site/HeroStatus.tsx | 16 ++------- 3 files changed, 15 insertions(+), 77 deletions(-) diff --git a/app/globals.css b/app/globals.css index b9cb2c4..7a6856e 100644 --- a/app/globals.css +++ b/app/globals.css @@ -300,8 +300,7 @@ a.cta-primary:focus-visible { /* The status link under the hero terminal. Text is a deepened whisper — raw --whisper is ~3.4:1 on paper, short of AA at body size — while the - dot, arrow and strip keep the full accent. The strip is the last 24h of - settlement per hour (lib/stats.ts fleetView), styled like .fleet-spark. */ + dot and arrow keep the full accent. */ a.hero-status { color: color-mix(in srgb, var(--whisper) 68%, var(--ink)); text-decoration: none; @@ -347,27 +346,11 @@ a.hero-status:focus-visible { animation: fleet-pulse 2.4s ease-in-out infinite; } -.status-strip { - display: inline-flex; - align-items: flex-end; - gap: 2px; - height: 12px; - border-bottom: 1px solid color-mix(in srgb, var(--whisper) 35%, transparent); -} - -.status-strip > span { - width: 3px; - height: max(1px, calc(var(--level, 0) * 100%)); - background: var(--whisper); - transition: height 0.6s cubic-bezier(0.3, 0, 0.2, 1); -} - @media (prefers-reduced-motion: reduce) { .status-live-active { animation: none; opacity: 1; } - .status-strip > span, .hero-status-text, a.hero-status .arrow { transition: none; diff --git a/components/site/HeroStatus.test.ts b/components/site/HeroStatus.test.ts index d42c19d..aa49b23 100644 --- a/components/site/HeroStatus.test.ts +++ b/components/site/HeroStatus.test.ts @@ -2,12 +2,7 @@ import { describe, expect, it } from "vitest"; import { HeroStatusLink } from "./HeroStatus"; import { HERO_STATUS } from "@/lib/copy"; import { STATUS_URL } from "@/lib/links"; -import { - FLEET_WINDOW_HOURS, - fleetView, - type Stats, - type StatsResult, -} from "@/lib/stats"; +import type { Stats, StatsResult } from "@/lib/stats"; import { attrs, findAll, findOne, textOf } from "@/test-utils/react-tree"; // HeroStatus itself calls useStats (useState), so the tree walker can't invoke @@ -18,27 +13,8 @@ const stats: Stats = { updatedAt: "2026-09-27T12:00:00.000Z", lastBlock: 100, caughtUp: true, - totals: { - valueSettled: "1500000", - bytesServed: "3000000000", - settlementCount: 2, - }, - hourly: [ - { - hour: "2026-09-27T11", - valueSettled: "500000", - bytesServed: "1000000000", - settlementCount: 1, - registeredNodes: 1, - }, - { - hour: "2026-09-27T12", - valueSettled: "1000000", - bytesServed: "2000000000", - settlementCount: 1, - registeredNodes: 1, - }, - ], + totals: { valueSettled: "0", bytesServed: "0", settlementCount: 0 }, + hourly: [], settlements: [], nodes: {}, }; @@ -47,16 +23,11 @@ const fetchedAt = 1_790_000_000_000; const render = (result: StatsResult) => { const tree = HeroStatusLink({ result }); - const spans = findAll(tree, "span"); - const strip = spans.find((el) => attrs(el).className === "status-strip"); - const dot = spans.find((el) => + const dot = findAll(tree, "span").find((el) => String(attrs(el).className).startsWith("status-live"), ); - if (!strip || !dot) throw new Error("strip or dot missing"); - const levels = findAll(strip, "span") - .filter((el) => el !== strip) - .map((el) => (attrs(el).style as Record)["--level"]); - return { tree, levels, dotClass: attrs(dot).className }; + if (!dot) throw new Error("live dot missing"); + return { tree, dotClass: attrs(dot).className }; }; describe("HeroStatusLink", () => { @@ -69,20 +40,16 @@ describe("HeroStatusLink", () => { }); it.each([{ status: "loading" }, { status: "error" }] as const)( - "renders a flat strip and no live pulse while $status", + "does not pulse while $status", (result) => { - const { levels, dotClass } = render(result); - expect(levels).toHaveLength(FLEET_WINDOW_HOURS); - expect(levels.every((l) => l === 0)).toBe(true); - expect(dotClass).toBe("status-live"); + expect(render(result).dotClass).toBe("status-live"); }, ); - it("renders the last 24h of settlement and pulses when current", () => { - const { levels, dotClass } = render({ status: "ok", stats, fetchedAt }); - expect(levels).toEqual(fleetView(stats).settledSpark); - expect(levels.at(-1)).toBe(1); - expect(dotClass).toBe("status-live status-live-active"); + it("pulses when the stats are current", () => { + expect(render({ status: "ok", stats, fetchedAt }).dotClass).toBe( + "status-live status-live-active", + ); }); it("does not claim live while the index is catching up", () => { diff --git a/components/site/HeroStatus.tsx b/components/site/HeroStatus.tsx index f0e04ca..4b3def6 100644 --- a/components/site/HeroStatus.tsx +++ b/components/site/HeroStatus.tsx @@ -2,12 +2,9 @@ import { HERO_STATUS } from "@/lib/copy"; import { STATUS_URL } from "@/lib/links"; -import { FLEET_WINDOW_HOURS, fleetView, type StatsResult } from "@/lib/stats"; +import type { StatsResult } from "@/lib/stats"; import { useStats } from "@/lib/use-stats"; -// Flat strip until the stats load. -const FLAT = Array(FLEET_WINDOW_HOURS).fill(0); - /** * The link itself, from a stats result. Split from `HeroStatus` so the * rendering is a plain function a test can walk without a React renderer; @@ -21,12 +18,8 @@ export function HeroStatusLink({ result: StatsResult; className?: string; }) { - const ok = result.status === "ok"; - // The last 24h of USDC settled, per hour — the same series as the fleet - // panel's settled strip. - const levels = ok ? fleetView(result.stats).settledSpark : FLAT; // Only claim "live" when the indexer is current. - const live = ok && result.stats.caughtUp; + const live = result.status === "ok" && result.stats.caughtUp; return ( - - {levels.map((level, i) => ( - - ))} - ); From 7dfca01caa18cb8bd21e3adf3c43d05cab16560c Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Mon, 28 Sep 2026 05:08:05 +0300 Subject: [PATCH 3/9] fix(hero): point the status link at stats.decdn.org Co-Authored-By: Claude Opus 5.5 --- lib/links.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/links.ts b/lib/links.ts index ad4ea70..9ecf096 100644 --- a/lib/links.ts +++ b/lib/links.ts @@ -46,7 +46,7 @@ export const BLOG_URL = `${SITE_URL}blog/`; export const DOCS_ORIGIN = new URL(links.docs).origin; // The network status site, linked from under the hero terminal. -export const STATUS_URL = "https://status.decdn.org"; +export const STATUS_URL = "https://stats.decdn.org/"; // Twitter expects an `@handle`; derive from the X profile URL so the X // account is the single source of truth. From 55ebb38390ff2b4acf5f2a5636fd5c14dba55913 Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Mon, 28 Sep 2026 05:15:12 +0300 Subject: [PATCH 4/9] refactor(hero): inline the status link and drop the client component Co-Authored-By: Claude Opus 5.5 --- app/globals.css | 6 +-- components/site/Hero.tsx | 30 ++++++++++++-- components/site/HeroStatus.test.ts | 63 ------------------------------ components/site/HeroStatus.tsx | 50 ------------------------ 4 files changed, 27 insertions(+), 122 deletions(-) delete mode 100644 components/site/HeroStatus.test.ts delete mode 100644 components/site/HeroStatus.tsx diff --git a/app/globals.css b/app/globals.css index 7a6856e..64b47ab 100644 --- a/app/globals.css +++ b/app/globals.css @@ -338,16 +338,12 @@ a.hero-status:focus-visible { height: 7px; flex: none; border-radius: 50%; - border: 1px solid var(--whisper); -} - -.status-live-active { background: var(--whisper); animation: fleet-pulse 2.4s ease-in-out infinite; } @media (prefers-reduced-motion: reduce) { - .status-live-active { + .status-live { animation: none; opacity: 1; } diff --git a/components/site/Hero.tsx b/components/site/Hero.tsx index d86ba9f..9580574 100644 --- a/components/site/Hero.tsx +++ b/components/site/Hero.tsx @@ -1,10 +1,14 @@ -import { links } from "@/lib/links"; -import { HERO_FIGURES, HERO_HEADLINE, HERO_LEAD } from "@/lib/copy"; +import { links, STATUS_URL } from "@/lib/links"; +import { + HERO_FIGURES, + HERO_HEADLINE, + HERO_LEAD, + HERO_STATUS, +} from "@/lib/copy"; import { highlightBrand } from "@/components/ui/brand"; import { Figure } from "@/components/ui/Figure"; import { Frame } from "@/components/ui/Frame"; import { SectionHeader } from "@/components/ui/SectionHeader"; -import { HeroStatus } from "@/components/site/HeroStatus"; import { HeroTerminal } from "@/components/site/HeroTerminal"; export function Hero() { @@ -78,7 +82,25 @@ export function Hero() { ))} - + {/* Mirrors Figure's label/value lines so it baseline-aligns with + the stat strip beside it. */} + + {HERO_STATUS.label} + + + + {HERO_STATUS.value} + + → + + + + diff --git a/components/site/HeroStatus.test.ts b/components/site/HeroStatus.test.ts deleted file mode 100644 index aa49b23..0000000 --- a/components/site/HeroStatus.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { HeroStatusLink } from "./HeroStatus"; -import { HERO_STATUS } from "@/lib/copy"; -import { STATUS_URL } from "@/lib/links"; -import type { Stats, StatsResult } from "@/lib/stats"; -import { attrs, findAll, findOne, textOf } from "@/test-utils/react-tree"; - -// HeroStatus itself calls useStats (useState), so the tree walker can't invoke -// it; HeroStatusLink is its render half. - -const stats: Stats = { - chainId: 421614, - updatedAt: "2026-09-27T12:00:00.000Z", - lastBlock: 100, - caughtUp: true, - totals: { valueSettled: "0", bytesServed: "0", settlementCount: 0 }, - hourly: [], - settlements: [], - nodes: {}, -}; - -const fetchedAt = 1_790_000_000_000; - -const render = (result: StatsResult) => { - const tree = HeroStatusLink({ result }); - const dot = findAll(tree, "span").find((el) => - String(attrs(el).className).startsWith("status-live"), - ); - if (!dot) throw new Error("live dot missing"); - return { tree, dotClass: attrs(dot).className }; -}; - -describe("HeroStatusLink", () => { - it("links to the status site in a new tab", () => { - const a = findOne(render({ status: "loading" }).tree, "a"); - expect(attrs(a).href).toBe(STATUS_URL); - expect(attrs(a).target).toBe("_blank"); - expect(attrs(a).rel).toBe("noopener noreferrer"); - expect(textOf(a)).toContain(HERO_STATUS.value); - }); - - it.each([{ status: "loading" }, { status: "error" }] as const)( - "does not pulse while $status", - (result) => { - expect(render(result).dotClass).toBe("status-live"); - }, - ); - - it("pulses when the stats are current", () => { - expect(render({ status: "ok", stats, fetchedAt }).dotClass).toBe( - "status-live status-live-active", - ); - }); - - it("does not claim live while the index is catching up", () => { - const { dotClass } = render({ - status: "ok", - stats: { ...stats, caughtUp: false }, - fetchedAt, - }); - expect(dotClass).toBe("status-live"); - }); -}); diff --git a/components/site/HeroStatus.tsx b/components/site/HeroStatus.tsx deleted file mode 100644 index 4b3def6..0000000 --- a/components/site/HeroStatus.tsx +++ /dev/null @@ -1,50 +0,0 @@ -"use client"; - -import { HERO_STATUS } from "@/lib/copy"; -import { STATUS_URL } from "@/lib/links"; -import type { StatsResult } from "@/lib/stats"; -import { useStats } from "@/lib/use-stats"; - -/** - * The link itself, from a stats result. Split from `HeroStatus` so the - * rendering is a plain function a test can walk without a React renderer; - * `HeroStatus` only adds the fetch. Mirrors `Figure`'s label/value lines so it - * baseline-aligns with the hero stat strip beside it. - */ -export function HeroStatusLink({ - result, - className, -}: { - result: StatsResult; - className?: string; -}) { - // Only claim "live" when the indexer is current. - const live = result.status === "ok" && result.stats.caughtUp; - - return ( - - {HERO_STATUS.label} - - - - {HERO_STATUS.value} - - → - - - - - ); -} - -export function HeroStatus({ className }: { className?: string }) { - return ; -} From 2961e27f5358ae1b4614698bd866d69d87e6392e Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Mon, 28 Sep 2026 05:15:14 +0300 Subject: [PATCH 5/9] style(hero): nudge the stat row and status link up Co-Authored-By: Claude Opus 5.5 --- components/site/Hero.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/site/Hero.tsx b/components/site/Hero.tsx index 9580574..12d9f0a 100644 --- a/components/site/Hero.tsx +++ b/components/site/Hero.tsx @@ -33,7 +33,7 @@ export function Hero() { {/* Stats and the status link share the grid's second row so they baseline-align on desktop; on mobile the DOM order stacks the link under the stats and the terminal last. */} -
+

{highlightBrand(HERO_LEAD)} From 963635f13520853bc520d3db86215eeb9a2650fb Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Mon, 28 Sep 2026 05:17:50 +0300 Subject: [PATCH 6/9] refactor(hero): trim status link css and move its url into links Co-Authored-By: Claude Opus 5.5 --- app/globals.css | 16 ++++------------ components/site/Hero.tsx | 4 ++-- lib/links.ts | 4 +--- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/app/globals.css b/app/globals.css index 64b47ab..158ec5c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -303,7 +303,6 @@ a.cta-primary:focus-visible { dot and arrow keep the full accent. */ a.hero-status { color: color-mix(in srgb, var(--whisper) 68%, var(--ink)); - text-decoration: none; } .hero-status-text { @@ -343,20 +342,13 @@ a.hero-status:focus-visible { } @media (prefers-reduced-motion: reduce) { - .status-live { - animation: none; - opacity: 1; - } - .hero-status-text, + a.cta-primary, + a.cta-primary .arrow, a.hero-status .arrow { transition: none; } -} - -@media (prefers-reduced-motion: reduce) { - a.cta-primary, - a.cta-primary .arrow { - transition: none; + .status-live { + animation: none; } } diff --git a/components/site/Hero.tsx b/components/site/Hero.tsx index 12d9f0a..36b1119 100644 --- a/components/site/Hero.tsx +++ b/components/site/Hero.tsx @@ -1,4 +1,4 @@ -import { links, STATUS_URL } from "@/lib/links"; +import { links } from "@/lib/links"; import { HERO_FIGURES, HERO_HEADLINE, @@ -86,7 +86,7 @@ export function Hero() { the stat strip beside it. */} diff --git a/lib/links.ts b/lib/links.ts index 9ecf096..1b595b8 100644 --- a/lib/links.ts +++ b/lib/links.ts @@ -8,6 +8,7 @@ export const links = { litepaper: "/decdn_litepaper.pdf", presskit: "/presskit/decdn-presskit.zip", docs: "https://docs.decdn.org/overview/introduction", + stats: "https://stats.decdn.org/", blog: "/blog/", contact: `mailto:${EMAIL}`, privacy: "/legal/privacy/", @@ -45,9 +46,6 @@ export const BLOG_URL = `${SITE_URL}blog/`; // (app/sitemap.xml, app/llms.txt, app/llms-full.txt); derive it once. export const DOCS_ORIGIN = new URL(links.docs).origin; -// The network status site, linked from under the hero terminal. -export const STATUS_URL = "https://stats.decdn.org/"; - // Twitter expects an `@handle`; derive from the X profile URL so the X // account is the single source of truth. export const X_HANDLE = `@${new URL(links.x).pathname.replace(/^\//, "")}`; From 89efdd5ef132a89658d2169688d229bc4166225c Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Mon, 28 Sep 2026 05:21:40 +0300 Subject: [PATCH 7/9] fix(hero): stack the status link under the terminal on mobile Co-Authored-By: Claude Opus 5.5 --- components/site/Hero.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/site/Hero.tsx b/components/site/Hero.tsx index 36b1119..84b27d0 100644 --- a/components/site/Hero.tsx +++ b/components/site/Hero.tsx @@ -32,7 +32,7 @@ export function Hero() { {/* Stats and the status link share the grid's second row so they baseline-align on desktop; on mobile the DOM order stacks the - link under the stats and the terminal last. */} + terminal under the stats and the link last. */}

From 8ad509fba7992465c87d93fe8013d0b01f6054e0 Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Mon, 28 Sep 2026 05:26:50 +0300 Subject: [PATCH 8/9] fix(hero): give the status label ink contrast and correct comments Co-Authored-By: Claude Opus 5.5 --- app/globals.css | 7 ++++--- components/site/Hero.tsx | 10 ++++++---- lib/copy.ts | 5 +++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/globals.css b/app/globals.css index 158ec5c..08fd7f1 100644 --- a/app/globals.css +++ b/app/globals.css @@ -298,9 +298,10 @@ a.cta-primary:focus-visible { outline-offset: 9px; } -/* The status link under the hero terminal. Text is a deepened whisper — - raw --whisper is ~3.4:1 on paper, short of AA at body size — while the - dot and arrow keep the full accent. */ +/* The status link under the hero terminal. Raw --whisper is ~3.5:1 on + paper, short of AA at body size, so the value text uses whisper mixed + 68% with ink (~6.6:1). The label is ink like the stat labels; the dot + and the arrow keep the full accent. */ a.hero-status { color: color-mix(in srgb, var(--whisper) 68%, var(--ink)); } diff --git a/components/site/Hero.tsx b/components/site/Hero.tsx index 84b27d0..962a513 100644 --- a/components/site/Hero.tsx +++ b/components/site/Hero.tsx @@ -30,9 +30,9 @@ export function Hero() { - {/* Stats and the status link share the grid's second row so they - baseline-align on desktop; on mobile the DOM order stacks the - terminal under the stats and the link last. */} + {/* At @4xl the stats and the status link share row 2 so they + baseline-align; below it, DOM order stacks lead/CTAs, stats, + terminal, then the link. */}

@@ -92,7 +92,9 @@ export function Hero() { target="_blank" rel="noopener noreferrer" > - {HERO_STATUS.label} + + {HERO_STATUS.label} + diff --git a/lib/copy.ts b/lib/copy.ts index 54f46a0..b63bb26 100644 --- a/lib/copy.ts +++ b/lib/copy.ts @@ -53,8 +53,9 @@ export const HERO_FIGURES = [ { label: "gas overhead", value: "<1%" }, ] as const satisfies readonly FigureCopy[]; -/** The status link under the hero terminal, set in the stat strip's - * label/value type so it lines up with it. */ +/** The status link under the hero terminal. Shaped like FigureCopy so it + * shares the stat strip's label/value type (rendered inline in Hero, not + * through Figure). */ export const HERO_STATUS = { label: "see also", value: "Status website", From 7c49f1c668fc1743bbd113f42797625135928be5 Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Mon, 28 Sep 2026 05:30:05 +0300 Subject: [PATCH 9/9] refactor(hero): drop redundant status link css Co-Authored-By: Claude Opus 5.5 --- app/globals.css | 9 --------- components/site/Hero.tsx | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/app/globals.css b/app/globals.css index 08fd7f1..0b26c52 100644 --- a/app/globals.css +++ b/app/globals.css @@ -307,9 +307,6 @@ a.hero-status { } .hero-status-text { - display: inline-flex; - align-items: center; - gap: 0.5rem; padding-bottom: 1px; border-bottom: 1px solid transparent; transition: border-bottom-color 260ms cubic-bezier(0.2, 0.8, 0.2, 1); @@ -328,15 +325,9 @@ a.hero-status:hover .arrow { transform: translateX(6px); } -a.hero-status:focus-visible { - outline: 1px dashed var(--ink); - outline-offset: 6px; -} - .status-live { width: 7px; height: 7px; - flex: none; border-radius: 50%; background: var(--whisper); animation: fleet-pulse 2.4s ease-in-out infinite; diff --git a/components/site/Hero.tsx b/components/site/Hero.tsx index 962a513..a698c21 100644 --- a/components/site/Hero.tsx +++ b/components/site/Hero.tsx @@ -97,7 +97,7 @@ export function Hero() { - + {HERO_STATUS.value} →