# Isometric Network (`isometric-network`)

> An isometric network plate — pin nodes on an extruded slab, connected by lifted arcs with pulses travelling from the hub outward.

- **Docs:** https://www.mellowui.com/components/isometric-network
- **Markdown:** https://www.mellowui.com/components/isometric-network.md
- **Registry:** https://www.mellowui.com/r/isometric-network.json
- **Tool prompt:** https://www.mellowui.com/api/prompt/isometric-network
- **Categories:** display, 3d, animation
- **Dependencies:** none

## AI prompt

Add an IsometricNetwork component from the mellow library — an isometric network plate. Pass `nodes` (x, y, optional hub); one hub node originates pulses that travel along lifted arcs to every other node. Tune `speed` and `accent`. Good as an edge-network / integrations illustration for bento cards.

## Install

```bash
npx shadcn@latest add https://mellowui.com/r/isometric-network.json
```

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `nodes` | `IsometricNetworkNode[]` | — | Pins on the plate. Defaults to one hub and four edges. |
| `speed` | `number` | `1` | Pulse travel speed multiplier. |
| `accent` | `string` | `"oklch(0.65 0.25 250)"` | Accent for hub cap and pulses. |
| `size` | `number` | `380` | Rendered width in px. |
| `className` | `string` | — | Additional CSS classes. |

## Design notes

- Client component (`"use client"`) — required for animation and refs.
- Uses Mellow design tokens: `--ink`, `--ink-rgb`, `--background`, `--background-rgb`, `--rule`, `--font-sans`, `--font-serif`, `--font-mono`. Tokens auto-flip between light and dark themes — never hardcode colors.
- Respects `prefers-reduced-motion: reduce` where applicable.
- Self-contained — no shared utilities. Copy-paste, not a package.

## Source

