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">
import { computed } from 'vue'
import { ChevronLeft, ChevronRight } from '@lucide/vue'
import { computed, ref, watch } from 'vue'
import { ChevronLeft, ChevronRight, Sun, Moon } from '@lucide/vue'
import { useDeck } from './useDeck'
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 VisionSlide from './slides/VisionSlide.vue'
import NetworkSlide from './slides/NetworkSlide.vue'
@ -44,15 +52,26 @@ const transitionName = computed(() => (direction.value === 1 ? 'slide-next' : 's
</script>
<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 -->
<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 }}
</span>
<span class="text-xs uppercase tracking-[0.2em]">{{ current.label }}</span>
<div class="flex items-center gap-3">
<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>
<!-- slide stage -->
@ -67,7 +86,7 @@ const transitionName = computed(() => (direction.value === 1 ? 'slide-next' : 's
<!-- controls -->
<div class="absolute inset-x-0 bottom-0 z-20 flex items-center justify-between gap-4 px-6 py-5">
<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"
aria-label="Previous slide"
@click="prev"
@ -81,7 +100,7 @@ const transitionName = computed(() => (direction.value === 1 ? 'slide-next' : 's
v-for="(s, i) in slides"
:key="s.id"
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"
:aria-label="`Go to ${s.label}`"
:aria-current="i === index"
@ -90,11 +109,11 @@ const transitionName = computed(() => (direction.value === 1 ? 'slide-next' : 's
</nav>
<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 }}
</span>
<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"
aria-label="Next slide"
@click="next"

View file

@ -11,17 +11,17 @@ const a = props.entity.accent
<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>
<h2 class="font-display text-4xl font-semibold text-fg 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>
<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" />
</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">
<!-- unique traits -->
@ -30,7 +30,7 @@ const a = props.entity.accent
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">
<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]">
<Icon name="Check" class="size-3" :class="accentText[a]" />
</span>
@ -46,7 +46,7 @@ const a = props.entity.accent
<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"
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]" />
{{ s }}
@ -60,15 +60,15 @@ const a = props.entity.accent
<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">
<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">
<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" />
<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" />
Runs on Archipelago same membership, kiosk &amp; app as every node.
</div>
</div>

View file

@ -75,7 +75,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKey, true))
<!-- close -->
<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"
@click.stop="close"
>
@ -85,7 +85,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKey, true))
<!-- prev / next -->
<button
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"
@click.stop="prev"
>
@ -93,7 +93,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKey, true))
</button>
<button
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"
@click.stop="next"
>
@ -103,10 +103,10 @@ onUnmounted(() => window.removeEventListener('keydown', onKey, true))
<!-- counter + hint -->
<div
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 }}
<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>
</Transition>

View file

@ -280,16 +280,16 @@ const festivalCaps = [
<div class="mt-6 grid gap-4 text-sm sm:grid-cols-2">
<div class="flex items-start gap-3">
<span class="mt-0.5 text-pine-300"><Icon name="Layers" class="size-5" /></span>
<p class="text-sand-dim">
<span class="font-medium text-sand">Archipelago is the mycelium.</span>
<span class="mt-0.5 text-pine"><Icon name="Layers" class="size-5" /></span>
<p class="text-fg-muted">
<span class="font-medium text-fg">Archipelago is the mycelium.</span>
One shared backbone runs unseen beneath every entity members feel it, they dont see it.
</p>
</div>
<div class="flex items-start gap-3">
<span class="mt-0.5 text-amber-300"><Icon name="Sparkles" class="size-5" /></span>
<p class="text-sand-dim">
<span class="font-medium text-sand">Flowers are cheap to grow.</span>
<span class="mt-0.5 text-amber"><Icon name="Sparkles" class="size-5" /></span>
<p class="text-fg-muted">
<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.
</p>
</div>

View file

@ -5,7 +5,7 @@ withDefaults(defineProps<{ label?: string }>(), { label: 'placeholder' })
<template>
<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"
>
{{ label }}

View file

@ -15,19 +15,19 @@ defineProps<{
>
<p
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 }}
</p>
<h2
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 }}
</h2>
<p
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' : ''"
>
{{ subtitle }}

View file

