This commit is contained in:
MarkLo 2025-12-01 01:54:44 +08:00
parent d21432dad7
commit e1eaf964f6
7 changed files with 2657 additions and 185 deletions

View File

@ -44,11 +44,13 @@ export default function AnalysisPage() {
};
return (
<div className="container mx-auto px-4 py-12">
<div className="max-w-6xl mx-auto space-y-8">
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-purple-50 to-pink-50 dark:from-gray-900 dark:via-purple-900/20 dark:to-blue-900/20">
<div className="container mx-auto px-4 py-12">
<div className="max-w-6xl mx-auto space-y-8">
{/* 標題區域 - 置中對齊 */}
<div className="text-center">
<h1 className="text-4xl font-bold mb-2"></h1>
<div className="text-center relative">
<div className="absolute inset-0 gradient-bg-radial opacity-40 -z-10" />
<h1 className="text-4xl font-bold mb-2 gradient-text-primary"></h1>
<p className="text-gray-600 dark:text-gray-400">
</p>
@ -66,6 +68,7 @@ export default function AnalysisPage() {
<p className="text-red-600 dark:text-red-400">{error}</p>
</div>
)}
</div>
</div>
</div>
);

View File

@ -129,12 +129,14 @@ export default function AnalysisResultsPage() {
const currentReport = getNestedValue(analysisResult.reports, currentAnalyst?.reportKey || "");
return (
<div className="container mx-auto px-4 py-12">
<div className="max-w-7xl mx-auto space-y-8">
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-purple-50 to-pink-50 dark:from-gray-900 dark:via-purple-900/20 dark:to-blue-900/20">
<div className="container mx-auto px-4 py-12">
<div className="max-w-7xl mx-auto space-y-8">
{/* Header */}
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4 animate-fade-in">
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4 animate-fade-in relative">
<div className="absolute inset-0 gradient-bg-radial opacity-30 -z-10 rounded-lg" />
<div>
<h1 className="text-4xl font-bold mb-2">
<h1 className="text-4xl font-bold mb-2 gradient-text-primary">
{analysisResult.ticker}
</h1>
<p className="text-gray-600 dark:text-gray-400">
@ -178,7 +180,7 @@ export default function AnalysisResultsPage() {
)}
{/* 分析師報告 */}
<Card className="animate-scale-up hover-lift">
<Card className="animate-scale-up hover-lift gradient-card gradient-shine">
<CardHeader>
<CardTitle>{analyst.label} </CardTitle>
<CardDescription>
@ -219,6 +221,7 @@ export default function AnalysisResultsPage() {
reports={analysisResult.reports}
/>
)}
</div>
</div>
</div>
);

View File

