Remove bright gradient bands, fix Sankey data, add newsletter CTA

- Replace bright gradient section backgrounds with flat bg-deep
- Remove wave distortion (showDistortion=false) from all diagrams
- Fix circular link errors in naturalFlows by breaking feedback cycles
- Add multi-dimensional flow colors (energy, resource, signal, commons)
- Update CTA: "Dive into the Current" with newsletter subscription
- Newsletter posts to listmonk via newsletter-api (FlowFi list #26)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-09 21:39:32 -07:00
parent 35ada4127d
commit a08a6bdb69
7 changed files with 172 additions and 96 deletions

View File

@ -1,8 +1,37 @@
'use client'
import { useState, FormEvent } from 'react'
import ScrollReveal from '@/components/ui/ScrollReveal'
const FLOWFI_LIST_UUID = 'fefebe2c-0966-4df5-81ec-1eae9b9dce3f'
const SUBSCRIBE_URL = 'https://newsletter.jeffemmett.com/subscribe'
export default function CTASection() {
const [email, setEmail] = useState('')
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle')
async function handleSubmit(e: FormEvent) {
e.preventDefault()
if (!email) return
setStatus('loading')
try {
const res = await fetch(SUBSCRIBE_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, list_uuid: FLOWFI_LIST_UUID }),
})
if (res.ok) {
setStatus('success')
setEmail('')
} else {
setStatus('error')
}
} catch {
setStatus('error')
}
}
return (
<section className="min-h-[60vh] flex flex-col items-center justify-center relative px-4 py-24">
<div className="absolute inset-0 bg-gradient-to-b from-deep to-ocean" />
@ -21,18 +50,49 @@ export default function CTASection() {
</ScrollReveal>
<ScrollReveal delay={0.2}>
<p className="font-caveat text-2xl text-foam/35 mb-14">
You already are.
<p className="font-caveat text-2xl text-foam/35 mb-10">
Dive into the current.
</p>
</ScrollReveal>
<ScrollReveal delay={0.3}>
{status === 'success' ? (
<p className="text-flow/70 text-sm font-light mb-10">
You&apos;re in the current now. Welcome.
</p>
) : (
<form onSubmit={handleSubmit} className="flex flex-col sm:flex-row gap-3 justify-center items-center mb-10 max-w-md mx-auto">
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
placeholder="your@email.com"
required
className="w-full sm:flex-1 bg-ocean/40 border border-flow/15 rounded-full px-6 py-3 text-foam/70 placeholder:text-mist/25 text-sm font-light focus:outline-none focus:border-flow/40 focus:bg-ocean/60 transition-all duration-500"
/>
<button
type="submit"
disabled={status === 'loading'}
className="border border-flow/25 px-8 py-3 text-foam/50 hover:text-flow/80 hover:border-flow/50 hover:bg-flow/5 transition-all duration-500 text-sm rounded-full font-light disabled:opacity-40 whitespace-nowrap"
>
{status === 'loading' ? 'Diving in...' : 'Subscribe'}
</button>
</form>
)}
{status === 'error' && (
<p className="text-anti-green/50 text-xs font-light mb-6">
Something went wrong. Try again?
</p>
)}
</ScrollReveal>
<ScrollReveal delay={0.4}>
<p className="text-mist/25 text-sm mb-10 tracking-wide font-light">
NoFi FlowFi ???
</p>
</ScrollReveal>
<ScrollReveal delay={0.4}>
<ScrollReveal delay={0.5}>
<a
href="https://nofi.lol"
className="inline-block border border-flow/15 px-10 py-4 text-foam/30 hover:text-flow/60 hover:border-flow/40 hover:bg-flow/5 transition-all duration-700 text-sm rounded-full font-light"

View File

@ -8,16 +8,13 @@ import { extractiveFlows } from '@/lib/sankey-data'
export default function ExtractivePipeSection() {
return (
<section className="min-h-screen flex flex-col items-center justify-center relative px-4 py-24">
<div className="absolute inset-0 bg-gradient-to-b from-deep via-current to-deep" />
{/* Red pollution tint — subtle, unsettling */}
<div className="absolute inset-0" style={{ background: 'radial-gradient(ellipse at 50% 40%, rgba(233, 69, 96, 0.06) 0%, transparent 60%)' }} />
<div className="absolute inset-0 bg-deep" />
<div className="relative z-10 max-w-5xl w-full text-center">
<SectionHeader className="text-anti-green/80">The Extractive Pattern</SectionHeader>
<ScrollReveal delay={0.2}>
<div className="my-14 rounded-2xl overflow-hidden p-4 bg-current/30 border border-anti-green/10">
<div className="my-14 rounded-2xl overflow-hidden p-4 bg-ocean/20 border border-anti-green/8">
<SankeyDiagram
data={extractiveFlows}
showLabels={true}

View File

@ -8,25 +8,28 @@ import { naturalFlows } from '@/lib/sankey-data'
export default function NatureFlowsSection() {
return (
<section className="min-h-screen flex flex-col items-center justify-center relative px-4 py-24">
<div className="absolute inset-0 bg-gradient-to-b from-deep via-ocean to-deep" />
{/* Subtle teal ambient light */}
<div
className="absolute top-1/3 left-1/4 w-[500px] h-[500px] rounded-full opacity-30"
style={{ background: 'radial-gradient(circle, rgba(45, 212, 191, 0.06) 0%, transparent 70%)' }}
/>
<div className="absolute inset-0 bg-deep" />
<div className="relative z-10 max-w-5xl w-full text-center">
<SectionHeader>Natural Flows</SectionHeader>
<ScrollReveal delay={0.1}>
<div className="flex justify-center gap-6 mb-6 text-xs font-light">
<span className="flex items-center gap-1.5"><span className="w-3 h-0.5 rounded bg-[#fbbf24]/60 inline-block" /> Energy</span>
<span className="flex items-center gap-1.5"><span className="w-3 h-0.5 rounded bg-flow/60 inline-block" /> Resources</span>
<span className="flex items-center gap-1.5"><span className="w-3 h-0.5 rounded bg-[#f472b6]/60 inline-block" /> Signals</span>
<span className="flex items-center gap-1.5"><span className="w-3 h-0.5 rounded bg-mist/60 inline-block" /> Commons</span>
</div>
</ScrollReveal>
<ScrollReveal delay={0.2}>
<div className="my-14 rounded-2xl overflow-hidden p-4 bg-ocean/30">
<div className="my-8 rounded-2xl overflow-hidden p-4 bg-ocean/20">
<SankeyDiagram
data={naturalFlows}
showLabels={false}
showLabels={true}
speed="slow"
showParticles={true}
showDistortion={true}
showDistortion={false}
maxParticles={8}
/>
</div>
@ -34,7 +37,7 @@ export default function NatureFlowsSection() {
<ScrollReveal delay={0.4}>
<p className="font-caveat text-xl md:text-2xl text-foam/45 max-w-2xl mx-auto leading-relaxed">
In living systems, value doesn&apos;t accumulate. It <span className="text-flow/70">circulates</span>.
In living systems, value doesn&apos;t accumulate. It <span className="text-flow/70">circulates</span> across dimensions.
</p>
</ScrollReveal>
</div>

View File

@ -8,25 +8,27 @@ import { regenerativeFlows } from '@/lib/sankey-data'
export default function RegenerativeSection() {
return (
<section className="min-h-screen flex flex-col items-center justify-center relative px-4 py-24">
<div className="absolute inset-0 bg-gradient-to-b from-deep via-ocean to-deep" />
{/* Green life glow */}
<div
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[700px] h-[500px] rounded-full"
style={{ background: 'radial-gradient(ellipse, rgba(45, 212, 191, 0.06) 0%, transparent 60%)' }}
/>
<div className="absolute inset-0 bg-deep" />
<div className="relative z-10 max-w-5xl w-full text-center">
<SectionHeader>The Regenerative Alternative</SectionHeader>
<ScrollReveal delay={0.1}>
<div className="flex justify-center gap-6 mb-6 text-xs font-light">
<span className="flex items-center gap-1.5"><span className="w-3 h-0.5 rounded bg-flow/60 inline-block" /> Economic</span>
<span className="flex items-center gap-1.5"><span className="w-3 h-0.5 rounded bg-[#a78bfa]/60 inline-block" /> Governance</span>
<span className="flex items-center gap-1.5"><span className="w-3 h-0.5 rounded bg-[#60a5fa]/60 inline-block" /> Data</span>
</div>
</ScrollReveal>
<ScrollReveal delay={0.2}>
<div className="my-14 rounded-2xl overflow-hidden p-4 bg-ocean/30 border border-flow/10">
<div className="my-8 rounded-2xl overflow-hidden p-4 bg-ocean/20 border border-flow/8">
<SankeyDiagram
data={regenerativeFlows}
showLabels={true}
speed="normal"
showParticles={true}
showDistortion={true}
showDistortion={false}
maxParticles={8}
/>
</div>

View File

@ -13,7 +13,7 @@ const lines = [
export default function TransitionSection() {
return (
<section className="min-h-screen flex flex-col items-center justify-center relative px-4 py-24">
<div className="absolute inset-0 bg-gradient-to-b from-deep via-current to-deep" />
<div className="absolute inset-0 bg-deep" />
{/* Flowing light streak */}
<div

View File

@ -155,10 +155,11 @@ export default function SankeyDiagram({
y={((node.y0 ?? 0) + (node.y1 ?? 0)) / 2}
textAnchor={(node.x0 ?? 0) < width / 2 ? 'start' : 'end'}
dominantBaseline="middle"
fill="#f5f5dc"
fontSize={12}
fontFamily="'Courier New', monospace"
opacity={0.7}
fill="#e0f2f1"
fontSize={11}
fontFamily="'Outfit', system-ui, sans-serif"
fontWeight={300}
opacity={0.65}
>
{node.label}
</text>

View File

@ -1,47 +1,53 @@
import { FlowData } from './types'
// S2: Natural flows — abstract organic Sankey (unlabeled, acyclic)
// Represents sunlight/rain → photosynthesis/absorption → distribution → endpoints
// Flow dimension colors:
// Economic = teal (#2dd4bf)
// Governance/Delegation = purple (#a78bfa)
// Data/Knowledge = blue (#60a5fa)
// Energy/Resources = amber (#fbbf24)
// S2: Multi-dimensional flows in a living system
// Same nodes participate in multiple flow types
// Note: d3-sankey requires acyclic graphs, so feedback loops are
// represented with separate source/sink nodes (e.g. soil-in → soil-out)
export const naturalFlows: FlowData = {
nodes: [
{ id: 'sun', color: '#fbbf24' },
{ id: 'rain', color: '#60a5fa' },
{ id: 'photo', color: '#34d399' },
{ id: 'soil', color: '#c3b091' },
{ id: 'canopy', color: '#34d399' },
{ id: 'roots', color: '#a78bfa' },
{ id: 'stream', color: '#60a5fa' },
{ id: 'mycelium', color: '#a78bfa' },
{ id: 'fauna', color: '#f59e0b' },
{ id: 'atmosphere', color: '#94a3b8' },
{ id: 'humus', color: '#92704f' },
{ id: 'sun', label: 'Energy', color: '#fbbf24' },
{ id: 'water', label: 'Water', color: '#38bdf8' },
{ id: 'soil', label: 'Substrate', color: '#c3b091' },
{ id: 'producers', label: 'Producers', color: '#34d399' },
{ id: 'consumers', label: 'Consumers', color: '#60a5fa' },
{ id: 'decomposers', label: 'Decomposers', color: '#a78bfa' },
{ id: 'signals', label: 'Signals', color: '#f472b6' },
{ id: 'nutrients', label: 'Nutrients', color: '#c3b091' },
{ id: 'commons', label: 'Commons', color: '#94a3b8' },
],
links: [
{ source: 'sun', target: 'photo', value: 8, color: '#fbbf2480' },
{ source: 'sun', target: 'canopy', value: 4, color: '#fbbf2440' },
{ source: 'rain', target: 'stream', value: 5, color: '#60a5fa80' },
{ source: 'rain', target: 'soil', value: 6, color: '#60a5fa60' },
{ source: 'photo', target: 'canopy', value: 5, color: '#34d39980' },
{ source: 'photo', target: 'roots', value: 3, color: '#34d39960' },
{ source: 'soil', target: 'roots', value: 4, color: '#c3b09180' },
{ source: 'soil', target: 'mycelium', value: 3, color: '#c3b09160' },
{ source: 'roots', target: 'mycelium', value: 3, color: '#a78bfa80' },
{ source: 'canopy', target: 'atmosphere', value: 4, color: '#34d39960' },
{ source: 'canopy', target: 'fauna', value: 3, color: '#34d39940' },
{ source: 'mycelium', target: 'humus', value: 4, color: '#a78bfa60' },
{ source: 'stream', target: 'atmosphere', value: 3, color: '#60a5fa60' },
{ source: 'fauna', target: 'humus', value: 2, color: '#f59e0b60' },
{ source: 'roots', target: 'humus', value: 2, color: '#a78bfa40' },
// Energy flows (amber)
{ source: 'sun', target: 'producers', value: 10, color: '#fbbf2460' },
{ source: 'producers', target: 'consumers', value: 6, color: '#fbbf2440' },
{ source: 'consumers', target: 'decomposers', value: 4, color: '#fbbf2430' },
// Resource flows (teal)
{ source: 'soil', target: 'producers', value: 6, color: '#2dd4bf50' },
{ source: 'water', target: 'producers', value: 5, color: '#38bdf850' },
{ source: 'decomposers', target: 'nutrients', value: 5, color: '#2dd4bf40' },
// Information/signal flows (pink)
{ source: 'producers', target: 'signals', value: 3, color: '#f472b640' },
{ source: 'signals', target: 'consumers', value: 3, color: '#f472b640' },
{ source: 'signals', target: 'decomposers', value: 2, color: '#f472b630' },
// Commons flows (grey-blue)
{ source: 'consumers', target: 'commons', value: 3, color: '#94a3b840' },
{ source: 'producers', target: 'commons', value: 4, color: '#94a3b830' },
],
}
// S3: Extractive pattern — finance funnel (labeled, acyclic)
// S3: Extractive pattern — all dimensions funnel through one bottleneck
export const extractiveFlows: FlowData = {
nodes: [
{ id: 'labor', label: 'Labor', color: '#e9456060' },
{ id: 'creativity', label: 'Creativity', color: '#e9456060' },
{ id: 'nature', label: 'Nature', color: '#e9456060' },
{ id: 'communities', label: 'Communities', color: '#e9456060' },
{ id: 'labor', label: 'Labor', color: '#e9456050' },
{ id: 'data', label: 'Data', color: '#e9456050' },
{ id: 'nature', label: 'Nature', color: '#e9456050' },
{ id: 'governance', label: 'Governance', color: '#e9456050' },
{ id: 'finance', label: 'Finance', color: '#e94560' },
{ id: 'shareholders', label: 'Shareholders', color: '#e94560cc' },
{ id: 'executives', label: 'Executives', color: '#e94560cc' },
@ -49,46 +55,53 @@ export const extractiveFlows: FlowData = {
{ id: 'public-good', label: 'Public Good', color: '#e9456030' },
],
links: [
{ source: 'labor', target: 'finance', value: 10, color: '#e9456040' },
{ source: 'creativity', target: 'finance', value: 8, color: '#e9456040' },
{ source: 'nature', target: 'finance', value: 12, color: '#e9456040' },
{ source: 'communities', target: 'finance', value: 6, color: '#e9456040' },
{ source: 'finance', target: 'shareholders', value: 15, color: '#e9456080' },
{ source: 'finance', target: 'executives', value: 12, color: '#e9456080' },
{ source: 'finance', target: 'tax-havens', value: 7, color: '#e9456060' },
{ source: 'finance', target: 'public-good', value: 2, color: '#e9456020' },
// Economic flows in (muted red)
{ source: 'labor', target: 'finance', value: 10, color: '#e9456035' },
{ source: 'nature', target: 'finance', value: 12, color: '#e9456035' },
// Data flows in (muted red-blue)
{ source: 'data', target: 'finance', value: 8, color: '#e9456030' },
// Delegation captured (muted red-purple)
{ source: 'governance', target: 'finance', value: 6, color: '#e9456028' },
// All flows out through narrow channels
{ source: 'finance', target: 'shareholders', value: 16, color: '#e9456070' },
{ source: 'finance', target: 'executives', value: 12, color: '#e9456060' },
{ source: 'finance', target: 'tax-havens', value: 6, color: '#e9456050' },
{ source: 'finance', target: 'public-good', value: 2, color: '#e9456018' },
],
}
// S4: Regenerative alternative — redistributive mesh (labeled, acyclic)
// Uses separate input/output nodes to represent the circular nature without actual cycles
// S4: Regenerative — multi-dimensional flows that interconnect
// Economic, governance, and knowledge flows between same nodes
export const regenerativeFlows: FlowData = {
nodes: [
{ id: 'commons-in', label: 'Commons', color: '#2dd4bf' },
{ id: 'labor-r', label: 'Labor', color: '#2dd4bfcc' },
{ id: 'commons', label: 'Commons', color: '#2dd4bf' },
{ id: 'labor-r', label: 'Workers', color: '#2dd4bfbb' },
{ id: 'ecology', label: 'Ecology', color: '#34d399' },
{ id: 'care', label: 'Care', color: '#a78bfa' },
{ id: 'creativity-r', label: 'Creativity', color: '#60a5fa' },
{ id: 'data-r', label: 'Data', color: '#60a5fa' },
{ id: 'community', label: 'Community', color: '#fbbf24' },
{ id: 'knowledge', label: 'Knowledge', color: '#f472b6' },
{ id: 'stewardship', label: 'Stewardship', color: '#34d399cc' },
{ id: 'commons-out', label: 'Commons', color: '#2dd4bf' },
{ id: 'stewardship', label: 'Stewardship', color: '#34d399bb' },
{ id: 'public', label: 'Public Good', color: '#2dd4bf' },
],
links: [
{ source: 'commons-in', target: 'labor-r', value: 5, color: '#2dd4bf60' },
{ source: 'commons-in', target: 'ecology', value: 6, color: '#2dd4bf60' },
{ source: 'commons-in', target: 'care', value: 5, color: '#2dd4bf60' },
{ source: 'commons-in', target: 'creativity-r', value: 4, color: '#2dd4bf60' },
{ source: 'labor-r', target: 'community', value: 4, color: '#2dd4bf40' },
{ source: 'labor-r', target: 'knowledge', value: 3, color: '#2dd4bf40' },
{ source: 'ecology', target: 'stewardship', value: 4, color: '#34d39960' },
{ source: 'ecology', target: 'community', value: 3, color: '#34d39940' },
{ source: 'care', target: 'community', value: 4, color: '#a78bfa60' },
{ source: 'care', target: 'knowledge', value: 3, color: '#a78bfa40' },
{ source: 'creativity-r', target: 'knowledge', value: 3, color: '#60a5fa60' },
{ source: 'creativity-r', target: 'community', value: 3, color: '#60a5fa40' },
{ source: 'community', target: 'commons-out', value: 6, color: '#fbbf2460' },
{ source: 'knowledge', target: 'commons-out', value: 4, color: '#f472b660' },
{ source: 'stewardship', target: 'commons-out', value: 4, color: '#34d39960' },
// Economic flows (teal)
{ source: 'commons', target: 'labor-r', value: 5, color: '#2dd4bf45' },
{ source: 'commons', target: 'ecology', value: 4, color: '#2dd4bf45' },
{ source: 'labor-r', target: 'community', value: 4, color: '#2dd4bf35' },
{ source: 'ecology', target: 'stewardship', value: 3, color: '#2dd4bf35' },
// Delegation/governance flows (purple)
{ source: 'commons', target: 'care', value: 4, color: '#a78bfa40' },
{ source: 'care', target: 'community', value: 3, color: '#a78bfa35' },
{ source: 'community', target: 'public', value: 5, color: '#a78bfa30' },
// Data/knowledge flows (blue)
{ source: 'commons', target: 'data-r', value: 4, color: '#60a5fa40' },
{ source: 'data-r', target: 'knowledge', value: 4, color: '#60a5fa35' },
{ source: 'knowledge', target: 'public', value: 4, color: '#60a5fa30' },
// Cross-dimension connections
{ source: 'care', target: 'knowledge', value: 2, color: '#a78bfa25' },
{ source: 'ecology', target: 'community', value: 2, color: '#34d39930' },
{ source: 'stewardship', target: 'public', value: 3, color: '#34d39930' },
{ source: 'labor-r', target: 'knowledge', value: 2, color: '#2dd4bf25' },
],
}