feat(deck): populate with real SolLunar Society business plan
Replace the invented financials/entities with the real content from SolLunarSociety.md — the first node's actual business: - data.ts: real problem statement; partnership (SPS 45 / Lab 484 45 / investors 10, $400k for 10%, $4M valuation); five tenants; three membership tiers + Austin comparables; recomputed monthly P&L ($65.4k rev / $34k exp / $31.4k profit / $376.8k yr) with the doc's non-reconciling summary kept in pnlDocSummary; buildings; investor ask. - New slides: Partnership (with the Lab 484↔revenue overlap called out), Tenants, Memberships, The building, and an interactive P&L drill-down. Rewrite the Ask ($400k/10% + profit-share scenarios). Title/Entities flag SolLunar as the funded first node; roadmap = festival → farm. - Remove the invented Revenue/CostBreakdown/Angel/Contributor/ Investment-overview and the Festivals/Chateau deep-dive slides. Ground the vision: SolLunar carries the numbers, the rest stay narrative. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b2d8153c62
commit
b292b987c3
18 changed files with 684 additions and 599 deletions
|
|
@ -10,15 +10,13 @@ import NetworkSlide from './slides/NetworkSlide.vue'
|
||||||
import TechSlide from './slides/TechSlide.vue'
|
import TechSlide from './slides/TechSlide.vue'
|
||||||
import EntitiesSlide from './slides/EntitiesSlide.vue'
|
import EntitiesSlide from './slides/EntitiesSlide.vue'
|
||||||
import SolLunarSlide from './slides/SolLunarSlide.vue'
|
import SolLunarSlide from './slides/SolLunarSlide.vue'
|
||||||
import FestivalsSlide from './slides/FestivalsSlide.vue'
|
import PartnershipSlide from './slides/PartnershipSlide.vue'
|
||||||
import ChateauSlide from './slides/ChateauSlide.vue'
|
import TenantsSlide from './slides/TenantsSlide.vue'
|
||||||
|
import MembershipSlide from './slides/MembershipSlide.vue'
|
||||||
|
import SpacesSlide from './slides/SpacesSlide.vue'
|
||||||
import GallerySlide from './slides/GallerySlide.vue'
|
import GallerySlide from './slides/GallerySlide.vue'
|
||||||
import EcosystemSlide from './slides/EcosystemSlide.vue'
|
import EcosystemSlide from './slides/EcosystemSlide.vue'
|
||||||
import RevenueSlide from './slides/RevenueSlide.vue'
|
import PnlSlide from './slides/PnlSlide.vue'
|
||||||
import InvestmentOverviewSlide from './slides/InvestmentOverviewSlide.vue'
|
|
||||||
import CostBreakdownSlide from './slides/CostBreakdownSlide.vue'
|
|
||||||
import AngelSlide from './slides/AngelSlide.vue'
|
|
||||||
import ContributorSlide from './slides/ContributorSlide.vue'
|
|
||||||
import RoadmapSlide from './slides/RoadmapSlide.vue'
|
import RoadmapSlide from './slides/RoadmapSlide.vue'
|
||||||
import AskSlide from './slides/AskSlide.vue'
|
import AskSlide from './slides/AskSlide.vue'
|
||||||
|
|
||||||
|
|
@ -28,16 +26,14 @@ const slides = [
|
||||||
{ id: 'network', label: 'The network', component: NetworkSlide },
|
{ id: 'network', label: 'The network', component: NetworkSlide },
|
||||||
{ id: 'archipelago', label: 'Archipelago', component: TechSlide },
|
{ id: 'archipelago', label: 'Archipelago', component: TechSlide },
|
||||||
{ id: 'entities', label: 'Three experiences', component: EntitiesSlide },
|
{ id: 'entities', label: 'Three experiences', component: EntitiesSlide },
|
||||||
{ id: 'sollunar', label: 'SolLunar Punk Society', component: SolLunarSlide },
|
{ id: 'sollunar', label: 'SolLunar Society', component: SolLunarSlide },
|
||||||
{ id: 'festivals', label: 'Pop-up Festivals', component: FestivalsSlide },
|
{ id: 'partnership', label: 'Ownership', component: PartnershipSlide },
|
||||||
{ id: 'chateau', label: 'Chateau du Faune', component: ChateauSlide },
|
{ id: 'tenants', label: 'Tenants', component: TenantsSlide },
|
||||||
|
{ id: 'membership', label: 'Memberships', component: MembershipSlide },
|
||||||
|
{ id: 'spaces', label: 'The building', component: SpacesSlide },
|
||||||
{ id: 'gallery', label: 'Gallery', component: GallerySlide },
|
{ id: 'gallery', label: 'Gallery', component: GallerySlide },
|
||||||
{ id: 'ecosystem', label: 'One membership', component: EcosystemSlide },
|
{ id: 'ecosystem', label: 'One membership', component: EcosystemSlide },
|
||||||
{ id: 'revenue', label: 'Revenue', component: RevenueSlide },
|
{ id: 'pnl', label: 'The numbers', component: PnlSlide },
|
||||||
{ id: 'invest-overview', label: 'The raise', component: InvestmentOverviewSlide },
|
|
||||||
{ id: 'cost-breakdown', label: 'Cost breakdown', component: CostBreakdownSlide },
|
|
||||||
{ id: 'angel', label: 'Angel investment', component: AngelSlide },
|
|
||||||
{ id: 'contribute', label: 'Contribute', component: ContributorSlide },
|
|
||||||
{ id: 'roadmap', label: 'Roadmap', component: RoadmapSlide },
|
{ id: 'roadmap', label: 'Roadmap', component: RoadmapSlide },
|
||||||
{ id: 'ask', label: 'The ask', component: AskSlide },
|
{ id: 'ask', label: 'The ask', component: AskSlide },
|
||||||
]
|
]
|
||||||
|
|
|
||||||
458
src/deck/data.ts
458
src/deck/data.ts
|
|
@ -1,18 +1,19 @@
|
||||||
/**
|
/**
|
||||||
* ARCHIPELAGO — pitch deck content & figures.
|
* ARCHIPELAGO — pitch deck content & figures.
|
||||||
*
|
*
|
||||||
* ⚠️ EVERYTHING IN THIS FILE IS A PLACEHOLDER.
|
* Single source of truth for the deck — no slide hard-codes a number or name.
|
||||||
* This is the single source of truth for the deck. To turn the deck into
|
*
|
||||||
* the real pitch, edit values here — no slide component hard-codes a number,
|
* The financials are REAL, from SolLunarSociety.md, and belong exclusively to
|
||||||
* a tier price, or an entity name. Search for `PLACEHOLDER` for every spot
|
* the first node — SolLunar Society (Austin). Archipelago (Lab 484), Pop-up
|
||||||
* that needs your real data.
|
* Festivals and Chateau du Faune are narrative/vision only and have no
|
||||||
|
* standalone numbers. P&L totals are recomputed from line items; the doc's
|
||||||
|
* own summary totals didn't reconcile (kept in `pnlDocSummary`).
|
||||||
*
|
*
|
||||||
* Archipelago is the shared backbone — open hardware + sovereign software (the
|
* Archipelago is the shared backbone — open hardware + sovereign software (the
|
||||||
* "mycelium"). Three surface entities run on top of it and share the full core
|
* "mycelium"). Three surface entities run on top of it:
|
||||||
* stack, so members get the same experience anywhere:
|
* • SolLunar Society — city members' club, Austin (the funded first node)
|
||||||
* • SolLunar Punk Society — city center (members' hub, built on Archipelago)
|
* • Pop-up Festivals — travelling gatherings (e.g. Solarpunk Summit)
|
||||||
* • Pop-up Festivals — travelling gatherings (e.g. Solar Punk Summit)
|
* • Chateau du Faune — regenerative farm center
|
||||||
* • Chateau du Faune — regenerative farm center
|
|
||||||
*
|
*
|
||||||
* Currency: all figures are USD unless noted.
|
* Currency: all figures are USD unless noted.
|
||||||
*/
|
*/
|
||||||
|
|
@ -25,24 +26,25 @@ export const brand = {
|
||||||
// a connected network of islands — one backbone, many places.
|
// a connected network of islands — one backbone, many places.
|
||||||
tagline: 'One network. Three ways to experience it.',
|
tagline: 'One network. Three ways to experience it.',
|
||||||
subtagline:
|
subtagline:
|
||||||
'A collaboration between SolLunar Punk Society, Pop-up Festivals and Chateau du Faune — all running on Archipelago, the shared backbone, so the membership, the payments and the experience travel with you.',
|
'A sovereign network on the Archipelago backbone. The first node — SolLunar Society, a members’ club in Austin — is live, modeled and raising now; the festival and the farm are next.',
|
||||||
contactEmail: 'invest@archipelago.example', // PLACEHOLDER
|
firstNode: 'SolLunar Society · Austin, TX',
|
||||||
|
contactEmail: 'invest@sollunar.example', // PLACEHOLDER
|
||||||
// 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
|
||||||
// "contribute" tiers. Left null until wired (see src/features/{lnbits,nostr}).
|
// "contribute" tiers. Left null until wired (see src/features/{lnbits,nostr}).
|
||||||
lnbitsBaseUrl: null as string | null,
|
lnbitsBaseUrl: null as string | null,
|
||||||
nostrNpub: null as string | null,
|
nostrNpub: null as string | null,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Big-picture framing for the vision slide. */
|
/** Big-picture framing for the vision slide (from the SolLunar Society plan). */
|
||||||
export const problem = {
|
export const problem = {
|
||||||
headline: 'Connectivity, community, and ownership are centralizing.',
|
headline: 'Transformation fades without the infrastructure to sustain it.',
|
||||||
points: [
|
points: [
|
||||||
'Networks, payments, and platforms are owned by a handful of gatekeepers.',
|
'People leave transformational events inspired — then return to environments that don’t support the change.',
|
||||||
'Local communities host the value but capture almost none of it.',
|
'There’s no lasting home for sovereignty-minded community: dreamers, creatives, technologists, regenerative living.',
|
||||||
'Every "smart" venue you can name is a walled garden that rents you access.',
|
'Peak experiences don’t compound without ongoing relationships, education, and a place to practice.',
|
||||||
],
|
],
|
||||||
thesis:
|
thesis:
|
||||||
'We build sovereign places that own their network, payments and data — a city hub, a travelling festival, and a farm — all on one shared backbone, each proving it can pay for itself.',
|
'SolLunar Society is the missing infrastructure — a community hub where the work begun at transformational events continues through membership, tenants, events and regenerative living.',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Accent = 'pine' | 'indigo' | 'rose' | 'clay'
|
export type Accent = 'pine' | 'indigo' | 'rose' | 'clay'
|
||||||
|
|
@ -69,20 +71,20 @@ export type Entity = {
|
||||||
export const entities: Entity[] = [
|
export const entities: Entity[] = [
|
||||||
{
|
{
|
||||||
id: 'sollunar',
|
id: 'sollunar',
|
||||||
name: 'SolLunar Punk Society',
|
name: 'SolLunar Society',
|
||||||
role: 'City center',
|
role: 'City center · the first node',
|
||||||
kind: 'Members’ hub · city',
|
kind: 'Members’ club & community hub · Austin, TX',
|
||||||
accent: 'indigo',
|
accent: 'indigo',
|
||||||
layer: 'surface',
|
layer: 'surface',
|
||||||
blurb:
|
blurb:
|
||||||
'The permanent city home: a members’ society and event space, built on Archipelago’s stack, where the community and the technology live year-round.',
|
'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 Archipelago stack.',
|
||||||
traits: [
|
traits: [
|
||||||
'The permanent, city-based flagship',
|
'Live, financially-modeled first node (real P&L)',
|
||||||
'Runs on Archipelago tech — public demonstration of the stack',
|
'Five founding tenants + a three-tier membership',
|
||||||
'Anchors membership, partnerships and the developer community',
|
'Bitcoin ATM, vending & the full member ecosystem on site',
|
||||||
],
|
],
|
||||||
spaces: ['Event hall', 'Co-working floor', 'Exchange kiosk', 'Café & bar', 'Studio / lab'],
|
spaces: ['Retail space', 'Event space', 'Private lounge', 'Co-working', 'Yellow House hostel & data center'],
|
||||||
revenue: ['Membership', 'Venue & desk hire', 'Events & workshops'],
|
revenue: ['Memberships', 'Tenants', 'Events & venue buyouts'],
|
||||||
icon: 'Building2',
|
icon: 'Building2',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -217,200 +219,282 @@ export const tech = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
/** Revenue streams across the collaboration. */
|
/* ─────────────────────────────────────────────────────────────
|
||||||
export const revenueStreams = [
|
SolLunar Society — the first node's REAL business (from
|
||||||
{ title: 'Tickets & festivals', note: 'Pop-up festival gatherings', icon: 'Ticket' },
|
SolLunarSociety.md). All figures below are SolLunar Society's
|
||||||
{ title: 'Room & lodging rentals', note: 'City & farm stays', icon: 'BedDouble' },
|
only; Archipelago (Lab 484), Festivals and Chateau have no
|
||||||
{ title: 'Retreats & residencies', note: 'Hosted programs at the farm', icon: 'Mountain' },
|
standalone numbers yet. Lab 484's economics appear here as
|
||||||
{ title: 'Experiences & workshops', note: 'Classes, tastings, tours', icon: 'Palette' },
|
revenue lines INTO SolLunar (~$16k/mo) — don't double-count.
|
||||||
{ title: 'Membership & venue hire', note: 'Society dues, event bookings', icon: 'PartyPopper' },
|
───────────────────────────────────────────────────────────── */
|
||||||
{ title: 'Network services', note: 'Connectivity, kiosk & payment fees', icon: 'Router' },
|
|
||||||
|
/** Ownership & partnership of the SolLunar Society JV. */
|
||||||
|
export type Partner = {
|
||||||
|
name: string
|
||||||
|
short?: string
|
||||||
|
equity: number // 0..1
|
||||||
|
accent: Accent | 'amber'
|
||||||
|
role: string
|
||||||
|
responsibilities: string[]
|
||||||
|
note?: string
|
||||||
|
}
|
||||||
|
export const partnership = {
|
||||||
|
valuation: 4_000_000,
|
||||||
|
raiseAmount: 400_000,
|
||||||
|
equityOffered: 0.1,
|
||||||
|
partners: [
|
||||||
|
{
|
||||||
|
name: 'SolLunar Punk Society',
|
||||||
|
short: 'SPS',
|
||||||
|
equity: 0.45,
|
||||||
|
accent: 'indigo',
|
||||||
|
role: 'Space & community',
|
||||||
|
responsibilities: [
|
||||||
|
'Curate & decorate the space',
|
||||||
|
'Lease to tenants · insurance',
|
||||||
|
'Outbound payments · SOPs',
|
||||||
|
'Marketing & community management',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Lab 484',
|
||||||
|
equity: 0.45,
|
||||||
|
accent: 'pine',
|
||||||
|
role: 'Tech & operations',
|
||||||
|
responsibilities: [
|
||||||
|
'Operate the spaces & staff',
|
||||||
|
'All IT / infrastructure (Archipelago)',
|
||||||
|
'Inbound payments · bootcamps',
|
||||||
|
'Tenant & investor relations',
|
||||||
|
],
|
||||||
|
note: 'Builds & operates the Archipelago backbone — and is itself a tenant + event driver (~$16k/mo of the revenue below).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Investors',
|
||||||
|
equity: 0.1,
|
||||||
|
accent: 'amber',
|
||||||
|
role: 'Capital',
|
||||||
|
responsibilities: ['$400,000 for 10% equity'],
|
||||||
|
},
|
||||||
|
] as Partner[],
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The five founding tenants inside SolLunar Society. */
|
||||||
|
export const tenants = [
|
||||||
|
{
|
||||||
|
name: 'Lab 484',
|
||||||
|
icon: 'Cpu',
|
||||||
|
blurb:
|
||||||
|
'Research lab & startup cluster building a “second internet” — Bitcoin, mesh networks, Nostr, e-cash and P2P. User-owned hardware, software and data.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Solarpunk Summit',
|
||||||
|
icon: 'Tent',
|
||||||
|
blurb:
|
||||||
|
'Transformational platform & community builder — immersive experiences, educational programming and regenerative culture.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Noderunners Shop',
|
||||||
|
icon: 'Router',
|
||||||
|
blurb:
|
||||||
|
'Retail for AI computers, mesh gear, Bitcoin nodes and repair. Open-source, secure laptops, phones and servers.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Divine Provisions',
|
||||||
|
icon: 'Coins',
|
||||||
|
blurb:
|
||||||
|
'A food co-op for low-cost, organic, local groceries — offered as a member service at the building entrance.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Kitchen 484',
|
||||||
|
icon: 'Sparkles',
|
||||||
|
blurb:
|
||||||
|
'Community food truck — organic, seed-oil-free, locally sourced. Runs even when disconnected from the internet.',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export type CostItem = { label: string; amount: number; note?: string }
|
/** Membership tiers (customer product / recurring revenue). */
|
||||||
export type CostCategory = {
|
export type Membership = {
|
||||||
id: string
|
name: string
|
||||||
title: string
|
price: number // monthly
|
||||||
subtitle: string
|
target: number // member-count target
|
||||||
icon: string
|
blurb: string
|
||||||
accent: Accent
|
perks: string[]
|
||||||
items: CostItem[]
|
featured?: boolean
|
||||||
}
|
}
|
||||||
|
export const memberships: Membership[] = [
|
||||||
|
{
|
||||||
|
name: 'Solarpunk Society',
|
||||||
|
price: 50,
|
||||||
|
target: 150,
|
||||||
|
blurb: 'Events access.',
|
||||||
|
perks: ['Member events', 'Amenities, vending & Bitcoin ATM', 'Access to tenants'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Cypherpunk Commons',
|
||||||
|
price: 350,
|
||||||
|
target: 50,
|
||||||
|
featured: true,
|
||||||
|
blurb: 'Full access, all locations.',
|
||||||
|
perks: [
|
||||||
|
'Coworking with the Lab 484 team',
|
||||||
|
'All locations · tech support',
|
||||||
|
'Bitcoin ATM, vending, coffee & tea',
|
||||||
|
'Full tenant & amenity access',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Lunarpunk Locals',
|
||||||
|
price: 150,
|
||||||
|
target: 100,
|
||||||
|
blurb: 'Coworking days.',
|
||||||
|
perks: ['Coworking days at SolLunar', 'Access to tenants', 'Amenities, vending & Bitcoin ATM'],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
/** Comparable Austin memberships, for pricing context. */
|
||||||
|
export const membershipComparables = [
|
||||||
|
{ name: 'Soho House', price: 350 },
|
||||||
|
{ name: 'Bond Collective', price: 300 },
|
||||||
|
{ name: 'Bathe', price: 200 },
|
||||||
|
{ name: 'Stardust Garage', price: 155 },
|
||||||
|
{ name: 'Submersive', price: 41 },
|
||||||
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Investment cost categories — one per entity. PLACEHOLDER amounts (USD).
|
* SolLunar Society monthly P&L. Line items are from the doc; category totals
|
||||||
* The shared Archipelago backbone plus a launch for each experience.
|
* are RECOMPUTED from them (the doc's summary totals didn't reconcile — see
|
||||||
|
* `pnlDocSummary`). Flagged oddity: Lunarpunk Locals income ($1,500) doesn't
|
||||||
|
* match its "100 × $150" annotation.
|
||||||
*/
|
*/
|
||||||
export const costCategories: CostCategory[] = [
|
export type PnlItem = { label: string; amount: number; note?: string }
|
||||||
{
|
export type PnlGroup = {
|
||||||
id: 'archipelago',
|
id: string
|
||||||
title: 'Archipelago · shared backbone',
|
title: string
|
||||||
subtitle: 'Servers, routers, exchange kiosks, NFC + core dev — powers all three',
|
|
||||||
icon: 'Cpu',
|
|
||||||
accent: 'pine',
|
|
||||||
items: [
|
|
||||||
{ label: 'Edge servers & compute', amount: 60_000 },
|
|
||||||
{ label: 'Mesh routers & networking', amount: 45_000 },
|
|
||||||
{ label: 'Exchange kiosks', amount: 50_000, note: '2 units' },
|
|
||||||
{ label: 'Membership-card & NFC systems', amount: 25_000 },
|
|
||||||
{ label: 'Core development salaries', amount: 300_000, note: '4 engineers · 12 mo' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sollunar',
|
|
||||||
title: 'SolLunar Punk Society · city',
|
|
||||||
subtitle: 'Fit-out & launch of the urban members’ hub',
|
|
||||||
icon: 'Building2',
|
|
||||||
accent: 'indigo',
|
|
||||||
items: [
|
|
||||||
{ label: 'Lease deposit & permits', amount: 60_000 },
|
|
||||||
{ label: 'Renovation & fit-out', amount: 260_000 },
|
|
||||||
{ label: 'AV, furniture & kitchen', amount: 120_000 },
|
|
||||||
{ label: 'On-site network install', amount: 40_000 },
|
|
||||||
{ label: 'Launch & working capital', amount: 40_000 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'festivals',
|
|
||||||
title: 'Pop-up Festivals',
|
|
||||||
subtitle: 'Mobile deployment kit + first-season production',
|
|
||||||
icon: 'Tent',
|
|
||||||
accent: 'rose',
|
|
||||||
items: [
|
|
||||||
{ label: 'Mobile mesh & power rig', amount: 60_000 },
|
|
||||||
{ label: 'Mobile exchange kiosk & POS', amount: 45_000 },
|
|
||||||
{ label: 'Stage, structures & staging gear', amount: 70_000 },
|
|
||||||
{ label: 'First-season production & launch', amount: 45_000 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'chateau',
|
|
||||||
title: 'Chateau du Faune · farm',
|
|
||||||
subtitle: 'Land, structures & off-grid infrastructure',
|
|
||||||
icon: 'Trees',
|
|
||||||
accent: 'clay',
|
|
||||||
items: [
|
|
||||||
{ label: 'Land improvements & access', amount: 90_000 },
|
|
||||||
{ label: 'Lodging & studio structures', amount: 300_000 },
|
|
||||||
{ label: 'Off-grid power & water', amount: 110_000 },
|
|
||||||
{ label: 'Alpacas & farm infrastructure', amount: 70_000 },
|
|
||||||
{ label: 'On-site mesh & launch', amount: 60_000 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export const categoryTotal = (c: CostCategory) => c.items.reduce((s, i) => s + i.amount, 0)
|
|
||||||
export const raiseTotal = costCategories.reduce((s, c) => s + categoryTotal(c), 0)
|
|
||||||
|
|
||||||
export type AngelTier = {
|
|
||||||
name: string
|
|
||||||
minAmount: number
|
|
||||||
equity: string
|
|
||||||
benefits: string[]
|
|
||||||
featured?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Angel investment — equity + benefits across the collaboration. PLACEHOLDER terms. */
|
|
||||||
export const angelTiers: AngelTier[] = [
|
|
||||||
{
|
|
||||||
name: 'Seed Angel',
|
|
||||||
minAmount: 50_000,
|
|
||||||
equity: 'Equity from a common SAFE',
|
|
||||||
benefits: [
|
|
||||||
'Pro-rata equity in the network',
|
|
||||||
'Founding-member access to all three entities',
|
|
||||||
'Quarterly investor updates',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Network Angel',
|
|
||||||
minAmount: 100_000,
|
|
||||||
equity: 'Priced equity + pro-rata rights',
|
|
||||||
featured: true,
|
|
||||||
benefits: [
|
|
||||||
'Everything in Seed, plus:',
|
|
||||||
'Advisory input on network direction',
|
|
||||||
'Festival, retreat & residency weeks',
|
|
||||||
'Named node sponsor',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Founding Angel',
|
|
||||||
minAmount: 250_000,
|
|
||||||
equity: 'Priced equity + board observer seat',
|
|
||||||
benefits: [
|
|
||||||
'Everything in Network, plus:',
|
|
||||||
'Board observer seat',
|
|
||||||
'Naming rights on a flagship space',
|
|
||||||
'Lifetime network membership',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export type ContributorTier = {
|
|
||||||
name: string
|
|
||||||
amount: number
|
|
||||||
unit?: string
|
|
||||||
blurb: string
|
|
||||||
icon: string
|
icon: string
|
||||||
featured?: boolean
|
accent: Accent
|
||||||
|
kind: 'revenue' | 'expense'
|
||||||
|
items: PnlItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Smaller backers — perks, not equity. PLACEHOLDER prices. */
|
export const pnlGroups: PnlGroup[] = [
|
||||||
export const contributorTiers: ContributorTier[] = [
|
|
||||||
{
|
{
|
||||||
name: 'Backer',
|
id: 'memberships',
|
||||||
amount: 25,
|
title: 'Memberships',
|
||||||
blurb: 'Fund a member card and get your name on the founders’ wall.',
|
|
||||||
icon: 'CreditCard',
|
icon: 'CreditCard',
|
||||||
|
accent: 'indigo',
|
||||||
|
kind: 'revenue',
|
||||||
|
items: [
|
||||||
|
{ label: 'Cypherpunk Commons', amount: 17_500, note: '50 × $350' },
|
||||||
|
{ label: 'Solarpunk Society', amount: 7_500, note: '150 × $50' },
|
||||||
|
{ label: 'Lunarpunk Locals', amount: 1_500, note: '$150/mo · count TBD' },
|
||||||
|
{ label: 'Vending machine sales', amount: 5_000 },
|
||||||
|
{ label: 'Promo / B2B network sales', amount: 5_000 },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Node Router',
|
id: 'events',
|
||||||
amount: 150,
|
title: 'Events',
|
||||||
blurb: 'Put a community-owned router on the mesh. Adopt-a-node on the live map.',
|
icon: 'PartyPopper',
|
||||||
icon: 'Router',
|
accent: 'rose',
|
||||||
|
kind: 'revenue',
|
||||||
|
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: 'SolLunar Society events', amount: 1_500, note: '$50 weekend passes' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Mesh Kit',
|
id: 'tenants',
|
||||||
amount: 250,
|
title: 'Tenants',
|
||||||
blurb: 'Sponsor a full mesh kit that lights up a location — or a festival.',
|
icon: 'Building2',
|
||||||
icon: 'Wifi',
|
accent: 'pine',
|
||||||
featured: true,
|
kind: 'revenue',
|
||||||
|
items: [
|
||||||
|
{ label: 'Solarpunk Summit', amount: 2_000 },
|
||||||
|
{ label: 'Lab 484', amount: 2_000 },
|
||||||
|
{ label: 'Divine Provisions', amount: 1_500 },
|
||||||
|
{ label: 'Noderunners Shop', amount: 1_500 },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Sponsor an Alpaca',
|
id: 'expenses',
|
||||||
amount: 500,
|
title: 'Expenses',
|
||||||
unit: '/yr',
|
icon: 'Coins',
|
||||||
blurb: 'Name & sponsor an alpaca at Chateau du Faune. Photos, updates, and a visit.',
|
accent: 'clay',
|
||||||
icon: 'Origami',
|
kind: 'expense',
|
||||||
|
items: [
|
||||||
|
{ label: 'Staff', amount: 22_500, note: '4 staff' },
|
||||||
|
{ label: 'Rent', amount: 6_500, note: '$4,500 + triple net' },
|
||||||
|
{ label: 'Overhead', amount: 3_000 },
|
||||||
|
{ label: 'Utilities', amount: 2_000, note: 'electric, water, trash' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
/** Roadmap / replication story. */
|
export const groupTotal = (g: PnlGroup) => g.items.reduce((s, i) => s + i.amount, 0)
|
||||||
|
const sumKind = (k: 'revenue' | 'expense') =>
|
||||||
|
pnlGroups.filter((g) => g.kind === k).reduce((s, g) => s + groupTotal(g), 0)
|
||||||
|
|
||||||
|
/** Recomputed monthly/yearly economics for SolLunar Society. */
|
||||||
|
export const pnl = {
|
||||||
|
monthlyRevenue: sumKind('revenue'),
|
||||||
|
monthlyExpenses: sumKind('expense'),
|
||||||
|
monthlyProfit: sumKind('revenue') - sumKind('expense'),
|
||||||
|
yearlyProfit: (sumKind('revenue') - sumKind('expense')) * 12,
|
||||||
|
}
|
||||||
|
/** What the source doc's summary table stated (did NOT reconcile with line items). */
|
||||||
|
export const pnlDocSummary = { revenue: 71_400, expenses: 38_500, profit: 32_900, yearlyProfit: 394_800 }
|
||||||
|
|
||||||
|
/** The building & spaces. */
|
||||||
|
export const buildings = [
|
||||||
|
{
|
||||||
|
name: 'SolLunar Society',
|
||||||
|
rooms: ['Retail space', 'Event space', 'Private office', 'Private lounge'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Lab 484 · Yellow House 1',
|
||||||
|
rooms: ['Hostel room · $40/night', 'Healer room', 'Coworking', 'Meeting room', 'Outdoor'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Lab 484 · Yellow House 2',
|
||||||
|
rooms: ['VIP guest room', 'Office', 'Data center', 'Outdoor'],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
/** The investor ask. */
|
||||||
|
export const investorAsk = {
|
||||||
|
amount: 400_000,
|
||||||
|
equity: 0.1,
|
||||||
|
valuation: 4_000_000,
|
||||||
|
scenarios: [0.1, 0.25], // profit-share scenarios on the recomputed yearly profit
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Roadmap / replication story. SolLunar Society first, then the network. */
|
||||||
export const roadmap = [
|
export const roadmap = [
|
||||||
{
|
{
|
||||||
phase: 'Phase 0',
|
phase: 'Phase 0',
|
||||||
title: 'Raise & finalize',
|
title: 'Raise & open SolLunar',
|
||||||
when: 'Now',
|
when: 'Now',
|
||||||
body: 'Close the angel round, lock sites, finalize the open bill of materials.',
|
body: 'Close the $400k round, sign the lease, fit out the space and onboard the founding tenants.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
phase: 'Phase 1',
|
phase: 'Phase 1',
|
||||||
title: 'Launch the three entities',
|
title: 'Operate & prove the model',
|
||||||
when: '~6–9 mo',
|
when: '~0–12 mo',
|
||||||
body: 'Open the SolLunar Punk Society city hub and Chateau du Faune; stage the first pop-up festival.',
|
body: 'Run memberships, events and tenants in Austin; validate the P&L publicly on the Archipelago stack.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
phase: 'Phase 2',
|
phase: 'Phase 2',
|
||||||
title: 'Operate & prove revenue',
|
title: 'Add the festival',
|
||||||
when: '~9–18 mo',
|
when: '~12–18 mo',
|
||||||
body: 'Run events, festivals, retreats and residencies; validate the unit economics publicly.',
|
body: 'Deploy the same stack to a travelling Solarpunk Summit — proving the network is portable, not tied to a building.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
phase: 'Phase 3',
|
phase: 'Phase 3',
|
||||||
title: 'Open the blueprint',
|
title: 'Add the farm & replicate',
|
||||||
when: '18 mo+',
|
when: '18 mo+',
|
||||||
body: 'Publish the replication kit; onboard new nodes & festivals worldwide on shared infrastructure.',
|
body: 'Open Chateau du Faune and publish the replication kit — new nodes plug into the same backbone anywhere.',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import SlideFrame from '../components/SlideFrame.vue'
|
|
||||||
import Icon from '../components/Icon.vue'
|
|
||||||
import PlaceholderBadge from '../components/PlaceholderBadge.vue'
|
|
||||||
import { angelTiers, fmtUsd } from '../data'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<SlideFrame
|
|
||||||
kicker="Ways to invest · Angels"
|
|
||||||
title="Equity, benefits, and a seat at the table"
|
|
||||||
subtitle="Angel investors are who we're after — capital plus belief, rewarded with ownership and lifelong access."
|
|
||||||
>
|
|
||||||
<div class="grid gap-5 md:grid-cols-3">
|
|
||||||
<div
|
|
||||||
v-for="tier in angelTiers"
|
|
||||||
:key="tier.name"
|
|
||||||
class="relative flex flex-col rounded-2xl border p-6"
|
|
||||||
:class="
|
|
||||||
tier.featured
|
|
||||||
? 'border-amber-400/50 bg-amber-400/[0.06]'
|
|
||||||
: 'border-white/10 bg-white/[0.03]'
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-if="tier.featured"
|
|
||||||
class="absolute -top-3 left-6 rounded-full bg-amber-400 px-3 py-0.5 text-[11px] font-semibold uppercase tracking-wider text-ink"
|
|
||||||
>
|
|
||||||
Most popular
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<h3 class="font-display text-xl font-semibold text-sand">{{ tier.name }}</h3>
|
|
||||||
<div class="mt-2 flex items-baseline gap-1">
|
|
||||||
<span class="text-sm text-sand-dim">from</span>
|
|
||||||
<span class="font-display text-3xl font-bold text-sand">{{ fmtUsd(tier.minAmount) }}</span>
|
|
||||||
</div>
|
|
||||||
<p class="mt-1 text-xs font-medium text-pine-300">{{ tier.equity }}</p>
|
|
||||||
|
|
||||||
<ul class="mt-5 flex-1 space-y-2.5">
|
|
||||||
<li v-for="b in tier.benefits" :key="b" class="flex items-start gap-2 text-sm text-sand-dim">
|
|
||||||
<Icon name="Check" class="mt-0.5 size-4 shrink-0 text-pine-300" />
|
|
||||||
{{ b }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="mt-5 flex items-center gap-2 text-xs text-sand-dim">
|
|
||||||
<PlaceholderBadge label="terms" /> Amounts, equity structure & benefits are indicative — final terms per the round.
|
|
||||||
</p>
|
|
||||||
</SlideFrame>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,26 +1,42 @@
|
||||||
<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 PlaceholderBadge from '../components/PlaceholderBadge.vue'
|
import { brand, investorAsk, pnl, fmtUsd } from '../data'
|
||||||
import { brand, raiseTotal, fmtUsd } from '../data'
|
|
||||||
|
const pct = (n: number) => Math.round(n * 100)
|
||||||
|
// annual profit-share per scenario, on the recomputed yearly profit
|
||||||
|
const shares = investorAsk.scenarios.map((s) => ({ equity: s, annual: Math.round(pnl.yearlyProfit * s) }))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SlideFrame center>
|
<SlideFrame center>
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.3em] text-amber-400">The ask</p>
|
<p class="text-xs font-semibold uppercase tracking-[0.3em] text-amber-400">The ask</p>
|
||||||
<h2 class="mt-4 font-display text-4xl font-semibold text-sand sm:text-5xl">
|
<h2 class="mt-4 font-display text-4xl font-semibold text-sand sm:text-5xl">
|
||||||
Help us grow the first three flowers
|
{{ fmtUsd(investorAsk.amount) }} for {{ pct(investorAsk.equity) }}% of SolLunar Society
|
||||||
</h2>
|
</h2>
|
||||||
|
<p class="mt-3 text-sand-dim">
|
||||||
<div class="mt-8 flex items-baseline justify-center gap-3">
|
{{ fmtUsd(investorAsk.valuation) }} valuation · the first node, open in Austin
|
||||||
<span class="font-display text-6xl font-bold text-sand">{{ fmtUsd(raiseTotal) }}</span>
|
|
||||||
<PlaceholderBadge />
|
|
||||||
</div>
|
|
||||||
<p class="mt-2 max-w-xl text-sand-dim">
|
|
||||||
to launch {{ brand.name }} — an angel round for equity partners, open to backers of every size.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mt-10 flex flex-col items-center gap-3 sm:flex-row">
|
<!-- profit-share scenarios -->
|
||||||
|
<div class="mt-8 flex flex-wrap items-stretch justify-center gap-4">
|
||||||
|
<div
|
||||||
|
v-for="s in shares"
|
||||||
|
:key="s.equity"
|
||||||
|
class="rounded-2xl border border-white/10 bg-white/[0.03] px-6 py-4"
|
||||||
|
>
|
||||||
|
<p class="text-xs uppercase tracking-wider text-sand-dim">{{ pct(s.equity) }}% share</p>
|
||||||
|
<p class="mt-1 font-display text-3xl font-bold text-sand">{{ fmtUsd(s.annual) }}</p>
|
||||||
|
<p class="text-xs text-sand-dim">/ yr profit</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-2xl border border-pine-500/40 bg-pine-500/[0.07] px-6 py-4">
|
||||||
|
<p class="text-xs uppercase tracking-wider text-pine-300">Node profit</p>
|
||||||
|
<p class="mt-1 font-display text-3xl font-bold text-sand">{{ fmtUsd(pnl.yearlyProfit) }}</p>
|
||||||
|
<p class="text-xs text-sand-dim">/ yr (recomputed)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-9 flex flex-col items-center gap-3 sm:flex-row">
|
||||||
<a
|
<a
|
||||||
:href="`mailto:${brand.contactEmail}`"
|
:href="`mailto:${brand.contactEmail}`"
|
||||||
class="inline-flex items-center gap-2 rounded-full bg-amber-400 px-6 py-3 font-semibold text-ink transition hover:bg-amber-300"
|
class="inline-flex items-center gap-2 rounded-full bg-amber-400 px-6 py-3 font-semibold text-ink transition hover:bg-amber-300"
|
||||||
|
|
@ -36,6 +52,8 @@ import { brand, raiseTotal, fmtUsd } from '../data'
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="mt-10 font-display text-lg text-pine-300">{{ brand.tagline }}</p>
|
<p class="mt-9 max-w-xl text-sm text-sand-dim">
|
||||||
|
Prove the model in Austin, then replicate it on Archipelago — the festival and the farm are next.
|
||||||
|
</p>
|
||||||
</SlideFrame>
|
</SlideFrame>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import EntityDetail from '../components/EntityDetail.vue'
|
|
||||||
import { entityById } from '../data'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<EntityDetail :entity="entityById('chateau')" />
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import SlideFrame from '../components/SlideFrame.vue'
|
|
||||||
import Icon from '../components/Icon.vue'
|
|
||||||
import { contributorTiers, brand, fmtUsdFull } from '../data'
|
|
||||||
|
|
||||||
// Seam: when brand.lnbitsBaseUrl is set, these become live Lightning
|
|
||||||
// checkouts (see src/features/lnbits). Until then they're "coming soon".
|
|
||||||
const live = !!brand.lnbitsBaseUrl
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<SlideFrame
|
|
||||||
kicker="Ways to invest · Everyone else"
|
|
||||||
title="Own a piece of the network for the price of dinner"
|
|
||||||
subtitle="You don't need to be an angel to build this. Back a specific piece of hardware — or an alpaca."
|
|
||||||
>
|
|
||||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
||||||
<div
|
|
||||||
v-for="tier in contributorTiers"
|
|
||||||
:key="tier.name"
|
|
||||||
class="flex flex-col rounded-2xl border p-5"
|
|
||||||
:class="tier.featured ? 'border-pine-500/50 bg-pine-500/[0.06]' : 'border-white/10 bg-white/[0.03]'"
|
|
||||||
>
|
|
||||||
<span class="grid size-11 place-items-center rounded-xl bg-white/5 text-pine-300">
|
|
||||||
<Icon :name="tier.icon" class="size-5" />
|
|
||||||
</span>
|
|
||||||
<div class="mt-4 flex items-baseline gap-1">
|
|
||||||
<span class="font-display text-2xl font-bold text-sand">{{ fmtUsdFull(tier.amount) }}</span>
|
|
||||||
<span v-if="tier.unit" class="text-sm text-sand-dim">{{ tier.unit }}</span>
|
|
||||||
</div>
|
|
||||||
<h3 class="mt-1 font-display text-base font-semibold text-sand">{{ tier.name }}</h3>
|
|
||||||
<p class="mt-1.5 flex-1 text-sm text-sand-dim">{{ tier.blurb }}</p>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="mt-4 flex items-center justify-center gap-1.5 rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm font-medium text-sand transition hover:bg-white/10 disabled:cursor-not-allowed disabled:opacity-60"
|
|
||||||
:disabled="!live"
|
|
||||||
>
|
|
||||||
<Icon name="Zap" class="size-4 text-amber-300" />
|
|
||||||
{{ live ? 'Contribute' : 'Coming soon' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="mt-5 flex items-center gap-2 text-xs text-sand-dim">
|
|
||||||
<Icon name="Zap" class="size-3.5 text-amber-300" />
|
|
||||||
Contributions settle over Bitcoin Lightning — wired to LNbits & nostr when we go live.
|
|
||||||
</p>
|
|
||||||
</SlideFrame>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import SlideFrame from '../components/SlideFrame.vue'
|
|
||||||
import Icon from '../components/Icon.vue'
|
|
||||||
import PlaceholderBadge from '../components/PlaceholderBadge.vue'
|
|
||||||
import { costCategories, categoryTotal, fmtUsd } from '../data'
|
|
||||||
|
|
||||||
const text: Record<string, string> = {
|
|
||||||
pine: 'text-pine-300',
|
|
||||||
indigo: 'text-indigo-300',
|
|
||||||
rose: 'text-rose-300',
|
|
||||||
clay: 'text-clay-400',
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<SlideFrame
|
|
||||||
kicker="The raise · line by line"
|
|
||||||
title="Cost breakdown"
|
|
||||||
subtitle="Every figure lives in one file — swap placeholders for your real budget in src/deck/data.ts."
|
|
||||||
>
|
|
||||||
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
|
||||||
<div
|
|
||||||
v-for="c in costCategories"
|
|
||||||
:key="c.id"
|
|
||||||
class="flex flex-col rounded-2xl border border-white/10 bg-white/[0.03] p-4"
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<Icon :name="c.icon" class="size-4 shrink-0" :class="text[c.accent]" />
|
|
||||||
<h3 class="font-display text-sm font-semibold leading-tight text-sand">{{ c.title }}</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="mt-3 flex-1 space-y-2">
|
|
||||||
<li v-for="item in c.items" :key="item.label" class="text-[13px]">
|
|
||||||
<div class="flex items-baseline justify-between gap-2">
|
|
||||||
<span class="text-sand-dim">{{ item.label }}</span>
|
|
||||||
<span class="shrink-0 tabular-nums text-sand">{{ fmtUsd(item.amount) }}</span>
|
|
||||||
</div>
|
|
||||||
<p v-if="item.note" class="text-[10px] text-sand-dim/70">{{ item.note }}</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="mt-3 flex items-center justify-between border-t border-white/10 pt-2.5">
|
|
||||||
<span class="text-[10px] uppercase tracking-wider text-sand-dim">Subtotal</span>
|
|
||||||
<span class="font-display text-base font-semibold" :class="text[c.accent]">
|
|
||||||
{{ fmtUsd(categoryTotal(c)) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="mt-4 flex items-center gap-2 text-xs text-sand-dim">
|
|
||||||
<PlaceholderBadge /> All amounts are illustrative pending your real quotes & salary bands.
|
|
||||||
</p>
|
|
||||||
</SlideFrame>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,7 +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 { userEcosystem, entities } from '../data'
|
import { userEcosystem, surfaceEntities as entities } from '../data'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,21 @@ const archipelago = entityById('archipelago')
|
||||||
<SlideFrame
|
<SlideFrame
|
||||||
kicker="The collaboration"
|
kicker="The collaboration"
|
||||||
title="Three experiences, one backbone"
|
title="Three experiences, one backbone"
|
||||||
subtitle="Deliberately different — a city hub, a travelling festival, a farm — each with its own character, all running on Archipelago."
|
subtitle="One is funded and opening now — SolLunar Society in Austin. The festival and the farm follow, on the same Archipelago stack."
|
||||||
>
|
>
|
||||||
<div class="grid gap-5 md:grid-cols-3">
|
<div class="grid gap-5 md:grid-cols-3">
|
||||||
<div
|
<div
|
||||||
v-for="e in surfaceEntities"
|
v-for="e in surfaceEntities"
|
||||||
:key="e.id"
|
:key="e.id"
|
||||||
class="flex flex-col rounded-2xl border p-6"
|
class="relative flex flex-col rounded-2xl border p-6"
|
||||||
:class="accentRing[e.accent]"
|
:class="e.id === 'sollunar' ? 'border-indigo-500/60 bg-indigo-500/[0.08]' : accentRing[e.accent]"
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
v-if="e.id === 'sollunar'"
|
||||||
|
class="absolute -top-3 left-6 rounded-full bg-indigo-400 px-3 py-0.5 text-[11px] font-semibold uppercase tracking-wider text-ink"
|
||||||
|
>
|
||||||
|
Raising now
|
||||||
|
</span>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span class="grid size-11 place-items-center rounded-xl bg-white/5" :class="accentText[e.accent]">
|
<span class="grid size-11 place-items-center rounded-xl bg-white/5" :class="accentText[e.accent]">
|
||||||
<Icon :name="e.icon" class="size-6" />
|
<Icon :name="e.icon" class="size-6" />
|
||||||
|
|
@ -32,8 +38,8 @@ const archipelago = entityById('archipelago')
|
||||||
<p class="mt-4 flex-1 text-sm leading-relaxed text-sand-dim">{{ e.blurb }}</p>
|
<p class="mt-4 flex-1 text-sm leading-relaxed text-sand-dim">{{ e.blurb }}</p>
|
||||||
|
|
||||||
<div class="mt-5 flex items-center gap-2 border-t border-white/10 pt-4 text-xs text-sand-dim">
|
<div class="mt-5 flex items-center gap-2 border-t border-white/10 pt-4 text-xs text-sand-dim">
|
||||||
<Icon name="Layers" class="size-4 text-amber-300" />
|
<Icon :name="e.id === 'sollunar' ? 'Check' : 'Layers'" class="size-4" :class="e.id === 'sollunar' ? 'text-indigo-300' : 'text-amber-300'" />
|
||||||
Full membership & ecosystem
|
{{ e.id === 'sollunar' ? 'Live P&L · this raise' : 'Vision · on the roadmap' }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import EntityDetail from '../components/EntityDetail.vue'
|
|
||||||
import { entityById } from '../data'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<EntityDetail :entity="entityById('festivals')" />
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,194 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed } from 'vue'
|
|
||||||
import SlideFrame from '../components/SlideFrame.vue'
|
|
||||||
import Icon from '../components/Icon.vue'
|
|
||||||
import PlaceholderBadge from '../components/PlaceholderBadge.vue'
|
|
||||||
import { costCategories, categoryTotal, raiseTotal, fmtUsd } from '../data'
|
|
||||||
|
|
||||||
// per-category colour (includes the shared 'amber' core-stack category)
|
|
||||||
const barBg: Record<string, string> = {
|
|
||||||
pine: 'bg-pine-500',
|
|
||||||
indigo: 'bg-indigo-500',
|
|
||||||
rose: 'bg-rose-500',
|
|
||||||
clay: 'bg-clay-400',
|
|
||||||
}
|
|
||||||
const text: Record<string, string> = {
|
|
||||||
pine: 'text-pine-300',
|
|
||||||
indigo: 'text-indigo-300',
|
|
||||||
rose: 'text-rose-300',
|
|
||||||
clay: 'text-clay-400',
|
|
||||||
}
|
|
||||||
const ring: Record<string, string> = {
|
|
||||||
pine: 'border-pine-500/50 bg-pine-500/[0.06]',
|
|
||||||
indigo: 'border-indigo-500/50 bg-indigo-500/[0.06]',
|
|
||||||
rose: 'border-rose-500/50 bg-rose-500/[0.06]',
|
|
||||||
clay: 'border-clay-400/50 bg-clay-400/[0.06]',
|
|
||||||
}
|
|
||||||
|
|
||||||
// interactive drill-down: which category is expanded (null = overview)
|
|
||||||
const selected = ref<string | null>(null)
|
|
||||||
const selectedCat = computed(() => costCategories.find((c) => c.id === selected.value) ?? null)
|
|
||||||
function toggle(id: string) {
|
|
||||||
selected.value = selected.value === id ? null : id
|
|
||||||
}
|
|
||||||
const pct = (c: (typeof costCategories)[number]) => (categoryTotal(c) / raiseTotal) * 100
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<SlideFrame
|
|
||||||
kicker="The raise"
|
|
||||||
title="Where the investment goes"
|
|
||||||
subtitle="Tap any category to break down its costs."
|
|
||||||
>
|
|
||||||
<div class="grid items-start gap-10 lg:grid-cols-[1fr_1.05fr]">
|
|
||||||
<!-- left: headline number, proportion bar, live detail panel -->
|
|
||||||
<div>
|
|
||||||
<div class="flex items-end gap-3">
|
|
||||||
<span class="font-display text-6xl font-bold text-sand">{{ fmtUsd(raiseTotal) }}</span>
|
|
||||||
<PlaceholderBadge />
|
|
||||||
</div>
|
|
||||||
<p class="mt-2 text-sand-dim">
|
|
||||||
One shared core stack, plus a launch for each of the three entities.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- proportion bar doubles as a selector -->
|
|
||||||
<div class="mt-8 flex h-4 w-full overflow-hidden rounded-full">
|
|
||||||
<button
|
|
||||||
v-for="c in costCategories"
|
|
||||||
:key="c.id"
|
|
||||||
class="h-full cursor-pointer transition-opacity"
|
|
||||||
:class="[barBg[c.accent], selected && selected !== c.id ? 'opacity-30' : 'opacity-100']"
|
|
||||||
:style="{ width: `${pct(c)}%` }"
|
|
||||||
:title="`${c.title} · ${fmtUsd(categoryTotal(c))}`"
|
|
||||||
:aria-label="`Show ${c.title} breakdown`"
|
|
||||||
@click="toggle(c.id)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mt-3 flex flex-wrap gap-x-5 gap-y-1 text-xs text-sand-dim">
|
|
||||||
<span v-for="c in costCategories" :key="c.id" class="flex items-center gap-2">
|
|
||||||
<span class="size-2 rounded-full" :class="barBg[c.accent]" />
|
|
||||||
{{ Math.round(pct(c)) }}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- detail panel: swaps between hint and the selected breakdown -->
|
|
||||||
<Transition name="detail" mode="out-in">
|
|
||||||
<div
|
|
||||||
v-if="selectedCat"
|
|
||||||
:key="selectedCat.id"
|
|
||||||
class="mt-6 rounded-2xl border p-5"
|
|
||||||
:class="ring[selectedCat.accent]"
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<span class="grid size-9 place-items-center rounded-lg bg-white/5" :class="text[selectedCat.accent]">
|
|
||||||
<Icon :name="selectedCat.icon" class="size-5" />
|
|
||||||
</span>
|
|
||||||
<div class="min-w-0 flex-1">
|
|
||||||
<h3 class="font-display text-base font-semibold leading-tight text-sand">
|
|
||||||
{{ selectedCat.title }}
|
|
||||||
</h3>
|
|
||||||
<p class="truncate text-xs text-sand-dim">{{ selectedCat.subtitle }}</p>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="grid size-7 shrink-0 place-items-center rounded-full border border-white/10 text-sand-dim transition hover:bg-white/10"
|
|
||||||
aria-label="Close breakdown"
|
|
||||||
@click="selected = null"
|
|
||||||
>
|
|
||||||
<Icon name="ArrowRight" class="size-4 rotate-180" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="mt-4 space-y-2">
|
|
||||||
<li
|
|
||||||
v-for="item in selectedCat.items"
|
|
||||||
:key="item.label"
|
|
||||||
class="flex items-baseline justify-between gap-3 text-sm"
|
|
||||||
>
|
|
||||||
<span class="text-sand-dim">
|
|
||||||
{{ item.label }}
|
|
||||||
<span v-if="item.note" class="text-[11px] text-sand-dim/60">· {{ item.note }}</span>
|
|
||||||
</span>
|
|
||||||
<span class="shrink-0 tabular-nums text-sand">{{ fmtUsd(item.amount) }}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="mt-4 flex items-center justify-between border-t border-white/10 pt-3">
|
|
||||||
<span class="text-xs uppercase tracking-wider text-sand-dim">Subtotal</span>
|
|
||||||
<span class="font-display text-lg font-semibold" :class="text[selectedCat.accent]">
|
|
||||||
{{ fmtUsd(categoryTotal(selectedCat)) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
key="hint"
|
|
||||||
class="mt-6 flex items-center gap-3 rounded-2xl border border-dashed border-white/15 p-5 text-sm text-sand-dim"
|
|
||||||
>
|
|
||||||
<Icon name="ArrowRight" class="size-4 shrink-0 text-amber-300" />
|
|
||||||
Select a category to see its detailed line-item breakdown.
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- right: four selectable category cards -->
|
|
||||||
<div class="space-y-2.5">
|
|
||||||
<button
|
|
||||||
v-for="c in costCategories"
|
|
||||||
:key="c.id"
|
|
||||||
class="flex w-full items-center gap-4 rounded-2xl border p-4 text-left transition"
|
|
||||||
:class="
|
|
||||||
selected === c.id
|
|
||||||
? ring[c.accent]
|
|
||||||
: 'border-white/10 bg-white/[0.03] hover:border-white/25 hover:bg-white/[0.06]'
|
|
||||||
"
|
|
||||||
:aria-pressed="selected === c.id"
|
|
||||||
@click="toggle(c.id)"
|
|
||||||
>
|
|
||||||
<span class="grid size-10 shrink-0 place-items-center rounded-xl bg-white/5" :class="text[c.accent]">
|
|
||||||
<Icon :name="c.icon" class="size-5" />
|
|
||||||
</span>
|
|
||||||
<div class="min-w-0 flex-1">
|
|
||||||
<h3 class="font-display text-base font-semibold text-sand">{{ c.title }}</h3>
|
|
||||||
<p class="truncate text-xs text-sand-dim">{{ c.subtitle }}</p>
|
|
||||||
</div>
|
|
||||||
<span class="shrink-0 font-display text-lg font-semibold text-sand">
|
|
||||||
{{ fmtUsd(categoryTotal(c)) }}
|
|
||||||
</span>
|
|
||||||
<Icon
|
|
||||||
name="ChevronRight"
|
|
||||||
class="size-4 shrink-0 text-sand-dim transition-transform"
|
|
||||||
:class="selected === c.id ? 'rotate-90' : ''"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<p class="pt-1 text-center text-[11px] text-sand-dim/70">
|
|
||||||
{{ costCategories.length }} categories · {{ fmtUsd(raiseTotal) }} total
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SlideFrame>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.detail-enter-active,
|
|
||||||
.detail-leave-active {
|
|
||||||
transition:
|
|
||||||
opacity 0.2s ease,
|
|
||||||
transform 0.2s ease;
|
|
||||||
}
|
|
||||||
.detail-enter-from,
|
|
||||||
.detail-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(6px);
|
|
||||||
}
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
.detail-enter-active,
|
|
||||||
.detail-leave-active {
|
|
||||||
transition: opacity 0.15s ease;
|
|
||||||
}
|
|
||||||
.detail-enter-from,
|
|
||||||
.detail-leave-to {
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
58
src/deck/slides/MembershipSlide.vue
Normal file
58
src/deck/slides/MembershipSlide.vue
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import SlideFrame from '../components/SlideFrame.vue'
|
||||||
|
import Icon from '../components/Icon.vue'
|
||||||
|
import { memberships, membershipComparables, fmtUsd } from '../data'
|
||||||
|
|
||||||
|
// display low → high price
|
||||||
|
const tiers = [...memberships].sort((a, b) => a.price - b.price)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SlideFrame
|
||||||
|
kicker="SolLunar Society · memberships"
|
||||||
|
title="Three ways to belong"
|
||||||
|
subtitle="Recurring membership is the core of the model — priced right for the Austin market."
|
||||||
|
>
|
||||||
|
<div class="grid gap-5 md:grid-cols-3">
|
||||||
|
<div
|
||||||
|
v-for="t in tiers"
|
||||||
|
:key="t.name"
|
||||||
|
class="relative flex flex-col rounded-2xl border p-6"
|
||||||
|
:class="t.featured ? 'border-indigo-500/50 bg-indigo-500/[0.07]' : 'border-white/10 bg-white/[0.03]'"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="t.featured"
|
||||||
|
class="absolute -top-3 left-6 rounded-full bg-indigo-400 px-3 py-0.5 text-[11px] font-semibold uppercase tracking-wider text-ink"
|
||||||
|
>
|
||||||
|
Flagship
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<h3 class="font-display text-xl font-semibold text-sand">{{ t.name }}</h3>
|
||||||
|
<div class="mt-2 flex items-baseline gap-1">
|
||||||
|
<span class="font-display text-4xl font-bold text-sand">{{ fmtUsd(t.price) }}</span>
|
||||||
|
<span class="text-sm text-sand-dim">/mo</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-1 text-xs text-sand-dim">{{ t.blurb }} · target {{ t.target }} members</p>
|
||||||
|
|
||||||
|
<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-sand-dim">
|
||||||
|
<Icon name="Check" class="mt-0.5 size-4 shrink-0 text-indigo-300" />
|
||||||
|
{{ p }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- comparables -->
|
||||||
|
<div class="mt-6 flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-sand-dim">
|
||||||
|
<span class="uppercase tracking-wider">Austin comparables</span>
|
||||||
|
<span
|
||||||
|
v-for="c in membershipComparables"
|
||||||
|
:key="c.name"
|
||||||
|
class="rounded-full border border-white/10 bg-white/5 px-3 py-1"
|
||||||
|
>
|
||||||
|
{{ c.name }} <span class="text-sand">{{ fmtUsd(c.price) }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</SlideFrame>
|
||||||
|
</template>
|
||||||
78
src/deck/slides/PartnershipSlide.vue
Normal file
78
src/deck/slides/PartnershipSlide.vue
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import SlideFrame from '../components/SlideFrame.vue'
|
||||||
|
import Icon from '../components/Icon.vue'
|
||||||
|
import { partnership, fmtUsd } from '../data'
|
||||||
|
|
||||||
|
const barBg: Record<string, string> = {
|
||||||
|
indigo: 'bg-indigo-500',
|
||||||
|
pine: 'bg-pine-500',
|
||||||
|
amber: 'bg-amber-400',
|
||||||
|
}
|
||||||
|
const text: Record<string, string> = {
|
||||||
|
indigo: 'text-indigo-300',
|
||||||
|
pine: 'text-pine-300',
|
||||||
|
amber: 'text-amber-300',
|
||||||
|
}
|
||||||
|
const ring: Record<string, string> = {
|
||||||
|
indigo: 'border-indigo-500/40 bg-indigo-500/[0.06]',
|
||||||
|
pine: 'border-pine-500/40 bg-pine-500/[0.06]',
|
||||||
|
amber: 'border-amber-400/40 bg-amber-400/[0.06]',
|
||||||
|
}
|
||||||
|
const pct = (e: number) => Math.round(e * 100)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SlideFrame
|
||||||
|
kicker="Ownership"
|
||||||
|
title="A three-way partnership"
|
||||||
|
subtitle="SolLunar Society is a joint venture. Investors buy in alongside the two operating partners."
|
||||||
|
>
|
||||||
|
<!-- equity bar -->
|
||||||
|
<div class="flex h-5 w-full overflow-hidden rounded-full">
|
||||||
|
<div
|
||||||
|
v-for="p in partnership.partners"
|
||||||
|
:key="p.name"
|
||||||
|
class="flex h-full items-center justify-center text-[11px] font-semibold text-ink"
|
||||||
|
:class="barBg[p.accent]"
|
||||||
|
:style="{ width: `${pct(p.equity)}%` }"
|
||||||
|
>
|
||||||
|
{{ pct(p.equity) }}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 grid gap-4 md:grid-cols-3">
|
||||||
|
<div
|
||||||
|
v-for="p in partnership.partners"
|
||||||
|
:key="p.name"
|
||||||
|
class="flex flex-col rounded-2xl border p-5"
|
||||||
|
:class="ring[p.accent]"
|
||||||
|
>
|
||||||
|
<div class="flex items-baseline justify-between gap-2">
|
||||||
|
<h3 class="font-display text-lg font-semibold text-sand">{{ p.name }}</h3>
|
||||||
|
<span class="font-display text-2xl font-bold" :class="text[p.accent]">{{ pct(p.equity) }}%</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs uppercase tracking-wider" :class="text[p.accent]">{{ p.role }}</p>
|
||||||
|
|
||||||
|
<ul class="mt-4 flex-1 space-y-1.5">
|
||||||
|
<li v-for="r in p.responsibilities" :key="r" class="flex items-start gap-2 text-sm text-sand-dim">
|
||||||
|
<Icon name="Check" class="mt-0.5 size-3.5 shrink-0" :class="text[p.accent]" />
|
||||||
|
{{ r }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p v-if="p.note" class="mt-3 border-t border-white/10 pt-3 text-[11px] leading-relaxed text-sand-dim">
|
||||||
|
{{ p.note }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5 flex flex-wrap items-center justify-center gap-x-6 gap-y-1 text-sm text-sand-dim">
|
||||||
|
<span>The ask:</span>
|
||||||
|
<span class="font-display text-lg font-semibold text-sand">
|
||||||
|
{{ fmtUsd(partnership.raiseAmount) }} for {{ pct(partnership.equityOffered) }}%
|
||||||
|
</span>
|
||||||
|
<span>·</span>
|
||||||
|
<span>{{ fmtUsd(partnership.valuation) }} valuation</span>
|
||||||
|
</div>
|
||||||
|
</SlideFrame>
|
||||||
|
</template>
|
||||||
166
src/deck/slides/PnlSlide.vue
Normal file
166
src/deck/slides/PnlSlide.vue
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import SlideFrame from '../components/SlideFrame.vue'
|
||||||
|
import Icon from '../components/Icon.vue'
|
||||||
|
import { pnlGroups, groupTotal, pnl, pnlDocSummary, fmtUsd } from '../data'
|
||||||
|
|
||||||
|
const barBg: Record<string, string> = {
|
||||||
|
pine: 'bg-pine-500',
|
||||||
|
indigo: 'bg-indigo-500',
|
||||||
|
rose: 'bg-rose-500',
|
||||||
|
clay: 'bg-clay-400',
|
||||||
|
}
|
||||||
|
const text: Record<string, string> = {
|
||||||
|
pine: 'text-pine-300',
|
||||||
|
indigo: 'text-indigo-300',
|
||||||
|
rose: 'text-rose-300',
|
||||||
|
clay: 'text-clay-400',
|
||||||
|
}
|
||||||
|
const ring: Record<string, string> = {
|
||||||
|
pine: 'border-pine-500/50 bg-pine-500/[0.06]',
|
||||||
|
indigo: 'border-indigo-500/50 bg-indigo-500/[0.06]',
|
||||||
|
rose: 'border-rose-500/50 bg-rose-500/[0.06]',
|
||||||
|
clay: 'border-clay-400/50 bg-clay-400/[0.06]',
|
||||||
|
}
|
||||||
|
|
||||||
|
const revenueGroups = pnlGroups.filter((g) => g.kind === 'revenue')
|
||||||
|
|
||||||
|
const selected = ref<string | null>(null)
|
||||||
|
const selectedGroup = computed(() => pnlGroups.find((g) => g.id === selected.value) ?? null)
|
||||||
|
function toggle(id: string) {
|
||||||
|
selected.value = selected.value === id ? null : id
|
||||||
|
}
|
||||||
|
const revPct = (id: string) =>
|
||||||
|
(groupTotal(pnlGroups.find((g) => g.id === id)!) / pnl.monthlyRevenue) * 100
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SlideFrame
|
||||||
|
kicker="SolLunar Society · unit economics"
|
||||||
|
title="The numbers"
|
||||||
|
subtitle="Real monthly P&L for the Austin node. Tap any category to break it down."
|
||||||
|
>
|
||||||
|
<!-- headline stats -->
|
||||||
|
<div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||||
|
<div class="rounded-2xl border border-white/10 bg-white/[0.03] p-4">
|
||||||
|
<p class="text-xs uppercase tracking-wider text-sand-dim">Revenue / mo</p>
|
||||||
|
<p class="mt-1 font-display text-2xl font-bold text-sand">{{ fmtUsd(pnl.monthlyRevenue) }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-2xl border border-white/10 bg-white/[0.03] p-4">
|
||||||
|
<p class="text-xs uppercase tracking-wider text-sand-dim">Expenses / mo</p>
|
||||||
|
<p class="mt-1 font-display text-2xl font-bold text-sand">{{ fmtUsd(pnl.monthlyExpenses) }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-2xl border border-pine-500/40 bg-pine-500/[0.07] p-4">
|
||||||
|
<p class="text-xs uppercase tracking-wider text-pine-300">Profit / mo</p>
|
||||||
|
<p class="mt-1 font-display text-2xl font-bold text-sand">{{ fmtUsd(pnl.monthlyProfit) }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-2xl border border-amber-400/40 bg-amber-400/[0.07] p-4">
|
||||||
|
<p class="text-xs uppercase tracking-wider text-amber-300">Profit / yr</p>
|
||||||
|
<p class="mt-1 font-display text-2xl font-bold text-sand">{{ fmtUsd(pnl.yearlyProfit) }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 grid items-start gap-8 lg:grid-cols-[1fr_1fr]">
|
||||||
|
<!-- revenue mix bar + live detail -->
|
||||||
|
<div>
|
||||||
|
<p class="mb-2 text-xs uppercase tracking-wider text-sand-dim">Revenue mix</p>
|
||||||
|
<div class="flex h-4 w-full overflow-hidden rounded-full">
|
||||||
|
<button
|
||||||
|
v-for="g in revenueGroups"
|
||||||
|
:key="g.id"
|
||||||
|
class="h-full cursor-pointer transition-opacity"
|
||||||
|
:class="[barBg[g.accent], selected && selected !== g.id ? 'opacity-30' : 'opacity-100']"
|
||||||
|
:style="{ width: `${revPct(g.id)}%` }"
|
||||||
|
:title="`${g.title} · ${fmtUsd(groupTotal(g))}`"
|
||||||
|
@click="toggle(g.id)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Transition name="fade" mode="out-in">
|
||||||
|
<div
|
||||||
|
v-if="selectedGroup"
|
||||||
|
:key="selectedGroup.id"
|
||||||
|
class="mt-5 rounded-2xl border p-5"
|
||||||
|
:class="ring[selectedGroup.accent]"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Icon :name="selectedGroup.icon" class="size-5" :class="text[selectedGroup.accent]" />
|
||||||
|
<h3 class="font-display text-base font-semibold text-sand">{{ selectedGroup.title }}</h3>
|
||||||
|
<span class="ml-auto font-display text-lg font-semibold" :class="text[selectedGroup.accent]">
|
||||||
|
{{ fmtUsd(groupTotal(selectedGroup)) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ul class="mt-3 space-y-2">
|
||||||
|
<li
|
||||||
|
v-for="item in selectedGroup.items"
|
||||||
|
:key="item.label"
|
||||||
|
class="flex items-baseline justify-between gap-3 text-sm"
|
||||||
|
>
|
||||||
|
<span class="text-sand-dim">
|
||||||
|
{{ item.label }}
|
||||||
|
<span v-if="item.note" class="text-[11px] text-sand-dim/60">· {{ item.note }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="shrink-0 tabular-nums text-sand">{{ fmtUsd(item.amount) }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
key="hint"
|
||||||
|
class="mt-5 flex items-center gap-3 rounded-2xl border border-dashed border-white/15 p-5 text-sm text-sand-dim"
|
||||||
|
>
|
||||||
|
<Icon name="ArrowRight" class="size-4 shrink-0 text-amber-300" />
|
||||||
|
Tap a category to see its line items.
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- clickable category list -->
|
||||||
|
<div class="space-y-2.5">
|
||||||
|
<button
|
||||||
|
v-for="g in pnlGroups"
|
||||||
|
:key="g.id"
|
||||||
|
class="flex w-full items-center gap-4 rounded-2xl border p-4 text-left transition"
|
||||||
|
:class="
|
||||||
|
selected === g.id
|
||||||
|
? ring[g.accent]
|
||||||
|
: 'border-white/10 bg-white/[0.03] hover:border-white/25 hover:bg-white/[0.06]'
|
||||||
|
"
|
||||||
|
@click="toggle(g.id)"
|
||||||
|
>
|
||||||
|
<span class="grid size-10 shrink-0 place-items-center rounded-xl bg-white/5" :class="text[g.accent]">
|
||||||
|
<Icon :name="g.icon" class="size-5" />
|
||||||
|
</span>
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<h3 class="font-display text-base font-semibold text-sand">{{ g.title }}</h3>
|
||||||
|
<p class="text-xs uppercase tracking-wider text-sand-dim">{{ g.kind }}</p>
|
||||||
|
</div>
|
||||||
|
<span class="shrink-0 font-display text-lg font-semibold" :class="text[g.accent]">
|
||||||
|
{{ fmtUsd(groupTotal(g)) }}
|
||||||
|
</span>
|
||||||
|
<Icon name="ChevronRight" class="size-4 shrink-0 text-sand-dim transition-transform" :class="selected === g.id ? 'rotate-90' : ''" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-4 text-[11px] text-sand-dim/70">
|
||||||
|
Figures recomputed from the plan's line items. The source summary stated
|
||||||
|
{{ fmtUsd(pnlDocSummary.revenue) }} rev / {{ fmtUsd(pnlDocSummary.expenses) }} exp —
|
||||||
|
being reconciled. Lunarpunk Locals count is TBD.
|
||||||
|
</p>
|
||||||
|
</SlideFrame>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition:
|
||||||
|
opacity 0.2s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
|
}
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(6px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import SlideFrame from '../components/SlideFrame.vue'
|
|
||||||
import Icon from '../components/Icon.vue'
|
|
||||||
import { revenueStreams } from '../data'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<SlideFrame
|
|
||||||
kicker="Not a cost centre — a business"
|
|
||||||
title="The nodes are fully operational event spaces"
|
|
||||||
subtitle="Every entity earns while it demonstrates the technology. Six revenue streams across the collaboration."
|
|
||||||
>
|
|
||||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
||||||
<div
|
|
||||||
v-for="r in revenueStreams"
|
|
||||||
:key="r.title"
|
|
||||||
class="flex items-start gap-4 rounded-2xl border border-white/10 bg-white/[0.03] p-5"
|
|
||||||
>
|
|
||||||
<span class="grid size-11 shrink-0 place-items-center rounded-xl bg-amber-400/10 text-amber-300">
|
|
||||||
<Icon :name="r.icon" class="size-5" />
|
|
||||||
</span>
|
|
||||||
<div>
|
|
||||||
<h3 class="font-display text-lg font-semibold text-sand">{{ r.title }}</h3>
|
|
||||||
<p class="mt-1 text-sm text-sand-dim">{{ r.note }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="mt-8 text-sm text-sand-dim">
|
|
||||||
Investors get access to these spaces — stays, retreats and residencies included in angel tiers.
|
|
||||||
</p>
|
|
||||||
</SlideFrame>
|
|
||||||
</template>
|
|
||||||
41
src/deck/slides/SpacesSlide.vue
Normal file
41
src/deck/slides/SpacesSlide.vue
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import SlideFrame from '../components/SlideFrame.vue'
|
||||||
|
import Icon from '../components/Icon.vue'
|
||||||
|
import { buildings } from '../data'
|
||||||
|
|
||||||
|
const icons = ['Building2', 'BedDouble', 'Cpu']
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SlideFrame
|
||||||
|
kicker="SolLunar Society · the building"
|
||||||
|
title="One address, three buildings"
|
||||||
|
subtitle="A members' society up front, two Yellow Houses behind — lodging, coworking and the data center that runs it all."
|
||||||
|
>
|
||||||
|
<div class="grid gap-4 md:grid-cols-3">
|
||||||
|
<div
|
||||||
|
v-for="(b, i) in buildings"
|
||||||
|
:key="b.name"
|
||||||
|
class="flex flex-col rounded-2xl border border-white/10 bg-white/[0.03] p-5"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="grid size-10 place-items-center rounded-xl bg-indigo-500/10 text-indigo-300">
|
||||||
|
<Icon :name="icons[i] ?? 'Building2'" class="size-5" />
|
||||||
|
</span>
|
||||||
|
<h3 class="font-display text-base font-semibold leading-tight text-sand">{{ b.name }}</h3>
|
||||||
|
</div>
|
||||||
|
<ul class="mt-4 space-y-1.5">
|
||||||
|
<li v-for="r in b.rooms" :key="r" class="flex items-center gap-2 text-sm text-sand-dim">
|
||||||
|
<span class="size-1.5 rounded-full bg-indigo-400" />
|
||||||
|
{{ r }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-6 text-sm text-sand-dim">
|
||||||
|
The Yellow House data center makes SolLunar self-hosting — the Archipelago stack runs on-site,
|
||||||
|
not in someone else's cloud.
|
||||||
|
</p>
|
||||||
|
</SlideFrame>
|
||||||
|
</template>
|
||||||
32
src/deck/slides/TenantsSlide.vue
Normal file
32
src/deck/slides/TenantsSlide.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import SlideFrame from '../components/SlideFrame.vue'
|
||||||
|
import Icon from '../components/Icon.vue'
|
||||||
|
import { tenants } from '../data'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SlideFrame
|
||||||
|
kicker="SolLunar Society · tenants"
|
||||||
|
title="Five founding tenants"
|
||||||
|
subtitle="The hub is anchored by businesses that are also the community — each one a reason to become a member."
|
||||||
|
>
|
||||||
|
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
<div
|
||||||
|
v-for="t in tenants"
|
||||||
|
:key="t.name"
|
||||||
|
class="flex flex-col rounded-2xl border border-white/10 bg-white/[0.03] p-5 transition hover:border-indigo-500/40 hover:bg-indigo-500/5"
|
||||||
|
>
|
||||||
|
<span class="grid size-10 place-items-center rounded-xl bg-indigo-500/10 text-indigo-300">
|
||||||
|
<Icon :name="t.icon" class="size-5" />
|
||||||
|
</span>
|
||||||
|
<h3 class="mt-4 font-display text-lg font-semibold text-sand">{{ t.name }}</h3>
|
||||||
|
<p class="mt-1.5 text-sm leading-relaxed text-sand-dim">{{ t.blurb }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-6 text-sm text-sand-dim">
|
||||||
|
Tenants pay rent <span class="text-sand">and</span> drive events, retail and food traffic —
|
||||||
|
the same footfall that fills memberships.
|
||||||
|
</p>
|
||||||
|
</SlideFrame>
|
||||||
|
</template>
|
||||||
|
|
@ -35,7 +35,12 @@ import { brand, surfaceEntities, accentText } from '../data'
|
||||||
{{ brand.subtagline }}
|
{{ brand.subtagline }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="mt-12 text-xs text-sand-dim">
|
<span class="mt-6 inline-flex items-center gap-2 rounded-full border border-indigo-500/40 bg-indigo-500/10 px-4 py-1.5 text-xs font-medium text-indigo-300">
|
||||||
|
<span class="size-1.5 rounded-full bg-indigo-400" />
|
||||||
|
Raising now · {{ brand.firstNode }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<p class="mt-10 text-xs text-sand-dim">
|
||||||
Use <kbd class="rounded border border-white/15 px-1.5 py-0.5">→</kbd> /
|
Use <kbd class="rounded border border-white/15 px-1.5 py-0.5">→</kbd> /
|
||||||
<kbd class="rounded border border-white/15 px-1.5 py-0.5">←</kbd> to navigate
|
<kbd class="rounded border border-white/15 px-1.5 py-0.5">←</kbd> to navigate
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue