From 35ada4127d8869c880274d8751b2762a5002127e Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 9 Mar 2026 20:58:25 -0700 Subject: [PATCH] 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 --- app/globals.css | 247 +++++++++++------- app/layout.tsx | 9 +- components/sandbox/FlowCanvas.tsx | 13 +- components/sandbox/FlowToolbar.tsx | 18 +- components/sandbox/nodes/PipeNode.tsx | 10 +- components/sandbox/nodes/SinkNode.tsx | 12 +- components/sandbox/nodes/SourceNode.tsx | 10 +- components/sections/CTASection.tsx | 34 ++- components/sections/ExtractivePipeSection.tsx | 17 +- components/sections/HeroSection.tsx | 68 +++-- components/sections/NatureFlowsSection.tsx | 18 +- components/sections/RegenerativeSection.tsx | 20 +- components/sections/SandboxSection.tsx | 16 +- components/sections/TransitionSection.tsx | 35 ++- components/ui/SectionHeader.tsx | 7 +- 15 files changed, 315 insertions(+), 219 deletions(-) diff --git a/app/globals.css b/app/globals.css index 6edeb17..f24e9b8 100644 --- a/app/globals.css +++ b/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); } diff --git a/app/layout.tsx b/app/layout.tsx index 3f0c457..60021f0 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -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 ( - + {children} diff --git a/components/sandbox/FlowCanvas.tsx b/components/sandbox/FlowCanvas.tsx index e0d9cd8..4b932d9 100644 --- a/components/sandbox/FlowCanvas.tsx +++ b/components/sandbox/FlowCanvas.tsx @@ -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 ( -
+
- + {showMiniMap && ( )} diff --git a/components/sandbox/FlowToolbar.tsx b/components/sandbox/FlowToolbar.tsx index 87b241a..845769c 100644 --- a/components/sandbox/FlowToolbar.tsx +++ b/components/sandbox/FlowToolbar.tsx @@ -20,24 +20,24 @@ export default function FlowToolbar({ return (
{/* Add nodes */} -
+
{/* Presets */} -
+
{presets.map((preset, i) => ( @@ -58,16 +58,16 @@ export default function FlowToolbar({
{/* Controls */} -
+
diff --git a/components/sandbox/nodes/PipeNode.tsx b/components/sandbox/nodes/PipeNode.tsx index 44ac16e..b52c977 100644 --- a/components/sandbox/nodes/PipeNode.tsx +++ b/components/sandbox/nodes/PipeNode.tsx @@ -4,12 +4,12 @@ import { Handle, Position, type NodeProps } from '@xyflow/react' export default function PipeNode({ data }: NodeProps) { return ( -
- - +
+ +
-
-
{(data as any).label || 'Pipe'}
+
+
{(data as any).label || 'Pipe'}
) diff --git a/components/sandbox/nodes/SinkNode.tsx b/components/sandbox/nodes/SinkNode.tsx index 1bfa421..ea5d9bd 100644 --- a/components/sandbox/nodes/SinkNode.tsx +++ b/components/sandbox/nodes/SinkNode.tsx @@ -6,15 +6,15 @@ export default function SinkNode({ data }: NodeProps) { const fillLevel = (data as any).fillLevel ?? 0 return ( -
- +
+
-
-
{(data as any).label || 'Sink'}
+
+
{(data as any).label || 'Sink'}
{/* Fill level indicator */} -
+
diff --git a/components/sandbox/nodes/SourceNode.tsx b/components/sandbox/nodes/SourceNode.tsx index f567598..bb2b0ef 100644 --- a/components/sandbox/nodes/SourceNode.tsx +++ b/components/sandbox/nodes/SourceNode.tsx @@ -4,13 +4,13 @@ import { Handle, Position, type NodeProps } from '@xyflow/react' export default function SourceNode({ data }: NodeProps) { return ( -
- +
+
-
-
{(data as any).label || 'Source'}
+
+
{(data as any).label || 'Source'}
{(data as any).flowRate !== undefined && ( -
+
{(data as any).flowRate} units/s
)} diff --git a/components/sections/CTASection.tsx b/components/sections/CTASection.tsx index b117caf..edad61d 100644 --- a/components/sections/CTASection.tsx +++ b/components/sections/CTASection.tsx @@ -4,46 +4,54 @@ import ScrollReveal from '@/components/ui/ScrollReveal' export default function CTASection() { return ( -
-
+
+
+ + {/* Soft glow behind CTA */} +
-

- Ready to flow? +

+ Ready to flow?

-

+

You already are.

- -

+ +

NoFi → FlowFi → ???

- + ← back to NoFi
- {/* Footer */} -