```tsx
"use client";

import React, { useEffect, useRef } from "react";

export interface IsometricNetworkNode {
  /** World coordinates on the plate, roughly -90..90 x and -55..55 y. */
  x: number;
  y: number;
  /** Hub nodes get the accent cap and originate the pulses. */
  hub?: boolean;
}

export interface IsometricNetworkProps {
  /** Pins on the plate. Defaults to one hub and four edges. */
  nodes?: IsometricNetworkNode[];
  /** Pulse travel speed multiplier. */
  speed?: number;
  /** Accent for hub cap and pulses. */
  accent?: string;
  /** Rendered width in px. */
  size?: number;
  className?: string;
  style?: React.CSSProperties;
}

const iso = (x: number, y: number, z: number): [number, number] => [
  0.866 * (x - y),
  0.5 * (x + y) - z,
];

const HW = 118;
const HH = 74;
const R = 20;
const DEPTH = 10;
const TOP_Z = DEPTH;

const FACE = "color-mix(in oklab, var(--ink) 5%, var(--background))";

const DEFAULT_NODES: IsometricNetworkNode[] = [
  { x: 0, y: 0, hub: true },
  { x: -78, y: -38 },
  { x: 72, y: -44 },
  { x: -58, y: 42 },
  { x: 84, y: 38 },
];

// stadium-cornered plate outline in world coords
const platePath = `M ${-HW + R} ${-HH} L ${HW - R} ${-HH} A ${R} ${R} 0 0 1 ${HW} ${
  -HH + R
} L ${HW} ${HH - R} A ${R} ${R} 0 0 1 ${HW - R} ${HH} L ${-HW + R} ${HH} A ${R} ${R} 0 0 1 ${-HW} ${
  HH - R
} L ${-HW} ${-HH + R} A ${R} ${R} 0 0 1 ${-HW + R} ${-HH} Z`;
const ISO = "matrix(0.866, 0.5, -0.866, 0.5, 0, 0)";

/**
 * An isometric network plate — pin nodes on an extruded slab, connected by
 * lifted arcs with pulses travelling from the hub outward. An edge-network /
 * integrations illustration for bento cards.
 */
export function IsometricNetwork({
  nodes = DEFAULT_NODES,
  speed = 1,
  accent = "oklch(0.65 0.25 250)",
  size = 380,
  className,
  style,
}: IsometricNetworkProps) {
  const pulseRefs = useRef<(SVGCircleElement | null)[]>([]);
  const arcRefs = useRef<(SVGPathElement | null)[]>([]);

  const hub = nodes.find((n) => n.hub) ?? nodes[0];
  const edges = nodes.filter((n) => n !== hub);

  const arcs = edges.map((node) => {
    const [ax, ay] = iso(hub.x, hub.y, TOP_Z + 4);
    const [bx, by] = iso(node.x, node.y, TOP_Z + 4);
    const lift = 26 + Math.hypot(node.x - hub.x, node.y - hub.y) * 0.12;
    return `M ${ax.toFixed(1)} ${ay.toFixed(1)} Q ${((ax + bx) / 2).toFixed(1)} ${(
      (ay + by) / 2 -
      lift
    ).toFixed(1)} ${bx.toFixed(1)} ${by.toFixed(1)}`;
  });

  useEffect(() => {
    const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduced) {
      pulseRefs.current.forEach((p) => p?.setAttribute("opacity", "0"));
      return;
    }
    const lengths = arcRefs.current.map((p) => p?.getTotalLength() ?? 0);
    let raf = 0;
    const loop = (t: number) => {
      raf = requestAnimationFrame(loop);
      pulseRefs.current.forEach((pulse, i) => {
        const path = arcRefs.current[i];
        if (!pulse || !path || !lengths[i]) return;
        const progress = (t * 0.00035 * speed + i * 0.23) % 1;
        const pt = path.getPointAtLength(progress * lengths[i]);
        pulse.setAttribute("cx", String(pt.x));
        pulse.setAttribute("cy", String(pt.y));
        // fade in and out at the ends
        const fade = Math.min(1, Math.min(progress, 1 - progress) * 8);
        pulse.setAttribute("opacity", fade.toFixed(2));
      });
    };
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
  }, [speed, nodes.length]);

  return (
    <svg
      role="img"
      aria-label={`Network plate with ${nodes.length} nodes`}
      viewBox="-185 -125 370 235"
      preserveAspectRatio="xMidYMid meet"
      className={["block h-auto w-full", className].filter(Boolean).join(" ")}
      style={{ maxWidth: size, aspectRatio: "370 / 235", ...style }}
    >
      {/* extruded plate */}
      {[0, 2, 4, 6, 8, DEPTH].map((z) => (
        <g key={z} transform={`translate(0, ${-z}) ${ISO}`}>
          <path
            d={platePath}
            fill={FACE}
            stroke="var(--ink)"
            strokeOpacity={z === 0 || z === DEPTH ? 0.85 : 0.45}
            strokeWidth={z === 0 || z === DEPTH ? 1.5 : 0.75}
            vectorEffect="non-scaling-stroke"
            className="transition-[fill] duration-300"
          />
        </g>
      ))}

      {/* pins */}
      {nodes.map((node, i) => {
        const [px, py] = iso(node.x, node.y, TOP_Z);
        return (
          <g key={i}>
            <ellipse
              cx={px}
              cy={py}
              rx={9}
              ry={5.2}
              fill="color-mix(in oklab, var(--ink) 14%, var(--background))"
              stroke="var(--ink)"
              strokeOpacity={0.6}
              strokeWidth={1}
              vectorEffect="non-scaling-stroke"
              className="transition-[fill] duration-300"
            />
            <line
              x1={px}
              y1={py}
              x2={px}
              y2={py - 8}
              stroke="var(--ink)"
              strokeOpacity={0.6}
              strokeWidth={1}
              vectorEffect="non-scaling-stroke"
            />
            <ellipse
              cx={px}
              cy={py - 8}
              rx={6}
              ry={3.6}
              fill={node.hub ? accent : "color-mix(in oklab, var(--ink) 30%, var(--background))"}
              stroke="var(--ink)"
              strokeOpacity={0.7}
              strokeWidth={1}
              vectorEffect="non-scaling-stroke"
              className="transition-[fill] duration-300"
            />
          </g>
        );
      })}

      {/* arcs + pulses */}
      {arcs.map((d, i) => (
        <g key={i}>
          <path
            ref={(el) => {
              arcRefs.current[i] = el;
            }}
            d={d}
            fill="none"
            stroke="var(--ink)"
            strokeOpacity={0.3}
            strokeWidth={1}
            strokeDasharray="3 4"
            vectorEffect="non-scaling-stroke"
          />
          <circle
            ref={(el) => {
              pulseRefs.current[i] = el;
            }}
            r={3}
            fill={accent}
            opacity={0}
          />
        </g>
      ))}
    </svg>
  );
}

export default IsometricNetwork;

```

## Demo

```tsx
"use client";

import React from "react";
import { IsometricNetwork } from "../mellow/isometric-network";

export default function IsometricNetworkDemo() {
  return (
    <div className="flex flex-col items-center gap-4 p-6">
      <div className="w-full max-w-md rounded-2xl border border-[var(--rule)] bg-[rgba(var(--ink-rgb),0.02)] p-6">
        <IsometricNetwork size={400} className="mx-auto w-full" />
        <div className="mt-2">
          <div className="[font-family:var(--font-mono)] text-[0.625rem] font-medium tracking-[0.16em] text-[rgba(var(--ink-rgb),0.45)] uppercase">
            Edge network
          </div>
          <div className="mt-1 [font-family:var(--font-sans)] text-base font-medium tracking-[-0.01em] text-[var(--ink)]">
            Close to every user
          </div>
        </div>
      </div>
      <p className="text-[0.8125rem] text-[rgba(var(--ink-rgb),0.35)]">
        Pulses travel from the hub to every region
      </p>
    </div>
  );
}

```
