/* Primitive components: buttons, badges, cards, chips, status dots */
const Card = ({ children, style, className = "", padded = true, ...props }) => (
{children}
);
const Btn = ({ children, variant = "default", size = "md", icon, style, ...props }) => {
const sizes = {
sm: { padding: "5px 10px", fontSize: 12, height: 28 },
md: { padding: "7px 14px", fontSize: 13, height: 34 },
lg: { padding: "10px 18px", fontSize: 14, height: 40 },
};
const variants = {
default: { background: "var(--bg-2)", color: "var(--text)", border: "1px solid var(--line)" },
primary: { background: "var(--accent)", color: "var(--accent-fg)", border: "1px solid var(--accent)" },
ghost: { background: "transparent", color: "var(--text-2)", border: "1px solid transparent" },
danger: { background: "transparent", color: "var(--bad)", border: "1px solid var(--bad-soft)" },
};
return (
);
};
const Badge = ({ children, tone = "default", style, ...props }) => {
const tones = {
default: { background: "var(--bg-2)", color: "var(--text-2)", border: "1px solid var(--line)" },
good: { background: "var(--good-soft)", color: "var(--good)", border: "1px solid transparent" },
warn: { background: "var(--warn-soft)", color: "var(--warn)", border: "1px solid transparent" },
bad: { background: "var(--bad-soft)", color: "var(--bad)", border: "1px solid transparent" },
info: { background: "var(--info-soft)", color: "var(--info)", border: "1px solid transparent" },
accent: { background: "var(--accent-soft)", color: "var(--accent)", border: "1px solid transparent" },
solid: { background: "var(--text)", color: "var(--bg)", border: "1px solid var(--text)" },
};
return (
{children}
);
};
const Dot = ({ tone = "good", pulse = false, size = 6 }) => (
);
const Chip = ({ children, active = false, onClick, icon }) => (
);
/* Source attribution: π Garmin Β· β Eufy etc */
const Source = ({ sources = [] }) => (
fuente
{sources.map((s, i) => (
{i > 0 && Β·}
{s}
))}
);
/* Trend arrow with delta */
const Trend = ({ delta, unit = "", goodDirection = "up", size = "md" }) => {
const isUp = delta > 0;
const isGood = (goodDirection === "up" && isUp) || (goodDirection === "down" && !isUp);
const isFlat = Math.abs(delta) < 0.5;
const tone = isFlat ? "var(--text-3)" : isGood ? "var(--good)" : "var(--bad)";
const arrow = isFlat ? "β" : isUp ? "β" : "β";
const fontSize = size === "lg" ? 14 : size === "sm" ? 10 : 12;
return (
{arrow}
{isFlat ? "0" : (isUp ? "+" : "") + delta.toFixed(1)}{unit}
);
};
/* Capability badge: β
available / β partial / β unavailable */
const CapBadge = ({ status, label, hint }) => {
const map = {
ok: { dot: "good", text: "var(--text-2)", icon: "β" },
partial: { dot: "warn", text: "var(--warn)", icon: "β" },
none: { dot: "text-3", text: "var(--text-3)", icon: "β" },
};
const s = map[status] || map.none;
return (
{s.icon}
{label}
);
};
/* IconBox: simple geometric icon containers (we avoid emoji in product UI) */
const Icon = ({ name, size = 16, color = "currentColor", strokeWidth = 1.5 }) => {
const paths = {
home: <>>,
activity: ,
metrics: <>>,
chat: ,
plan: <>>,
settings: <>>,
heart: ,
moon: ,
flame: ,
weight: <>>,
plus: <>>,
check: ,
arrow: <>>,
sync: <>>,
zap: ,
lock: <>>,
cloud: ,
info: <>>,
alert: <>>,
bluetooth: ,
chevR: ,
chevD: ,
close: ,
search: <>>,
map: <>>,
bolt: ,
target: <>>,
droplet: ,
user: <>>,
bot: <>>,
medical: ,
coach: <>>,
apple: <>>,
refresh: <>>,
merge: <>>,
clock: <>>,
spinner: ,
agents: <>>,
book: <>>,
x: ,
};
const spinning = name === "spinner";
return (
);
};
if (typeof document !== "undefined" && !document.getElementById("af-spin-style")) {
const s = document.createElement("style");
s.id = "af-spin-style";
s.textContent = "@keyframes af-spin { to { transform: rotate(360deg); } }";
document.head.appendChild(s);
}
/* Empty state */
const Empty = ({ icon = "info", title, body, action }) => (
{title}
{body &&
{body}
}
{action &&
{action}
}
);
/* Segmented tabs */
const SegTabs = ({ value, onChange, options }) => (
{options.map((o) => (
))}
);
/* Tab bar */
const Tabs = ({ tabs, value, onChange }) => (
{tabs.map((t) => (
))}
);
/* Pill nav (range selector) */
const RangePicker = ({ value, onChange, options = ["7d", "30d", "90d", "1a"] }) => (
{options.map((o) => (
))}
);
/* Insight card β small "good/bad/improve" tile */
const InsightTile = ({ kind = "good", title, body }) => {
const map = {
good: { color: "var(--good)", label: "BIEN", bg: "var(--good-soft)", icon: "β" },
bad: { color: "var(--bad)", label: "ATENCIΓN", bg: "var(--bad-soft)", icon: "!" },
improve: { color: "var(--accent)", label: "MEJORA", bg: "var(--accent-soft)", icon: "β" },
info: { color: "var(--info)", label: "DATO", bg: "var(--info-soft)", icon: "i" },
}[kind];
return (
{map.icon}
{map.label}
{title}
{body && β {body}}
);
};
Object.assign(window, {
Card, Btn, Badge, Dot, Chip, Source, Trend, CapBadge, Icon, Tabs, RangePicker, InsightTile,
Empty, SegTabs,
});