{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "hero-globe-interactive-nodes",
  "title": "Hero Globe Interactive Nodes",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/hero-globe-interactive-nodes/hero-globe-interactive-nodes.component.ts",
      "content": "// Global edge latency simulation hero with interactive PoP node inspection and\n// real-time telemetry. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport {\n  Activity,\n  ArrowRight,\n  Check,\n  Copy,\n  Globe,\n  LucideAngularModule,\n  Radio,\n  Server,\n  ShieldCheck,\n  Terminal,\n  Zap,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component'\n\nexport interface EdgeNode {\n  id: string\n  name: string\n  region: string\n  country: string\n  city: string\n  latencyMs: number\n  p99LatencyMs: number\n  cacheHitRatio: number\n  qps: number\n  status: 'optimal' | 'operational' | 'degraded'\n  coordinates: { x: number; y: number }\n}\n\nconst DEFAULT_NODES: EdgeNode[] = [\n  {\n    id: 'node-iad',\n    name: 'iad-1',\n    region: 'us-east-1',\n    country: 'United States',\n    city: 'Washington, D.C.',\n    latencyMs: 8,\n    p99LatencyMs: 14,\n    cacheHitRatio: 99.4,\n    qps: 18450,\n    status: 'optimal',\n    coordinates: { x: 28, y: 38 },\n  },\n  {\n    id: 'node-sfo',\n    name: 'sfo-1',\n    region: 'us-west-1',\n    country: 'United States',\n    city: 'San Francisco',\n    latencyMs: 12,\n    p99LatencyMs: 22,\n    cacheHitRatio: 99.1,\n    qps: 14200,\n    status: 'optimal',\n    coordinates: { x: 18, y: 40 },\n  },\n  {\n    id: 'node-fra',\n    name: 'fra-1',\n    region: 'eu-central-1',\n    country: 'Germany',\n    city: 'Frankfurt',\n    latencyMs: 15,\n    p99LatencyMs: 27,\n    cacheHitRatio: 98.9,\n    qps: 21300,\n    status: 'optimal',\n    coordinates: { x: 52, y: 32 },\n  },\n  {\n    id: 'node-sin',\n    name: 'sin-1',\n    region: 'ap-southeast-1',\n    country: 'Singapore',\n    city: 'Singapore',\n    latencyMs: 24,\n    p99LatencyMs: 41,\n    cacheHitRatio: 98.6,\n    qps: 16800,\n    status: 'optimal',\n    coordinates: { x: 76, y: 58 },\n  },\n  {\n    id: 'node-nrt',\n    name: 'nrt-1',\n    region: 'ap-northeast-1',\n    country: 'Japan',\n    city: 'Tokyo',\n    latencyMs: 19,\n    p99LatencyMs: 34,\n    cacheHitRatio: 99.2,\n    qps: 19400,\n    status: 'optimal',\n    coordinates: { x: 84, y: 42 },\n  },\n  {\n    id: 'node-syd',\n    name: 'syd-1',\n    region: 'ap-southeast-2',\n    country: 'Australia',\n    city: 'Sydney',\n    latencyMs: 32,\n    p99LatencyMs: 52,\n    cacheHitRatio: 98.2,\n    qps: 9800,\n    status: 'optimal',\n    coordinates: { x: 88, y: 78 },\n  },\n  {\n    id: 'node-gru',\n    name: 'gru-1',\n    region: 'sa-east-1',\n    country: 'Brazil',\n    city: 'São Paulo',\n    latencyMs: 44,\n    p99LatencyMs: 68,\n    cacheHitRatio: 97.9,\n    qps: 7600,\n    status: 'optimal',\n    coordinates: { x: 36, y: 74 },\n  },\n  {\n    id: 'node-lhr',\n    name: 'lhr-1',\n    region: 'eu-west-2',\n    country: 'United Kingdom',\n    city: 'London',\n    latencyMs: 11,\n    p99LatencyMs: 20,\n    cacheHitRatio: 99.5,\n    qps: 24600,\n    status: 'optimal',\n    coordinates: { x: 48, y: 30 },\n  },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-hero-globe-interactive-nodes, [ui-hero-globe-interactive-nodes]',\n  standalone: true,\n  // React renders the root <section> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent],\n  template: `\n    <section data-slot=\"hero-globe-interactive-nodes\" [class]=\"rootClass\">\n      <div class=\"mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\">\n        <div class=\"grid grid-cols-1 gap-12 lg:grid-cols-12 lg:items-center\">\n          <!-- Left: Copy & Actions (6 Cols) -->\n          <div class=\"space-y-6 lg:col-span-6\">\n            <div\n              class=\"border-primary/20 bg-primary/10 text-primary inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium shadow-2xs\"\n            >\n              <lucide-icon [img]=\"Radio\" class=\"text-primary size-3.5 animate-pulse\" />\n              <span>Global Edge CDN Mesh Active &bull; 310+ PoPs</span>\n            </div>\n\n            <h1 class=\"text-foreground text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl\">\n              {{ title }}\n            </h1>\n\n            <p class=\"text-muted-foreground text-base sm:text-lg\">\n              {{ description }}\n            </p>\n\n            <!-- CLI Command Sandbox -->\n            <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n              <div\n                class=\"border-border bg-muted/60 text-foreground flex items-center justify-between rounded-lg border px-3 py-2 font-mono text-xs shadow-2xs sm:w-80\"\n              >\n                <div class=\"flex items-center gap-2 truncate\">\n                  <lucide-icon [img]=\"Terminal\" class=\"text-muted-foreground size-3.5 shrink-0\" />\n                  <span class=\"truncate\">{{ cliCommand }}</span>\n                </div>\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  type=\"button\"\n                  class=\"text-muted-foreground hover:text-foreground size-7 shrink-0\"\n                  aria-label=\"Copy CLI command\"\n                  (click)=\"copyCommand()\"\n                >\n                  @if (copied()) {\n                    <lucide-icon [img]=\"Check\" class=\"text-primary size-3.5\" />\n                  } @else {\n                    <lucide-icon [img]=\"Copy\" class=\"size-3.5\" />\n                  }\n                </button>\n              </div>\n\n              <button ui-button size=\"default\" type=\"button\" class=\"gap-1.5 shadow-xs\">\n                <span>Explore Registry</span>\n                <lucide-icon [img]=\"ArrowRight\" class=\"size-4\" />\n              </button>\n            </div>\n\n            <!-- Quick Telemetry Strip -->\n            <div class=\"border-border grid grid-cols-3 gap-4 border-t pt-6\">\n              <div class=\"space-y-1\">\n                <div class=\"text-muted-foreground text-xs font-medium\">Global Avg Ping</div>\n                <div class=\"text-foreground text-xl font-bold tracking-tight\">{{ avgGlobalLatency() }}ms</div>\n                <div class=\"text-success text-xs font-medium\">&minus;42% vs origin</div>\n              </div>\n              <div class=\"space-y-1\">\n                <div class=\"text-muted-foreground text-xs font-medium\">Network Throughput</div>\n                <div class=\"text-foreground text-xl font-bold tracking-tight\">{{ totalGlobalQps() }}</div>\n                <div class=\"text-muted-foreground text-xs\">req/sec live</div>\n              </div>\n              <div class=\"space-y-1\">\n                <div class=\"text-muted-foreground text-xs font-medium\">Edge Availability</div>\n                <div class=\"text-foreground text-xl font-bold tracking-tight\">99.95%</div>\n                <div class=\"text-success text-xs font-medium\">Enterprise SLA</div>\n              </div>\n            </div>\n          </div>\n\n          <!-- Right: Interactive SVG Node Map & Telemetry Workbench (6 Cols) -->\n          <div class=\"space-y-4 lg:col-span-6\">\n            <div ui-card class=\"border-border bg-card shadow-sm\">\n              <div ui-card-content class=\"space-y-4 p-4 sm:p-6\">\n                <!-- Top bar: active node switch -->\n                <div class=\"border-border flex items-center justify-between border-b pb-3\">\n                  <div class=\"flex items-center gap-2\">\n                    <lucide-icon [img]=\"Globe\" class=\"text-primary size-4\" />\n                    <span class=\"text-foreground text-xs font-semibold\">Active Edge Inspection</span>\n                  </div>\n                  <span ui-badge variant=\"outline\" class=\"border-border text-muted-foreground font-mono text-xs\">\n                    {{ selectedNode().city }} ({{ selectedNode().name }})\n                  </span>\n                </div>\n\n                <!-- Interactive Node Grid / Map Canvas -->\n                <div class=\"border-border/80 bg-muted/30 relative h-60 w-full overflow-hidden rounded-lg border p-3\">\n                  <!-- SVG Connections Network Layer -->\n                  <svg\n                    class=\"stroke-border/70 absolute inset-0 size-full\"\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                    aria-hidden=\"true\"\n                  >\n                    @for (n of activeNodes().slice(1); track n.id) {\n                      <line\n                        [attr.x1]=\"hubNode().coordinates.x + '%'\"\n                        [attr.y1]=\"hubNode().coordinates.y + '%'\"\n                        [attr.x2]=\"n.coordinates.x + '%'\"\n                        [attr.y2]=\"n.coordinates.y + '%'\"\n                        stroke-dasharray=\"3 3\"\n                        stroke-width=\"1\"\n                      />\n                    }\n                  </svg>\n\n                  <!-- Interactive Region Nodes -->\n                  @for (node of activeNodes(); track node.id) {\n                    <button\n                      type=\"button\"\n                      [style.left]=\"node.coordinates.x + '%'\"\n                      [style.top]=\"node.coordinates.y + '%'\"\n                      [class]=\"nodeButtonClass(node.id)\"\n                      [attr.aria-label]=\"'Inspect ' + node.name + ' in ' + node.city\"\n                      (click)=\"selectNode(node.id)\"\n                    >\n                      <span class=\"font-mono text-xs leading-none font-bold\">\n                        {{ selectedNodeId() === node.id ? node.name.slice(0, 3).toUpperCase() : '' }}\n                      </span>\n                      @if (selectedNodeId() !== node.id) {\n                        <span class=\"bg-success size-2 rounded-full\"></span>\n                      }\n                    </button>\n                  }\n                </div>\n\n                <!-- Edge Node Telemetry Card -->\n                <div class=\"border-border bg-background grid grid-cols-2 gap-2.5 rounded-lg border p-3 sm:grid-cols-4\">\n                  <div class=\"space-y-0.5\">\n                    <div class=\"text-muted-foreground flex items-center gap-1 text-xs\">\n                      <lucide-icon [img]=\"Zap\" class=\"text-primary size-3\" />\n                      <span>Ping (p50)</span>\n                    </div>\n                    <div class=\"text-foreground font-mono text-sm font-bold\">{{ selectedNode().latencyMs }}ms</div>\n                  </div>\n\n                  <div class=\"space-y-0.5\">\n                    <div class=\"text-muted-foreground flex items-center gap-1 text-xs\">\n                      <lucide-icon [img]=\"Activity\" class=\"text-success size-3\" />\n                      <span>Ping (p99)</span>\n                    </div>\n                    <div class=\"text-foreground font-mono text-sm font-bold\">{{ selectedNode().p99LatencyMs }}ms</div>\n                  </div>\n\n                  <div class=\"space-y-0.5\">\n                    <div class=\"text-muted-foreground flex items-center gap-1 text-xs\">\n                      <lucide-icon [img]=\"Server\" class=\"text-info size-3\" />\n                      <span>Cache Hit</span>\n                    </div>\n                    <div class=\"text-foreground font-mono text-sm font-bold\">{{ selectedNode().cacheHitRatio }}%</div>\n                  </div>\n\n                  <div class=\"space-y-0.5\">\n                    <div class=\"text-muted-foreground flex items-center gap-1 text-xs\">\n                      <lucide-icon [img]=\"ShieldCheck\" class=\"text-primary size-3\" />\n                      <span>Throughput</span>\n                    </div>\n                    <div class=\"text-foreground font-mono text-sm font-bold\">\n                      {{ selectedNode().qps.toLocaleString() }} qps\n                    </div>\n                  </div>\n                </div>\n\n                <!-- Quick Node Pill Selector -->\n                <div class=\"flex flex-wrap gap-1.5 pt-1\">\n                  @for (node of activeNodes(); track node.id) {\n                    <button type=\"button\" [class]=\"nodePillClass(node.id)\" (click)=\"selectNode(node.id)\">\n                      {{ node.name }} &bull; {{ node.city }}\n                    </button>\n                  }\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </section>\n  `,\n})\nexport class UiHeroGlobeInteractiveNodesComponent {\n  protected readonly Activity = Activity\n  protected readonly ArrowRight = ArrowRight\n  protected readonly Check = Check\n  protected readonly Copy = Copy\n  protected readonly Globe = Globe\n  protected readonly Radio = Radio\n  protected readonly Server = Server\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly Terminal = Terminal\n  protected readonly Zap = Zap\n\n  @Input() title = 'Sub-millisecond UI edge delivery across 310+ PoPs globally.'\n  @Input() description =\n    'Compile unbundled Vue 3.5 and React 19 components with instant edge caching. Zero cold starts, deterministic tree-shaking, and hardware-accelerated telemetry.'\n  @Input() cliCommand = 'npx shadcn add @uipkge/init'\n  @Input() nodes?: EdgeNode[]\n  @Input('class') className?: string\n\n  readonly selectedNodeId = signal('node-iad')\n  readonly copied = signal(false)\n\n  private copyTimer: ReturnType<typeof setTimeout> | undefined\n\n  readonly activeNodes = computed(() => this.nodes ?? DEFAULT_NODES)\n\n  readonly hubNode = computed<EdgeNode>(() => this.activeNodes()[0] ?? DEFAULT_NODES[0]!)\n\n  readonly selectedNode = computed<EdgeNode>(() => {\n    const all = this.activeNodes()\n    return all.find((n) => n.id === this.selectedNodeId()) ?? all[0] ?? DEFAULT_NODES[0]!\n  })\n\n  readonly avgGlobalLatency = computed(() => {\n    const all = this.activeNodes()\n    if (!all.length) return 0\n    return Math.round(all.reduce((acc, curr) => acc + curr.latencyMs, 0) / all.length)\n  })\n\n  readonly totalGlobalQps = computed(() =>\n    this.activeNodes()\n      .reduce((acc, curr) => acc + curr.qps, 0)\n      .toLocaleString(),\n  )\n\n  get rootClass(): string {\n    return cn('bg-background relative overflow-hidden py-16 sm:py-24 lg:py-28', this.className)\n  }\n\n  nodeButtonClass(id: string): string {\n    return cn(\n      'focus-visible:ring-ring absolute flex -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full transition-colors duration-150 focus-visible:ring-2 focus-visible:outline-none',\n      this.selectedNodeId() === id\n        ? 'bg-primary text-primary-foreground ring-primary/20 z-10 size-7 scale-110 shadow-sm ring-4'\n        : 'bg-card hover:bg-accent border-border text-foreground size-5 border hover:scale-105',\n    )\n  }\n\n  nodePillClass(id: string): string {\n    return cn(\n      'rounded-md border px-2 py-1 font-mono text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none',\n      this.selectedNodeId() === id\n        ? 'border-primary bg-primary/10 text-primary'\n        : 'border-border bg-card text-muted-foreground hover:text-foreground hover:bg-accent/50',\n    )\n  }\n\n  selectNode(id: string): void {\n    this.selectedNodeId.set(id)\n  }\n\n  async copyCommand(): Promise<void> {\n    try {\n      await navigator.clipboard.writeText(this.cliCommand)\n      this.copied.set(true)\n      clearTimeout(this.copyTimer)\n      this.copyTimer = setTimeout(() => this.copied.set(false), 2000)\n    } catch {\n      // Clipboard unavailable (e.g. insecure context): leave the Copy icon.\n    }\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/hero-globe-interactive-nodes/hero-globe-interactive-nodes.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/hero-globe-interactive-nodes/index.ts",
      "content": "export { UiHeroGlobeInteractiveNodesComponent, type EdgeNode } from './hero-globe-interactive-nodes.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/hero-globe-interactive-nodes/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json"
  ],
  "description": "Global edge latency simulation hero with interactive PoP node inspection and real-time telemetry.",
  "categories": [
    "hero",
    "marketing"
  ],
  "deprecated": true,
  "replacedBy": "hero"
}