summaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'frontend')
-rw-r--r--frontend/app/page.tsx2
-rw-r--r--frontend/app/prism-shell.css51
-rw-r--r--frontend/components/prism/VolumeCard.tsx51
3 files changed, 104 insertions, 0 deletions
diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx
index 7ccb781..d6bde2a 100644
--- a/frontend/app/page.tsx
+++ b/frontend/app/page.tsx
@@ -11,6 +11,7 @@ import { KPIStrip } from "@/components/prism/KPIStrip";
import { Sidebar } from "@/components/prism/Sidebar";
import { TickerHeader } from "@/components/prism/TickerHeader";
import { TopBar } from "@/components/prism/TopBar";
+import { VolumeCard } from "@/components/prism/VolumeCard";
import { ApiError, api } from "@/lib/api";
import { deltaClass, fmtCurrency, fmtLarge, fmtNumber, fmtPct } from "@/lib/format";
import { buildKpis, limitIndices, marketClock, OVERVIEW_NAV_ITEMS, signalTone } from "@/lib/overview";
@@ -309,6 +310,7 @@ function OverviewClient() {
<div className="psm-main-grid">
<div className="psm-column">
<ChartCard symbol={overview.profile.symbol} period={period} points={history} chartState={chartState} chartError={chartError} onChangePeriod={setPeriod} />
+ <VolumeCard overview={overview} />
<SignalCard overview={overview} />
</div>
<div className="psm-column">
diff --git a/frontend/app/prism-shell.css b/frontend/app/prism-shell.css
index 7b9ccf4..ce9b3bd 100644
--- a/frontend/app/prism-shell.css
+++ b/frontend/app/prism-shell.css
@@ -1829,3 +1829,54 @@
width: 64px;
}
}
+
+/* ── Volume card ────────────────────────────────── */
+
+.psm-vol-list {
+ display: flex;
+ flex-direction: column;
+ gap: var(--sp-3);
+}
+
+.psm-vol-row {
+ display: grid;
+ grid-template-columns: 52px 1fr auto;
+ gap: var(--sp-3);
+ align-items: center;
+}
+
+.psm-vol-label {
+ color: var(--fg-4);
+ font-size: var(--fs-12);
+ font-weight: 600;
+ letter-spacing: var(--tr-wider);
+ text-transform: uppercase;
+}
+
+.psm-vol-track {
+ height: 4px;
+ border-radius: var(--r-full);
+ background: var(--ink-3);
+ overflow: hidden;
+}
+
+.psm-vol-fill {
+ height: 100%;
+ border-radius: var(--r-full);
+ background: var(--info);
+ transition: width 300ms ease;
+}
+
+.psm-vol-fill.accent {
+ background: var(--brass);
+}
+
+.psm-vol-value {
+ color: var(--fg-2);
+ font-family: var(--font-mono);
+ font-size: var(--fs-13);
+ font-variant-numeric: tabular-nums;
+ text-align: right;
+ white-space: nowrap;
+ min-width: 60px;
+}
diff --git a/frontend/components/prism/VolumeCard.tsx b/frontend/components/prism/VolumeCard.tsx
new file mode 100644
index 0000000..08b4059
--- /dev/null
+++ b/frontend/components/prism/VolumeCard.tsx
@@ -0,0 +1,51 @@
+import type { TickerOverview } from "@/types/api";
+import { fmtNumber } from "@/lib/format";
+
+function activityLabel(ratio: number): string {
+ if (ratio >= 1.5) return "elevated activity";
+ if (ratio < 0.7) return "below average";
+ return "normal activity";
+}
+
+export function VolumeCard({ overview }: { overview: TickerOverview }) {
+ const today = overview.stats.volume;
+ const avg = overview.stats.average_volume;
+ if (today == null && avg == null) return null;
+
+ const max = Math.max(today ?? 0, avg ?? 0);
+ const todayPct = today != null && max > 0 ? Math.round((today / max) * 100) : 0;
+ const avgPct = avg != null && max > 0 ? Math.round((avg / max) * 100) : 0;
+ const ratio = today != null && avg != null && avg > 0 ? today / avg : null;
+
+ return (
+ <section className="psm-card">
+ <div className="psm-card-head">
+ <div>
+ <div className="psm-eyebrow">Volume</div>
+ <h2 className="psm-card-title">Volume</h2>
+ </div>
+ </div>
+ <div className="psm-vol-list">
+ <div className="psm-vol-row">
+ <span className="psm-vol-label">Today</span>
+ <div className="psm-vol-track">
+ <div className="psm-vol-fill accent" style={{ width: `${todayPct}%` }} />
+ </div>
+ <span className="psm-vol-value">{today != null ? fmtNumber(today, 0) : "—"}</span>
+ </div>
+ <div className="psm-vol-row">
+ <span className="psm-vol-label">30d avg</span>
+ <div className="psm-vol-track">
+ <div className="psm-vol-fill" style={{ width: `${avgPct}%` }} />
+ </div>
+ <span className="psm-vol-value">{avg != null ? fmtNumber(avg, 0) : "—"}</span>
+ </div>
+ </div>
+ {ratio != null && (
+ <p className="psm-muted-copy" style={{ marginTop: "var(--sp-3)" }}>
+ {ratio >= 1 ? "↑" : "↓"} {ratio.toFixed(2)}× average · {activityLabel(ratio)}
+ </p>
+ )}
+ </section>
+ );
+}