
3D Extruded Buildings & Urban Footprints
Real-world 3D building extrusions with dynamic sunlight shadows, terrain DEM elevations, and pitch/bearing camera controls.
Real estate neighborhood intelligence widget with Walk Score, Transit Score, Bike Score, categorized points of interest (transit, dining, schools, parks), and commute time estimator.
Also available for Vue ->$pnpm dlx shadcn@latest add https://uipkge.dev/r/react/neighborhood-walkscore-card.json$npx shadcn@latest add https://uipkge.dev/r/react/neighborhood-walkscore-card.json$yarn dlx shadcn@latest add https://uipkge.dev/r/react/neighborhood-walkscore-card.json$bunx shadcn@latest add https://uipkge.dev/r/react/neighborhood-walkscore-card.jsonnpx shadcn@latest add @uipkge-react/neighborhood-walkscore-cardInstalls to:components/blocks/| Name | Type / Values | Default | Required |
|---|---|---|---|
title | string | — | optional |
address | string | — | optional |
neighborhood | string | — | optional |
city | string | — | optional |
zipCode | string | — | optional |
walkScore | MobilityScore | — | optional |
transitScore | MobilityScore | — | optional |
bikeScore | MobilityScore | — | optional |
pois | PoiItem[] | — | optional |
commutes | CommuteDestination[] | — | optional |
className | string | — | optional |
Type aliases exported from this item's source. Use these to shape the data you pass in.
MobilityScoreinterface MobilityScore {
score: number
maxScore?: number
label: string
verdict: string
description: string
verdictClass: string
gaugeColor: string
subtext: string
highlights: string[]
}PoiIteminterface PoiItem {
id: string
name: string
category: 'transit' | 'dining' | 'schools' | 'parks'
categoryLabel: string
distance: string
timeWalk?: string
timeDrive?: string
rating?: string
badge?: string
description: string
address: string
coords?: [number, number]
}CommuteDestinationinterface CommuteDestination {
id: string
name: string
region: string
distance: string
transitTime: number
transitLabel: string
transitRoute: string
driveTime: number
driveLabel: string
driveRoute: string
peakRushTime?: string
}CSS custom properties referenced in this item. Override any of them in your :root or per-element to retheme.
--chart-1'use client'
import * as React from 'react'
import {
Footprints,
Bus,
Bike,
MapPin,
Search,
Navigation,
School,
TreePine,
Coffee,
GraduationCap,
UtensilsCrossed,
Car,
Train,
Clock,
Share2,
Bookmark,
Check,
ArrowUpRight,
ShieldCheck,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card'
import { Progress } from '@/components/ui/progress'
import { Separator } from '@/components/ui/separator'
import { Map, MapMarker, MapSource, MapLayer } from '@/components/ui/map'
export type PoiCategoryType = 'all' | 'transit' | 'dining' | 'schools' | 'parks'
export type CommuteMode = 'all' | 'transit' | 'drive'
export interface MobilityScore {
score: number
maxScore?: number
label: string
verdict: string
description: string
verdictClass: string
gaugeColor: string
subtext: string
highlights: string[]
}
export interface PoiItem {
id: string
name: string
category: 'transit' | 'dining' | 'schools' | 'parks'
categoryLabel: string
distance: string
timeWalk?: string
timeDrive?: string
rating?: string
badge?: string
description: string
address: string
coords?: [number, number]
}
export interface CommuteDestination {
id: string
name: string
region: string
distance: string
transitTime: number
transitLabel: string
transitRoute: string
driveTime: number
driveLabel: string
driveRoute: string
peakRushTime?: string
}
export interface NeighborhoodWalkscoreCardProps {
title?: string
address?: string
neighborhood?: string
city?: string
zipCode?: string
walkScore?: MobilityScore
transitScore?: MobilityScore
bikeScore?: MobilityScore
pois?: PoiItem[]
commutes?: CommuteDestination[]
className?: string
}
const defaultWalkScore: MobilityScore = {
score: 94,
maxScore: 100,
label: 'Walk Score®',
verdict: "Walker's Paradise",
description: 'Daily errands do not require a car',
verdictClass: 'text-success',
gaugeColor: '#10b981',
subtext: '94 out of 100 on walkable street accessibility',
highlights: ['48 amenities within 10-min walk', '98% continuous sidewalks', 'Pedestrian safety grade: A+'],
}
const defaultTransitScore: MobilityScore = {
score: 82,
maxScore: 100,
label: 'Transit Score®',
verdict: 'Excellent Transit',
description: '6 nearby bus & light rail routes',
verdictClass: 'text-info',
gaugeColor: '#0ea5e9',
subtext: '82 out of 100 on public transportation connectivity',
highlights: ['Metro A Line station in 4 min', '5 frequent municipal bus routes', '18 min direct train to DTLA'],
}
const defaultBikeScore: MobilityScore = {
score: 88,
maxScore: 100,
label: 'Bike Score®',
verdict: 'Very Bikeable',
description: 'Dedicated bike lanes and flat terrain',
verdictClass: 'text-chart-1',
gaugeColor: 'var(--chart-1)',
subtext: '88 out of 100 on cycling infrastructure & slope',
highlights: ['14 miles of protected bike lanes', '<2% average street gradient', '3 bikeshare docks nearby'],
}
const defaultPois: PoiItem[] = [
{
id: 'poi-1',
name: 'Metro A Line Station',
category: 'transit',
categoryLabel: 'Public Transit',
distance: '0.2 mi',
timeWalk: '4 min walk',
badge: 'Light Rail',
description: 'High-frequency light rail connecting Pasadena directly to Downtown LA and Long Beach.',
address: 'Del Mar Ave & S Raymond Ave',
},
{
id: 'poi-2',
name: 'Pasadena Transit Bus Stop',
category: 'transit',
categoryLabel: 'Public Transit',
distance: '0.1 mi',
timeWalk: '2 min walk',
badge: 'Local Transit',
description: 'Direct routes 10 & 20 connecting South Lake District to Old Town & City Hall.',
address: 'S Lake Ave & E Green St',
},
{
id: 'poi-3',
name: 'Memorial Park Station',
category: 'transit',
categoryLabel: 'Public Transit',
distance: '0.5 mi',
timeWalk: '10 min walk',
badge: 'Light Rail',
description: 'Alternative metro access with regional bus connection bays and bike lockups.',
address: '125 E Holly St',
},
{
id: 'poi-4',
name: 'Blue Bottle Coffee',
category: 'dining',
categoryLabel: 'Dining & Coffee',
distance: '0.1 mi',
timeWalk: '2 min walk',
rating: '4.8 ★',
badge: 'Artisanal Cafe',
description: 'Specialty pour-over coffee, single-origin espresso drinks, and fresh bakery pastries.',
address: '60 S Lake Ave',
},
{
id: 'poi-5',
name: 'Whole Foods Market',
category: 'dining',
categoryLabel: 'Dining & Coffee',
distance: '0.3 mi',
timeWalk: '6 min walk',
rating: '4.7 ★',
badge: 'Organic Grocery',
description: 'Full-service natural grocer with organic produce, prepared hot bar, and wellness center.',
address: '3751 E Foothill Blvd / Arroyo Pkwy',
},
{
id: 'poi-6',
name: "Trader Joe's",
category: 'dining',
categoryLabel: 'Dining & Coffee',
distance: '0.4 mi',
timeWalk: '8 min walk',
rating: '4.9 ★',
badge: 'Grocery',
description: 'Neighborhood market renowned for unique provisions, fresh bakery, and wine selection.',
address: '610 S Lake Ave',
},
{
id: 'poi-7',
name: 'Urth Caffe Pasadena',
category: 'dining',
categoryLabel: 'Dining & Coffee',
distance: '0.2 mi',
timeWalk: '5 min walk',
rating: '4.6 ★',
badge: 'Organic Eatery',
description: 'Vibrant indoor/outdoor patio serving farm-to-table organic breakfast, lunch, and matcha.',
address: '594 E Colorado Blvd',
},
{
id: 'poi-8',
name: 'Pasadena High School',
category: 'schools',
categoryLabel: 'Schools & Education',
distance: '0.6 mi',
timeWalk: '12 min walk',
rating: '9/10',
badge: 'Public 9-12',
description: 'Top-rated public high school with Law and Creative Arts academies and AP honors programs.',
address: '2925 E Sierra Madre Blvd',
},
{
id: 'poi-9',
name: 'Caltech Campus',
category: 'schools',
categoryLabel: 'Schools & Education',
distance: '0.8 mi',
timeWalk: '15 min walk',
rating: 'Top Tier',
badge: 'Higher Education',
description: 'World-renowned private science & engineering research university campus and open grounds.',
address: '1200 E California Blvd',
},
{
id: 'poi-10',
name: 'McKinley Elementary School',
category: 'schools',
categoryLabel: 'Schools & Education',
distance: '0.4 mi',
timeWalk: '8 min walk',
rating: '8/10',
badge: 'Public K-8',
description: 'STEAM-focused neighborhood magnet school with dual-language immersion track.',
address: '325 S Oak Knoll Ave',
},
{
id: 'poi-11',
name: 'Central Park',
category: 'parks',
categoryLabel: 'Parks & Recreation',
distance: '0.3 mi',
timeWalk: '6 min walk',
rating: '4.8 ★',
badge: 'Public Park',
description: 'Historic shaded park with rolling green lawns, dedicated dog park, and lawn bowling greens.',
address: '275 S Raymond Ave',
},
{
id: 'poi-12',
name: 'Rose Bowl Aquatics Center',
category: 'parks',
categoryLabel: 'Parks & Recreation',
distance: '1.4 mi',
timeDrive: '5 min drive',
rating: '4.9 ★',
badge: 'Sports Complex',
description: 'Twin Olympic 50-meter pools, fitness facilities, and adjacent Arroyo Seco running trails.',
address: '360 N Arroyo Blvd',
},
]
const defaultCommutes: CommuteDestination[] = [
{
id: 'commute-dtla',
name: 'Downtown Los Angeles',
region: 'Financial District & Union Station',
distance: '10.4 mi',
transitTime: 18,
transitLabel: '18m by train',
transitRoute: 'Metro A Line (Direct Express)',
driveTime: 24,
driveLabel: '24m by car',
driveRoute: 'via CA-110 S Freeway',
peakRushTime: '28-34m at 8:30 AM',
},
{
id: 'commute-sm',
name: 'Santa Monica',
region: 'Silicon Beach & Pier',
distance: '26.2 mi',
transitTime: 62,
transitLabel: '62m by train',
transitRoute: 'Metro A Line → Metro E Line',
driveTime: 45,
driveLabel: '45m by car',
driveRoute: 'via I-10 W / CA-110 S',
peakRushTime: '55-68m at 8:30 AM',
},
{
id: 'commute-burbank',
name: 'Burbank Media District',
region: 'Warner Bros & Disney Studios',
distance: '12.1 mi',
transitTime: 38,
transitLabel: '38m by transit',
transitRoute: 'Metro Bus 501 Rapid',
driveTime: 22,
driveLabel: '22m by car',
driveRoute: 'via CA-134 W Express',
peakRushTime: '26-32m at 8:30 AM',
},
{
id: 'commute-century',
name: 'Century City / Westwood',
region: 'Fox Studios & UCLA Area',
distance: '21.5 mi',
transitTime: 54,
transitLabel: '54m by transit',
transitRoute: 'Commuter Express 423',
driveTime: 38,
driveLabel: '38m by car',
driveRoute: 'via CA-134 W to I-405 S',
peakRushTime: '48-58m at 8:30 AM',
},
]
export function NeighborhoodWalkscoreCard({
title = 'Neighborhood & Location Intelligence',
address = '742 Evergreen Terrace, Pasadena, CA 91101',
neighborhood = 'South Lake Commercial & Arts District',
city = 'Pasadena',
zipCode = '91101',
walkScore = defaultWalkScore,
transitScore = defaultTransitScore,
bikeScore = defaultBikeScore,
pois = defaultPois,
commutes = defaultCommutes,
className,
}: NeighborhoodWalkscoreCardProps) {
const [selectedCategory, setSelectedCategory] = React.useState<PoiCategoryType>('all')
const [searchQuery, setSearchQuery] = React.useState('')
const [selectedCommuteMode, setSelectedCommuteMode] = React.useState<CommuteMode>('all')
const [isSaved, setIsSaved] = React.useState(false)
const [isCopied, setIsCopied] = React.useState(false)
const categoriesList = [
{ id: 'all' as const, label: 'All Places', count: pois.length },
{
id: 'transit' as const,
label: 'Public Transit',
count: pois.filter((p) => p.category === 'transit').length,
},
{
id: 'dining' as const,
label: 'Dining & Coffee',
count: pois.filter((p) => p.category === 'dining').length,
},
{
id: 'schools' as const,
label: 'Schools & Education',
count: pois.filter((p) => p.category === 'schools').length,
},
{
id: 'parks' as const,
label: 'Parks & Rec',
count: pois.filter((p) => p.category === 'parks').length,
},
]
const filteredPois = React.useMemo(() => {
return pois.filter((poi) => {
const matchesCat = selectedCategory === 'all' || poi.category === selectedCategory
const matchesSearch =
searchQuery.trim() === '' ||
poi.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
poi.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
poi.address.toLowerCase().includes(searchQuery.toLowerCase()) ||
(poi.badge && poi.badge.toLowerCase().includes(searchQuery.toLowerCase()))
return matchesCat && matchesSearch
})
}, [pois, selectedCategory, searchQuery])
const handleCopyAddress = () => {
if (typeof navigator !== 'undefined' && navigator.clipboard) {
navigator.clipboard.writeText(`${address}, ${neighborhood}`)
setIsCopied(true)
setTimeout(() => {
setIsCopied(false)
}, 2000)
}
}
// Circular gauge calculation helper (radius 38, perimeter ~238.76)
const circumference = 2 * Math.PI * 38
const getGaugeDashOffset = (score: number): number => {
const clamped = Math.min(100, Math.max(0, score))
return circumference - (clamped / 100) * circumference
}
const defaultPoiCoords: Record<string, [number, number]> = {
'poi-1': [-118.148, 34.141],
'poi-2': [-118.132, 34.152],
'poi-3': [-118.149, 34.148],
'poi-4': [-118.133, 34.144],
'poi-5': [-118.149, 34.135],
'poi-6': [-118.132, 34.136],
'poi-7': [-118.136, 34.146],
'poi-8': [-118.125, 34.155],
'poi-9': [-118.138, 34.138],
'poi-10': [-118.148, 34.143],
'poi-11': [-118.142, 34.133],
}
const getPoiCoords = (poi: PoiItem): [number, number] => {
if (poi.coords) return poi.coords
return (
defaultPoiCoords[poi.id] || [
-118.138 + Math.sin(poi.name.length) * 0.008,
34.145 + Math.cos(poi.name.length) * 0.008,
]
)
}
const walkshedGeoJson = React.useMemo(() => {
const centerLng = -118.138
const centerLat = 34.145
const radius = 0.011
const points = 36
const coordinates: [number, number][] = []
for (let i = 0; i <= points; i++) {
const angle = (i * 2 * Math.PI) / points
coordinates.push([centerLng + Math.cos(angle) * radius * 1.2, centerLat + Math.sin(angle) * radius])
}
return {
type: 'Feature' as const,
properties: {},
geometry: {
type: 'Polygon' as const,
coordinates: [coordinates],
},
}
}, [])
const walkshedFillPaint = {
'fill-color': 'rgba(16, 185, 129, 0.12)',
}
const walkshedLinePaint = {
'line-color': 'rgba(16, 185, 129, 0.6)',
'line-width': 1.5,
'line-dasharray': [2, 2],
}
return (
<div className={cn('w-full space-y-6', className)} data-slot="neighborhood-walkscore-card">
{/* Header Banner Card */}
<Card className="border-border/80 bg-card text-card-foreground shadow-xs">
<CardHeader className="p-4 sm:p-6">
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div className="space-y-2">
{/* Badges Bar */}
<div className="flex flex-wrap items-center gap-2">
<Badge wrap variant="outline" className="gap-1.5 font-medium">
<MapPin className="text-primary size-3.5" />
<span>
{city} · {zipCode}
</span>
</Badge>
<Badge wrap variant="success" className="gap-1 font-medium">
<Footprints className="size-3.5" />
<span>Walker's Paradise</span>
</Badge>
<Badge wrap variant="secondary" className="font-medium">
Urban Transit Hub
</Badge>
</div>
{/* Title & Address */}
<div>
<h1 className="text-foreground text-xl font-bold tracking-tight sm:text-2xl">{title}</h1>
<p className="text-foreground mt-1 text-sm font-semibold sm:text-base">{address}</p>
<p className="text-muted-foreground mt-0.5 text-xs sm:text-sm">
Neighborhood: <span className="text-foreground font-medium">{neighborhood}</span>
</p>
</div>
</div>
{/* Top Actions */}
<div className="flex flex-wrap items-center gap-2 self-start lg:self-auto">
<Button
variant="outline"
size="sm"
className="h-8 gap-1.5 text-xs font-medium shadow-xs"
onClick={handleCopyAddress}
>
{isCopied ? <Check className="text-success size-3.5" /> : <Share2 className="size-3.5" />}
<span>{isCopied ? 'Address Copied' : 'Share Location'}</span>
</Button>
<Button
variant={isSaved ? 'default' : 'outline'}
size="sm"
className="h-8 gap-1.5 text-xs font-medium shadow-xs"
onClick={() => setIsSaved(!isSaved)}
>
<Bookmark className={cn('size-3.5', isSaved && 'fill-current')} />
<span>{isSaved ? 'Saved Neighborhood' : 'Save Area'}</span>
</Button>
</div>
</div>
</CardHeader>
</Card>
{/* Section 1: 3 Primary Mobility Score Cards */}
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
{/* Card 1: Walk Score */}
<Card className="border-border/80 bg-card text-card-foreground hover:border-success/40 shadow-xs transition-colors">
<CardHeader className="p-4 pb-2 sm:p-5 sm:pb-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="border-success/30 bg-success/10 text-success flex size-8 items-center justify-center rounded-lg border">
<Footprints className="size-4" />
</div>
<div>
<CardTitle className="text-sm font-semibold">{walkScore.label}</CardTitle>
<CardDescription className="text-xs">Pedestrian Index</CardDescription>
</div>
</div>
<Badge wrap variant="success" className="text-xs font-semibold">
{walkScore.score}/100
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-4 p-4 pt-2 sm:p-5 sm:pt-2">
{/* Circular Gauge & Score Verdict */}
<div className="flex items-center gap-4 pt-1">
<div className="relative flex size-20 shrink-0 items-center justify-center">
<svg className="size-20 -rotate-90" viewBox="0 0 96 96">
<circle
cx="48"
cy="48"
r="38"
fill="none"
stroke="currentColor"
strokeWidth="7"
className="text-muted/40"
/>
<circle
cx="48"
cy="48"
r="38"
fill="none"
stroke={walkScore.gaugeColor}
strokeWidth="7"
strokeLinecap="round"
strokeDasharray={circumference}
strokeDashoffset={getGaugeDashOffset(walkScore.score)}
className="transition-colors duration-700 ease-out"
/>
</svg>
<div className="absolute inset-0 flex flex-col items-center justify-center text-center">
<span className="text-foreground font-mono text-xl font-bold tracking-tight tabular-nums">
{walkScore.score}
</span>
<span className="text-muted-foreground text-xs font-medium">/ 100</span>
</div>
</div>
<div className="space-y-1">
<div className="text-foreground text-sm font-bold tracking-tight">{walkScore.verdict}</div>
<p className="text-success text-xs leading-relaxed font-semibold">{walkScore.description}</p>
</div>
</div>
<Separator className="bg-border/60" />
{/* Key Highlights */}
<div className="space-y-1.5">
<p className="text-muted-foreground text-xs font-medium tracking-wider uppercase">Mobility Highlights</p>
<ul className="space-y-1 text-xs">
{walkScore.highlights.map((item, idx) => (
<li key={idx} className="text-foreground flex items-center gap-2">
<Check className="text-success size-3.5 shrink-0" />
<span>{item}</span>
</li>
))}
</ul>
</div>
</CardContent>
</Card>
{/* Card 2: Transit Score */}
<Card className="border-border/80 bg-card text-card-foreground hover:border-info/40 shadow-xs transition-colors">
<CardHeader className="p-4 pb-2 sm:p-5 sm:pb-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="border-info/30 bg-info/10 text-info flex size-8 items-center justify-center rounded-lg border">
<Bus className="size-4" />
</div>
<div>
<CardTitle className="text-sm font-semibold">{transitScore.label}</CardTitle>
<CardDescription className="text-xs">Transit Connectivity</CardDescription>
</div>
</div>
<Badge wrap variant="info" className="text-xs font-semibold">
{transitScore.score}/100
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-4 p-4 pt-2 sm:p-5 sm:pt-2">
{/* Circular Gauge & Score Verdict */}
<div className="flex items-center gap-4 pt-1">
<div className="relative flex size-20 shrink-0 items-center justify-center">
<svg className="size-20 -rotate-90" viewBox="0 0 96 96">
<circle
cx="48"
cy="48"
r="38"
fill="none"
stroke="currentColor"
strokeWidth="7"
className="text-muted/40"
/>
<circle
cx="48"
cy="48"
r="38"
fill="none"
stroke={transitScore.gaugeColor}
strokeWidth="7"
strokeLinecap="round"
strokeDasharray={circumference}
strokeDashoffset={getGaugeDashOffset(transitScore.score)}
className="transition-colors duration-700 ease-out"
/>
</svg>
<div className="absolute inset-0 flex flex-col items-center justify-center text-center">
<span className="text-foreground font-mono text-xl font-bold tracking-tight tabular-nums">
{transitScore.score}
</span>
<span className="text-muted-foreground text-xs font-medium">/ 100</span>
</div>
</div>
<div className="space-y-1">
<div className="text-foreground text-sm font-bold tracking-tight">{transitScore.verdict}</div>
<p className="text-info text-xs leading-relaxed font-semibold">{transitScore.description}</p>
</div>
</div>
<Separator className="bg-border/60" />
{/* Key Highlights */}
<div className="space-y-1.5">
<p className="text-muted-foreground text-xs font-medium tracking-wider uppercase">Transit Highlights</p>
<ul className="space-y-1 text-xs">
{transitScore.highlights.map((item, idx) => (
<li key={idx} className="text-foreground flex items-center gap-2">
<Check className="text-info size-3.5 shrink-0" />
<span>{item}</span>
</li>
))}
</ul>
</div>
</CardContent>
</Card>
{/* Card 3: Bike Score */}
<Card className="border-border/80 bg-card text-card-foreground hover:border-chart-1/40 shadow-xs transition-colors">
<CardHeader className="p-4 pb-2 sm:p-5 sm:pb-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="border-chart-1/30 bg-chart-1/10 text-chart-1 flex size-8 items-center justify-center rounded-lg border">
<Bike className="size-4" />
</div>
<div>
<CardTitle className="text-sm font-semibold">{bikeScore.label}</CardTitle>
<CardDescription className="text-xs">Cycling Grid</CardDescription>
</div>
</div>
<Badge wrap variant="outline" className="text-xs font-semibold">
{bikeScore.score}/100
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-4 p-4 pt-2 sm:p-5 sm:pt-2">
{/* Circular Gauge & Score Verdict */}
<div className="flex items-center gap-4 pt-1">
<div className="relative flex size-20 shrink-0 items-center justify-center">
<svg className="size-20 -rotate-90" viewBox="0 0 96 96">
<circle
cx="48"
cy="48"
r="38"
fill="none"
stroke="currentColor"
strokeWidth="7"
className="text-muted/40"
/>
<circle
cx="48"
cy="48"
r="38"
fill="none"
stroke={bikeScore.gaugeColor}
strokeWidth="7"
strokeLinecap="round"
strokeDasharray={circumference}
strokeDashoffset={getGaugeDashOffset(bikeScore.score)}
className="transition-colors duration-700 ease-out"
/>
</svg>
<div className="absolute inset-0 flex flex-col items-center justify-center text-center">
<span className="text-foreground font-mono text-xl font-bold tracking-tight tabular-nums">
{bikeScore.score}
</span>
<span className="text-muted-foreground text-xs font-medium">/ 100</span>
</div>
</div>
<div className="space-y-1">
<div className="text-foreground text-sm font-bold tracking-tight">{bikeScore.verdict}</div>
<p className="text-chart-1 text-xs leading-relaxed font-semibold">{bikeScore.description}</p>
</div>
</div>
<Separator className="bg-border/60" />
{/* Key Highlights */}
<div className="space-y-1.5">
<p className="text-muted-foreground text-xs font-medium tracking-wider uppercase">Cycling Highlights</p>
<ul className="space-y-1 text-xs">
{bikeScore.highlights.map((item, idx) => (
<li key={idx} className="text-foreground flex items-center gap-2">
<Check className="text-chart-1 size-3.5 shrink-0" />
<span>{item}</span>
</li>
))}
</ul>
</div>
</CardContent>
</Card>
</div>
{/* Section 2: Commute Time Estimator */}
<div className="space-y-3">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div>
<h2 className="text-foreground text-sm font-semibold tracking-tight">Commute Time Estimator</h2>
<p className="text-muted-foreground text-xs">
Estimated travel duration from 742 Evergreen Terrace to key employment & lifestyle hubs.
</p>
</div>
{/* Mode selector filters */}
<div className="border-border/80 bg-muted/40 flex items-center rounded-lg border p-1 text-xs">
<button
type="button"
className={cn(
'focus-visible:ring-ring rounded-md px-2.5 py-1 font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none',
selectedCommuteMode === 'all'
? 'bg-background text-foreground shadow-xs'
: 'text-muted-foreground hover:text-foreground',
)}
onClick={() => setSelectedCommuteMode('all')}
>
All Modes
</button>
<button
type="button"
className={cn(
'focus-visible:ring-ring flex items-center gap-1 rounded-md px-2.5 py-1 font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none',
selectedCommuteMode === 'transit'
? 'bg-background text-foreground shadow-xs'
: 'text-muted-foreground hover:text-foreground',
)}
onClick={() => setSelectedCommuteMode('transit')}
>
<Train className="size-3" />
<span>Transit</span>
</button>
<button
type="button"
className={cn(
'focus-visible:ring-ring flex items-center gap-1 rounded-md px-2.5 py-1 font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none',
selectedCommuteMode === 'drive'
? 'bg-background text-foreground shadow-xs'
: 'text-muted-foreground hover:text-foreground',
)}
onClick={() => setSelectedCommuteMode('drive')}
>
<Car className="size-3" />
<span>Driving</span>
</button>
</div>
</div>
<Card className="border-border/80 bg-card text-card-foreground overflow-hidden shadow-xs">
<div className="divide-border/60 divide-y">
{commutes.map((commute) => (
<div key={commute.id} className="hover:bg-muted/15 p-4 transition-colors sm:p-5">
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
{/* Destination Info */}
<div className="space-y-1">
<div className="flex items-center gap-2">
<h3 className="text-foreground text-sm font-semibold">{commute.name}</h3>
<span className="text-muted-foreground font-mono text-xs tabular-nums">· {commute.distance}</span>
</div>
<p className="text-muted-foreground text-xs">{commute.region}</p>
</div>
{/* Time Badges & Routes */}
<div className="flex flex-wrap items-center gap-3">
{/* Transit Mode Pill */}
{(selectedCommuteMode === 'all' || selectedCommuteMode === 'transit') && (
<div className="border-info/20 bg-info/10 flex items-center gap-2 rounded-lg border px-3 py-1.5 text-xs">
<Train className="text-info size-3.5" />
<div>
<span className="text-foreground font-mono font-semibold tabular-nums">
{commute.transitLabel}
</span>
<span className="text-muted-foreground hidden sm:inline"> — {commute.transitRoute}</span>
</div>
</div>
)}
{/* Drive Mode Pill */}
{(selectedCommuteMode === 'all' || selectedCommuteMode === 'drive') && (
<div className="border-success/20 bg-success/10 flex items-center gap-2 rounded-lg border px-3 py-1.5 text-xs">
<Car className="text-success size-3.5" />
<div>
<span className="text-foreground font-mono font-semibold tabular-nums">
{commute.driveLabel}
</span>
<span className="text-muted-foreground hidden sm:inline"> — {commute.driveRoute}</span>
</div>
</div>
)}
{/* Peak Rush Info */}
{commute.peakRushTime && (
<div className="text-muted-foreground flex items-center gap-1 font-mono text-xs tabular-nums">
<Clock className="text-muted-foreground size-3" />
<span>Rush: {commute.peakRushTime}</span>
</div>
)}
</div>
</div>
{/* Visual Progress Bar for Comparative Duration */}
<div className="mt-3 grid grid-cols-1 gap-2 pt-1 sm:grid-cols-2">
{(selectedCommuteMode === 'all' || selectedCommuteMode === 'transit') && (
<div className="space-y-1">
<div className="text-muted-foreground flex items-center justify-between text-xs">
<span>Transit Duration</span>
<span className="font-mono tabular-nums">{commute.transitTime} min</span>
</div>
<Progress value={Math.min(100, (commute.transitTime / 70) * 100)} className="bg-muted h-1.5" />
</div>
)}
{(selectedCommuteMode === 'all' || selectedCommuteMode === 'drive') && (
<div className="space-y-1">
<div className="text-muted-foreground flex items-center justify-between text-xs">
<span>Driving Duration</span>
<span className="font-mono tabular-nums">{commute.driveTime} min</span>
</div>
<Progress value={Math.min(100, (commute.driveTime / 70) * 100)} className="bg-muted h-1.5" />
</div>
)}
</div>
</div>
))}
</div>
</Card>
</div>
{/* Section 3: Nearby Points of Interest Categorized List */}
<div className="space-y-3">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div>
<h2 className="text-foreground text-sm font-semibold tracking-tight">Nearby Points of Interest</h2>
<p className="text-muted-foreground text-xs">
Key transit hubs, dining, specialty cafes, schools, and parks located within walking distance.
</p>
</div>
<div className="text-muted-foreground flex items-center gap-1.5 text-xs">
<Navigation className="text-primary size-3.5" />
<span className="font-mono tabular-nums">{pois.length} verified local venues</span>
</div>
</div>
{/* Controls: Category Filter Tabs & Search Bar */}
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
{/* Category Filter Tabs */}
<div className="flex flex-wrap items-center gap-1.5">
{categoriesList.map((cat) => (
<button
key={cat.id}
type="button"
className={cn(
'focus-visible:ring-ring rounded-md border px-3 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none',
selectedCategory === cat.id
? 'border-primary bg-primary text-primary-foreground shadow-xs'
: 'border-border/80 bg-card text-muted-foreground hover:bg-muted hover:text-foreground',
)}
onClick={() => setSelectedCategory(cat.id)}
>
{cat.label}
<span
className={cn(
'ml-1 font-mono tabular-nums',
selectedCategory === cat.id ? 'opacity-80' : 'text-muted-foreground',
)}
>
({cat.count})
</span>
</button>
))}
</div>
{/* Search Input */}
<div className="relative w-full sm:w-64">
<Search className="text-muted-foreground pointer-events-none absolute top-2.5 left-2.5 size-3.5" />
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search venue or street..."
className="border-border bg-background placeholder:text-muted-foreground focus-visible:ring-ring h-8 w-full rounded-md border pr-3 pl-8 text-xs focus-visible:ring-2 focus-visible:outline-none"
/>
</div>
</div>
{/* Interactive 15-Minute Walkshed & POI Map */}
<Card className="overflow-hidden">
<CardHeader className="border-border/70 border-b p-4 pb-3 sm:p-5 sm:pb-3">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div>
<CardTitle className="flex items-center gap-2 text-base font-semibold">
<Navigation className="text-success size-4" />
<span>15-Minute Walkshed & Amenity Locator</span>
</CardTitle>
<CardDescription>
Mapbox street basemap with 15-minute isochrone walking perimeter and categorized local destinations.
</CardDescription>
</div>
<Badge wrap variant="outline" className="w-fit gap-1 font-mono text-xs">
<Footprints className="text-success size-3" />
<span>98 Walk Score Paradise</span>
</Badge>
</div>
</CardHeader>
<div className="relative h-72 w-full sm:h-96">
<Map variant="dark" center={[-118.138, 34.145]} zoom={14.2} className="size-full">
{/* Isochrone 15-min Walk Circle */}
<MapSource id="walkshed-source" type="geojson" data={walkshedGeoJson}>
<MapLayer id="walkshed-fill" type="fill" paint={walkshedFillPaint} />
<MapLayer id="walkshed-line" type="line" paint={walkshedLinePaint} />
</MapSource>
{/* Home Property Marker */}
<MapMarker longitude={-118.138} latitude={34.145} anchor="center" className="z-30">
<div className="relative flex size-9 items-center justify-center">
<span className="bg-warning absolute inline-flex size-full animate-ping rounded-full opacity-60" />
<div className="bg-warning relative flex size-7 items-center justify-center rounded-full border-2 border-white text-white shadow-lg">
<MapPin className="size-4" />
</div>
</div>
</MapMarker>
{/* Filtered POI Markers */}
{filteredPois.map((poi) => {
const coords = getPoiCoords(poi)
return (
<MapMarker
key={poi.id}
longitude={coords[0]}
latitude={coords[1]}
anchor="center"
className="cursor-pointer select-none"
>
<div
className="border-background bg-background flex size-7 items-center justify-center rounded-full border-2 shadow-md"
title={poi.name}
>
{poi.category === 'dining' ? (
<Coffee className="text-warning size-3.5" />
) : poi.category === 'transit' ? (
<Train className="text-info size-3.5" />
) : poi.category === 'schools' ? (
<School className="text-chart-2 size-3.5" />
) : (
<TreePine className="text-success size-3.5" />
)}
</div>
</MapMarker>
)
})}
</Map>
</div>
</Card>
{/* POIs Grid */}
{filteredPois.length > 0 ? (
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
{filteredPois.map((poi) => (
<Card
key={poi.id}
className="border-border/80 bg-card text-card-foreground hover:border-border transition-colors hover:shadow-xs"
>
<CardContent className="p-4 sm:p-4">
<div className="flex items-start gap-3">
{/* Icon Box based on category */}
<div
className={cn(
'flex size-9 shrink-0 items-center justify-center rounded-lg border',
poi.category === 'transit'
? 'border-info/30 bg-info/10 text-info'
: poi.category === 'dining'
? 'border-warning/30 bg-warning/10 text-warning'
: poi.category === 'schools'
? 'border-chart-2/30 bg-chart-2/10 text-chart-2'
: 'border-success/30 bg-success/10 text-success',
)}
>
{poi.category === 'transit' && poi.badge === 'Light Rail' ? (
<Train className="size-4" />
) : poi.category === 'transit' ? (
<Bus className="size-4" />
) : poi.category === 'dining' && poi.name.includes('Coffee') ? (
<Coffee className="size-4" />
) : poi.category === 'dining' ? (
<UtensilsCrossed className="size-4" />
) : poi.category === 'schools' && poi.name.includes('Caltech') ? (
<GraduationCap className="size-4" />
) : poi.category === 'schools' ? (
<School className="size-4" />
) : (
<TreePine className="size-4" />
)}
</div>
{/* Main POI Details */}
<div className="min-w-0 flex-1 space-y-1">
<div className="flex items-start justify-between gap-2">
<h3 className="text-foreground truncate text-sm font-semibold">{poi.name}</h3>
{/* Distance / Walk Time Pill */}
<div className="bg-muted/70 text-foreground border-border/60 shrink-0 rounded-md border px-2 py-0.5 font-mono text-xs font-medium tabular-nums">
<span>{poi.distance}</span>
{poi.timeWalk && <span className="text-muted-foreground"> · {poi.timeWalk}</span>}
{poi.timeDrive && <span className="text-muted-foreground"> · {poi.timeDrive}</span>}
</div>
</div>
<p className="text-muted-foreground line-clamp-2 text-xs leading-relaxed">{poi.description}</p>
<div className="flex flex-wrap items-center justify-between gap-2 pt-1.5 text-xs">
<span className="text-muted-foreground truncate">{poi.address}</span>
<div className="flex items-center gap-1.5">
{poi.rating && (
<Badge wrap variant="secondary" className="font-mono text-xs font-semibold tabular-nums">
{poi.rating}
</Badge>
)}
{poi.badge && (
<Badge wrap variant="outline" className="text-xs">
{poi.badge}
</Badge>
)}
</div>
</div>
</div>
</div>
</CardContent>
</Card>
))}
</div>
) : (
/* Empty State when filter yields nothing */
<Card className="border-border/80 bg-card text-card-foreground p-8 text-center shadow-xs">
<div className="mx-auto flex max-w-sm flex-col items-center justify-center space-y-2">
<Search className="text-muted-foreground size-8" />
<p className="text-foreground text-sm font-semibold">No points of interest match "{searchQuery}"</p>
<p className="text-muted-foreground text-xs">
Try searching for other amenities like "coffee", "market", "school", or clear the filter.
</p>
<Button
variant="outline"
size="sm"
className="mt-2 text-xs"
onClick={() => {
setSearchQuery('')
setSelectedCategory('all')
}}
>
Clear Filters
</Button>
</div>
</Card>
)}
</div>
{/* Section 4: Quick Neighborhood Summary Footnote */}
<Card className="border-border/80 bg-muted/25 text-card-foreground p-4 shadow-xs sm:p-5">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="space-y-1">
<div className="flex items-center gap-2">
<ShieldCheck className="text-success size-4" />
<p className="text-foreground text-xs font-semibold">Official Location Score Intelligence Verification</p>
</div>
<p className="text-muted-foreground text-xs">
Calculated based on standard 15-minute pedestrian catchment models, municipal route timetables, and GIS
road grades.
</p>
</div>
<div className="flex items-center gap-3">
<Button
variant="outline"
size="sm"
className="h-8 gap-1.5 text-xs font-medium shadow-xs"
onClick={handleCopyAddress}
>
<ArrowUpRight className="size-3.5" />
<span>Open in Maps</span>
</Button>
</div>
</div>
</Card>
</div>
)
}
Raw manifest:https://uipkge.dev/r/react/neighborhood-walkscore-card.json