redesign: modern dark dashboard with gradients and animations
- New color system: deep #0a0a0f bg with indigo/violet/emerald accents - Sidebar redesign: gradient DH badge, improved active state, cleaner typography - Card system: .card class with colored top-border accents, hover glow - WeatherWidget: large temperature display, gradient bg, better 7-day grid - CalendarWidget: gradient today highlight, violet accents, improved events panel - TasksWidget: priority dots, hover states, max-height scroll - System page: circular SVG gauges with glow, accent cards, better bars - Bookmarks page: hover lift effect, colored category headers - Claude widgets: gradient badge headers, accent borders - DashboardHeader: live clock, gradient greeting text - ServicesGrid: pulsing online indicator (animate-ping), card lift on hover - globals.css: Inter font, custom scrollbar, card/glass-card, gradient-text helper - tailwind.config.ts: dash colors, gradient BG images, glow/float animations
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import { RefreshCw, Wind, Droplets, Thermometer } from "lucide-react";
|
||||
import { RefreshCw } from "lucide-react";
|
||||
|
||||
interface CurrentWeather {
|
||||
temp: number; feelsLike: number; humidity: number; windKmh: number; desc: string; icon: string;
|
||||
@@ -14,7 +14,6 @@ interface WeatherData {
|
||||
}
|
||||
|
||||
const DAY_NAMES = ["Вс","Пн","Вт","Ср","Чт","Пт","Сб"];
|
||||
const MONTH_NAMES = ["янв","фев","мар","апр","май","июн","июл","авг","сен","окт","ноя","дек"];
|
||||
|
||||
export function WeatherWidget() {
|
||||
const [data, setData] = useState<WeatherData | null>(null);
|
||||
@@ -36,80 +35,98 @@ export function WeatherWidget() {
|
||||
|
||||
const c = data?.current;
|
||||
const day = data?.forecast?.[selected];
|
||||
const formatDate = (dateStr: string) => {
|
||||
|
||||
const formatDay = (dateStr: string, idx: number) => {
|
||||
if (idx === 0) return "Сег";
|
||||
const d = new Date(dateStr + "T12:00:00");
|
||||
return `${DAY_NAMES[d.getDay()]}, ${d.getDate()} ${MONTH_NAMES[d.getMonth()]}`;
|
||||
return DAY_NAMES[d.getDay()];
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="glass-card p-5 flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-slate-300">Погода · Санкт-Петербург</span>
|
||||
<button onClick={fetchData} className="text-slate-500 hover:text-slate-300 transition-colors">
|
||||
<RefreshCw className={`w-3.5 h-3.5 ${loading ? "animate-spin" : ""}`} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="card card-accent-blue relative overflow-hidden">
|
||||
{/* Decorative background */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-blue-900/20 via-transparent to-violet-900/10 pointer-events-none" />
|
||||
|
||||
{error ? (
|
||||
<div className="text-slate-500 text-sm text-center py-4">Нет данных</div>
|
||||
) : loading ? (
|
||||
<div className="space-y-3 animate-pulse">
|
||||
<div className="h-12 bg-slate-700/50 rounded w-32" />
|
||||
<div className="h-4 bg-slate-700/50 rounded w-48" />
|
||||
<div className="flex gap-2">{[...Array(7)].map((_,i)=><div key={i} className="h-20 bg-slate-700/50 rounded flex-1"/>)}</div>
|
||||
<div className="relative p-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-5">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs font-semibold text-blue-400 uppercase tracking-wider">Погода</span>
|
||||
<span className="text-xs text-slate-500">· Санкт-Петербург</span>
|
||||
</div>
|
||||
<button onClick={fetchData} className="text-slate-600 hover:text-slate-300 transition-colors p-1 rounded-lg hover:bg-white/5">
|
||||
<RefreshCw className={`w-3.5 h-3.5 ${loading ? "animate-spin" : ""}`} />
|
||||
</button>
|
||||
</div>
|
||||
) : c && day ? (
|
||||
<>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-5xl">{selected === 0 ? c.icon : day.icon}</span>
|
||||
<div>
|
||||
<div className="flex items-baseline gap-1">
|
||||
<span className="text-4xl font-bold text-white">
|
||||
{selected === 0 ? c.temp : day.maxTemp}°
|
||||
</span>
|
||||
{selected !== 0 && (
|
||||
<span className="text-slate-400 text-lg">{day.minTemp}°</span>
|
||||
|
||||
{error ? (
|
||||
<div className="text-slate-500 text-sm text-center py-8">Нет данных о погоде</div>
|
||||
) : loading ? (
|
||||
<div className="space-y-4 animate-pulse">
|
||||
<div className="flex items-end gap-4">
|
||||
<div className="h-20 w-20 bg-white/5 rounded-2xl" />
|
||||
<div className="space-y-2">
|
||||
<div className="h-12 w-28 bg-white/5 rounded-lg" />
|
||||
<div className="h-4 w-40 bg-white/5 rounded" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">{[...Array(7)].map((_,i) => <div key={i} className="flex-1 h-24 bg-white/5 rounded-xl" />)}</div>
|
||||
</div>
|
||||
) : c && day ? (
|
||||
<>
|
||||
{/* Current weather */}
|
||||
<div className="flex items-start justify-between mb-6">
|
||||
<div>
|
||||
<div className="flex items-baseline gap-3 mb-1">
|
||||
<span className="text-7xl font-extralight text-white leading-none">
|
||||
{selected === 0 ? c.temp : day.maxTemp}°
|
||||
</span>
|
||||
<span className="text-4xl">{selected === 0 ? c.icon : day.icon}</span>
|
||||
</div>
|
||||
<p className="text-slate-300 mt-2">{selected === 0 ? c.desc : day.desc}</p>
|
||||
{selected === 0 && (
|
||||
<p className="text-slate-500 text-xs mt-0.5">Ощущается как {c.feelsLike}°</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-slate-300 text-sm">{selected === 0 ? c.desc : day.desc}</div>
|
||||
<div className="text-xs text-slate-500">{selected === 0 ? "Сейчас" : formatDate(day.date)}</div>
|
||||
{selected === 0 && (
|
||||
<div className="text-right space-y-2 mt-2">
|
||||
<div className="flex items-center gap-2 justify-end text-sm">
|
||||
<span className="text-slate-500 text-xs">Влажность</span>
|
||||
<span className="text-blue-400 font-medium">💧 {c.humidity}%</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 justify-end text-sm">
|
||||
<span className="text-slate-500 text-xs">Ветер</span>
|
||||
<span className="text-cyan-400 font-medium">💨 {c.windKmh} км/ч</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{selected === 0 && (
|
||||
<div className="ml-auto flex flex-col gap-1 text-xs text-slate-400">
|
||||
<span className="flex items-center gap-1"><Thermometer className="w-3 h-3 text-orange-400"/>Ощущ. {c.feelsLike}°</span>
|
||||
<span className="flex items-center gap-1"><Droplets className="w-3 h-3 text-blue-400"/>{c.humidity}%</span>
|
||||
<span className="flex items-center gap-1"><Wind className="w-3 h-3 text-teal-400"/>{c.windKmh} км/ч</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1.5 overflow-x-auto pb-1">
|
||||
{data.forecast.map((f, i) => {
|
||||
const d = new Date(f.date + "T12:00:00");
|
||||
const isToday = i === 0;
|
||||
return (
|
||||
{/* 7-day forecast */}
|
||||
<div className="grid grid-cols-7 gap-1.5">
|
||||
{data!.forecast.map((f, i) => (
|
||||
<button
|
||||
key={f.date}
|
||||
onClick={() => setSelected(i)}
|
||||
className={`flex-1 min-w-[52px] flex flex-col items-center gap-1 p-2 rounded-xl transition-all text-center ${
|
||||
className={`flex flex-col items-center gap-1.5 p-2 rounded-xl transition-all ${
|
||||
selected === i
|
||||
? "bg-indigo-600/60 ring-1 ring-indigo-500"
|
||||
: "bg-slate-800/40 hover:bg-slate-700/50"
|
||||
? "bg-white/10 ring-1 ring-white/20"
|
||||
: "hover:bg-white/5"
|
||||
}`}
|
||||
>
|
||||
<span className="text-xs text-slate-400">{isToday ? "Сег" : DAY_NAMES[d.getDay()]}</span>
|
||||
<span className="text-lg">{f.icon}</span>
|
||||
<span className="text-xs font-medium text-white">{f.maxTemp}°</span>
|
||||
<span className="text-xs text-slate-500">{f.minTemp}°</span>
|
||||
<span className="text-[10px] font-medium text-slate-400">{formatDay(f.date, i)}</span>
|
||||
<span className="text-xl">{f.icon}</span>
|
||||
<span className="text-xs font-semibold text-white">{f.maxTemp}°</span>
|
||||
<span className="text-[10px] text-slate-600">{f.minTemp}°</span>
|
||||
{f.precipProb > 20 && (
|
||||
<span className="text-[10px] text-blue-400">{f.precipProb}%</span>
|
||||
<span className="text-[9px] text-blue-400 font-medium">{f.precipProb}%</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user