@ -252,3 +252,282 @@
background-size: 200% auto;
animation: shimmer 3s linear infinite;
}
/* ========================================
GRADIENT UTILITIES
======================================== */
/* Gradient Backgrounds */
.gradient-bg-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.dark .gradient-bg-primary {
background: linear-gradient(135deg, #4c51bf 0%, #553c9a 100%);
}
.gradient-bg-secondary {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.dark .gradient-bg-secondary {
background: linear-gradient(135deg, #c471ed 0%, #c94b4b 100%);
}
.gradient-bg-accent {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.dark .gradient-bg-accent {
background: linear-gradient(135deg, #3b82f6 0%, #06b6d4 100%);
}
.gradient-bg-radial {
background: radial-gradient(
circle at top right,
rgba(102, 126, 234, 0.15),
transparent 50%
),
radial-gradient(
circle at bottom left,
rgba(118, 75, 162, 0.15),
transparent 50%
);
}
.dark .gradient-bg-radial {
background: radial-gradient(
circle at top right,
rgba(76, 81, 191, 0.2),
transparent 50%
),
radial-gradient(
circle at bottom left,
rgba(85, 60, 154, 0.2),
transparent 50%
);
}
.gradient-bg-mesh {
background: linear-gradient(
135deg,
#667eea 0%,
#764ba2 25%,
#f093fb 50%,
#f5576c 75%,
#4facfe 100%
);
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
}
.dark .gradient-bg-mesh {
background: linear-gradient(
135deg,
#4c51bf 0%,
#553c9a 25%,
#c471ed 50%,
#c94b4b 75%,
#3b82f6 100%
);
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
}
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* Gradient Page Backgrounds - Enhanced visibility */
.gradient-page-bg {
background: linear-gradient(135deg,
rgba(102, 126, 234, 0.12) 0%,
rgba(240, 242, 255, 0.8) 25%,
rgba(255, 240, 245, 0.8) 50%,
rgba(245, 240, 255, 0.8) 75%,
rgba(118, 75, 162, 0.12) 100%
);
min-height: 100vh;
}
.dark .gradient-page-bg {
background: linear-gradient(135deg,
rgba(76, 81, 191, 0.25) 0%,
rgba(30, 30, 45, 0.95) 25%,
rgba(40, 30, 45, 0.95) 50%,
rgba(35, 30, 50, 0.95) 75%,
rgba(85, 60, 154, 0.25) 100%
);
min-height: 100vh;
}
/* Gradient Borders */
.gradient-border {
position: relative;
border: 2px solid transparent;
background: linear-gradient(var(--background), var(--background)) padding-box,
linear-gradient(135deg, #667eea, #764ba2) border-box;
}
.dark .gradient-border {
background: linear-gradient(var(--background), var(--background)) padding-box,
linear-gradient(135deg, #4c51bf, #553c9a) border-box;
}
.gradient-border-animated {
position: relative;
border: 2px solid transparent;
background: linear-gradient(var(--background), var(--background)) padding-box,
linear-gradient(135deg, #667eea, #764ba2, #f093fb, #667eea) border-box;
background-size: 300% 300%;
animation: gradientBorder 4s ease infinite;
}
.dark .gradient-border-animated {
background: linear-gradient(var(--background), var(--background)) padding-box,
linear-gradient(135deg, #4c51bf, #553c9a, #c471ed, #4c51bf) border-box;
background-size: 300% 300%;
animation: gradientBorder 4s ease infinite;
}
@keyframes gradientBorder {
0%,
100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
/* Gradient Text */
.gradient-text-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.gradient-text-secondary {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.gradient-text-accent {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Gradient Overlays */
.gradient-overlay {
position: relative;
}
.gradient-overlay::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
135deg,
rgba(102, 126, 234, 0.1),
rgba(118, 75, 162, 0.1)
);
pointer-events: none;
border-radius: inherit;
}
.dark .gradient-overlay::before {
background: linear-gradient(
135deg,
rgba(76, 81, 191, 0.15),
rgba(85, 60, 154, 0.15)
);
}
/* Gradient Shine Effect */
.gradient-shine {
position: relative;
overflow: hidden;
}
.gradient-shine::after {
content: "";
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(
45deg,
transparent 30%,
rgba(255, 255, 255, 0.1) 50%,
transparent 70%
);
transform: translateX(-100%) translateY(-100%);
transition: transform 0.6s;
}
.gradient-shine:hover::after {
transform: translateX(100%) translateY(100%);
}
.dark .gradient-shine::after {
background: linear-gradient(
45deg,
transparent 30%,
rgba(255, 255, 255, 0.05) 50%,
transparent 70%
);
}
/* Enhanced Card Gradients */
.gradient-card {
background: linear-gradient(
135deg,
rgba(102, 126, 234, 0.05) 0%,
rgba(118, 75, 162, 0.05) 100%
);
border: 1px solid rgba(102, 126, 234, 0.2);
transition: all 0.3s ease;
}
.gradient-card:hover {
background: linear-gradient(
135deg,
rgba(102, 126, 234, 0.1) 0%,
rgba(118, 75, 162, 0.1) 100%
);
border-color: rgba(102, 126, 234, 0.4);
box-shadow: 0 10px 40px rgba(102, 126, 234, 0.2);
}
.dark .gradient-card {
background: linear-gradient(
135deg,
rgba(76, 81, 191, 0.1) 0%,
rgba(85, 60, 154, 0.1) 100%
);
border-color: rgba(76, 81, 191, 0.3);
}
.dark .gradient-card:hover {
background: linear-gradient(
135deg,
rgba(76, 81, 191, 0.15) 0%,
rgba(85, 60, 154, 0.15) 100%
);
border-color: rgba(76, 81, 191, 0.5);
box-shadow: 0 10px 40px rgba(76, 81, 191, 0.3);
}

View File

@ -23,7 +23,7 @@ export default function RootLayout({
<body className={inter.className}>
<ThemeProvider>
<AnalysisProvider>
<div className="flex flex-col min-h-screen">
<div className="flex flex-col min-h-screen gradient-page-bg">
<Header />
<main className="flex-1">{children}</main>
<Footer />

View File

@ -11,10 +11,12 @@ import {
export default function HomePage() {
return (
<div className="container mx-auto px-4 py-12">
{/* Hero Section */}
<div className="text-center mb-16 animate-fade-in">
<h1 className="text-5xl font-bold mb-4 bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-purple-50 to-pink-50 dark:from-gray-900 dark:via-purple-900/20 dark:to-blue-900/20">
<div className="container mx-auto px-4 py-12">
{/* Hero Section */}
<div className="text-center mb-16 animate-fade-in relative py-8">
<div className="absolute inset-0 gradient-bg-radial -z-10" />
<h1 className="text-5xl md:text-6xl font-bold mb-6 gradient-text-primary leading-tight">
TradingAgentsX
</h1>
<p className="text-xl text-gray-600 dark:text-gray-400 mb-8 max-w-2xl mx-auto">
@ -24,7 +26,7 @@ export default function HomePage() {
<Link href="/analysis">
<Button
size="lg"
className="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700"
className="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 shadow-lg hover:shadow-xl transition-shadow"
>
</Button>
@ -324,20 +326,22 @@ export default function HomePage() {
</div>
{/* Call to Action Section */}
<div className="text-center py-16">
<h2 className="text-3xl font-bold mb-4"></h2>
<div className="text-center py-16 relative">
<div className="absolute inset-0 gradient-bg-radial opacity-60 -z-10" />
<h2 className="text-3xl font-bold mb-4 gradient-text-primary"></h2>
<p className="text-lg text-gray-600 dark:text-gray-400 mb-8 max-w-2xl mx-auto">
12 AI
</p>
<Link href="/analysis">
<Button
size="lg"
className="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-lg px-8 py-6"
className="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-lg px-8 py-6 shadow-lg hover:shadow-2xl transition-all"
>
</Button>
</Link>
</div>
</div>
</div>
);
}
@ -352,7 +356,7 @@ function FeatureCard({
icon: string;
}) {
return (
<Card className="hover-lift animate-slide-up">
<Card className="hover-lift animate-slide-up gradient-shine gradient-card">
<CardHeader>
<div className="text-4xl mb-2">{icon}</div>
<CardTitle className="text-lg">{title}</CardTitle>
@ -376,7 +380,7 @@ function AgentCard({
responsibilities: string[];
}) {
return (
<Card className="hover-lift animate-scale-up">
<Card className="hover-lift animate-scale-up gradient-card">
<CardHeader>
<CardTitle className="text-base">{name}</CardTitle>
<CardDescription className="text-xs">{role}</CardDescription>
@ -417,7 +421,7 @@ function LLMProviderCard({
const logoSrc = logoMap[name];
return (
<Card className="hover-lift animate-slide-up animate-delay-100">
<Card className="hover-lift animate-slide-up animate-delay-100 gradient-card gradient-shine">
<CardHeader>
<div className="flex items-center gap-3">
{logoSrc ? (
@ -458,7 +462,7 @@ function TechnicalCard({
features: string[];
}) {
return (
<Card className="hover-lift animate-slide-up animate-delay-300">
<Card className="hover-lift animate-slide-up animate-delay-300 gradient-card">
<CardHeader>
<CardTitle className="text-lg">{title}</CardTitle>
</CardHeader>

View File

@ -109,6 +109,7 @@ export function PriceChart({ priceData, priceStats, ticker }: PriceChartProps) {
/>
</LineChart>
) : (
// 真正的K線圖蠟燭圖
<ComposedChart data={priceData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
@ -121,41 +122,89 @@ export function PriceChart({ priceData, priceStats, ticker }: PriceChartProps) {
tickFormatter={(value) => `$${value.toFixed(0)}`}
/>
<Tooltip
formatter={(value: number, name: string) => [`$${formatNumber(value)}`, name]}
labelFormatter={(label) => `日期: ${label}`}
content={({ active, payload }) => {
if (active && payload && payload.length) {
const data = payload[0].payload;
return (
<div className="bg-background border border-border p-3 rounded-lg shadow-lg">
<p className="text-sm font-semibold mb-2">: {data.Date}</p>
<div className="space-y-1 text-sm">
<p className="text-green-600">: ${formatNumber(data.Open)}</p>
<p className="text-red-600">: ${formatNumber(data.Close)}</p>
<p className="text-blue-600">: ${formatNumber(data.High)}</p>
<p className="text-orange-600">: ${formatNumber(data.Low)}</p>
</div>
</div>
);
}
return null;
}}
/>
<Legend />
<Area
type="monotone"
dataKey="High"
stroke="#86efac"
fill="#86efac"
fillOpacity={0.3}
name="最高價"
/>
<Area
type="monotone"
dataKey="Low"
stroke="#fca5a5"
fill="#fca5a5"
fillOpacity={0.3}
name="最低價"
/>
<Line
type="monotone"
dataKey="Open"
stroke="#f59e0b"
strokeWidth={2}
name="開盤價"
dot={false}
/>
<Line
type="monotone"
dataKey="Close"
stroke="#2563eb"
strokeWidth={2}
name="收盤價"
dot={false}
<Bar
dataKey="Close"
shape={(props: any) => {
const { x, y, width, payload } = props;
const { Open, Close, High, Low } = payload;
// 計算Y軸的比例尺
const yAxis = props.yAxis;
const yScale = (value: number) => {
const { domain, height } = yAxis;
const [min, max] = domain;
return height - ((value - min) / (max - min)) * height;
};
const openY = yScale(Open);
const closeY = yScale(Close);
const highY = yScale(High);
const lowY = yScale(Low);
// 判斷漲跌
const isUp = Close >= Open;
const color = isUp ? '#22c55e' : '#ef4444'; // 綠色上漲,紅色下跌
const fillColor = isUp ? '#22c55e' : '#ef4444';
// K線寬度
const candleWidth = Math.min(width * 0.6, 8);
const centerX = x + width / 2;
// 實體高度
const bodyHeight = Math.abs(closeY - openY);
const bodyY = Math.min(openY, closeY);
return (
<g>
{/* 上影線 */}
<line
x1={centerX}
y1={highY}
x2={centerX}
y2={Math.min(openY, closeY)}
stroke={color}
strokeWidth={1}
/>
{/* 下影線 */}
<line
x1={centerX}
y1={Math.max(openY, closeY)}
x2={centerX}
y2={lowY}
stroke={color}
strokeWidth={1}
/>
{/* K線實體 */}
<rect
x={centerX - candleWidth / 2}
y={bodyY}
width={candleWidth}
height={bodyHeight || 1} // 至少1px高度避免十字星消失
fill={fillColor}
stroke={color}
strokeWidth={1}
/>
</g>
);
}}
/>
</ComposedChart>
)}

File diff suppressed because it is too large Load Diff