Barbie’s Heat Map
import React, { useState, useMemo } from "react";
// Sample sector data — swap in a live feed later (Massive/TradingView/etc.)
const SECTORS = [
{ name: "Technology", ticker: "XLK", weight: 31, perf: 2.76, relVol: 1.42, iv: 24, week: 3.4, relStr: 99 },
{ name: "Financials", ticker: "XLF", weight: 13, perf: -0.20, relVol: 0.95, iv: 18, week: 0.8, relStr: 58 },
{ name: "Health Care", ticker: "XLV", weight: 11, perf: -1.29, relVol: 1.05, iv: 16, week: -1.9, relStr: 34 },
{ name: "Consumer Disc.", ticker: "XLY", weight: 11, perf: 0.14, relVol: 1.18, iv: 22, week: 1.1, relStr: 61 },
{ name: "Communication", ticker: "XLC", weight: 9, perf: -0.70, relVol: 1.10, iv: 21, week: 0.5, relStr: 52 },
{ name: "Industrials", ticker: "XLI", weight: 8, perf: 1.35, relVol: 1.22, iv: 19, week: 2.0, relStr: 74 },
{ name: "Consumer Stap.", ticker: "XLP", weight: 6, perf: -1.54, relVol: 0.88, iv: 13, week: -2.2, relStr: 21 },
{ name: "Energy", ticker: "XLE", weight: 4, perf: -0.88, relVol: 1.35, iv: 27, week: -0.6, relStr: 41 },
{ name: "Utilities", ticker: "XLU", weight: 3, perf: -1.48, relVol: 0.79, iv: 14, week: -1.4, relStr: 25 },
{ name: "Materials", ticker: "XLB", weight: 2, perf: 0.34, relVol: 1.02, iv: 20, week: 1.5, relStr: 65 },
{ name: "Real Estate", ticker: "XLRE", weight: 2, perf: -1.98, relVol: 0.91, iv: 17, week: -2.8, relStr: 12 },
];
const METRICS = [
{ key: "perf", label: "Performance", suffix: "%", domain: [-2, 2] },
{ key: "relVol", label: "Rel. Volume", suffix: "x", domain: [0.5, 1.6] },
{ key: "iv", label: "Volatility", suffix: "%", domain: [10, 30] },
];
function colorFor(value, domain) {
const [lo, hi] = domain;
const mid = (lo + hi) / 2;
const t = Math.max(-1, Math.min(1, (value - mid) / (mid - lo)));
// negative -> deep magenta/plum, positive -> gold/lime edge, near-zero -> charcoal
if (t >= 0) {
// 0 -> charcoal, 1 -> gold
const g1 = [26, 24, 28];
const g2 = [212, 175, 55];
const c = g1.map((v, i) => Math.round(v + (g2[i] - v) * t));
return `rgb(${c.join(",")})`;
} else {
const at = -t;
const g1 = [26, 24, 28];
const g2 = [255, 45, 120];
const c = g1.map((v, i) => Math.round(v + (g2[i] - v) * at));
return `rgb(${c.join(",")})`;
}
}
function textColorFor(value, domain) {
const [lo, hi] = domain;
const mid = (lo + hi) / 2;
const t = Math.abs((value - mid) / (mid - lo));
return t > 0.45 ? "#0d0d0d" : "#f5f1ea";
}
export default function BarbieHeatmap() {
const [metric, setMetric] = useState("perf");
const [selected, setSelected] = useState(SECTORS[0].ticker);
const activeMetric = METRICS.find((m) => m.key === metric);
const selectedSector = SECTORS.find((s) => s.ticker === selected);
const sorted = useMemo(
() => [...SECTORS].sort((a, b) => b.weight - a.weight),
[]
);
const flowingIn = [...SECTORS].sort((a, b) => b.perf - a.perf).slice(0, 4);
const flowingOut = [...SECTORS].sort((a, b) => a.perf - b.perf).slice(0, 4);
return (
<div
style={{
minHeight: "100vh",
background: "#0d0d0d",
color: "#f5f1ea",
fontFamily: "'DM Sans', ui-sans-serif, system-ui, sans-serif",
padding: "28px 18px 60px",
}}
>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700;900&family=DM+Sans:wght@400;500;700&display=swap');
.corner { position: relative; }
.corner::before, .corner::after {
content: ""; position: absolute; width: 10px; height: 10px;
border-color: #D4AF37; border-style: solid; opacity: 0.75;
}
.corner::before { top: -1px; left: -1px; border-width: 2px 0 0 2px; }
.corner::after { bottom: -1px; right: -1px; border-width: 0 2px 2px 0; }
.tile { transition: transform 0.15s ease, box-shadow 0.15s ease; cursor: pointer; }
.tile:hover { transform: translateY(-2px); box-shadow: 0 6px 24px rgba(255,45,120,0.25); }
.tab-btn { transition: color 0.15s ease, border-color 0.15s ease; }
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-thumb { background: #ff2d78; border-radius: 4px; }
`}</style>
<div style={{ maxWidth: 980, margin: "0 auto" }}>
{/* Header */}
<div style={{ marginBottom: 22 }}>
<div
style={{
fontFamily: "'DM Sans', sans-serif",
fontSize: 11,
letterSpacing: "0.18em",
textTransform: "uppercase",
color: "#ff2d78",
marginBottom: 6,
}}
>
Trader Barbie · Command Center
</div>
<h1
style={{
fontFamily: "'Playfair Display', serif",
fontWeight: 900,
fontSize: "clamp(28px, 5vw, 42px)",
margin: 0,
lineHeight: 1.05,
}}
>
The Market, Mapped
</h1>
<p style={{ color: "#a8a29e", fontSize: 14, marginTop: 8, maxWidth: 520 }}>
Block size is sector weight. Color is the move. Tap a sector for the why
behind the color.
</p>
</div>
{/* Tabs */}
<div
style={{
display: "flex",
gap: 4,
borderBottom: "1px solid #262626",
marginBottom: 18,
}}
>
{METRICS.map((m) => (
<button
key={m.key}
onClick={() => setMetric(m.key)}
className="tab-btn"
style={{
background: "none",
border: "none",
borderBottom:
metric === m.key ? "2px solid #FF2D78" : "2px solid transparent",
color: metric === m.key ? "#f5f1ea" : "#78716c",
fontFamily: "'DM Sans', sans-serif",
fontWeight: 700,
fontSize: 13,
padding: "8px 14px",
cursor: "pointer",
}}
>
{m.label}
</button>
))}
</div>
{/* Heatmap grid */}
<div
className="corner"
style={{
display: "grid",
gridTemplateColumns: "repeat(6, 1fr)",
gridAutoRows: "70px",
gap: 3,
padding: 4,
background: "#141212",
border: "1px solid #262626",
}}
>
{sorted.map((s) => {
const span = Math.max(1, Math.round((s.weight / 31) * 3));
const value = s[metric];
return (
<div
key={s.ticker}
className="tile"
onClick={() => setSelected(s.ticker)}
style={{
gridColumn: `span ${Math.min(span + 1, 3)}`,
gridRow: `span ${s.weight > 15 ? 2 : 1}`,
background: colorFor(value, activeMetric.domain),
color: textColorFor(value, activeMetric.domain),
border:
selected === s.ticker
? "2px solid #D4AF37"
: "1px solid rgba(0,0,0,0.2)",
padding: "8px 10px",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
}}
>
<div>
<div style={{ fontWeight: 700, fontSize: 12.5, lineHeight: 1.15 }}>
{s.name}
</div>
<div style={{ fontSize: 10.5, opacity: 0.75, fontWeight: 500 }}>
{s.ticker}
</div>
</div>
<div style={{ fontFamily: "'Playfair Display', serif", fontWeight: 700, fontSize: 15 }}>
{value > 0 ? "+" : ""}
{value.toFixed(2)}
{activeMetric.suffix}
</div>
</div>
);
})}
</div>
{/* Legend */}
<div style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 10, fontSize: 11, color: "#78716c" }}>
<span>{activeMetric.domain[0]}{activeMetric.suffix}</span>
<div
style={{
flex: 1,
height: 6,
background: "linear-gradient(90deg, #ff2d78, #1a181c, #D4AF37)",
borderRadius: 3,
}}
/>
<span>+{activeMetric.domain[1]}{activeMetric.suffix}</span>
</div>
{/* Detail panel */}
{selectedSector && (
<div
className="corner"
style={{
marginTop: 26,
background: "#141212",
border: "1px solid #262626",
padding: "20px 22px",
}}
>
<div style={{ fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: "#D4AF37" }}>
Selected sector · {selectedSector.ticker}
</div>
<h2
style={{
fontFamily: "'Playfair Display', serif",
fontWeight: 700,
fontSize: 26,
margin: "4px 0 4px",
}}
>
{selectedSector.name}
</h2>
<div
style={{
fontFamily: "'Playfair Display', serif",
fontWeight: 900,
fontSize: 22,
color: selectedSector.perf >= 0 ? "#D4AF37" : "#ff2d78",
marginBottom: 10,
}}
>
{selectedSector.perf > 0 ? "+" : ""}
{selectedSector.perf.toFixed(2)}%
</div>
<p style={{ fontSize: 13.5, color: "#c9c4bc", maxWidth: 560, lineHeight: 1.5 }}>
{selectedSector.perf >= 0.5
? `Among the day's stronger groups, up ${selectedSector.perf.toFixed(2)}% on ${selectedSector.relVol >= 1.1 ? "above-average" : "lighter-than-usual"} volume. One-week trend ${selectedSector.week > 0 ? "+" : ""}${selectedSector.week.toFixed(1)}%.`
: selectedSector.perf <= -0.5
? `Among the day's weaker groups, down ${Math.abs(selectedSector.perf).toFixed(2)}% on ${selectedSector.relVol >= 1.1 ? "above-average" : "lighter-than-usual"} volume. One-week trend ${selectedSector.week > 0 ? "+" : ""}${selectedSector.week.toFixed(1)}%.`
: `Roughly flat on the day at ${selectedSector.perf.toFixed(2)}%, trading on ${selectedSector.relVol >= 1.1 ? "above-average" : "lighter-than-usual"} volume. One-week trend ${selectedSector.week > 0 ? "+" : ""}${selectedSector.week.toFixed(1)}%.`}
</p>
<div style={{ display: "flex", gap: 26, marginTop: 14, flexWrap: "wrap" }}>
{[
["1D", `${selectedSector.perf > 0 ? "+" : ""}${selectedSector.perf.toFixed(2)}%`],
["1W", `${selectedSector.week > 0 ? "+" : ""}${selectedSector.week.toFixed(1)}%`],
["Rel Str", selectedSector.relStr],
].map(([label, val]) => (
<div key={label}>
<div style={{ fontSize: 10.5, color: "#78716c", letterSpacing: "0.1em", textTransform: "uppercase" }}>
{label}
</div>
<div style={{ fontFamily: "'Playfair Display', serif", fontWeight: 700, fontSize: 18 }}>
{val}
</div>
</div>
))}
</div>
{/* Rotation */}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 18, marginTop: 22 }}>
<div>
<div style={{ fontSize: 11, letterSpacing: "0.1em", textTransform: "uppercase", color: "#D4AF37", marginBottom: 8 }}>
Money flowing in
</div>
{flowingIn.map((s) => (
<div key={s.ticker} style={{ display: "flex", justifyContent: "space-between", fontSize: 13, padding: "5px 0", borderBottom: "1px solid #1f1f1f" }}>
<span>{s.name}</span>
<span style={{ color: "#D4AF37", fontWeight: 700 }}>+{s.perf.toFixed(2)}%</span>
</div>
))}
</div>
<div>
<div style={{ fontSize: 11, letterSpacing: "0.1em", textTransform: "uppercase", color: "#ff2d78", marginBottom: 8 }}>
Money flowing out
</div>
{flowingOut.map((s) => (
<div key={s.ticker} style={{ display: "flex", justifyContent: "space-between", fontSize: 13, padding: "5px 0", borderBottom: "1px solid #1f1f1f" }}>
<span>{s.name}</span>
<span style={{ color: "#ff2d78", fontWeight: 700 }}>{s.perf.toFixed(2)}%</span>
</div>
))}
</div>
</div>
</div>
)}
<div style={{ marginTop: 20, fontSize: 11, color: "#57534e" }}>
Sample data for layout preview — wire up a live quotes feed to replace it. Educational purposes only, not financial advice.
</div>
</div>
</div>
);
}