feat(deck): show three entity flowers in the mycelium viz
City and farm render as permanent blooms; Solunar Society is an ephemeral pop-up cluster (indigo, dashed stem, floats + fades). Underground caption reframed as the shared core stack every entity runs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2fbdada957
commit
9e74000f9f
1 changed files with 141 additions and 70 deletions
|
|
@ -2,35 +2,37 @@
|
||||||
/**
|
/**
|
||||||
* The core metaphor, as an animated SVG.
|
* The core metaphor, as an animated SVG.
|
||||||
*
|
*
|
||||||
* Underground → the mycelium = our tech foundation / custom software
|
* Underground → the mycelium = the shared core stack / custom software.
|
||||||
* stacks. Unseen, always working, connects everything.
|
* Unseen, always working, connects every entity.
|
||||||
* Surface → "flowers" (fruiting bodies) = physical locations
|
* Flowers → the three entities people actually experience:
|
||||||
* (City + Farm) that people actually experience.
|
* • Archipelago (city center) — a permanent bloom
|
||||||
* Ghost flowers→ once the mycelium runs, new locations sprout cheaply,
|
* • Solunar Society (pop-up fest.) — ephemeral cluster
|
||||||
* anywhere in the world.
|
* • Chateau du Faune (farm center) — a permanent bloom
|
||||||
|
* Ghost flowers→ once the mycelium runs, new locations & festivals sprout
|
||||||
|
* cheaply, anywhere in the world.
|
||||||
*
|
*
|
||||||
* Pure SVG + CSS so it scales crisply on a projector and respects
|
* Pure SVG + CSS so it scales crisply on a projector and respects
|
||||||
* prefers-reduced-motion.
|
* prefers-reduced-motion.
|
||||||
*/
|
*/
|
||||||
import Icon from './Icon.vue'
|
import Icon from './Icon.vue'
|
||||||
import { locations } from '../data'
|
import { entityById } from '../data'
|
||||||
|
|
||||||
const GROUND = 300
|
const GROUND = 300
|
||||||
|
|
||||||
// underground mycelium nodes (below the ground line)
|
// underground mycelium nodes (below the ground line)
|
||||||
const nodes: [number, number][] = [
|
const nodes: [number, number][] = [
|
||||||
[330, 340], // root under city
|
[230, 340], // root · Archipelago
|
||||||
[670, 340], // root under farm
|
[500, 335], // root · Solunar
|
||||||
[150, 335], // root under ghost 1
|
[770, 340], // root · Chateau
|
||||||
[500, 335], // root under ghost 2
|
[100, 340], // root · ghost
|
||||||
[850, 335], // root under ghost 3
|
[900, 340], // root · ghost
|
||||||
[90, 430],
|
[160, 430],
|
||||||
[250, 480],
|
[330, 470],
|
||||||
[410, 420],
|
[430, 420],
|
||||||
[560, 470],
|
[560, 470],
|
||||||
[720, 430],
|
[660, 430],
|
||||||
[900, 470],
|
[840, 470],
|
||||||
[180, 560],
|
[240, 560],
|
||||||
[430, 560],
|
[430, 560],
|
||||||
[620, 560],
|
[620, 560],
|
||||||
[810, 560],
|
[810, 560],
|
||||||
|
|
@ -38,33 +40,30 @@ const nodes: [number, number][] = [
|
||||||
|
|
||||||
// threads weaving the mycelium together (indices into `nodes`)
|
// threads weaving the mycelium together (indices into `nodes`)
|
||||||
const edges: [number, number][] = [
|
const edges: [number, number][] = [
|
||||||
[0, 7],
|
|
||||||
[0, 6],
|
[0, 6],
|
||||||
[0, 2],
|
[0, 5],
|
||||||
|
[0, 7],
|
||||||
[0, 3],
|
[0, 3],
|
||||||
[1, 8],
|
[3, 5],
|
||||||
[1, 9],
|
|
||||||
[1, 3],
|
|
||||||
[1, 4],
|
|
||||||
[2, 5],
|
|
||||||
[2, 6],
|
|
||||||
[3, 7],
|
|
||||||
[3, 8],
|
|
||||||
[4, 9],
|
|
||||||
[4, 10],
|
|
||||||
[5, 11],
|
[5, 11],
|
||||||
[6, 11],
|
[6, 11],
|
||||||
[6, 12],
|
[6, 12],
|
||||||
[7, 12],
|
[7, 12],
|
||||||
|
[7, 1],
|
||||||
|
[1, 8],
|
||||||
|
[8, 12],
|
||||||
[8, 13],
|
[8, 13],
|
||||||
|
[1, 9],
|
||||||
[9, 13],
|
[9, 13],
|
||||||
[9, 14],
|
[9, 2],
|
||||||
|
[2, 10],
|
||||||
[10, 14],
|
[10, 14],
|
||||||
[3, 0],
|
[4, 10],
|
||||||
[3, 1],
|
[9, 14],
|
||||||
|
[7, 8],
|
||||||
|
[2, 4],
|
||||||
]
|
]
|
||||||
|
|
||||||
// organic curved path between two nodes
|
|
||||||
function thread([a, b]: [number, number], i: number) {
|
function thread([a, b]: [number, number], i: number) {
|
||||||
const [ax, ay] = nodes[a]
|
const [ax, ay] = nodes[a]
|
||||||
const [bx, by] = nodes[b]
|
const [bx, by] = nodes[b]
|
||||||
|
|
@ -73,14 +72,25 @@ function thread([a, b]: [number, number], i: number) {
|
||||||
return `M ${ax} ${ay} Q ${mx} ${my} ${bx} ${by}`
|
return `M ${ax} ${ay} Q ${mx} ${my} ${bx} ${by}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// surface flowers: the two real nodes + three faint "future" ones
|
const arch = entityById('archipelago')
|
||||||
|
const sol = entityById('solunar')
|
||||||
|
const cha = entityById('chateau')
|
||||||
|
|
||||||
|
// surface flowers: three entities + two faint "future" ones
|
||||||
const flowers = [
|
const flowers = [
|
||||||
{ x: 330, kind: 'real', color: 'var(--color-pine-400)', name: locations.city.name, tag: 'City node' },
|
{ x: 230, kind: 'bloom', color: 'var(--color-pine-400)', name: arch.name, tag: arch.role },
|
||||||
{ x: 670, kind: 'real', color: 'var(--color-clay-400)', name: locations.farm.name, tag: 'Farm node' },
|
{ x: 500, kind: 'festival', color: 'var(--color-indigo-400)', name: sol.name, tag: sol.role },
|
||||||
{ x: 150, kind: 'ghost' },
|
{ x: 770, kind: 'bloom', color: 'var(--color-clay-400)', name: cha.name, tag: cha.role },
|
||||||
{ x: 500, kind: 'ghost' },
|
{ x: 100, kind: 'ghost' },
|
||||||
{ x: 850, kind: 'ghost' },
|
{ x: 900, kind: 'ghost' },
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
|
// small pop-up caps for the ephemeral festival flower
|
||||||
|
const festivalCaps = [
|
||||||
|
{ dx: -18, dy: -58, r: 15 },
|
||||||
|
{ dx: 0, dy: -74, r: 20 },
|
||||||
|
{ dx: 20, dy: -60, r: 14 },
|
||||||
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -165,18 +175,16 @@ const flowers = [
|
||||||
|
|
||||||
<!-- surface flowers -->
|
<!-- surface flowers -->
|
||||||
<g v-for="(f, i) in flowers" :key="`flower-${i}`">
|
<g v-for="(f, i) in flowers" :key="`flower-${i}`">
|
||||||
<!-- stem -->
|
<!-- permanent bloom (city / farm) -->
|
||||||
|
<template v-if="f.kind === 'bloom'">
|
||||||
<path
|
<path
|
||||||
:d="`M ${f.x} ${GROUND} C ${f.x - 6} ${GROUND - 26}, ${f.x + 6} ${GROUND - 44}, ${f.x} ${GROUND - 70}`"
|
:d="`M ${f.x} ${GROUND} C ${f.x - 6} ${GROUND - 26}, ${f.x + 6} ${GROUND - 44}, ${f.x} ${GROUND - 70}`"
|
||||||
fill="none"
|
fill="none"
|
||||||
:stroke="f.kind === 'real' ? f.color : 'var(--color-sand-dim)'"
|
:stroke="f.color"
|
||||||
:stroke-opacity="f.kind === 'real' ? 0.9 : 0.35"
|
stroke-opacity="0.9"
|
||||||
:stroke-dasharray="f.kind === 'ghost' ? '3 5' : '0'"
|
|
||||||
stroke-width="5"
|
stroke-width="5"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
/>
|
/>
|
||||||
<!-- cap -->
|
|
||||||
<template v-if="f.kind === 'real'">
|
|
||||||
<path
|
<path
|
||||||
:d="`M ${f.x - 42} ${GROUND - 70} Q ${f.x} ${GROUND - 116} ${f.x + 42} ${GROUND - 70} Q ${f.x} ${GROUND - 82} ${f.x - 42} ${GROUND - 70} Z`"
|
:d="`M ${f.x - 42} ${GROUND - 70} Q ${f.x} ${GROUND - 116} ${f.x + 42} ${GROUND - 70} Q ${f.x} ${GROUND - 82} ${f.x - 42} ${GROUND - 70} Z`"
|
||||||
:fill="f.color"
|
:fill="f.color"
|
||||||
|
|
@ -185,23 +193,46 @@ const flowers = [
|
||||||
/>
|
/>
|
||||||
<circle :cx="f.x - 16" :cy="GROUND - 88" r="3" fill="hsl(0 0% 100% / 0.55)" />
|
<circle :cx="f.x - 16" :cy="GROUND - 88" r="3" fill="hsl(0 0% 100% / 0.55)" />
|
||||||
<circle :cx="f.x + 12" :cy="GROUND - 92" r="2.4" fill="hsl(0 0% 100% / 0.45)" />
|
<circle :cx="f.x + 12" :cy="GROUND - 92" r="2.4" fill="hsl(0 0% 100% / 0.45)" />
|
||||||
<text
|
|
||||||
:x="f.x"
|
|
||||||
:y="GROUND - 132"
|
|
||||||
text-anchor="middle"
|
|
||||||
class="myc-label-name"
|
|
||||||
:fill="f.color"
|
|
||||||
>
|
|
||||||
{{ f.name }}
|
|
||||||
</text>
|
|
||||||
<text :x="f.x" :y="GROUND - 152" text-anchor="middle" class="myc-label-tag">
|
|
||||||
{{ f.tag?.toUpperCase() }}
|
|
||||||
</text>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- ephemeral festival cluster (Solunar) -->
|
||||||
|
<template v-else-if="f.kind === 'festival'">
|
||||||
|
<path
|
||||||
|
:d="`M ${f.x} ${GROUND} C ${f.x - 6} ${GROUND - 20}, ${f.x + 6} ${GROUND - 34}, ${f.x} ${GROUND - 46}`"
|
||||||
|
fill="none"
|
||||||
|
:stroke="f.color"
|
||||||
|
stroke-opacity="0.8"
|
||||||
|
stroke-width="4"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-dasharray="3 5"
|
||||||
|
/>
|
||||||
|
<g class="myc-festival" :style="{ animationDelay: `${i * 0.3}s` }">
|
||||||
|
<template v-for="(c, ci) in festivalCaps" :key="ci">
|
||||||
|
<path
|
||||||
|
:d="`M ${f.x + c.dx - c.r} ${GROUND + c.dy} Q ${f.x + c.dx} ${GROUND + c.dy - c.r * 1.1} ${f.x + c.dx + c.r} ${GROUND + c.dy} Q ${f.x + c.dx} ${GROUND + c.dy - c.r * 0.28} ${f.x + c.dx - c.r} ${GROUND + c.dy} Z`"
|
||||||
|
:fill="f.color"
|
||||||
|
:fill-opacity="0.75 + ci * 0.08"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<!-- solunar mark: sun + moon spark -->
|
||||||
|
<circle :cx="f.x" :cy="GROUND - 96" r="3.4" fill="var(--color-amber-300)" />
|
||||||
|
<circle :cx="f.x + 9" :cy="GROUND - 100" r="2" fill="var(--color-indigo-300)" />
|
||||||
|
</g>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- ghost future flower -->
|
<!-- ghost future flower -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<path
|
<path
|
||||||
:d="`M ${f.x - 30} ${GROUND - 70} Q ${f.x} ${GROUND - 104} ${f.x + 30} ${GROUND - 70} Q ${f.x} ${GROUND - 80} ${f.x - 30} ${GROUND - 70} Z`"
|
:d="`M ${f.x} ${GROUND} C ${f.x - 5} ${GROUND - 22}, ${f.x + 5} ${GROUND - 38}, ${f.x} ${GROUND - 60}`"
|
||||||
|
fill="none"
|
||||||
|
stroke="var(--color-sand-dim)"
|
||||||
|
stroke-opacity="0.3"
|
||||||
|
stroke-dasharray="3 5"
|
||||||
|
stroke-width="4"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<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`"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="var(--color-sand-dim)"
|
stroke="var(--color-sand-dim)"
|
||||||
stroke-opacity="0.35"
|
stroke-opacity="0.35"
|
||||||
|
|
@ -209,20 +240,41 @@ const flowers = [
|
||||||
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 - 86" 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="hsl(160 24% 9%)" stroke="var(--color-amber-400)" stroke-opacity="0.6" />
|
||||||
<path
|
<path
|
||||||
:d="`M ${f.x - 4} ${GROUND - 86} h 8 M ${f.x} ${GROUND - 90} 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-400)"
|
||||||
stroke-width="1.6"
|
stroke-width="1.6"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- labels for the three named entities -->
|
||||||
|
<template v-if="f.kind !== 'ghost'">
|
||||||
|
<text
|
||||||
|
:x="f.x"
|
||||||
|
:y="f.kind === 'festival' ? GROUND - 122 : GROUND - 132"
|
||||||
|
text-anchor="middle"
|
||||||
|
class="myc-label-name"
|
||||||
|
:fill="f.color"
|
||||||
|
>
|
||||||
|
{{ f.name }}
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
:x="f.x"
|
||||||
|
:y="f.kind === 'festival' ? GROUND - 142 : GROUND - 152"
|
||||||
|
text-anchor="middle"
|
||||||
|
class="myc-label-tag"
|
||||||
|
>
|
||||||
|
{{ f.tag?.toUpperCase() }}
|
||||||
|
</text>
|
||||||
|
</template>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- captions -->
|
<!-- captions -->
|
||||||
<text x="500" y="34" text-anchor="middle" class="myc-zone">ABOVE GROUND · WHAT PEOPLE EXPERIENCE</text>
|
<text x="500" y="34" text-anchor="middle" class="myc-zone">ABOVE GROUND · WHAT PEOPLE EXPERIENCE</text>
|
||||||
<text x="500" y="588" text-anchor="middle" class="myc-zone">UNDERGROUND · THE TECH FOUNDATION THEY NEVER SEE</text>
|
<text x="500" y="588" text-anchor="middle" class="myc-zone">UNDERGROUND · THE SHARED CORE STACK THEY NEVER SEE</text>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<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">
|
||||||
|
|
@ -230,14 +282,14 @@ const flowers = [
|
||||||
<span class="mt-0.5 text-pine-300"><Icon name="Layers" class="size-5" /></span>
|
<span class="mt-0.5 text-pine-300"><Icon name="Layers" class="size-5" /></span>
|
||||||
<p class="text-sand-dim">
|
<p class="text-sand-dim">
|
||||||
<span class="font-medium text-sand">The mycelium is the product.</span>
|
<span class="font-medium text-sand">The mycelium is the product.</span>
|
||||||
Custom software stacks and open hardware run out of sight — members feel it, they don’t see it.
|
One core stack runs unseen beneath every entity — members feel it, they don’t see it.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-start gap-3">
|
<div class="flex items-start gap-3">
|
||||||
<span class="mt-0.5 text-amber-300"><Icon name="Sparkles" class="size-5" /></span>
|
<span class="mt-0.5 text-amber-300"><Icon name="Sparkles" class="size-5" /></span>
|
||||||
<p class="text-sand-dim">
|
<p class="text-sand-dim">
|
||||||
<span class="font-medium text-sand">Flowers are cheap to grow.</span>
|
<span class="font-medium text-sand">Flowers are cheap to grow.</span>
|
||||||
Once the network is alive, each new location is just another fruiting body on proven infrastructure.
|
A city hub, a travelling festival, a farm — and every new bloom is just another fruiting body.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -307,6 +359,24 @@ const flowers = [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* festival cluster gently floats + fades — it's ephemeral */
|
||||||
|
.myc-festival {
|
||||||
|
transform-box: fill-box;
|
||||||
|
transform-origin: bottom center;
|
||||||
|
animation: myc-float 4.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes myc-float {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.myc-plus {
|
.myc-plus {
|
||||||
transform-box: fill-box;
|
transform-box: fill-box;
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
|
|
@ -326,6 +396,7 @@ const flowers = [
|
||||||
.myc-pulse,
|
.myc-pulse,
|
||||||
.myc-node,
|
.myc-node,
|
||||||
.myc-cap,
|
.myc-cap,
|
||||||
|
.myc-festival,
|
||||||
.myc-plus {
|
.myc-plus {
|
||||||
animation: none;
|
animation: none;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue