feat: integrate Tailwind CSS with custom theme and animations
Added Tailwind CSS and tw-animate-css imports to globals.css. Introduced a custom dark variant and an extensive @theme inline block with CSS variables for colors, radii, and animations. Included keyframes for shimmer-slide, spin-around, gradient, shine, and background-position-spin to enable consistent theming and dynamic UI effects across the application.
This commit is contained in:
1 parent
55c169bd6e
commit
5c1ccbc69a
67 files changed
+8984
-160
No files matched your search
@@ -0,0 +1,246 @@
|
||||
"use client";
|
||||
|
||||
import { MarketOffer } from "@/types/item";
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PriceDisplay } from "@/components/ui/price-display";
|
||||
import { useTranslations } from "next-intl";
|
||||
import {
|
||||
Clock,
|
||||
User,
|
||||
Coins,
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
Star,
|
||||
Heart,
|
||||
Shield,
|
||||
Sword,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { getRarityColor, getRarityBgColor } from "@/lib/currency-utils";
|
||||
|
||||
interface OfferCardProps {
|
||||
offer: MarketOffer;
|
||||
}
|
||||
|
||||
export function OfferCard({ offer }: OfferCardProps) {
|
||||
const t = useTranslations("home.stats");
|
||||
const tButtons = useTranslations("home.buttons");
|
||||
const [isFavorited, setIsFavorited] = useState(false);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
const formatTimeAgo = (timestamp: Date) => {
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - timestamp.getTime();
|
||||
const minutes = Math.floor(diff / (1000 * 60));
|
||||
|
||||
if (minutes < 1) return "Agora";
|
||||
if (minutes < 60) return `há ${minutes} ${t("minutes")}`;
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours < 24) return `há ${hours} ${t("hours")}`;
|
||||
const days = Math.floor(hours / 24);
|
||||
return `há ${days} dias`;
|
||||
};
|
||||
|
||||
const getInitials = (name: string) => {
|
||||
return name
|
||||
.split(" ")
|
||||
.map((word) => word[0])
|
||||
.join("")
|
||||
.toUpperCase()
|
||||
.slice(0, 2);
|
||||
};
|
||||
|
||||
const getCategoryIcon = (category: string) => {
|
||||
switch (category) {
|
||||
case "weapon":
|
||||
return <Sword className="w-6 h-6" />;
|
||||
case "armor":
|
||||
return <Shield className="w-6 h-6" />;
|
||||
default:
|
||||
return "📦";
|
||||
}
|
||||
};
|
||||
|
||||
const getRarityColor = (rarity: string) => {
|
||||
switch (rarity) {
|
||||
case "common":
|
||||
return "bg-gray-500";
|
||||
case "uncommon":
|
||||
return "bg-green-500";
|
||||
case "rare":
|
||||
return "bg-blue-500";
|
||||
case "epic":
|
||||
return "bg-purple-500";
|
||||
case "legendary":
|
||||
return "bg-orange-500";
|
||||
default:
|
||||
return "bg-gray-500";
|
||||
}
|
||||
};
|
||||
|
||||
const isHotDeal = offer.price < offer.item.priceHistory.average * 0.8;
|
||||
const isNewOffer =
|
||||
new Date().getTime() - offer.timestamp.getTime() < 1000 * 60 * 30; // 30 min
|
||||
|
||||
return (
|
||||
<div
|
||||
className="group relative flex items-center justify-between p-6 border border-border/50 rounded-xl hover:border-primary/30 hover:shadow-lg transition-all duration-300 hover:scale-[1.01] bg-background/50 backdrop-blur-sm"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
{/* Badges de status */}
|
||||
<div className="absolute top-3 left-3 flex gap-2">
|
||||
{isNewOffer && (
|
||||
<Badge className="bg-green-500 text-white text-xs animate-pulse">
|
||||
⭐ {t("status.new")}
|
||||
</Badge>
|
||||
)}
|
||||
{isHotDeal && (
|
||||
<Badge className="bg-red-500 text-white text-xs animate-bounce">
|
||||
🔥 {t("status.hotDeal")}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Botão de favorito */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="absolute top-3 right-3 h-8 w-8 bg-background/80 backdrop-blur-sm hover:bg-background opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
onClick={() => setIsFavorited(!isFavorited)}
|
||||
>
|
||||
<Heart
|
||||
className={`h-4 w-4 transition-colors ${
|
||||
isFavorited
|
||||
? "fill-red-500 text-red-500"
|
||||
: "text-muted-foreground hover:text-red-500"
|
||||
}`}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<div className="flex items-center gap-6 flex-1">
|
||||
{/* Ícone do item com raridade */}
|
||||
<div className="relative">
|
||||
<div className="w-16 h-16 bg-muted/50 rounded-xl flex items-center justify-center text-2xl backdrop-blur-sm">
|
||||
{getCategoryIcon(offer.item.category)}
|
||||
</div>
|
||||
<Badge
|
||||
className={`${getRarityColor(
|
||||
offer.item.rarity
|
||||
)} text-white absolute -top-2 -right-2 text-xs shadow-lg`}
|
||||
>
|
||||
{offer.item.rarity}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Informações do item */}
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="space-y-1">
|
||||
<h3 className="font-bold text-lg text-foreground">
|
||||
{offer.item.name}
|
||||
</h3>
|
||||
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
||||
<div className="flex items-center gap-1">
|
||||
<Avatar className="w-5 h-5">
|
||||
<AvatarFallback className="text-xs">
|
||||
{getInitials(offer.seller)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="font-medium">{offer.seller}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>{formatTimeAgo(offer.timestamp)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Estatísticas do item */}
|
||||
{offer.item.stats && (
|
||||
<div className="flex flex-wrap gap-3 text-sm">
|
||||
{offer.item.stats.attack && (
|
||||
<div className="flex items-center gap-1 text-muted-foreground">
|
||||
<Sword className="h-3 w-3" />
|
||||
<span>{offer.item.stats.attack} ATK</span>
|
||||
</div>
|
||||
)}
|
||||
{offer.item.stats.defense && (
|
||||
<div className="flex items-center gap-1 text-muted-foreground">
|
||||
<Shield className="h-3 w-3" />
|
||||
<span>{offer.item.stats.defense} DEF</span>
|
||||
</div>
|
||||
)}
|
||||
{offer.item.stats.armor && (
|
||||
<div className="flex items-center gap-1 text-muted-foreground">
|
||||
<Shield className="h-3 w-3" />
|
||||
<span>{offer.item.stats.armor} ARM</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Preço e ações */}
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="text-right space-y-2">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-1 text-sm text-muted-foreground">
|
||||
<Coins className="h-4 w-4" />
|
||||
<span>Preço</span>
|
||||
</div>
|
||||
<PriceDisplay
|
||||
value={offer.price}
|
||||
size="lg"
|
||||
className="text-foreground"
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{offer.item.stackable
|
||||
? `Stack: ${offer.quantity}x`
|
||||
: `Qtd: ${offer.quantity}`}
|
||||
{offer.item.stackable && offer.quantity > 1 && (
|
||||
<span className="block text-xs">
|
||||
{Math.round(offer.price / offer.quantity).toLocaleString()}{" "}
|
||||
gp cada
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Badge
|
||||
variant={offer.type === "buy" ? "default" : "secondary"}
|
||||
className={`${
|
||||
offer.type === "buy"
|
||||
? "bg-green-500 hover:bg-green-600"
|
||||
: "bg-blue-500 hover:bg-blue-600"
|
||||
} text-white transition-colors`}
|
||||
>
|
||||
{offer.type === "buy" ? "Comprando" : "Vendendo"}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Button
|
||||
size="sm"
|
||||
className={`w-full transition-all duration-200 ${
|
||||
offer.type === "buy"
|
||||
? "bg-green-600 hover:bg-green-700"
|
||||
: "bg-blue-600 hover:bg-blue-700"
|
||||
} text-white`}
|
||||
>
|
||||
{tButtons("negotiate")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
>
|
||||
{tButtons("viewProfile")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user