diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index b19b876..02a1a73 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,43 +1,239 @@ 'use client'; - + import Link from 'next/link'; +import { useRef } from 'react'; +import { motion, useInView, useMotionValue, useTransform, animate } from 'motion/react'; import { - Badge, + Anchor, Button, - Card, - Center, Container, Grid, Group, - Paper, SimpleGrid, Stack, Text, - ThemeIcon, Title, Box, } from '@mantine/core'; import { - IconMap2, - IconChartBar, - IconDownload, - IconHeartHandshake, - IconDatabase, - IconBuildingCommunity, - IconPencil, -} from '@tabler/icons-react'; - + MapTrifoldIcon, + ChartBarIcon, + DownloadSimpleIcon, + HandHeartIcon, + PencilSimpleIcon, + GithubLogoIcon, + UsersThreeIcon, + CheckCircleIcon, + XCircleIcon, + ArrowRightIcon, + NewspaperIcon +} from '@phosphor-icons/react'; + import { useProfile } from '@/components/profile/profileStore'; - -interface HeroSectionProps { - myLocation: any; - comparison: any; - interests: string[]; - yearMin: number; - yearMax: number; - openProfileModal: () => void; + +/* --------------------------------------------------------------- + Design tokens + Palette drawn from Vermont's own landscape rather than a generic + SaaS gradient: deep spruce, granite slate, birch cream, and a + maple-syrup amber for the single warm accent. +----------------------------------------------------------------*/ +const COLOR = { + spruce: '#1B3A2F', + spruceDeep: '#122820', + slate: '#40525A', + birch: '#F6F5EF', + birchDim: '#EEEBE0', + ink: '#1B211D', + amber: '#dd9a2f', + amberSoft: '#E7B563', + amberYellow: '#FFD100', + line: 'rgba(27, 58, 47, 0.14)', +}; + +const FONT_DISPLAY = "'Fraunces', 'Iowan Old Style', serif"; +const FONT_BODY = "'General Sans', 'Inter', sans-serif"; +const FONT_MONO = "'IBM Plex Mono', ui-monospace, monospace"; + +/* --------------------------------------------------------------- + Hero title typewriter timing +----------------------------------------------------------------*/ +const HERO_LINE_1 = 'See Vermont,'; +const HERO_LINE_2 = 'town by town.'; +const TYPE_CHAR_DELAY = 0.045; +const TYPE_LINE1_START = 0.3; +const TYPE_LINE1_DURATION = HERO_LINE_1.length * TYPE_CHAR_DELAY; +const TYPE_LINE2_START = TYPE_LINE1_START + TYPE_LINE1_DURATION + 0.15; +const TYPE_LINE2_DURATION = HERO_LINE_2.length * TYPE_CHAR_DELAY; + +/* --------------------------------------------------------------- + TypewriterLine — reveals a line character by character, as if + it were being typed live. +----------------------------------------------------------------*/ +function TypewriterLine({ + text, + startDelay, + charDelay = TYPE_CHAR_DELAY, +}: { + text: string; + startDelay: number; + charDelay?: number; +}) { + return ( + + {Array.from(text).map((char, i) => ( + + {char} + + ))} + + ); } - + +/* --------------------------------------------------------------- + BlinkingCursor — a typed-text caret that starts blinking once + the line above it has finished typing. +----------------------------------------------------------------*/ +function BlinkingCursor({ delay }: { delay: number }) { + return ( + + ); +} + +/* --------------------------------------------------------------- + Reveal — scroll-triggered fade/slide wrapper. Used sparingly: + each section reveals once, as a whole, rather than every child + animating independently (that reads as noisy/AI-generated). +----------------------------------------------------------------*/ +function Reveal({ + children, + delay = 0, + y = 24, +}: { + children: React.ReactNode; + delay?: number; + y?: number; +}) { + return ( + + {children} + + ); +} + +/* --------------------------------------------------------------- + StatNumber — counts up from 0 when it scrolls into view. +----------------------------------------------------------------*/ +function StatNumber({ + value, + suffix = '', + prefix = '', +}: { + value: number; + suffix?: string; + prefix?: string; +}) { + const ref = useRef(null); + const inView = useInView(ref, { once: true, margin: '-40px' }); + const count = useMotionValue(0); + const rounded = useTransform(count, (v) => `${prefix}${Math.round(v).toLocaleString()}${suffix}`); + + if (inView) { + animate(count, value, { duration: 1.4, ease: [0.22, 1, 0.36, 1] }); + } + + return ( + + {rounded} + + ); +} + +/* --------------------------------------------------------------- + Topographic contour — the page's one signature motif. Vermont + is read through its elevation lines; a single set of contour + paths draws itself in on load, then sits quietly as texture. +----------------------------------------------------------------*/ +function ContourMark({ width = 260, height = 300 }: { width?: number; height?: number }) { + const paths = [ + 'M10,220 C60,180 90,240 140,200 C190,160 220,210 250,180', + 'M0,180 C50,150 80,190 130,160 C180,130 210,170 260,150', + 'M20,140 C60,115 90,145 130,120 C170,95 200,125 240,110', + 'M30,100 C60,82 90,102 120,85 C150,68 175,90 210,75', + ]; + return ( + + {paths.map((d, i) => ( + + ))} + + ); +} + +/* --------------------------------------------------------------- + Hero +----------------------------------------------------------------*/ function HeroSection({ myLocation, comparison, @@ -45,395 +241,564 @@ function HeroSection({ yearMin, yearMax, openProfileModal, -}: HeroSectionProps) { +}: { + myLocation: any; + comparison: any; + interests: string[]; + yearMin: number; + yearMax: number; + openProfileModal: () => void; +}) { return ( - <> - - + // Full-bleed: breaks out of the page's centered Container so the hero + // touches both edges of the viewport instead of floating as a card. + + + + - - - Vermont Open Data Platform - - - + <Text style={{ - fontSize: 'clamp(2.5rem, 5vw, 4rem)', - lineHeight: 1.05, + fontFamily: FONT_MONO, + fontSize: 12, + letterSpacing: '0.14em', + textTransform: 'uppercase', + color: COLOR.amberSoft, }} > - Vermont Data Explorer - - - - Explore Vermont through interactive maps, data analysis, - downloadable datasets, and planning tools. Built to help - communities better understand local conditions. + A project of the Vermont Data Collaborative - - - - + - + - + + {/* Profile panel — sits beside the title, always on the dark + background so the light text stays legible. */} - - {/* Vermont outline */} - Vermont outline - - - - - Your Profile - + + + Your profile + - - - LOCATION - - - {myLocation.name} - - - - - COMPARISON AREA - - - {comparison.name} - - - - - ANALYSIS PERIOD - - - - {yearMin}–{yearMax} - - - - - - INTERESTS - - - + + + + + + + Interests {interests.length > 0 ? ( - interests.map((interest) => ( - - {interest} - - )) - ) : ( - - No interests selected + + {interests.join(' · ')} + ) : ( + None selected )} - - - - + + + + - - + + ); } -function FeatureCards() { + +function FieldLabel({ children }: { children: React.ReactNode }) { return ( - <> -
-
- Explore the Platform -
+ + {children} + + ); +} - - { - e.currentTarget.style.transform = 'translateY(-2px)'; - e.currentTarget.style.boxShadow = - '0 4px 12px rgba(0, 0, 0, 0.15)'; - }} - onMouseLeave={(e) => { - e.currentTarget.style.transform = 'none'; - e.currentTarget.style.boxShadow = 'sm'; - }} - > - - - - - Mapping - - - Explore zoning, flood risk, wastewater suitability, and other - Vermont spatial datasets. +function ProfileField({ label, value }: { label: string; value?: string }) { + return ( + + {label} + {value || '—'} + + ); +} + +/* --------------------------------------------------------------- + Stat strip +----------------------------------------------------------------*/ +function StatStrip() { + const stats = [ + { value: 251, label: 'towns & cities mapped' }, + { value: 20, label: 'open datasets', suffix: '' }, + { value: 15, label: 'years of data', suffix: '+' }, + { value: 100, label: 'free to use', suffix: '%' }, + ]; + return ( + + + {stats.map((s) => ( + + + + {s.label} - + + ))} + + + ); +} - { - e.currentTarget.style.transform = 'translateY(-2px)'; - e.currentTarget.style.boxShadow = - '0 4px 12px rgba(0, 0, 0, 0.15)'; - }} - onMouseLeave={(e) => { - e.currentTarget.style.transform = 'none'; - e.currentTarget.style.boxShadow = 'sm'; - }} - > - - - - - Data Analysis - +function CapabilityFlow() { + const items = [ + { + icon: MapTrifoldIcon, + title: 'Mapping', + body: 'Statewide zoning, flood risk, wastewater, and other spatial layers at the county and town level.', + href: '/mapping', + }, + { + icon: ChartBarIcon, + title: 'Data Analysis', + body: 'Explore charts, tables, and comparisons across towns, counties, or the whole state.', + href: '/data-viewer', + }, + { + icon: NewspaperIcon, + title: 'Reporting', + body: 'Gather data visualizations and tables for community decisions.', + href: '/working-report', + }, + { + icon: DownloadSimpleIcon, + title: 'Open Data Export', + body: 'Download clean, analysis-ready datasets with readable variable names.', + href: '/data-export', + }, + { + icon: HandHeartIcon, + title: 'Benefits Estimator', + body: 'Check eligibility for Vermont assistance programs with a few questions.', + href: '/tools/benefits-estimator', + }, + ]; - - Generate charts, summaries, and comparisons from Vermont datasets. - - + return ( + + + + Explore Data Your Way + + + + + + + {items.map((item, i) => { + const Icon = item.icon; + return ( + + + + + - { - e.currentTarget.style.transform = 'translateY(-2px)'; - e.currentTarget.style.boxShadow = - '0 4px 12px rgba(0, 0, 0, 0.15)'; - }} - onMouseLeave={(e) => { - e.currentTarget.style.transform = 'none'; - e.currentTarget.style.boxShadow = 'sm'; - }} - > - - - + + + {item.title} + + + {item.body} + + + + + ); + })} + + + + ); +} - - Data Export - - - Download clean, analysis-ready datasets with readable variable - names. - - +function FeaturedDatasets() { + const datasets = [ + 'Zoning', + 'Flood Risk', + 'Wastewater', + 'Housing', + 'Demographics', + 'Labor & Economy', + 'Transportation', + 'Community Health', + ]; - + + + { - e.currentTarget.style.transform = 'translateY(-2px)'; - e.currentTarget.style.boxShadow = - '0 4px 12px rgba(0, 0, 0, 0.15)'; + fontFamily: FONT_MONO, + fontSize: 14, + letterSpacing: '0.1em', + textTransform: 'uppercase', + color: COLOR.amber, + textAlign: 'center', }} - onMouseLeave={(e) => { - e.currentTarget.style.transform = 'none'; - e.currentTarget.style.boxShadow = 'sm'; + mb={20} + > + Featured data + + + + - - - - - - Benefits Estimator - - - - Estimate eligibility for Vermont assistance programs and benefits. - - - -
- + {datasets.map((d, i) => ( + + + {d} + + {i < datasets.length - 1 && ( + · + )} + + ))} + + + + ); } -function FeaturedDatasets() { +/* --------------------------------------------------------------- + Value-adds — a direct comparison, not another card grid. +----------------------------------------------------------------*/ +function ValueAdds() { + const rows = [ + { label: 'Free, no account required', us: true, others: false }, + { label: 'Plain-language field names', us: true, others: false }, + { label: 'Vermont-specific, town-level detail', us: true, others: false }, + { label: 'Maintained with community input', us: true, others: false }, + { label: 'Updated on a public schedule', us: true, others: false }, + ]; + return ( - <> - {/* Featured Datasets */} - - - Featured Data + + + + What's different here + + + Compared to a typical government data portal. + + + + + + + + + Our platform + + + Typical portal + + - - - Zoning - - - Flood Risk - - - Wastewater - - - Housing - - - Demographics - - - Economy - - - Transportation - - - Community Data - - - - - + {rows.map((row, i) => ( + + + {row.label} + + + + + + + + + ))} + + + ); } -function PlatformValue() { +/* --------------------------------------------------------------- + Open source & community +----------------------------------------------------------------*/ +function CommunitySection() { return ( - <> - {/* Paste Platform Value exactly as-is */} - - - - - - - - - Integrated Data - - - - Census, planning, environmental, infrastructure, and community - datasets are brought together into a single platform for - exploration and analysis. - - - - - - - - - - - - Built for Vermont - - - - Designed to support residents, municipalities, planners, - researchers, and organizations working throughout Vermont. - - - - - + + + + + + + + Built in the open, by Vermonters + + + The platform's code and data pipelines are open source. Towns, + researchers, and volunteers help shape what gets added next — + and anyone can inspect exactly how a number was calculated. + + + + + + How to contribute → + + + + + ); } +/* --------------------------------------------------------------- + Page +----------------------------------------------------------------*/ function App() { const { myLocation, @@ -446,7 +811,7 @@ function App() { return ( - + - - + + - + + ); } -export default App; +export default App; \ No newline at end of file