Compare commits

..

4 commits

Author SHA1 Message Date
Patrick Mulligan
e6c3d5d1ee Sync PITCH-CONTENT.md with the clarity pass
Mirrors the deck copy: soil/flowers framing + wordmark note, southern
France for Château, self-hosting line, venue rental, real P&L, Lightning
gloss, choose-your-way-in ask, unpacked blueprint line, and the
memberships revenue view — including a flagged reconcile-before-sending
note on the Lunarpunk target vs. its conservative P&L amount.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-12 20:24:07 +02:00
Patrick Mulligan
1edfa8c9c3 Reframe deck for a first-time reader (Bucket 2)
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 <noreply@anthropic.com>
2026-08-12 20:23:59 +02:00
Patrick Mulligan
1ee4d64cb1 Add inline glossary tooltips across the deck
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 <noreply@anthropic.com>
2026-08-12 20:23:48 +02:00
Patrick Mulligan
2fac844863 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>
2026-08-12 20:23:39 +02:00
16 changed files with 353 additions and 77 deletions

View file

@ -7,8 +7,13 @@
> **Brand note:** *SolLunar* is the cover brand (the venture / the raise). > **Brand note:** *SolLunar* is the cover brand (the venture / the raise).
> *Lab 484* is the shared tech backbone (open hardware + sovereign software) > *Lab 484* is the shared tech backbone (open hardware + sovereign software)
> **and** a 45% operating partner. *SolLunar Punk Society (SPS)* is the other > **and** a 45% operating partner. *SolLunar Punk Society (SPS)* is the other
> 45% partner (space + community). Tagline: *One network. Three ways to > 45% partner (space + community). Tagline: *One living foundation. Many
> experience it.* / *Energy · Intelligence · Harmony.* > flowers.* / *Energy · Intelligence · Harmony.*
>
> **Core metaphor:** Lab 484 is the *soil* — a shared hardware + software
> ecosystem (built on decentralized protocols: Nostr + Bitcoin). Three *flowers*
> grow from it: **City** (SolLunar, Austin), **Farm** (Château du Faune, southern
> France) and **Pop-up** (travelling summit gatherings) — with room for more.
--- ---
@ -16,13 +21,15 @@
**SolLunar** — Investment prospectus (draft) **SolLunar** — Investment prospectus (draft)
**One network. Three ways to experience it.** **One living foundation. Many flowers.**
A sovereign network on shared, open infrastructure. The first node — SolLunar Lab 484 is the soil — a shared, open ecosystem of hardware + software. Three
Society, a members' club in Austin — is live, modeled and raising now; the flowers grow from it: a members' club in Austin (live and raising now), a
summit gathering and the farm are next. regenerative farm in southern France, and pop-up gatherings that travel. Prove
one, then replicate the blueprint anywhere.
- **Three experiences, one backbone:** SolLunar Society · Pop-up Summit Gatherings · Château du Faune - **Lab 484 is the soil — three flowers grow from it:** SolLunar Society (City · Austin) · Pop-up Summit Gatherings (Travelling) · Château du Faune (Farm · S. France) · + future flowers
- **Wordmark:** the mark is *So·lL·unar* (the lL ligature, not a numeral).
- **Raising now · SolLunar Society · Austin, TX** - **Raising now · SolLunar Society · Austin, TX**
--- ---
@ -61,9 +68,12 @@ Summit Gatherings** (travelling), **Château du Faune** (farm) — plus faint
## 4 · Lab 484 · the shared backbone ## 4 · Lab 484 · the shared backbone
**Open hardware, sovereign software.** Lab 484 is the stack under every entity **Open hardware, sovereign software.** Lab 484 is the stack under every entity —
— owned by the network, documented to be rebuilt anywhere. Build it once; every the shared hardware + software every location runs on, owned by the network and
place plugs in. documented to be rebuilt anywhere. Build the stack once; every place plugs in.
*Every node self-hosts — the stack runs on-site, on the network's own hardware,
not in someone else's cloud.*
- **Decentralized mesh network** — Community-owned routers and mesh kits blanket each location; connectivity no single provider can switch off. - **Decentralized mesh network** — Community-owned routers and mesh kits blanket each location; connectivity no single provider can switch off.
- **Open hardware** — Servers, routers, exchange kiosks and near-field communication (NFC) hardware from a documented, replicable bill of materials. - **Open hardware** — Servers, routers, exchange kiosks and near-field communication (NFC) hardware from a documented, replicable bill of materials.
@ -74,18 +84,20 @@ place plugs in.
--- ---
## 5 · Three experiences, one backbone ## 5 · One soil, three flowers
*One is funded and opening now — SolLunar Society in Austin. The summit *One flower is funded and opening now — SolLunar Society in Austin. A
gathering and the farm follow, on the same Lab 484 stack.* regenerative farm in southern France and travelling pop-up gatherings follow —
the same Lab 484 stack, proven in three very different environments.*
- **SolLunar Society** — City center · the first node (Members' club & community hub · Austin, TX). *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.* — **Live P&L · this raise.** - **SolLunar Society** — City center · the first node (Members' club & community hub · Austin, TX). *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.* — **Live P&L · this raise.**
- **Pop-up Summit Gatherings** — Travelling gatherings (e.g. Solar Punk Summit). *A repeatable summit-gathering format that deploys the entire stack anywhere for a few days, then packs down and moves on.* — Vision · on the roadmap. - **Pop-up Summit Gatherings** — Travelling gatherings (e.g. Solar Punk Summit). *A repeatable summit-gathering format that deploys the entire stack anywhere for a few days, then packs down and moves on.* — Vision · on the roadmap.
- **Château du Faune** — Farm center (off-grid · regenerative farm). *An off-grid regenerative farm hosting retreats, artist & developer residencies, and the network's living lab — home to the alpacas.* — Vision · on the roadmap. - **Château du Faune** — Farm center · southern France (off-grid regenerative farm). *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.* — Vision · on the roadmap.
**Beneath all three — Lab 484 · Shared backbone:** the invisible foundation every **Beneath all three — Lab 484 · Shared backbone:** the invisible foundation every
entity runs on: open hardware and self-hosted software — the mycelium beneath the entity runs on: open hardware and self-hosted software, built on decentralized
whole network. Build it once, and every place plugs in. protocols (Nostr + Bitcoin) — the mycelium beneath the whole network. Build the
stack once, and every place plugs in.
--- ---
@ -95,12 +107,12 @@ whole network. Build it once, and every place plugs in.
we're raising for. we're raising for.
**What makes it unique** **What makes it unique**
- Live, financially-modeled first node (real P&L) - Live, financially-modeled first node — real P&L (profit & loss)
- 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
**How it earns:** Memberships · Tenants · Events & venue buyouts **How it earns:** Memberships · Tenants · Events & venue rental
--- ---
@ -140,15 +152,23 @@ reason to become a member.
--- ---
## 9 · Memberships — three ways to belong ## 9 · Memberships — three recurring-revenue streams
Recurring membership is the core of the model — priced right for the Austin market. Memberships are the model's core revenue engine — three tiers, each a
predictable monthly stream. Below each tier is its revenue *at target*, priced
against the Austin market. *(This is a revenue view for investors, not a
membership sales pitch.)*
| Tier | Price / mo | Target members | Includes | | Tier | Price / mo | Target members | Revenue at target | Includes |
| --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| **Solarpunk Society** | $48 | 150 | Member events; amenities (cold plunge, sauna & gym); vending & Bitcoin terminal; access to tenants | | **Solarpunk Society** | $48 | 150 | ≈ $7,200/mo | Member events; amenities (cold plunge, sauna & gym); vending & Bitcoin terminal; access to tenants |
| **Lunarpunk Locals** | $148 | 100 | Coworking days at SolLunar; amenities (cold plunge, sauna & gym); vending & Bitcoin terminal; access to tenants | | **Lunarpunk Locals** | $148 | 100 | ≈ $14,800/mo | Coworking days at SolLunar; amenities (cold plunge, sauna & gym); vending & Bitcoin terminal; access to tenants |
| **Cypherpunk Commons** *(flagship)* | $348 | 50 | Coworking with the Lab 484 team; all locations · tech support; cold plunge, sauna, gym & coworking; vending, coffee, tea & Bitcoin terminal; full tenant & amenity access | | **Cypherpunk Commons** *(flagship)* | $348 | 50 | ≈ $17,400/mo | Coworking with the Lab 484 team; all locations · tech support; cold plunge, sauna, gym & coworking; vending, coffee, tea & Bitcoin terminal; full tenant & amenity access |
> ⚠️ **Reconcile before sending:** the P&L (slide 13) conservatively models
> Lunarpunk Locals at only **$1,500/mo** ("count TBD"), not the $14,800/mo its
> 100-member target implies. Decide the real Lunarpunk target and align both
> slides.
**Austin comparables:** Soho House $350 · Bond Collective $300 · Bathe $200 · Stardust Garage $155 · Submersive $41 **Austin comparables:** Soho House $350 · Bond Collective $300 · Bathe $200 · Stardust Garage $155 · Submersive $41
@ -156,7 +176,7 @@ Recurring membership is the core of the model — priced right for the Austin ma
## 10 · The building — one address, three buildings ## 10 · The building — one address, three buildings
A members' society up front, Cypherpunk Commons A & B behind — lodging, coworking A members' club building in front, with Cypherpunk Commons A & B in the back — lodging, coworking
and the data center that runs it all. *The Cypherpunk Commons data center makes and the data center that runs it all. *The Cypherpunk Commons data center makes
SolLunar self-hosting — the Lab 484 stack runs on-site, not in someone else's cloud.* SolLunar self-hosting — the Lab 484 stack runs on-site, not in someone else's cloud.*
@ -182,7 +202,7 @@ money, your identity travel with you.
- **One membership card** — A single NFC card is your identity, access and wallet everywhere. - **One membership card** — A single NFC card is your identity, access and wallet everywhere.
- **Money exchange kiosk** — On-ramp cash to Bitcoin and back at any location. The same kiosk, everywhere. - **Money exchange kiosk** — On-ramp cash to Bitcoin and back at any location. The same kiosk, everywhere.
- **Pay by Lightning** — Tap to pay for drinks, tickets, rooms and experiences — instant, low-fee settlement. - **Pay by Lightning** — Tap to pay for drinks, tickets, rooms and experiences — instant settlement, tiny fees. *(Lightning = Bitcoin's instant-settlement layer.)*
- **The Lab 484 app** — Book spaces, buy tickets, find events and manage membership from one self-hosted app. - **The Lab 484 app** — Book spaces, buy tickets, find events and manage membership from one self-hosted app.
- **Portable identity** — Your nostr identity and social graph travel with you between every node and beyond. - **Portable identity** — Your nostr identity and social graph travel with you between every node and beyond.
- **Member perks** — Access, discounts and priority booking that follow you across the whole network. - **Member perks** — Access, discounts and priority booking that follow you across the whole network.
@ -213,7 +233,7 @@ stated $71.4k rev / $38.5k exp, which didn't reconcile — see the caveat below.
**Revenue — Events ($21,900/mo)** **Revenue — Events ($21,900/mo)**
- Lab 484 tech events — $8,000 - Lab 484 tech events — $8,000
- Lab 484 bootcamps — $6,000 - Lab 484 bootcamps — $6,000
- Venue buyouts — $6,400 ($400/hr · 4hr min · 4×/mo) - Venue rental · private hire — $6,400 ($400/hr · 4hr min · 4×/mo)
- SolLunar Society events — $1,500 ($50 weekend passes) - SolLunar Society events — $1,500 ($50 weekend passes)
**Revenue — Tenants ($7,000/mo)** **Revenue — Tenants ($7,000/mo)**
@ -240,7 +260,8 @@ stated $71.4k rev / $38.5k exp, which didn't reconcile — see the caveat below.
## 14 · Roadmap — the replication play ## 14 · Roadmap — the replication play
**Prove it three ways, then grow it anywhere.** The three entities aren't the **Prove it three ways, then grow it anywhere.** The three entities aren't the
endgame — they're the seed. The blueprint is the business. endgame — they're the proof. The real business is the replicable blueprint:
prove it once, then open new locations on the same backbone at marginal cost.
- **Phase 0 · Now — Raise & open SolLunar:** Close the $400k round, sign the lease, fit out the space and onboard the founding tenants. - **Phase 0 · Now — Raise & open SolLunar:** Close the $400k round, sign the lease, fit out the space and onboard the founding tenants.
- **Phase 1 · ~012 mo — Operate & prove the model:** Run memberships, events and tenants in Austin; validate the P&L publicly on the Lab 484 stack. - **Phase 1 · ~012 mo — Operate & prove the model:** Run memberships, events and tenants in Austin; validate the P&L publicly on the Lab 484 stack.
@ -251,14 +272,15 @@ endgame — they're the seed. The blueprint is the business.
--- ---
## 15 · The ask — two ways in ## 15 · The ask — choose your way in
Invest in SolLunar Society — the first node, opening in Austin. Invest in SolLunar Society — the first node, opening in Austin. Pick the entry
that fits.
| Option | Amount | Equity | Annual profit share¹ | What it funds | | Option | Amount | Equity | Annual profit share¹ | What it funds |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| **Get started** | $400k | 10% | ~$37,200 / yr | Seeds the launch — sign the lease, fit out the space and open the doors. | | **Option 1 · Get started** | $400k | 10% | ~$37,200 / yr | Seeds the launch — sign the lease, fit out the space and open the doors. |
| **Full execution** *(full build)* | $900k | 25% | ~$93,000 / yr | Funds the full build-out and the first operating year end to end. | | **Option 2 · Full execution** *(full build)* | $900k | 25% | ~$93,000 / yr | Funds the full build-out and the first operating year end to end. |
¹ On SolLunar Society's estimated ~$372,000/yr node profit. ¹ On SolLunar Society's estimated ~$372,000/yr node profit.

View file

@ -2,6 +2,7 @@
import SlideFrame from './SlideFrame.vue' import SlideFrame from './SlideFrame.vue'
import Icon from './Icon.vue' import Icon from './Icon.vue'
import PlaceholderBadge from './PlaceholderBadge.vue' import PlaceholderBadge from './PlaceholderBadge.vue'
import GlossedText from './GlossedText.vue'
import { accentText, accentRing, accentDot, type Entity } from '../data' import { accentText, accentRing, accentDot, type Entity } from '../data'
const props = defineProps<{ entity: Entity }>() const props = defineProps<{ entity: Entity }>()
@ -21,7 +22,7 @@ const a = props.entity.accent
<span v-if="entity.example" class="text-sm text-fg-muted">e.g. {{ entity.example }}</span> <span v-if="entity.example" class="text-sm text-fg-muted">e.g. {{ entity.example }}</span>
<PlaceholderBadge label="name" /> <PlaceholderBadge label="name" />
</div> </div>
<p class="mt-4 max-w-2xl text-lg text-fg-muted">{{ entity.blurb }}</p> <p class="mt-4 max-w-2xl text-lg text-fg-muted"><GlossedText :text="entity.blurb" /></p>
<div class="mt-8 grid gap-6 lg:grid-cols-3"> <div class="mt-8 grid gap-6 lg:grid-cols-3">
<!-- unique traits --> <!-- unique traits -->
@ -34,7 +35,7 @@ const a = props.entity.accent
<span class="mt-0.5 grid size-5 shrink-0 place-items-center rounded-full" :class="accentRing[a]"> <span class="mt-0.5 grid size-5 shrink-0 place-items-center rounded-full" :class="accentRing[a]">
<Icon name="Check" class="size-3" :class="accentText[a]" /> <Icon name="Check" class="size-3" :class="accentText[a]" />
</span> </span>
{{ t }} <GlossedText :text="t" />
</li> </li>
</ul> </ul>
</div> </div>
@ -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" class="flex items-center gap-2 rounded-lg bg-surface px-3 py-2 text-sm text-fg"
> >
<span class="size-1.5 rounded-full" :class="accentDot[a]" /> <span class="size-1.5 rounded-full" :class="accentDot[a]" />
{{ s }} <GlossedText :text="s" />
</div> </div>
</div> </div>
</div> </div>
@ -69,7 +70,7 @@ const a = props.entity.accent
</div> </div>
<div class="flex items-center gap-2 rounded-2xl border border-line bg-surface px-4 py-3 text-xs text-fg-muted"> <div class="flex items-center gap-2 rounded-2xl border border-line bg-surface px-4 py-3 text-xs text-fg-muted">
<Icon name="Layers" class="size-4 text-amber" /> <Icon name="Layers" class="size-4 text-amber" />
Runs on Lab 484 same membership, kiosk &amp; app as every node. <GlossedText text="Runs on Lab 484 — same membership, kiosk & app as every node." />
</div> </div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,96 @@
<script setup lang="ts">
/**
* An inline glossary term: dotted-underline trigger that reveals a one-line
* definition (and an optional "learn more" link) on hover or tap.
*
* Deliberately dependency-free the deck stage uses transforms + overflow, so
* a portaled popover would escape the themed `.dark` tree and lose its tokens.
* We render the card inline instead and let it float just below the term.
*/
import { ref, onBeforeUnmount } from 'vue'
import { ExternalLink } from '@lucide/vue'
import type { GlossaryEntry } from '../data'
defineProps<{ entry: GlossaryEntry }>()
const open = ref(false)
const root = ref<HTMLElement | null>(null)
let hoverTimer: ReturnType<typeof setTimeout> | undefined
function show() {
clearTimeout(hoverTimer)
open.value = true
}
function hideSoon() {
// small grace period so the reader can travel from term to card
hoverTimer = setTimeout(() => (open.value = false), 120)
}
function toggle() {
open.value = !open.value
}
function onKey(e: KeyboardEvent) {
if (e.key === 'Escape') open.value = false
}
// close when tapping elsewhere (mobile has no hover-out)
function onDocPointer(e: PointerEvent) {
if (open.value && root.value && !root.value.contains(e.target as Node)) open.value = false
}
if (typeof document !== 'undefined') document.addEventListener('pointerdown', onDocPointer)
onBeforeUnmount(() => {
clearTimeout(hoverTimer)
if (typeof document !== 'undefined') document.removeEventListener('pointerdown', onDocPointer)
})
</script>
<template>
<span ref="root" class="relative inline-block" @mouseenter="show" @mouseleave="hideSoon">
<button
type="button"
class="cursor-help font-medium text-fg underline decoration-amber-400/70 decoration-dotted underline-offset-4 transition hover:decoration-amber-400"
:aria-expanded="open"
@click="toggle"
@keydown="onKey"
>
<slot>{{ entry.term }}</slot>
</button>
<Transition name="gloss">
<span
v-if="open"
class="absolute left-1/2 top-full z-30 mt-2 block w-64 -translate-x-1/2 cursor-auto rounded-xl border border-line-2 bg-surface-2 p-3 text-left text-sm font-normal normal-case leading-relaxed text-fg-muted shadow-xl"
@mouseenter="show"
@mouseleave="hideSoon"
>
<span class="mb-1 block font-display text-xs font-semibold uppercase tracking-wider text-amber">
{{ entry.term }}
</span>
{{ entry.short }}
<a
v-if="entry.more"
:href="entry.more.href"
target="_blank"
rel="noopener noreferrer"
class="mt-2 inline-flex items-center gap-1 text-xs font-medium text-indigo transition hover:text-indigo-300"
>
{{ entry.more.label }}
<ExternalLink class="size-3" />
</a>
</span>
</Transition>
</span>
</template>
<style scoped>
.gloss-enter-active,
.gloss-leave-active {
transition:
opacity 0.15s ease,
transform 0.15s ease;
}
.gloss-enter-from,
.gloss-leave-to {
opacity: 0;
transform: translate(-50%, -4px);
}
</style>

View file

@ -0,0 +1,19 @@
<script setup lang="ts">
/**
* Renders a string with any glossary terms turned into inline tooltips.
* Drop-in for plain interpolation: `<GlossedText :text="subtitle" />`.
*/
import GlossaryTerm from './GlossaryTerm.vue'
import { glossedParts } from '../data'
defineProps<{ text: string }>()
</script>
<template>
<span
><template v-for="p in glossedParts(text)" :key="p.id"
><GlossaryTerm v-if="p.entry" :entry="p.entry">{{ p.text }}</GlossaryTerm
><template v-else>{{ p.text }}</template></template
></span
>
</template>

View file

@ -32,6 +32,7 @@ import {
ChevronRight, ChevronRight,
Images, Images,
Expand, Expand,
Server,
type LucideProps, type LucideProps,
} from '@lucide/vue' } from '@lucide/vue'
import type { FunctionalComponent } from 'vue' import type { FunctionalComponent } from 'vue'
@ -68,6 +69,7 @@ const registry: Record<string, FunctionalComponent<LucideProps>> = {
ChevronRight, ChevronRight,
Images, Images,
Expand, Expand,
Server,
} }
const props = defineProps<{ name: string }>() const props = defineProps<{ name: string }>()

