Initial commit: Apartment dashboard React app

This commit is contained in:
2025-07-15 16:22:17 -06:00
commit be399f30fe
15 changed files with 5225 additions and 0 deletions

42
src/App.css Normal file
View File

@ -0,0 +1,42 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

549
src/App.jsx Normal file
View File

@ -0,0 +1,549 @@
import React, { useState, useEffect } from 'react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, BarChart, Bar, PieChart, Pie, Cell, Area, AreaChart } from 'recharts';
import { TrendingUp, TrendingDown, Home, DollarSign, Calendar, MapPin, Activity, AlertCircle, Star } from 'lucide-react';
const ApartmentDashboard = () => {
const [selectedUnit, setSelectedUnit] = useState(null);
const [timeRange, setTimeRange] = useState('30d');
const [viewMode, setViewMode] = useState('overview');
// Mock data based on your MongoDB structure
const [mockData] = useState({
dailySummary: {
date: '2025-01-15',
newUnits: 3,
rentedUnits: 5,
staleUnits: 1,
netChange: -3,
turnoverRate: 8.2,
totalAvailable: 47
},
priceHistory: [
{ date: '2025-01-01', avgPrice: 3850, unitCount: 52 },
{ date: '2025-01-02', avgPrice: 3845, unitCount: 48 },
{ date: '2025-01-03', avgPrice: 3860, unitCount: 51 },
{ date: '2025-01-04', avgPrice: 3875, unitCount: 49 },
{ date: '2025-01-05', avgPrice: 3820, unitCount: 53 },
{ date: '2025-01-06', avgPrice: 3835, unitCount: 50 },
{ date: '2025-01-07', avgPrice: 3890, unitCount: 47 },
{ date: '2025-01-08', avgPrice: 3865, unitCount: 52 },
{ date: '2025-01-09', avgPrice: 3880, unitCount: 48 },
{ date: '2025-01-10', avgPrice: 3855, unitCount: 51 },
{ date: '2025-01-11', avgPrice: 3870, unitCount: 49 },
{ date: '2025-01-12', avgPrice: 3825, unitCount: 54 },
{ date: '2025-01-13', avgPrice: 3845, unitCount: 50 },
{ date: '2025-01-14', avgPrice: 3885, unitCount: 46 },
{ date: '2025-01-15', avgPrice: 3875, unitCount: 47 }
],
availableUnits: [
{
unitCode: 'W2506',
planName: 'Maroon Peak',
bedCount: 2,
bathCount: 2,
area: 1089,
currentPrice: 3750,
minPrice: 3650,
maxPrice: 3850,
daysAvailable: 12,
community: 'Country Club Towers',
priceHistory: [
{ date: '2025-01-04', price: 3850 },
{ date: '2025-01-05', price: 3825 },
{ date: '2025-01-06', price: 3800 },
{ date: '2025-01-07', price: 3775 },
{ date: '2025-01-08', price: 3750 }
]
},
{
unitCode: 'T1205',
planName: 'North Maroon Peak',
bedCount: 2,
bathCount: 2,
area: 1150,
currentPrice: 3950,
minPrice: 3875,
maxPrice: 3995,
daysAvailable: 8,
community: 'Country Club Towers',
priceHistory: [
{ date: '2025-01-08', price: 3995 },
{ date: '2025-01-09', price: 3975 },
{ date: '2025-01-10', price: 3950 }
]
},
{
unitCode: 'G0304',
planName: 'Snowmass',
bedCount: 1,
bathCount: 1,
area: 750,
currentPrice: 2850,
minPrice: 2800,
maxPrice: 2950,
daysAvailable: 22,
community: 'Country Club Gardens',
priceHistory: [
{ date: '2024-12-24', price: 2950 },
{ date: '2024-12-30', price: 2925 },
{ date: '2025-01-05', price: 2900 },
{ date: '2025-01-10', price: 2875 },
{ date: '2025-01-15', price: 2850 }
]
}
],
recentActivity: [
{
type: 'new',
unitCode: 'W1408',
planName: 'Maroon Peak',
price: 3825,
date: '2025-01-15',
bedCount: 2,
bathCount: 2
},
{
type: 'rented',
unitCode: 'T0912',
planName: 'North Maroon Peak',
price: 3950,
date: '2025-01-15',
bedCount: 2,
bathCount: 2
},
{
type: 'price_drop',
unitCode: 'W2506',
oldPrice: 3775,
newPrice: 3750,
date: '2025-01-15',
planName: 'Maroon Peak'
}
],
planStats: [
{ name: 'Maroon Peak', count: 15, avgPrice: 3780, avgArea: 1089 },
{ name: 'North Maroon Peak', count: 8, avgPrice: 3920, avgArea: 1150 },
{ name: 'Snowmass', count: 12, avgPrice: 2850, avgArea: 750 },
{ name: 'Aspen', count: 7, avgPrice: 3200, avgArea: 950 },
{ name: 'Capitol Peak', count: 5, avgPrice: 4200, avgArea: 1350 }
]
});
const MetricCard = ({ title, value, subtitle, icon: Icon, trend, color = "blue" }) => (
<div className="bg-white rounded-lg shadow-md p-6 border-l-4" style={{borderLeftColor: color === 'blue' ? '#3B82F6' : color === 'green' ? '#10B981' : color === 'red' ? '#EF4444' : '#F59E0B'}}>
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">{title}</p>
<p className="text-2xl font-bold text-gray-900">{value}</p>
{subtitle && <p className="text-sm text-gray-500">{subtitle}</p>}
</div>
<div className="flex items-center space-x-2">
{trend && (
<span className={`text-sm ${trend > 0 ? 'text-green-600' : 'text-red-600'} flex items-center`}>
{trend > 0 ? <TrendingUp className="w-4 h-4" /> : <TrendingDown className="w-4 h-4" />}
{Math.abs(trend)}%
</span>
)}
<Icon className="w-8 h-8 text-gray-400" />
</div>
</div>
</div>
);
const formatPrice = (price) => `$${price?.toLocaleString()}`;
const formatDate = (dateStr) => new Date(dateStr).toLocaleDateString();
const COLORS = ['#8B0000', '#DC143C', '#B22222', '#CD5C5C', '#F08080'];
return (
<div className="min-h-screen bg-gray-50 p-6">
<div className="max-w-7xl mx-auto">
{/* Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-2">Country Club Towers & Gardens</h1>
<p className="text-gray-600">Real-time apartment market dashboard</p>
<div className="flex items-center space-x-4 mt-4">
<button
onClick={() => setViewMode('overview')}
className={`px-4 py-2 rounded-md ${viewMode === 'overview' ? 'bg-blue-600 text-white' : 'bg-white text-gray-700 border'}`}
>
Overview
</button>
<button
onClick={() => setViewMode('units')}
className={`px-4 py-2 rounded-md ${viewMode === 'units' ? 'bg-blue-600 text-white' : 'bg-white text-gray-700 border'}`}
>
Available Units
</button>
<button
onClick={() => setViewMode('analytics')}
className={`px-4 py-2 rounded-md ${viewMode === 'analytics' ? 'bg-blue-600 text-white' : 'bg-white text-gray-700 border'}`}
>
Market Analytics
</button>
</div>
</div>
{viewMode === 'overview' && (
<>
{/* Daily Metrics */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<MetricCard
title="Available Units"
value={mockData.dailySummary.totalAvailable}
icon={Home}
color="blue"
/>
<MetricCard
title="New Today"
value={mockData.dailySummary.newUnits}
subtitle="Units available"
icon={TrendingUp}
color="green"
/>
<MetricCard
title="Rented Today"
value={mockData.dailySummary.rentedUnits}
subtitle="Units leased"
icon={TrendingDown}
color="red"
/>
<MetricCard
title="Turnover Rate"
value={`${mockData.dailySummary.turnoverRate}%`}
subtitle="Daily activity"
icon={Activity}
color="orange"
/>
</div>
{/* Price Trends */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold mb-4">Average Price Trend (15 days)</h3>
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={mockData.priceHistory}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="date"
tickFormatter={(date) => new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
/>
<YAxis tickFormatter={(value) => `$${value}`} />
<Tooltip
formatter={(value, name) => [formatPrice(value), 'Avg Price']}
labelFormatter={(date) => formatDate(date)}
/>
<Area type="monotone" dataKey="avgPrice" stroke="#8B0000" fill="#8B0000" fillOpacity={0.1} />
</AreaChart>
</ResponsiveContainer>
</div>
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold mb-4">Unit Availability</h3>
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={mockData.priceHistory}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="date"
tickFormatter={(date) => new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
/>
<YAxis />
<Tooltip
formatter={(value, name) => [value, 'Available Units']}
labelFormatter={(date) => formatDate(date)}
/>
<Area type="monotone" dataKey="unitCount" stroke="#3B82F6" fill="#3B82F6" fillOpacity={0.2} />
</AreaChart>
</ResponsiveContainer>
</div>
</div>
{/* Plan Distribution */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold mb-4">Units by Floor Plan</h3>
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
data={mockData.planStats}
cx="50%"
cy="50%"
labelLine={false}
label={({ name, count }) => `${name}: ${count}`}
outerRadius={80}
fill="#8884d8"
dataKey="count"
>
{mockData.planStats.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip />
</PieChart>
</ResponsiveContainer>
</div>
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold mb-4">Average Price by Plan</h3>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={mockData.planStats}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" angle={-45} textAnchor="end" height={80} />
<YAxis tickFormatter={(value) => `$${value}`} />
<Tooltip formatter={(value, name) => [formatPrice(value), 'Avg Price']} />
<Bar dataKey="avgPrice" fill="#8B0000" />
</BarChart>
</ResponsiveContainer>
</div>
</div>
{/* Recent Activity */}
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold mb-4">Recent Activity</h3>
<div className="space-y-4">
{mockData.recentActivity.map((activity, index) => (
<div key={index} className="flex items-center justify-between p-4 bg-gray-50 rounded-lg">
<div className="flex items-center space-x-3">
{activity.type === 'new' && <div className="w-3 h-3 bg-green-500 rounded-full"></div>}
{activity.type === 'rented' && <div className="w-3 h-3 bg-red-500 rounded-full"></div>}
{activity.type === 'price_drop' && <div className="w-3 h-3 bg-orange-500 rounded-full"></div>}
<div>
<p className="font-medium">
{activity.type === 'new' && `New unit available: ${activity.unitCode}`}
{activity.type === 'rented' && `Unit rented: ${activity.unitCode}`}
{activity.type === 'price_drop' && `Price drop: ${activity.unitCode}`}
</p>
<p className="text-sm text-gray-500">
{activity.planName} {activity.bedCount}BR/{activity.bathCount}BA
{activity.type === 'price_drop'
? `${formatPrice(activity.oldPrice)}${formatPrice(activity.newPrice)}`
: `${formatPrice(activity.price)}`
}
</p>
</div>
</div>
<span className="text-sm text-gray-500">{formatDate(activity.date)}</span>
</div>
))}
</div>
</div>
</>
)}
{viewMode === 'units' && (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h2 className="text-xl font-semibold">Available Units ({mockData.availableUnits.length})</h2>
<div className="flex space-x-2">
<select className="border rounded-md px-3 py-2">
<option>All Plans</option>
<option>Maroon Peak</option>
<option>North Maroon Peak</option>
<option>Snowmass</option>
</select>
<select className="border rounded-md px-3 py-2">
<option>All Bedrooms</option>
<option>1 BR</option>
<option>2 BR</option>
<option>3 BR</option>
</select>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
{mockData.availableUnits.map((unit) => (
<div key={unit.unitCode} className="bg-white rounded-lg shadow-md overflow-hidden">
<div className="p-6">
<div className="flex justify-between items-start mb-4">
<div>
<h3 className="text-lg font-bold">{unit.unitCode}</h3>
<p className="text-gray-600">{unit.planName}</p>
{unit.planName.toLowerCase().includes('maroon peak') &&
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-red-100 text-red-800 mt-1">
<Star className="w-3 h-3 mr-1" />
Priority
</span>
}
</div>
<div className="text-right">
<p className="text-2xl font-bold text-green-600">{formatPrice(unit.currentPrice)}</p>
<p className="text-sm text-gray-500">Current Price</p>
</div>
</div>
<div className="grid grid-cols-2 gap-4 mb-4">
<div className="text-center p-3 bg-gray-50 rounded">
<p className="text-sm text-gray-600">Bedrooms</p>
<p className="font-semibold">{unit.bedCount}</p>
</div>
<div className="text-center p-3 bg-gray-50 rounded">
<p className="text-sm text-gray-600">Bathrooms</p>
<p className="font-semibold">{unit.bathCount}</p>
</div>
<div className="text-center p-3 bg-gray-50 rounded">
<p className="text-sm text-gray-600">Area</p>
<p className="font-semibold">{unit.area} sqft</p>
</div>
<div className="text-center p-3 bg-gray-50 rounded">
<p className="text-sm text-gray-600">Available</p>
<p className="font-semibold">{unit.daysAvailable} days</p>
</div>
</div>
<div className="border-t pt-4">
<div className="flex justify-between text-sm mb-2">
<span>Price Range:</span>
<span>{formatPrice(unit.minPrice)} - {formatPrice(unit.maxPrice)}</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full"
style={{
width: `${((unit.currentPrice - unit.minPrice) / (unit.maxPrice - unit.minPrice)) * 100}%`
}}
></div>
</div>
</div>
<button
onClick={() => setSelectedUnit(unit)}
className="w-full mt-4 bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition-colors"
>
View Details
</button>
</div>
</div>
))}
</div>
</div>
)}
{viewMode === 'analytics' && (
<div className="space-y-6">
<h2 className="text-xl font-semibold">Market Analytics</h2>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold mb-4">Price Distribution by Plan</h3>
<div className="space-y-4">
{mockData.planStats.map((plan, index) => (
<div key={plan.name} className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div
className="w-4 h-4 rounded"
style={{ backgroundColor: COLORS[index % COLORS.length] }}
></div>
<span className="font-medium">{plan.name}</span>
</div>
<div className="text-right">
<p className="font-semibold">{formatPrice(plan.avgPrice)}</p>
<p className="text-sm text-gray-500">{plan.count} units</p>
</div>
</div>
))}
</div>
</div>
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold mb-4">Market Insights</h3>
<div className="space-y-4">
<div className="p-4 bg-red-50 rounded-lg border-l-4 border-red-500">
<h4 className="font-semibold text-red-800">Maroon Peak Priority</h4>
<p className="text-sm text-red-700">
Maroon Peak units under $3,800 are highlighted as priority opportunities based on your preferences.
</p>
</div>
<div className="p-4 bg-blue-50 rounded-lg border-l-4 border-blue-500">
<h4 className="font-semibold text-blue-800">Market Activity</h4>
<p className="text-sm text-blue-700">
Daily turnover rate of {mockData.dailySummary.turnoverRate}% indicates moderate market activity.
</p>
</div>
<div className="p-4 bg-green-50 rounded-lg border-l-4 border-green-500">
<h4 className="font-semibold text-green-800">Price Trends</h4>
<p className="text-sm text-green-700">
Average prices have stabilized around $3,850 with minimal daily fluctuation.
</p>
</div>
</div>
</div>
</div>
</div>
)}
{/* Unit Detail Modal */}
{selectedUnit && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-white rounded-lg max-w-2xl w-full max-h-[90vh] overflow-y-auto">
<div className="p-6">
<div className="flex justify-between items-start mb-6">
<div>
<h2 className="text-2xl font-bold">{selectedUnit.unitCode}</h2>
<p className="text-gray-600">{selectedUnit.planName} {selectedUnit.community}</p>
</div>
<button
onClick={() => setSelectedUnit(null)}
className="text-gray-500 hover:text-gray-700"
>
</button>
</div>
<div className="grid grid-cols-2 gap-6 mb-6">
<div>
<h3 className="font-semibold mb-2">Current Price</h3>
<p className="text-3xl font-bold text-green-600">{formatPrice(selectedUnit.currentPrice)}</p>
</div>
<div>
<h3 className="font-semibold mb-2">Price Range</h3>
<p className="text-lg">{formatPrice(selectedUnit.minPrice)} - {formatPrice(selectedUnit.maxPrice)}</p>
</div>
</div>
<div className="grid grid-cols-4 gap-4 mb-6">
<div className="text-center">
<p className="text-sm text-gray-600">Bedrooms</p>
<p className="text-xl font-semibold">{selectedUnit.bedCount}</p>
</div>
<div className="text-center">
<p className="text-sm text-gray-600">Bathrooms</p>
<p className="text-xl font-semibold">{selectedUnit.bathCount}</p>
</div>
<div className="text-center">
<p className="text-sm text-gray-600">Area</p>
<p className="text-xl font-semibold">{selectedUnit.area}</p>
</div>
<div className="text-center">
<p className="text-sm text-gray-600">Days Available</p>
<p className="text-xl font-semibold">{selectedUnit.daysAvailable}</p>
</div>
</div>
<div className="mb-6">
<h3 className="font-semibold mb-4">Price History</h3>
<ResponsiveContainer width="100%" height={200}>
<LineChart data={selectedUnit.priceHistory}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="date"
tickFormatter={(date) => new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
/>
<YAxis tickFormatter={(value) => `$${value}`} />
<Tooltip
formatter={(value, name) => [formatPrice(value), 'Price']}
labelFormatter={(date) => formatDate(date)}
/>
<Line type="monotone" dataKey="price" stroke="#8B0000" strokeWidth={2} />
</LineChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
)}
</div>
</div>
);
};
export default ApartmentDashboard;

1
src/assets/react.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

16
src/index.css Normal file
View File

@ -0,0 +1,16 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
* {
box-sizing: border-box;
}

10
src/main.jsx Normal file
View File

@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)