style(dashboard): unify Macro composite into one card + dark-mode fix

for Performance accent

User flagged the macro index box on the overview page as feeling
disjointed. Reshaped it from three stacked elements (band → track →
scale) into a single bordered card so it reads as ONE component.

  - JSX: wrap the three pieces in <div className="overview-macro-card">
  - CSS: new .overview-macro-card with tone-coloured rim (bull/bear/neutral)
  - Solid neutral-gray filled needle (was a hollow ring — looked like a
    placeholder); tone-coloured background + white inner ring + double
    shadow so it stands out on any gradient position
  - Removed the .overview-score-fill overlay — the gradient already
    encodes the spectrum; layering an opaque fill obscured it near 0
  - Thinner track (14px vs 22px), tighter scale labels, smaller pill
  - Added "TODAY · 8 INDICATORS" stamp next to the title — gives users
    a quick anchor of what they're looking at + freshness

Plus: dark-mode override for .overview-stat-card.accent (the Performance
card). It was using a cream gradient that floated as a glaring
out-of-theme block on dark mode. Mirrored the existing .kpi.accent dark
treatment so it stays visually grouped with the rest of the dashboard.

Also includes the in-flight overview rewrite from the other AI tool
(legacy /btc redirect to /macro, middleware.ts replacing proxy.ts for
Next.js routing, refactored several dashboard panels). TypeScript clean,
production build passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
k
2026-05-27 11:25:59 +08:00
parent ee3648c4cb
commit d01adc4790
23 changed files with 1793 additions and 592 deletions
+105 -49
View File
@@ -19,6 +19,7 @@ import {
getCachedViewEnvelope,
} from '@/lib/signedRequest'
import { walletErrorLabel } from '@/lib/walletError'
import { getFirstReadyConnector, walletConnectErrorLabel } from '@/lib/walletConnect'
import { confirmSign } from '@/components/wallet/SignConfirmSheet'
// Defaults shown to a brand-new, un-configured wallet. take_profit_pct,
@@ -51,7 +52,7 @@ export default function BotConfigPanel() {
const locale = useLocale()
const isZh = false // i18n shelved — Chinese branches kept as dead code for future revival; see messages/zh.json
const { address, isConnected } = useAccount()
const { connect, connectors } = useConnect()
const { connectAsync, connectors } = useConnect()
const { signMessageAsync } = useSignMessage()
const {
isSubscribed, hlApiKeySet, hlApiKeyMasked, botReadiness,
@@ -97,6 +98,7 @@ export default function BotConfigPanel() {
// cached signature; otherwise wait for the user to click "Load settings".
const [loadState, setLoadState] = useState<'idle'|'loading'|'loaded'|'err'>('idle')
const [loadErr, setLoadErr] = useState('')
const [connectErr, setConnectErr] = useState('')
// Apply a fetched user payload into local form state.
function applyUserPayload(u: {
@@ -296,10 +298,19 @@ export default function BotConfigPanel() {
}
}
const handleConnectWallet = useCallback(() => {
const connector = connectors[0]
if (connector) connect({ connector })
}, [connect, connectors])
const handleConnectWallet = useCallback(async () => {
setConnectErr('')
try {
const connector = await getFirstReadyConnector(connectors)
if (!connector) {
setConnectErr('No wallet connector is available right now.')
return
}
await connectAsync({ connector })
} catch (err: unknown) {
setConnectErr(walletConnectErrorLabel(err))
}
}, [connectAsync, connectors])
// ── Manual-window: arm for N hours, or clear (hours = 0) ──────────────────
async function handleManualWindow(hours: number) {
@@ -335,7 +346,10 @@ export default function BotConfigPanel() {
<p style={{ color: 'var(--ink-3)', marginBottom: 16 }}>
{isZh ? '连接钱包后即可配置机器人并查看你的交易。' : 'Connect your wallet to configure the bot and view your trades.'}
</p>
<button className="btn amber" onClick={handleConnectWallet}>{isZh ? '连接钱包' : 'Connect wallet'}</button>
<button className="btn amber" onClick={() => { void handleConnectWallet() }}>{isZh ? '连接钱包' : 'Connect wallet'}</button>
{connectErr ? (
<p style={{ fontSize: 12, color: 'var(--down)', margin: '12px 0 0' }}>{connectErr}</p>
) : null}
</div>
)
}
@@ -370,6 +384,20 @@ export default function BotConfigPanel() {
: 'API wallet saved. Recommended next step: keep size tiny and run your own first BTC test before treating the setup as production-ready.')
: null
function ScopePill({
label,
tone = 'amber',
}: {
label: string
tone?: 'amber' | 'up' | 'violet'
}) {
return (
<span className={`chip ${tone}`} style={{ fontSize: 10, padding: '2px 8px', marginLeft: 8 }}>
{label}
</span>
)
}
// ── Hyperliquid key strip ─────────────────────────────────────────────────
const hlKeyStrip = (
<div className="card" style={{ padding: '16px 20px', marginBottom: 16, display: 'grid', gridTemplateColumns: '1fr auto', alignItems: 'center', gap: 16 }}>
@@ -537,12 +565,12 @@ export default function BotConfigPanel() {
})()}
{/* ── Main settings card ── */}
<div className="card" style={{ padding: 0, overflow: 'hidden' }}>
<div className="card" id="bot-config-panel" style={{ padding: 0, overflow: 'hidden' }}>
{/* Header bar */}
<div style={{ padding: '20px 24px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderBottom: '1px solid var(--line)' }}>
<div>
<div style={{ fontSize: 16, fontWeight: 600, letterSpacing: '-0.01em' }}>Trading bot</div>
<div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 2 }}>Executes on Hyperliquid when a Trump post clears your filters.</div>
<div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 2 }}>Controls Trump Signal entries, Macro Vibes BTC management, and shared execution limits on Hyperliquid.</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
{dirty && isSubscribed && <span style={{ fontSize: 12, color: 'var(--amber-ink)' }}> Unsaved</span>}
@@ -688,14 +716,17 @@ export default function BotConfigPanel() {
) : (
<div style={{ padding: '8px 24px 24px' }}>
{/* ── EXECUTION ── */}
<div className="section-head">
<span className="section-head-label">Execution</span>
<div className="section-head" id="trump-settings-panel">
<span className="section-head-label">Trump Signal settings</span>
<div style={{ flex: 1, height: 1, background: 'var(--line)' }} />
</div>
<div style={{ fontSize: 12, color: 'var(--ink-3)', margin: '4px 0 10px' }}>
Event-driven trade sizing, leverage, and signal threshold for Trump-triggered entries.
</div>
<div className="form-row">
<div className="form-row-label">
Per-trade size
Per-trade size <ScopePill label="Trump only" />
<span className="hint">Notional in USD. margin ${(settings.position_size_usd / settings.leverage).toFixed(2)} at {settings.leverage}×</span>
</div>
<div className="form-row-control">
@@ -710,7 +741,7 @@ export default function BotConfigPanel() {
<div className="form-row">
<div className="form-row-label">
Leverage <span style={{ opacity: 0.6 }}>(Trump / System 1)</span>
Leverage <span style={{ opacity: 0.6 }}>(Trump / System 1)</span> <ScopePill label="Trump only" />
<span className="hint">Event-driven scalp positions</span>
</div>
<div className="form-row-control">
@@ -726,43 +757,71 @@ export default function BotConfigPanel() {
{(() => {
const aggressive = settings.sys2_mode === 'aggressive'
return (
<>
<div className="section-head" id="btc-settings-panel" style={{ marginTop: 20 }}>
<span className="section-head-label">Macro Vibes settings</span>
<div style={{ flex: 1, height: 1, background: 'var(--line)' }} />
</div>
<div style={{ fontSize: 12, color: 'var(--ink-3)', margin: '4px 0 10px' }}>
Manage-only BTC mode, leverage, and pyramiding behavior for the Macro Vibes system.
</div>
<div className="form-row">
<div className="form-row-label">
BTC strategy mode <span style={{ opacity: 0.6 }}>(System 2)</span>
BTC strategy mode <span style={{ opacity: 0.6 }}>(System 2)</span> <ScopePill label="BTC only" tone="up" />
<span className="hint">
A separately-funded sleeve. <strong>Aggressive</strong> =
high leverage + earlier/heavier pyramiding + wider
trailing explosive in a clean bull, but a normal
2535% correction will scale you out hard. Both modes
stay inside the liquidation line.
Choose how hard the BTC system presses winners. Standard is calmer. Aggressive adds more leverage and wider pyramiding.
</span>
</div>
<div className="form-row-control">
<div style={{ display: 'flex', gap: 6 }}>
<div style={{
display: 'inline-flex',
gap: 6,
padding: 4,
borderRadius: 999,
background: 'var(--bg-sunk)',
border: '1px solid var(--line)',
}}>
<button type="button"
onClick={() => updateSettings({ sys2_mode: 'standard' })}
className={`btn ${!aggressive ? '' : 'ghost'}`}
style={{ padding: '7px 14px', fontSize: 12, fontWeight: 700 }}>
aria-pressed={!aggressive}
style={{
padding: '8px 16px',
fontSize: 12,
fontWeight: 700,
borderRadius: 999,
border: !aggressive ? '1px solid var(--line)' : '1px solid transparent',
background: !aggressive ? 'var(--surface)' : 'transparent',
color: !aggressive ? 'var(--ink)' : 'var(--ink-3)',
boxShadow: !aggressive ? 'var(--shadow-1)' : 'none',
cursor: 'pointer',
}}>
Standard
</button>
<button type="button"
onClick={() => updateSettings({ sys2_mode: 'aggressive' })}
className={`btn ${aggressive ? '' : 'ghost'}`}
style={{ padding: '7px 14px', fontSize: 12, fontWeight: 700,
...(aggressive ? { background: 'var(--down)', color: '#fff', border: 'none' } : {}) }}>
aria-pressed={aggressive}
style={{
padding: '8px 16px',
fontSize: 12,
fontWeight: 700,
borderRadius: 999,
border: aggressive ? '1px solid var(--down)' : '1px solid transparent',
background: aggressive ? 'var(--down)' : 'transparent',
color: aggressive ? '#fff' : 'var(--ink-3)',
boxShadow: aggressive ? 'var(--shadow-1)' : 'none',
cursor: 'pointer',
}}>
🔥 Aggressive
</button>
</div>
{aggressive && (
<div className="hint" style={{ marginTop: 6, color: 'var(--down)' }}>
High-risk sleeve: default 8×, pyramids up to +1.5×
base, gives back up to 42% off the peak. Fund this
separately expect frequent full scale-outs on
corrections in exchange for explosive clean runs.
<div className="settings-note warn" style={{ marginTop: 6 }}>
High-risk sleeve. Uses more leverage, pyramids harder, and gives back more off the peak. Best treated as a separate risk bucket.
</div>
)}
</div>
</div>
</>
)
})()}
@@ -778,12 +837,9 @@ export default function BotConfigPanel() {
return (
<div className="form-row">
<div className="form-row-label">
BTC bottom leverage <span style={{ opacity: 0.6 }}>(System 2)</span>
BTC bottom leverage <span style={{ opacity: 0.6 }}>(System 2)</span> <ScopePill label="BTC only" tone="up" />
<span className="hint">
Independent of Trump. Wrong scales OUT in 3 stages
inside the liquidation line (<strong>never
exchange-liquidated</strong>). Right pyramids IN on a
confirmed trend, stop floored at breakeven.
Separate from Trump leverage. The bot de-risks in stages before the exchange liquidation line.
</span>
</div>
<div className="form-row-control">
@@ -793,16 +849,13 @@ export default function BotConfigPanel() {
<div className="ticks"><span>1×</span><span>5×</span><span>10×</span></div>
</div>
<span className="slider-readout">{lev}×</span>
<div className="hint" style={{ marginTop: 6, color: risky ? 'var(--down)' : 'var(--ink-3)' }}>
At {lev}× it sheds {e1} near {(prot * 0.6).toFixed(0)}%,
{' '}{e2} near {(prot * 0.8).toFixed(0)}%, fully out by
{prot.toFixed(0)}% (exchange would liquidate
{liq.toFixed(0)}%).{' '}
<div className={`settings-note ${risky ? 'warn' : ''}`} style={{ marginTop: 6 }}>
At {lev}× it sheds {e1} near {(prot * 0.6).toFixed(0)}%, {e2} near {(prot * 0.8).toFixed(0)}%, and is fully out by {prot.toFixed(0)}%.
Exchange liquidation would be around {liq.toFixed(0)}%.
{' '}
{risky
? (aggressive
? '🔥 Aggressive: a normal 2535% bull correction will scale you out — youre betting on clean explosive runs.'
: '⚠️ Above 2× a normal 2535% bull correction can scale you out before the top. To ride a super-bull, keep ≤2× — amplify via pyramiding, not leverage.')
: 'Wide enough to ride normal bull corrections; amplification comes from pyramiding.'}
? 'Above 2×, a normal bull-market correction can push you out early.'
: 'This is wide enough to survive a normal correction; extra upside should come from pyramiding, not leverage.'}
</div>
</div>
</div>
@@ -811,7 +864,7 @@ export default function BotConfigPanel() {
<div className="form-row">
<div className="form-row-label">
Min AI confidence
Min AI confidence <ScopePill label="Trump only" />
<span className="hint">Skip any signal below this score (0100)</span>
</div>
<div className="form-row-control">
@@ -825,14 +878,17 @@ export default function BotConfigPanel() {
</div>
{/* ── LIMITS ── */}
<div className="section-head" style={{ marginTop: 20 }}>
<span className="section-head-label">Limits &amp; risk</span>
<div className="section-head" id="global-settings-panel" style={{ marginTop: 20 }}>
<span className="section-head-label">Global risk limits</span>
<div style={{ flex: 1, height: 1, background: 'var(--line)' }} />
</div>
<div style={{ fontSize: 12, color: 'var(--ink-3)', margin: '4px 0 10px' }}>
Shared safety rules that cap or gate automated trading across the account.
</div>
<div className="form-row">
<div className="form-row-label">
Daily budget <span style={{ color: 'var(--down)' }}>*</span>
Daily budget <span style={{ color: 'var(--down)' }}>*</span> <ScopePill label="Global" tone="violet" />
<span className="hint">Stop opening new trades once the day&apos;s total notional reaches this (UTC). Required.</span>
</div>
<div className="form-row-control">
@@ -849,7 +905,7 @@ export default function BotConfigPanel() {
<div className="form-row">
<div className="form-row-label">
Take profit <span style={{ color: 'var(--down)' }}>*</span>
Take profit <span style={{ color: 'var(--down)' }}>*</span> <ScopePill label="Trump only" />
<span className="hint">Auto-close when unrealised gain hits target. Required.</span>
</div>
<div className="form-row-control">
@@ -865,7 +921,7 @@ export default function BotConfigPanel() {
<div className="form-row">
<div className="form-row-label">
Stop loss <span style={{ color: 'var(--down)' }}>*</span>
Stop loss <span style={{ color: 'var(--down)' }}>*</span> <ScopePill label="Trump only" />
<span className="hint">Auto-close when drawdown hits limit. Required.</span>
</div>
<div className="form-row-control">