feat(deck): entity + ecosystem slides and 16-slide registry

Title becomes the 3-way collaboration; new EntitiesSlide (three partners),
per-entity deep-dives (Archipelago/Solunar/Chateau) via a shared
EntityDetail, and an EcosystemSlide selling the identical membership/kiosk/
app experience at every location. Deck registry reordered to 16 slides.
(Old Locations/City/Farm slides + LocationDetail were removed in the
preceding commit.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Patrick Mulligan 2026-07-28 18:35:15 +02:00
commit 7acc2b550d
8 changed files with 205 additions and 14 deletions

View file

@ -8,9 +8,11 @@ import TitleSlide from './slides/TitleSlide.vue'
import VisionSlide from './slides/VisionSlide.vue' import VisionSlide from './slides/VisionSlide.vue'
import NetworkSlide from './slides/NetworkSlide.vue' import NetworkSlide from './slides/NetworkSlide.vue'
import TechSlide from './slides/TechSlide.vue' import TechSlide from './slides/TechSlide.vue'
import LocationsSlide from './slides/LocationsSlide.vue' import EntitiesSlide from './slides/EntitiesSlide.vue'
import CitySlide from './slides/CitySlide.vue' import ArchipelagoSlide from './slides/ArchipelagoSlide.vue'
import FarmSlide from './slides/FarmSlide.vue' import SolunarSlide from './slides/SolunarSlide.vue'
import ChateauSlide from './slides/ChateauSlide.vue'
import EcosystemSlide from './slides/EcosystemSlide.vue'
import RevenueSlide from './slides/RevenueSlide.vue' import RevenueSlide from './slides/RevenueSlide.vue'
import InvestmentOverviewSlide from './slides/InvestmentOverviewSlide.vue' import InvestmentOverviewSlide from './slides/InvestmentOverviewSlide.vue'
import CostBreakdownSlide from './slides/CostBreakdownSlide.vue' import CostBreakdownSlide from './slides/CostBreakdownSlide.vue'
@ -23,10 +25,12 @@ const slides = [
{ id: 'title', label: 'Title', component: TitleSlide }, { id: 'title', label: 'Title', component: TitleSlide },
{ id: 'vision', label: 'Vision', component: VisionSlide }, { id: 'vision', label: 'Vision', component: VisionSlide },
{ id: 'network', label: 'The network', component: NetworkSlide }, { id: 'network', label: 'The network', component: NetworkSlide },
{ id: 'tech', label: 'Technology', component: TechSlide }, { id: 'tech', label: 'Core stack', component: TechSlide },
{ id: 'locations', label: 'Two nodes', component: LocationsSlide }, { id: 'entities', label: 'Three entities', component: EntitiesSlide },
{ id: 'city', label: 'City node', component: CitySlide }, { id: 'archipelago', label: 'Archipelago', component: ArchipelagoSlide },
{ id: 'farm', label: 'Farm node', component: FarmSlide }, { id: 'solunar', label: 'Solunar Society', component: SolunarSlide },
{ id: 'chateau', label: 'Chateau du Faune', component: ChateauSlide },
{ id: 'ecosystem', label: 'One membership', component: EcosystemSlide },
{ id: 'revenue', label: 'Revenue', component: RevenueSlide }, { id: 'revenue', label: 'Revenue', component: RevenueSlide },
{ id: 'invest-overview', label: 'The raise', component: InvestmentOverviewSlide }, { id: 'invest-overview', label: 'The raise', component: InvestmentOverviewSlide },
{ id: 'cost-breakdown', label: 'Cost breakdown', component: CostBreakdownSlide }, { id: 'cost-breakdown', label: 'Cost breakdown', component: CostBreakdownSlide },
@ -48,7 +52,7 @@ const transitionName = computed(() => (direction.value === 1 ? 'slide-next' : 's
class="pointer-events-none absolute inset-x-0 top-0 z-20 flex items-center justify-between px-6 py-5 text-sand-dim" class="pointer-events-none absolute inset-x-0 top-0 z-20 flex items-center justify-between px-6 py-5 text-sand-dim"
> >
<span class="font-display text-sm font-semibold tracking-wide text-sand"> <span class="font-display text-sm font-semibold tracking-wide text-sand">
{{ brand.name }} {{ brand.short }}
</span> </span>
<span class="text-xs uppercase tracking-[0.2em]">{{ current.label }}</span> <span class="text-xs uppercase tracking-[0.2em]">{{ current.label }}</span>
</header> </header>

View file

@ -0,0 +1,76 @@
<script setup lang="ts">
import SlideFrame from './SlideFrame.vue'
import Icon from './Icon.vue'
import PlaceholderBadge from './PlaceholderBadge.vue'
import { accentText, accentRing, accentDot, type Entity } from '../data'
const props = defineProps<{ entity: Entity }>()
const a = props.entity.accent
</script>
<template>
<SlideFrame :kicker="entity.kind">
<div class="flex flex-wrap items-center gap-3">
<h2 class="font-display text-4xl font-semibold text-sand lg:text-5xl">{{ entity.name }}</h2>
<span
class="rounded-full border px-3 py-0.5 text-xs uppercase tracking-wider"
:class="[accentRing[a], accentText[a]]"
>
{{ entity.role }}
</span>
<PlaceholderBadge label="name" />
</div>
<p class="mt-4 max-w-2xl text-lg text-sand-dim">{{ entity.blurb }}</p>
<div class="mt-8 grid gap-6 lg:grid-cols-3">
<!-- unique traits -->
<div class="lg:col-span-1">
<p class="text-xs font-semibold uppercase tracking-[0.2em]" :class="accentText[a]">
What makes it unique
</p>
<ul class="mt-4 space-y-3">
<li v-for="t in entity.traits" :key="t" class="flex items-start gap-3 text-sm text-sand-dim">
<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]" />
</span>
{{ t }}
</li>
</ul>
</div>
<!-- spaces -->
<div class="rounded-2xl border p-6" :class="accentRing[a]">
<p class="text-xs font-semibold uppercase tracking-[0.2em]" :class="accentText[a]">On site</p>
<div class="mt-4 grid grid-cols-1 gap-2">
<div
v-for="s in entity.spaces"
:key="s"
class="flex items-center gap-2 rounded-lg bg-white/[0.03] px-3 py-2 text-sm text-sand"
>
<span class="size-1.5 rounded-full" :class="accentDot[a]" />
{{ s }}
</div>
</div>
</div>
<!-- revenue + shared-stack reminder -->
<div class="flex flex-col gap-4">
<div class="rounded-2xl border p-6" :class="accentRing[a]">
<p class="text-xs font-semibold uppercase tracking-[0.2em]" :class="accentText[a]">
How it earns
</p>
<ul class="mt-3 space-y-1.5 text-sm text-sand-dim">
<li v-for="r in entity.revenue" :key="r" class="flex items-center gap-2">
<Icon name="Coins" class="size-3.5" :class="accentText[a]" />
{{ r }}
</li>
</ul>
</div>
<div class="flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-xs text-sand-dim">
<Icon name="Layers" class="size-4 text-amber-300" />
Runs the full core stack same membership, kiosk &amp; app as every node.
</div>
</div>
</div>
</SlideFrame>
</template>

View file

@ -0,0 +1,8 @@
<script setup lang="ts">
import EntityDetail from '../components/EntityDetail.vue'
import { entityById } from '../data'
</script>
<template>
<EntityDetail :entity="entityById('archipelago')" />
</template>

View file

@ -0,0 +1,8 @@
<script setup lang="ts">
import EntityDetail from '../components/EntityDetail.vue'
import { entityById } from '../data'
</script>
<template>
<EntityDetail :entity="entityById('chateau')" />
</template>

View file

@ -0,0 +1,32 @@
<script setup lang="ts">
import SlideFrame from '../components/SlideFrame.vue'
import Icon from '../components/Icon.vue'
import { userEcosystem, entities } from '../data'
</script>
<template>
<SlideFrame
kicker="One membership, everywhere"
title="The same full experience, at every location"
subtitle="Wherever you are in the network — city, festival or farm — the ecosystem is identical. Your card, your money, your identity travel with you."
>
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<div
v-for="item in userEcosystem"
:key="item.title"
class="rounded-2xl border border-white/10 bg-white/[0.03] p-5"
>
<span class="grid size-10 place-items-center rounded-xl bg-amber-400/10 text-amber-300">
<Icon :name="item.icon" class="size-5" />
</span>
<h3 class="mt-4 font-display text-lg font-semibold text-sand">{{ item.title }}</h3>
<p class="mt-1.5 text-sm leading-relaxed text-sand-dim">{{ item.body }}</p>
</div>
</div>
<div class="mt-6 flex flex-wrap items-center justify-center gap-x-3 gap-y-1 text-xs text-sand-dim">
<span>Works identically at</span>
<span v-for="e in entities" :key="e.id" class="font-medium text-sand">{{ e.name }}</span>
</div>
</SlideFrame>
</template>

View file

@ -0,0 +1,44 @@
<script setup lang="ts">
import SlideFrame from '../components/SlideFrame.vue'
import Icon from '../components/Icon.vue'
import { entities, accentText, accentRing } from '../data'
</script>
<template>
<SlideFrame
kicker="The collaboration"
title="Three entities, one network"
subtitle="Deliberately different — a city hub, a travelling festival, a farm — each with its own character, all running the identical core stack."
>
<div class="grid gap-5 md:grid-cols-3">
<div
v-for="e in entities"
:key="e.id"
class="flex flex-col rounded-2xl border p-6"
:class="accentRing[e.accent]"
>
<div class="flex items-center gap-3">
<span class="grid size-11 place-items-center rounded-xl bg-white/5" :class="accentText[e.accent]">
<Icon :name="e.icon" class="size-6" />
</span>
<div>
<h3 class="font-display text-xl font-semibold text-sand">{{ e.name }}</h3>
<p class="text-xs uppercase tracking-wider" :class="accentText[e.accent]">{{ e.role }}</p>
</div>
</div>
<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">
<Icon name="Layers" class="size-4 text-amber-300" />
Full core stack &amp; membership
</div>
</div>
</div>
<p class="mt-6 text-center text-sm text-sand-dim">
Same card, same kiosk, same app the
<span class="text-sand">full experience regardless of location.</span>
</p>
</SlideFrame>
</template>

View file

@ -0,0 +1,8 @@
<script setup lang="ts">
import EntityDetail from '../components/EntityDetail.vue'
import { entityById } from '../data'
</script>
<template>
<EntityDetail :entity="entityById('solunar')" />
</template>

View file

@ -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 PlaceholderBadge from '../components/PlaceholderBadge.vue' import PlaceholderBadge from '../components/PlaceholderBadge.vue'
import { brand } from '../data' import { brand, entities, accentText } from '../data'
</script> </script>
<template> <template>
@ -12,19 +12,30 @@ import { brand } from '../data'
<PlaceholderBadge label="draft" /> <PlaceholderBadge label="draft" />
</span> </span>
<h1 class="font-display text-6xl font-bold tracking-tight text-sand sm:text-7xl lg:text-8xl"> <h1 class="font-display text-5xl font-bold tracking-tight text-sand sm:text-6xl lg:text-7xl">
{{ brand.name }} {{ brand.name }}
</h1> </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"> <p class="mt-6 max-w-2xl text-balance text-xl text-sand sm:text-2xl">
{{ brand.tagline }} {{ brand.tagline }}
</p> </p>
<p class="mt-4 max-w-xl text-balance text-sm text-sand-dim sm:text-base">
<!-- the three collaborating entities -->
<div class="mt-10 flex flex-wrap items-center justify-center gap-x-3 gap-y-2 text-sm text-sand-dim">
<span class="uppercase tracking-[0.2em]">A collaboration between</span>
</div>
<div class="mt-3 flex flex-wrap items-center justify-center gap-3">
<template v-for="(e, i) in entities" :key="e.id">
<span class="font-display text-lg font-semibold" :class="accentText[e.accent]">{{ e.name }}</span>
<span v-if="i < entities.length - 1" class="text-sand-dim">·</span>
</template>
</div>
<p class="mt-8 max-w-xl text-balance text-sm text-sand-dim">
{{ brand.subtagline }} {{ brand.subtagline }}
</p> </p>
<p class="mt-14 text-xs text-sand-dim"> <p class="mt-12 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>