@ -10,11 +10,11 @@ const shares = investorAsk.scenarios.map((s) => ({ equity: s, annual: Math.round
<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">
<p class="text-xs font-semibold uppercase tracking-[0.3em] text-amber">The ask</p>
<h2 class="mt-4 font-display text-4xl font-semibold text-fg sm:text-5xl">
{{ fmtUsd(investorAsk.amount) }} for {{ pct(investorAsk.equity) }}% of SolLunar Society
</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
</p>
@ -23,36 +23,36 @@ const shares = investorAsk.scenarios.map((s) => ({ equity: s, annual: Math.round
<div
v-for="s in shares"
: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="mt-1 font-display text-3xl font-bold text-sand">{{ fmtUsd(s.annual) }}</p>
<p class="text-xs text-sand-dim">/ yr profit</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-fg">{{ fmtUsd(s.annual) }}</p>
<p class="text-xs text-fg-muted">/ 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>
<p class="text-xs uppercase tracking-wider text-pine">Node profit</p>
<p class="mt-1 font-display text-3xl font-bold text-fg">{{ fmtUsd(pnl.yearlyProfit) }}</p>
<p class="text-xs text-fg-muted">/ yr (recomputed)</p>
</div>
</div>
<div class="mt-9 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"
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" />
{{ 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"
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" />
Book an investor call
</button>
</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.
</p>
</SlideFrame>

View file

@ -14,19 +14,19 @@ import { userEcosystem, surfaceEntities as entities } from '../data'
<div
v-for="item in userEcosystem"
: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" />
</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>
<h3 class="mt-4 font-display text-lg font-semibold text-fg">{{ item.title }}</h3>
<p class="mt-1.5 text-sm leading-relaxed text-fg-muted">{{ item.body }}</p>
</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 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>
</SlideFrame>
</template>

View file

@ -21,24 +21,24 @@ const archipelago = entityById('archipelago')
>
<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"
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
</span>
<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" />
</span>
<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>
</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">
<Icon :name="e.id === 'sollunar' ? 'Check' : 'Layers'" class="size-4" :class="e.id === 'sollunar' ? 'text-indigo-300' : 'text-amber-300'" />
<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' : 'text-amber'" />
{{ e.id === 'sollunar' ? 'Live P&amp;L · this raise' : 'Vision · on the roadmap' }}
</div>
</div>
@ -46,17 +46,17 @@ const archipelago = entityById('archipelago')
<!-- the shared backbone beneath all three -->
<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" />
</span>
<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 }}
<span class="ml-1 text-xs font-normal uppercase tracking-wider" :class="accentText[archipelago.accent]">
· {{ archipelago.role }}
</span>
</h3>
<p class="text-sm text-sand-dim">{{ archipelago.blurb }}</p>
<p class="text-sm text-fg-muted">{{ archipelago.blurb }}</p>
</div>
</div>
</SlideFrame>

View file

@ -33,14 +33,14 @@ function selectTab(id: (typeof gallerySections)[number]['id']) {
:class="
activeId === s.id
? [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"
@click="selectTab(s.id)"
>
<span class="font-medium">{{ s.label }}</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>
</div>
@ -50,7 +50,7 @@ function selectTab(id: (typeof gallerySections)[number]['id']) {
<button
v-for="(img, i) in active.images"
: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}`"
@click="lightboxIndex = i"
>
@ -63,7 +63,7 @@ function selectTab(id: (typeof gallerySections)[number]['id']) {
<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"
>
<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" />
</span>
</span>
@ -73,15 +73,15 @@ function selectTab(id: (typeof gallerySections)[number]['id']) {
<!-- empty state -->
<div
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" />
</span>
<p class="mt-4 text-sand">No {{ active.label }} photos yet.</p>
<p class="mt-1 max-w-md text-sm text-sand-dim">
<p class="mt-4 text-fg">No {{ active.label }} photos yet.</p>
<p class="mt-1 max-w-md text-sm text-fg-muted">
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 theyll appear here automatically.
</p>
</div>

View file

@ -18,25 +18,25 @@ const tiers = [...memberships].sort((a, b) => a.price - b.price)
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]'"
:class="t.featured ? 'border-indigo-500/50 bg-indigo-500/[0.07]' : 'border-line bg-surface'"
>
<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"
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
</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">
<span class="font-display text-4xl font-bold text-sand">{{ fmtUsd(t.price) }}</span>
<span class="text-sm text-sand-dim">/mo</span>
<span class="font-display text-4xl font-bold text-fg">{{ fmtUsd(t.price) }}</span>
<span class="text-sm text-fg-muted">/mo</span>
</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">
<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" />
<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" />
{{ p }}
</li>
</ul>
@ -44,14 +44,14 @@ const tiers = [...memberships].sort((a, b) => a.price - b.price)
</div>
<!-- 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
v-for="c in membershipComparables"
: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>
</div>
</SlideFrame>

View file

@ -9,9 +9,9 @@ const barBg: Record<string, string> = {
amber: 'bg-amber-400',
}
const text: Record<string, string> = {
indigo: 'text-indigo-300',
pine: 'text-pine-300',
amber: 'text-amber-300',
indigo: 'text-indigo',
pine: 'text-pine',
amber: 'text-amber',
}
const ring: Record<string, string> = {
indigo: 'border-indigo-500/40 bg-indigo-500/[0.06]',
@ -32,7 +32,7 @@ const pct = (e: number) => Math.round(e * 100)
<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="flex h-full items-center justify-center text-[11px] font-semibold text-on-accent"
:class="barBg[p.accent]"
:style="{ width: `${pct(p.equity)}%` }"
>
@ -48,27 +48,27 @@ const pct = (e: number) => Math.round(e * 100)
: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>
<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>
</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">
<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]" />
{{ 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 v-if="p.note" class="mt-3 border-t border-line pt-3 text-[11px] leading-relaxed text-fg-muted">
{{ 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">
<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 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) }}%
</span>
<span>·</span>

View file

@ -11,10 +11,10 @@ const barBg: Record<string, string> = {
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',
pine: 'text-pine',
indigo: 'text-indigo',
rose: 'text-rose',
clay: 'text-clay',
}
const ring: Record<string, string> = {
pine: 'border-pine-500/50 bg-pine-500/[0.06]',
@ -42,28 +42,28 @@ const revPct = (id: string) =>
>
<!-- 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 class="rounded-2xl border border-line bg-surface p-4">
<p class="text-xs uppercase tracking-wider text-fg-muted">Revenue / mo</p>
<p class="mt-1 font-display text-2xl font-bold text-fg">{{ 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 class="rounded-2xl border border-line bg-surface p-4">
<p class="text-xs uppercase tracking-wider text-fg-muted">Expenses / mo</p>
<p class="mt-1 font-display text-2xl font-bold text-fg">{{ 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>
<p class="text-xs uppercase tracking-wider text-pine">Profit / mo</p>
<p class="mt-1 font-display text-2xl font-bold text-fg">{{ 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>
<p class="text-xs uppercase tracking-wider text-amber">Profit / yr</p>
<p class="mt-1 font-display text-2xl font-bold text-fg">{{ 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>
<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">
<button
v-for="g in revenueGroups"
@ -85,7 +85,7 @@ const revPct = (id: string) =>
>
<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>
<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]">
{{ fmtUsd(groupTotal(selectedGroup)) }}
</span>
@ -96,20 +96,20 @@ const revPct = (id: string) =>
:key="item.label"
class="flex items-baseline justify-between gap-3 text-sm"
>
<span class="text-sand-dim">
<span class="text-fg-muted">
{{ 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 class="shrink-0 tabular-nums text-sand">{{ fmtUsd(item.amount) }}</span>
<span class="shrink-0 tabular-nums text-fg">{{ 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"
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.
</div>
</Transition>
@ -124,26 +124,26 @@ const revPct = (id: string) =>
:class="
selected === g.id
? 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)"
>
<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" />
</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>
<h3 class="font-display text-base font-semibold text-fg">{{ g.title }}</h3>
<p class="text-xs uppercase tracking-wider text-fg-muted">{{ 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' : ''" />
<Icon name="ChevronRight" class="size-4 shrink-0 text-fg-muted transition-transform" :class="selected === g.id ? 'rotate-90' : ''" />
</button>
</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
{{ fmtUsd(pnlDocSummary.revenue) }} rev / {{ fmtUsd(pnlDocSummary.expenses) }} exp
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">
<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"
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 }}
</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 }}
</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>
<h3 class="mt-2 font-display text-lg font-semibold text-fg">{{ p.title }}</h3>
<p class="mt-1 text-sm text-fg-muted">{{ 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">
<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
<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>
</SlideFrame>
</template>

View file

@ -16,16 +16,16 @@ const icons = ['Building2', 'BedDouble', 'Cpu']
<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"
class="flex flex-col rounded-2xl border border-line bg-surface 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">
<span class="grid size-10 place-items-center rounded-xl bg-indigo-500/10 text-indigo">
<Icon :name="icons[i] ?? 'Building2'" class="size-5" />
</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>
<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" />
{{ r }}
</li>
@ -33,7 +33,7 @@ const icons = ['Building2', 'BedDouble', 'Cpu']
</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,
not in someone else's cloud.
</p>

View file

@ -14,13 +14,13 @@ import { tech } from '../data'
<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"
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" />
</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>
<h3 class="mt-4 font-display text-lg font-semibold text-fg">{{ t.title }}</h3>
<p class="mt-1.5 text-sm leading-relaxed text-fg-muted">{{ t.body }}</p>
</div>
</div>
</SlideFrame>

View file

@ -14,18 +14,18 @@ import { tenants } from '../data'
<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"
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" />
</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>
<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-fg-muted">{{ 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
<p class="mt-6 text-sm text-fg-muted">
Tenants pay rent <span class="text-fg">and</span> drive events, retail and food traffic
the same footfall that fills memberships.
</p>
</SlideFrame>

View file

@ -7,42 +7,42 @@ import { brand, surfaceEntities, accentText } from '../data'
<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">
<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
<PlaceholderBadge label="draft" />
</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 }}
</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 }}
</p>
<!-- 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>
</div>
<div class="mt-3 flex flex-wrap items-center justify-center gap-3">
<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 v-if="i < surfaceEntities.length - 1" class="text-sand-dim">·</span>
<span v-if="i < surfaceEntities.length - 1" class="text-fg-muted">·</span>
</template>
</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 }}
</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" />
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> /
<kbd class="rounded border border-white/15 px-1.5 py-0.5"></kbd> to navigate
<p class="mt-10 text-xs text-fg-muted">
Use <kbd class="rounded border border-line-2 px-1.5 py-0.5"></kbd> /
<kbd class="rounded border border-line-2 px-1.5 py-0.5"></kbd> to navigate
</p>
</div>
</SlideFrame>

View file

@ -11,9 +11,9 @@ import { problem } from '../data'
<li
v-for="(p, i) in problem.points"
: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 }}
</span>
{{ p }}
@ -21,11 +21,11 @@ import { problem } from '../data'
</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">
<Icon name="HeartHandshake" class="size-8 text-pine" />
<p class="mt-4 text-xs font-semibold uppercase tracking-[0.2em] text-pine">
Our thesis
</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 }}
</p>
</div>

View file

@ -24,6 +24,24 @@
--input: hsl(0 0% 89.8%);
--ring: hsl(0 0% 3.9%);
--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 {
@ -46,6 +64,23 @@
--border: hsl(0 0% 14.9%);
--input: hsl(0 0% 14.9%);
--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 {
@ -71,19 +106,30 @@
--radius-lg: var(--radius);
--radius-md: calc(var(--radius) - 2px);
--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
brand assets land). Regenerative-tech: pine green + warm amber
on a deep near-black ground. Used by the self-contained dark
deck; the shadcn tokens above are left intact for other pages.
ARCHIPELAGO pitch-deck brand palette. Numbered accent scales are
static SOLID fills (bars, dots, tinted panels) that read on both
light and dark; the flipping `--color-{accent}` above is for TEXT.
*/
@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-400: hsl(159 45% 50%);
--color-pine-500: hsl(160 60% 40%);
@ -93,19 +139,18 @@
--color-amber-400: hsl(36 95% 60%);
--color-amber-500: hsl(32 92% 52%);
/* SolLunar Punk Society (city) accent — lunarpunk indigo */
--color-indigo-300: hsl(255 85% 78%);
--color-indigo-400: hsl(256 78% 68%);
--color-indigo-500: hsl(258 70% 58%);
/* Pop-up Festivals accent — vibrant rave rose */
--color-rose-300: hsl(342 95% 76%);
--color-rose-400: hsl(344 85% 66%);
--color-rose-500: hsl(346 78% 56%);
--color-clay-400: hsl(14 72% 62%); /* farm / warmth accent */
--color-sand: hsl(44 30% 90%); /* primary deck text */
--color-sand-dim: hsl(150 8% 62%); /* muted deck text */
--color-clay-400: hsl(14 72% 62%);
/* 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;
}
@ -118,12 +163,18 @@
@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 {
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:
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%),
var(--color-ink);
color: var(--color-sand);
var(--deck-bg);
}
}