feat(deck): add the 14 pitch slides

Title, vision, network, technology, two nodes, city, farm, revenue,
the raise, cost breakdown, angel tiers, contributor tiers, roadmap, ask.
Contributor CTAs are a presentational seam for LNbits/nostr later.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Patrick Mulligan 2026-07-28 18:21:17 +02:00
commit cb4b845f90
14 changed files with 495 additions and 0 deletions

View file

@ -0,0 +1,52 @@
<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>

View file

@ -0,0 +1,41 @@
<script setup lang="ts">
import SlideFrame from '../components/SlideFrame.vue'
import Icon from '../components/Icon.vue'
import PlaceholderBadge from '../components/PlaceholderBadge.vue'
import { brand, raiseTotal, fmtUsd } from '../data'
</script>
<template>
<SlideFrame center>
<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">
Help us grow the first two flowers
</h2>
<div class="mt-8 flex items-baseline justify-center gap-3">
<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.fullName }} an angel round for equity partners, open to backers of every size.
</p>
<div class="mt-10 flex flex-col items-center gap-3 sm:flex-row">
<a
: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"
>
<Icon name="Mail" class="size-5" />
{{ brand.contactEmail }}
</a>
<button
class="inline-flex items-center gap-2 rounded-full border border-white/15 bg-white/5 px-6 py-3 font-semibold text-sand transition hover:bg-white/10"
>
<Icon name="CalendarClock" class="size-5" />
Book an investor call
</button>
</div>
<p class="mt-10 font-display text-lg text-pine-300">{{ brand.tagline }}</p>
</SlideFrame>
</template>

View file

@ -0,0 +1,8 @@
<script setup lang="ts">
import LocationDetail from '../components/LocationDetail.vue'
import { locations } from '../data'
</script>
<template>
<LocationDetail :location="locations.city" accent="pine" />
</template>

View file

@ -0,0 +1,49 @@
<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 &amp; nostr when we go live.
</p>
</SlideFrame>
</template>

View file

@ -0,0 +1,50 @@
<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 heads = ['text-pine-300', 'text-amber-300', '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-4 md:grid-cols-3">
<div
v-for="(c, i) in costCategories"
:key="c.id"
class="flex flex-col rounded-2xl border border-white/10 bg-white/[0.03] p-5"
>
<div class="flex items-center gap-2">
<Icon :name="c.icon" class="size-5" :class="heads[i]" />
<h3 class="font-display text-base font-semibold text-sand">{{ c.title }}</h3>
</div>
<ul class="mt-4 flex-1 space-y-2.5">
<li v-for="item in c.items" :key="item.label" class="text-sm">
<div class="flex items-baseline justify-between gap-3">
<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-[11px] text-sand-dim/70">{{ item.note }}</p>
</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="heads[i]">
{{ fmtUsd(categoryTotal(c)) }}
</span>
</div>
</div>
</div>
<p class="mt-5 flex items-center gap-2 text-xs text-sand-dim">
<PlaceholderBadge /> All amounts are illustrative pending your real quotes & salary bands.
</p>
</SlideFrame>
</template>

View file

@ -0,0 +1,8 @@
<script setup lang="ts">
import LocationDetail from '../components/LocationDetail.vue'
import { locations } from '../data'
</script>
<template>
<LocationDetail :location="locations.farm" accent="clay" />
</template>

View file

@ -0,0 +1,58 @@
<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, raiseTotal, fmtUsd } from '../data'
const tones = ['bg-pine-500', 'bg-amber-400', 'bg-clay-400']
const dot = ['text-pine-300', 'text-amber-300', 'text-clay-400']
</script>
<template>
<SlideFrame kicker="The raise" title="Where the investment goes">
<div class="grid items-center gap-10 lg:grid-cols-[1fr_1.1fr]">
<!-- headline number + proportion bar -->
<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">Total to launch both proof-of-concept nodes.</p>
<div class="mt-8 flex h-4 w-full overflow-hidden rounded-full">
<div
v-for="(c, i) in costCategories"
:key="c.id"
:class="tones[i]"
:style="{ width: `${(categoryTotal(c) / raiseTotal) * 100}%` }"
class="h-full"
/>
</div>
<div class="mt-3 flex flex-wrap gap-x-6 gap-y-1 text-xs text-sand-dim">
<span v-for="(c, i) in costCategories" :key="c.id" class="flex items-center gap-2">
<span class="size-2 rounded-full" :class="tones[i]" />
{{ Math.round((categoryTotal(c) / raiseTotal) * 100) }}%
</span>
</div>
</div>
<!-- three categories -->
<div class="space-y-3">
<div
v-for="(c, i) in costCategories"
:key="c.id"
class="flex items-center 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-white/5" :class="dot[i]">
<Icon :name="c.icon" class="size-5" />
</span>
<div class="min-w-0 flex-1">
<h3 class="font-display text-lg font-semibold text-sand">{{ c.title }}</h3>
<p class="truncate text-xs text-sand-dim">{{ c.subtitle }}</p>
</div>
<span class="font-display text-xl font-semibold text-sand">{{ fmtUsd(categoryTotal(c)) }}</span>
</div>
</div>
</div>
</SlideFrame>
</template>

