{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "event-stream",
  "title": "Event Stream",
  "description": "Frameless cycling JSON animationâ€”three webhook events type out in sequence with edge fade, no borders, no syntax colors, designed to embed in any section.",
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/new-york/blocks/event-stream/page.tsx",
      "content": "import EventStream from \"./components/event-stream\"\n\nexport default function Page() {\n  return (\n    <div className=\"px-4 py-10 md:px-6 md:py-14\">\n      <EventStream />\n    </div>\n  )\n}\n",
      "type": "registry:page",
      "target": "app/event-stream/page.tsx"
    },
    {
      "path": "registry/new-york/blocks/event-stream/components/event-stream.tsx",
      "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { useEventAnimation } from \"../hooks/use-event-animation\"\nimport TokenText from \"./token-text\"\n\nimport \"../styles/event-stream.css\"\n\nexport default function EventStream({ className }: { className?: string }) {\n  const { text, scrollRef } = useEventAnimation()\n\n  return (\n    <div\n      className={cn(\n        \"es-root relative mx-auto w-full max-w-3xl min-w-0 overflow-hidden\",\n        className\n      )}\n      style={{ height: \"18rem\" }}\n    >\n      <div\n        ref={scrollRef}\n        className=\"flex h-full justify-center overflow-y-auto pt-4 pb-16 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n      >\n        <pre className=\"m-0 w-fit font-mono text-[11px] leading-[1.45] whitespace-pre text-muted-foreground\">\n          <TokenText text={text} />\n          <span className=\"es-cursor ml-px inline-block h-[1em] w-1.5 bg-muted-foreground align-bottom\" />\n        </pre>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "app/event-stream/components/event-stream.tsx"
    },
    {
      "path": "registry/new-york/blocks/event-stream/components/token-text.tsx",
      "content": "import { useMemo } from \"react\"\n\n// Splits text into plain segments and JSON number-literal segments.\n// Swap this component to add syntax colours, highlights, or diff markers.\nfunction tokenize(text: string): Array<{ t: string; num: boolean }> {\n  const out: Array<{ t: string; num: boolean }> = []\n  const re = /-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?/g\n  let last = 0\n  let m: RegExpExecArray | null\n  while ((m = re.exec(text)) !== null) {\n    if (m.index > last) out.push({ t: text.slice(last, m.index), num: false })\n    out.push({ t: m[0], num: true })\n    last = m.index + m[0].length\n  }\n  if (last < text.length) out.push({ t: text.slice(last), num: false })\n  return out\n}\n\ninterface TokenTextProps {\n  text: string\n}\n\nexport default function TokenText({ text }: TokenTextProps) {\n  const tokens = useMemo(() => tokenize(text), [text])\n\n  return (\n    <>\n      {tokens.map((tok, i) =>\n        tok.num ? (\n          <span key={i} className=\"tabular-nums\">\n            {tok.t}\n          </span>\n        ) : (\n          tok.t\n        )\n      )}\n    </>\n  )\n}\n",
      "type": "registry:component",
      "target": "app/event-stream/components/token-text.tsx"
    },
    {
      "path": "registry/new-york/blocks/event-stream/hooks/use-event-animation.ts",
      "content": "﻿import { useReducer, useLayoutEffect, useMemo, useRef } from \"react\"\n\nimport { EVENTS, TYPE_CHAR_MS, TYPE_NEWLINE_MS, VISIBLE_EVENT_BUFFER } from \"../lib/config\"\n\ntype AnimationState = {\n  buffer: string[]\n  eventIdx: number\n  charIdx: number\n}\n\ntype AnimationAction = { type: \"advanceChar\" } | { type: \"completeEvent\" }\n\nfunction animationReducer(state: AnimationState, action: AnimationAction): AnimationState {\n  switch (action.type) {\n    case \"advanceChar\":\n      return { ...state, charIdx: state.charIdx + 1 }\n    case \"completeEvent\": {\n      const content = EVENTS[state.eventIdx]\n      return {\n        buffer: [...state.buffer, content].slice(-VISIBLE_EVENT_BUFFER),\n        charIdx: 0,\n        eventIdx: (state.eventIdx + 1) % EVENTS.length,\n      }\n    }\n    default:\n      return state\n  }\n}\n\nfunction buildDisplayText(buffer: string[], eventIdx: number, charIdx: number): string {\n  const wip = EVENTS[eventIdx].slice(0, charIdx)\n  if (buffer.length === 0) return wip\n  return buffer.join(\"\\n\\n\") + \"\\n\\n\" + wip\n}\n\nexport function useEventAnimation() {\n  const [{ buffer, eventIdx, charIdx }, dispatch] = useReducer(animationReducer, {\n    buffer: [],\n    eventIdx: 0,\n    charIdx: 0,\n  })\n  const scrollRef = useRef<HTMLDivElement>(null)\n\n  const text = useMemo(\n    () => buildDisplayText(buffer, eventIdx, charIdx),\n    [buffer, eventIdx, charIdx],\n  )\n\n  useLayoutEffect(() => {\n    const el = scrollRef.current\n    if (!el) return\n    el.scrollTop = Math.max(0, el.scrollHeight - el.clientHeight)\n  }, [text])\n\n  useLayoutEffect(() => {\n    const content = EVENTS[eventIdx]\n\n    if (charIdx >= content.length) {\n      dispatch({ type: \"completeEvent\" })\n      return\n    }\n\n    const ch = content[charIdx]\n    const delay = ch === \"\\n\" ? TYPE_NEWLINE_MS : TYPE_CHAR_MS\n    const t = setTimeout(() => dispatch({ type: \"advanceChar\" }), delay)\n    return () => clearTimeout(t)\n  }, [charIdx, eventIdx])\n\n  return { text, scrollRef }\n}\n",
      "type": "registry:hook",
      "target": "app/event-stream/hooks/use-event-animation.ts"
    },
    {
      "path": "registry/new-york/blocks/event-stream/lib/config.ts",
      "content": "export const TYPE_CHAR_MS = 16\nexport const TYPE_NEWLINE_MS = 38\n\n/** Completed events kept in the sliding window; older ones drop off the top through the mask. */\nexport const VISIBLE_EVENT_BUFFER = 3\n\nexport const EVENTS = [\n  `{\n  \"event\": \"order.created\",\n  \"id\": \"evt_9x2k4mpl\",\n  \"timestamp\": \"2024-03-15T10:22:07Z\",\n  \"data\": {\n    \"order_id\": \"ord_8f3k2x9p\",\n    \"status\": \"confirmed\",\n    \"customer\": {\n      \"id\": \"cus_4m9xp1r\",\n      \"name\": \"Steve Jobs\",\n      \"email\": \"s.jobs@icloud.com\"\n    },\n    \"items\": [\n      {\n        \"id\": \"itm_3t7vk2nx\",\n        \"name\": \"MacBook Pro 14-inch M4 Pro\",\n        \"sku\": \"MBP14-M4P-16-512-SLV\",\n        \"price\": 1999.00,\n        \"quantity\": 1\n      },\n      {\n        \"id\": \"itm_8p1zr5qw\",\n        \"name\": \"AirPods Pro (USB-C)\",\n        \"sku\": \"APP2-USBC-WHT\",\n        \"price\": 249.00,\n        \"quantity\": 1\n      }\n    ],\n    \"shipping\": {\n      \"method\": \"free\",\n      \"address\": {\n        \"line1\": \"One Apple Park Way\",\n        \"city\": \"Cupertino\",\n        \"state\": \"CA\",\n        \"postcode\": \"95014\",\n        \"country\": \"US\"\n      }\n    },\n    \"totals\": {\n      \"subtotal\": 2248.00,\n      \"shipping\": 0.00,\n      \"tax\": 207.94,\n      \"total\": 2455.94,\n      \"currency\": \"USD\"\n    }\n  }\n}`,\n  `{\n  \"event\": \"payment.captured\",\n  \"id\": \"evt_4r7n1qws\",\n  \"timestamp\": \"2024-03-15T10:22:09Z\",\n  \"data\": {\n    \"payment_id\": \"pay_3m8xp2kn\",\n    \"order_id\": \"ord_8f3k2x9p\",\n    \"amount\": 2455.94,\n    \"currency\": \"USD\",\n    \"method\": {\n      \"type\": \"apple_pay\",\n      \"brand\": \"visa\",\n      \"last4\": \"1776\",\n      \"exp_month\": 9,\n      \"exp_year\": 2027\n    },\n    \"billing\": {\n      \"name\": \"Steve Jobs\",\n      \"address\": {\n        \"line1\": \"One Apple Park Way\",\n        \"city\": \"Cupertino\",\n        \"state\": \"CA\",\n        \"postcode\": \"95014\",\n        \"country\": \"US\"\n      }\n    },\n    \"status\": \"succeeded\",\n    \"risk_score\": 2\n  }\n}`,\n  `{\n  \"event\": \"inventory.updated\",\n  \"id\": \"evt_7m3kp9xv\",\n  \"timestamp\": \"2024-03-15T10:22:11Z\",\n  \"data\": {\n    \"updates\": [\n      {\n        \"sku\": \"MBP14-M4P-16-512-SLV\",\n        \"product_id\": \"prod_tv3m8n\",\n        \"warehouse\": \"wh_cali_01\",\n        \"previous\": 48,\n        \"current\": 47,\n        \"reserved\": 3\n      },\n      {\n        \"sku\": \"APP2-USBC-WHT\",\n        \"product_id\": \"prod_kx9p2r\",\n        \"warehouse\": \"wh_cali_01\",\n        \"previous\": 214,\n        \"current\": 213,\n        \"reserved\": 8\n      }\n    ],\n    \"trigger\": \"order.fulfilled\",\n    \"order_id\": \"ord_8f3k2x9p\"\n  }\n}`,\n  `{\n  \"event\": \"shipment.dispatched\",\n  \"id\": \"evt_2p5v8ztq\",\n  \"timestamp\": \"2024-03-15T14:05:33Z\",\n  \"data\": {\n    \"shipment_id\": \"shp_7k1r4ynx\",\n    \"order_id\": \"ord_8f3k2x9p\",\n    \"carrier\": {\n      \"name\": \"FedEx\",\n      \"service\": \"Priority Overnight\",\n      \"tracking_url\": \"https://www.fedex.com/tracking\"\n    },\n    \"tracking_number\": \"7489129045289\",\n    \"items\": [\n      { \"sku\": \"MBP14-M4P-16-512-SLV\", \"quantity\": 1 },\n      { \"sku\": \"APP2-USBC-WHT\", \"quantity\": 1 }\n    ],\n    \"weight_kg\": 1.84,\n    \"estimated_delivery\": \"2024-03-16\",\n    \"label_created_at\": \"2024-03-15T13:58:12Z\"\n  }\n}`,\n  `{\n  \"event\": \"email.sent\",\n  \"id\": \"evt_1q6hn7cz\",\n  \"timestamp\": \"2024-03-15T14:06:01Z\",\n  \"data\": {\n    \"message_id\": \"msg_5r2wp4lv\",\n    \"order_id\": \"ord_8f3k2x9p\",\n    \"recipient\": \"s.jobs@icloud.com\",\n    \"template\": \"shipment_confirmation\",\n    \"subject\": \"Your order is on its way\",\n    \"tracking_number\": \"7489129045289\",\n    \"variables\": {\n      \"customer_name\": \"Steve\",\n      \"carrier\": \"FedEx\",\n      \"estimated_delivery\": \"March 16, 2024\"\n    },\n    \"provider\": \"sendgrid\",\n    \"status\": \"delivered\"\n  }\n}`,\n  `{\n  \"event\": \"review.submitted\",\n  \"id\": \"evt_5n8wq3ym\",\n  \"timestamp\": \"2024-03-17T09:14:52Z\",\n  \"data\": {\n    \"review_id\": \"rev_6z4qf8rp\",\n    \"order_id\": \"ord_8f3k2x9p\",\n    \"product_id\": \"prod_tv3m8n\",\n    \"customer_id\": \"cus_4m9xp1r\",\n    \"rating\": 5,\n    \"title\": \"One more thing — it is perfect\",\n    \"body\": \"Insanely great. The M4 Pro chip is a work of art.\",\n    \"verified_purchase\": true,\n    \"helpful_votes\": 0,\n    \"moderation_status\": \"approved\"\n  }\n}`,\n]\n",
      "type": "registry:lib",
      "target": "app/event-stream/lib/config.ts"
    },
    {
      "path": "registry/new-york/blocks/event-stream/styles/event-stream.css",
      "content": "/* event-stream — edge fade mask + cursor blink */\n\n.es-root {\n  -webkit-mask-image: linear-gradient(\n    to bottom,\n    transparent 0%,\n    black 3%,\n    black 97%,\n    transparent 100%\n  );\n  mask-image: linear-gradient(\n    to bottom,\n    transparent 0%,\n    black 3%,\n    black 97%,\n    transparent 100%\n  );\n}\n\n@keyframes es-blink {\n  0%,\n  49.9% {\n    opacity: 1;\n  }\n  50%,\n  100% {\n    opacity: 0;\n  }\n}\n\n.es-cursor {\n  animation: es-blink 1.15s step-start infinite;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .es-cursor {\n    animation: none;\n  }\n}\n",
      "type": "registry:file",
      "target": "app/event-stream/styles/event-stream.css"
    }
  ],
  "type": "registry:block"
}