Initial commit

This commit is contained in:
2025-07-18 13:07:28 +08:00
commit e3832acfa8
91 changed files with 10929 additions and 0 deletions

429
components/wish-card.tsx Normal file
View File

@@ -0,0 +1,429 @@
"use client"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
MessageCircle,
Target,
Lightbulb,
Calendar,
Sparkles,
Clock,
Zap,
ChevronDown,
ChevronUp,
Heart,
Users,
} from "lucide-react"
import { categorizeWishMultiple, type Wish } from "@/lib/categorization"
import { generateSolutionRecommendations, type SolutionCategory } from "@/lib/solution-recommendations"
import { useState, useEffect } from "react"
import { soundManager } from "@/lib/sound-effects"
interface WishCardProps {
wish: Wish
}
export default function WishCard({ wish }: WishCardProps) {
const [showSolutions, setShowSolutions] = useState(false)
const [selectedSolution, setSelectedSolution] = useState<SolutionCategory | null>(null)
const [likeCount, setLikeCount] = useState(0)
const [hasLiked, setHasLiked] = useState(false)
const [isLiking, setIsLiking] = useState(false)
// 載入點讚數據
useEffect(() => {
const likes = JSON.parse(localStorage.getItem("wishLikes") || "{}")
const likedWishes = JSON.parse(localStorage.getItem("userLikedWishes") || "[]")
setLikeCount(likes[wish.id] || 0)
setHasLiked(likedWishes.includes(wish.id))
}, [wish.id])
const handleLike = async () => {
if (hasLiked || isLiking) return
setIsLiking(true)
// 播放點讚音效
await soundManager.play("click")
// 更新點讚數據
const likes = JSON.parse(localStorage.getItem("wishLikes") || "{}")
const likedWishes = JSON.parse(localStorage.getItem("userLikedWishes") || "[]")
likes[wish.id] = (likes[wish.id] || 0) + 1
likedWishes.push(wish.id)
localStorage.setItem("wishLikes", JSON.stringify(likes))
localStorage.setItem("userLikedWishes", JSON.stringify(likedWishes))
setLikeCount(likes[wish.id])
setHasLiked(true)
// 播放成功音效
setTimeout(async () => {
await soundManager.play("success")
setIsLiking(false)
}, 300)
}
const formatDate = (dateString: string) => {
const date = new Date(dateString)
return date.toLocaleDateString("zh-TW", {
year: "numeric",
month: "long",
day: "numeric",
})
}
// 多標籤自動分類最多3個
const categories = categorizeWishMultiple(wish).slice(0, 3)
// 生成解決方案建議
const solutionRecommendation = generateSolutionRecommendations(wish)
const getDifficultyColor = (difficulty: string) => {
switch (difficulty) {
case "easy":
return "bg-green-500/20 text-green-300 border-green-400/40"
case "medium":
return "bg-yellow-500/20 text-yellow-300 border-yellow-400/40"
case "hard":
return "bg-orange-500/20 text-orange-300 border-orange-400/40"
default:
return "bg-gray-500/20 text-gray-300 border-gray-400/40"
}
}
const getDifficultyLabel = (difficulty: string) => {
switch (difficulty) {
case "easy":
return "容易實現"
case "medium":
return "中等難度"
case "hard":
return "需要投入"
default:
return "未知"
}
}
return (
<Card className="group relative overflow-hidden bg-gradient-to-br from-slate-800/90 to-slate-900/90 backdrop-blur-sm border border-slate-600/50 hover:border-cyan-400/50 shadow-2xl hover:shadow-cyan-500/20 transition-all duration-500 transform hover:scale-[1.01] mx-2 md:mx-0">
{/* 背景光效 */}
<div className="absolute inset-0 bg-gradient-to-br from-cyan-500/5 via-transparent to-blue-500/5 opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
{/* 頂部裝飾線 */}
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-cyan-400 via-blue-400 to-purple-400"></div>
<CardHeader className="relative pb-3 md:pb-4 px-4 md:px-6 pt-4 md:pt-6">
<div className="flex items-start justify-between gap-3 md:gap-4 mb-3">
<CardTitle className="text-xl md:text-2xl font-bold text-white group-hover:text-cyan-100 transition-colors duration-300 leading-tight flex-1">
{wish.title}
</CardTitle>
<div className="flex items-center gap-2 shrink-0">
<Badge className="bg-slate-700/80 hover:bg-slate-600/80 text-slate-200 border border-slate-500/50 px-2 md:px-3 py-1 text-xs md:text-sm">
<Calendar className="w-3 h-3 mr-1 md:mr-1.5" />
<span className="hidden sm:inline">{formatDate(wish.createdAt)}</span>
<span className="sm:hidden">
{new Date(wish.createdAt).toLocaleDateString("zh-TW", { month: "short", day: "numeric" })}
</span>
</Badge>
</div>
</div>
{/* 最多3個問題領域標籤 */}
<div className="flex items-center gap-2 flex-wrap">
{categories.map((category, index) => (
<Badge
key={`${category.name}-${index}`}
className={`bg-gradient-to-r ${category.bgColor} ${category.borderColor} ${category.textColor} border backdrop-blur-sm px-3 py-1.5 text-xs md:text-sm font-medium shadow-lg ${
index === 0 ? "ring-2 ring-white/20" : ""
}`}
>
<div className="w-2 h-2 rounded-full mr-2 shadow-sm" style={{ backgroundColor: category.color }}></div>
{category.name}
{index === 0 && categories.length > 1 && <span className="ml-1 text-xs opacity-75"></span>}
</Badge>
))}
</div>
</CardHeader>
<CardContent className="relative space-y-4 md:space-y-5 px-4 md:px-6 pb-4 md:pb-6">
{/* 目前困擾 - 手機優化 */}
<div className="group/section relative overflow-hidden rounded-lg md:rounded-xl bg-gradient-to-r from-slate-700/60 to-slate-800/60 border border-slate-600/40 hover:border-purple-400/30 p-4 md:p-5 backdrop-blur-sm cursor-pointer transition-all duration-300 hover:transform hover:scale-[1.02] hover:shadow-lg hover:shadow-purple-500/10">
<div className="absolute inset-0 bg-gradient-to-r from-purple-500/8 to-indigo-500/8 group-hover/section:from-purple-500/15 group-hover/section:to-indigo-500/15 transition-all duration-300"></div>
{/* 懸停時的光暈效果 */}
<div className="absolute inset-0 bg-gradient-to-r from-purple-400/0 via-purple-400/5 to-purple-400/0 opacity-0 group-hover/section:opacity-100 transition-opacity duration-500"></div>
<div className="relative">
<div className="flex items-center gap-2 md:gap-3 mb-2 md:mb-3">
<div className="w-7 h-7 md:w-8 md:h-8 rounded-full bg-gradient-to-br from-purple-400/80 to-indigo-500/80 flex items-center justify-center shadow-lg shadow-purple-500/20 group-hover/section:shadow-purple-500/30 group-hover/section:scale-110 transition-all duration-300">
<MessageCircle className="w-3.5 h-3.5 md:w-4 md:h-4 text-white group-hover/section:rotate-12 transition-transform duration-300" />
</div>
<h4 className="font-semibold text-purple-200 group-hover/section:text-purple-100 text-base md:text-lg transition-colors duration-300">
</h4>
</div>
<CardDescription className="text-slate-200 group-hover/section:text-slate-100 text-sm md:text-base leading-relaxed font-medium transition-colors duration-300">
{wish.currentPain}
</CardDescription>
</div>
</div>
{/* 期望解決方式 - 手機優化 */}
<div className="group/section relative overflow-hidden rounded-lg md:rounded-xl bg-gradient-to-r from-slate-700/60 to-slate-800/60 border border-slate-600/40 hover:border-cyan-400/30 p-4 md:p-5 backdrop-blur-sm cursor-pointer transition-all duration-300 hover:transform hover:scale-[1.02] hover:shadow-lg hover:shadow-cyan-500/10">
<div className="absolute inset-0 bg-gradient-to-r from-cyan-500/8 to-blue-500/8 group-hover/section:from-cyan-500/15 group-hover/section:to-blue-500/15 transition-all duration-300"></div>
{/* 懸停時的光暈效果 */}
<div className="absolute inset-0 bg-gradient-to-r from-cyan-400/0 via-cyan-400/5 to-cyan-400/0 opacity-0 group-hover/section:opacity-100 transition-opacity duration-500"></div>
<div className="relative">
<div className="flex items-center gap-2 md:gap-3 mb-2 md:mb-3">
<div className="w-7 h-7 md:w-8 md:h-8 rounded-full bg-gradient-to-br from-cyan-400/80 to-blue-500/80 flex items-center justify-center shadow-lg shadow-cyan-500/20 group-hover/section:shadow-cyan-500/30 group-hover/section:scale-110 transition-all duration-300">
<Lightbulb className="w-3.5 h-3.5 md:w-4 md:h-4 text-white group-hover/section:rotate-12 transition-transform duration-300" />
</div>
<h4 className="font-semibold text-cyan-200 group-hover/section:text-cyan-100 text-base md:text-lg transition-colors duration-300">
</h4>
</div>
<CardDescription className="text-slate-200 group-hover/section:text-slate-100 text-sm md:text-base leading-relaxed font-medium transition-colors duration-300">
{wish.expectedSolution}
</CardDescription>
</div>
</div>
{/* 預期效果 - 手機優化 */}
{wish.expectedEffect && (
<div className="group/section relative overflow-hidden rounded-lg md:rounded-xl bg-gradient-to-r from-slate-700/60 to-slate-800/60 border border-slate-600/40 hover:border-indigo-400/30 p-4 md:p-5 backdrop-blur-sm cursor-pointer transition-all duration-300 hover:transform hover:scale-[1.02] hover:shadow-lg hover:shadow-indigo-500/10">
<div className="absolute inset-0 bg-gradient-to-r from-indigo-500/8 to-purple-500/8 group-hover/section:from-indigo-500/15 group-hover/section:to-purple-500/15 transition-all duration-300"></div>
{/* 懸停時的光暈效果 */}
<div className="absolute inset-0 bg-gradient-to-r from-indigo-400/0 via-indigo-400/5 to-indigo-400/0 opacity-0 group-hover/section:opacity-100 transition-opacity duration-500"></div>
<div className="relative">
<div className="flex items-center gap-2 md:gap-3 mb-2 md:mb-3">
<div className="w-7 h-7 md:w-8 md:h-8 rounded-full bg-gradient-to-br from-indigo-400/80 to-purple-500/80 flex items-center justify-center shadow-lg shadow-indigo-500/20 group-hover/section:shadow-indigo-500/30 group-hover/section:scale-110 transition-all duration-300">
<Target className="w-3.5 h-3.5 md:w-4 md:h-4 text-white group-hover/section:rotate-12 transition-transform duration-300" />
</div>
<h4 className="font-semibold text-indigo-200 group-hover/section:text-indigo-100 text-base md:text-lg transition-colors duration-300">
</h4>
</div>
<CardDescription className="text-slate-200 group-hover/section:text-slate-100 text-sm md:text-base leading-relaxed font-medium transition-colors duration-300">
{wish.expectedEffect}
</CardDescription>
</div>
</div>
)}
{/* 共鳴支持區塊 - 新增 */}
<div className="relative overflow-hidden rounded-lg md:rounded-xl bg-gradient-to-r from-pink-800/30 to-rose-800/30 border border-pink-600/40 p-3 md:p-4 backdrop-blur-sm transition-all duration-300">
<div className="absolute inset-0 bg-gradient-to-r from-pink-500/10 to-rose-500/10"></div>
<div className="relative flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="flex items-center gap-2">
<Users className="w-4 h-4 text-pink-300" />
<span className="text-sm md:text-base text-pink-200 font-medium">
{likeCount > 0 ? `${likeCount} 人也遇到相同問題` : "成為第一個表達支持的人"}
</span>
</div>
{likeCount > 0 && (
<div className="flex items-center gap-1">
{[...Array(Math.min(likeCount, 5))].map((_, i) => (
<Heart
key={i}
className="w-3 h-3 text-pink-400 animate-pulse"
fill="currentColor"
style={{ animationDelay: `${i * 0.2}s` }}
/>
))}
{likeCount > 5 && <span className="text-xs text-pink-300 ml-1">+{likeCount - 5}</span>}
</div>
)}
</div>
<Button
onClick={handleLike}
disabled={hasLiked || isLiking}
size="sm"
className={`
transition-all duration-300 transform hover:scale-105 px-3 md:px-4 py-2
${
hasLiked
? "bg-pink-600/50 text-pink-200 border border-pink-500/50 cursor-not-allowed"
: "bg-gradient-to-r from-pink-500/80 to-rose-600/80 hover:from-pink-600/90 hover:to-rose-700/90 text-white shadow-lg shadow-pink-500/25"
}
${isLiking ? "animate-pulse" : ""}
`}
>
<Heart
className={`w-3 h-3 md:w-4 md:h-4 mr-1.5 md:mr-2 transition-all duration-300 ${
hasLiked ? "text-pink-300" : "text-white"
} ${isLiking ? "animate-bounce" : ""}`}
fill={hasLiked ? "currentColor" : "none"}
/>
<span className="text-xs md:text-sm font-medium">
{isLiking ? "支持中..." : hasLiked ? "已支持" : "我也是"}
</span>
</Button>
</div>
{hasLiked && (
<div className="mt-2 pt-2 border-t border-pink-600/30">
<p className="text-xs text-pink-300 text-center animate-in fade-in duration-500">
💝
</p>
</div>
)}
</div>
{/* AI 解決方案建議區塊 - 改用藍紫色系 */}
{solutionRecommendation.recommendations.length > 0 && (
<div className="relative overflow-hidden rounded-lg md:rounded-xl bg-gradient-to-r from-indigo-800/50 to-purple-800/50 border border-indigo-500/60 p-4 md:p-5 backdrop-blur-sm transition-all duration-300 shadow-lg shadow-indigo-500/20">
<div className="absolute inset-0 bg-gradient-to-r from-indigo-500/15 to-purple-500/15"></div>
<div className="relative">
<div className="flex items-center justify-between mb-3 md:mb-4">
<div className="flex items-center gap-2 md:gap-3">
<div className="w-7 h-7 md:w-8 md:h-8 rounded-full bg-gradient-to-br from-indigo-400 to-purple-500 flex items-center justify-center shadow-lg shadow-indigo-500/30">
<Sparkles className="w-3.5 h-3.5 md:w-4 md:h-4 text-white animate-pulse" />
</div>
<div>
<h4 className="font-semibold text-white text-base md:text-lg">AI </h4>
<div className="flex items-center gap-2 mt-1">
<Badge className="bg-indigo-500/30 text-indigo-100 border border-indigo-400/50 text-xs px-2 py-0.5 font-medium">
{solutionRecommendation.confidence}%
</Badge>
<Badge className="bg-purple-500/30 text-purple-100 border border-purple-400/50 text-xs px-2 py-0.5 font-medium">
</Badge>
</div>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => setShowSolutions(!showSolutions)}
className="text-indigo-200 hover:text-white hover:bg-indigo-700/50 px-2 transition-all duration-200"
>
{showSolutions ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
</Button>
</div>
{/* 個人化訊息 */}
<div className="mb-4 p-3 bg-slate-800/60 rounded-lg border border-slate-600/50">
<p className="text-slate-100 text-sm md:text-base leading-relaxed whitespace-pre-line">
{solutionRecommendation.personalizedMessage}
</p>
</div>
{/* 解決方案建議 */}
{showSolutions && (
<div className="space-y-3 animate-in slide-in-from-top-2 duration-300">
{solutionRecommendation.recommendations.map((solution, index) => (
<div
key={solution.id}
className="p-3 md:p-4 bg-slate-800/60 rounded-lg border border-slate-600/50 hover:bg-slate-700/60 hover:border-slate-500/70 transition-all duration-200 cursor-pointer"
onClick={() => setSelectedSolution(selectedSolution?.id === solution.id ? null : solution)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="text-xl md:text-2xl">{solution.icon}</div>
<div>
<div className="flex items-center gap-2 mb-1">
<h5 className="font-semibold text-white text-sm md:text-base">{solution.name}</h5>
<Badge
className={`text-xs px-2 py-0.5 border ${getDifficultyColor(solution.difficulty)}`}
>
{getDifficultyLabel(solution.difficulty)}
</Badge>
</div>
<p className="text-slate-300 text-xs md:text-sm">{solution.description}</p>
</div>
</div>
<div className="flex items-center gap-2 text-xs text-slate-400">
<Clock className="w-3 h-3" />
<span className="font-medium">{solution.timeframe}</span>
</div>
</div>
{/* 展開的詳細資訊 */}
{selectedSolution?.id === solution.id && (
<div className="mt-4 pt-4 border-t border-slate-600/40 space-y-3 animate-in slide-in-from-top-1 duration-200">
<div>
<h6 className="text-sm font-semibold text-cyan-300 mb-2 flex items-center gap-1">
</h6>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{solution.benefits.map((benefit, idx) => (
<div key={idx} className="flex items-center gap-2 text-xs text-slate-200">
<div className="w-1.5 h-1.5 bg-cyan-400 rounded-full flex-shrink-0"></div>
{benefit}
</div>
))}
</div>
</div>
<div>
<h6 className="text-sm font-semibold text-blue-300 mb-2 flex items-center gap-1">
🛠
</h6>
<div className="flex flex-wrap gap-1">
{solution.techStack.map((tech, idx) => (
<Badge
key={idx}
variant="secondary"
className="text-xs px-2 py-0.5 bg-blue-500/20 text-blue-200 border border-blue-400/30"
>
{tech}
</Badge>
))}
</div>
</div>
<div>
<h6 className="text-sm font-semibold text-yellow-300 mb-2 flex items-center gap-1">
💡
</h6>
<div className="space-y-1">
{solution.examples.map((example, idx) => (
<div key={idx} className="text-xs text-slate-200 flex items-center gap-2">
<div className="w-1 h-1 bg-yellow-400 rounded-full flex-shrink-0"></div>
{example}
</div>
))}
</div>
</div>
</div>
)}
</div>
))}
{/* 專業團隊協助訊息 */}
<div className="mt-4 p-3 bg-gradient-to-r from-cyan-800/40 to-blue-800/40 rounded-lg border border-cyan-500/50">
<div className="flex items-center gap-2 mb-2">
<Zap className="w-4 h-4 text-cyan-300" />
<span className="text-sm font-semibold text-cyan-200"></span>
</div>
<p className="text-xs md:text-sm text-cyan-100 leading-relaxed">
AI
</p>
</div>
</div>
)}
</div>
</div>
)}
</CardContent>
{/* 底部裝飾 */}
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-cyan-400/50 to-transparent"></div>
</Card>
)
}