-
{t('title')}
-
{t('comingSoon')}
+
+
+
+
Analytics
+
Deep dive into {period} of signals and trades.
+
+
+ {['7d', '30d', '90d', 'All'].map(p => (
+
+ ))}
+
+
+ {/* Summary card */}
+
+
+
+
Performance · {period}
+
+ {perf ? fmtMoney(perf.net_pnl_usd) : '—'}
+
+
+ = 0.5 ? 'up' : 'down'}`}>{winRate.toFixed(1)}% win rate
+ {perf?.total_trades ?? 0} trades
+
+
+
+
+
+ {/* Metric grid */}
+
+ {metrics.map(m => (
+
+
{m.k}
+
{m.v}
+
{m.sub}
+
+ ))}
+
+
+ {/* No data message */}
+ {!perf && !trades.length && (
+
+
No trade data yet. The bot will populate analytics once it starts executing.
+
+ )}
)
}
diff --git a/app/[locale]/globals.css b/app/[locale]/globals.css
index 0e8fa3d..1e43891 100644
--- a/app/[locale]/globals.css
+++ b/app/[locale]/globals.css
@@ -1,22 +1,903 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
+/* ============================================================
+ Trump Alpha — Design System
+ ============================================================ */
-* {
- box-sizing: border-box;
+:root {
+ /* Surfaces — warm off-whites */
+ --bg: oklch(99% 0.004 85);
+ --bg-sunk: oklch(97.8% 0.006 85);
+ --surface: #ffffff;
+ --surface-2: oklch(97.2% 0.006 85);
+ --surface-3: oklch(94% 0.008 85);
+
+ /* Text */
+ --ink: oklch(18% 0.008 85);
+ --ink-2: oklch(38% 0.008 85);
+ --ink-3: oklch(55% 0.008 85);
+ --ink-4: oklch(70% 0.006 85);
+
+ /* Borders */
+ --line: oklch(93% 0.008 85);
+ --line-2: oklch(88% 0.01 85);
+
+ /* Amber accent */
+ --amber: oklch(78% 0.17 75);
+ --amber-ink: oklch(45% 0.16 70);
+ --amber-soft: oklch(96% 0.05 85);
+ --amber-ring: oklch(88% 0.12 80);
+
+ /* Signal colors */
+ --up: oklch(62% 0.17 148);
+ --up-soft: oklch(95% 0.05 148);
+ --down: oklch(58% 0.22 25);
+ --down-soft: oklch(95% 0.05 25);
+ --violet: oklch(55% 0.17 280);
+ --violet-soft: oklch(96% 0.04 280);
+
+ /* Radii */
+ --r-xs: 8px;
+ --r-sm: 10px;
+ --r-md: 14px;
+ --r-lg: 20px;
+ --r-xl: 28px;
+ --r-pill: 999px;
+
+ /* Shadow */
+ --shadow-1: 0 1px 2px rgba(20, 18, 14, 0.04), 0 1px 1px rgba(20, 18, 14, 0.02);
+ --shadow-2: 0 4px 16px rgba(20, 18, 14, 0.06), 0 1px 2px rgba(20, 18, 14, 0.03);
+ --shadow-3: 0 12px 40px rgba(20, 18, 14, 0.08), 0 2px 4px rgba(20, 18, 14, 0.04);
+
+ --sans: 'Geist', ui-sans-serif, system-ui, -apple-system, 'Helvetica Neue', sans-serif;
+ --mono: 'Geist Mono', ui-monospace, 'SF Mono', Menlo, monospace;
}
-body {
- background-color: #000000;
+html[data-theme="dark"] {
+ --bg: oklch(15% 0.008 85);
+ --bg-sunk: oklch(12% 0.008 85);
+ --surface: oklch(18% 0.008 85);
+ --surface-2: oklch(21% 0.008 85);
+ --surface-3: oklch(25% 0.01 85);
+
+ --ink: oklch(97% 0.005 85);
+ --ink-2: oklch(82% 0.006 85);
+ --ink-3: oklch(62% 0.006 85);
+ --ink-4: oklch(45% 0.006 85);
+
+ --line: oklch(24% 0.008 85);
+ --line-2: oklch(30% 0.01 85);
+
+ --amber: oklch(78% 0.17 75);
+ --amber-ink: oklch(82% 0.16 75);
+ --amber-soft: oklch(25% 0.05 75);
+ --amber-ring: oklch(38% 0.12 75);
+
+ --up: oklch(70% 0.18 148);
+ --up-soft: oklch(24% 0.06 148);
+ --down: oklch(68% 0.22 25);
+ --down-soft: oklch(24% 0.08 25);
+ --violet: oklch(70% 0.17 280);
+ --violet-soft: oklch(24% 0.06 280);
+
+ --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.3);
+ --shadow-2: 0 4px 16px rgba(0, 0, 0, 0.35);
+ --shadow-3: 0 12px 40px rgba(0, 0, 0, 0.5);
+
+ color-scheme: dark;
}
-/* Remove default focus ring and apply custom */
-button:focus-visible {
- outline: 1px solid #f97316;
- outline-offset: 2px;
+html[data-theme="dark"] .src-ico.x { background: oklch(92% 0.005 85); color: oklch(15% 0.008 85); }
+
+html[data-theme="dark"] .bot-status {
+ background: linear-gradient(160deg, oklch(28% 0.015 85) 0%, oklch(20% 0.01 85) 100%);
+ border: 1px solid oklch(30% 0.01 85);
}
-input:focus-visible {
- outline: 1px solid #222222;
- outline-offset: 0;
+html[data-theme="dark"] .kpi.accent {
+ background: linear-gradient(135deg, oklch(28% 0.05 75), oklch(24% 0.07 70));
}
+
+html[data-theme="dark"] .landing-nav {
+ background: oklch(15% 0.008 85 / 0.8);
+}
+
+html[data-theme="dark"] .src-ico.truth {
+ background: oklch(30% 0.08 25); color: oklch(80% 0.15 25);
+}
+
+html[data-theme="dark"] .post-row.selected {
+ background: oklch(22% 0.03 75);
+}
+
+html, body {
+ margin: 0;
+ padding: 0;
+ background: var(--bg);
+ color: var(--ink);
+ font-family: var(--sans);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ font-feature-settings: 'ss01', 'cv11';
+ transition: background 200ms, color 200ms;
+}
+
+* { box-sizing: border-box; }
+
+button { font-family: inherit; cursor: pointer; border: 0; background: transparent; color: inherit; }
+input, textarea { font-family: inherit; }
+a { color: inherit; text-decoration: none; }
+
+.mono { font-family: var(--mono); font-feature-settings: 'tnum', 'zero'; }
+.tnum { font-variant-numeric: tabular-nums; }
+
+/* ============================================================
+ App scaffold
+ ============================================================ */
+
+.app {
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+}
+
+/* ---------- Top navbar ---------- */
+.nav {
+ height: 64px;
+ padding: 0 28px;
+ display: flex;
+ align-items: center;
+ gap: 32px;
+ background: var(--bg);
+ border-bottom: 1px solid var(--line);
+ position: sticky;
+ top: 0;
+ z-index: 40;
+}
+
+.brand {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-weight: 600;
+ font-size: 15px;
+ letter-spacing: -0.01em;
+}
+.brand-mark {
+ width: 28px;
+ height: 28px;
+ border-radius: 8px;
+ background: var(--ink);
+ color: var(--bg);
+ display: grid;
+ place-items: center;
+ font-family: var(--mono);
+ font-size: 15px;
+ font-weight: 500;
+ position: relative;
+}
+.brand-mark::after {
+ content: '';
+ position: absolute;
+ right: -3px;
+ bottom: -3px;
+ width: 10px;
+ height: 10px;
+ border-radius: 999px;
+ background: var(--amber);
+ border: 2px solid var(--bg);
+}
+
+.nav-tabs {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 4px;
+ background: var(--bg-sunk);
+ border: 1px solid var(--line);
+ border-radius: var(--r-pill);
+}
+.nav-tab {
+ padding: 7px 14px;
+ border-radius: var(--r-pill);
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--ink-3);
+ transition: color 120ms, background 120ms;
+}
+.nav-tab:hover { color: var(--ink); }
+.nav-tab.active {
+ background: var(--surface);
+ color: var(--ink);
+ box-shadow: var(--shadow-1);
+}
+
+.nav-spacer { flex: 1; }
+
+.nav-right {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.icon-btn {
+ width: 36px;
+ height: 36px;
+ border-radius: var(--r-pill);
+ background: var(--bg-sunk);
+ border: 1px solid var(--line);
+ display: grid;
+ place-items: center;
+ color: var(--ink-2);
+ transition: background 120ms, color 120ms;
+}
+.icon-btn:hover { background: var(--surface-3); color: var(--ink); }
+
+.connect-btn {
+ padding: 12px 22px;
+ border-radius: var(--r-pill);
+ background: var(--ink);
+ color: var(--bg);
+ font-size: 14px;
+ font-weight: 600;
+ letter-spacing: -0.005em;
+ transition: background 120ms, transform 120ms, box-shadow 120ms;
+ box-shadow: var(--shadow-1);
+}
+.connect-btn:hover { background: oklch(30% 0.01 85); box-shadow: var(--shadow-2); }
+.connect-btn:active { transform: scale(0.98); }
+.connect-btn.lg { padding: 13px 26px; font-size: 14px; }
+html[data-theme="dark"] .connect-btn { background: var(--amber); color: oklch(20% 0.04 75); }
+html[data-theme="dark"] .connect-btn:hover { background: oklch(82% 0.17 75); }
+
+.theme-toggle { width: 40px; height: 40px; }
+html[data-theme="dark"] .icon-btn { background: var(--surface-2); border-color: var(--line); }
+html[data-theme="dark"] .icon-btn:hover { background: var(--surface-3); }
+html[data-theme="dark"] .nav-tab.active { background: var(--surface-2); }
+html[data-theme="dark"] .asset-switch button.on { background: var(--surface-2); }
+
+.wallet-chip {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 12px 6px 6px;
+ border-radius: var(--r-pill);
+ background: var(--bg-sunk);
+ border: 1px solid var(--line);
+ font-size: 13px;
+}
+.wallet-chip .ava {
+ width: 24px;
+ height: 24px;
+ border-radius: 999px;
+ background: linear-gradient(135deg, var(--amber), oklch(65% 0.2 35));
+}
+
+/* ============================================================
+ Page shell
+ ============================================================ */
+
+.page {
+ max-width: 1360px;
+ margin: 0 auto;
+ padding: 32px 28px 80px;
+ width: 100%;
+}
+.page.wide { max-width: 1600px; }
+
+.page-head {
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-between;
+ margin-bottom: 28px;
+ gap: 24px;
+}
+.page-title {
+ font-size: 34px;
+ letter-spacing: -0.02em;
+ font-weight: 600;
+ line-height: 1.05;
+ margin: 0;
+}
+.page-sub {
+ color: var(--ink-3);
+ font-size: 14px;
+ margin-top: 6px;
+ margin-bottom: 0;
+}
+
+/* ============================================================
+ Cards + primitives
+ ============================================================ */
+
+.card {
+ background: var(--surface);
+ border: 1px solid var(--line);
+ border-radius: var(--r-md);
+ padding: 20px;
+}
+.card.flush { padding: 0; }
+.card.soft { background: var(--bg-sunk); }
+.card.raise { box-shadow: var(--shadow-1); }
+
+.section-title {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 14px;
+}
+.section-title h2 {
+ margin: 0;
+ font-size: 16px;
+ font-weight: 600;
+ letter-spacing: -0.01em;
+}
+.section-title .hint {
+ font-size: 12px;
+ color: var(--ink-3);
+}
+
+.chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 4px 10px;
+ border-radius: var(--r-pill);
+ background: var(--bg-sunk);
+ border: 1px solid var(--line);
+ font-size: 12px;
+ color: var(--ink-2);
+ font-weight: 500;
+}
+.chip .dot { width: 6px; height: 6px; border-radius: 999px; background: currentColor; }
+.chip.up { color: oklch(40% 0.16 148); background: var(--up-soft); border-color: oklch(85% 0.08 148); }
+.chip.down { color: oklch(44% 0.2 25); background: var(--down-soft); border-color: oklch(87% 0.08 25); }
+.chip.amber { color: var(--amber-ink); background: var(--amber-soft); border-color: var(--amber-ring); }
+.chip.violet { color: oklch(38% 0.15 280); background: var(--violet-soft); border-color: oklch(86% 0.08 280); }
+.chip.neutral { color: var(--ink-2); }
+
+.live-dot {
+ width: 6px;
+ height: 6px;
+ border-radius: 999px;
+ background: var(--up);
+ position: relative;
+ display: inline-block;
+}
+.live-dot::after {
+ content: '';
+ position: absolute;
+ inset: -4px;
+ border-radius: 999px;
+ background: var(--up);
+ opacity: 0.3;
+ animation: pulse 1.6s ease-out infinite;
+}
+@keyframes pulse {
+ 0% { transform: scale(0.6); opacity: 0.5; }
+ 100% { transform: scale(2.2); opacity: 0; }
+}
+
+.btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 10px 16px;
+ border-radius: var(--r-pill);
+ font-size: 13px;
+ font-weight: 500;
+ transition: background 120ms, transform 120ms;
+ border: 1px solid transparent;
+}
+.btn.primary { background: var(--ink); color: var(--bg); }
+.btn.primary:hover { background: oklch(30% 0.01 85); }
+.btn.amber {
+ background: var(--amber);
+ color: oklch(22% 0.04 75);
+ font-weight: 600;
+}
+.btn.amber:hover { background: oklch(82% 0.17 75); }
+.btn.ghost { background: var(--bg-sunk); color: var(--ink); border-color: var(--line); }
+.btn.ghost:hover { background: var(--surface-3); }
+.btn.lg { padding: 14px 22px; font-size: 15px; }
+.btn:active { transform: scale(0.98); }
+
+.delta { font-family: var(--mono); font-weight: 500; }
+.delta.up { color: oklch(42% 0.16 148); }
+.delta.down { color: oklch(46% 0.2 25); }
+
+/* ============================================================
+ Dashboard specific
+ ============================================================ */
+
+.dash-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 360px;
+ gap: 20px;
+ align-items: start;
+}
+
+.hero-value {
+ font-size: 44px;
+ font-weight: 500;
+ letter-spacing: -0.025em;
+ line-height: 1;
+ font-family: var(--mono);
+ font-feature-settings: 'tnum';
+}
+.hero-value .cents { color: var(--ink-3); font-size: 0.6em; }
+
+.asset-switch {
+ display: inline-flex;
+ padding: 4px;
+ background: var(--bg-sunk);
+ border: 1px solid var(--line);
+ border-radius: var(--r-pill);
+ gap: 2px;
+}
+.asset-switch button {
+ padding: 7px 14px;
+ border-radius: var(--r-pill);
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--ink-3);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+.asset-switch button.on {
+ background: var(--surface);
+ color: var(--ink);
+ box-shadow: var(--shadow-1);
+}
+.asset-dot {
+ width: 14px;
+ height: 14px;
+ border-radius: 999px;
+}
+.asset-dot.btc { background: linear-gradient(135deg, #f7931a, #f2a93b); }
+.asset-dot.eth { background: linear-gradient(135deg, #627eea, #8fa2ff); }
+
+.tf-bar {
+ display: inline-flex;
+ gap: 2px;
+ padding: 3px;
+ background: var(--bg-sunk);
+ border: 1px solid var(--line);
+ border-radius: var(--r-pill);
+}
+.tf-bar button {
+ padding: 5px 10px;
+ border-radius: var(--r-pill);
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--ink-3);
+ font-family: var(--mono);
+}
+.tf-bar button.on { background: var(--ink); color: var(--bg); }
+
+.chart-wrap {
+ margin-top: 8px;
+ position: relative;
+ height: 360px;
+}
+
+.chart-footnote {
+ display: flex;
+ gap: 18px;
+ font-size: 12px;
+ color: var(--ink-3);
+ border-top: 1px dashed var(--line);
+ padding-top: 14px;
+ margin-top: 4px;
+}
+.chart-footnote .item { display: flex; align-items: center; gap: 6px; }
+.legend-dot { width: 8px; height: 8px; border-radius: 2px; }
+
+/* KPI tiles */
+.kpi-row {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 12px;
+ margin-bottom: 20px;
+}
+.kpi {
+ background: var(--surface);
+ border: 1px solid var(--line);
+ border-radius: var(--r-md);
+ padding: 16px 18px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ position: relative;
+ overflow: hidden;
+}
+.kpi .label {
+ font-size: 12px;
+ color: var(--ink-3);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+.kpi .value {
+ font-size: 22px;
+ font-weight: 500;
+ letter-spacing: -0.01em;
+ font-family: var(--mono);
+}
+.kpi .foot {
+ font-size: 12px;
+ color: var(--ink-3);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+.kpi.accent {
+ background: linear-gradient(135deg, oklch(97% 0.04 85), oklch(95% 0.07 80));
+ border-color: var(--amber-ring);
+}
+.kpi.accent .label { color: var(--amber-ink); }
+
+/* Right rail */
+.rail { display: flex; flex-direction: column; gap: 20px; }
+
+.signal-card {
+ background: var(--surface);
+ border: 1px solid var(--line);
+ border-radius: var(--r-md);
+ padding: 18px;
+}
+.signal-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 14px;
+}
+.signal-head h3 {
+ margin: 0;
+ font-size: 14px;
+ font-weight: 600;
+}
+
+.latest-post {
+ border: 1px solid var(--line);
+ border-radius: var(--r-sm);
+ padding: 14px;
+ background: var(--bg-sunk);
+}
+.latest-post .meta {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 12px;
+ color: var(--ink-3);
+ margin-bottom: 10px;
+}
+.latest-post .text {
+ font-size: 13px;
+ line-height: 1.55;
+ color: var(--ink);
+ margin: 0 0 12px;
+}
+.latest-post .divider {
+ height: 1px;
+ background: var(--line);
+ margin: 12px 0;
+}
+.latest-post .lp-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 12px;
+}
+.latest-post .lp-row + .lp-row { margin-top: 6px; }
+.latest-post .lp-row .k { color: var(--ink-3); }
+
+.confidence-bar {
+ height: 6px;
+ background: var(--surface-3);
+ border-radius: 999px;
+ overflow: hidden;
+ position: relative;
+ margin-top: 6px;
+}
+.confidence-bar > div {
+ height: 100%;
+ background: linear-gradient(90deg, var(--amber) 0%, oklch(68% 0.2 45) 100%);
+ border-radius: 999px;
+ transition: width 600ms ease-out;
+}
+
+/* Bot status */
+.bot-status {
+ background: linear-gradient(160deg, oklch(22% 0.01 85) 0%, oklch(14% 0.01 85) 100%);
+ color: oklch(96% 0.005 85);
+ border-radius: var(--r-lg);
+ padding: 22px;
+ position: relative;
+ overflow: hidden;
+}
+.bot-status::after {
+ content: '';
+ position: absolute;
+ right: -40px;
+ top: -40px;
+ width: 180px;
+ height: 180px;
+ background: radial-gradient(circle, oklch(75% 0.17 75 / 0.4) 0%, transparent 70%);
+ pointer-events: none;
+}
+.bot-head {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 18px;
+}
+.bot-head h3 { margin: 0; font-size: 15px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
+.bot-stats {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 14px 20px;
+ position: relative;
+ z-index: 1;
+}
+.bot-stat .k {
+ font-size: 11px;
+ color: oklch(70% 0.01 85);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ margin-bottom: 4px;
+}
+.bot-stat .v {
+ font-size: 20px;
+ font-family: var(--mono);
+ font-weight: 500;
+ letter-spacing: -0.01em;
+}
+.bot-stat .v.up { color: oklch(75% 0.17 148); }
+.bot-stat .v.amber { color: var(--amber); }
+
+.bot-cta {
+ margin-top: 18px;
+ display: flex;
+ gap: 8px;
+ position: relative;
+ z-index: 1;
+}
+.bot-cta .btn { flex: 1; }
+
+/* Post list (stream) */
+.post-stream {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+.post-row {
+ display: grid;
+ grid-template-columns: 36px 1fr auto;
+ gap: 14px;
+ padding: 14px 16px;
+ background: var(--surface);
+ border: 1px solid var(--line);
+ border-radius: var(--r-md);
+ align-items: flex-start;
+ transition: border-color 120ms, background 120ms;
+ cursor: pointer;
+}
+.post-row:hover { border-color: var(--line-2); }
+.post-row.selected { border-color: var(--amber-ring); background: oklch(99% 0.02 85); }
+.src-ico {
+ width: 36px;
+ height: 36px;
+ border-radius: 10px;
+ display: grid;
+ place-items: center;
+ font-family: var(--mono);
+ font-weight: 600;
+ font-size: 15px;
+ flex-shrink: 0;
+}
+.src-ico.x { background: #111; color: #fff; }
+.src-ico.truth { background: oklch(94% 0.05 25); color: oklch(45% 0.2 25); }
+.post-body .meta {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 6px;
+ font-size: 12px;
+ color: var(--ink-3);
+}
+.post-body .text {
+ font-size: 14px;
+ line-height: 1.5;
+ color: var(--ink);
+ margin: 0 0 10px;
+}
+.post-aside {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ align-items: flex-end;
+ min-width: 120px;
+}
+
+/* Signal pill */
+.sig {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 5px 10px;
+ border-radius: var(--r-pill);
+ font-size: 12px;
+ font-weight: 600;
+ letter-spacing: 0.02em;
+}
+.sig.buy { background: var(--up-soft); color: oklch(38% 0.16 148); }
+.sig.sell, .sig.short { background: var(--down-soft); color: oklch(42% 0.2 25); }
+.sig.hold { background: var(--bg-sunk); color: var(--ink-2); }
+
+.impact-mini {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ font-family: var(--mono);
+ font-size: 12px;
+}
+.impact-mini .tf { color: var(--ink-4); font-size: 11px; }
+
+/* Trades table */
+.table {
+ width: 100%;
+ border-collapse: separate;
+ border-spacing: 0;
+ font-size: 13px;
+}
+.table th {
+ text-align: left;
+ font-weight: 500;
+ color: var(--ink-3);
+ padding: 14px 18px;
+ border-bottom: 1px solid var(--line);
+ font-size: 12px;
+ background: var(--bg-sunk);
+}
+.table th:first-child { border-top-left-radius: var(--r-md); }
+.table th:last-child { border-top-right-radius: var(--r-md); text-align: right; }
+.table td {
+ padding: 14px 18px;
+ border-bottom: 1px solid var(--line);
+}
+.table td:last-child { text-align: right; }
+.table tr:last-child td { border-bottom: 0; }
+.table tr:hover td { background: var(--bg-sunk); }
+
+.side-pill {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 3px 9px;
+ border-radius: var(--r-pill);
+ font-size: 12px;
+ font-weight: 600;
+}
+.side-pill.long { background: var(--up-soft); color: oklch(38% 0.16 148); }
+.side-pill.short { background: var(--down-soft); color: oklch(42% 0.2 25); }
+
+/* Settings */
+.settings-grid {
+ display: grid;
+ grid-template-columns: 260px 1fr;
+ gap: 32px;
+ align-items: start;
+}
+.settings-side {
+ position: sticky;
+ top: 84px;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+.settings-side button {
+ text-align: left;
+ padding: 10px 14px;
+ border-radius: var(--r-sm);
+ font-size: 13px;
+ color: var(--ink-2);
+ font-weight: 500;
+}
+.settings-side button.on { background: var(--bg-sunk); color: var(--ink); }
+.settings-side button:hover { background: var(--bg-sunk); color: var(--ink); }
+
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ margin-bottom: 20px;
+}
+.field label {
+ font-size: 13px;
+ font-weight: 500;
+}
+.field .hint {
+ font-size: 12px;
+ color: var(--ink-3);
+}
+.field input, .field select, .field textarea {
+ border: 1px solid var(--line);
+ background: var(--surface);
+ border-radius: var(--r-sm);
+ padding: 11px 14px;
+ font-size: 14px;
+ outline: none;
+ transition: border-color 120ms, box-shadow 120ms;
+ color: var(--ink);
+}
+.field input:focus, .field textarea:focus {
+ border-color: var(--amber-ring);
+ box-shadow: 0 0 0 3px oklch(88% 0.12 80 / 0.4);
+}
+
+.switch {
+ --w: 40px;
+ width: var(--w);
+ height: 24px;
+ border-radius: 999px;
+ background: var(--surface-3);
+ position: relative;
+ cursor: pointer;
+ transition: background 150ms;
+ border: 1px solid var(--line);
+ flex-shrink: 0;
+}
+.switch::after {
+ content: '';
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ width: 18px;
+ height: 18px;
+ border-radius: 999px;
+ background: var(--surface);
+ box-shadow: var(--shadow-1);
+ transition: transform 180ms;
+}
+.switch.on {
+ background: var(--ink);
+ border-color: var(--ink);
+}
+.switch.on::after { transform: translateX(16px); }
+
+.setting-row {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 24px;
+ padding: 16px 0;
+ border-bottom: 1px solid var(--line);
+}
+.setting-row:last-child { border-bottom: 0; }
+.setting-row .desc { font-size: 12px; color: var(--ink-3); margin-top: 4px; max-width: 440px; line-height: 1.5; }
+
+/* Analytics */
+.metric-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 16px;
+}
+
+/* Misc utilities */
+.stack { display: flex; flex-direction: column; }
+.row { display: flex; align-items: center; }
+.between { justify-content: space-between; }
+.gap-s { gap: 8px; }
+.gap-m { gap: 14px; }
+.gap-l { gap: 20px; }
+.grow { flex: 1; }
+.mono-num { font-family: var(--mono); font-variant-numeric: tabular-nums; }
+.muted { color: var(--ink-3); }
+.tiny { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-3); font-weight: 500; }
+
+/* Scrollbar */
+::-webkit-scrollbar { width: 10px; height: 10px; }
+::-webkit-scrollbar-track { background: transparent; }
+::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 999px; border: 2px solid var(--bg); }
+::-webkit-scrollbar-thumb:hover { background: oklch(80% 0.01 85); }
diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx
index 260b7cc..62cdc8a 100644
--- a/app/[locale]/layout.tsx
+++ b/app/[locale]/layout.tsx
@@ -1,17 +1,14 @@
import type { Metadata } from 'next'
-import { Inter } from 'next/font/google'
import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server'
import { notFound } from 'next/navigation'
import { locales } from '@/i18n'
-import Navbar from '@/components/nav/Navbar'
import Providers from './Providers'
+import Navbar from '@/components/nav/Navbar'
import './globals.css'
-const inter = Inter({ subsets: ['latin'] })
-
export const metadata: Metadata = {
- title: 'TrumpSignal — AI-powered crypto trading signals',
+ title: 'Trump Alpha — AI signal trading',
description: 'Trade crypto based on Trump social media signals with AI confidence scoring.',
}
@@ -29,11 +26,19 @@ export default async function LocaleLayout({ children, params: { locale } }: Lay
return (
-
+
+
+
+
+
+
-
- {children}
+
+ {children}
diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx
index ac4290e..ae22dd3 100644
--- a/app/[locale]/page.tsx
+++ b/app/[locale]/page.tsx
@@ -1,20 +1,18 @@
-import { getPosts, getPerformance, getTrades } from '@/lib/api'
+import { getPosts, getPerformance } from '@/lib/api'
import DashboardClient from './DashboardClient'
export const revalidate = 30
export default async function OverviewPage() {
- const [posts, performance, trades] = await Promise.allSettled([
+ const [posts, performance] = await Promise.allSettled([
getPosts(500, 1),
getPerformance(),
- getTrades(20, 1),
])
return (
)
}
diff --git a/app/[locale]/posts/page.tsx b/app/[locale]/posts/page.tsx
index 19d94b0..1122647 100644
--- a/app/[locale]/posts/page.tsx
+++ b/app/[locale]/posts/page.tsx
@@ -1,14 +1,124 @@
-import { useTranslations } from 'next-intl'
-import { getTranslations } from 'next-intl/server'
+'use client'
-export default async function PostsPage() {
- const t = await getTranslations('posts')
+import { useState, useEffect } from 'react'
+import type { TrumpPost } from '@/types'
+import { getPosts } from '@/lib/api'
+import PostRow, { SignalPill, SourceIcon, fmtPct, timeAgo } from '@/components/dashboard/PostCards'
+
+function PostDetail({ post, onClose }: { post: TrumpPost; onClose: () => void }) {
+ return (
+
+
+
+
+
+
@realDonaldTrump
+
{new Date(post.published_at).toLocaleString()}
+
+
+
{post.text}
+
+
+
+ {post.sentiment}
+
+
+
AI reasoning
+
+ {post.ai_reasoning || 'No reasoning available.'}
+
+
+
+ AI confidence
+ {post.ai_confidence}%
+
+
+
+ {post.price_impact && (
+
+
Actual price impact · {post.price_impact.asset}
+
+ {(['m5', 'm15', 'm1h'] as const).map(k => {
+ const v = post.price_impact![k]
+ const correct = post.price_impact!['correct_' + k as 'correct_m5' | 'correct_m15' | 'correct_m1h']
+ const label = k === 'm5' ? '5m' : k === 'm15' ? '15m' : '1h'
+ return (
+
+
{label}
+
= 0 ? 'up' : 'down'}`} style={{ fontSize: 15 }}>{fmtPct(v)}
+ {correct != null && (
+
+ {correct ? '✓ correct' : '✗ missed'}
+
+ )}
+
+ )
+ })}
+
+
+ )}
+
+ )
+}
+
+export default function PostsPage() {
+ const [posts, setPosts] = useState
([])
+ const [loading, setLoading] = useState(true)
+ const [filter, setFilter] = useState('all')
+ const [selected, setSelected] = useState(null)
+
+ useEffect(() => {
+ getPosts(200, 1).then(setPosts).catch(() => {}).finally(() => setLoading(false))
+ }, [])
+
+ const filtered = posts.filter(p => {
+ if (filter !== 'all' && p.sentiment !== filter) return false
+ return true
+ })
+
+ const counts = {
+ all: posts.length,
+ bullish: posts.filter(p => p.sentiment === 'bullish').length,
+ bearish: posts.filter(p => p.sentiment === 'bearish').length,
+ neutral: posts.filter(p => p.sentiment === 'neutral').length,
+ }
+
+ const selectedPost = posts.find(p => p.id === selected)
return (
-
-
-
{t('title')}
-
{t('comingSoon')}
+
+
+
+
Signals feed
+
Every post we tracked, every prediction we made. {posts.length} posts in the last 30 days.
+
+
Streaming
+
+
+
+
+ {(['all', 'bullish', 'bearish', 'neutral'] as const).map(f => (
+
+ ))}
+
+
+
+ {loading &&
Loading…
}
+
+
+
+ {filtered.map(p => (
+
setSelected(selected === p.id ? null : p.id)} />
+ ))}
+
+ {selected && selectedPost &&
setSelected(null)} />}
)
diff --git a/app/[locale]/settings/SettingsClient.tsx b/app/[locale]/settings/SettingsClient.tsx
index 2dde770..25f8ca7 100644
--- a/app/[locale]/settings/SettingsClient.tsx
+++ b/app/[locale]/settings/SettingsClient.tsx
@@ -1,31 +1,325 @@
'use client'
-import { useTranslations } from 'next-intl'
-import { useAccount } from 'wagmi'
-import { useConnectModal } from '@rainbow-me/rainbowkit'
+import { useEffect, useState } from 'react'
+import Link from 'next/link'
+import { useAccount, useSignMessage } from 'wagmi'
+import { useDashboardStore } from '@/store/dashboard'
+import { getUser, getUserPublic, setUserSettings, type UserSettings } from '@/lib/api'
+import { getOrCreateViewEnvelope, signRequest } from '@/lib/signedRequest'
+import BotPanel from '@/components/dashboard/BotPanel'
-export default function SettingsClient() {
- const t = useTranslations('settings')
- const { isConnected } = useAccount()
- const { openConnectModal } = useConnectModal()
+const ACTION_SET_SETTINGS = 'set_settings'
+const ACTION_VIEW_USER = 'view_user'
+const DEFAULT_SETTINGS: UserSettings = {
+ leverage: 3,
+ position_size_usd: 20,
+ take_profit_pct: null,
+ stop_loss_pct: null,
+ min_confidence: 80,
+}
- if (!isConnected) {
+function LockedCard({ label }: { label: string }) {
+ return (
+
+ )
+}
+
+function BotSettings({
+ connected, subscribed, wallet, initial,
+}: {
+ connected: boolean
+ subscribed: boolean
+ wallet?: string
+ initial: UserSettings
+}) {
+ if (!connected) {
return (
-
-
{t('connectRequired')}
-