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:
parent
b292b987c3
commit
ce11b60ca0
20 changed files with 239 additions and 169 deletions
|
|
@ -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>
|
||||
<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"
|
||||
|
|
|
|||
|
|
@ -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 & app as every node.
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 don’t 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>
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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&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>
|
||||
|
|
|
|||
|
|
@ -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 they’ll appear here automatically.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue