Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 62 additions & 28 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -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`, …
Expand Down Expand Up @@ -178,7 +177,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);
Expand All @@ -198,15 +197,18 @@ 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;
}
}

@keyframes reveal-wipe {
from {
opacity: 0;
transform: translate3d(0, 72px, 0);
transform: translate3d(0, 24px, 0);
}
to {
opacity: 1;
Expand Down Expand Up @@ -253,6 +255,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.
Expand Down Expand Up @@ -447,10 +496,11 @@ a.underline-brutal:hover .arrow {
}

/* --- 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:
Expand All @@ -459,25 +509,10 @@ a.underline-brutal:hover .arrow {
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 {
Expand Down Expand Up @@ -1318,9 +1353,8 @@ a.underline-brutal:hover .arrow {
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"] {
Expand Down
12 changes: 4 additions & 8 deletions app/llms-full.txt/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
COMPARE_ROWS,
HERO_FIGURES,
HERO_LEAD,
METHOD_FIGURES,
METHOD_STEPS,
STACK,
statusBlock,
Expand Down Expand Up @@ -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(" · ")}`);
Expand Down
6 changes: 3 additions & 3 deletions app/llms-full.txt/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -217,8 +217,6 @@ ${compareTable}

Stack: ${STACK.join(" · ")}

${figureList(METHOD_FIGURES)}

${methodSteps}

### FAQ
Expand All @@ -227,6 +225,8 @@ ${faq}

### Contact

${CONTACT_HEADLINE.join(" ")}

${CONTACT_LEAD}

Email ${EMAIL} · GitHub ${links.github} · X ${links.x} · LinkedIn ${links.linkedin}
Expand Down
2 changes: 1 addition & 1 deletion components/site/Compare.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function Compare() {
<table
role="table"
aria-labelledby="compare-caption"
className="mt-auto flex flex-col pt-12"
className="flex flex-col pt-12"
>
<caption id="compare-caption" className="sr-only">
{CAPTION}
Expand Down
34 changes: 12 additions & 22 deletions components/site/Contact.tsx
Original file line number Diff line number Diff line change
@@ -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 <svg> is aria-hidden. iconClass
// sizes them in em (1.05×), so each glyph scales with its link's font-size;
Expand Down Expand Up @@ -74,7 +72,7 @@ function XIcon() {

export function Contact() {
return (
<Frame id="contact" tone="paper" className="overflow-hidden" fill={false}>
<Frame id="contact" tone="paper" className="overflow-hidden">
<SectionHeader
index="05"
label="Contact"
Expand All @@ -83,24 +81,16 @@ export function Contact() {

<div className="mt-14 grid gap-12 @4xl:grid-cols-[minmax(0,1fr)_minmax(0,460px)] @4xl:items-start @4xl:gap-14">
<div className="flex flex-col gap-12">
<h2 id="contact-h" className="sr-only">
Contact
</h2>

{/* eslint-disable-next-line @next/next/no-img-element */}
<img
{/* A display line, not the wordmark: the nav already carries the
logo, and repeating it at 11rem said nothing about the section. */}
<h2
data-reveal
src="/wordmark-light.svg"
alt="decdn"
width={411}
height={110}
className="block"
style={{
height: wordmarkH,
width: "auto",
marginLeft: `calc(${wordmarkH} * -0.18)`,
}}
/>
id="contact-h"
className="hug flex flex-col text-h2 leading-[0.92] font-semibold tracking-[-0.04em]"
>
<span>{CONTACT_HEADLINE[0]}</span>
<span className="pl-[3vw] opacity-60">{CONTACT_HEADLINE[1]}</span>
</h2>

<p
data-reveal
Expand Down Expand Up @@ -144,7 +134,7 @@ export function Contact() {
target="_blank"
rel="noopener noreferrer"
>
<XIcon />x (twitter)
<XIcon />X
<span className="arrow" aria-hidden>
→
</span>
Expand Down
8 changes: 7 additions & 1 deletion components/site/Faq.tsx
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -9,7 +10,12 @@ const SECTION = "04";
export function Faq() {
return (
<Frame id="faq" tone="ink">
<SectionHeader index={SECTION} label="FAQ" timestamp="field notes" />
{/* Not "field notes": that is the blog's name, and this is not the blog. */}
<SectionHeader
index={SECTION}
label="FAQ"
timestamp={`${FAQ_ITEMS.length} questions`}
/>

<div className="mt-14 flex flex-col gap-10">
<h2
Expand Down
47 changes: 25 additions & 22 deletions components/site/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,19 @@ export function Hero() {
.
</span>
</span>
<span className="rise rise-2 pl-[12vw]">{HERO_HEADLINE[2]}</span>
</h1>

<div className="grid gap-10 @4xl:grid-cols-[minmax(0,1fr)_minmax(0,440px)] @4xl:items-end @4xl:gap-12">
<div className="grid gap-10 @4xl:grid-cols-[minmax(0,1fr)_minmax(0,440px)] @4xl:items-start @4xl:gap-12">
<div className="flex flex-col gap-8 @4xl:gap-12">
<p className="rise rise-3 max-w-[64ch] text-body leading-[1.65]">
{highlightBrand(HERO_LEAD)}
</p>

<div className="rise rise-4 flex flex-col flex-wrap gap-x-8 gap-y-3 @md:flex-row @md:items-end">
{/* One primary action; docs and source sit beside it as quieter
text links so the eye lands on the litepaper first. */}
<div className="rise rise-4 flex flex-col items-start gap-x-8 gap-y-5 @md:flex-row @md:items-center">
<a
className="underline-brutal text-cta font-semibold tracking-[0.14em] uppercase"
className="cta-primary"
href={links.litepaper}
target="_blank"
rel="noopener noreferrer"
Expand All @@ -44,24 +45,26 @@ export function Hero() {
→
</span>
</a>
<a
className="underline-brutal text-cta font-semibold tracking-[0.14em] uppercase"
href={links.docs}
>
read the docs
<span className="arrow" aria-hidden>
→
</span>
</a>
<a
className="underline-brutal text-cta font-semibold tracking-[0.14em] uppercase"
href={links.github}
>
source
<span className="arrow" aria-hidden>
→
</span>
</a>
<div className="flex gap-x-8">
<a
className="underline-brutal text-cta font-medium tracking-[0.14em] uppercase opacity-70 hover:opacity-100"
href={links.docs}
>
read the docs
<span className="arrow" aria-hidden>
→
</span>
</a>
<a
className="underline-brutal text-cta font-medium tracking-[0.14em] uppercase opacity-70 hover:opacity-100"
href={links.github}
>
source
<span className="arrow" aria-hidden>
→
</span>
</a>
</div>
</div>

<div className="rise rise-5 grid grid-cols-2 gap-y-4 @xl:grid-cols-4">
Expand Down
Loading
Loading