Untitled
unknown
plain_text
9 months ago
10 kB
7
Indexable
"use client"
import React, { useEffect, useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { TreePine, Leaf, Trophy, Unlock } from "lucide-react";
import { Progress } from "@/components/ui/progress";
interface TreeVisualizationProps {
impact: {
co2_saved: number;
trees_saved: number;
};
}
// Spruce Tree (Original)
const SpruceTree = ({ scale }: { scale: number }) => {
if (scale === 0) return null;
return (
<svg viewBox="0 0 100 100" className="w-full h-full transform transition-all duration-1000"
style={{ transform: `scale(${0.5 + (scale * 0.5) / 100})` }}>
<path d="M45 90 L55 90 L53 60 L47 60 Z" fill="#8B4513" />
<g>
<path d="M20 60 L80 60 L50 30 Z"
fill="#2F855A"
opacity={scale >= 20 ? "1" : "0"}
className="transition-all duration-500" />
<path d="M25 45 L75 45 L50 20 Z"
fill="#276749"
opacity={scale >= 50 ? "1" : "0"}
className="transition-all duration-500" />
<path d="M30 30 L70 30 L50 10 Z"
fill="#22543D"
opacity={scale >= 80 ? "1" : "0"}
className="transition-all duration-500" />
{scale >= 95 && (
<>
<circle cx="35" cy="50" r="2" fill="#48BB78" />
<circle cx="65" cy="50" r="2" fill="#48BB78" />
<circle cx="50" cy="25" r="2" fill="#48BB78" />
</>
)}
</g>
</svg>
);
};
// Oak Tree
const OakTree = ({ scale }: { scale: number }) => {
if (scale === 0) return null;
return (
<svg viewBox="0 0 100 100" className="w-full h-full transform transition-all duration-1000"
style={{ transform: `scale(${0.5 + (scale * 0.5) / 100})` }}>
<path d="M45 90 L55 90 L53 50 L47 50 Z" fill="#795548" />
<g>
<circle
cx="50" cy="45" r={scale >= 20 ? "25" : "0"}
fill="#2E7D32"
className="transition-all duration-500" />
{scale >= 50 && (
<>
<circle cx="35" cy="35" r="15" fill="#388E3C" />
<circle cx="65" cy="35" r="15" fill="#388E3C" />
</>
)}
{scale >= 80 && (
<>
<circle cx="50" cy="25" r="12" fill="#43A047" />
<circle cx="30" cy="45" r="10" fill="#43A047" />
<circle cx="70" cy="45" r="10" fill="#43A047" />
</>
)}
{scale >= 95 && (
<>
<circle cx="40" cy="30" r="2" fill="#81C784" />
<circle cx="60" cy="30" r="2" fill="#81C784" />
<circle cx="50" cy="20" r="2" fill="#81C784" />
</>
)}
</g>
</svg>
);
};
// Cherry Blossom
const CherryBlossomTree = ({ scale }: { scale: number }) => {
if (scale === 0) return null;
return (
<svg viewBox="0 0 100 100" className="w-full h-full transform transition-all duration-1000"
style={{ transform: `scale(${0.5 + (scale * 0.5) / 100})` }}>
<path d="M45 90 Q50 70 47 50 L53 50 Q50 70 55 90" fill="#8D6E63" />
<g>
{scale >= 20 && (
<circle cx="50" cy="45" r="20" fill="#FFCDD2" className="transition-all duration-500" />
)}
{scale >= 50 && (
<>
<circle cx="35" cy="35" r="12" fill="#F8BBD0" />
<circle cx="65" cy="35" r="12" fill="#F8BBD0" />
<path d="M50 25 Q60 35 70 25" stroke="#8D6E63" fill="none" strokeWidth="2" />
</>
)}
{scale >= 80 && (
<>
<circle cx="50" cy="25" r="10" fill="#F48FB1" />
<circle cx="30" cy="45" r="8" fill="#F48FB1" />
<circle cx="70" cy="45" r="8" fill="#F48FB1" />
</>
)}
{scale >= 95 && (
<>
<circle cx="40" cy="30" r="2" fill="#FCE4EC" />
<circle cx="60" cy="30" r="2" fill="#FCE4EC" />
<circle cx="50" cy="20" r="2" fill="#FCE4EC" />
</>
)}
</g>
</svg>
);
};
// Dragon Blood Tree
const DragonBloodTree = ({ scale }: { scale: number }) => {
if (scale === 0) return null;
return (
<svg viewBox="0 0 100 100" className="w-full h-full transform transition-all duration-1000"
style={{ transform: `scale(${0.5 + (scale * 0.5) / 100})` }}>
<path d="M40 90 L60 90 L57 50 L43 50 Z" fill="#5D4037" />
<g>
{scale >= 20 && (
<path d="M20 50 Q50 20 80 50"
fill="#1B5E20"
className="transition-all duration-500" />
)}
{scale >= 50 && (
<path d="M25 45 Q50 25 75 45"
fill="#2E7D32" />
)}
{scale >= 80 && (
<>
<path d="M30 40 Q50 30 70 40" fill="#388E3C" />
<line x1="50" y1="30" x2="50" y2="45" stroke="#5D4037" strokeWidth="2" />
</>
)}
{scale >= 95 && (
<>
<circle cx="40" cy="35" r="2" fill="#66BB6A" />
<circle cx="60" cy="35" r="2" fill="#66BB6A" />
<circle cx="50" cy="30" r="2" fill="#66BB6A" />
</>
)}
</g>
</svg>
);
};
// Yucca Tree
const YuccaTree = ({ scale }: { scale: number }) => {
if (scale === 0) return null;
return (
<svg viewBox="0 0 100 100" className="w-full h-full transform transition-all duration-1000"
style={{ transform: `scale(${0.5 + (scale * 0.5) / 100})` }}>
<path d="M45 90 L55 90 L53 40 L47 40 Z" fill="#8D6E63" />
<g>
{scale >= 20 && (
<>
<path d="M30 40 L50 20 L70 40" fill="#33691E" className="transition-all duration-500" />
<path d="M35 45 L50 25 L65 45" fill="#558B2F" className="transition-all duration-500" />
</>
)}
{scale >= 50 && (
<>
<path d="M40 50 L50 30 L60 50" fill="#689F38" />
<line x1="50" y1="20" x2="50" y2="40" stroke="#8D6E63" strokeWidth="2" />
</>
)}
{scale >= 80 && (
<>
<path d="M45 55 L50 35 L55 55" fill="#7CB342" />
<path d="M42 45 L50 25 L58 45" fill="#7CB342" />
</>
)}
{scale >= 95 && (
<>
<circle cx="50" cy="25" r="2" fill="#FFF59D" />
<circle cx="45" cy="30" r="2" fill="#FFF59D" />
<circle cx="55" cy="30" r="2" fill="#FFF59D" />
</>
)}
</g>
</svg>
);
};
const treeComponents = [
SpruceTree,
OakTree,
CherryBlossomTree,
DragonBloodTree,
YuccaTree
];
const TreeVisualization: React.FC<TreeVisualizationProps> = ({ impact }) => {
const [treeStates, setTreeStates] = useState<number[]>([]);
const maxTrees = 5;
useEffect(() => {
const isMaxed = impact.trees_saved >= maxTrees;
const totalTrees = Math.floor(impact.trees_saved);
const partialTreeScale = isMaxed ? 100 : (impact.trees_saved % 1) * 100;
let newTreeStates: number[] = [];
// Add full trees
for (let i = 0; i < Math.min(totalTrees, maxTrees); i++) {
newTreeStates.push(100);
}
// Add partial tree if there's room and not maxed
if (!isMaxed && newTreeStates.length < maxTrees) {
newTreeStates.push(partialTreeScale);
}
// Pad with empty trees if needed
while (newTreeStates.length < maxTrees) {
newTreeStates.push(0);
}
setTreeStates(newTreeStates.slice(0, maxTrees));
}, [impact.trees_saved]);
const totalProgress = Math.min((impact.trees_saved / maxTrees) * 100, 100);
const isMaxed = impact.trees_saved >= maxTrees;
return (
<div className={`flex flex-col transition-all duration-300 ${
isMaxed
? 'dark:bg-gradient-to-br dark:from-primary/10 dark:to-primary/5 ' +
'bg-gradient-to-br from-primary/20 to-background border-primary/50 ' +
'shadow-lg shadow-primary/5 rounded-lg p-4'
: ''
}`}>
<CardHeader className="items-center pb-2">
<div className="flex items-center justify-center w-full gap-2">
<TreePine className="h-5 w-5 text-green-700" />
<CardTitle>Your Impact Forest</CardTitle>
{isMaxed && <Unlock className="h-5 w-5 text-primary" />}
</div>
<CardDescription className="text-center">
Watch your forest grow with your impact
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col items-center space-y-4">
<div className="grid grid-cols-5 gap-2 w-full h-48">
{treeStates.map((scale, index) => {
const TreeComponent = treeComponents[index];
return (
<div key={index} className="relative flex items-center justify-center">
<TreeComponent scale={scale} />
</div>
);
})}
</div>
<div className="w-full space-y-2">
<div className="flex justify-between text-sm">
<span>Forest Growth Progress</span>
<span>{Math.round(totalProgress)}%</span>
</div>
<Progress value={totalProgress} className="h-2" />
</div>
<div className="grid grid-cols-2 gap-4 text-sm text-muted-foreground">
<div className="flex items-center gap-2">
<Leaf className="h-4 w-4 text-green-500" />
<span>{impact.co2_saved.toFixed(2)} kg CO₂ saved</span>
</div>
<div className="flex items-center gap-2">
<TreePine className="h-4 w-4 text-green-700" />
<span>{impact.trees_saved.toFixed(3)} trees preserved</span>
</div>
</div>
{isMaxed && (
<div className="flex items-center gap-2 py-2 px-4 bg-primary/10 rounded-lg border border-primary/20">
<Trophy className="h-6 w-6 text-primary" />
<span className="font-semibold text-primary">
Maximum Forest Size Reached!
</span>
</div>
)}
</CardContent>
</div>
);
};
export default TreeVisualization;Editor is loading...
Leave a Comment