Restyle FlowFi with distinct flowing aesthetic
Replace NoFi's rigid style with organic, aquatic design: - Outfit font (sans-serif) replaces Permanent Marker + monospace - Deep ocean color palette replaces harsh void blacks - Flowing wave underlines replace scrawl-underlines - Rounded corners, soft glows, gradient ambient lighting - Remove wobble rotations, add wave/breathe/glow animations - Cool foam/mist text colors replace warm beige/brown Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
07bed35517
commit
35ada4127d
247
app/globals.css
247
app/globals.css
|
|
@ -1,35 +1,95 @@
|
|||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--color-void: #0a0a0a;
|
||||
--color-nothing: #1a1a2e;
|
||||
--color-less: #16213e;
|
||||
--color-anti-green: #e94560;
|
||||
--color-flow-green: #2dd4bf;
|
||||
--color-zen: #f5f5dc;
|
||||
--color-sloth: #c3b091;
|
||||
/* Deep ocean palette — distinct from NoFi's harsh void */
|
||||
--color-deep: #050d1a;
|
||||
--color-ocean: #0c1e3a;
|
||||
--color-current: #0f2847;
|
||||
--color-surface: #143561;
|
||||
|
||||
--font-family-mono: "Courier New", monospace;
|
||||
--font-family-marker: var(--font-marker), "Permanent Marker", cursive;
|
||||
/* Flow accents */
|
||||
--color-flow: #2dd4bf;
|
||||
--color-flow-light: #5eead4;
|
||||
--color-glow: #a7f3d0;
|
||||
--color-anti-green: #e94560;
|
||||
|
||||
/* Text colors — cooler than NoFi's beige */
|
||||
--color-foam: #e0f2f1;
|
||||
--color-mist: #94a3b8;
|
||||
|
||||
/* Typography — sans-serif, not monospace */
|
||||
--font-family-sans: var(--font-outfit), "Outfit", system-ui, sans-serif;
|
||||
--font-family-caveat: var(--font-caveat), "Caveat", cursive;
|
||||
}
|
||||
|
||||
/* Pipe flow animation — core effect */
|
||||
/* ============================================
|
||||
FLOWING ANIMATIONS — organic, not mechanical
|
||||
============================================ */
|
||||
|
||||
/* Gentle wave undulation */
|
||||
@keyframes wave {
|
||||
0%, 100% { transform: translateY(0) rotate(0deg); }
|
||||
25% { transform: translateY(-3px) rotate(0.3deg); }
|
||||
75% { transform: translateY(3px) rotate(-0.3deg); }
|
||||
}
|
||||
|
||||
/* Slow horizontal drift like a current */
|
||||
@keyframes current {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
50% { transform: translateX(12px); }
|
||||
}
|
||||
|
||||
/* Breathing scale — organic pulse */
|
||||
@keyframes breathe {
|
||||
0%, 100% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.015); opacity: 0.95; }
|
||||
}
|
||||
|
||||
/* Soft glow pulse */
|
||||
@keyframes glow-pulse {
|
||||
0%, 100% { opacity: 0.4; filter: blur(0px); }
|
||||
50% { opacity: 0.7; filter: blur(1px); }
|
||||
}
|
||||
|
||||
/* Flowing gradient background */
|
||||
@keyframes flow-gradient {
|
||||
0% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
|
||||
/* Ripple expand */
|
||||
@keyframes ripple {
|
||||
0% { transform: scale(0.95); opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
/* Rising bubbles */
|
||||
@keyframes rise {
|
||||
0% { transform: translateY(0); opacity: 0.6; }
|
||||
100% { transform: translateY(-60px); opacity: 0; }
|
||||
}
|
||||
|
||||
.animate-wave { animation: wave 6s ease-in-out infinite; }
|
||||
.animate-current { animation: current 10s ease-in-out infinite; }
|
||||
.animate-breathe { animation: breathe 6s ease-in-out infinite; }
|
||||
.animate-glow-pulse { animation: glow-pulse 4s ease-in-out infinite; }
|
||||
.animate-ripple { animation: ripple 0.8s ease-out forwards; }
|
||||
.animate-flow-gradient {
|
||||
background-size: 200% 200%;
|
||||
animation: flow-gradient 8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
PIPE FLOW — core Sankey animation
|
||||
============================================ */
|
||||
|
||||
@keyframes pipe-flow {
|
||||
0% { stroke-dashoffset: 24; }
|
||||
100% { stroke-dashoffset: 0; }
|
||||
}
|
||||
|
||||
@keyframes pipe-flow-fast {
|
||||
0% { stroke-dashoffset: 24; }
|
||||
100% { stroke-dashoffset: 0; }
|
||||
}
|
||||
|
||||
@keyframes pipe-flow-reverse {
|
||||
0% { stroke-dashoffset: 0; }
|
||||
100% { stroke-dashoffset: 24; }
|
||||
}
|
||||
|
||||
.animate-pipe-flow {
|
||||
stroke-dasharray: 8 4;
|
||||
animation: pipe-flow 1.5s linear infinite;
|
||||
|
|
@ -59,89 +119,69 @@
|
|||
animation-delay: var(--delay, 0s);
|
||||
}
|
||||
|
||||
/* Water distortion */
|
||||
@keyframes turbulence {
|
||||
0% { }
|
||||
50% { }
|
||||
100% { }
|
||||
}
|
||||
/* ============================================
|
||||
FLOWING UNDERLINE — SVG wave, not scrawl
|
||||
============================================ */
|
||||
|
||||
/* Scroll reveal */
|
||||
@keyframes reveal-up {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* Breathing / pulsing */
|
||||
@keyframes breathe {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.02); }
|
||||
}
|
||||
|
||||
@keyframes slow-pulse {
|
||||
0%, 100% { opacity: 0.3; }
|
||||
50% { opacity: 0.6; }
|
||||
}
|
||||
|
||||
@keyframes drift {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
50% { transform: translateX(20px); }
|
||||
}
|
||||
|
||||
@keyframes flow-gradient {
|
||||
0% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
|
||||
@keyframes float-up {
|
||||
0% { opacity: 0.6; transform: translateY(0) rotate(0deg); }
|
||||
100% { opacity: 0; transform: translateY(-200px) rotate(10deg); }
|
||||
}
|
||||
|
||||
.animate-breathe { animation: breathe 6s ease-in-out infinite; }
|
||||
.animate-slow-pulse { animation: slow-pulse 8s ease-in-out infinite; }
|
||||
.animate-drift { animation: drift 12s ease-in-out infinite; }
|
||||
.animate-flow-gradient {
|
||||
background-size: 200% 200%;
|
||||
animation: flow-gradient 8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Wobble rotations */
|
||||
.wobble-1 { transform: rotate(-1.5deg); }
|
||||
.wobble-2 { transform: rotate(0.8deg); }
|
||||
.wobble-3 { transform: rotate(-0.5deg); }
|
||||
.wobble-4 { transform: rotate(1.2deg); }
|
||||
|
||||
/* Scrawl underlines */
|
||||
.scrawl-underline {
|
||||
.flow-underline {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.scrawl-underline::after {
|
||||
.flow-underline::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -4%;
|
||||
bottom: -4px;
|
||||
width: 108%;
|
||||
height: 4px;
|
||||
background: var(--color-flow-green);
|
||||
transform: rotate(-0.5deg) scaleX(0.95);
|
||||
border-radius: 2px;
|
||||
left: -2%;
|
||||
bottom: -6px;
|
||||
width: 104%;
|
||||
height: 6px;
|
||||
background: linear-gradient(90deg, transparent, var(--color-flow), transparent);
|
||||
border-radius: 3px;
|
||||
opacity: 0.6;
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 8'%3E%3Cpath d='M0,4 Q15,0 30,4 Q45,8 60,4 Q75,0 90,4 Q105,8 120,4' fill='none' stroke='white' stroke-width='6'/%3E%3C/svg%3E");
|
||||
mask-size: 100% 100%;
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 8'%3E%3Cpath d='M0,4 Q15,0 30,4 Q45,8 60,4 Q75,0 90,4 Q105,8 120,4' fill='none' stroke='white' stroke-width='6'/%3E%3C/svg%3E");
|
||||
-webkit-mask-size: 100% 100%;
|
||||
}
|
||||
|
||||
/* Sandbox node styles */
|
||||
/* ============================================
|
||||
SECTION DIVIDERS — flowing wave transitions
|
||||
============================================ */
|
||||
|
||||
.wave-divider {
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.wave-divider svg {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: calc(100% + 1.3px);
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
COMPONENT STYLES
|
||||
============================================ */
|
||||
|
||||
/* Sandbox nodes — rounded, glowing */
|
||||
.flow-node {
|
||||
border: 2px solid rgba(245, 245, 220, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
background: rgba(26, 26, 46, 0.9);
|
||||
backdrop-filter: blur(4px);
|
||||
border: 1.5px solid rgba(45, 212, 191, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: 14px;
|
||||
background: rgba(12, 30, 58, 0.85);
|
||||
backdrop-filter: blur(8px);
|
||||
min-width: 120px;
|
||||
transition: all 0.4s ease;
|
||||
box-shadow: 0 0 20px rgba(45, 212, 191, 0.05);
|
||||
}
|
||||
|
||||
.flow-node:hover {
|
||||
border-color: var(--color-flow-green);
|
||||
border-color: rgba(45, 212, 191, 0.5);
|
||||
box-shadow: 0 0 30px rgba(45, 212, 191, 0.15);
|
||||
}
|
||||
|
||||
/* Disable feTurbulence on mobile for performance */
|
||||
|
|
@ -153,20 +193,33 @@
|
|||
|
||||
/* React Flow overrides */
|
||||
.react-flow__background {
|
||||
background-color: var(--color-void) !important;
|
||||
background-color: var(--color-deep) !important;
|
||||
}
|
||||
|
||||
.react-flow__controls button {
|
||||
background-color: var(--color-nothing) !important;
|
||||
border-color: rgba(245, 245, 220, 0.1) !important;
|
||||
color: var(--color-zen) !important;
|
||||
fill: var(--color-zen) !important;
|
||||
background-color: var(--color-ocean) !important;
|
||||
border-color: rgba(45, 212, 191, 0.15) !important;
|
||||
color: var(--color-foam) !important;
|
||||
fill: var(--color-foam) !important;
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
|
||||
.react-flow__controls button:hover {
|
||||
background-color: var(--color-less) !important;
|
||||
background-color: var(--color-current) !important;
|
||||
}
|
||||
|
||||
.react-flow__minimap {
|
||||
background-color: var(--color-void) !important;
|
||||
background-color: var(--color-deep) !important;
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
|
||||
/* Smooth scrolling */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Selection color */
|
||||
::selection {
|
||||
background: rgba(45, 212, 191, 0.3);
|
||||
color: var(--color-foam);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import type { Metadata } from 'next'
|
||||
import { Permanent_Marker, Caveat } from 'next/font/google'
|
||||
import { Outfit, Caveat } from 'next/font/google'
|
||||
import './globals.css'
|
||||
|
||||
const marker = Permanent_Marker({
|
||||
weight: '400',
|
||||
const outfit = Outfit({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-marker',
|
||||
variable: '--font-outfit',
|
||||
})
|
||||
|
||||
const caveat = Caveat({
|
||||
|
|
@ -28,7 +27,7 @@ export const metadata: Metadata = {
|
|||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={`bg-void text-zen font-mono antialiased ${marker.variable} ${caveat.variable}`}>
|
||||
<body className={`bg-deep text-foam font-sans antialiased ${outfit.variable} ${caveat.variable}`}>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client'
|
||||
|
||||
import { useCallback, useState, useMemo } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import {
|
||||
ReactFlow,
|
||||
Controls,
|
||||
|
|
@ -82,14 +82,14 @@ export default function FlowCanvas() {
|
|||
)
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
setNodes(presets[2].nodes) // Blank preset
|
||||
setNodes(presets[2].nodes)
|
||||
setEdges(presets[2].edges)
|
||||
}, [setNodes, setEdges])
|
||||
|
||||
const showMiniMap = typeof window !== 'undefined' && window.innerWidth >= 768
|
||||
|
||||
return (
|
||||
<div className="w-full h-[500px] md:h-[600px] rounded-lg border border-zen/10 overflow-hidden relative bg-void">
|
||||
<div className="w-full h-[500px] md:h-[600px] rounded-2xl border border-flow/10 overflow-hidden relative bg-deep">
|
||||
<FlowToolbar
|
||||
onAddNode={handleAddNode}
|
||||
onLoadPreset={handleLoadPreset}
|
||||
|
|
@ -107,15 +107,14 @@ export default function FlowCanvas() {
|
|||
edgeTypes={edgeTypes}
|
||||
defaultEdgeOptions={{ type: 'animatedPipe' }}
|
||||
fitView
|
||||
className={!isPlaying ? '[&_.animate-pipe-flow]:!animation-play-state-paused' : ''}
|
||||
>
|
||||
<Background variant={BackgroundVariant.Dots} gap={20} size={1} color="#f5f5dc10" />
|
||||
<Background variant={BackgroundVariant.Dots} gap={24} size={1} color="#2dd4bf08" />
|
||||
<Controls position="bottom-right" />
|
||||
{showMiniMap && (
|
||||
<MiniMap
|
||||
position="bottom-left"
|
||||
nodeColor="#2dd4bf40"
|
||||
maskColor="#0a0a0acc"
|
||||
nodeColor="#2dd4bf30"
|
||||
maskColor="#050d1acc"
|
||||
/>
|
||||
)}
|
||||
</ReactFlow>
|
||||
|
|
|
|||
|
|
@ -20,24 +20,24 @@ export default function FlowToolbar({
|
|||
return (
|
||||
<div className="absolute top-4 left-4 z-10 flex flex-wrap gap-2">
|
||||
{/* Add nodes */}
|
||||
<div className="flex gap-1 bg-nothing/80 backdrop-blur-sm rounded-lg p-1 border border-zen/10">
|
||||
<div className="flex gap-1 bg-ocean/80 backdrop-blur-md rounded-xl p-1 border border-flow/10">
|
||||
<button
|
||||
onClick={() => onAddNode('source')}
|
||||
className="px-3 py-1.5 text-xs text-flow-green/70 hover:bg-flow-green/10 rounded transition-colors"
|
||||
className="px-3 py-1.5 text-xs text-flow/70 hover:bg-flow/10 rounded-lg transition-all duration-300 font-light"
|
||||
title="Add Source"
|
||||
>
|
||||
+ Source
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onAddNode('pipe')}
|
||||
className="px-3 py-1.5 text-xs text-zen/50 hover:bg-zen/10 rounded transition-colors"
|
||||
className="px-3 py-1.5 text-xs text-foam/40 hover:bg-foam/5 rounded-lg transition-all duration-300 font-light"
|
||||
title="Add Pipe"
|
||||
>
|
||||
+ Pipe
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onAddNode('sink')}
|
||||
className="px-3 py-1.5 text-xs text-zen/40 hover:bg-zen/10 rounded transition-colors"
|
||||
className="px-3 py-1.5 text-xs text-foam/35 hover:bg-foam/5 rounded-lg transition-all duration-300 font-light"
|
||||
title="Add Sink"
|
||||
>
|
||||
+ Sink
|
||||
|
|
@ -45,12 +45,12 @@ export default function FlowToolbar({
|
|||
</div>
|
||||
|
||||
{/* Presets */}
|
||||
<div className="flex gap-1 bg-nothing/80 backdrop-blur-sm rounded-lg p-1 border border-zen/10">
|
||||
<div className="flex gap-1 bg-ocean/80 backdrop-blur-md rounded-xl p-1 border border-flow/10">
|
||||
{presets.map((preset, i) => (
|
||||
<button
|
||||
key={preset.name}
|
||||
onClick={() => onLoadPreset(i)}
|
||||
className="px-3 py-1.5 text-xs text-zen/50 hover:bg-zen/10 rounded transition-colors"
|
||||
className="px-3 py-1.5 text-xs text-foam/40 hover:bg-foam/5 rounded-lg transition-all duration-300 font-light"
|
||||
>
|
||||
{preset.name}
|
||||
</button>
|
||||
|
|
@ -58,16 +58,16 @@ export default function FlowToolbar({
|
|||
</div>
|
||||
|
||||
{/* Controls */}
|
||||
<div className="flex gap-1 bg-nothing/80 backdrop-blur-sm rounded-lg p-1 border border-zen/10">
|
||||
<div className="flex gap-1 bg-ocean/80 backdrop-blur-md rounded-xl p-1 border border-flow/10">
|
||||
<button
|
||||
onClick={onTogglePlay}
|
||||
className="px-3 py-1.5 text-xs text-flow-green/70 hover:bg-flow-green/10 rounded transition-colors"
|
||||
className="px-3 py-1.5 text-xs text-flow/70 hover:bg-flow/10 rounded-lg transition-all duration-300"
|
||||
>
|
||||
{isPlaying ? '⏸' : '▶'}
|
||||
</button>
|
||||
<button
|
||||
onClick={onReset}
|
||||
className="px-3 py-1.5 text-xs text-anti-green/50 hover:bg-anti-green/10 rounded transition-colors"
|
||||
className="px-3 py-1.5 text-xs text-anti-green/40 hover:bg-anti-green/10 rounded-lg transition-all duration-300 font-light"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import { Handle, Position, type NodeProps } from '@xyflow/react'
|
|||
|
||||
export default function PipeNode({ data }: NodeProps) {
|
||||
return (
|
||||
<div className="flow-node border-zen/20">
|
||||
<Handle type="target" position={Position.Left} className="!bg-zen/40 !w-3 !h-3" />
|
||||
<Handle type="source" position={Position.Right} className="!bg-zen/40 !w-3 !h-3" />
|
||||
<div className="flow-node">
|
||||
<Handle type="target" position={Position.Left} className="!bg-foam/30 !w-3 !h-3 !rounded-full !border-0" />
|
||||
<Handle type="source" position={Position.Right} className="!bg-foam/30 !w-3 !h-3 !rounded-full !border-0" />
|
||||
<div className="text-center">
|
||||
<div className="text-zen/50 text-lg mb-1">⟷</div>
|
||||
<div className="text-zen/60 text-xs font-mono">{(data as any).label || 'Pipe'}</div>
|
||||
<div className="text-foam/40 text-lg mb-1">⟷</div>
|
||||
<div className="text-foam/50 text-xs font-light">{(data as any).label || 'Pipe'}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,15 +6,15 @@ export default function SinkNode({ data }: NodeProps) {
|
|||
const fillLevel = (data as any).fillLevel ?? 0
|
||||
|
||||
return (
|
||||
<div className="flow-node border-flow-green/30">
|
||||
<Handle type="target" position={Position.Left} className="!bg-flow-green/60 !w-3 !h-3" />
|
||||
<div className="flow-node">
|
||||
<Handle type="target" position={Position.Left} className="!bg-flow/50 !w-3 !h-3 !rounded-full !border-0" />
|
||||
<div className="text-center">
|
||||
<div className="text-zen/40 text-lg mb-1">◎</div>
|
||||
<div className="text-zen/60 text-xs font-mono">{(data as any).label || 'Sink'}</div>
|
||||
<div className="text-foam/35 text-lg mb-1">◎</div>
|
||||
<div className="text-foam/50 text-xs font-light">{(data as any).label || 'Sink'}</div>
|
||||
{/* Fill level indicator */}
|
||||
<div className="w-full h-2 bg-void/50 rounded mt-2 overflow-hidden">
|
||||
<div className="w-full h-1.5 bg-deep/60 rounded-full mt-2 overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-flow-green/60 rounded transition-all duration-500"
|
||||
className="h-full bg-flow/50 rounded-full transition-all duration-500"
|
||||
style={{ width: `${Math.min(100, fillLevel)}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import { Handle, Position, type NodeProps } from '@xyflow/react'
|
|||
|
||||
export default function SourceNode({ data }: NodeProps) {
|
||||
return (
|
||||
<div className="flow-node border-flow-green/40">
|
||||
<Handle type="source" position={Position.Right} className="!bg-flow-green !w-3 !h-3" />
|
||||
<div className="flow-node">
|
||||
<Handle type="source" position={Position.Right} className="!bg-flow !w-3 !h-3 !rounded-full !border-0" />
|
||||
<div className="text-center">
|
||||
<div className="text-flow-green text-lg mb-1">⊕</div>
|
||||
<div className="text-zen/70 text-xs font-mono">{(data as any).label || 'Source'}</div>
|
||||
<div className="text-flow/70 text-lg mb-1">◉</div>
|
||||
<div className="text-foam/60 text-xs font-light">{(data as any).label || 'Source'}</div>
|
||||
{(data as any).flowRate !== undefined && (
|
||||
<div className="text-flow-green/50 text-[10px] mt-1">
|
||||
<div className="text-flow/40 text-[10px] mt-1 font-light">
|
||||
{(data as any).flowRate} units/s
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -4,46 +4,54 @@ import ScrollReveal from '@/components/ui/ScrollReveal'
|
|||
|
||||
export default function CTASection() {
|
||||
return (
|
||||
<section className="min-h-[60vh] flex flex-col items-center justify-center relative px-4 py-20">
|
||||
<div className="absolute inset-0 bg-void" />
|
||||
<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" />
|
||||
|
||||
{/* Soft glow behind CTA */}
|
||||
<div
|
||||
className="absolute top-1/3 left-1/2 -translate-x-1/2 w-[400px] h-[400px] rounded-full"
|
||||
style={{ background: 'radial-gradient(circle, rgba(45, 212, 191, 0.06) 0%, transparent 70%)' }}
|
||||
/>
|
||||
|
||||
<div className="relative z-10 text-center max-w-3xl">
|
||||
<ScrollReveal>
|
||||
<h2 className="font-marker text-4xl md:text-6xl text-zen/80 mb-4 wobble-4">
|
||||
Ready to <span className="text-flow-green">flow</span>?
|
||||
<h2 className="font-sans font-extralight text-4xl md:text-6xl text-foam/80 mb-5 tracking-tight">
|
||||
Ready to <span className="text-flow">flow</span>?
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
|
||||
<ScrollReveal delay={0.2}>
|
||||
<p className="font-caveat text-2xl text-zen/40 mb-12">
|
||||
<p className="font-caveat text-2xl text-foam/35 mb-14">
|
||||
You already are.
|
||||
</p>
|
||||
</ScrollReveal>
|
||||
|
||||
<ScrollReveal delay={0.4}>
|
||||
<p className="text-zen/20 text-sm mb-8">
|
||||
<ScrollReveal delay={0.3}>
|
||||
<p className="text-mist/25 text-sm mb-10 tracking-wide font-light">
|
||||
NoFi → FlowFi → ???
|
||||
</p>
|
||||
</ScrollReveal>
|
||||
|
||||
<ScrollReveal delay={0.5}>
|
||||
<ScrollReveal delay={0.4}>
|
||||
<a
|
||||
href="https://nofi.lol"
|
||||
className="inline-block border border-zen/10 px-8 py-4 text-zen/30 hover:text-flow-green/60 hover:border-flow-green/30 transition-all duration-1000 text-sm"
|
||||
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"
|
||||
>
|
||||
← back to NoFi
|
||||
</a>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="relative z-10 w-full border-t border-zen/5 mt-20 pt-6 flex justify-between items-center max-w-4xl">
|
||||
<span className="text-zen/15 text-xs">
|
||||
{/* Footer — clean, minimal */}
|
||||
<footer className="relative z-10 w-full mt-24 pt-6 flex justify-between items-center max-w-4xl"
|
||||
style={{ borderTop: '1px solid rgba(45, 212, 191, 0.08)' }}
|
||||
>
|
||||
<span className="text-mist/20 text-xs font-light">
|
||||
Part of the CoFi cinematic universe
|
||||
</span>
|
||||
<a
|
||||
href="https://nofi.lol"
|
||||
className="text-zen/15 text-xs hover:text-flow-green/40 transition-all duration-1000"
|
||||
className="text-mist/20 text-xs font-light hover:text-flow/40 transition-all duration-700"
|
||||
>
|
||||
nofi.lol
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -7,16 +7,17 @@ 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-20">
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-void via-less to-void opacity-80" />
|
||||
{/* Red tint overlay */}
|
||||
<div className="absolute inset-0 bg-anti-green/5" />
|
||||
<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="relative z-10 max-w-5xl w-full text-center">
|
||||
<SectionHeader wobble={3}>The Extractive Pattern</SectionHeader>
|
||||
<SectionHeader className="text-anti-green/80">The Extractive Pattern</SectionHeader>
|
||||
|
||||
<ScrollReveal delay={0.2}>
|
||||
<div className="my-12">
|
||||
<div className="my-14 rounded-2xl overflow-hidden p-4 bg-current/30 border border-anti-green/10">
|
||||
<SankeyDiagram
|
||||
data={extractiveFlows}
|
||||
showLabels={true}
|
||||
|
|
@ -29,8 +30,8 @@ export default function ExtractivePipeSection() {
|
|||
</ScrollReveal>
|
||||
|
||||
<ScrollReveal delay={0.4}>
|
||||
<blockquote className="font-caveat text-xl md:text-2xl text-zen/50 max-w-2xl mx-auto border-l-2 border-anti-green/30 pl-6 wobble-1">
|
||||
The plumbing hired a marketing team and <span className="text-anti-green/70">went public</span>.
|
||||
<blockquote className="font-caveat text-xl md:text-2xl text-foam/40 max-w-2xl mx-auto border-l-2 border-anti-green/25 pl-6 leading-relaxed">
|
||||
The plumbing hired a marketing team and <span className="text-anti-green/60">went public</span>.
|
||||
</blockquote>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,77 +3,91 @@
|
|||
export default function HeroSection() {
|
||||
return (
|
||||
<section className="min-h-screen flex flex-col items-center justify-center relative px-4 overflow-hidden">
|
||||
{/* Background with water ripple SVG filter */}
|
||||
<svg className="absolute inset-0 w-full h-full" style={{ zIndex: 0 }}>
|
||||
{/* Animated gradient ocean background */}
|
||||
<div
|
||||
className="absolute inset-0 animate-flow-gradient"
|
||||
style={{
|
||||
backgroundSize: '400% 400%',
|
||||
backgroundImage: 'radial-gradient(ellipse at 20% 50%, #0f2847 0%, #0c1e3a 30%, #050d1a 70%), radial-gradient(ellipse at 80% 20%, #143561 0%, transparent 50%)',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Subtle water ripple SVG */}
|
||||
<svg className="absolute inset-0 w-full h-full opacity-30" style={{ zIndex: 1 }}>
|
||||
<defs>
|
||||
<filter id="hero-ripple">
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency="0.01 0.02"
|
||||
numOctaves={3}
|
||||
baseFrequency="0.008 0.015"
|
||||
numOctaves={4}
|
||||
seed={5}
|
||||
result="turbulence"
|
||||
>
|
||||
<animate
|
||||
attributeName="seed"
|
||||
from="0"
|
||||
to="200"
|
||||
dur="20s"
|
||||
to="100"
|
||||
dur="30s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</feTurbulence>
|
||||
<feDisplacementMap
|
||||
in="SourceGraphic"
|
||||
in2="turbulence"
|
||||
scale={8}
|
||||
scale={12}
|
||||
xChannelSelector="R"
|
||||
yChannelSelector="G"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient id="hero-water" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#2dd4bf" stopOpacity="0.08" />
|
||||
<stop offset="50%" stopColor="#5eead4" stopOpacity="0.04" />
|
||||
<stop offset="100%" stopColor="#2dd4bf" stopOpacity="0.06" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect
|
||||
width="100%"
|
||||
height="100%"
|
||||
fill="url(#hero-gradient)"
|
||||
fill="url(#hero-water)"
|
||||
className="water-distortion"
|
||||
filter="url(#hero-ripple)"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient id="hero-gradient" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stopColor="#0a0a0a" />
|
||||
<stop offset="40%" stopColor="#1a1a2e" />
|
||||
<stop offset="70%" stopColor="#16213e" />
|
||||
<stop offset="100%" stopColor="#0a0a0a" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-void via-nothing/50 to-void opacity-90" />
|
||||
{/* Soft radial glow */}
|
||||
<div
|
||||
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] rounded-full animate-breathe"
|
||||
style={{
|
||||
background: 'radial-gradient(circle, rgba(45, 212, 191, 0.08) 0%, transparent 70%)',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="relative z-10 text-center max-w-4xl">
|
||||
<p className="text-sloth/50 text-sm tracking-[0.3em] uppercase mb-6 animate-slow-pulse">
|
||||
<p className="text-mist/60 text-sm tracking-[0.4em] uppercase mb-8 animate-glow-pulse font-light">
|
||||
everything flows
|
||||
</p>
|
||||
|
||||
<h1 className="font-marker text-6xl md:text-8xl lg:text-9xl mb-6 wobble-1">
|
||||
<span className="text-flow-green">Flow</span>
|
||||
<span className="text-zen/90">Fi</span>
|
||||
<span className="text-zen/20">.</span>
|
||||
<h1 className="font-sans font-extralight text-7xl md:text-9xl lg:text-[10rem] mb-8 tracking-tighter leading-none">
|
||||
<span className="text-flow bg-gradient-to-r from-flow-light via-flow to-glow bg-clip-text text-transparent animate-flow-gradient" style={{ backgroundSize: '200% 200%' }}>
|
||||
Flow
|
||||
</span>
|
||||
<span className="text-foam/80">Fi</span>
|
||||
<span className="text-flow/20">.</span>
|
||||
</h1>
|
||||
|
||||
<p className="font-caveat text-2xl md:text-3xl text-zen/50 mb-12">
|
||||
Everything flows. The question is: <span className="text-flow-green/80">where?</span>
|
||||
<p className="font-caveat text-2xl md:text-3xl text-foam/45 mb-14">
|
||||
Everything flows. The question is: <span className="text-flow/70">where?</span>
|
||||
</p>
|
||||
|
||||
<a
|
||||
href="https://nofi.lol"
|
||||
className="inline-block border border-zen/10 px-6 py-3 text-sm text-zen/30 hover:text-flow-green/60 hover:border-flow-green/30 transition-all duration-1000"
|
||||
className="inline-block border border-flow/15 px-8 py-3 text-sm text-foam/30 hover:text-flow/60 hover:border-flow/40 hover:bg-flow/5 transition-all duration-700 rounded-full"
|
||||
>
|
||||
← back to NoFi
|
||||
</a>
|
||||
|
||||
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 animate-drift">
|
||||
<span className="text-zen/15 text-sm">scroll to flow ↓</span>
|
||||
<div className="absolute bottom-10 left-1/2 -translate-x-1/2 animate-current">
|
||||
<span className="text-foam/15 text-sm font-light tracking-wide">scroll to flow ↓</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -7,14 +7,20 @@ 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-20">
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-void via-nothing to-void opacity-80" />
|
||||
<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="relative z-10 max-w-5xl w-full text-center">
|
||||
<SectionHeader wobble={2}>Natural Flows</SectionHeader>
|
||||
<SectionHeader>Natural Flows</SectionHeader>
|
||||
|
||||
<ScrollReveal delay={0.2}>
|
||||
<div className="my-12">
|
||||
<div className="my-14 rounded-2xl overflow-hidden p-4 bg-ocean/30">
|
||||
<SankeyDiagram
|
||||
data={naturalFlows}
|
||||
showLabels={false}
|
||||
|
|
@ -27,8 +33,8 @@ export default function NatureFlowsSection() {
|
|||
</ScrollReveal>
|
||||
|
||||
<ScrollReveal delay={0.4}>
|
||||
<p className="font-caveat text-xl md:text-2xl text-zen/50 max-w-2xl mx-auto">
|
||||
In living systems, value doesn't accumulate. It <span className="text-flow-green/70">circulates</span>.
|
||||
<p className="font-caveat text-xl md:text-2xl text-foam/45 max-w-2xl mx-auto leading-relaxed">
|
||||
In living systems, value doesn't accumulate. It <span className="text-flow/70">circulates</span>.
|
||||
</p>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,16 +7,20 @@ 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-20">
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-void via-nothing to-void opacity-80" />
|
||||
{/* Green tint overlay */}
|
||||
<div className="absolute inset-0 bg-flow-green/3" />
|
||||
<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="relative z-10 max-w-5xl w-full text-center">
|
||||
<SectionHeader wobble={1}>The Regenerative Alternative</SectionHeader>
|
||||
<SectionHeader>The Regenerative Alternative</SectionHeader>
|
||||
|
||||
<ScrollReveal delay={0.2}>
|
||||
<div className="my-12">
|
||||
<div className="my-14 rounded-2xl overflow-hidden p-4 bg-ocean/30 border border-flow/10">
|
||||
<SankeyDiagram
|
||||
data={regenerativeFlows}
|
||||
showLabels={true}
|
||||
|
|
@ -29,8 +33,8 @@ export default function RegenerativeSection() {
|
|||
</ScrollReveal>
|
||||
|
||||
<ScrollReveal delay={0.4}>
|
||||
<p className="font-caveat text-xl md:text-2xl text-zen/50 max-w-2xl mx-auto">
|
||||
<span className="text-flow-green/70">Pre-distributive</span>. <span className="text-flow-green/70">Re-distributive</span>. Naturally.
|
||||
<p className="font-caveat text-xl md:text-2xl text-foam/45 max-w-2xl mx-auto leading-relaxed">
|
||||
<span className="text-flow/70">Pre-distributive</span>. <span className="text-flow/70">Re-distributive</span>. Naturally.
|
||||
</p>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,22 +7,22 @@ import ScrollReveal from '@/components/ui/ScrollReveal'
|
|||
const FlowCanvas = dynamic(() => import('@/components/sandbox/FlowCanvas'), {
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<div className="w-full h-[500px] md:h-[600px] rounded-lg border border-zen/10 bg-void flex items-center justify-center">
|
||||
<span className="text-zen/20 text-sm animate-slow-pulse">loading canvas...</span>
|
||||
<div className="w-full h-[500px] md:h-[600px] rounded-2xl border border-flow/10 bg-deep flex items-center justify-center">
|
||||
<span className="text-foam/20 text-sm animate-glow-pulse font-light">loading canvas...</span>
|
||||
</div>
|
||||
),
|
||||
})
|
||||
|
||||
export default function SandboxSection() {
|
||||
return (
|
||||
<section className="min-h-screen flex flex-col items-center justify-center relative px-4 py-20">
|
||||
<div className="absolute inset-0 bg-void" />
|
||||
<section className="min-h-screen flex flex-col items-center justify-center relative px-4 py-24">
|
||||
<div className="absolute inset-0 bg-deep" />
|
||||
|
||||
<div className="relative z-10 max-w-6xl w-full">
|
||||
<SectionHeader wobble={2}>Flow Sandbox</SectionHeader>
|
||||
<SectionHeader>Flow Sandbox</SectionHeader>
|
||||
|
||||
<ScrollReveal delay={0.1}>
|
||||
<p className="font-caveat text-lg md:text-xl text-zen/40 text-center mb-8 max-w-2xl mx-auto">
|
||||
<p className="font-caveat text-lg md:text-xl text-foam/35 text-center mb-10 max-w-2xl mx-auto">
|
||||
Reconnect the flows. What would you build?
|
||||
</p>
|
||||
</ScrollReveal>
|
||||
|
|
@ -32,8 +32,8 @@ export default function SandboxSection() {
|
|||
</ScrollReveal>
|
||||
|
||||
<ScrollReveal delay={0.3}>
|
||||
<p className="text-zen/20 text-xs text-center mt-4">
|
||||
drag nodes • connect ports • load presets • build your own flow system
|
||||
<p className="text-mist/30 text-xs text-center mt-6 font-light tracking-wide">
|
||||
drag nodes · connect ports · load presets · build your own flow system
|
||||
</p>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,27 +3,40 @@
|
|||
import ScrollReveal from '@/components/ui/ScrollReveal'
|
||||
|
||||
const lines = [
|
||||
'NoFi was the diagnosis.',
|
||||
'FlowFi is the prognosis.',
|
||||
'Not no flows — better flows.',
|
||||
'Not no finance — living finance.',
|
||||
'Finance as metabolism, not mechanism.',
|
||||
{ text: 'NoFi was the diagnosis.', accent: false },
|
||||
{ text: 'FlowFi is the prognosis.', accent: true },
|
||||
{ text: 'Not no flows — better flows.', accent: false },
|
||||
{ text: 'Not no finance — living finance.', accent: false },
|
||||
{ text: 'Finance as metabolism, not mechanism.', accent: true },
|
||||
]
|
||||
|
||||
export default function TransitionSection() {
|
||||
return (
|
||||
<section className="min-h-screen flex flex-col items-center justify-center relative px-4 py-20">
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-void via-less to-void opacity-80" />
|
||||
<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="relative z-10 max-w-3xl w-full space-y-12 text-center">
|
||||
{/* Flowing light streak */}
|
||||
<div
|
||||
className="absolute top-0 left-0 w-full h-px"
|
||||
style={{ background: 'linear-gradient(90deg, transparent, rgba(45, 212, 191, 0.2), transparent)' }}
|
||||
/>
|
||||
|
||||
<div className="relative z-10 max-w-3xl w-full space-y-14 text-center">
|
||||
{lines.map((line, i) => (
|
||||
<ScrollReveal key={i} delay={i * 0.15}>
|
||||
<p className={`font-caveat text-2xl md:text-4xl text-zen/60 wobble-${(i % 4) + 1}`}>
|
||||
{line}
|
||||
<ScrollReveal key={i} delay={i * 0.12}>
|
||||
<p className={`font-caveat text-2xl md:text-4xl leading-relaxed transition-colors duration-700 ${
|
||||
line.accent ? 'text-flow/60' : 'text-foam/50'
|
||||
}`}>
|
||||
{line.text}
|
||||
</p>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="absolute bottom-0 left-0 w-full h-px"
|
||||
style={{ background: 'linear-gradient(90deg, transparent, rgba(45, 212, 191, 0.2), transparent)' }}
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,16 @@ import ScrollReveal from './ScrollReveal'
|
|||
|
||||
interface SectionHeaderProps {
|
||||
children: React.ReactNode
|
||||
wobble?: 1 | 2 | 3 | 4
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function SectionHeader({ children, wobble = 1, className = '' }: SectionHeaderProps) {
|
||||
export default function SectionHeader({ children, className = '' }: SectionHeaderProps) {
|
||||
return (
|
||||
<ScrollReveal>
|
||||
<h2
|
||||
className={`font-marker text-3xl md:text-5xl text-zen/90 mb-8 wobble-${wobble} ${className}`}
|
||||
className={`font-sans font-light text-3xl md:text-5xl text-foam/90 mb-8 tracking-tight ${className}`}
|
||||
>
|
||||
<span className="scrawl-underline">{children}</span>
|
||||
<span className="flow-underline">{children}</span>
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue