;
}
function Ratio({ ratio }) {
// ratio = current daily / 30d avg
const pct = Math.round(ratio * 100);
let color = "var(--text-2)";
let bg = "var(--chip)";
if (ratio > 3) { color = "var(--red-400)"; bg = "color-mix(in oklab, var(--red-500) 18%, transparent)"; }
else if (ratio > 2) { color = "var(--amber-400)"; bg = "color-mix(in oklab, var(--amber-500) 18%, transparent)"; }
else if (ratio > 1.2) { color = "var(--text-2)"; }
else if (ratio > 0) { color = "var(--green-400)"; bg = "color-mix(in oklab, var(--green-500) 14%, transparent)"; }
return (
{ratio > 0 ? pct : 0}%
);
}
function Button({ children, kind = "ghost", size="sm", icon, onClick, className="", ...rest }) {
const sizes = {
xs: "h-6 px-2 text-[11px]",
sm: "h-8 px-2.5 text-[12px]",
md: "h-9 px-3 text-[13px]",
};
const kinds = {
ghost: "border border-soft hover:bg-[var(--surface-2)]",
subtle: "bg-[var(--chip)] hover:bg-[var(--surface-2)] border border-transparent",
primary: "accent-bg hover:brightness-110 border border-transparent font-medium",
danger: "bg-[color-mix(in_oklab,var(--red-500)_18%,transparent)] text-[var(--red-400)] hover:bg-[color-mix(in_oklab,var(--red-500)_26%,transparent)] border border-transparent",
};
return (
);
}
function SearchInput({ value, onChange, placeholder = "Search…", kbd, className="" }) {
return (
onChange(e.target.value)}
placeholder={placeholder}
className="w-full h-8 pl-8 pr-12 rounded-md bg-[var(--surface)] border border-soft text-[12px] placeholder:text-[var(--text-3)]"
/>
{kbd && {kbd}}
);
}
function Select({ value, onChange, options, className="" }) {
return (
);
}
function Chip({ children, tone="default", className="" }) {
const tones = {
default: { color:"var(--text-2)", bg:"var(--chip)" },
accent: { color:"var(--accent-400)", bg:"color-mix(in oklab, var(--accent-500) 14%, transparent)" },
red: { color:"var(--red-400)", bg:"color-mix(in oklab, var(--red-500) 16%, transparent)" },
amber: { color:"var(--amber-400)", bg:"color-mix(in oklab, var(--amber-500) 16%, transparent)" },
green: { color:"var(--green-400)", bg:"color-mix(in oklab, var(--green-500) 16%, transparent)" },
};
const t = tones[tone];
return (
{children}
);
}
function Tabs({ tabs, value, onChange }) {
return (
{tabs.map(t => (
))}
);
}
// Context menu
function ContextMenu({ x, y, items, onClose }) {
const ref = useRef(null);
useEffect(() => {
const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) onClose(); };
const onEsc = (e) => { if (e.key === "Escape") onClose(); };
document.addEventListener("mousedown", onDoc);
document.addEventListener("keydown", onEsc);
return () => { document.removeEventListener("mousedown", onDoc); document.removeEventListener("keydown", onEsc); };
}, [onClose]);
return (
{items.map((it, i) => it.sep
?
: (
))}
);
}
// Toast
function Toast({ text, onDone }) {
useEffect(() => {
const t = setTimeout(onDone, 2400);
return () => clearTimeout(t);
}, []);
return (
);
}
function Breadcrumbs({ items, onNav }) {
return (
);
}
// Diff view inline
function Diff({ oldV, newV, money, ccy }) {
const fmt = (v) => {
if (money) return (ccy ? window.fmtCCY(v, ccy) : window.fmtHKD(v));
if (typeof v === "string" && v.length > 40) return v.slice(0, 40) + "…";
return String(v);
};
return (
{fmt(oldV)}
→
{fmt(newV)}
);
}
Object.assign(window, {
Sparkline, StatusPill, SevDot, Ratio, Button, SearchInput, Select, Chip, Tabs,
ContextMenu, Toast, Breadcrumbs, Diff,
});