View file

@ -0,0 +1,51 @@
<script setup lang="ts">
import SlideFrame from '../components/SlideFrame.vue'
import Icon from '../components/Icon.vue'
import PlaceholderBadge from '../components/PlaceholderBadge.vue'
import { locations } from '../data'
const list = [locations.city, locations.farm]
const accents = ['text-pine-300 border-pine-500/30 bg-pine-500/5', 'text-clay-400 border-clay-400/30 bg-clay-400/5']
</script>
<template>
<SlideFrame
kicker="Two proof-of-concept launches"
title="One network, two very different flowers"
subtitle="Deliberately opposite environments — an urban flagship and an off-grid farm — to prove the stack works anywhere."
>
<div class="grid gap-6 md:grid-cols-2">
<div
v-for="(loc, i) in list"
:key="loc.id"
class="flex flex-col rounded-2xl border p-6"
:class="accents[i]"
>
<div class="flex items-center gap-3">
<span class="grid size-11 place-items-center rounded-xl bg-white/5">
<Icon :name="loc.icon" class="size-6" />
</span>
<div>
<div class="flex items-center gap-2">
<h3 class="font-display text-2xl font-semibold text-sand">{{ loc.name }}</h3>
<PlaceholderBadge label="name" />
</div>
<p class="text-xs uppercase tracking-wider text-sand-dim">{{ loc.kind }}</p>
</div>
</div>
<p class="mt-4 text-sm leading-relaxed text-sand-dim">{{ loc.blurb }}</p>
<div class="mt-5 flex flex-wrap gap-2">
<span
v-for="s in loc.spaces"
:key="s"
class="rounded-full border border-white/10 bg-white/5 px-3 py-1 text-xs text-sand"
>
{{ s }}
</span>
</div>
</div>
</div>
</SlideFrame>
</template>

View file

@ -0,0 +1,14 @@
<script setup lang="ts">
import SlideFrame from '../components/SlideFrame.vue'
import MyceliumNetwork from '../components/MyceliumNetwork.vue'
</script>
<template>
<SlideFrame
kicker="The network"
title="A mycelial network"
subtitle="What you see are the locations. What makes them work is the network beneath them — and it grows the same way mycelium does: spread the network once, then fruit anywhere."
>
<MyceliumNetwork />
</SlideFrame>
</template>

View file

@ -0,0 +1,33 @@
<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 location earns while it demonstrates the technology. Six revenue streams across both nodes."
>
<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>

View file

@ -0,0 +1,37 @@
<script setup lang="ts">
import SlideFrame from '../components/SlideFrame.vue'
import { roadmap } from '../data'
</script>
<template>
<SlideFrame
kicker="The replication play"
title="Prove it twice, then grow it anywhere"
subtitle="The two nodes aren't the endgame — they're the seed. The blueprint is the business."
>
<ol class="relative grid gap-6 md:grid-cols-4">
<!-- connecting line -->
<div class="absolute left-0 right-0 top-5 hidden h-px bg-gradient-to-r from-pine-500/40 via-amber-400/40 to-clay-400/40 md:block" />
<li v-for="(p, i) in roadmap" :key="p.phase" class="relative">
<div class="flex items-center gap-3 md:block">
<span
class="z-10 grid size-10 shrink-0 place-items-center rounded-full border border-white/15 bg-ink font-display text-sm font-bold text-amber-300"
>
{{ i + 1 }}
</span>
<span class="mt-3 block text-[11px] font-semibold uppercase tracking-wider text-pine-300">
{{ p.phase }} · {{ p.when }}
</span>
</div>
<h3 class="mt-2 font-display text-lg font-semibold text-sand">{{ p.title }}</h3>
<p class="mt-1 text-sm text-sand-dim">{{ p.body }}</p>
</li>
</ol>
<p class="mt-10 rounded-2xl border border-pine-500/20 bg-pine-500/5 p-5 text-center text-sand">
Once the mycelium runs, every new flower is
<span class="font-semibold text-pine-300">marginal cost, not a new company.</span>
</p>
</SlideFrame>
</template>

