Add FE dashboard
This commit is contained in:
parent
22616e8bdb
commit
6b833e6942
|
|
@ -0,0 +1,41 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
|
|
@ -0,0 +1,140 @@
|
|||
@import "tailwindcss";
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
THEME TOKENS
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
:root {
|
||||
--bg-void: #08090c;
|
||||
--bg-surface: #0e1015;
|
||||
--bg-elevated: #14161d;
|
||||
--bg-panel: #1a1d27;
|
||||
--bg-panel-hover: #1f2230;
|
||||
|
||||
--border-subtle: rgba(255, 255, 255, 0.04);
|
||||
--border-medium: rgba(255, 255, 255, 0.08);
|
||||
--border-accent: rgba(212, 175, 55, 0.2);
|
||||
|
||||
--text-primary: #e8e6e1;
|
||||
--text-secondary: #8a8780;
|
||||
--text-tertiary: #5a5853;
|
||||
|
||||
--amber: #d4af37;
|
||||
--amber-dim: rgba(212, 175, 55, 0.15);
|
||||
--amber-glow: rgba(212, 175, 55, 0.06);
|
||||
--cyan: #4ecdc4;
|
||||
--cyan-dim: rgba(78, 205, 196, 0.12);
|
||||
--red: #e74c3c;
|
||||
--red-dim: rgba(231, 76, 60, 0.12);
|
||||
--green: #2ecc71;
|
||||
--green-dim: rgba(46, 204, 113, 0.12);
|
||||
--blue: #5b9bd5;
|
||||
--blue-dim: rgba(91, 155, 213, 0.12);
|
||||
--purple: #9b59b6;
|
||||
--purple-dim: rgba(155, 89, 182, 0.12);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-bg-void: var(--bg-void);
|
||||
--color-bg-surface: var(--bg-surface);
|
||||
--color-bg-elevated: var(--bg-elevated);
|
||||
--color-bg-panel: var(--bg-panel);
|
||||
|
||||
--color-border-subtle: var(--border-subtle);
|
||||
--color-border-medium: var(--border-medium);
|
||||
--color-border-accent: var(--border-accent);
|
||||
|
||||
--color-text-primary: var(--text-primary);
|
||||
--color-text-secondary: var(--text-secondary);
|
||||
--color-text-tertiary: var(--text-tertiary);
|
||||
|
||||
--color-amber: var(--amber);
|
||||
--color-amber-dim: var(--amber-dim);
|
||||
--color-amber-glow: var(--amber-glow);
|
||||
--color-cyan: var(--cyan);
|
||||
--color-cyan-dim: var(--cyan-dim);
|
||||
--color-red: var(--red);
|
||||
--color-red-dim: var(--red-dim);
|
||||
--color-green: var(--green);
|
||||
--color-green-dim: var(--green-dim);
|
||||
--color-blue: var(--blue);
|
||||
--color-blue-dim: var(--blue-dim);
|
||||
--color-purple: var(--purple);
|
||||
--color-purple-dim: var(--purple-dim);
|
||||
|
||||
--font-display: var(--font-syne);
|
||||
--font-serif: var(--font-instrument-serif);
|
||||
--font-mono: var(--font-dm-mono);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
BASE STYLES
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
html {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg-void);
|
||||
color: var(--text-primary);
|
||||
font-family: var(--font-mono);
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Atmosphere */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(ellipse 80% 50% at 20% 10%, rgba(212, 175, 55, 0.03), transparent),
|
||||
radial-gradient(ellipse 60% 40% at 80% 80%, rgba(78, 205, 196, 0.02), transparent);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Scanlines */
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent,
|
||||
transparent 2px,
|
||||
rgba(0, 0, 0, 0.02) 2px,
|
||||
rgba(0, 0, 0, 0.02) 4px
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar { width: 4px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: var(--border-medium); border-radius: 2px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--text-tertiary); }
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
ANIMATIONS
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
@keyframes pulse-dot {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
}
|
||||
|
||||
@keyframes node-glow {
|
||||
0%, 100% { box-shadow: 0 0 15px var(--amber-dim); }
|
||||
50% { box-shadow: 0 0 30px rgba(212, 175, 55, 0.2); }
|
||||
}
|
||||
|
||||
@keyframes data-pulse {
|
||||
0%, 100% { opacity: 1; transform: translateY(-50%) scale(1); }
|
||||
50% { opacity: 0.3; transform: translateY(-50%) scale(1.5); }
|
||||
}
|
||||
|
||||
@keyframes ticker-scroll {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import type { Metadata } from "next";
|
||||
import { Syne, Instrument_Serif, DM_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const syne = Syne({
|
||||
variable: "--font-syne",
|
||||
subsets: ["latin"],
|
||||
weight: ["400", "500", "600", "700", "800"],
|
||||
});
|
||||
|
||||
const instrumentSerif = Instrument_Serif({
|
||||
variable: "--font-instrument-serif",
|
||||
subsets: ["latin"],
|
||||
weight: "400",
|
||||
style: ["normal", "italic"],
|
||||
});
|
||||
|
||||
const dmMono = DM_Mono({
|
||||
variable: "--font-dm-mono",
|
||||
subsets: ["latin"],
|
||||
weight: ["300", "400", "500"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "TradingAgents — Multi-Agent Trading Intelligence",
|
||||
description:
|
||||
"Multi-agent LLM framework that mirrors real-world trading firm dynamics for AI-powered market analysis and trading decisions.",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${syne.variable} ${instrumentSerif.variable} ${dmMono.variable}`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import TickerBar from "@/components/TickerBar";
|
||||
import TopBar from "@/components/TopBar";
|
||||
import AgentPipeline from "@/components/AgentPipeline";
|
||||
import MarketOverview from "@/components/MarketOverview";
|
||||
import AgentStatus from "@/components/AgentStatus";
|
||||
import AnalystGrid from "@/components/AnalystGrid";
|
||||
import DebatePanel from "@/components/DebatePanel";
|
||||
import DecisionPanel from "@/components/DecisionPanel";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="relative z-1 min-h-screen px-5 pb-5 max-w-[1600px] mx-auto">
|
||||
<TickerBar />
|
||||
<TopBar />
|
||||
<AgentPipeline />
|
||||
|
||||
<main className="grid grid-cols-[1fr_1fr_340px] gap-3">
|
||||
{/* Row 1: Market Overview + Agent Status */}
|
||||
<MarketOverview />
|
||||
<AgentStatus />
|
||||
|
||||
{/* Row 2: Analyst Grid + Debate Panel (spans 2 rows) */}
|
||||
<AnalystGrid />
|
||||
<DebatePanel />
|
||||
|
||||
{/* Row 3: Decision Panel */}
|
||||
<DecisionPanel />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
type NodeState = "complete" | "active" | "pending";
|
||||
|
||||
interface PipelineNode {
|
||||
icon: string;
|
||||
label: string;
|
||||
state: NodeState;
|
||||
}
|
||||
|
||||
interface PipelineStage {
|
||||
group: string;
|
||||
nodes: PipelineNode[];
|
||||
}
|
||||
|
||||
const stages: PipelineStage[] = [
|
||||
{
|
||||
group: "Analysts",
|
||||
nodes: [
|
||||
{ icon: "\u{1F4CA}", label: "Market", state: "complete" },
|
||||
{ icon: "\u{1F4CB}", label: "Fundamentals", state: "complete" },
|
||||
{ icon: "\u{1F4F0}", label: "News", state: "complete" },
|
||||
{ icon: "\u{1F4AC}", label: "Social", state: "complete" },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: "Debate",
|
||||
nodes: [
|
||||
{ icon: "\u{1F402}", label: "Bull", state: "active" },
|
||||
{ icon: "\u{1F43B}", label: "Bear", state: "active" },
|
||||
{ icon: "\u2696\uFE0F", label: "Judge", state: "pending" },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: "Execution",
|
||||
nodes: [{ icon: "\u{1F4B9}", label: "Trader", state: "pending" }],
|
||||
},
|
||||
{
|
||||
group: "Risk",
|
||||
nodes: [
|
||||
{ icon: "\u{1F525}", label: "Aggressive", state: "pending" },
|
||||
{ icon: "\u{1F6E1}\uFE0F", label: "Conservative", state: "pending" },
|
||||
{ icon: "\u2696\uFE0F", label: "Neutral", state: "pending" },
|
||||
{ icon: "\u{1F3DB}\uFE0F", label: "Risk Judge", state: "pending" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function NodeIcon({ node }: { node: PipelineNode }) {
|
||||
const stateClasses: Record<NodeState, string> = {
|
||||
complete: "bg-green-dim border-green",
|
||||
active: "bg-amber-dim border-amber",
|
||||
pending: "bg-bg-elevated border-border-medium",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-1.5 px-2 py-1 group cursor-default">
|
||||
<div
|
||||
className={`w-10 h-10 rounded-[10px] grid place-items-center text-base
|
||||
border transition-all duration-400 ${stateClasses[node.state]}
|
||||
group-hover:-translate-y-0.5 group-hover:border-amber group-hover:shadow-[0_4px_20px_var(--amber-dim)]`}
|
||||
style={
|
||||
node.state === "active"
|
||||
? { animation: "node-glow 2s ease-in-out infinite" }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{node.icon}
|
||||
</div>
|
||||
<span
|
||||
className={`text-[9px] uppercase tracking-[1.5px] whitespace-nowrap
|
||||
${node.state === "active" ? "text-amber" : node.state === "complete" ? "text-green" : "text-text-tertiary"}`}
|
||||
>
|
||||
{node.label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Connector({ active }: { active: boolean }) {
|
||||
return (
|
||||
<div className="relative">
|
||||
<div
|
||||
className={`h-px transition-all duration-500 ${
|
||||
active
|
||||
? "bg-gradient-to-r from-amber to-amber-dim h-0.5 shadow-[0_0_8px_var(--amber-dim)]"
|
||||
: "bg-border-medium"
|
||||
}`}
|
||||
style={{ width: 20 }}
|
||||
/>
|
||||
{active && (
|
||||
<div
|
||||
className="absolute right-[-2px] top-1/2 w-1 h-1 bg-amber rounded-full"
|
||||
style={{
|
||||
transform: "translateY(-50%)",
|
||||
animation: "data-pulse 1.5s ease-in-out infinite",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AgentPipeline() {
|
||||
return (
|
||||
<motion.section
|
||||
initial={{ opacity: 0, y: -8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.1, ease: [0.22, 1, 0.36, 1] }}
|
||||
className="flex items-center justify-center gap-0 py-7 overflow-x-auto"
|
||||
>
|
||||
{stages.map((stage, si) => (
|
||||
<div key={stage.group} className="flex items-center">
|
||||
{si > 0 && (
|
||||
<div className="mx-1">
|
||||
<div
|
||||
className={`h-px ${
|
||||
stages[si - 1].nodes.every((n) => n.state === "complete")
|
||||
? "bg-gradient-to-r from-amber to-amber-dim h-0.5 shadow-[0_0_8px_var(--amber-dim)]"
|
||||
: "bg-border-medium"
|
||||
}`}
|
||||
style={{ width: 40 }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="relative flex items-center px-2.5 py-1.5 border border-border-subtle rounded-xl bg-bg-surface gap-1">
|
||||
<span className="absolute -top-2 left-3 text-[8px] text-text-tertiary uppercase tracking-[2px] bg-bg-surface px-1.5">
|
||||
{stage.group}
|
||||
</span>
|
||||
{stage.nodes.map((node, ni) => (
|
||||
<div key={node.label} className="flex items-center">
|
||||
{ni > 0 && (
|
||||
<Connector
|
||||
active={
|
||||
stage.nodes[ni - 1].state === "complete" ||
|
||||
stage.nodes[ni - 1].state === "active"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<NodeIcon node={node} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</motion.section>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
"use client";
|
||||
|
||||
import Panel from "./ui/Panel";
|
||||
|
||||
const agents = [
|
||||
{ icon: "\u{1F4CA}", label: "Market Analyst", detail: "RSI, MACD, Bollinger analysis complete", status: "Done", color: "cyan" as const },
|
||||
{ icon: "\u{1F4CB}", label: "Fundamentals Analyst", detail: "Balance sheet & cash flow reviewed", status: "Done", color: "amber" as const },
|
||||
{ icon: "\u{1F4F0}", label: "News Analyst", detail: "Global news & macro scan complete", status: "Done", color: "cyan" as const },
|
||||
{ icon: "\u{1F4AC}", label: "Social Media Analyst", detail: "Sentiment scoring finalized", status: "Done", color: "purple" as const },
|
||||
{ icon: "\u{1F402}", label: "Bull Researcher", detail: "Argument round 1 submitted", status: "Active", color: "green" as const },
|
||||
{ icon: "\u{1F43B}", label: "Bear Researcher", detail: "Counter-argument pending", status: "Waiting", color: "amber" as const },
|
||||
{ icon: "\u2696\uFE0F", label: "Trader", detail: "Awaiting debate conclusion", status: "Idle", color: "amber" as const },
|
||||
{ icon: "\u{1F6E1}\uFE0F", label: "Risk Manager", detail: "Awaiting trade proposal", status: "Idle", color: "amber" as const },
|
||||
];
|
||||
|
||||
const logs = [
|
||||
{ time: "14:32:08", agent: "MKT", agentType: "analyst" as const, msg: "Technical indicators computed \u2014 MACD bullish cross detected" },
|
||||
{ time: "14:32:15", agent: "FND", agentType: "analyst" as const, msg: "Balance sheet analysis complete \u2014 strong cash position" },
|
||||
{ time: "14:32:22", agent: "NWS", agentType: "analyst" as const, msg: "Processed 47 news articles \u2014 net positive sentiment" },
|
||||
{ time: "14:32:28", agent: "SOC", agentType: "analyst" as const, msg: "Social sentiment score: 0.62 \u2014 mixed retail signals" },
|
||||
{ time: "14:32:35", agent: "BULL", agentType: "researcher" as const, msg: "Opening argument submitted \u2014 AI infrastructure thesis" },
|
||||
{ time: "14:32:42", agent: "BEAR", agentType: "researcher" as const, msg: "Counter-argument: valuation stretched at 65x forward" },
|
||||
];
|
||||
|
||||
const iconBgMap = {
|
||||
cyan: "bg-cyan-dim",
|
||||
amber: "bg-amber-dim",
|
||||
green: "bg-green-dim",
|
||||
purple: "bg-purple-dim",
|
||||
};
|
||||
|
||||
const statusColorMap: Record<string, string> = {
|
||||
Done: "text-green",
|
||||
Active: "text-amber",
|
||||
Waiting: "text-text-tertiary",
|
||||
Idle: "text-text-tertiary",
|
||||
};
|
||||
|
||||
const agentTypeColorMap = {
|
||||
analyst: "text-cyan",
|
||||
researcher: "text-amber",
|
||||
trader: "text-green",
|
||||
risk: "text-red",
|
||||
};
|
||||
|
||||
export default function AgentStatus() {
|
||||
return (
|
||||
<Panel title="Agent Status" badge="8 Agents" badgeVariant="amber" delay={0.2}>
|
||||
<div className="flex flex-col gap-2">
|
||||
{agents.map((a) => (
|
||||
<div key={a.label} className="flex items-center gap-2.5 p-2 rounded bg-bg-elevated">
|
||||
<div className={`w-7 h-7 rounded-md grid place-items-center text-xs shrink-0 ${iconBgMap[a.color]}`}>
|
||||
{a.icon}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-[11px] text-text-primary font-medium">{a.label}</div>
|
||||
<div className="text-[10px] text-text-tertiary mt-px">{a.detail}</div>
|
||||
</div>
|
||||
<div className={`text-[10px] font-medium ${statusColorMap[a.status]}`}>{a.status}</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="mt-2 max-h-[150px] overflow-y-auto flex flex-col">
|
||||
{logs.map((l, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex gap-2 py-1.5 border-b border-border-subtle text-[10px]"
|
||||
>
|
||||
<span className="text-text-tertiary shrink-0 tabular-nums">{l.time}</span>
|
||||
<span className={`shrink-0 font-medium ${agentTypeColorMap[l.agentType]}`}>{l.agent}</span>
|
||||
<span className="text-text-secondary overflow-hidden text-ellipsis whitespace-nowrap">{l.msg}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import Panel from "./ui/Panel";
|
||||
|
||||
interface AnalystCard {
|
||||
name: string;
|
||||
role: string;
|
||||
avatarType: string;
|
||||
icon: string;
|
||||
signal: "bullish" | "bearish" | "neutral";
|
||||
signalText: string;
|
||||
metrics: { label: string; value: string }[];
|
||||
excerpt: string;
|
||||
}
|
||||
|
||||
const analysts: AnalystCard[] = [
|
||||
{
|
||||
name: "Market Analyst",
|
||||
role: "Technical Analysis",
|
||||
avatarType: "market",
|
||||
icon: "\u{1F4CA}",
|
||||
signal: "bullish",
|
||||
signalText: "Bullish \u2014 Uptrend Confirmed",
|
||||
metrics: [
|
||||
{ label: "Trend", value: "Strong Uptrend" },
|
||||
{ label: "Momentum", value: "Accelerating" },
|
||||
{ label: "Support", value: "$842.10" },
|
||||
],
|
||||
excerpt:
|
||||
"MACD crossed above signal line with increasing histogram bars. RSI at 67.4 shows room before overbought. Price above all major moving averages with expanding volume.",
|
||||
},
|
||||
{
|
||||
name: "Fundamentals",
|
||||
role: "Financial Analysis",
|
||||
avatarType: "fundamentals",
|
||||
icon: "\u{1F4CB}",
|
||||
signal: "bullish",
|
||||
signalText: "Bullish \u2014 Strong Financials",
|
||||
metrics: [
|
||||
{ label: "Revenue Growth", value: "+122% YoY" },
|
||||
{ label: "Gross Margin", value: "74.8%" },
|
||||
{ label: "Free Cash Flow", value: "$27.1B" },
|
||||
],
|
||||
excerpt:
|
||||
"Data center revenue surged 409% YoY driven by AI infrastructure demand. Operating margins expanding to 61.6%. Balance sheet shows $26B cash with manageable debt.",
|
||||
},
|
||||
{
|
||||
name: "News Analyst",
|
||||
role: "Macro & News",
|
||||
avatarType: "news",
|
||||
icon: "\u{1F4F0}",
|
||||
signal: "bullish",
|
||||
signalText: "Bullish \u2014 Favorable Headlines",
|
||||
metrics: [
|
||||
{ label: "Sentiment", value: "Positive (82%)" },
|
||||
{ label: "Key Events", value: "3 Catalysts" },
|
||||
{ label: "Risk Flags", value: "1 Moderate" },
|
||||
],
|
||||
excerpt:
|
||||
"New Blackwell GPU architecture receiving strong OEM adoption. Sovereign AI investments from multiple nations. Minor concern: potential China export restrictions.",
|
||||
},
|
||||
{
|
||||
name: "Social Media",
|
||||
role: "Sentiment Analysis",
|
||||
avatarType: "social",
|
||||
icon: "\u{1F4AC}",
|
||||
signal: "neutral",
|
||||
signalText: "Neutral \u2014 Mixed Signals",
|
||||
metrics: [
|
||||
{ label: "Overall Score", value: "0.62 / 1.0" },
|
||||
{ label: "Buzz Volume", value: "Very High" },
|
||||
{ label: "Inst. Sentiment", value: "Positive" },
|
||||
],
|
||||
excerpt:
|
||||
"Institutional sentiment strongly positive with multiple analyst upgrades. Retail shows FOMO \u2014 elevated put/call ratio suggests hedging. Insider selling noted.",
|
||||
},
|
||||
];
|
||||
|
||||
const avatarBg: Record<string, string> = {
|
||||
market: "bg-cyan-dim border-cyan/20",
|
||||
fundamentals: "bg-amber-dim border-amber/20",
|
||||
news: "bg-blue-dim border-blue/20",
|
||||
social: "bg-purple-dim border-purple/20",
|
||||
};
|
||||
|
||||
const signalStyle: Record<string, string> = {
|
||||
bullish: "bg-green-dim text-green",
|
||||
bearish: "bg-red-dim text-red",
|
||||
neutral: "bg-cyan-dim text-cyan",
|
||||
};
|
||||
|
||||
export default function AnalystGrid() {
|
||||
return (
|
||||
<Panel
|
||||
title="Analyst Reports"
|
||||
badge="Updated"
|
||||
badgeVariant="live"
|
||||
className="col-span-2"
|
||||
delay={0.25}
|
||||
>
|
||||
<div className="grid grid-cols-4 gap-2.5">
|
||||
{analysts.map((a, i) => (
|
||||
<motion.div
|
||||
key={a.name}
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
delay: 0.3 + i * 0.08,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
className="bg-bg-elevated border border-border-subtle rounded-lg p-3.5
|
||||
transition-all duration-300 cursor-default
|
||||
hover:border-border-medium hover:-translate-y-0.5 hover:shadow-[0_8px_30px_rgba(0,0,0,0.3)]"
|
||||
>
|
||||
<div className="flex items-center gap-2.5 mb-3">
|
||||
<div
|
||||
className={`w-8 h-8 rounded-lg grid place-items-center text-sm shrink-0 border ${avatarBg[a.avatarType]}`}
|
||||
>
|
||||
{a.icon}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-display font-semibold text-xs text-text-primary">
|
||||
{a.name}
|
||||
</div>
|
||||
<div className="text-[9px] text-text-tertiary uppercase tracking-[1px]">
|
||||
{a.role}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`flex items-center gap-1.5 mb-2.5 px-2.5 py-1.5 rounded text-[11px] font-medium ${signalStyle[a.signal]}`}
|
||||
>
|
||||
{a.signalText}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{a.metrics.map((m) => (
|
||||
<div key={m.label} className="flex justify-between items-center">
|
||||
<span className="text-[10px] text-text-tertiary">{m.label}</span>
|
||||
<span className="text-[11px] font-medium text-text-secondary">
|
||||
{m.value}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-2.5 pt-2.5 border-t border-border-subtle text-[11px] text-text-secondary leading-relaxed line-clamp-3">
|
||||
{a.excerpt}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
const messages = [
|
||||
{
|
||||
side: "bull" as const,
|
||||
author: "Bull Researcher",
|
||||
text: "NVDA demonstrates exceptional fundamentals with 122% revenue growth and industry-defining margins. The AI infrastructure buildout is in early innings \u2014 data center revenue alone grew 409% YoY. The MACD bullish crossover confirms technical strength.",
|
||||
},
|
||||
{
|
||||
side: "bear" as const,
|
||||
author: "Bear Researcher",
|
||||
text: "Valuation is stretched at 65x forward earnings with RSI approaching overbought territory. Insider selling has accelerated and China export restrictions pose material revenue risk. Social sentiment shows FOMO-driven buying \u2014 a contrarian red flag.",
|
||||
},
|
||||
{
|
||||
side: "bull" as const,
|
||||
author: "Bull Researcher",
|
||||
text: "The premium valuation is justified by a near-monopoly in AI accelerators. Blackwell architecture orders extend visibility through 2026. The addressable market is expanding \u2014 sovereign AI alone represents a $100B+ opportunity.",
|
||||
},
|
||||
{
|
||||
side: "bear" as const,
|
||||
author: "Bear Researcher",
|
||||
text: "Competition is intensifying from AMD MI300, Intel Gaudi, and custom silicon from Google, Amazon, and Microsoft. Customer concentration risk is real \u2014 top 4 hyperscalers represent 45% of data center revenue.",
|
||||
},
|
||||
{
|
||||
side: "judge" as const,
|
||||
author: "Research Manager",
|
||||
text: "RECOMMENDATION: BUY with conviction. Bull arguments regarding early-cycle AI infrastructure and expanding TAM outweigh near-term valuation concerns. Position sizing should reflect elevated volatility. Entry at current levels with 3-month horizon.",
|
||||
},
|
||||
];
|
||||
|
||||
const sideStyles = {
|
||||
bull: {
|
||||
border: "border-l-2 border-l-green",
|
||||
bg: "bg-gradient-to-r from-green-dim to-transparent",
|
||||
author: "text-green",
|
||||
},
|
||||
bear: {
|
||||
border: "border-l-2 border-l-red",
|
||||
bg: "bg-gradient-to-r from-red-dim to-transparent",
|
||||
author: "text-red",
|
||||
},
|
||||
judge: {
|
||||
border: "border-l-2 border-l-amber",
|
||||
bg: "bg-gradient-to-r from-amber-glow to-transparent",
|
||||
author: "text-amber",
|
||||
},
|
||||
};
|
||||
|
||||
export default function DebatePanel() {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.3, ease: [0.22, 1, 0.36, 1] }}
|
||||
className="bg-bg-surface border border-border-subtle rounded-xl overflow-hidden
|
||||
transition-colors duration-300 hover:border-border-medium col-span-1 row-span-2 flex flex-col"
|
||||
>
|
||||
<div className="flex items-center justify-between px-4 pt-3.5 pb-2.5 border-b border-border-subtle">
|
||||
<span className="font-display font-semibold text-[11px] tracking-[2px] uppercase text-text-secondary">
|
||||
Investment Debate
|
||||
</span>
|
||||
<span className="text-[9px] px-2 py-0.5 rounded-full tracking-[1px] uppercase bg-amber-dim text-amber">
|
||||
Round 1
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-4 px-4 py-3 border-b border-border-subtle">
|
||||
<span className="flex items-center gap-1.5 text-[11px] font-medium text-green">
|
||||
▲ Bull
|
||||
</span>
|
||||
<span className="font-serif italic text-sm text-text-tertiary">vs</span>
|
||||
<span className="flex items-center gap-1.5 text-[11px] font-medium text-red">
|
||||
▼ Bear
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto max-h-[500px]">
|
||||
{messages.map((m, i) => {
|
||||
const style = sideStyles[m.side];
|
||||
return (
|
||||
<motion.div
|
||||
key={i}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.4, delay: 0.4 + i * 0.1 }}
|
||||
className={`px-4 py-3 border-b border-border-subtle ${style.border} ${style.bg}`}
|
||||
>
|
||||
<div className={`text-[10px] font-medium uppercase tracking-[1px] mb-1 ${style.author}`}>
|
||||
{m.author}
|
||||
</div>
|
||||
<div className="text-[11px] leading-relaxed text-text-secondary">
|
||||
{m.text}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import Panel from "./ui/Panel";
|
||||
|
||||
function RiskGauge({
|
||||
label,
|
||||
level,
|
||||
color,
|
||||
}: {
|
||||
label: string;
|
||||
level: number;
|
||||
color: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex-1 flex flex-col items-center gap-1">
|
||||
<div className="w-full h-1 bg-bg-panel rounded-sm overflow-hidden">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${level}%` }}
|
||||
transition={{ duration: 1, delay: 0.5, ease: [0.22, 1, 0.36, 1] }}
|
||||
className="h-full rounded-sm"
|
||||
style={{ background: color }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-[9px] text-text-tertiary uppercase tracking-[1px]">
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DecisionPanel() {
|
||||
return (
|
||||
<Panel
|
||||
title="Final Decision"
|
||||
badge="Risk-Adjusted"
|
||||
badgeVariant="amber"
|
||||
className="col-span-2"
|
||||
delay={0.35}
|
||||
>
|
||||
<div className="grid grid-cols-[200px_1fr_1fr_1fr] gap-4 items-center">
|
||||
{/* Verdict */}
|
||||
<div className="flex flex-col items-center gap-2 p-4 bg-bg-elevated rounded-lg border border-border-accent">
|
||||
<div className="text-[9px] text-text-tertiary uppercase tracking-[2px]">
|
||||
Final Verdict
|
||||
</div>
|
||||
<motion.div
|
||||
initial={{ scale: 0.8, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{
|
||||
duration: 0.6,
|
||||
delay: 0.5,
|
||||
ease: [0.34, 1.56, 0.64, 1],
|
||||
}}
|
||||
className="font-display font-extrabold text-[32px] tracking-[2px] text-green"
|
||||
style={{ textShadow: "0 0 30px rgba(46, 204, 113, 0.15)" }}
|
||||
>
|
||||
BUY
|
||||
</motion.div>
|
||||
<div className="text-[11px] text-text-secondary">Confidence: 78%</div>
|
||||
<div className="flex gap-3 w-full mt-1">
|
||||
<RiskGauge label="Risk" level={60} color="var(--amber)" />
|
||||
<RiskGauge label="Reward" level={85} color="var(--green)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Position Size */}
|
||||
<div className="p-3.5 bg-bg-elevated rounded-lg">
|
||||
<div className="text-[9px] text-text-tertiary uppercase tracking-[1.5px] mb-2">
|
||||
Position Size
|
||||
</div>
|
||||
<div className="font-display font-semibold text-lg text-text-primary mb-1">
|
||||
12.5%
|
||||
</div>
|
||||
<div className="text-[10px] text-text-tertiary">of portfolio allocation</div>
|
||||
<div className="mt-2 text-[10px] text-text-tertiary leading-relaxed">
|
||||
Risk-adjusted by conservative analyst. Reduced from trader's initial 18%.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Entry Target */}
|
||||
<div className="p-3.5 bg-bg-elevated rounded-lg">
|
||||
<div className="text-[9px] text-text-tertiary uppercase tracking-[1.5px] mb-2">
|
||||
Entry Target
|
||||
</div>
|
||||
<div className="font-display font-semibold text-lg text-cyan mb-1">
|
||||
$885 – $895
|
||||
</div>
|
||||
<div className="text-[10px] text-text-tertiary">current: $892.45</div>
|
||||
<div className="mt-2 text-[10px] text-text-tertiary leading-relaxed">
|
||||
Scale in on pullbacks to 50 SMA support zone at $842.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stop / Target */}
|
||||
<div className="p-3.5 bg-bg-elevated rounded-lg">
|
||||
<div className="text-[9px] text-text-tertiary uppercase tracking-[1.5px] mb-2">
|
||||
Stop / Target
|
||||
</div>
|
||||
<div className="font-display font-semibold text-lg mb-1">
|
||||
<span className="text-red">$820</span>
|
||||
<span className="text-text-tertiary text-sm"> / </span>
|
||||
<span className="text-green">$980</span>
|
||||
</div>
|
||||
<div className="text-[10px] text-text-tertiary">R:R ratio 1 : 2.4</div>
|
||||
<div className="mt-2 text-[10px] text-text-tertiary leading-relaxed">
|
||||
3-month horizon. Re-evaluate on earnings date.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
AreaChart,
|
||||
Area,
|
||||
XAxis,
|
||||
YAxis,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
} from "recharts";
|
||||
import Panel from "./ui/Panel";
|
||||
|
||||
function generatePriceData() {
|
||||
const data = [];
|
||||
let price = 870;
|
||||
for (let i = 0; i < 60; i++) {
|
||||
price += (Math.random() - 0.47) * 8;
|
||||
data.push({
|
||||
time: `${Math.floor(i / 4) + 9}:${String((i % 4) * 15).padStart(2, "0")}`,
|
||||
price: Math.round(price * 100) / 100,
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
const stats = [
|
||||
{ label: "Current Price", value: "$892.45", type: "up" as const },
|
||||
{ label: "Day Change", value: "+$27.83 (+3.21%)", type: "up" as const },
|
||||
{ label: "Volume", value: "48.2M", type: "neutral" as const },
|
||||
{ label: "RSI (14)", value: "67.4", type: "neutral" as const },
|
||||
{ label: "MACD Signal", value: "Bullish Cross", type: "up" as const },
|
||||
{ label: "50 SMA", value: "$842.10", type: "up" as const },
|
||||
{ label: "200 SMA", value: "$756.30", type: "up" as const },
|
||||
{ label: "ATR (14)", value: "$18.92", type: "neutral" as const },
|
||||
{ label: "Bollinger", value: "Upper Band", type: "down" as const },
|
||||
];
|
||||
|
||||
const typeColors = {
|
||||
up: "text-green border-l-green",
|
||||
down: "text-red border-l-red",
|
||||
neutral: "text-cyan border-l-cyan",
|
||||
};
|
||||
|
||||
export default function MarketOverview() {
|
||||
const data = useMemo(() => generatePriceData(), []);
|
||||
|
||||
return (
|
||||
<Panel
|
||||
title="Market Overview"
|
||||
badge="Live"
|
||||
badgeVariant="live"
|
||||
className="col-span-2"
|
||||
delay={0.15}
|
||||
>
|
||||
<div className="grid grid-cols-[1fr_280px] gap-4">
|
||||
<div className="h-[200px] bg-bg-elevated rounded-lg overflow-hidden">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart
|
||||
data={data}
|
||||
margin={{ top: 10, right: 10, left: 0, bottom: 0 }}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="priceGrad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#4ecdc4" stopOpacity={0.2} />
|
||||
<stop offset="100%" stopColor="#4ecdc4" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
tick={{ fill: "#5a5853", fontSize: 10 }}
|
||||
axisLine={{ stroke: "rgba(255,255,255,0.04)" }}
|
||||
tickLine={false}
|
||||
interval={9}
|
||||
/>
|
||||
<YAxis
|
||||
domain={["dataMin - 5", "dataMax + 5"]}
|
||||
tick={{ fill: "#5a5853", fontSize: 10 }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tickFormatter={(v: number) => `$${v.toFixed(0)}`}
|
||||
width={48}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
background: "#14161d",
|
||||
border: "1px solid rgba(255,255,255,0.08)",
|
||||
borderRadius: 8,
|
||||
fontSize: 11,
|
||||
color: "#e8e6e1",
|
||||
}}
|
||||
labelStyle={{ color: "#8a8780" }}
|
||||
formatter={(value) => [`$${Number(value).toFixed(2)}`, "Price"]}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="price"
|
||||
stroke="#4ecdc4"
|
||||
strokeWidth={1.5}
|
||||
fill="url(#priceGrad)"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 overflow-y-auto max-h-[200px]">
|
||||
{stats.map((s) => (
|
||||
<div
|
||||
key={s.label}
|
||||
className={`flex justify-between items-center px-3 py-2 bg-bg-elevated rounded border-l-2 ${typeColors[s.type]}`}
|
||||
>
|
||||
<span className="text-[10px] text-text-tertiary uppercase tracking-[1px]">
|
||||
{s.label}
|
||||
</span>
|
||||
<span className={`font-display font-semibold text-[13px] ${typeColors[s.type].split(" ")[0]}`}>
|
||||
{s.value}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
const tickerData = [
|
||||
{ symbol: "NVDA", price: 892.45, change: 3.21 },
|
||||
{ symbol: "AAPL", price: 213.07, change: -0.45 },
|
||||
{ symbol: "MSFT", price: 441.2, change: 1.87 },
|
||||
{ symbol: "GOOGL", price: 178.92, change: 0.63 },
|
||||
{ symbol: "TSLA", price: 248.5, change: -2.14 },
|
||||
{ symbol: "META", price: 612.3, change: 4.5 },
|
||||
{ symbol: "AMZN", price: 225.88, change: 1.02 },
|
||||
{ symbol: "AMD", price: 178.34, change: 2.76 },
|
||||
{ symbol: "INTC", price: 31.22, change: -1.55 },
|
||||
{ symbol: "NFLX", price: 895.6, change: 5.12 },
|
||||
];
|
||||
|
||||
function TickerItem({ symbol, price, change }: (typeof tickerData)[0]) {
|
||||
const isUp = change >= 0;
|
||||
return (
|
||||
<div className="flex items-center gap-2 whitespace-nowrap text-[11px]">
|
||||
<span className="font-display font-semibold text-text-primary">
|
||||
{symbol}
|
||||
</span>
|
||||
<span className="text-text-secondary tabular-nums">
|
||||
${price.toFixed(2)}
|
||||
</span>
|
||||
<span className={isUp ? "text-green" : "text-red"}>
|
||||
{isUp ? "+" : ""}
|
||||
{change.toFixed(2)}%
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function TickerBar() {
|
||||
const doubled = [...tickerData, ...tickerData];
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="overflow-hidden border-b border-border-subtle py-1.5"
|
||||
>
|
||||
<div
|
||||
className="flex gap-10 w-max"
|
||||
style={{ animation: "ticker-scroll 30s linear infinite" }}
|
||||
>
|
||||
{doubled.map((t, i) => (
|
||||
<TickerItem key={`${t.symbol}-${i}`} {...t} />
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function TopBar() {
|
||||
const [ticker, setTicker] = useState("NVDA");
|
||||
const [running, setRunning] = useState(false);
|
||||
|
||||
const handleRun = () => {
|
||||
setRunning(true);
|
||||
setTimeout(() => setRunning(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.header
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
|
||||
className="flex items-center justify-between py-4 border-b border-border-subtle"
|
||||
>
|
||||
<div className="flex items-center gap-3.5">
|
||||
<div
|
||||
className="w-9 h-9 rounded-md grid place-items-center font-display font-extrabold text-base
|
||||
text-bg-void tracking-tighter"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #d4af37, #b8941f)",
|
||||
boxShadow: "0 0 20px rgba(212, 175, 55, 0.15)",
|
||||
}}
|
||||
>
|
||||
TA
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-display font-bold text-lg tracking-tight text-text-primary">
|
||||
TradingAgents
|
||||
</div>
|
||||
<div className="text-[11px] text-text-tertiary tracking-[2px] uppercase">
|
||||
Multi-Agent Intelligence
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="flex items-center gap-2 text-[11px] text-text-secondary">
|
||||
<div
|
||||
className="w-1.5 h-1.5 rounded-full bg-green"
|
||||
style={{ animation: "pulse-dot 2s ease-in-out infinite" }}
|
||||
/>
|
||||
<span>System Online</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 bg-bg-elevated border border-border-medium rounded-lg px-3 py-1.5">
|
||||
<label className="text-[10px] text-text-tertiary uppercase tracking-[1px]">
|
||||
Ticker
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={ticker}
|
||||
onChange={(e) => setTicker(e.target.value.toUpperCase())}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleRun()}
|
||||
maxLength={5}
|
||||
spellCheck={false}
|
||||
className="bg-transparent border-none outline-none text-amber font-display font-bold text-base w-20 tracking-wide"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleRun}
|
||||
className="border-none px-5 py-2 rounded-lg font-display font-bold text-xs tracking-[1px]
|
||||
uppercase cursor-pointer transition-all duration-300 text-bg-void
|
||||
hover:-translate-y-0.5"
|
||||
style={{
|
||||
background: running
|
||||
? "linear-gradient(135deg, #4ecdc4, #3ab5ad)"
|
||||
: "linear-gradient(135deg, #d4af37, #c9a020)",
|
||||
boxShadow: running
|
||||
? "0 0 20px rgba(78, 205, 196, 0.15)"
|
||||
: "0 0 20px rgba(212, 175, 55, 0.15)",
|
||||
}}
|
||||
>
|
||||
{running ? "Analyzing..." : "Run Analysis"}
|
||||
</button>
|
||||
</div>
|
||||
</motion.header>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
interface PanelProps {
|
||||
title: string;
|
||||
badge?: string;
|
||||
badgeVariant?: "live" | "amber";
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
export default function Panel({
|
||||
title,
|
||||
badge,
|
||||
badgeVariant = "amber",
|
||||
children,
|
||||
className = "",
|
||||
delay = 0,
|
||||
}: PanelProps) {
|
||||
const badgeColors = {
|
||||
live: "bg-green-dim text-green",
|
||||
amber: "bg-amber-dim text-amber",
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay, ease: [0.22, 1, 0.36, 1] }}
|
||||
className={`bg-bg-surface border border-border-subtle rounded-xl overflow-hidden
|
||||
transition-colors duration-300 hover:border-border-medium ${className}`}
|
||||
>
|
||||
<div className="flex items-center justify-between px-4 pt-3.5 pb-2.5 border-b border-border-subtle">
|
||||
<span className="font-display font-semibold text-[11px] tracking-[2px] uppercase text-text-secondary">
|
||||
{title}
|
||||
</span>
|
||||
{badge && (
|
||||
<span
|
||||
className={`text-[9px] px-2 py-0.5 rounded-full tracking-[1px] uppercase ${badgeColors[badgeVariant]}`}
|
||||
>
|
||||
{badge}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4">{children}</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||
import nextTs from "eslint-config-next/typescript";
|
||||
|
||||
const eslintConfig = defineConfig([
|
||||
...nextVitals,
|
||||
...nextTs,
|
||||
// Override default ignores of eslint-config-next.
|
||||
globalIgnores([
|
||||
// Default ignores of eslint-config-next:
|
||||
".next/**",
|
||||
"out/**",
|
||||
"build/**",
|
||||
"next-env.d.ts",
|
||||
]),
|
||||
]);
|
||||
|
||||
export default eslintConfig;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "ui",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"framer-motion": "^12.36.0",
|
||||
"next": "16.1.6",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"recharts": "^3.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.1.6",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
@ -0,0 +1 @@
|
|||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 391 B |
|
|
@ -0,0 +1 @@
|
|||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 128 B |
|
|
@ -0,0 +1 @@
|
|||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||
|
After Width: | Height: | Size: 385 B |
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts",
|
||||
"**/*.mts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Loading…
Reference in New Issue