Compare commits

..

2 commits

Author SHA1 Message Date
Patrick Mulligan
4f84bec477 fix(deck): make the mycelium visualization light-mode aware
Slide 3's SVG hardcoded a dark-tuned palette (light-green threads/nodes at
low opacity) and referenced the removed --color-sand/ink vars, so it washed
out / broke in light mode. Route soil, sky, threads, nodes, ground line and
ghost/plus marks through mode-aware --myc-* tokens (darker & more saturated
in light), and use the flipping accent tokens for flower fills, the pulse
and labels. Reads clearly in both themes now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 22:13:25 +02:00
Patrick Mulligan
ce11b60ca0 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>
2026-07-31 21:47:14 +02:00
20 changed files with 281 additions and 193 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

@ -79,9 +79,9 @@ const cha = entityById('chateau') // farm
// surface flowers: the three experiences + two faint "future" ones. // surface flowers: the three experiences + two faint "future" ones.
// (Archipelago is the mycelium itself the network below, not a flower.) // (Archipelago is the mycelium itself the network below, not a flower.)
const flowers = [ const flowers = [
{ x: 230, kind: 'bloom', color: 'var(--color-indigo-400)', name: sol.name, tag: sol.role }, { x: 230, kind: 'bloom', color: 'var(--color-indigo)', name: sol.name, tag: sol.role },
{ x: 500, kind: 'festival', color: 'var(--color-rose-400)', name: fest.name, tag: fest.role }, { x: 500, kind: 'festival', color: 'var(--color-rose)', name: fest.name, tag: fest.role },
{ x: 770, kind: 'bloom', color: 'var(--color-clay-400)', name: cha.name, tag: cha.role }, { x: 770, kind: 'bloom', color: 'var(--color-clay)', name: cha.name, tag: cha.role },
{ x: 100, kind: 'ghost' }, { x: 100, kind: 'ghost' },
{ x: 900, kind: 'ghost' }, { x: 900, kind: 'ghost' },
] as const ] as const
@ -104,12 +104,12 @@ const festivalCaps = [
> >
<defs> <defs>
<linearGradient id="skyGrad" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="skyGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="hsl(160 40% 10%)" stop-opacity="0" /> <stop offset="0%" stop-color="var(--myc-sky-top)" stop-opacity="0" />
<stop offset="100%" stop-color="hsl(160 45% 12%)" stop-opacity="0.5" /> <stop offset="100%" stop-color="var(--myc-sky-bot)" stop-opacity="0.5" />
</linearGradient> </linearGradient>
<linearGradient id="soilGrad" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="soilGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="hsl(28 40% 10%)" stop-opacity="0.55" /> <stop offset="0%" stop-color="var(--myc-soil-top)" stop-opacity="0.55" />
<stop offset="100%" stop-color="hsl(28 45% 6%)" stop-opacity="0.15" /> <stop offset="100%" stop-color="var(--myc-soil-bot)" stop-opacity="0.15" />
</linearGradient> </linearGradient>
<filter id="soft" x="-40%" y="-40%" width="180%" height="180%"> <filter id="soft" x="-40%" y="-40%" width="180%" height="180%">
<feGaussianBlur stdDeviation="4" /> <feGaussianBlur stdDeviation="4" />
@ -124,8 +124,8 @@ const festivalCaps = [
:y1="GROUND" :y1="GROUND"
x2="1000" x2="1000"
:y2="GROUND" :y2="GROUND"
stroke="var(--color-pine-500)" stroke="var(--myc-thread)"
stroke-opacity="0.35" stroke-opacity="0.5"
stroke-dasharray="2 6" stroke-dasharray="2 6"
/> />
@ -135,7 +135,7 @@ const festivalCaps = [
v-for="(e, i) in edges" v-for="(e, i) in edges"
:key="`glow-${i}`" :key="`glow-${i}`"
:d="thread(e, i)" :d="thread(e, i)"
stroke="var(--color-pine-500)" stroke="var(--myc-glow)"
stroke-opacity="0.18" stroke-opacity="0.18"
stroke-width="6" stroke-width="6"
filter="url(#soft)" filter="url(#soft)"
@ -144,8 +144,8 @@ const festivalCaps = [
v-for="(e, i) in edges" v-for="(e, i) in edges"
:key="`line-${i}`" :key="`line-${i}`"
:d="thread(e, i)" :d="thread(e, i)"
stroke="var(--color-pine-400)" stroke="var(--myc-thread)"
stroke-opacity="0.5" stroke-opacity="0.6"
stroke-width="1.4" stroke-width="1.4"
/> />
<path <path
@ -154,7 +154,7 @@ const festivalCaps = [
class="myc-pulse" class="myc-pulse"
:style="{ animationDelay: `${(i % 6) * 0.5}s` }" :style="{ animationDelay: `${(i % 6) * 0.5}s` }"
:d="thread(e, i)" :d="thread(e, i)"
stroke="var(--color-amber-300)" stroke="var(--color-amber)"
stroke-width="2.2" stroke-width="2.2"
stroke-linecap="round" stroke-linecap="round"
/> />
@ -170,7 +170,7 @@ const festivalCaps = [
:cx="n[0]" :cx="n[0]"
:cy="n[1]" :cy="n[1]"
r="4" r="4"
fill="var(--color-pine-300)" fill="var(--myc-node)"
/> />
</g> </g>
@ -216,8 +216,8 @@ const festivalCaps = [
/> />
</template> </template>
<!-- solunar mark: sun + moon spark --> <!-- solunar mark: sun + moon spark -->
<circle :cx="f.x" :cy="GROUND - 96" r="3.4" fill="var(--color-amber-300)" /> <circle :cx="f.x" :cy="GROUND - 96" r="3.4" fill="var(--color-amber)" />
<circle :cx="f.x + 9" :cy="GROUND - 100" r="2" fill="var(--color-indigo-300)" /> <circle :cx="f.x + 9" :cy="GROUND - 100" r="2" fill="var(--color-indigo)" />
</g> </g>
</template> </template>
@ -226,8 +226,8 @@ const festivalCaps = [
<path <path
:d="`M ${f.x} ${GROUND} C ${f.x - 5} ${GROUND - 22}, ${f.x + 5} ${GROUND - 38}, ${f.x} ${GROUND - 60}`" :d="`M ${f.x} ${GROUND} C ${f.x - 5} ${GROUND - 22}, ${f.x + 5} ${GROUND - 38}, ${f.x} ${GROUND - 60}`"
fill="none" fill="none"
stroke="var(--color-sand-dim)" stroke="var(--color-fg-muted)"
stroke-opacity="0.3" stroke-opacity="0.45"
stroke-dasharray="3 5" stroke-dasharray="3 5"
stroke-width="4" stroke-width="4"
stroke-linecap="round" stroke-linecap="round"
@ -235,16 +235,16 @@ const festivalCaps = [
<path <path
:d="`M ${f.x - 26} ${GROUND - 60} Q ${f.x} ${GROUND - 90} ${f.x + 26} ${GROUND - 60} Q ${f.x} ${GROUND - 68} ${f.x - 26} ${GROUND - 60} Z`" :d="`M ${f.x - 26} ${GROUND - 60} Q ${f.x} ${GROUND - 90} ${f.x + 26} ${GROUND - 60} Q ${f.x} ${GROUND - 68} ${f.x - 26} ${GROUND - 60} Z`"
fill="none" fill="none"
stroke="var(--color-sand-dim)" stroke="var(--color-fg-muted)"
stroke-opacity="0.35" stroke-opacity="0.5"
stroke-dasharray="3 5" stroke-dasharray="3 5"
stroke-width="1.6" stroke-width="1.6"
/> />
<g class="myc-plus" :style="{ animationDelay: `${i * 0.7}s` }"> <g class="myc-plus" :style="{ animationDelay: `${i * 0.7}s` }">
<circle :cx="f.x" :cy="GROUND - 76" r="9" fill="hsl(160 24% 9%)" stroke="var(--color-amber-400)" stroke-opacity="0.6" /> <circle :cx="f.x" :cy="GROUND - 76" r="9" fill="var(--deck-bg)" stroke="var(--color-amber)" stroke-opacity="0.7" />
<path <path
:d="`M ${f.x - 4} ${GROUND - 76} h 8 M ${f.x} ${GROUND - 80} v 8`" :d="`M ${f.x - 4} ${GROUND - 76} h 8 M ${f.x} ${GROUND - 80} v 8`"
stroke="var(--color-amber-400)" stroke="var(--color-amber)"
stroke-width="1.6" stroke-width="1.6"
stroke-linecap="round" stroke-linecap="round"
/> />
@ -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 dont see it. One shared backbone runs unseen beneath every entity members feel it, they dont 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>
@ -306,12 +306,12 @@ const festivalCaps = [
.myc-label-tag { .myc-label-tag {
font-size: 10px; font-size: 10px;
letter-spacing: 0.22em; letter-spacing: 0.22em;
fill: var(--color-sand-dim); fill: var(--color-fg-muted);
} }
.myc-zone { .myc-zone {
font-size: 11px; font-size: 11px;
letter-spacing: 0.24em; letter-spacing: 0.24em;
fill: var(--color-sand-dim); fill: var(--color-fg-muted);
opacity: 0.7; opacity: 0.7;
} }

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 theyll appear here automatically. and theyll 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,33 @@
--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%);
/* mycelium diagram · LIGHT — darker/saturated so it doesn't wash out */
--myc-glow: hsl(162 55% 42%);
--myc-thread: hsl(162 58% 32%);
--myc-node: hsl(162 62% 30%);
--myc-sky-top: hsl(160 35% 74%);
--myc-sky-bot: hsl(160 38% 66%);
--myc-soil-top: hsl(32 48% 56%);
--myc-soil-bot: hsl(32 42% 64%);
} }
.dark { .dark {
@ -46,6 +73,32 @@
--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%);
/* mycelium diagram · DARK — the original light-on-dark palette */
--myc-glow: hsl(160 60% 40%);
--myc-thread: hsl(159 45% 50%);
--myc-node: hsl(158 42% 62%);
--myc-sky-top: hsl(160 40% 10%);
--myc-sky-bot: hsl(160 45% 12%);
--myc-soil-top: hsl(28 40% 10%);
--myc-soil-bot: hsl(28 45% 6%);
} }
@theme inline { @theme inline {
@ -71,19 +124,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 +157,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 +181,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);
} }
} }