Skip to content

Commit 528355b

Browse files
btraversclaude
andcommitted
feat(website): landing redesign (v2)
Port the refreshed Claude Design landing: - new hero copy (the two convictions), philosophy reworked into 'Expressive to read. Robust to run.' with three filled principle cards - new 'In the age of AI' section (why fail-fast + expressiveness matter when an agent writes the code) - new 'Inspirations' section (zod / prisma / tRPC / oRPC) - project cards restructured (56px mark + stars on top, tag below) and demesne copy refreshed (Injection tag, new points, 'pnpm add demesne unthrown') - nav adds 'Why now'; npm link omitted (the ~btravers namespace is empty — each package publishes under its own name) Also fixes the website build: @btravstack/theme's Layout imports 'vue', which the workspace build couldn't resolve — add vue as a devDependency of the theme package and noExternal it in the website's Vite config. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 5e1892a commit 528355b

4 files changed

Lines changed: 103 additions & 40 deletions

File tree

apps/website/.vitepress/config.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { defineConfig } from "vitepress";
22

33
const SITE_TITLE = "btravstack";
44
const SITE_DESCRIPTION =
5-
"Type-safe building blocks for the TypeScript backend: contracts for AMQP & Temporal, and errors as values.";
5+
"Type-safe building blocks for the TypeScript backend: contracts for AMQP & Temporal, typed dependency injection, and errors as values.";
66
const SITE_URL = "https://btravstack.github.io";
77

88
// https://vitepress.dev/reference/site-config
@@ -157,4 +157,13 @@ export default defineConfig({
157157
},
158158
search: { provider: "local" },
159159
},
160+
161+
vite: {
162+
ssr: {
163+
// @btravstack/theme's entry imports "vue" (for its Layout); mark it
164+
// noExternal so Vite processes the package and resolves "vue" to the
165+
// app's copy instead of failing to externalize it during the build.
166+
noExternal: ["@btravstack/theme"],
167+
},
168+
},
160169
});

apps/website/.vitepress/theme/Landing.vue

