fix: pre-launch UI hardening + KOL reduce-action type, proxy IP relay, settings redesign

Frontend half of the pre-launch audit campaign:

- types/index.ts + kol/KolPageClient.tsx: add missing 'reduce' KolAction
  (backend emits it; frontend lacked the type + color/label maps → undefined
  styling). Adds ACTION_COLOR/actionLabel/postActionLabel entries.
- proxy/[...path]/route.ts: relay real client IP (x-forwarded-for / x-real-ip)
  so the backend rate limiter buckets per-user instead of per-Next-server (BUG-02).
- Settings/BotConfigPanel redesign, paper-mode clarity, copy cleanup.
- Assorted page/display fixes, loading states, Pagination component.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
k
2026-05-29 11:57:43 +08:00
parent b76de36af0
commit d50c05b120
32 changed files with 1003 additions and 224 deletions
+140 -51
View File
@@ -224,8 +224,9 @@ export default function LandingPage() {
useCursorVars(rootRef as React.RefObject<HTMLElement>)
// Decoded hero text — assembles from random glyphs on mount.
const heroLine1 = useScramble('Where crypto alpha actually lives —', 1100)
const heroLine2 = useScramble('all in one tab.', 1500)
const heroLine1 = useScramble('Trump. KOL. Macro.', 900)
const heroLine2 = useScramble('Six engines.', 1200)
const heroLine3 = useScramble('Always first.', 1500)
// primaryRef / useMagnetic intentionally removed — the hero CTA is now a
// static button. The hook is still defined above in case the effect is
// reinstated on another element later.
@@ -254,56 +255,74 @@ export default function LandingPage() {
<div className="lp-wrap">
{/* ---------- HERO ---------- */}
<section className="lp-hero">
<div className="lp-live-badge">
<span className="lp-live-dot" />
Six signal engines running · right now
</div>
<h1 className="lp-h1 lp-decode">
<span className="lp-decode-line">{heroLine1}</span>
<br />
<span className="grad lp-decode-line">{heroLine2}</span>
</h1>
<p className="lp-sub">
Trump posts. On-chain bottoms. KOL essays. Wallets that move before
they tweet. We scrape every edge, score it with AI, and surface only
what&rsquo;s tradeable. You sleep. It doesn&rsquo;t.
</p>
<div className="lp-ctas">
<Link href="/en" className="lp-btn lp-btn-primary">
Launch Dashboard
<span className="lp-arrow"></span>
</Link>
<a href="#pillars" className="lp-btn lp-btn-secondary">
See all six signals
</a>
</div>
{/* Live counter strip — animates in when hero is visible */}
<HeroStats />
{/* Ticker strip */}
<div className="lp-ticker" aria-hidden>
<div className="lp-ticker-track">
{[0, 1].map((k) => (
<div key={k} style={{ display: 'flex', gap: 48 }}>
<TickerItem asset="BTC" kind="buy" text="Post scored 82 · AUTO-LONG fired" />
<TickerItem asset="ETH" kind="hold" text="Below user confidence floor · skipped" />
<TickerItem asset="BTC" kind="sell" text="Signal SHORT · TP/SL armed" />
<TickerItem asset="ETH" kind="buy" text="Post scored 74 · AUTO-LONG fired" />
<TickerItem asset="BTC" kind="hold" text="Outside active window · standing by" />
<TickerItem asset="ETH" kind="sell" text="Signal SHORT · position opened" />
</div>
))}
{/* LEFT column */}
<div className="lp-hero-left">
<div className="lp-live-badge">
<span className="lp-live-dot" />
6 signal engines · live right now
</div>
<h1 className="lp-h1 lp-decode">
<span className="lp-decode-line">{heroLine1}</span>
<br />
<span className="grad lp-decode-line">{heroLine2}</span>
<br />
<span className="lp-decode-line">{heroLine3}</span>
</h1>
<p className="lp-sub">
AI reads every signal in under 3&nbsp;seconds.
You&rsquo;re in the position before the headline hits.
</p>
<div className="lp-ctas">
<Link href="/en" className="lp-btn lp-btn-primary">
Launch Dashboard
<span className="lp-arrow"></span>
</Link>
<a href="#pillars" className="lp-btn lp-btn-secondary">
See all six signals
</a>
</div>
{/* Engine chips — show breadth at a glance */}
<div className="lp-engine-chips">
<span className="lp-engine-chip trump">Trump Signal</span>
<span className="lp-engine-chip macro">Macro Vibes</span>
<span className="lp-engine-chip kol">KOL Signal</span>
<span className="lp-engine-chip tvt">Talks vs Trades</span>
<span className="lp-engine-chip fund">Funding Reversal</span>
<span className="lp-engine-chip auto">Auto-Trader</span>
</div>
{/* Live counter strip */}
<HeroStats />
</div>
{/* RIGHT column — X-style live signal feed */}
<div className="lp-hero-right">
<LiveFeed />
</div>
</section>
{/* Ticker strip — below the 2-col hero */}
<div className="lp-ticker" aria-hidden>
<div className="lp-ticker-track">
{[0, 1].map((k) => (
<div key={k} style={{ display: 'flex', gap: 48 }}>
<TickerItem asset="BTC" kind="buy" text="Post scored 82 · AUTO-LONG fired" />
<TickerItem asset="ETH" kind="hold" text="Below user confidence floor · skipped" />
<TickerItem asset="BTC" kind="sell" text="Signal SHORT · TP/SL armed" />
<TickerItem asset="ETH" kind="buy" text="Post scored 74 · AUTO-LONG fired" />
<TickerItem asset="BTC" kind="hold" text="Outside active window · standing by" />
<TickerItem asset="ETH" kind="sell" text="Signal SHORT · position opened" />
</div>
))}
</div>
</div>
{/* ---------- FOUR PILLARS ---------- */}
<section id="pillars" className="lp-section" style={{ paddingTop: 60 }}>
<section id="pillars" className="lp-section" style={{ paddingTop: 48 }}>
<div className="lp-reveal" style={{ maxWidth: 680 }}>
<div className="lp-eyebrow">What runs inside</div>
<h2 className="lp-h2">
@@ -369,7 +388,7 @@ export default function LandingPage() {
</section>
{/* ---------- THE ENGINE (terminal block) — Pillar 1 deep-dive ---------- */}
<section id="engine" className="lp-section" style={{ paddingTop: 60 }}>
<section id="engine" className="lp-section" style={{ paddingTop: 48 }}>
<div className="lp-reveal" style={{ maxWidth: 640 }}>
<div className="lp-eyebrow">Pillar 1 · live engine</div>
<h2 className="lp-h2">
@@ -439,7 +458,7 @@ export default function LandingPage() {
</section>
{/* ---------- PRESS / HEADLINES ---------- */}
<section className="lp-section" style={{ paddingTop: 60 }}>
<section className="lp-section" style={{ paddingTop: 48 }}>
<div className="lp-reveal" style={{ maxWidth: 680 }}>
<div className="lp-eyebrow">Headlines you can Google</div>
<h2 className="lp-h2">
@@ -598,7 +617,7 @@ export default function LandingPage() {
</section>
{/* ---------- COMPARISON TABLE ---------- */}
<section id="compare" className="lp-section" style={{ paddingTop: 60 }}>
<section id="compare" className="lp-section" style={{ paddingTop: 48 }}>
<div className="lp-reveal" style={{ maxWidth: 680 }}>
<div className="lp-eyebrow">How it compares</div>
<h2 className="lp-h2">
@@ -631,7 +650,7 @@ export default function LandingPage() {
</section>
{/* ---------- WHAT IS TRUMP ALPHA (canonical definition for GEO) ---------- */}
<section id="about" className="lp-section" style={{ paddingTop: 80 }}>
<section id="about" className="lp-section" style={{ paddingTop: 56 }}>
<div className="lp-reveal" style={{ maxWidth: 720 }}>
<div className="lp-eyebrow">What is Trump Alpha</div>
<h2 className="lp-h2">
@@ -730,6 +749,76 @@ export default function LandingPage() {
)
}
/* ---------- Live feed (hero right column) ---------- */
function LiveFeed() {
return (
<div className="lp-livefeed">
<div className="lp-livefeed-head">
<span className="lp-livefeed-dot" />
Live signal feed
<span className="lp-livefeed-count">3 today</span>
</div>
<LiveSignalPost
isNew
ts="2m ago"
text="BITCOIN is the FUTURE of money. America will LEAD the world in crypto. BIG things coming very soon. ENJOY!"
signal={{ type: 'LONG', asset: 'BTC', conf: 82, action: 'AUTO-LONG fired' }}
/>
<LiveSignalPost
ts="14m ago"
text="Massive new TARIFFS on China coming — they have RIPPED OFF the USA for decades. No more!"
signal={{ type: 'SHORT', asset: 'BTC', conf: 71, action: 'AUTO-SHORT fired' }}
/>
<LiveSignalPost
ts="38m ago"
text="Crooked Joe and the Radical Left Lunatics are destroying our beautiful country. SAD!"
signal={{ type: 'NOISE', skip: 'off-topic · skipped' }}
/>
</div>
)
}
function LiveSignalPost({
isNew, ts, text, signal,
}: {
isNew?: boolean
ts: string
text: string
signal:
| { type: 'LONG' | 'SHORT'; asset: string; conf: number; action: string }
| { type: 'NOISE'; skip: string }
}) {
const t = signal.type
return (
<div className={`lp-signal-post${isNew ? ' is-new' : ''}`}>
<div className="lp-signal-post-head">
<span className="lp-signal-handle">Donald Trump</span>
<span className="lp-signal-at">@realDonaldTrump</span>
{isNew && <span className="lp-signal-new-badge">new</span>}
<span className="lp-signal-ts">{ts}</span>
</div>
<p className="lp-signal-text">{text}</p>
<div className="lp-signal-chips">
<span className={`lp-signal-chip ${t === 'LONG' ? 'long' : t === 'SHORT' ? 'short' : 'noise'}`}>
{t}
</span>
{'asset' in signal && (
<>
<span className="lp-signal-chip asset">{signal.asset}</span>
<span className="lp-signal-chip conf">CONF&nbsp;{signal.conf}</span>
<span className={`lp-signal-chip ${signal.action.includes('fired') ? 'action-ok' : 'action-skip'}`}>
· {signal.action}
</span>
</>
)}
{'skip' in signal && (
<span className="lp-signal-chip action-skip">· {signal.skip}</span>
)}
</div>
</div>
)
}
/* ---------- Terminal row ---------- */
function TermRow({
isNew,