{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "integrations-hub-diagram",
  "title": "Integrations — Hub Diagram",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/integrations-hub-diagram/integrations-hub-diagram.component.ts",
      "content": "// Radial integration diagram: a central platform node connected by drawn SVG\n// spokes to eight orbiting service nodes, with a legend splitting sources from\n// destinations. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input } from '@angular/core'\nimport { ArrowRight, Boxes, LucideAngularModule } 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\ninterface HubNode {\n  mark: string\n  name: string\n  direction: string\n}\n\ninterface PlacedNode extends HubNode {\n  x: number\n  y: number\n}\n\nconst nodes: HubNode[] = [\n  { mark: 'SF', name: 'Snowflake', direction: 'source' },\n  { mark: 'BQ', name: 'BigQuery', direction: 'source' },\n  { mark: 'PG', name: 'Postgres', direction: 'source' },\n  { mark: 'DT', name: 'dbt', direction: 'source' },\n  { mark: 'SL', name: 'Slack', direction: 'destination' },\n  { mark: 'PD', name: 'PagerDuty', direction: 'destination' },\n  { mark: 'LK', name: 'Looker', direction: 'destination' },\n  { mark: 'HX', name: 'Hex', direction: 'destination' },\n]\n\n// Positions are computed on a circle in a 100x100 user-space viewBox, so any\n// number of nodes distributes evenly without touching the markup.\nconst RADIUS = 38\nconst placed: PlacedNode[] = nodes.map((node, index) => {\n  const angle = (index / nodes.length) * Math.PI * 2 - Math.PI / 2\n  return { ...node, x: 50 + RADIUS * Math.cos(angle), y: 50 + RADIUS * Math.sin(angle) }\n})\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-integrations-hub-diagram, [ui-integrations-hub-diagram]',\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=\"integrations-hub-diagram\" [class]=\"rootClass\">\n      <div class=\"mx-auto max-w-6xl px-6 py-20 lg:py-28\">\n        <div class=\"grid items-center gap-12 lg:grid-cols-[1fr_1.1fr] lg:gap-16\">\n          <div>\n            <span ui-badge variant=\"secondary\">Architecture</span>\n            <h2 class=\"mt-4 text-3xl font-semibold tracking-tight text-balance sm:text-4xl\">\n              One hub between your warehouse and everything downstream\n            </h2>\n            <p class=\"text-muted-foreground mt-3 leading-relaxed\">\n              Sources stay read-only and in place. Destinations receive certified metrics rather than raw extracts, so\n              nothing downstream can quietly redefine a number.\n            </p>\n\n            <dl class=\"mt-8 flex flex-wrap gap-x-8 gap-y-3 text-sm\">\n              <div class=\"flex items-center gap-2\">\n                <span class=\"bg-primary size-2 rounded-full\" aria-hidden=\"true\"></span>\n                <dt class=\"font-medium\">Sources</dt>\n                <dd class=\"text-muted-foreground\">read-only</dd>\n              </div>\n              <div class=\"flex items-center gap-2\">\n                <span class=\"bg-muted-foreground/50 size-2 rounded-full\" aria-hidden=\"true\"></span>\n                <dt class=\"font-medium\">Destinations</dt>\n                <dd class=\"text-muted-foreground\">certified metrics only</dd>\n              </div>\n            </dl>\n\n            <button ui-button class=\"mt-8\">\n              See the reference architecture\n              <lucide-icon [img]=\"ArrowRight\" class=\"ml-2 size-4\" aria-hidden=\"true\" />\n            </button>\n          </div>\n\n          <div ui-card>\n            <div ui-card-content class=\"p-6\">\n              <div class=\"relative aspect-square\">\n                <!-- Spokes are drawn in the same 100-unit space the nodes are\n                     placed in, so line ends always meet the node centres. -->\n                <svg class=\"absolute inset-0 size-full\" viewBox=\"0 0 100 100\" aria-hidden=\"true\">\n                  @for (node of placed; track node.name) {\n                    <line\n                      x1=\"50\"\n                      y1=\"50\"\n                      [attr.x2]=\"node.x\"\n                      [attr.y2]=\"node.y\"\n                      class=\"stroke-border\"\n                      stroke-width=\"0.4\"\n                      [attr.stroke-dasharray]=\"node.direction === 'destination' ? '2 2' : null\"\n                    />\n                  }\n                  <circle cx=\"50\" cy=\"50\" [attr.r]=\"radius\" class=\"stroke-border/60\" fill=\"none\" stroke-width=\"0.3\" />\n                </svg>\n\n                <div\n                  class=\"border-border bg-card absolute top-1/2 left-1/2 flex size-20 -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center gap-1 rounded-xl border shadow-sm\"\n                >\n                  <lucide-icon [img]=\"Boxes\" class=\"text-primary size-5\" aria-hidden=\"true\" />\n                  <span class=\"text-xs font-semibold tracking-tight\">Metric layer</span>\n                </div>\n\n                @for (node of placed; track node.name) {\n                  <div\n                    class=\"absolute flex -translate-x-1/2 -translate-y-1/2 flex-col items-center gap-1\"\n                    [style.left]=\"node.x + '%'\"\n                    [style.top]=\"node.y + '%'\"\n                  >\n                    <span [class]=\"nodeTileClass(node)\" aria-hidden=\"true\">\n                      {{ node.mark }}\n                    </span>\n                    <span class=\"text-muted-foreground text-xs whitespace-nowrap\">{{ node.name }}</span>\n                  </div>\n                }\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </section>\n  `,\n})\nexport class UiIntegrationsHubDiagramComponent {\n  protected readonly ArrowRight = ArrowRight\n  protected readonly Boxes = Boxes\n\n  protected readonly placed = placed\n  protected readonly radius = RADIUS\n\n  @Input('class') className?: string\n\n  get rootClass(): string {\n    return cn('bg-background', this.className)\n  }\n\n  nodeTileClass(node: HubNode): string {\n    return cn(\n      'border-border bg-background flex size-10 items-center justify-center rounded-lg border font-mono text-xs font-semibold',\n      node.direction === 'source' ? 'text-foreground' : 'text-muted-foreground',\n    )\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/integrations-hub-diagram/integrations-hub-diagram.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/integrations-hub-diagram/index.ts",
      "content": "export { UiIntegrationsHubDiagramComponent } from './integrations-hub-diagram.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/integrations-hub-diagram/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": "Radial integration diagram: a central platform node connected by drawn SVG spokes to eight orbiting service nodes, with a legend splitting sources from destinations.",
  "categories": [
    "marketing"
  ]
}