Lines changed: 88 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,22 @@ function toggleAppearance() {
1010
}
1111
1212
const principles = [
13-
{ num: "01", title: "Contracts, not guesswork", body: "Declare your interface once. Types, autocompletion and validation flow to every call site — publisher, consumer, worker and client." },
14-
{ num: "02", title: "Errors as values", body: "Model failure in the type system. Anticipated errors are returned, not thrown — only a true defect ever escapes up the stack." },
15-
{ num: "03", title: "Validated at the edges", body: "Standard-Schema validation runs at every network boundary, so malformed data never makes it past the door of your service." },
13+
{ num: "01", title: "The signature is the doc", body: "A contract or a function signature should be enough to understand a system. Declare the interface once and read everything from it — no digging into implementations." },
14+
{ num: "02", title: "Fail fast, everywhere", body: "The sooner the error, the better the code. Anticipated failures are values in the type, malformed data stops at the boundary, missing wiring is a compile error." },
15+
{ num: "03", title: "Coherence at a high level", body: "Contracts pin down how the pieces fit — publisher and consumer, workflow and client, module and dependency can't drift apart while implementations change underneath." },
16+
];
17+
18+
const aiCards = [
19+
{ title: "The tightest feedback loop", body: "An agent is only as good as the signal it gets back. Fail-fast code turns a wrong guess into a precise error in seconds — and fast errors are what make iteration converge." },
20+
{ title: "A deterministic metric", body: "Typechecking has no opinions. The contract holds or it doesn't — a reproducible, binary measure of generated code, before a single test runs." },
21+
{ title: "Review at the contract level", body: "Expressive contracts carry the intent. You review interfaces while the AI churns through implementations — the types guarantee the whole still fits together." },
22+
];
23+
24+
const inspirations = [
25+
{ name: "zod", href: "https://zod.dev", body: "The schema as a single source of truth — write it once, infer the types." },
26+
{ name: "prisma", href: "https://www.prisma.io", body: "One declaration, a fully typed client — the whole database readable from its types." },
27+
{ name: "tRPC", href: "https://trpc.io", body: "End-to-end type safety across a network boundary, with no codegen step." },
28+
{ name: "oRPC", href: "https://orpc.unnoq.com", body: "Contract-first RPC — the contract is an artifact you define, share and implement against." },
1629
];
1730
1831
interface Project {
@@ -55,11 +68,11 @@ const projects: Project[] = [
5568
repo: "https://github.com/btravstack/unthrown", docs: "https://btravstack.github.io/unthrown/",
5669
},
5770
{
58-
tag: "Wiring", name: "demesne", pkg: "demesne",
59-
logo: "/logos/demesne", repoFull: "btravstack/demesne", stars0: 0,
60-
blurb: "Type-safe dependency injection. A container holds your services' domain and provides itwith requirements and construction errors tracked in the type system.",
61-
points: ["Missing wiring is a compile error", "Construction errors in the result type", "No decorators, no reflect-metadata"],
62-
install: "pnpm add demesne",
71+
tag: "Injection", name: "demesne", pkg: "demesne",
72+
logo: "/logos/demesne", repoFull: "btravstack/demesne", stars0: 2,
73+
blurb: "Type-safe dependency injection. Requirements and construction errors live in the type systemyou cannot build until every dependency is wired.",
74+
points: ["Missing wiring is a compile error", "Failures as one static union", "Scoped resources, LIFO release"],
75+
install: "pnpm add demesne unthrown",
6376
repo: "https://github.com/btravstack/demesne", docs: "https://btravstack.github.io/demesne/",
6477
},
6578
];
@@ -101,6 +114,7 @@ onMounted(() => {
101114
</a>
102115
<nav class="btv-nav">
103116
<a class="navlink nav-hide" href="#philosophy">Philosophy</a>
117+
<a class="navlink nav-hide" href="#ai">Why now</a>
104118
<a class="navlink nav-hide" href="#projects" style="margin-right:6px">Projects</a>
105119
<ClientOnly>
106120
<button type="button" class="btv-toggle" :title="isDark ? 'Switch to light' : 'Switch to dark'" :aria-label="isDark ? 'Switch to light theme' : 'Switch to dark theme'" :aria-pressed="isDark" @click="toggleAppearance">
@@ -124,7 +138,7 @@ onMounted(() => {
124138
</div>
125139
<h1 class="btv-title"><span class="btv-pink">btrav</span><span>stack</span></h1>
126140
<p class="btv-lead">An expressive, robust TypeScript backend.</p>
127-
<p class="btv-sub">A small set of type-safe building blocks for Node. Describe your messaging, workflows and errors once — and let the types flow to every call site.</p>
141+
<p class="btv-sub">A small stack of type-safe building blocks for Node, built on two convictions: a signature should be enough to understand a system, and the fastest error is the best one. Declare the contract once — types, validation and feedback flow everywhere.</p>
128142
<div class="btv-cta-row">
129143
<a href="#projects" class="cta-primary btv-primary-btn">Explore the packages</a>
130144
<a href="https://github.com/btravstack" target="_blank" rel="noopener" class="cta-ghost btv-ghost-cta">
@@ -140,32 +154,56 @@ onMounted(() => {
140154

141155
<section id="philosophy" class="btv-section btv-narrow">
142156
<p class="btv-eyebrow">Why btravstack</p>
143-
<h2 class="btv-h2 btv-h2-wide">The compiler should catch what tests can't.</h2>
144-
<p class="btv-section-lead">Each package is small, focused and does one thing well — but they share a worldview: your interfaces are contracts, failure is part of the type, and bad data stops at the door.</p>
157+
<h2 class="btv-h2 btv-h2-wide">Expressive to read. Robust to run.</h2>
158+
<p class="btv-section-lead">Each package is small, focused and does one thing well — but they share a worldview: expressive code you can understand from its signature alone, and robust code that fails fast enough to learn from.</p>
145159
<div class="btv-prin">
146-
<div v-for="pr in principles" :key="pr.num" class="btv-prin-item">
147-
<span class="btv-prin-num" aria-hidden="true">{{ pr.num }}</span>
160+
<div v-for="pr in principles" :key="pr.num" class="btv-prin-card">
161+
<div class="btv-prin-chip" aria-hidden="true">{{ pr.num }}</div>
148162
<h3 class="btv-prin-title">{{ pr.title }}</h3>
149163
<p class="btv-prin-body">{{ pr.body }}</p>
150164
</div>
151165
</div>
152166
</section>
153167

168+
<section id="ai" class="btv-section btv-ai">
169+
<div class="btv-ai-panel">
170+
<p class="btv-eyebrow">In the age of AI</p>
171+
<h2 class="btv-h2 btv-ai-h2">Written with AI. Judged by the compiler.</h2>
172+
<p class="btv-section-lead">None of this was invented for AI — AI just raised the stakes. When an agent writes the code, expressiveness and fail-fast stop being a matter of taste and become infrastructure.</p>
173+
<div class="btv-ai-grid">
174+
<div v-for="c in aiCards" :key="c.title" class="btv-ai-card">
175+
<h3>{{ c.title }}</h3>
176+
<p>{{ c.body }}</p>
177+
</div>
178+
</div>
179+
</div>
180+
</section>
181+
182+
<section class="btv-section btv-insp">
183+
<p class="btv-eyebrow">Inspirations</p>
184+
<h2 class="btv-h2 btv-insp-h2">Standing on good shoulders.</h2>
185+
<p class="btv-section-lead">btravstack borrows its instincts from the libraries that made TypeScript feel this way in the first place.</p>
186+
<div class="btv-insp-grid">
187+
<a v-for="i in inspirations" :key="i.name" :href="i.href" target="_blank" rel="noopener" class="pcard btv-insp-card">
188+
<span class="btv-insp-name">{{ i.name }}</span>
189+
<p class="btv-insp-body">{{ i.body }}</p>
190+
</a>
191+
</div>
192+
</section>
193+
154194
<section id="projects" class="btv-section">
155195
<p class="btv-eyebrow">The packages</p>
156196
<h2 class="btv-h2" style="margin-bottom:30px">Four libraries, one stack.</h2>
157197
<div class="grid-3 btv-projects">
158198
<div v-for="p in projects" :key="p.name" class="pcard btv-pcard">
159199
<div class="btv-pcard-top">
160-
<div class="btv-pcard-id">
161-
<img :src="`${p.logo}-${isDark ? 'dark' : 'light'}.svg`" width="46" height="46" :alt="`${p.name} logo`" class="btv-logo" />
162-
<span class="btv-tag">{{ p.tag }}</span>
163-
</div>
200+
<img :src="`${p.logo}-${isDark ? 'dark' : 'light'}.svg`" width="56" height="56" :alt="`${p.name} logo`" class="btv-logo" />
164201
<span class="btv-stars">
165202
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1.3l2.06 4.17 4.6.67-3.33 3.24.78 4.58L8 11.8l-4.11 2.16.78-4.58L1.34 6.14l4.6-.67L8 1.3z"/></svg>
166203
{{ formatStars(stars[p.repoFull]) }}
167204
</span>
168205
</div>
206+
<span class="btv-tag">{{ p.tag }}</span>
169207
<h3 class="btv-pname">{{ p.name }}</h3>
170208
<code class="btv-pkg">{{ p.pkg }}</code>
171209
<p class="btv-blurb">{{ p.blurb }}</p>
@@ -230,7 +268,7 @@ onMounted(() => {
230268
</div>
231269
<div class="btv-foot-bottom">
232270
<span>MIT © 2026 <a class="foot-link btv-foot-muted2" href="https://github.com/btravers" target="_blank" rel="noopener">Benoit Travers</a></span>
233-
<a class="foot-link btv-foot-mono" href="https://github.com/btravers" target="_blank" rel="noopener">@btravers</a>
271+
<a class="foot-link btv-foot-mono" href="https://github.com/btravers" target="_blank" rel="noopener">@btravers · betterave 🇫🇷</a>
234272
</div>
235273
</footer>
236274

@@ -258,13 +296,15 @@ onMounted(() => {
258296
--btv-hover: rgba(255, 255, 255, 0.05);
259297
--btv-pill: rgba(255, 255, 255, 0.04);
260298
--btv-footer: rgba(0, 0, 0, 0.2);
299+
--btv-ai-card: rgba(21, 16, 28, 0.55);
261300
--card-rest-shadow: none; /* dark cards: depth from the fill + hairline */
262301
--glow-display: block; /* pink hero glow — a dark-scheme signature */
263302
}
264303
:global(html:not(.dark)) .btv {
265304
--btv-hover: rgba(26, 12, 20, 0.05);
266305
--btv-pill: rgba(26, 12, 20, 0.035);
267306
--btv-footer: rgba(26, 12, 20, 0.025);
307+
--btv-ai-card: rgba(255, 255, 255, 0.6);
268308
--card-rest-shadow: 0 1px 2px rgba(26, 12, 20, 0.05), 0 16px 32px -24px rgba(26, 12, 20, 0.22);
269309
--glow-display: none; /* on near-white the pink glow reads as a washed
270310
"livid" cloud behind the wordmark — drop it */
@@ -323,34 +363,42 @@ onMounted(() => {
323363
.btv-h2-wide { max-width: 680px; }
324364
.btv-section-lead { margin: 16px 0 0; max-width: 640px; font-size: 16.5px; line-height: 1.65; color: var(--muted); }
325365
326-
/* Principles — editorial, deliberately lighter than the filled package cards:
327-
no fill, hairline rules, ghost numerals. Reads as "why"; the filled package
328-
cards below read as "what". */
329-
.btv-prin { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0; margin-top: 44px; border-top: 1px solid var(--border-2); }
330-
.btv-prin-item { padding: 30px 30px 8px; border-left: 1px solid var(--border); }
331-
.btv-prin-item:first-child { border-left: none; padding-left: 0; }
332-
.btv-prin-num { font-family: var(--mono); font-weight: 500; font-size: 13px; letter-spacing: 1px; color: var(--text-accent); opacity: .85; }
333-
.btv-prin-title { margin: 18px 0 0; font-weight: 700; font-size: 19px; letter-spacing: -0.3px; color: var(--text); }
334-
.btv-prin-body { margin: 10px 0 0; font-size: 14.5px; line-height: 1.62; color: var(--muted); }
335-
@media (max-width: 880px) {
336-
.btv-prin { grid-template-columns: minmax(0, 1fr); }
337-
.btv-prin-item { border-left: none; padding: 26px 0 8px; border-top: 1px solid var(--border); }
338-
.btv-prin-item:first-child { border-top: none; padding-top: 30px; }
339-
}
366+
/* Philosophy — filled cards with a numbered accent chip. */
367+
.btv-prin { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; margin-top: 34px; }
368+
.btv-prin-card { background: var(--card); border: 1px solid var(--border); border-radius: 16px; padding: 22px; box-shadow: var(--card-rest-shadow); }
369+
.btv-prin-chip { width: 38px; height: 38px; border-radius: 10px; display: flex; align-items: center; justify-content: center; background: var(--accent-wash-2); border: 1px solid var(--accent-line); color: var(--text-accent); font-family: var(--mono); font-weight: 600; font-size: 15px; }
370+
.btv-prin-title { margin: 16px 0 0; font-weight: 700; font-size: 18px; letter-spacing: -0.3px; color: var(--text); }
371+
.btv-prin-body { margin: 9px 0 0; font-size: 14.5px; line-height: 1.6; color: var(--muted); }
372+
373+
/* "In the age of AI" — a bordered gradient panel with translucent inner cards. */
374+
.btv-ai { padding-top: 76px; padding-bottom: 0; }
375+
.btv-ai-panel { background: linear-gradient(120deg, rgba(142,26,82,0.30), rgba(224,88,154,0.07)); border: 1px solid var(--border-2); border-radius: 24px; padding: 46px 40px; }
376+
.btv-ai-h2 { max-width: 640px; font-size: clamp(26px, 3.6vw, 36px); }
377+
.btv-ai-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; margin-top: 32px; }
378+
.btv-ai-card { background: var(--btv-ai-card); border: 1px solid var(--border); border-radius: 16px; padding: 22px; }
379+
.btv-ai-card h3 { margin: 0; font-weight: 700; font-size: 17.5px; letter-spacing: -0.3px; color: var(--text); }
380+
.btv-ai-card p { margin: 9px 0 0; font-size: 14.5px; line-height: 1.6; color: var(--muted); }
381+
382+
/* Inspirations — compact link cards. */
383+
.btv-insp { padding-top: 64px; padding-bottom: 0; }
384+
.btv-insp-h2 { font-size: clamp(24px, 3.2vw, 32px); }
385+
.btv-insp-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px; margin-top: 28px; }
386+
.btv-insp-card { display: block; text-decoration: none; background: var(--card); border: 1px solid var(--border); border-radius: 16px; padding: 20px 22px; box-shadow: var(--card-rest-shadow); }
387+
.btv-insp-name { font-family: var(--mono); font-weight: 600; font-size: 15.5px; color: var(--text); }
388+
.btv-insp-body { margin: 8px 0 0; font-size: 14px; line-height: 1.55; color: var(--muted); }
340389
341390
/* Project cards — four across on wide screens ("four libraries, one stack"),
342391
two up on tablet, one on mobile (the .grid-3 rule below handles ≤880). */
343392
.btv-projects { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px; }
344-
@media (max-width: 1080px) { .btv-projects { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
393+
@media (max-width: 1080px) { .btv-projects, .btv-insp-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
345394
.btv-pcard { display: flex; flex-direction: column; background: var(--card); border: 1px solid var(--border); border-radius: 20px; padding: 26px; box-shadow: var(--card-rest-shadow); }
346395
.pcard { transition: transform .2s ease, border-color .2s ease, box-shadow .2s ease; }
347396
.pcard:hover { transform: translateY(-4px); border-color: var(--border-2); box-shadow: var(--shadow-card); }
348-
.btv-pcard-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
349-
.btv-pcard-id { display: flex; align-items: center; gap: 11px; min-width: 0; }
350-
.btv-logo { display: block; flex: none; }
351-
.btv-tag { display: inline-flex; align-items: center; font-family: var(--mono); font-size: 11.5px; font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase; color: var(--text-accent); background: var(--accent-wash); border: 1px solid var(--accent-line); padding: 5px 10px; border-radius: 999px; }
397+
.btv-pcard-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
398+
.btv-logo { display: block; flex: none; width: 56px; height: 56px; object-fit: contain; filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.35)); }
399+
.btv-tag { display: inline-flex; align-self: flex-start; align-items: center; margin-top: 20px; font-family: var(--mono); font-size: 11.5px; font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase; color: var(--text-accent); background: var(--accent-wash); border: 1px solid var(--accent-line); padding: 5px 10px; border-radius: 999px; }
352400
.btv-stars { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; font-weight: 600; color: var(--faint); }
353-
.btv-pname { margin: 18px 0 0; font-weight: 800; font-size: 23px; letter-spacing: -0.6px; color: var(--text); }
401+
.btv-pname { margin: 14px 0 0; font-weight: 800; font-size: 23px; letter-spacing: -0.6px; color: var(--text); }
354402
.btv-pkg { display: inline-block; margin: 7px 0 0; font-family: var(--mono); font-size: 13px; color: var(--text-accent); background: none; padding: 0; }
355403
.btv-blurb { margin: 14px 0 0; min-height: 92px; font-size: 14.5px; line-height: 1.6; color: var(--muted); }
356404
.btv-points { list-style: none; margin: 18px 0 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
@@ -397,7 +445,9 @@ onMounted(() => {
397445
398446
@media (max-width: 880px) {
399447
.grid-3 { grid-template-columns: minmax(0, 1fr) !important; }
448+
.btv-prin, .btv-ai-grid, .btv-insp-grid { grid-template-columns: minmax(0, 1fr); }
400449
.btv-foot-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) !important; }
450+
.btv-ai-panel { padding: 34px 24px; }
401451
}
402452
@media (max-width: 560px) {
403453
.nav-hide { display: none !important; }

packages/theme/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"@types/node": "catalog:",
4141
"tsdown": "catalog:",
4242
"typescript": "catalog:",
43-
"vitepress": "catalog:"
43+
"vitepress": "catalog:",
44+
"vue": "^3.5.0"
4445
}
4546
}

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)