feat(deck): light/dark mode via semantic tokens + toggle

Refactor the deck off the bespoke ink/sand palette and literal white/x
overlays onto flipping semantic tokens (fg, fg-muted, surface[-2..4],
line[-2..3], and mode-aware accent text) defined once under the shadcn
:root/.dark blocks in style.css. A single `.dark` class on the deck root
now flips the entire theme.

Add a Sun/Moon toggle in the top bar — dark by default, persisted to
localStorage. Accent TEXT darkens in light mode for contrast; solid
accent fills (bars/dots/badges) stay saturated for both. The lightbox
stays an always-dark overlay with fixed light chrome.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Patrick Mulligan 2026-07-31 21:47:14 +02:00
commit ce11b60ca0
20 changed files with 239 additions and 169 deletions

View file

@ -1,9 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed, ref, watch } from 'vue'
import { ChevronLeft, ChevronRight } from '@lucide/vue' import { ChevronLeft, ChevronRight, Sun, Moon } from '@lucide/vue'
import { useDeck } from './useDeck' import { useDeck } from './useDeck'
import { brand } from './data' import { brand } from './data'
// theme: dark by default, persisted; a single `.dark` class flips every token
function readTheme() {
const s = typeof localStorage !== 'undefined' ? localStorage.getItem('deck-theme') : null
return s === 'light' ? false : true
}
const dark = ref(readTheme())
watch(dark, (v) => localStorage.setItem('deck-theme', v ? 'dark' : 'light'))
import TitleSlide from './slides/TitleSlide.vue' 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'
@ -44,15 +52,26 @@ const transitionName = computed(() => (direction.value === 1 ? 'slide-next' : 's
</script> </script>
<template> <template>
<div class="deck-root relative h-dvh w-full overflow-hidden"> <div class="deck-root relative h-dvh w-full overflow-hidden" :class="{ dark }">
<!-- top bar --> <!-- top bar -->
<header <header
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-fg-muted"
> >
<span class="font-display text-sm font-semibold tracking-wide text-sand"> <span class="font-display text-sm font-semibold tracking-wide text-fg">
{{ brand.short }} {{ brand.short }}
</span> </span>
<div class="flex items-center gap-3">
<span class="text-xs uppercase tracking-[0.2em]">{{ current.label }}</span> <span class="text-xs uppercase tracking-[0.2em]">{{ current.label }}</span>
<button
class="pointer-events-auto grid size-8 place-items-center rounded-full border border-line bg-surface-2 text-fg transition hover:bg-surface-3"
:aria-label="dark ? 'Switch to light mode' : 'Switch to dark mode'"
:title="dark ? 'Light mode' : 'Dark mode'"
@click="dark = !dark"
>
<Sun v-if="dark" class="size-4" />
<Moon v-else class="size-4" />
</button>
</div>
</header> </header>
<!-- slide stage --> <!-- slide stage -->
@ -67,7 +86,7 @@ const transitionName = computed(() => (direction.value === 1 ? 'slide-next' : 's
<!-- controls --> <!-- controls -->
<div class="absolute inset-x-0 bottom-0 z-20 flex items-center justify-between gap-4 px-6 py-5"> <div class="absolute inset-x-0 bottom-0 z-20 flex items-center justify-between gap-4 px-6 py-5">
<button <button
class="pointer-events-auto grid size-10 place-items-center rounded-full border border-white/10 bg-white/5 text-sand transition hover:bg-white/10 disabled:opacity-30" class="pointer-events-auto grid size-10 place-items-center rounded-full border border-line bg-surface-2 text-fg transition hover:bg-surface-3 disabled:opacity-30"
:disabled="isFirst" :disabled="isFirst"
aria-label="Previous slide" aria-label="Previous slide"
@click="prev" @click="prev"
@ -81,7 +100,7 @@ const transitionName = computed(() => (direction.value === 1 ? 'slide-next' : 's
v-for="(s, i) in slides" v-for="(s, i) in slides"
:key="s.id" :key="s.id"
class="h-1.5 rounded-full transition-all" class="h-1.5 rounded-full transition-all"
:class="i === index ? 'w-6 bg-amber-400' : 'w-1.5 bg-white/20 hover:bg-white/40'" :class="i === index ? 'w-6 bg-amber-400' : 'w-1.5 bg-surface-3 hover:bg-surface-4'"
:title="s.label" :title="s.label"
:aria-label="`Go to ${s.label}`" :aria-label="`Go to ${s.label}`"
:aria-current="i === index" :aria-current="i === index"
@ -90,11 +109,11 @@ const transitionName = computed(() => (direction.value === 1 ? 'slide-next' : 's
</nav> </nav>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span class="hidden text-xs tabular-nums text-sand-dim sm:inline"> <span class="hidden text-xs tabular-nums text-fg-muted sm:inline">
{{ index + 1 }} / {{ slides.length }} {{ index + 1 }} / {{ slides.length }}
</span> </span>
<button <button
class="pointer-events-auto grid size-10 place-items-center rounded-full border border-white/10 bg-white/5 text-sand transition hover:bg-white/10 disabled:opacity-30" class="pointer-events-auto grid size-10 place-items-center rounded-full border border-line bg-surface-2 text-fg transition hover:bg-surface-3 disabled:opacity-30"
:disabled="isLast" :disabled="isLast"
aria-label="Next slide" aria-label="Next slide"
@click="next" @click="next"

View file

@ -11,17 +11,17 @@ const a = props.entity.accent
<template> <template>
<SlideFrame :kicker="entity.kind"> <SlideFrame :kicker="entity.kind">
<div class="flex flex-wrap items-center gap-3"> <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> <h2 class="font-display text-4xl font-semibold text-fg lg:text-5xl">{{ entity.name }}</h2>
<span <span
class="rounded-full border px-3 py-0.5 text-xs uppercase tracking-wider" class="rounded-full border px-3 py-0.5 text-xs uppercase tracking-wider"
:class="[accentRing[a], accentText[a]]" :class="[accentRing[a], accentText[a]]"
> >
{{ entity.role }} {{ entity.role }}
</span> </span>
<span v-if="entity.example" class="text-sm text-sand-dim">e.g. {{ entity.example }}</span> <span v-if="entity.example" class="text-sm text-fg-muted">e.g. {{ entity.example }}</span>
<PlaceholderBadge label="name" /> <PlaceholderBadge label="name" />
</div> </div>
<p class="mt-4 max-w-2xl text-lg text-sand-dim">{{ entity.blurb }}</p> <p class="mt-4 max-w-2xl text-lg text-fg-muted">{{ entity.blurb }}</p>
<div class="mt-8 grid gap-6 lg:grid-cols-3"> <div class="mt-8 grid gap-6 lg:grid-cols-3">
<!-- unique traits --> <!-- unique traits -->
@ -30,7 +30,7 @@ const a = props.entity.accent
What makes it unique What makes it unique
</p> </p>
<ul class="mt-4 space-y-3"> <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"> <li v-for="t in entity.traits" :key="t" class="flex items-start gap-3 text-sm text-fg-muted">
<span class="mt-0.5 grid size-5 shrink-0 place-items-center rounded-full" :class="accentRing[a]"> <span class="mt-0.5 grid size-5 shrink-0 place-items-center rounded-full" :class="accentRing[a]">
<Icon name="Check" class="size-3" :class="accentText[a]" /> <Icon name="Check" class="size-3" :class="accentText[a]" />
</span> </span>
@ -46,7 +46,7 @@ const a = props.entity.accent
<div <div
v-for="s in entity.spaces" v-for="s in entity.spaces"
:key="s" :key="s"
class="flex items-center gap-2 rounded-lg bg-white/[0.03] px-3 py-2 text-sm text-sand" class="flex items-center gap-2 rounded-lg bg-surface px-3 py-2 text-sm text-fg"
> >
<span class="size-1.5 rounded-full" :class="accentDot[a]" /> <span class="size-1.5 rounded-full" :class="accentDot[a]" />
{{ s }} {{ s }}
@ -60,15 +60,15 @@ const a = props.entity.accent
<p class="text-xs font-semibold uppercase tracking-[0.2em]" :class="accentText[a]"> <p class="text-xs font-semibold uppercase tracking-[0.2em]" :class="accentText[a]">
How it earns How it earns
</p> </p>
<ul class="mt-3 space-y-1.5 text-sm text-sand-dim"> <ul class="mt-3 space-y-1.5 text-sm text-fg-muted">
<li v-for="r in entity.revenue" :key="r" class="flex items-center gap-2"> <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]" /> <Icon name="Coins" class="size-3.5" :class="accentText[a]" />
{{ r }} {{ r }}
</li> </li>
</ul> </ul>
</div> </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"> <div class="flex items-center gap-2 rounded-2xl border border-line bg-surface px-4 py-3 text-xs text-fg-muted">
<Icon name="Layers" class="size-4 text-amber-300" /> <Icon name="Layers" class="size-4 text-amber" />
Runs on Archipelago — same membership, kiosk &amp; app as every node. Runs on Archipelago — same membership, kiosk &amp; app as every node.
</div> </div>
</div> </div>

View file

@ -75,7 +75,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKey, true))
<!-- close --> <!-- close -->
<button <button
class="absolute right-5 top-5 grid size-11 place-items-center rounded-full border border-white/15 bg-white/10 text-sand backdrop-blur transition hover:bg-white/20" class="absolute right-5 top-5 grid size-11 place-items-center rounded-full border border-white/15 bg-white/10 text-white backdrop-blur transition hover:bg-white/20"
aria-label="Close" aria-label="Close"
@click.stop="close" @click.stop="close"
> >
@ -85,7 +85,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKey, true))
<!-- prev / next --> <!-- prev / next -->
<button <button
v-if="hasPrev" v-if="hasPrev"
class="absolute left-5 top-1/2 grid size-11 -translate-y-1/2 place-items-center rounded-full border border-white/15 bg-white/10 text-sand backdrop-blur transition hover:bg-white/20" class="absolute left-5 top-1/2 grid size-11 -translate-y-1/2 place-items-center rounded-full border border-white/15 bg-white/10 text-white backdrop-blur transition hover:bg-white/20"
aria-label="Previous image" aria-label="Previous image"
@click.stop="prev" @click.stop="prev"
> >
@ -93,7 +93,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKey, true))
</button> </button>
<button <button
v-if="hasNext" v-if="hasNext"
class="absolute right-5 top-1/2 grid size-11 -translate-y-1/2 place-items-center rounded-full border border-white/15 bg-white/10 text-sand backdrop-blur transition hover:bg-white/20" class="absolute right-5 top-1/2 grid size-11 -translate-y-1/2 place-items-center rounded-full border border-white/15 bg-white/10 text-white backdrop-blur transition hover:bg-white/20"
aria-label="Next image" aria-label="Next image"
@click.stop="next" @click.stop="next"
> >
@ -103,10 +103,10 @@ onUnmounted(() => window.removeEventListener('keydown', onKey, true))
<!-- counter + hint --> <!-- counter + hint -->
<div <div
v-if="images.length > 1" v-if="images.length > 1"
class="absolute bottom-5 left-1/2 -translate-x-1/2 rounded-full border border-white/15 bg-white/10 px-4 py-1.5 text-sm text-sand backdrop-blur" class="absolute bottom-5 left-1/2 -translate-x-1/2 rounded-full border border-white/15 bg-white/10 px-4 py-1.5 text-sm text-white backdrop-blur"
> >
{{ (index ?? 0) + 1 }} / {{ images.length }} {{ (index ?? 0) + 1 }} / {{ images.length }}
<span class="ml-2 hidden text-sand-dim sm:inline">← → to browse · Esc to close</span> <span class="ml-2 hidden text-white/60 sm:inline">← → to browse · Esc to close</span>
</div> </div>
</div> </div>
</Transition> </Transition>

View file

@ -280,16 +280,16 @@ const festivalCaps = [
<div class="mt-6 grid gap-4 text-sm sm:grid-cols-2"> <div class="mt-6 grid gap-4 text-sm sm:grid-cols-2">
<div class="flex items-start gap-3"> <div class="flex items-start gap-3">
<span class="mt-0.5 text-pine-300"><Icon name="Layers" class="size-5" /></span> <span class="mt-0.5 text-pine"><Icon name="Layers" class="size-5" /></span>
<p class="text-sand-dim"> <p class="text-fg-muted">
<span class="font-medium text-sand">Archipelago is the mycelium.</span> <span class="font-medium text-fg">Archipelago is the mycelium.</span>
One shared backbone runs unseen beneath every entity — members feel it, they don’t see it. One shared backbone runs unseen beneath every entity — members feel it, they don’t see it.
</p> </p>
</div> </div>
<div class="flex items-start gap-3"> <div class="flex items-start gap-3">
<span class="mt-0.5 text-amber-300"><Icon name="Sparkles" class="size-5" /></span> <span class="mt-0.5 text-amber"><Icon name="Sparkles" class="size-5" /></span>
<p class="text-sand-dim"> <p class="text-fg-muted">
<span class="font-medium text-sand">Flowers are cheap to grow.</span> <span class="font-medium text-fg">Flowers are cheap to grow.</span>
A city hub, a travelling festival, a farm — and every new bloom is just another fruiting body. A city hub, a travelling festival, a farm — and every new bloom is just another fruiting body.
</p> </p>
</div> </div>

View file

@ -5,7 +5,7 @@ withDefaults(defineProps<{ label?: string }>(), { label: 'placeholder' })
<template> <template>
<span <span
class="inline-flex items-center gap-1 rounded-full border border-amber-400/30 bg-amber-400/10 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wider text-amber-300" class="inline-flex items-center gap-1 rounded-full border border-amber-400/30 bg-amber-400/10 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wider text-amber"
title="This value is a placeholder — edit src/deck/data.ts" title="This value is a placeholder — edit src/deck/data.ts"
> >
{{ label }} {{ label }}

View file

@ -15,19 +15,19 @@ defineProps<{
> >
<p <p
v-if="kicker" v-if="kicker"
class="mb-3 text-xs font-semibold uppercase tracking-[0.25em] text-amber-400" class="mb-3 text-xs font-semibold uppercase tracking-[0.25em] text-amber"
> >
{{ kicker }} {{ kicker }}
</p> </p>
<h2 <h2
v-if="title" v-if="title"
class="font-display text-3xl font-semibold leading-tight text-sand sm:text-4xl lg:text-5xl" class="font-display text-3xl font-semibold leading-tight text-fg sm:text-4xl lg:text-5xl"
> >
{{ title }} {{ title }}
</h2> </h2>
<p <p
v-if="subtitle" v-if="subtitle"
class="mt-4 max-w-2xl text-base text-sand-dim sm:text-lg" class="mt-4 max-w-2xl text-base text-fg-muted sm:text-lg"
:class="center ? 'mx-auto' : ''" :class="center ? 'mx-auto' : ''"
> >
{{ subtitle }} {{ subtitle }}

View file

@ -10,11 +10,11 @@ const shares = investorAsk.scenarios.map((s) => ({ equity: s, annual: Math.round
<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">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-fg sm:text-5xl">
{{ fmtUsd(investorAsk.amount) }} for {{ pct(investorAsk.equity) }}% of SolLunar Society {{ fmtUsd(investorAsk.amount) }} for {{ pct(investorAsk.equity) }}% of SolLunar Society
</h2> </h2>
<p class="mt-3 text-sand-dim"> <p class="mt-3 text-fg-muted">
{{ fmtUsd(investorAsk.valuation) }} valuation · the first node, open in Austin {{ fmtUsd(investorAsk.valuation) }} valuation · the first node, open in Austin
</p> </p>
@ -23,36 +23,36 @@ const shares = investorAsk.scenarios.map((s) => ({ equity: s, annual: Math.round
<div <div
v-for="s in shares" v-for="s in shares"
:key="s.equity" :key="s.equity"
class="rounded-2xl border border-white/10 bg-white/[0.03] px-6 py-4" class="rounded-2xl border border-line bg-surface px-6 py-4"
> >
<p class="text-xs uppercase tracking-wider text-sand-dim">{{ pct(s.equity) }}% share</p> <p class="text-xs uppercase tracking-wider text-fg-muted">{{ pct(s.equity) }}% share</p>
<p class="mt-1 font-display text-3xl font-bold text-sand">{{ fmtUsd(s.annual) }}</p> <p class="mt-1 font-display text-3xl font-bold text-fg">{{ fmtUsd(s.annual) }}</p>
<p class="text-xs text-sand-dim">/ yr profit</p> <p class="text-xs text-fg-muted">/ yr profit</p>
</div> </div>
<div class="rounded-2xl border border-pine-500/40 bg-pine-500/[0.07] px-6 py-4"> <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="text-xs uppercase tracking-wider text-pine">Node profit</p>
<p class="mt-1 font-display text-3xl font-bold text-sand">{{ fmtUsd(pnl.yearlyProfit) }}</p> <p class="mt-1 font-display text-3xl font-bold text-fg">{{ fmtUsd(pnl.yearlyProfit) }}</p>
<p class="text-xs text-sand-dim">/ yr (recomputed)</p> <p class="text-xs text-fg-muted">/ yr (recomputed)</p>
</div> </div>
</div> </div>
<div class="mt-9 flex flex-col items-center gap-3 sm:flex-row"> <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-on-accent transition hover:bg-amber-300"
> >
<Icon name="Mail" class="size-5" /> <Icon name="Mail" class="size-5" />
{{ brand.contactEmail }} {{ brand.contactEmail }}
</a> </a>
<button <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" class="inline-flex items-center gap-2 rounded-full border border-line-2 bg-surface-2 px-6 py-3 font-semibold text-fg transition hover:bg-surface-3"
> >
<Icon name="CalendarClock" class="size-5" /> <Icon name="CalendarClock" class="size-5" />
Book an investor call Book an investor call
</button> </button>
</div> </div>
<p class="mt-9 max-w-xl text-sm text-sand-dim"> <p class="mt-9 max-w-xl text-sm text-fg-muted">
Prove the model in Austin, then replicate it on Archipelago — the festival and the farm are next. Prove the model in Austin, then replicate it on Archipelago — the festival and the farm are next.
</p> </p>
</SlideFrame> </SlideFrame>

View file

@ -14,19 +14,19 @@ import { userEcosystem, surfaceEntities as entities } from '../data'
<div <div
v-for="item in userEcosystem" v-for="item in userEcosystem"
:key="item.title" :key="item.title"
class="rounded-2xl border border-white/10 bg-white/[0.03] p-5" class="rounded-2xl border border-line bg-surface p-5"
> >
<span class="grid size-10 place-items-center rounded-xl bg-amber-400/10 text-amber-300"> <span class="grid size-10 place-items-center rounded-xl bg-amber-400/10 text-amber">
<Icon :name="item.icon" class="size-5" /> <Icon :name="item.icon" class="size-5" />
</span> </span>
<h3 class="mt-4 font-display text-lg font-semibold text-sand">{{ item.title }}</h3> <h3 class="mt-4 font-display text-lg font-semibold text-fg">{{ item.title }}</h3>
<p class="mt-1.5 text-sm leading-relaxed text-sand-dim">{{ item.body }}</p> <p class="mt-1.5 text-sm leading-relaxed text-fg-muted">{{ item.body }}</p>
</div> </div>
</div> </div>
<div class="mt-6 flex flex-wrap items-center justify-center gap-x-3 gap-y-1 text-xs text-sand-dim"> <div class="mt-6 flex flex-wrap items-center justify-center gap-x-3 gap-y-1 text-xs text-fg-muted">
<span>Works identically at</span> <span>Works identically at</span>
<span v-for="e in entities" :key="e.id" class="font-medium text-sand">{{ e.name }}</span> <span v-for="e in entities" :key="e.id" class="font-medium text-fg">{{ e.name }}</span>
</div> </div>
</SlideFrame> </SlideFrame>
</template> </template>

View file

@ -21,24 +21,24 @@ const archipelago = entityById('archipelago')
> >
<span <span
v-if="e.id === 'sollunar'" 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" class="absolute -top-3 left-6 rounded-full bg-indigo-400 px-3 py-0.5 text-[11px] font-semibold uppercase tracking-wider text-on-accent"
> >
Raising now Raising now
</span> </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-surface-2" :class="accentText[e.accent]">
<Icon :name="e.icon" class="size-6" /> <Icon :name="e.icon" class="size-6" />
</span> </span>
<div> <div>
<h3 class="font-display text-lg font-semibold leading-tight text-sand">{{ e.name }}</h3> <h3 class="font-display text-lg font-semibold leading-tight text-fg">{{ e.name }}</h3>
<p class="text-xs uppercase tracking-wider" :class="accentText[e.accent]">{{ e.role }}</p> <p class="text-xs uppercase tracking-wider" :class="accentText[e.accent]">{{ e.role }}</p>
</div> </div>
</div> </div>
<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-fg-muted">{{ 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-line pt-4 text-xs text-fg-muted">
<Icon :name="e.id === 'sollunar' ? 'Check' : 'Layers'" class="size-4" :class="e.id === 'sollunar' ? 'text-indigo-300' : 'text-amber-300'" /> <Icon :name="e.id === 'sollunar' ? 'Check' : 'Layers'" class="size-4" :class="e.id === 'sollunar' ? 'text-indigo' : 'text-amber'" />
{{ e.id === 'sollunar' ? 'Live P&amp;L · this raise' : 'Vision · on the roadmap' }} {{ e.id === 'sollunar' ? 'Live P&amp;L · this raise' : 'Vision · on the roadmap' }}
</div> </div>
</div> </div>
@ -46,17 +46,17 @@ const archipelago = entityById('archipelago')
<!-- the shared backbone beneath all three --> <!-- the shared backbone beneath all three -->
<div class="mt-5 flex items-center gap-4 rounded-2xl border p-5" :class="accentRing[archipelago.accent]"> <div class="mt-5 flex items-center gap-4 rounded-2xl border p-5" :class="accentRing[archipelago.accent]">
<span class="grid size-11 shrink-0 place-items-center rounded-xl bg-white/5" :class="accentText[archipelago.accent]"> <span class="grid size-11 shrink-0 place-items-center rounded-xl bg-surface-2" :class="accentText[archipelago.accent]">
<Icon :name="archipelago.icon" class="size-6" /> <Icon :name="archipelago.icon" class="size-6" />
</span> </span>
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<h3 class="font-display text-lg font-semibold text-sand"> <h3 class="font-display text-lg font-semibold text-fg">
{{ archipelago.name }} {{ archipelago.name }}
<span class="ml-1 text-xs font-normal uppercase tracking-wider" :class="accentText[archipelago.accent]"> <span class="ml-1 text-xs font-normal uppercase tracking-wider" :class="accentText[archipelago.accent]">
· {{ archipelago.role }} · {{ archipelago.role }}
</span> </span>
</h3> </h3>
<p class="text-sm text-sand-dim">{{ archipelago.blurb }}</p> <p class="text-sm text-fg-muted">{{ archipelago.blurb }}</p>
</div> </div>
</div> </div>
</SlideFrame> </SlideFrame>

View file

@ -33,14 +33,14 @@ function selectTab(id: (typeof gallerySections)[number]['id']) {
:class=" :class="
activeId === s.id activeId === s.id
? [accentRing[s.accent], accentText[s.accent]] ? [accentRing[s.accent], accentText[s.accent]]
: 'border-white/10 bg-white/[0.03] text-sand-dim hover:border-white/25 hover:text-sand' : 'border-line bg-surface text-fg-muted hover:border-line-3 hover:text-fg'
" "
:aria-pressed="activeId === s.id" :aria-pressed="activeId === s.id"
@click="selectTab(s.id)" @click="selectTab(s.id)"
> >
<span class="font-medium">{{ s.label }}</span> <span class="font-medium">{{ s.label }}</span>
<span class="text-xs opacity-70">{{ s.entityName }}</span> <span class="text-xs opacity-70">{{ s.entityName }}</span>
<span class="rounded-full bg-white/10 px-1.5 text-[11px] tabular-nums">{{ s.images.length }}</span> <span class="rounded-full bg-surface-3 px-1.5 text-[11px] tabular-nums">{{ s.images.length }}</span>
</button> </button>
</div> </div>
@ -50,7 +50,7 @@ function selectTab(id: (typeof gallerySections)[number]['id']) {
<button <button
v-for="(img, i) in active.images" v-for="(img, i) in active.images"
:key="img.src" :key="img.src"
class="group relative mb-3 block w-full break-inside-avoid overflow-hidden rounded-xl border border-white/10 bg-white/[0.03]" class="group relative mb-3 block w-full break-inside-avoid overflow-hidden rounded-xl border border-line bg-surface"
:aria-label="`Open ${img.name}`" :aria-label="`Open ${img.name}`"
@click="lightboxIndex = i" @click="lightboxIndex = i"
> >
@ -63,7 +63,7 @@ function selectTab(id: (typeof gallerySections)[number]['id']) {
<span <span
class="pointer-events-none absolute inset-0 flex items-end justify-end bg-gradient-to-t from-black/50 to-transparent p-2 opacity-0 transition group-hover:opacity-100" class="pointer-events-none absolute inset-0 flex items-end justify-end bg-gradient-to-t from-black/50 to-transparent p-2 opacity-0 transition group-hover:opacity-100"
> >
<span class="grid size-8 place-items-center rounded-full bg-white/15 text-sand backdrop-blur"> <span class="grid size-8 place-items-center rounded-full bg-surface-3 text-fg backdrop-blur">
<Icon name="Expand" class="size-4" /> <Icon name="Expand" class="size-4" />
</span> </span>
</span> </span>
@ -73,15 +73,15 @@ function selectTab(id: (typeof gallerySections)[number]['id']) {
<!-- empty state --> <!-- empty state -->
<div <div
v-else v-else
class="flex flex-col items-center justify-center rounded-2xl border border-dashed border-white/15 px-6 py-16 text-center" class="flex flex-col items-center justify-center rounded-2xl border border-dashed border-line-2 px-6 py-16 text-center"
> >
<span class="grid size-12 place-items-center rounded-xl bg-white/5" :class="accentText[active.accent]"> <span class="grid size-12 place-items-center rounded-xl bg-surface-2" :class="accentText[active.accent]">
<Icon name="Images" class="size-6" /> <Icon name="Images" class="size-6" />
</span> </span>
<p class="mt-4 text-sand">No {{ active.label }} photos yet.</p> <p class="mt-4 text-fg">No {{ active.label }} photos yet.</p>
<p class="mt-1 max-w-md text-sm text-sand-dim"> <p class="mt-1 max-w-md text-sm text-fg-muted">
Drop images into Drop images into
<code class="rounded bg-white/10 px-1.5 py-0.5 text-xs text-sand">{{ active.folder }}</code> <code class="rounded bg-surface-3 px-1.5 py-0.5 text-xs text-fg">{{ active.folder }}</code>
and they’ll appear here automatically. and they’ll appear here automatically.
</p> </p>
</div> </div>

View file

@ -18,25 +18,25 @@ const tiers = [...memberships].sort((a, b) => a.price - b.price)
v-for="t in tiers" v-for="t in tiers"
:key="t.name" :key="t.name"
class="relative flex flex-col rounded-2xl border p-6" 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]'" :class="t.featured ? 'border-indigo-500/50 bg-indigo-500/[0.07]' : 'border-line bg-surface'"
> >
<span <span
v-if="t.featured" 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" class="absolute -top-3 left-6 rounded-full bg-indigo-400 px-3 py-0.5 text-[11px] font-semibold uppercase tracking-wider text-on-accent"
> >
Flagship Flagship
</span> </span>
<h3 class="font-display text-xl font-semibold text-sand">{{ t.name }}</h3> <h3 class="font-display text-xl font-semibold text-fg">{{ t.name }}</h3>
<div class="mt-2 flex items-baseline gap-1"> <div class="mt-2 flex items-baseline gap-1">
<span class="font-display text-4xl font-bold text-sand">{{ fmtUsd(t.price) }}</span> <span class="font-display text-4xl font-bold text-fg">{{ fmtUsd(t.price) }}</span>
<span class="text-sm text-sand-dim">/mo</span> <span class="text-sm text-fg-muted">/mo</span>
</div> </div>
<p class="mt-1 text-xs text-sand-dim">{{ t.blurb }} · target {{ t.target }} members</p> <p class="mt-1 text-xs text-fg-muted">{{ t.blurb }} · target {{ t.target }} members</p>
<ul class="mt-5 flex-1 space-y-2"> <ul class="mt-5 flex-1 space-y-2">
<li v-for="p in t.perks" :key="p" class="flex items-start gap-2 text-sm text-sand-dim"> <li v-for="p in t.perks" :key="p" class="flex items-start gap-2 text-sm text-fg-muted">
<Icon name="Check" class="mt-0.5 size-4 shrink-0 text-indigo-300" /> <Icon name="Check" class="mt-0.5 size-4 shrink-0 text-indigo" />
{{ p }} {{ p }}
</li> </li>
</ul> </ul>
@ -44,14 +44,14 @@ const tiers = [...memberships].sort((a, b) => a.price - b.price)
</div> </div>
<!-- comparables --> <!-- comparables -->
<div class="mt-6 flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-sand-dim"> <div class="mt-6 flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-fg-muted">
<span class="uppercase tracking-wider">Austin comparables</span> <span class="uppercase tracking-wider">Austin comparables</span>
<span <span
v-for="c in membershipComparables" v-for="c in membershipComparables"
:key="c.name" :key="c.name"
class="rounded-full border border-white/10 bg-white/5 px-3 py-1" class="rounded-full border border-line bg-surface-2 px-3 py-1"
> >
{{ c.name }} <span class="text-sand">{{ fmtUsd(c.price) }}</span> {{ c.name }} <span class="text-fg">{{ fmtUsd(c.price) }}</span>
</span> </span>
</div> </div>
</SlideFrame> </SlideFrame>

View file

@ -9,9 +9,9 @@ const barBg: Record<string, string> = {
amber: 'bg-amber-400', amber: 'bg-amber-400',
} }
const text: Record<string, string> = { const text: Record<string, string> = {
indigo: 'text-indigo-300', indigo: 'text-indigo',
pine: 'text-pine-300', pine: 'text-pine',
amber: 'text-amber-300', amber: 'text-amber',
} }
const ring: Record<string, string> = { const ring: Record<string, string> = {
indigo: 'border-indigo-500/40 bg-indigo-500/[0.06]', indigo: 'border-indigo-500/40 bg-indigo-500/[0.06]',
@ -32,7 +32,7 @@ const pct = (e: number) => Math.round(e * 100)
<div <div
v-for="p in partnership.partners" v-for="p in partnership.partners"
:key="p.name" :key="p.name"
class="flex h-full items-center justify-center text-[11px] font-semibold text-ink" class="flex h-full items-center justify-center text-[11px] font-semibold text-on-accent"
:class="barBg[p.accent]" :class="barBg[p.accent]"
:style="{ width: `${pct(p.equity)}%` }" :style="{ width: `${pct(p.equity)}%` }"
> >
@ -48,27 +48,27 @@ const pct = (e: number) => Math.round(e * 100)
:class="ring[p.accent]" :class="ring[p.accent]"
> >
<div class="flex items-baseline justify-between gap-2"> <div class="flex items-baseline justify-between gap-2">
<h3 class="font-display text-lg font-semibold text-sand">{{ p.name }}</h3> <h3 class="font-display text-lg font-semibold text-fg">{{ p.name }}</h3>
<span class="font-display text-2xl font-bold" :class="text[p.accent]">{{ pct(p.equity) }}%</span> <span class="font-display text-2xl font-bold" :class="text[p.accent]">{{ pct(p.equity) }}%</span>
</div> </div>
<p class="text-xs uppercase tracking-wider" :class="text[p.accent]">{{ p.role }}</p> <p class="text-xs uppercase tracking-wider" :class="text[p.accent]">{{ p.role }}</p>
<ul class="mt-4 flex-1 space-y-1.5"> <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"> <li v-for="r in p.responsibilities" :key="r" class="flex items-start gap-2 text-sm text-fg-muted">
<Icon name="Check" class="mt-0.5 size-3.5 shrink-0" :class="text[p.accent]" /> <Icon name="Check" class="mt-0.5 size-3.5 shrink-0" :class="text[p.accent]" />
{{ r }} {{ r }}
</li> </li>
</ul> </ul>
<p v-if="p.note" class="mt-3 border-t border-white/10 pt-3 text-[11px] leading-relaxed text-sand-dim"> <p v-if="p.note" class="mt-3 border-t border-line pt-3 text-[11px] leading-relaxed text-fg-muted">
{{ p.note }} {{ p.note }}
</p> </p>
</div> </div>
</div> </div>
<div class="mt-5 flex flex-wrap items-center justify-center gap-x-6 gap-y-1 text-sm text-sand-dim"> <div class="mt-5 flex flex-wrap items-center justify-center gap-x-6 gap-y-1 text-sm text-fg-muted">
<span>The ask:</span> <span>The ask:</span>
<span class="font-display text-lg font-semibold text-sand"> <span class="font-display text-lg font-semibold text-fg">
{{ fmtUsd(partnership.raiseAmount) }} for {{ pct(partnership.equityOffered) }}% {{ fmtUsd(partnership.raiseAmount) }} for {{ pct(partnership.equityOffered) }}%
</span> </span>
<span>·</span> <span>·</span>

View file

@ -11,10 +11,10 @@ const barBg: Record<string, string> = {
clay: 'bg-clay-400', clay: 'bg-clay-400',
} }
const text: Record<string, string> = { const text: Record<string, string> = {
pine: 'text-pine-300', pine: 'text-pine',
indigo: 'text-indigo-300', indigo: 'text-indigo',
rose: 'text-rose-300', rose: 'text-rose',
clay: 'text-clay-400', clay: 'text-clay',
} }
const ring: Record<string, string> = { const ring: Record<string, string> = {
pine: 'border-pine-500/50 bg-pine-500/[0.06]', pine: 'border-pine-500/50 bg-pine-500/[0.06]',
@ -42,28 +42,28 @@ const revPct = (id: string) =>
> >
<!-- headline stats --> <!-- headline stats -->
<div class="grid grid-cols-2 gap-3 sm:grid-cols-4"> <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"> <div class="rounded-2xl border border-line bg-surface p-4">
<p class="text-xs uppercase tracking-wider text-sand-dim">Revenue / mo</p> <p class="text-xs uppercase tracking-wider text-fg-muted">Revenue / mo</p>
<p class="mt-1 font-display text-2xl font-bold text-sand">{{ fmtUsd(pnl.monthlyRevenue) }}</p> <p class="mt-1 font-display text-2xl font-bold text-fg">{{ fmtUsd(pnl.monthlyRevenue) }}</p>
</div> </div>
<div class="rounded-2xl border border-white/10 bg-white/[0.03] p-4"> <div class="rounded-2xl border border-line bg-surface p-4">
<p class="text-xs uppercase tracking-wider text-sand-dim">Expenses / mo</p> <p class="text-xs uppercase tracking-wider text-fg-muted">Expenses / mo</p>
<p class="mt-1 font-display text-2xl font-bold text-sand">{{ fmtUsd(pnl.monthlyExpenses) }}</p> <p class="mt-1 font-display text-2xl font-bold text-fg">{{ fmtUsd(pnl.monthlyExpenses) }}</p>
</div> </div>
<div class="rounded-2xl border border-pine-500/40 bg-pine-500/[0.07] p-4"> <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="text-xs uppercase tracking-wider text-pine">Profit / mo</p>
<p class="mt-1 font-display text-2xl font-bold text-sand">{{ fmtUsd(pnl.monthlyProfit) }}</p> <p class="mt-1 font-display text-2xl font-bold text-fg">{{ fmtUsd(pnl.monthlyProfit) }}</p>
</div> </div>
<div class="rounded-2xl border border-amber-400/40 bg-amber-400/[0.07] p-4"> <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="text-xs uppercase tracking-wider text-amber">Profit / yr</p>
<p class="mt-1 font-display text-2xl font-bold text-sand">{{ fmtUsd(pnl.yearlyProfit) }}</p> <p class="mt-1 font-display text-2xl font-bold text-fg">{{ fmtUsd(pnl.yearlyProfit) }}</p>
</div> </div>
</div> </div>
<div class="mt-6 grid items-start gap-8 lg:grid-cols-[1fr_1fr]"> <div class="mt-6 grid items-start gap-8 lg:grid-cols-[1fr_1fr]">
<!-- revenue mix bar + live detail --> <!-- revenue mix bar + live detail -->
<div> <div>
<p class="mb-2 text-xs uppercase tracking-wider text-sand-dim">Revenue mix</p> <p class="mb-2 text-xs uppercase tracking-wider text-fg-muted">Revenue mix</p>
<div class="flex h-4 w-full overflow-hidden rounded-full"> <div class="flex h-4 w-full overflow-hidden rounded-full">
<button <button
v-for="g in revenueGroups" v-for="g in revenueGroups"
@ -85,7 +85,7 @@ const revPct = (id: string) =>
> >
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Icon :name="selectedGroup.icon" class="size-5" :class="text[selectedGroup.accent]" /> <Icon :name="selectedGroup.icon" class="size-5" :class="text[selectedGroup.accent]" />
<h3 class="font-display text-base font-semibold text-sand">{{ selectedGroup.title }}</h3> <h3 class="font-display text-base font-semibold text-fg">{{ selectedGroup.title }}</h3>
<span class="ml-auto font-display text-lg font-semibold" :class="text[selectedGroup.accent]"> <span class="ml-auto font-display text-lg font-semibold" :class="text[selectedGroup.accent]">
{{ fmtUsd(groupTotal(selectedGroup)) }} {{ fmtUsd(groupTotal(selectedGroup)) }}
</span> </span>
@ -96,20 +96,20 @@ const revPct = (id: string) =>
:key="item.label" :key="item.label"
class="flex items-baseline justify-between gap-3 text-sm" class="flex items-baseline justify-between gap-3 text-sm"
> >
<span class="text-sand-dim"> <span class="text-fg-muted">
{{ item.label }} {{ item.label }}
<span v-if="item.note" class="text-[11px] text-sand-dim/60">· {{ item.note }}</span> <span v-if="item.note" class="text-[11px] text-fg-muted/60">· {{ item.note }}</span>
</span> </span>
<span class="shrink-0 tabular-nums text-sand">{{ fmtUsd(item.amount) }}</span> <span class="shrink-0 tabular-nums text-fg">{{ fmtUsd(item.amount) }}</span>
</li> </li>
</ul> </ul>
</div> </div>
<div <div
v-else v-else
key="hint" 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" class="mt-5 flex items-center gap-3 rounded-2xl border border-dashed border-line-2 p-5 text-sm text-fg-muted"
> >
<Icon name="ArrowRight" class="size-4 shrink-0 text-amber-300" /> <Icon name="ArrowRight" class="size-4 shrink-0 text-amber" />
Tap a category to see its line items. Tap a category to see its line items.
</div> </div>
</Transition> </Transition>
@ -124,26 +124,26 @@ const revPct = (id: string) =>
:class=" :class="
selected === g.id selected === g.id
? ring[g.accent] ? ring[g.accent]
: 'border-white/10 bg-white/[0.03] hover:border-white/25 hover:bg-white/[0.06]' : 'border-line bg-surface hover:border-line-3 hover:bg-surface-2'
" "
@click="toggle(g.id)" @click="toggle(g.id)"
> >
<span class="grid size-10 shrink-0 place-items-center rounded-xl bg-white/5" :class="text[g.accent]"> <span class="grid size-10 shrink-0 place-items-center rounded-xl bg-surface-2" :class="text[g.accent]">
<Icon :name="g.icon" class="size-5" /> <Icon :name="g.icon" class="size-5" />
</span> </span>
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<h3 class="font-display text-base font-semibold text-sand">{{ g.title }}</h3> <h3 class="font-display text-base font-semibold text-fg">{{ g.title }}</h3>
<p class="text-xs uppercase tracking-wider text-sand-dim">{{ g.kind }}</p> <p class="text-xs uppercase tracking-wider text-fg-muted">{{ g.kind }}</p>
</div> </div>
<span class="shrink-0 font-display text-lg font-semibold" :class="text[g.accent]"> <span class="shrink-0 font-display text-lg font-semibold" :class="text[g.accent]">
{{ fmtUsd(groupTotal(g)) }} {{ fmtUsd(groupTotal(g)) }}
</span> </span>
<Icon name="ChevronRight" class="size-4 shrink-0 text-sand-dim transition-transform" :class="selected === g.id ? 'rotate-90' : ''" /> <Icon name="ChevronRight" class="size-4 shrink-0 text-fg-muted transition-transform" :class="selected === g.id ? 'rotate-90' : ''" />
</button> </button>
</div> </div>
</div> </div>
<p class="mt-4 text-[11px] text-sand-dim/70"> <p class="mt-4 text-[11px] text-fg-muted/70">
Figures recomputed from the plan's line items. The source summary stated Figures recomputed from the plan's line items. The source summary stated
{{ fmtUsd(pnlDocSummary.revenue) }} rev / {{ fmtUsd(pnlDocSummary.expenses) }} exp — {{ fmtUsd(pnlDocSummary.revenue) }} rev / {{ fmtUsd(pnlDocSummary.expenses) }} exp —
being reconciled. Lunarpunk Locals count is TBD. being reconciled. Lunarpunk Locals count is TBD.

View file

@ -16,22 +16,22 @@ import { roadmap } from '../data'
<li v-for="(p, i) in roadmap" :key="p.phase" class="relative"> <li v-for="(p, i) in roadmap" :key="p.phase" class="relative">
<div class="flex items-center gap-3 md:block"> <div class="flex items-center gap-3 md:block">
<span <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" class="z-10 grid size-10 shrink-0 place-items-center rounded-full border border-line-2 bg-background font-display text-sm font-bold text-amber"
> >
{{ i + 1 }} {{ i + 1 }}
</span> </span>
<span class="mt-3 block text-[11px] font-semibold uppercase tracking-wider text-pine-300"> <span class="mt-3 block text-[11px] font-semibold uppercase tracking-wider text-pine">
{{ p.phase }} · {{ p.when }} {{ p.phase }} · {{ p.when }}
</span> </span>
</div> </div>
<h3 class="mt-2 font-display text-lg font-semibold text-sand">{{ p.title }}</h3> <h3 class="mt-2 font-display text-lg font-semibold text-fg">{{ p.title }}</h3>
<p class="mt-1 text-sm text-sand-dim">{{ p.body }}</p> <p class="mt-1 text-sm text-fg-muted">{{ p.body }}</p>
</li> </li>
</ol> </ol>
<p class="mt-10 rounded-2xl border border-pine-500/20 bg-pine-500/5 p-5 text-center text-sand"> <p class="mt-10 rounded-2xl border border-pine-500/20 bg-pine-500/5 p-5 text-center text-fg">
Once the mycelium runs, every new flower is Once the mycelium runs, every new flower is
<span class="font-semibold text-pine-300">marginal cost, not a new company.</span> <span class="font-semibold text-pine">marginal cost, not a new company.</span>
</p> </p>
</SlideFrame> </SlideFrame>
</template> </template>

View file

@ -16,16 +16,16 @@ const icons = ['Building2', 'BedDouble', 'Cpu']
<div <div
v-for="(b, i) in buildings" v-for="(b, i) in buildings"
:key="b.name" :key="b.name"
class="flex flex-col rounded-2xl border border-white/10 bg-white/[0.03] p-5" class="flex flex-col rounded-2xl border border-line bg-surface p-5"
> >
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span class="grid size-10 place-items-center rounded-xl bg-indigo-500/10 text-indigo-300"> <span class="grid size-10 place-items-center rounded-xl bg-indigo-500/10 text-indigo">
<Icon :name="icons[i] ?? 'Building2'" class="size-5" /> <Icon :name="icons[i] ?? 'Building2'" class="size-5" />
</span> </span>
<h3 class="font-display text-base font-semibold leading-tight text-sand">{{ b.name }}</h3> <h3 class="font-display text-base font-semibold leading-tight text-fg">{{ b.name }}</h3>
</div> </div>
<ul class="mt-4 space-y-1.5"> <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"> <li v-for="r in b.rooms" :key="r" class="flex items-center gap-2 text-sm text-fg-muted">
<span class="size-1.5 rounded-full bg-indigo-400" /> <span class="size-1.5 rounded-full bg-indigo-400" />
{{ r }} {{ r }}
</li> </li>
@ -33,7 +33,7 @@ const icons = ['Building2', 'BedDouble', 'Cpu']
</div> </div>
</div> </div>
<p class="mt-6 text-sm text-sand-dim"> <p class="mt-6 text-sm text-fg-muted">
The Yellow House data center makes SolLunar self-hosting — the Archipelago stack runs on-site, The Yellow House data center makes SolLunar self-hosting — the Archipelago stack runs on-site,
not in someone else's cloud. not in someone else's cloud.
</p> </p>

View file

@ -14,13 +14,13 @@ import { tech } from '../data'
<div <div
v-for="t in tech" v-for="t in tech"
:key="t.title" :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" class="rounded-2xl border border-line bg-surface 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"> <span class="grid size-10 place-items-center rounded-xl bg-pine-500/10 text-pine">
<Icon :name="t.icon" class="size-5" /> <Icon :name="t.icon" class="size-5" />
</span> </span>
<h3 class="mt-4 font-display text-lg font-semibold text-sand">{{ t.title }}</h3> <h3 class="mt-4 font-display text-lg font-semibold text-fg">{{ t.title }}</h3>
<p class="mt-1.5 text-sm leading-relaxed text-sand-dim">{{ t.body }}</p> <p class="mt-1.5 text-sm leading-relaxed text-fg-muted">{{ t.body }}</p>
</div> </div>
</div> </div>
</SlideFrame> </SlideFrame>

View file

@ -14,18 +14,18 @@ import { tenants } from '../data'
<div <div
v-for="t in tenants" v-for="t in tenants"
:key="t.name" :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" class="flex flex-col rounded-2xl border border-line bg-surface 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"> <span class="grid size-10 place-items-center rounded-xl bg-indigo-500/10 text-indigo">
<Icon :name="t.icon" class="size-5" /> <Icon :name="t.icon" class="size-5" />
</span> </span>
<h3 class="mt-4 font-display text-lg font-semibold text-sand">{{ t.name }}</h3> <h3 class="mt-4 font-display text-lg font-semibold text-fg">{{ t.name }}</h3>
<p class="mt-1.5 text-sm leading-relaxed text-sand-dim">{{ t.blurb }}</p> <p class="mt-1.5 text-sm leading-relaxed text-fg-muted">{{ t.blurb }}</p>
</div> </div>
</div> </div>
<p class="mt-6 text-sm text-sand-dim"> <p class="mt-6 text-sm text-fg-muted">
Tenants pay rent <span class="text-sand">and</span> drive events, retail and food traffic — Tenants pay rent <span class="text-fg">and</span> drive events, retail and food traffic —
the same footfall that fills memberships. the same footfall that fills memberships.
</p> </p>
</SlideFrame> </SlideFrame>

View file

@ -7,42 +7,42 @@ import { brand, surfaceEntities, accentText } from '../data'
<template> <template>
<SlideFrame center> <SlideFrame center>
<div class="flex flex-col items-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"> <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">
Investment prospectus Investment prospectus
<PlaceholderBadge label="draft" /> <PlaceholderBadge label="draft" />
</span> </span>
<h1 class="font-display text-5xl font-bold tracking-tight text-sand sm:text-6xl lg:text-7xl"> <h1 class="font-display text-5xl font-bold tracking-tight text-fg sm:text-6xl lg:text-7xl">
{{ brand.name }} {{ brand.name }}
</h1> </h1>
<p class="mt-6 max-w-2xl text-balance text-xl text-sand sm:text-2xl"> <p class="mt-6 max-w-2xl text-balance text-xl text-fg sm:text-2xl">
{{ brand.tagline }} {{ brand.tagline }}
</p> </p>
<!-- the three collaborating entities --> <!-- 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"> <div class="mt-10 flex flex-wrap items-center justify-center gap-x-3 gap-y-2 text-sm text-fg-muted">
<span class="uppercase tracking-[0.2em]">Three experiences, one backbone</span> <span class="uppercase tracking-[0.2em]">Three experiences, one backbone</span>
</div> </div>
<div class="mt-3 flex flex-wrap items-center justify-center gap-3"> <div class="mt-3 flex flex-wrap items-center justify-center gap-3">
<template v-for="(e, i) in surfaceEntities" :key="e.id"> <template v-for="(e, i) in surfaceEntities" :key="e.id">
<span class="font-display text-lg font-semibold" :class="accentText[e.accent]">{{ e.name }}</span> <span class="font-display text-lg font-semibold" :class="accentText[e.accent]">{{ e.name }}</span>
<span v-if="i < surfaceEntities.length - 1" class="text-sand-dim">·</span> <span v-if="i < surfaceEntities.length - 1" class="text-fg-muted">·</span>
</template> </template>
</div> </div>
<p class="mt-8 max-w-xl text-balance text-sm text-sand-dim"> <p class="mt-8 max-w-xl text-balance text-sm text-fg-muted">
{{ brand.subtagline }} {{ brand.subtagline }}
</p> </p>
<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="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">
<span class="size-1.5 rounded-full bg-indigo-400" /> <span class="size-1.5 rounded-full bg-indigo-400" />
Raising now · {{ brand.firstNode }} Raising now · {{ brand.firstNode }}
</span> </span>
<p class="mt-10 text-xs text-sand-dim"> <p class="mt-10 text-xs text-fg-muted">
Use <kbd class="rounded border border-white/15 px-1.5 py-0.5">→</kbd> / Use <kbd class="rounded border border-line-2 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-line-2 px-1.5 py-0.5">←</kbd> to navigate
</p> </p>
</div> </div>
</SlideFrame> </SlideFrame>

View file

@ -11,9 +11,9 @@ import { problem } from '../data'
<li <li
v-for="(p, i) in problem.points" v-for="(p, i) in problem.points"
:key="i" :key="i"
class="flex items-start gap-4 text-lg text-sand-dim" class="flex items-start gap-4 text-lg text-fg-muted"
> >
<span class="mt-1 grid size-6 shrink-0 place-items-center rounded-full bg-white/5 text-xs font-semibold text-amber-300"> <span class="mt-1 grid size-6 shrink-0 place-items-center rounded-full bg-surface-2 text-xs font-semibold text-amber">
{{ i + 1 }} {{ i + 1 }}
</span> </span>
{{ p }} {{ p }}
@ -21,11 +21,11 @@ import { problem } from '../data'
</ul> </ul>
<div class="flex flex-col justify-center rounded-2xl border border-pine-500/20 bg-pine-500/5 p-8"> <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" /> <Icon name="HeartHandshake" class="size-8 text-pine" />
<p class="mt-4 text-xs font-semibold uppercase tracking-[0.2em] text-pine-300"> <p class="mt-4 text-xs font-semibold uppercase tracking-[0.2em] text-pine">
Our thesis Our thesis
</p> </p>
<p class="mt-3 font-display text-2xl leading-snug text-sand"> <p class="mt-3 font-display text-2xl leading-snug text-fg">
{{ problem.thesis }} {{ problem.thesis }}
</p> </p>
</div> </div>

View file

@ -24,6 +24,24 @@
--input: hsl(0 0% 89.8%); --input: hsl(0 0% 89.8%);
--ring: hsl(0 0% 3.9%); --ring: hsl(0 0% 3.9%);
--radius: 0.5rem; --radius: 0.5rem;
/* ── deck tokens · LIGHT ──────────────────────────────────── */
--deck-bg: hsl(150 20% 97%);
--deck-fg: hsl(160 25% 12%); /* primary text */
--deck-fg-muted: hsl(155 10% 38%); /* muted text */
--deck-surface: rgb(20 30 25 / 0.035); /* panels / cards */
--deck-surface-2: rgb(20 30 25 / 0.06); /* chips / icon bg */
--deck-surface-3: rgb(20 30 25 / 0.1); /* buttons / active */
--deck-surface-4: rgb(20 30 25 / 0.16); /* strong hover */
--deck-line: rgb(20 30 25 / 0.12);
--deck-line-2: rgb(20 30 25 / 0.18);
--deck-line-3: rgb(20 30 25 / 0.32);
/* accent TEXT — darker shades so they read on a light ground */
--deck-pine: hsl(162 60% 30%);
--deck-indigo: hsl(258 58% 52%);
--deck-rose: hsl(346 72% 48%);
--deck-clay: hsl(14 70% 44%);
--deck-amber: hsl(30 90% 38%);
} }
.dark { .dark {
@ -46,6 +64,23 @@
--border: hsl(0 0% 14.9%); --border: hsl(0 0% 14.9%);
--input: hsl(0 0% 14.9%); --input: hsl(0 0% 14.9%);
--ring: hsl(0 0% 83.1%); --ring: hsl(0 0% 83.1%);
/* ── deck tokens · DARK ───────────────────────────────────── */
--deck-bg: hsl(160 28% 6%);
--deck-fg: hsl(44 30% 90%);
--deck-fg-muted: hsl(150 8% 62%);
--deck-surface: rgb(255 255 255 / 0.03);
--deck-surface-2: rgb(255 255 255 / 0.05);
--deck-surface-3: rgb(255 255 255 / 0.1);
--deck-surface-4: rgb(255 255 255 / 0.2);
--deck-line: rgb(255 255 255 / 0.1);
--deck-line-2: rgb(255 255 255 / 0.15);
--deck-line-3: rgb(255 255 255 / 0.28);
--deck-pine: hsl(158 42% 62%);
--deck-indigo: hsl(255 85% 78%);
--deck-rose: hsl(342 95% 76%);
--deck-clay: hsl(14 72% 62%);
--deck-amber: hsl(38 96% 70%);
} }
@theme inline { @theme inline {
@ -71,19 +106,30 @@
--radius-lg: var(--radius); --radius-lg: var(--radius);
--radius-md: calc(var(--radius) - 2px); --radius-md: calc(var(--radius) - 2px);
--radius-sm: calc(var(--radius) - 4px); --radius-sm: calc(var(--radius) - 4px);
/* deck semantic tokens — these FLIP with the .dark class */
--color-fg: var(--deck-fg);
--color-fg-muted: var(--deck-fg-muted);
--color-surface: var(--deck-surface);
--color-surface-2: var(--deck-surface-2);
--color-surface-3: var(--deck-surface-3);
--color-surface-4: var(--deck-surface-4);
--color-line: var(--deck-line);
--color-line-2: var(--deck-line-2);
--color-line-3: var(--deck-line-3);
--color-pine: var(--deck-pine);
--color-indigo: var(--deck-indigo);
--color-rose: var(--deck-rose);
--color-clay: var(--deck-clay);
--color-amber: var(--deck-amber);
} }
/* ───────────────────────────────────────────────────────────── /* ─────────────────────────────────────────────────────────────
ARCHIPELAGO pitch-deck brand palette (PLACEHOLDER — swap when real ARCHIPELAGO pitch-deck brand palette. Numbered accent scales are
brand assets land). Regenerative-tech: pine green + warm amber static SOLID fills (bars, dots, tinted panels) that read on both
on a deep near-black ground. Used by the self-contained dark light and dark; the flipping `--color-{accent}` above is for TEXT.
deck; the shadcn tokens above are left intact for other pages.
───────────────────────────────────────────────────────────── */ ───────────────────────────────────────────────────────────── */
@theme { @theme {
--color-ink: hsl(160 28% 6%); /* deck ground */
--color-ink-2: hsl(160 24% 9%); /* raised panels */
--color-ink-3: hsl(158 20% 13%); /* cards / borders */
--color-pine-300: hsl(158 42% 62%); --color-pine-300: hsl(158 42% 62%);
--color-pine-400: hsl(159 45% 50%); --color-pine-400: hsl(159 45% 50%);
--color-pine-500: hsl(160 60% 40%); --color-pine-500: hsl(160 60% 40%);
@ -93,19 +139,18 @@
--color-amber-400: hsl(36 95% 60%); --color-amber-400: hsl(36 95% 60%);
--color-amber-500: hsl(32 92% 52%); --color-amber-500: hsl(32 92% 52%);
/* SolLunar Punk Society (city) accent — lunarpunk indigo */
--color-indigo-300: hsl(255 85% 78%); --color-indigo-300: hsl(255 85% 78%);
--color-indigo-400: hsl(256 78% 68%); --color-indigo-400: hsl(256 78% 68%);
--color-indigo-500: hsl(258 70% 58%); --color-indigo-500: hsl(258 70% 58%);
/* Pop-up Festivals accent — vibrant rave rose */
--color-rose-300: hsl(342 95% 76%); --color-rose-300: hsl(342 95% 76%);
--color-rose-400: hsl(344 85% 66%); --color-rose-400: hsl(344 85% 66%);
--color-rose-500: hsl(346 78% 56%); --color-rose-500: hsl(346 78% 56%);
--color-clay-400: hsl(14 72% 62%); /* farm / warmth accent */ --color-clay-400: hsl(14 72% 62%);
--color-sand: hsl(44 30% 90%); /* primary deck text */
--color-sand-dim: hsl(150 8% 62%); /* muted deck text */ /* dark ink text that sits on bright accents — same in both modes */
--color-on-accent: hsl(160 28% 8%);
--font-display: 'Georgia', 'Times New Roman', serif; --font-display: 'Georgia', 'Times New Roman', serif;
} }
@ -118,12 +163,18 @@
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
/* Deck root: self-contained dark canvas independent of shadcn tokens */ /* Deck canvas — a brand-tinted gradient that flips with .dark */
.deck-root { .deck-root {
background:
radial-gradient(120% 80% at 80% -10%, hsl(162 45% 82% / 0.7), transparent 60%),
radial-gradient(90% 70% at -10% 110%, hsl(32 85% 85% / 0.6), transparent 55%),
var(--deck-bg);
color: var(--color-fg);
}
.deck-root.dark {
background: background:
radial-gradient(120% 80% at 80% -10%, hsl(162 60% 14% / 0.55), transparent 60%), radial-gradient(120% 80% at 80% -10%, hsl(162 60% 14% / 0.55), transparent 60%),
radial-gradient(90% 70% at -10% 110%, hsl(32 80% 18% / 0.35), transparent 55%), radial-gradient(90% 70% at -10% 110%, hsl(32 80% 18% / 0.35), transparent 55%),
var(--color-ink); var(--deck-bg);
color: var(--color-sand);
} }
} }