View file

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
/** Shared slide chrome: consistent padding, kicker, title, subtitle + body slot. */ /** Shared slide chrome: consistent padding, kicker, title, subtitle + body slot. */
import { brandCaseParts } from '../data' import { brandCaseParts } from '../data'
import GlossedText from './GlossedText.vue'
defineProps<{ defineProps<{
kicker?: string kicker?: string
@ -32,7 +33,7 @@ defineProps<{
class="mt-4 max-w-2xl text-base text-fg-muted sm:text-lg" class="mt-4 max-w-2xl text-base text-fg-muted sm:text-lg"
:class="center ? 'mx-auto' : ''" :class="center ? 'mx-auto' : ''"
> >
{{ subtitle }} <GlossedText :text="subtitle" />
</p> </p>
<div :class="[title || kicker ? 'mt-10' : '', 'w-full']"> <div :class="[title || kicker ? 'mt-10' : '', 'w-full']">
<slot /> <slot />

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',

View file

@ -4,15 +4,20 @@ import Icon from '../components/Icon.vue'
import { brand, investorAsk, pnl, fmtUsd } from '../data' import { brand, investorAsk, pnl, fmtUsd } from '../data'
const pct = (n: number) => Math.round(n * 100) const pct = (n: number) => Math.round(n * 100)
// annual profit share per option, on the recomputed yearly profit // annual profit share per option, on the recomputed yearly profit.
const options = investorAsk.options.map((o) => ({ ...o, annual: Math.round(pnl.yearlyProfit * o.equity) })) // 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),
}))
</script> </script>
<template> <template>
<SlideFrame center> <SlideFrame center>
<p class="text-xs font-semibold uppercase tracking-[0.3em] text-amber">The ask</p> <p class="text-xs font-semibold uppercase tracking-[0.3em] text-amber">The ask</p>
<h2 class="mt-4 font-display text-4xl font-semibold text-fg sm:text-5xl">Two ways in</h2> <h2 class="mt-4 font-display text-4xl font-semibold text-fg sm:text-5xl">Choose your way in</h2>
<p class="mt-3 text-fg-muted">Invest in SolLunar Society the first node, opening in Austin.</p> <p class="mt-3 text-fg-muted">Invest in SolLunar Society the first node, opening in Austin. Pick the entry that fits.</p>
<!-- the two options --> <!-- the two options -->
<div class="mx-auto mt-9 grid w-full max-w-2xl gap-5 sm:grid-cols-2"> <div class="mx-auto mt-9 grid w-full max-w-2xl gap-5 sm:grid-cols-2">
@ -29,9 +34,10 @@ const options = investorAsk.options.map((o) => ({ ...o, annual: Math.round(pnl.y
Full build Full build
</span> </span>
<p <p
class="text-xs font-semibold uppercase tracking-wider" class="flex items-center gap-2 text-xs font-semibold uppercase tracking-wider"
:class="o.featured ? 'text-indigo' : 'text-fg-muted'" :class="o.featured ? 'text-indigo' : 'text-fg-muted'"
> >
<span class="rounded-full border border-current px-2 py-0.5">{{ o.choice }}</span>
{{ o.label }} {{ o.label }}
</p> </p>
<div class="mt-1 flex items-baseline gap-2"> <div class="mt-1 flex items-baseline gap-2">

View file

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import SlideFrame from '../components/SlideFrame.vue' import SlideFrame from '../components/SlideFrame.vue'
import Icon from '../components/Icon.vue' import Icon from '../components/Icon.vue'
import GlossedText from '../components/GlossedText.vue'
import { userEcosystem, surfaceEntities as entities } from '../data' import { userEcosystem, surfaceEntities as entities } from '../data'
</script> </script>
@ -20,7 +21,7 @@ import { userEcosystem, surfaceEntities as entities } from '../data'
<Icon :name="item.icon" class="size-5" /> <Icon :name="item.icon" class="size-5" />
</span> </span>
<h3 class="mt-4 font-display text-lg font-semibold text-fg">{{ item.title }}</h3> <h3 class="mt-4 font-display text-lg font-semibold text-fg">{{ item.title }}</h3>
<p class="mt-1.5 text-sm leading-relaxed text-fg-muted">{{ item.body }}</p> <p class="mt-1.5 text-sm leading-relaxed text-fg-muted"><GlossedText :text="item.body" /></p>
</div> </div>
</div> </div>

View file

@ -9,8 +9,8 @@ const backbone = entityById('lab484')
<template> <template>
<SlideFrame <SlideFrame
kicker="The collaboration" kicker="The collaboration"
title="Three experiences, one backbone" title="One soil, three flowers"
subtitle="One is funded and opening now — SolLunar Society in Austin. The summit gathering and the farm follow, on the same Lab 484 stack." subtitle="One flower is funded and opening now — SolLunar Society in Austin. A regenerative farm in southern France and travelling pop-up gatherings follow — the same Lab 484 stack, proven in three very different environments."
> >
<div class="grid gap-5 md:grid-cols-3"> <div class="grid gap-5 md:grid-cols-3">
<div <div

View file

@ -3,15 +3,18 @@ import SlideFrame from '../components/SlideFrame.vue'
import Icon from '../components/Icon.vue' import Icon from '../components/Icon.vue'
import { memberships, membershipComparables, fmtUsd } from '../data' import { memberships, membershipComparables, fmtUsd } from '../data'
// display low high price // display low high price; carry each tier's monthly revenue at its member target
const tiers = [...memberships].sort((a, b) => a.price - b.price) // so the slide reads as three recurring-revenue streams, not a "buy a membership" pitch
const tiers = [...memberships]
.map((t) => ({ ...t, atTarget: t.price * t.target }))
.sort((a, b) => a.price - b.price)
</script> </script>
<template> <template>
<SlideFrame <SlideFrame
kicker="SolLunar Society · memberships" kicker="SolLunar Society · recurring revenue"
title="Three ways to belong" title="Three recurring-revenue streams"
subtitle="Recurring membership is the core of the model — priced right for the Austin market." subtitle="Memberships are the model's core revenue engine — three tiers, each a predictable monthly stream. Below each tier is its revenue at target, priced against the Austin market."
> >
<div class="grid gap-5 md:grid-cols-3"> <div class="grid gap-5 md:grid-cols-3">
<div <div
@ -30,9 +33,13 @@ const tiers = [...memberships].sort((a, b) => a.price - b.price)
<h3 class="font-display text-xl font-semibold text-fg">{{ t.name }}</h3> <h3 class="font-display text-xl font-semibold text-fg">{{ t.name }}</h3>
<div class="mt-2 flex items-baseline gap-1"> <div class="mt-2 flex items-baseline gap-1">
<span class="font-display text-4xl font-bold text-fg">{{ fmtUsd(t.price) }}</span> <span class="font-display text-4xl font-bold text-fg">{{ fmtUsd(t.price) }}</span>
<span class="text-sm text-fg-muted">/mo</span> <span class="text-sm text-fg-muted">/mo · per member</span>
</div> </div>
<p class="mt-1 text-xs text-fg-muted">{{ t.blurb }} · target {{ t.target }} members</p> <p class="mt-2 font-display text-sm font-semibold text-indigo">
{{ fmtUsd(t.atTarget) }}/mo
<span class="font-sans font-normal text-fg-muted">revenue at {{ t.target }} members</span>
</p>
<p class="mt-1 text-xs text-fg-muted">{{ t.blurb }}</p>
<ul class="mt-5 flex-1 space-y-2"> <ul class="mt-5 flex-1 space-y-2">
<li v-for="p in t.perks" :key="p" class="flex items-start gap-2 text-sm text-fg-muted"> <li v-for="p in t.perks" :key="p" class="flex items-start gap-2 text-sm text-fg-muted">

View file

@ -138,6 +138,9 @@ const revPct = (id: string) =>
<span class="shrink-0 font-display text-lg font-semibold" :class="text[g.accent]"> <span class="shrink-0 font-display text-lg font-semibold" :class="text[g.accent]">
{{ fmtUsd(groupTotal(g)) }} {{ fmtUsd(groupTotal(g)) }}
</span> </span>
<span class="hidden shrink-0 text-[10px] uppercase tracking-wider text-fg-muted/70 sm:inline">
{{ selected === g.id ? 'Hide' : 'Tap to expand' }}
</span>
<Icon name="ChevronRight" class="size-4 shrink-0 text-fg-muted transition-transform" :class="selected === g.id ? 'rotate-90' : ''" /> <Icon name="ChevronRight" class="size-4 shrink-0 text-fg-muted transition-transform" :class="selected === g.id ? 'rotate-90' : ''" />
</button> </button>
</div> </div>

View file

@ -7,7 +7,7 @@ import { roadmap } from '../data'
<SlideFrame <SlideFrame
kicker="The replication play" kicker="The replication play"
title="Prove it three ways, then grow it anywhere" title="Prove it three ways, then grow it anywhere"
subtitle="The three entities aren't the endgame — they're the seed. The blueprint is the business." subtitle="The three entities aren't the endgame — they're the proof. The real business is the replicable blueprint: prove it once, then open new locations on the same backbone at marginal cost."
> >
<ol class="relative grid gap-6 md:grid-cols-4"> <ol class="relative grid gap-6 md:grid-cols-4">
<!-- connecting line --> <!-- connecting line -->

View file

@ -10,7 +10,7 @@ const icons = ['Building2', 'BedDouble', 'Cpu']
<SlideFrame <SlideFrame
kicker="SolLunar Society · the building" kicker="SolLunar Society · the building"
title="One address, three buildings" title="One address, three buildings"
subtitle="A members' society up front, Cypherpunk Commons A & B behind — lodging, coworking and the data center that runs it all." subtitle="A members' club building in front, with Cypherpunk Commons A & B in the back — lodging, coworking and the data center that runs it all."
> >
<div class="grid gap-4 md:grid-cols-3"> <div class="grid gap-4 md:grid-cols-3">
<div <div

View file

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import SlideFrame from '../components/SlideFrame.vue' import SlideFrame from '../components/SlideFrame.vue'
import Icon from '../components/Icon.vue' import Icon from '../components/Icon.vue'
import GlossedText from '../components/GlossedText.vue'
import { tech } from '../data' import { tech } from '../data'
</script> </script>
@ -8,7 +9,7 @@ import { tech } from '../data'
<SlideFrame <SlideFrame
kicker="Lab 484 · the shared backbone" kicker="Lab 484 · the shared backbone"
title="Open hardware, sovereign software" title="Open hardware, sovereign software"
subtitle="Lab 484 is the stack under every entity — owned by the network, documented to be rebuilt anywhere. Build it once; every place plugs in." subtitle="Lab 484 is the stack under every entity — the shared hardware + software every location runs on, owned by the network and documented to be rebuilt anywhere. Build the stack once; every place plugs in."
> >
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<div <div
@ -20,8 +21,17 @@ import { tech } from '../data'
<Icon :name="t.icon" class="size-5" /> <Icon :name="t.icon" class="size-5" />
</span> </span>
<h3 class="mt-4 font-display text-lg font-semibold text-fg">{{ t.title }}</h3> <h3 class="mt-4 font-display text-lg font-semibold text-fg">{{ t.title }}</h3>
<p class="mt-1.5 text-sm leading-relaxed text-fg-muted">{{ t.body }}</p> <p class="mt-1.5 text-sm leading-relaxed text-fg-muted"><GlossedText :text="t.body" /></p>
</div> </div>
</div> </div>
<!-- self-hosting: surfaced here (reviewer found this the clearest idea, buried on the building slide) -->
<div class="mt-5 flex items-start gap-3 rounded-2xl border border-pine-500/20 bg-pine-500/5 p-5 text-sm text-fg">
<Icon name="Server" class="mt-0.5 size-5 shrink-0 text-pine" />
<span>
Every node <span class="font-semibold text-pine">self-hosts</span> the stack runs on-site,
on the networks own hardware, not in someone elses cloud.
</span>
</div>
</SlideFrame> </SlideFrame>
</template> </template>

View file

@ -5,6 +5,14 @@ import PlaceholderBadge from '../components/PlaceholderBadge.vue'
import { Alert, AlertTitle, AlertDescription } from '@/components/ui/alert' import { Alert, AlertTitle, AlertDescription } from '@/components/ui/alert'
import { brand, surfaceEntities, accentText } from '../data' import { brand, surfaceEntities, accentText } from '../data'
import logoUrl from '@/deck/assets/logo.webp' import logoUrl from '@/deck/assets/logo.webp'
// the "flowers" that grow from the Lab 484 soil type + where each one lives,
// so a first-time reader immediately places them (incl. the French farm)
const flowerMeta: Record<string, { type: string; place: string }> = {
sollunar: { type: 'City', place: 'Austin, TX' },
summit: { type: 'Pop-up', place: 'Travelling' },
chateau: { type: 'Farm', place: 'S. France' },
}
</script> </script>
<template> <template>
@ -37,19 +45,36 @@ import logoUrl from '@/deck/assets/logo.webp'
</div> </div>
</div> </div>
<p class="mt-7 max-w-2xl text-balance text-xl text-fg sm:text-2xl"> <!-- wordmark: the mark reads "So·lL·unar" spell it out so the lL ligature isn't misread -->
<p class="mt-5 font-display text-2xl font-semibold tracking-wide text-fg">
So<span class="text-amber">lL</span>unar
</p>
<p class="mt-4 max-w-2xl text-balance text-xl text-fg sm:text-2xl">
{{ brand.tagline }} {{ brand.tagline }}
</p> </p>
<!-- the three collaborating entities --> <!-- the metaphor, made explicit: Lab 484 is the soil; three flowers grow from it -->
<div class="mt-10 flex flex-wrap items-center justify-center gap-x-3 gap-y-2 text-sm text-fg-muted"> <div class="mt-10 flex flex-wrap items-center justify-center gap-x-2 gap-y-1 text-sm text-fg-muted">
<span class="uppercase tracking-[0.2em]">Three experiences, one backbone</span> <span class="uppercase tracking-[0.2em]">Lab 484 is the soil</span>
<span class="text-fg-muted/50"> three flowers grow from it </span>
</div> </div>
<div class="mt-3 flex flex-wrap items-center justify-center gap-3"> <div class="mt-4 flex flex-wrap items-center justify-center gap-3">
<template v-for="(e, i) in surfaceEntities" :key="e.id"> <span
<span class="font-display text-lg font-semibold" :class="accentText[e.accent]">{{ e.name }}</span> v-for="e in surfaceEntities"
<span v-if="i < surfaceEntities.length - 1" class="text-fg-muted">·</span> :key="e.id"
</template> class="flex flex-col items-center rounded-xl border border-line bg-surface px-4 py-2"
>
<span class="font-display text-base font-semibold" :class="accentText[e.accent]">{{ e.name }}</span>
<span class="text-[11px] uppercase tracking-wider text-fg-muted">
{{ flowerMeta[e.id].type }} · {{ flowerMeta[e.id].place }}
</span>
</span>
<!-- room to grow: future flowers -->
<span class="flex flex-col items-center rounded-xl border border-dashed border-line-2 px-4 py-2 opacity-60">
<span class="font-display text-base font-semibold text-fg-muted">+ more</span>
<span class="text-[11px] uppercase tracking-wider text-fg-muted">Future flowers</span>
</span>
</div> </div>
<p class="mt-8 max-w-xl text-balance text-sm text-fg-muted"> <p class="mt-8 max-w-xl text-balance text-sm text-fg-muted">