From 2fac844863770ce534e7bc29dca29169be03a7ee Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Wed, 12 Aug 2026 20:23:39 +0200 Subject: [PATCH 1/4] Add glossary definitions + soil/flowers copy to data.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/deck/data.ts | 105 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 94 insertions(+), 11 deletions(-) diff --git a/src/deck/data.ts b/src/deck/data.ts index 8cda8fa..d5376f4 100644 --- a/src/deck/data.ts +++ b/src/deck/data.ts @@ -24,9 +24,9 @@ export const brand = { name: 'Lab 484', short: 'Lab 484', // 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: - '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', contactEmail: 'team@l484.com', // Seam for later: an LNbits instance + the founder's nostr npub power the @@ -79,12 +79,12 @@ export const entities: Entity[] = [ blurb: 'The first node and the one we’re 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: [ - 'Live, financially-modeled first node (real P&L)', + 'Live, financially-modeled first node — real P&L', 'Five founding tenants + a three-tier membership', 'Bitcoin terminal, vending & the full member ecosystem on site', ], 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', }, { @@ -109,12 +109,12 @@ export const entities: Entity[] = [ { id: 'chateau', name: 'Château du Faune', - role: 'Farm center', - kind: 'Off-grid · regenerative farm', + role: 'Farm center · southern France', + kind: 'Off-grid regenerative farm · southern France', accent: 'clay', layer: 'surface', blurb: - 'The rural node: an off-grid regenerative farm hosting retreats, artist & developer residencies, and the network’s living lab — home to the alpacas.', + 'The rural node: an off-grid regenerative farm in southern France, hosting retreats, artist & developer residencies, and the network’s living lab — home to the alpacas. Same Lab 484 stack as Austin, in a completely different environment.', traits: [ 'Proves the stack runs off-grid, anywhere', 'Premium revenue from retreats & residencies', @@ -132,7 +132,7 @@ export const entities: Entity[] = [ accent: 'pine', layer: 'backbone', 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: [ 'Powers all three entities from one shared stack', 'Open bill of materials — replicable anywhere', @@ -164,8 +164,8 @@ export const userEcosystem = [ icon: 'Coins', }, { - title: 'Pay by Lightning', - body: 'Tap to pay for drinks, tickets, rooms and experiences — instant, low-fee settlement.', + title: 'Pay by Lightning ⚡', + body: 'Tap to pay for drinks, tickets, rooms and experiences — instant settlement, tiny fees.', icon: 'Zap', }, { @@ -411,7 +411,7 @@ export const pnlGroups: PnlGroup[] = [ items: [ { label: 'Lab 484 tech events', amount: 8_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' }, ], }, @@ -552,6 +552,89 @@ 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.com' }, + }, + 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.', + }, + 'p&l': { + term: 'P&L', + short: + 'Profit & loss — a business’s real operating income measured against its expenses.', + }, +} + +/** 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', -- 2.53.0 From 1ee4d64cb19129241113e1e12b5e7bac4ea4c93a Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Wed, 12 Aug 2026 20:23:48 +0200 Subject: [PATCH 2/4] Add inline glossary tooltips across the deck MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-contained GlossaryTerm (dotted-underline, hover + tap, with an optional "learn more" link) and GlossedText that wraps glossary hits in any string. Wired into every slide subtitle via SlideFrame, plus the entity detail, ecosystem and tech card bodies where the jargon lives. Deliberately not portaled — the deck stage uses transforms + overflow, so a portaled popover would escape the themed .dark tree and lose its tokens; the card floats inline just below the term instead. TechSlide also surfaces the self-hosting line here (reviewer found it the clearest idea but it was buried on the building slide). Registers the Server icon for it. Co-Authored-By: Claude Opus 4.8 --- src/deck/components/EntityDetail.vue | 9 +-- src/deck/components/GlossaryTerm.vue | 96 ++++++++++++++++++++++++++++ src/deck/components/GlossedText.vue | 19 ++++++ src/deck/components/Icon.vue | 2 + src/deck/components/SlideFrame.vue | 3 +- src/deck/slides/EcosystemSlide.vue | 3 +- src/deck/slides/TechSlide.vue | 14 +++- 7 files changed, 138 insertions(+), 8 deletions(-) create mode 100644 src/deck/components/GlossaryTerm.vue create mode 100644 src/deck/components/GlossedText.vue diff --git a/src/deck/components/EntityDetail.vue b/src/deck/components/EntityDetail.vue index e9f7523..48b21b2 100644 --- a/src/deck/components/EntityDetail.vue +++ b/src/deck/components/EntityDetail.vue @@ -2,6 +2,7 @@ import SlideFrame from './SlideFrame.vue' import Icon from './Icon.vue' import PlaceholderBadge from './PlaceholderBadge.vue' +import GlossedText from './GlossedText.vue' import { accentText, accentRing, accentDot, type Entity } from '../data' const props = defineProps<{ entity: Entity }>() @@ -21,7 +22,7 @@ const a = props.entity.accent e.g. {{ entity.example }} -

{{ entity.blurb }}

+

@@ -34,7 +35,7 @@ const a = props.entity.accent - {{ t }} +
@@ -49,7 +50,7 @@ const a = props.entity.accent class="flex items-center gap-2 rounded-lg bg-surface px-3 py-2 text-sm text-fg" > - {{ s }} + @@ -69,7 +70,7 @@ const a = props.entity.accent
- Runs on Lab 484 — same membership, kiosk & app as every node. +
diff --git a/src/deck/components/GlossaryTerm.vue b/src/deck/components/GlossaryTerm.vue new file mode 100644 index 0000000..55fc99b --- /dev/null +++ b/src/deck/components/GlossaryTerm.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/src/deck/components/GlossedText.vue b/src/deck/components/GlossedText.vue new file mode 100644 index 0000000..cc79d07 --- /dev/null +++ b/src/deck/components/GlossedText.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/deck/components/Icon.vue b/src/deck/components/Icon.vue index e46ad45..a4edda9 100644 --- a/src/deck/components/Icon.vue +++ b/src/deck/components/Icon.vue @@ -32,6 +32,7 @@ import { ChevronRight, Images, Expand, + Server, type LucideProps, } from '@lucide/vue' import type { FunctionalComponent } from 'vue' @@ -68,6 +69,7 @@ const registry: Record> = { ChevronRight, Images, Expand, + Server, } const props = defineProps<{ name: string }>() diff --git a/src/deck/components/SlideFrame.vue b/src/deck/components/SlideFrame.vue index 57e4c21..37fe1b6 100644 --- a/src/deck/components/SlideFrame.vue +++ b/src/deck/components/SlideFrame.vue @@ -1,6 +1,7 @@ @@ -20,7 +21,7 @@ import { userEcosystem, surfaceEntities as entities } from '../data'

{{ item.title }}

-

{{ item.body }}

+

diff --git a/src/deck/slides/TechSlide.vue b/src/deck/slides/TechSlide.vue index 890cd19..ad044ea 100644 --- a/src/deck/slides/TechSlide.vue +++ b/src/deck/slides/TechSlide.vue @@ -1,6 +1,7 @@ @@ -8,7 +9,7 @@ import { tech } from '../data'

{{ t.title }}

-

{{ t.body }}

+

+ + +
+ + + Every node self-hosts — the stack runs on-site, + on the network’s own hardware, not in someone else’s cloud. + +
-- 2.53.0 From 1edfa8c9c3ca7ad361e0e900646305956c0a9dca Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Wed, 12 Aug 2026 20:23:59 +0200 Subject: [PATCH 3/4] Reframe deck for a first-time reader (Bucket 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Title: "One soil, three flowers" not "3-in-1" — spells out the So·lL·unar wordmark, labels each flower with its type + place (incl. the French farm), and shows a "future flowers" slot. Entities slide echoes the metaphor and names the southern-France environment. Ask: "Choose your way in" with numbered Option 1 / Option 2, framing it as a decision rather than a menu. Roadmap: unpacks the "blueprint is the business" riddle. Memberships: reframed as three recurring-revenue streams with revenue-at-target per tier. Building: literal front/back layout. P&L: adds a "Tap to expand" affordance so the breakdown is discoverable. Co-Authored-By: Claude Opus 4.8 --- src/deck/slides/AskSlide.vue | 16 +++++++---- src/deck/slides/EntitiesSlide.vue | 4 +-- src/deck/slides/MembershipSlide.vue | 21 +++++++++----- src/deck/slides/PnlSlide.vue | 3 ++ src/deck/slides/RoadmapSlide.vue | 2 +- src/deck/slides/SpacesSlide.vue | 2 +- src/deck/slides/TitleSlide.vue | 43 +++++++++++++++++++++++------ 7 files changed, 66 insertions(+), 25 deletions(-) diff --git a/src/deck/slides/AskSlide.vue b/src/deck/slides/AskSlide.vue index 4b5fe19..3816549 100644 --- a/src/deck/slides/AskSlide.vue +++ b/src/deck/slides/AskSlide.vue @@ -4,15 +4,20 @@ import Icon from '../components/Icon.vue' import { brand, investorAsk, pnl, fmtUsd } from '../data' const pct = (n: number) => Math.round(n * 100) -// annual profit share per option, on the recomputed yearly profit -const options = investorAsk.options.map((o) => ({ ...o, annual: Math.round(pnl.yearlyProfit * o.equity) })) +// annual profit share per option, on the recomputed yearly profit. +// Number them so the ask reads as a choice ("which way in?"), not a menu of two. +const options = investorAsk.options.map((o, i) => ({ + ...o, + choice: `Option ${i + 1}`, + annual: Math.round(pnl.yearlyProfit * o.equity), +}))