diff --git a/src/deck/data.ts b/src/deck/data.ts index 5dc328f..29439b6 100644 --- a/src/deck/data.ts +++ b/src/deck/data.ts @@ -556,6 +556,84 @@ export const brandCaseParts = (text: string) => .filter(Boolean) .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 = { + stack: { + term: 'stack', + short: + 'The shared hardware + software every location runs on — Lab 484’s open backbone.', + }, + node: { + term: 'node', + short: + 'One location running the full Lab 484 stack. SolLunar Austin is the first node.', + }, + lightning: { + term: 'Lightning', + short: + 'Bitcoin’s 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.how' }, + }, + cypherpunk: { + term: 'Cypherpunk', + short: + 'Using cryptography to defend individual privacy and autonomy from centralized control.', + more: { label: 'what’s 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.', + }, +} + +/** Aliases that resolve to a canonical glossary key. */ +const glossaryAliases: Record = { '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(`(? + 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. */ export const accentText: Record = { pine: 'text-pine-300',