/* ============================================================ METABOLE — App-surface widgets (floating cards + live demos) ============================================================ */ /* ---- Small shared bits ---- */ function CardLabel({ icon, children, right }) { return (
{icon && }{children} {right}
); } /* ---- Next dose card ---- */ function DoseCard({ active = true, style }) { return (
Due today}>Next dose
Tirzepatide
7.5 mg · 37.5 units · 10 mg/mL

Left abdomen · rotated
); } /* ---- Weight card ---- */ function WeightCard({ active = true, style }) { return (
▼ 20.4 lb}>Weight trend
193.6 lb 14 WK
); } /* ---- Check-in card ---- */ function CheckinCard({ active = true, style }) { const rows = [ { k: "Energy", v: 4, icon: "bolt", c: "var(--green)" }, { k: "Sleep", v: 3, icon: "moon", c: "var(--sage)" }, { k: "Mood", v: 5, icon: "spark", c: "var(--clay)" }, ]; return (
Daily check-in
{rows.map((r, i) => (
{r.k}
{[1, 2, 3, 4, 5].map(n => ( ))}
))}
); } /* ---- Side effect card ---- */ function SideEffectCard({ active = true, style }) { const items = [ { k: "Nausea", lvl: "Mild", w: 28, c: "var(--amber)" }, { k: "Fatigue", lvl: "Mild", w: 22, c: "var(--sage)" }, { k: "Constipation", lvl: "None", w: 6, c: "var(--mint-2)" }, ]; return (
THIS WK}>Side effects
{items.map((it, i) => (
{it.k}{it.lvl}
))}
); } /* ---- Hydration / protein rings ---- */ function VitalsCard({ active = true, style }) { return (
Today
); } /* ---- Insight card ("what changed this week") ---- */ function InsightCard({ active = true, style }) { return (
What changed this week

Your protein averaged 112g/day — up 18% — and nausea dropped on days you hydrated before noon.

+18% PROTEIN −1.4 LB
); } /* ---- Injection-site rotation map (abstract) ---- */ function InjectionSiteMap({ active = true }) { const zones = [ { id: "LA", label: "L abdomen", x: 0, y: 0, days: 0, next: true }, { id: "RA", label: "R abdomen", x: 1, y: 0, days: 14 }, { id: "LT", label: "L thigh", x: 0, y: 1, days: 7 }, { id: "RT", label: "R thigh", x: 1, y: 1, days: 11 }, { id: "LM", label: "L arm", x: 0, y: 2, days: 4 }, { id: "RM", label: "R arm", x: 1, y: 2, days: 18 }, ]; const fresh = (d) => d === 0 ? "var(--clay)" : `color-mix(in oklab, var(--green) ${Math.min(100, d / 18 * 100)}%, var(--mint-2))`; return (
{zones.map((z, i) => (
{z.label}
{z.next ? "USE NEXT →" : z.days + " DAYS AGO"}
))}
); } /* ---- Interactive compound level estimator ---- */ const COMPOUNDS = [ { id: "tirz", name: "Tirzepatide", t12: 120, tmax: 24, interval: 168, dose: "7.5 mg", color: "var(--green)" }, { id: "sema", name: "Semaglutide", t12: 168, tmax: 30, interval: 168, dose: "1.0 mg", color: "var(--sage)" }, { id: "lira", name: "Liraglutide", t12: 13, tmax: 11, interval: 24, dose: "1.8 mg", color: "var(--clay)" }, { id: "bpc", name: "BPC-157", t12: 4, tmax: 1.5, interval: 24, dose: "250 mcg", color: "var(--amber)" }, ]; function bateman(t, ke, ka) { if (t <= 0) return 0; return (ke / (ka - ke)) * (Math.exp(-ke * t) - Math.exp(-ka * t)); } function CompoundEstimator({ active = true }) { const [cid, setCid] = useState("tirz"); const c = COMPOUNDS.find(x => x.id === cid); const [t, setT] = useState(c.tmax); useEffect(() => { setT(COMPOUNDS.find(x => x.id === cid).tmax); }, [cid]); const ke = Math.log(2) / c.t12; const ka = ke * 5.2; const interval = c.interval; const W = 520, H = 190, padL = 10, padR = 10, padT = 14, padB = 26; const innerW = W - padL - padR, innerH = H - padT - padB; const N = 120; const samples = useMemo(() => Array.from({ length: N + 1 }, (_, i) => bateman((i / N) * interval, ke, ka)), [cid]); const peak = Math.max(...samples); const pts = samples.map((v, i) => [padL + (i / N) * innerW, padT + (1 - v / peak) * innerH]); const line = buildPath(pts); const area = line + ` L${pts[pts.length - 1][0]} ${padT + innerH} L${pts[0][0]} ${padT + innerH} Z`; const lvl = bateman(t, ke, ka) / peak; const markX = padL + (t / interval) * innerW; const markY = padT + (1 - lvl) * innerH; const pct = Math.round(lvl * 100); const phase = t < c.tmax * 0.85 ? "Absorbing" : t < c.tmax * 1.8 ? "Near peak" : lvl > 0.35 ? "Declining" : "Trough"; const phaseColor = phase === "Near peak" ? "var(--green)" : phase === "Trough" ? "var(--clay)" : "var(--sage)"; const hoursToNext = Math.max(0, interval - t); const fmtT = (h) => h < 48 ? h.toFixed(0) + "h" : (h / 24).toFixed(1) + "d"; return (
{COMPOUNDS.map(x => ( ))}
{[0, 0.5, 1].map((f, i) => ( ))} DOSE NEXT · {fmtT(interval)} setT(Number(e.target.value))} style={{ width: "100%", marginTop: 8, accentColor: "var(--green)" }} />
Time since dose {fmtT(t)}
Estimated level
{pct} % of peak
{phase}
); } function Row({ k, v, accent }) { return (
{k} {v}
); } Object.assign(window, { DoseCard, WeightCard, CheckinCard, SideEffectCard, VitalsCard, InsightCard, InjectionSiteMap, CompoundEstimator, CardLabel, });