/* 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 ( {paths[name]} ); }; 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, });