View file

@ -0,0 +1,27 @@
<script setup lang="ts">
import SlideFrame from '../components/SlideFrame.vue'
import Icon from '../components/Icon.vue'
import { tech } from '../data'
</script>
<template>
<SlideFrame
kicker="The technology"
title="Open hardware, sovereign software"
subtitle="The stack under every node — owned by the network, documented to be rebuilt anywhere."
>
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<div
v-for="t in tech"
:key="t.title"
class="rounded-2xl border border-white/10 bg-white/[0.03] p-5 transition hover:border-pine-500/40 hover:bg-pine-500/5"
>
<span class="grid size-10 place-items-center rounded-xl bg-pine-500/10 text-pine-300">
<Icon :name="t.icon" class="size-5" />
</span>
<h3 class="mt-4 font-display text-lg font-semibold text-sand">{{ t.title }}</h3>
<p class="mt-1.5 text-sm leading-relaxed text-sand-dim">{{ t.body }}</p>
</div>
</div>
</SlideFrame>
</template>

View file

@ -0,0 +1,33 @@
<script setup lang="ts">
import SlideFrame from '../components/SlideFrame.vue'
import PlaceholderBadge from '../components/PlaceholderBadge.vue'
import { brand } from '../data'
</script>
<template>
<SlideFrame center>
<div class="flex flex-col items-center">
<span class="mb-6 inline-flex items-center gap-2 rounded-full border border-pine-500/30 bg-pine-500/10 px-4 py-1.5 text-xs uppercase tracking-[0.3em] text-pine-300">
Investment prospectus
<PlaceholderBadge label="draft" />
</span>
<h1 class="font-display text-6xl font-bold tracking-tight text-sand sm:text-7xl lg:text-8xl">
{{ brand.name }}
</h1>
<p class="mt-4 text-lg text-pine-300 sm:text-xl">{{ brand.fullName }}</p>
<p class="mt-8 max-w-2xl text-balance text-xl text-sand sm:text-2xl">
{{ brand.tagline }}
</p>
<p class="mt-4 max-w-xl text-balance text-sm text-sand-dim sm:text-base">
{{ brand.subtagline }}
</p>
<p class="mt-14 text-xs text-sand-dim">
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
</p>
</div>
</SlideFrame>
</template>

View file

@ -0,0 +1,34 @@
<script setup lang="ts">
import SlideFrame from '../components/SlideFrame.vue'
import Icon from '../components/Icon.vue'
import { problem } from '../data'
</script>
<template>
<SlideFrame kicker="The problem" :title="problem.headline">
<div class="grid gap-10 lg:grid-cols-[1.1fr_1fr]">
<ul class="space-y-5">
<li
v-for="(p, i) in problem.points"
:key="i"
class="flex items-start gap-4 text-lg text-sand-dim"
>
<span class="mt-1 grid size-6 shrink-0 place-items-center rounded-full bg-white/5 text-xs font-semibold text-amber-300">
{{ i + 1 }}
</span>
{{ p }}
</li>
</ul>
<div class="flex flex-col justify-center rounded-2xl border border-pine-500/20 bg-pine-500/5 p-8">
<Icon name="HeartHandshake" class="size-8 text-pine-300" />
<p class="mt-4 text-xs font-semibold uppercase tracking-[0.2em] text-pine-300">
Our thesis
</p>
<p class="mt-3 font-display text-2xl leading-snug text-sand">
{{ problem.thesis }}
</p>
</div>
</div>
</SlideFrame>
</template>