Clarity pass: glossary tooltips + first-time-reader reframe (Buckets 1 & 2) #1

Open
padreug wants to merge 4 commits from feedback-clarity-pass into main
Showing only changes of commit 2fac844863 - Show all commits

Add glossary definitions + soil/flowers copy to data.ts

Keeps the Bitcoin-native voice but adds a glossary layer for the
load-bearing jargon (stack, node, Lightning, Nostr, Cypherpunk,
mycelium, P&L) plus a glossedParts() tokenizer for inline tooltips.

Also the feedback-pass copy edits that live in the source of truth:
soil/flowers tagline + subtagline, Château du Faune located in
southern France, "build the stack once" names the it, venue rental
(was "venue buyouts"), real P&L spelled out, Lightning gloss + emoji.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Patrick Mulligan 2026-08-12 20:23:39 +02:00

View file

@ -24,9 +24,9 @@ export const brand = {
name: 'Lab 484', name: 'Lab 484',
short: 'Lab 484', short: 'Lab 484',
// Lab 484 — the tech co / shared backbone (also the 45% operating partner). // Lab 484 — the tech co / shared backbone (also the 45% operating partner).
tagline: 'One network. Three ways to experience it.', tagline: 'One living foundation. Many flowers.',
subtagline: subtagline:
'A sovereign network on shared, open infrastructure. The first node — SolLunar Society, a members club in Austin — is live, modeled and raising now; the summit gathering and the farm are next.', 'Lab 484 is the soil — a shared, open ecosystem of hardware + software. Three flowers grow from it: a members club in Austin (live and raising now), a regenerative farm in southern France, and pop-up gatherings that travel. Prove one, then replicate the blueprint anywhere.',
firstNode: 'SolLunar Society · Austin, TX', firstNode: 'SolLunar Society · Austin, TX',
contactEmail: 'team@l484.com', contactEmail: 'team@l484.com',
// Seam for later: an LNbits instance + the founder's nostr npub power the // Seam for later: an LNbits instance + the founder's nostr npub power the
@ -79,12 +79,12 @@ export const entities: Entity[] = [
blurb: blurb:
'The first node and the one were raising for: a members club in Austin where sovereignty-minded community — tenants, events, memberships and regenerative living — comes together year-round on the Lab 484 stack.', 'The first node and the one were raising for: a members club in Austin where sovereignty-minded community — tenants, events, memberships and regenerative living — comes together year-round on the Lab 484 stack.',
traits: [ traits: [
'Live, financially-modeled first node (real P&L)', 'Live, financially-modeled first node — real P&L',
'Five founding tenants + a three-tier membership', 'Five founding tenants + a three-tier membership',
'Bitcoin terminal, vending & the full member ecosystem on site', 'Bitcoin terminal, vending & the full member ecosystem on site',
], ],
spaces: ['Retail space', 'Event space', 'Private lounge', 'Co-working', 'Cypherpunk Commons hostel & data center'], spaces: ['Retail space', 'Event space', 'Private lounge', 'Co-working', 'Cypherpunk Commons hostel & data center'],
revenue: ['Memberships', 'Tenants', 'Events & venue buyouts'], revenue: ['Memberships', 'Tenants', 'Events & venue rental'],
icon: 'Building2', icon: 'Building2',
}, },
{ {
@ -109,12 +109,12 @@ export const entities: Entity[] = [
{ {
id: 'chateau', id: 'chateau',
name: 'Château du Faune', name: 'Château du Faune',
role: 'Farm center', role: 'Farm center · southern France',
kind: 'Off-grid · regenerative farm', kind: 'Off-grid regenerative farm · southern France',
accent: 'clay', accent: 'clay',
layer: 'surface', layer: 'surface',
blurb: blurb:
'The rural node: an off-grid regenerative farm hosting retreats, artist & developer residencies, and the networks living lab — home to the alpacas.', 'The rural node: an off-grid regenerative farm in southern France, hosting retreats, artist & developer residencies, and the networks living lab — home to the alpacas. Same Lab 484 stack as Austin, in a completely different environment.',
traits: [ traits: [
'Proves the stack runs off-grid, anywhere', 'Proves the stack runs off-grid, anywhere',
'Premium revenue from retreats & residencies', 'Premium revenue from retreats & residencies',
@ -132,7 +132,7 @@ export const entities: Entity[] = [
accent: 'pine', accent: 'pine',
layer: 'backbone', layer: 'backbone',
blurb: blurb:
'The invisible foundation every entity runs on: open hardware and self-hosted software — the mycelium beneath the whole network. Build it once, and every place plugs in.', 'The invisible foundation every entity runs on: open hardware and self-hosted software, built on decentralized protocols (Nostr + Bitcoin) — the mycelium beneath the whole network. Build the stack once, and every place plugs in.',
traits: [ traits: [
'Powers all three entities from one shared stack', 'Powers all three entities from one shared stack',
'Open bill of materials — replicable anywhere', 'Open bill of materials — replicable anywhere',
@ -164,8 +164,8 @@ export const userEcosystem = [
icon: 'Coins', icon: 'Coins',
}, },
{ {
title: 'Pay by Lightning', title: 'Pay by Lightning',
body: 'Tap to pay for drinks, tickets, rooms and experiences — instant, low-fee settlement.', body: 'Tap to pay for drinks, tickets, rooms and experiences — instant settlement, tiny fees.',
icon: 'Zap', icon: 'Zap',
}, },
{ {
@ -411,7 +411,7 @@ export const pnlGroups: PnlGroup[] = [
items: [ items: [
{ label: 'Lab 484 tech events', amount: 8_000 }, { label: 'Lab 484 tech events', amount: 8_000 },
{ label: 'Lab 484 bootcamps', amount: 6_000 }, { label: 'Lab 484 bootcamps', amount: 6_000 },
{ label: 'Venue buyouts', amount: 6_400, note: '$400/hr · 4hr min · 4×/mo' }, { label: 'Venue rental · private hire', amount: 6_400, note: '$400/hr · 4hr min · 4×/mo' },
{ label: 'SolLunar Society events', amount: 1_500, note: '$50 weekend passes' }, { label: 'SolLunar Society events', amount: 1_500, note: '$50 weekend passes' },
], ],
}, },
@ -552,6 +552,89 @@ export const brandCaseParts = (text: string) =>
.filter(Boolean) .filter(Boolean)
.map((t, i) => ({ id: i, text: t, keep: t === 'SolLunar' })) .map((t, i) => ({ id: i, text: t, keep: t === 'SolLunar' }))
/*
Glossary we keep the Bitcoin-native voice (this deck is for a
sovereignty-literate audience) but gloss the load-bearing jargon
inline: a dotted-underline term reveals a one-line definition and,
where useful, a "learn more" link so the reader can go deeper on
their own. Definitions live here; `glossedParts` splits any string
into plain text + term hits so `GlossedText` can render tooltips
without every slide rewriting its markup.
*/
export type GlossaryEntry = {
term: string
short: string
more?: { label: string; href: string }
}
/** Keyed by the lowercased canonical term. */
export const glossary: Record<string, GlossaryEntry> = {
stack: {
term: 'stack',
short:
'The shared hardware + software every location runs on — Lab 484s open backbone.',
},
node: {
term: 'node',
short:
'One location running the full Lab 484 stack. SolLunar Austin is the first node.',
},
lightning: {
term: 'Lightning',
short:
'Bitcoins instant-settlement layer — tap-to-pay speed with tiny fees.',
more: { label: 'lightning.network', href: 'https://lightning.network' },
},
nostr: {
term: 'Nostr',
short:
'An open protocol for identity & messaging you own — no platform can revoke it.',
more: { label: 'nostr.com', href: 'https://nostr.com' },
},
cypherpunk: {
term: 'Cypherpunk',
short:
'Using cryptography to defend individual privacy and autonomy from centralized control.',
more: { label: 'whats a cypherpunk?', href: 'https://en.wikipedia.org/wiki/Cypherpunk' },
},
mycelium: {
term: 'mycelium',
short:
'The underground fungal web that connects a forest — our metaphor for the shared backbone.',
},
'p&l': {
term: 'P&L',
short:
'Profit & loss — a businesss real operating income measured against its expenses.',
},
}
/** Aliases that resolve to a canonical glossary key. */
const glossaryAliases: Record<string, string> = { 'profit & loss': 'p&l' }
// Longest-first so multi-word aliases win the alternation (e.g. "profit & loss").
const glossaryPattern = [...Object.keys(glossary), ...Object.keys(glossaryAliases)]
.sort((a, b) => b.length - a.length)
.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
.join('|')
const glossaryRe = new RegExp(`(?<![A-Za-z0-9])(${glossaryPattern})(?![A-Za-z0-9])`, 'gi')
export type GlossedPart = { id: number; text: string; entry?: GlossaryEntry }
/**
* Split `text` into plain runs and glossary hits, so `GlossedText` can wrap the
* hits in a tooltip. Matching is case-insensitive and boundary-aware, so
* "node" won't fire inside "Noderunners".
*/
export const glossedParts = (text: string): GlossedPart[] =>
text
.split(glossaryRe)
.filter((t) => t !== '')
.map((t, id) => {
const key = glossaryAliases[t.toLowerCase()] ?? t.toLowerCase()
return { id, text: t, entry: glossary[key] }
})
/** Tailwind class helpers keyed by entity accent — keeps slides declarative. */ /** Tailwind class helpers keyed by entity accent — keeps slides declarative. */
export const accentText: Record<Accent, string> = { export const accentText: Record<Accent, string> = {
pine: 'text-pine-300', pine: 'text-pine-300',