// API response block — dark code surface with syntax highlighting + typing animation. const apiStyles = { wrap: { width: "100%", maxWidth: 460, background: "var(--code-bg)", border: "1px solid rgba(255, 255, 255, 0.06)", borderRadius: 18, padding: "20px 22px 22px", fontFamily: '"JetBrains Mono", ui-monospace, monospace', color: "#E9E4F2", boxShadow: "0 24px 48px -28px rgba(70, 59, 94, 0.35)", position: "relative", overflow: "hidden", }, bgGlow: { position: "absolute", top: -80, right: -80, width: 200, height: 200, background: "radial-gradient(closest-side, rgba(196, 168, 232, 0.18), transparent)", pointerEvents: "none", }, header: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 14, fontFamily: '"DM Sans", sans-serif', }, label: { fontSize: 10.5, letterSpacing: 1.6, fontWeight: 600, color: "rgba(233, 228, 242, 0.55)", textTransform: "uppercase", }, endpoint: { fontSize: 11, color: "rgba(233, 228, 242, 0.45)", fontFamily: '"JetBrains Mono", monospace', display: "flex", alignItems: "center", gap: 8, }, methodPill: { fontSize: 9.5, letterSpacing: 0.6, fontWeight: 600, padding: "2px 6px", borderRadius: 4, background: "rgba(120, 200, 160, 0.18)", color: "#8DDDB1", }, pre: { margin: 0, fontSize: 13, lineHeight: 1.65, whiteSpace: "pre", fontFamily: '"JetBrains Mono", monospace', color: "#CFC8DD", }, cursor: { display: "inline-block", width: 7, height: 14, background: "#C4A8E8", marginLeft: 1, verticalAlign: "-2px", animation: "lc-blink 1s steps(2, start) infinite", }, }; // Color tokens for syntax const SYNTAX = { key: "#9DB7E0", // soft blue-lavender for keys string: "#A7CDE8", // blue strings stringVerified: "#8DDDB1", // green for "verified" literalTrue: "#8DDDB1", literalFalse: "#E58A93", literalNull: "#C4A8E8", number: "#F4C8A0", punct: "rgba(207, 200, 221, 0.55)", }; function colorizeValue(key, value) { if (value === null) return { color: SYNTAX.literalNull, text: "null" }; if (value === true) return { color: SYNTAX.literalTrue, text: "true" }; if (value === false) return { color: SYNTAX.literalFalse, text: "false" }; if (typeof value === "number") return { color: SYNTAX.number, text: String(value) }; if (typeof value === "string") { const isStatus = key === "status"; const isVerified = isStatus && value === "verified"; const isFailed = isStatus && (value === "rejected" || value === "expired"); return { color: isVerified ? SYNTAX.stringVerified : isFailed ? SYNTAX.literalFalse : SYNTAX.string, text: `"${value}"`, }; } return { color: SYNTAX.string, text: JSON.stringify(value) }; } // Build the JSON as an array of typed segments so we can animate typing function buildSegments(api) { const entries = Object.entries(api); const segs = []; segs.push({ text: "{\n", color: SYNTAX.punct }); entries.forEach(([k, v], i) => { segs.push({ text: " ", color: SYNTAX.punct }); segs.push({ text: `"${k}"`, color: SYNTAX.key }); segs.push({ text: ": ", color: SYNTAX.punct }); const cv = colorizeValue(k, v); segs.push({ text: cv.text, color: cv.color }); segs.push({ text: i < entries.length - 1 ? ",\n" : "\n", color: SYNTAX.punct, }); }); segs.push({ text: "}", color: SYNTAX.punct }); return segs; } function ApiBlock({ api, animateKey, play = true, onDone }) { const segments = React.useMemo(() => buildSegments(api), [api]); const totalLen = segments.reduce((n, s) => n + s.text.length, 0); const [count, setCount] = React.useState(totalLen); React.useEffect(() => { if (!play) { setCount(totalLen); return; } setCount(0); let i = 0; let cancelled = false; const baseMs = 40; const linePauseMs = 340; const joinedText = segments.map((s) => s.text).join(""); const tick = () => { if (cancelled) return; if (i >= totalLen) return; i = Math.min(totalLen, i + 1); setCount(i); const isNewline = joinedText[i - 1] === "\n"; const delay = baseMs + (isNewline ? linePauseMs : 0); setTimeout(tick, delay); }; setTimeout(tick, baseMs); return () => { cancelled = true; }; }, [animateKey, totalLen, segments, play]); const doneRef = React.useRef(false); React.useEffect(() => { doneRef.current = false; }, [animateKey]); React.useEffect(() => { if (!onDone) return; if (!play) return; if (doneRef.current) return; if (count >= totalLen) { doneRef.current = true; onDone(); } }, [count, totalLen, onDone]); // Render segments up to `count` characters let remaining = count; const rendered = []; for (const seg of segments) { if (remaining <= 0) break; const take = Math.min(seg.text.length, remaining); rendered.push( {seg.text.slice(0, take)} ); remaining -= take; } return (
API Response POST /v1/verify
        {rendered}
        {play && count < totalLen && }
      
); } window.ApiBlock = ApiBlock;