/* global React */
/* ANIMA KERNEL — primitive component set */
const { useState, useMemo, useEffect, useRef } = React;
/* -----------------------------------------------------------
HashBadge — hash-as-identity
Props: value, kind (MERKLE|FEATURE|SIG|KEY|MODEL|RULESET|TX),
ver ("verified"|"pending"|"failed"|"unsigned"),
short (4), onClick
----------------------------------------------------------- */
function HashBadge({ value = "", kind, ver = "verified", short = 4, onClick }) {
const display = useMemo(() => {
if (!value) return "";
const v = value.replace(/^0x/, "");
if (v.length <= short * 2 + 1) return "0x" + v;
return `0x${v.slice(0, short)}…${v.slice(-short)}`;
}, [value, short]);
return (
{kind && {kind}}
{display}
);
}
/* -----------------------------------------------------------
AttestationStatePill — crypto state machine
----------------------------------------------------------- */
const STATE_META = {
unsigned: { label: "UNSIGNED", shape: "square-hollow" },
signed: { label: "SIGNED", shape: "diamond" },
witnessed: { label: "WITNESSED", shape: "circle-ring" },
threshold: { label: "THRESHOLD", shape: "hex" },
finalized: { label: "FINALIZED", shape: "check-square" },
failed: { label: "FAILED", shape: "cross" },
expired: { label: "EXPIRED", shape: "clock" },
};
function StateShape({ kind }) {
const c = "currentColor";
switch (kind) {
case "square-hollow":
return ;
case "diamond":
return ;
case "circle-ring":
return ;
case "hex":
return ;
case "check-square":
return ;
case "cross":
return ;
case "clock":
return ;
default: return null;
}
}
function AttestationStatePill({ state = "signed", label }) {
const meta = STATE_META[state] || STATE_META.signed;
return (
{label || meta.label}
);
}
/* -----------------------------------------------------------
ReasonCodeChip
----------------------------------------------------------- */
function ReasonCodeChip({ code, label, weight = "low" }) {
return (
{code}
{label}
);
}
/* -----------------------------------------------------------
LatencyBudgetMeter — against 200ms card-auth window
----------------------------------------------------------- */
function LatencyBudgetMeter({ value, budget = 200, ticks = [50, 100, 150], height, showLabel = true }) {
const pct = Math.min(100, (value / budget) * 100);
let color = "var(--ak-latency-ok)";
if (pct > 90) color = "var(--ak-latency-over)";
else if (pct > 60) color = "var(--ak-latency-hot)";
else if (pct > 25) color = "var(--ak-latency-warn)";
return (
{showLabel && (
{value.toFixed(2)}/{budget}ms
)}
);
}
/* -----------------------------------------------------------
MerkleProofTree — readable inclusion proof
Shows the siblings along the path from leaf → root.
Props: leaf, siblings [{side:'L'|'R', hash}], root
----------------------------------------------------------- */
function MerkleProofTree({ leaf, siblings = [], root, highlighted = true }) {
// render as a compact ladder: leaf at bottom, each level folds one sibling in
const levels = siblings.length;
return (
inclusion proof · depth {levels}
{siblings.slice().reverse().map((s, i) => {
const level = levels - i;
return (
h{level}
{s.side === "L"
?
: ↓ path}
{s.side === "R"
?
: ↓ path}
);
})}
);
}
/* -----------------------------------------------------------
TimelineAxis — dual-scale (sub-ms for auth window, day-scale for settlement)
Props: mode ("submillisecond" | "day"), startMs, endMs, events [{t, label, state}], height
----------------------------------------------------------- */
function TimelineAxis({ mode = "submillisecond", startMs = 0, endMs = 200, events = [], height = 80, title }) {
const ref = useRef(null);
const [w, setW] = useState(0);
useEffect(() => {
if (!ref.current) return;
const ro = new ResizeObserver(entries => setW(entries[0].contentRect.width));
ro.observe(ref.current);
return () => ro.disconnect();
}, []);
const span = endMs - startMs;
const tickCount = mode === "submillisecond" ? 10 : 8;
const ticks = Array.from({ length: tickCount + 1 }, (_, i) => startMs + (span * i) / tickCount);
const fmt = t => {
if (mode === "submillisecond") {
if (span <= 10) return t.toFixed(2) + "ms";
return t.toFixed(0) + "ms";
}
// day scale — ms since epoch
const d = new Date(t);
if (span > 86400000) return d.toISOString().slice(5, 10);
return d.toISOString().slice(11, 16);
};
return (
{title && (
{title} · {mode === "submillisecond" ? "sub-ms scale" : "day scale"}
)}
{/* SLA band for submillisecond mode — the 200ms card-auth window */}
{mode === "submillisecond" && endMs >= 200 && (
200ms SLA
)}
{/* ticks */}
{ticks.map((t, i) => (
{fmt(t)}
))}
{/* events */}
{events.map((e, i) => {
const left = ((e.t - startMs) / span) * 100;
const color = e.state === "failed"
? "var(--ak-state-failed-fg)"
: e.state === "finalized"
? "var(--ak-state-finalized-fg)"
: e.state === "witnessed"
? "var(--ak-state-witnessed-fg)"
: "var(--ak-state-signed-fg)";
return (
{e.label}
{mode === "submillisecond" ? e.t.toFixed(2) + "ms" : ""}
);
})}
);
}
/* -----------------------------------------------------------
ThresholdGateIndicator — M-of-N signers
----------------------------------------------------------- */
function ThresholdGateIndicator({ met = 2, needed = 3, total = 4, label }) {
const pct = (met / needed) * 100;
const metState = met >= needed ? "finalized" : met > 0 ? "threshold" : "unsigned";
return (
{Array.from({ length: total }).map((_, i) => (
))}
{met}/{needed}
{label || "M-of-N"}
= needed ? "THRESHOLD MET" : "PARTIAL"} />
);
}
/* -----------------------------------------------------------
PartyAttestationRow — one party's attestation lifecycle
----------------------------------------------------------- */
function PartyAttestationRow({ party, role, state, signer, t, sig, isLast }) {
return (
{signer}
{state !== "unsigned" && (
)}
{t}
);
}
/* -----------------------------------------------------------
Small utility — Spark sparkline
----------------------------------------------------------- */
function Spark({ values = [], w = 120, h = 26, color = "var(--ak-accent)" }) {
if (!values.length) return null;
const max = Math.max(...values), min = Math.min(...values);
const span = (max - min) || 1;
const step = w / (values.length - 1 || 1);
const d = values.map((v, i) => `${i ? "L" : "M"}${(i * step).toFixed(1)},${(h - ((v - min) / span) * h).toFixed(1)}`).join(" ");
return (
);
}
/* -----------------------------------------------------------
Export to window for other Babel files
----------------------------------------------------------- */
Object.assign(window, {
HashBadge,
AttestationStatePill,
ReasonCodeChip,
LatencyBudgetMeter,
MerkleProofTree,
TimelineAxis,
ThresholdGateIndicator,
PartyAttestationRow,
Spark,
});