{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "uk-material-network",
  "title": "Uk Material Network",
  "description": "A decorative, synthetic UK material market: seeded site lifecycles, compatible local matching, reserved truck loads, fulfilment and fair-share dispatch. Includes a pure simulation, deterministic scrub timeline and a stylised UK silhouette. Not a forecast or road-routing model. Explore /patterns/material-network.",
  "registryDependencies": [
    "@nx-ui/use-background-clock",
    "@nx-ui/utils"
  ],
  "files": [
    {
      "path": "components/blocks/uk-material-network/market.ts",
      "content": "import { HUBS, landmassAt, type MapPoint } from \"./uk-outline\"\n\nexport interface MarketConfig {\n  seed: number\n  /** Maximum concurrent sites, 12–80. */\n  siteLimit: number\n  /** Mean seconds between new sites, 2–20. */\n  arrivalInterval: number\n  /** Fraction of new positions that require imports, 0.2–0.8. */\n  demandBias: number\n  /** Chance of a large import position, 0–1. */\n  majorProjects: number\n  /** Maximum abstract link distance in illustration units, 60–650. */\n  reach: number\n  /** Concurrent loads in transit, 4–64. */\n  fleet: number\n  /** Material units per load, 1–20; these are NOT tonnes. */\n  payload: number\n  /** Map units per model second, not a road speed. */\n  haulSpeed: number\n  /** Regional clustering strength, 0–1. */\n  clustering: number\n  compatibility: boolean\n  rebalance: boolean\n  /** Mean seconds between fairness rounds, 5–60. */\n  rebalanceInterval: number\n}\nexport const DEFAULT_MARKET: MarketConfig = {\n  seed: 42, siteLimit: 40, arrivalInterval: 3, demandBias: .5, majorProjects: .2,\n  reach: 280, fleet: 24, payload: 5, haulSpeed: 12, clustering: .7,\n  compatibility: true, rebalance: true, rebalanceInterval: 18,\n}\nconst bound = (n: number, lo: number, hi: number, fallback: number) => Number.isFinite(n) ? Math.max(lo, Math.min(hi, n)) : fallback\nexport function marketConfig(input: Partial<MarketConfig> = {}): MarketConfig {\n  const c = { ...DEFAULT_MARKET, ...input }\n  return { seed: Math.round(bound(c.seed, 1, 9999, 42)), siteLimit: Math.round(bound(c.siteLimit, 12, 80, 40)),\n    arrivalInterval: bound(c.arrivalInterval, 2, 20, 5), demandBias: bound(c.demandBias, .2, .8, .52),\n    majorProjects: bound(c.majorProjects, 0, 1, .2), reach: bound(c.reach, 60, 650, 280),\n    fleet: Math.round(bound(c.fleet, 4, 64, 24)), payload: Math.round(bound(c.payload, 1, 20, 5)),\n    haulSpeed: bound(c.haulSpeed, 5, 60, 18), clustering: bound(c.clustering, 0, 1, .7),\n    compatibility: c.compatibility !== false, rebalance: c.rebalance !== false,\n    rebalanceInterval: bound(c.rebalanceInterval, 5, 60, 18) }\n}\n\nexport interface MarketSite {\n  id: number; point: MapPoint; landmass: number; kind: \"import\" | \"export\"; material: 0 | 1\n  volume: number; remaining: number; born: number; lastServed: number; lastDispatch: number; completedAt?: number\n}\nexport interface MarketLoad {\n  id: number; from: number; to: number; amount: number; departed: number; arrives: number; fair: boolean\n}\nexport interface MarketFrame {\n  time: number; sites: MarketSite[]; loads: MarketLoad[]; delivered: number; completed: number\n  createdSupply: number; createdDemand: number; nextSite: number; nextLoad: number\n  nextArrival: number; nextRebalance: number; lastRebalance: number; fairnessUntil: number; rebalancedLoads: number\n}\n// Counter-based randomness: unrelated rendering, scrubbing and component mounts\n// cannot consume random numbers or alter the event stream.\nexport function marketRandom(seed: number, counter: number, channel = 0) {\n  let n = Math.imul(seed, 1597334677) ^ Math.imul(counter + 1, 3812015801) ^ Math.imul(channel + 3, 958282273)\n  n = Math.imul(n ^ n >>> 16, 2246822507)\n  return ((n ^ n >>> 13) >>> 0) / 4294967296\n}\nexport function sitePosition(seed: number, id: number, clustering: number): MapPoint {\n  for (let attempt = 0; attempt < 160; attempt++) {\n    const hub = HUBS[Math.floor(marketRandom(seed, id, attempt * 3) * HUBS.length)] ?? HUBS[0] ?? [280, 570]\n    const clustered = marketRandom(seed, id, 999) < clustering\n    const point: MapPoint = clustered\n      ? [hub[0] + (marketRandom(seed, id, attempt * 3 + 1) - .5) * 110, hub[1] + (marketRandom(seed, id, attempt * 3 + 2) - .5) * 110]\n      : [105 + marketRandom(seed, id, attempt * 3 + 1) * 385, 70 + marketRandom(seed, id, attempt * 3 + 2) * 670]\n    const rounded = point.map(n => Math.round(n * 100) / 100) as MapPoint\n    if (landmassAt(rounded) >= 0) return rounded\n  }\n  const fallback: MapPoint = HUBS[0] ?? [280, 570]\n  return [fallback[0], fallback[1]]\n}\nfunction spawn(frame: MarketFrame, config: MarketConfig) {\n  const id = frame.nextSite++, random = (channel: number) => marketRandom(config.seed, id, channel)\n  const kind = random(501) < config.demandBias ? \"import\" : \"export\"\n  const volume = Math.round((16 + random(502) * 45) * (kind === \"import\" && random(503) < config.majorProjects ? 4 : 1))\n  const point = sitePosition(config.seed, id, config.clustering)\n  frame.sites.push({ id, point, landmass: landmassAt(point), kind, volume, remaining: volume,\n    material: random(504) < .6 ? 0 : 1, born: frame.time, lastServed: frame.time, lastDispatch: frame.time })\n  if (kind === \"export\") frame.createdSupply += volume\n  else frame.createdDemand += volume\n}\nexport function initialMarket(config: MarketConfig): MarketFrame {\n  const frame: MarketFrame = { time: 0, sites: [], loads: [], delivered: 0, completed: 0, createdSupply: 0, createdDemand: 0,\n    nextSite: 0, nextLoad: 0, nextArrival: 3, nextRebalance: config.rebalanceInterval, lastRebalance: -100, fairnessUntil: -1, rebalancedLoads: 0 }\n  for (let i = 0; i < Math.min(config.siteLimit, 20); i++) spawn(frame, config)\n  return frame\n}\n\n/** One immutable, one-second step. Export stock leaves at dispatch, import\n * balances settle on arrival, and in-flight capacity is reserved. Rebalancing\n * changes the NEXT dispatches only: cargo in transit is never teleported. */\nexport function stepMarket(previous: MarketFrame, config: MarketConfig): MarketFrame {\n  const time = previous.time + 1\n  const frame: MarketFrame = { ...previous, time, sites: previous.sites.filter(s => s.completedAt === undefined || time - s.completedAt < 7).map(s => ({ ...s })), loads: [...previous.loads] }\n  const byId = new Map(frame.sites.map(s => [s.id, s]))\n  for (const load of frame.loads) if (load.arrives <= time) {\n    const destination = byId.get(load.to)!, source = byId.get(load.from)!\n    destination.remaining -= load.amount\n    destination.lastServed = time; source.lastServed = time\n    frame.delivered += load.amount\n  }\n  frame.loads = frame.loads.filter(load => load.arrives > time)\n  for (const site of frame.sites) if (site.remaining === 0 && site.completedAt === undefined && !frame.loads.some(load => load.to === site.id || load.from === site.id)) {\n    site.completedAt = time; frame.completed++\n  }\n  if (time >= frame.nextArrival) {\n    if (frame.sites.length < config.siteLimit) spawn(frame, config)\n    frame.nextArrival = time + Math.max(1, Math.round(config.arrivalInterval * (.6 + marketRandom(config.seed, time, 800) * .8)))\n  }\n  if (time >= frame.nextRebalance) {\n    frame.nextRebalance = time + Math.max(3, Math.round(config.rebalanceInterval * (.65 + marketRandom(config.seed, time, 801) * .7)))\n    if (config.rebalance) { frame.lastRebalance = time; frame.fairnessUntil = time + Math.max(4, config.rebalanceInterval * .65) }\n  }\n  const fair = config.rebalance && time <= frame.fairnessUntil\n  const reserved = new Map<number, number>()\n  for (const load of frame.loads) reserved.set(load.to, (reserved.get(load.to) ?? 0) + load.amount)\n  // Bounded throughput: enough simultaneous connections to read as a network,\n  // never an unbounded all-pairs graph. Large orders dominate the unbalanced mode.\n  for (let dispatch = 0; dispatch < 6 && frame.loads.length < config.fleet; dispatch++) {\n    const imports = frame.sites.filter(s => s.kind === \"import\" && s.remaining > (reserved.get(s.id) ?? 0) && time - s.born >= 1)\n    const priority = (s: MarketSite) => fair\n      ? (time - Math.max(s.lastServed, s.lastDispatch)) * 8 + (1 - (reserved.get(s.id) ?? 0) / s.remaining) * 20\n      : s.remaining\n    imports.sort((a, b) => priority(b) - priority(a) || a.id - b.id)\n    let matched = false\n    for (const destination of imports) {\n      const distance = (s: MarketSite) => Math.hypot(s.point[0] - destination.point[0], s.point[1] - destination.point[1])\n      const source = frame.sites.filter(s => s.kind === \"export\" && s.remaining > 0 && time - s.born >= 1 && s.landmass === destination.landmass && (!config.compatibility || s.material === destination.material) && distance(s) <= config.reach)\n        .sort((a, b) => distance(a) - distance(b) || a.id - b.id)[0]\n      if (!source) continue\n      const amount = Math.min(config.payload, source.remaining, destination.remaining - (reserved.get(destination.id) ?? 0))\n      source.remaining -= amount\n      source.lastDispatch = time; destination.lastDispatch = time\n      reserved.set(destination.id, (reserved.get(destination.id) ?? 0) + amount)\n      const departed = time + dispatch * .14\n      frame.loads.push({ id: frame.nextLoad++, from: source.id, to: destination.id, amount,\n        departed, arrives: Math.ceil(departed + Math.max(3, distance(source) / config.haulSpeed)), fair })\n      if (fair) frame.rebalancedLoads++\n      matched = true\n      break\n    }\n    if (!matched) break\n  }\n  return frame\n}\n\n/** Precomputed, bounded history makes backwards seeking exact and cheap. */\nexport function createMarketTimeline(input: Partial<MarketConfig> = {}, duration = 600) {\n  const config = marketConfig(input), frames = [initialMarket(config)]\n  const end = Math.round(bound(duration, 1, 900, 600))\n  for (let t = 1; t <= end; t++) frames.push(stepMarket(frames[t - 1] ?? initialMarket(config), config))\n  return frames\n}\nexport const marketFrameAt = (frames: MarketFrame[], time: number): MarketFrame =>\n  frames[Math.floor(bound(time, 0, frames.length - 1, 0))] ?? frames[0] ?? initialMarket(DEFAULT_MARKET)\n\n/** Integrated breathing phase, in cycles. Dividing by playback rate keeps the\n * visual pulse at 0.15–0.55Hz even when model time runs at 32×. */\nexport function pressurePhase(waitingSeconds: number, playbackRate = 1) {\n  const age = Math.max(0, waitingSeconds)\n  const rampIntegral = age < 60 ? age * age / 120 : age - 30\n  return (.15 * age + .4 * rampIntegral) / bound(playbackRate, 1, 32, 1)\n}\n",
      "type": "registry:component",
      "target": "components/blocks/uk-material-network/market.ts"
    },
    {
      "path": "components/blocks/uk-material-network/uk-material-network.tsx",
      "content": "\"use client\"\n\nimport { useId, useMemo } from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport { useBackgroundClock } from \"@/hooks/use-background-clock\"\nimport { createMarketTimeline, marketConfig, marketFrameAt, pressurePhase, type MarketConfig, type MarketFrame } from \"./market\"\nimport { ukPath } from \"./uk-outline\"\n\nexport interface UKMaterialNetworkProps extends Partial<MarketConfig> {\n  /** Optional shared precomputed timeline. Otherwise computed from market props. */\n  timeline?: MarketFrame[]\n  /** Controlled model seconds. Caller owns playback/reduced-motion when supplied. */\n  time?: number\n  animated?: boolean\n  paused?: boolean\n  /** Model seconds per wall-clock second, 1–32. Also bounds pulse pace with controlled time. */\n  speed?: number\n  /** Static fallback and starting position, in model seconds. */\n  initialTime?: number\n  loop?: boolean\n  opacity?: number\n  importColor?: string\n  exportColor?: string\n  showMap?: boolean\n  showGrid?: boolean\n  showRoutes?: boolean\n  showLoads?: boolean\n  showPressure?: boolean\n  showLabels?: boolean\n  showCompletion?: boolean\n  /** Graphic oblique projection, 0–1. Not geographic coordinates. */\n  tilt?: number\n  /** Site glyph scale, 0.5–2. */\n  siteScale?: number\n  /** Decorative path curvature, 0–1. Lines are NOT road routes. */\n  curvature?: number\n  /** Composition zoom, 0.6–1.6. */\n  zoom?: number\n  className?: string\n}\nconst bounded = (n: number, lo: number, hi: number, fallback: number) => Number.isFinite(n) ? Math.max(lo, Math.min(hi, n)) : fallback\nconst ease = (n: number) => 1 - (1 - bounded(n, 0, 1, 0)) ** 3\n\n/** Decorative, synthetic UK market. Transparent SVG; position inside a relative\n * container with a pause control. Site glyphs encode volume, polarity, progress\n * and fulfilment; curves illustrate links, not roads. No live data or forecasts. */\nexport function UKMaterialNetwork({ timeline, time, animated = true, paused = false, speed = 1, initialTime = 45, loop = true,\n  opacity = .85, importColor = \"#94c8ac\", exportColor = \"#d89895\", showMap = true, showGrid = true,\n  showRoutes = true, showLoads = true, showPressure = true, showLabels = false, showCompletion = true,\n  tilt = .2, siteScale = 1, curvature = .25, zoom = 1, className, ...market\n}: UKMaterialNetworkProps) {\n  const key = JSON.stringify(marketConfig(market))\n  const frames = useMemo(() => timeline ?? createMarketTimeline(JSON.parse(key)), [timeline, key])\n  const clock = useBackgroundClock({ playing: time === undefined && animated && !paused, rate: bounded(speed, 1, 32, 1),\n    initialTime, end: frames.length - 1, loop, resetKey: key })\n  const seconds = bounded(time ?? clock.time, 0, frames.length - 1, initialTime)\n  const frame = marketFrameAt(frames, seconds), previous = marketFrameAt(frames, Math.max(0, seconds - 1))\n  const previousSites = new Map(previous.sites.map(s => [s.id, s]))\n  const byId = new Map(frame.sites.map(s => [s.id, s]))\n  const id = useId().replace(/:/g, \"\")\n  const inclination = bounded(tilt, 0, 1, .2), magnify = bounded(zoom, .6, 1.6, 1)\n  const bend = bounded(curvature, 0, 1, .25)\n  const glyphScale = bounded(siteScale, .5, 2, 1)\n  const routes = new Set<string>()\n  return <div ref={clock.viewportRef} className={cn(\"pointer-events-none absolute inset-0\", className)} data-uk-network data-network-time={seconds.toFixed(3)}>\n    <svg aria-hidden=\"true\" focusable=\"false\" className=\"size-full\" viewBox=\"0 0 1000 800\" preserveAspectRatio=\"xMidYMid slice\" fill=\"none\" style={{ opacity: bounded(opacity, 0, 1, .85) }}>\n      <defs><pattern id={`${id}-grid`} width=\"30\" height=\"30\" patternUnits=\"userSpaceOnUse\"><path d=\"M30 0H0V30\" stroke=\"currentColor\" strokeOpacity=\".065\" strokeWidth=\".6\" /></pattern></defs>\n      {showGrid && <rect data-market-layer=\"grid\" width=\"1000\" height=\"800\" fill={`url(#${id}-grid)`} />}\n      <g transform={`translate(500 400) scale(${magnify}) translate(-300 -400)`}>\n        <g transform={`matrix(1 0 ${inclination * -.15} ${1 - inclination * .28} ${inclination * 60} ${inclination * 120})`}>\n          {showMap && <g data-market-layer=\"map\">\n            {[12, 6].map(y => <path key={y} d={ukPath} transform={`translate(0 ${y * inclination})`} stroke=\"currentColor\" strokeOpacity=\".09\" strokeWidth=\".8\" />)}\n            <path d={ukPath} fill=\"currentColor\" fillOpacity=\".028\" stroke=\"currentColor\" strokeOpacity=\".3\" strokeWidth=\"1\" />\n          </g>}\n          {frame.loads.map(load => {\n            const a = byId.get(load.from)!, b = byId.get(load.to)!\n            const [x, y] = a.point, [u, v] = b.point\n            const cx = (x + u) / 2 + (v - y) * bend * .3, cy = (y + v) / 2 - Math.abs(u - x) * bend * .3\n            const path = `M${x} ${y}Q${cx} ${cy} ${u} ${v}`\n            const p = bounded((seconds - load.departed) / (load.arrives - load.departed), 0, 1, 0)\n            const px = (1 - p) ** 2 * x + 2 * (1 - p) * p * cx + p ** 2 * u\n            const py = (1 - p) ** 2 * y + 2 * (1 - p) * p * cy + p ** 2 * v\n            const routeKey = `${a.id}:${b.id}`, first = !routes.has(routeKey)\n            routes.add(routeKey)\n            return <g key={load.id} data-market-load={load.id}>\n              {showRoutes && first && <path data-market-layer=\"routes\" d={path} stroke={load.fair ? importColor : \"currentColor\"} strokeOpacity={load.fair ? .4 : .22} strokeWidth=\".8\" strokeDasharray={load.fair ? \"3 3\" : undefined} />}\n              {showLoads && <g data-market-layer=\"loads\"><circle cx={px} cy={py} r=\"4\" fill={importColor} opacity=\".12\" /><circle cx={px} cy={py} r=\"1.8\" fill=\"currentColor\" opacity=\".9\" /></g>}\n            </g>\n          })}\n          {frame.sites.map(site => {\n            const complete = site.completedAt !== undefined\n            const completedAge = complete ? seconds - site.completedAt! : 0\n            const appear = ease((seconds - site.born) / .8)\n            const fade = complete ? 1 - ease((completedAge - 3) / 4) : 1\n            const radius = (4 + Math.sqrt(site.volume) * .75) * glyphScale\n            const last = previousSites.get(site.id)?.remaining ?? site.remaining\n            const remaining = last + (site.remaining - last) * ease((seconds % 1) * 2)\n            const fillRatio = complete ? 1 : site.kind === \"import\" ? 1 - remaining / site.volume : remaining / site.volume\n            const filled = radius * Math.sqrt(Math.max(0, fillRatio))\n            const busy = frame.loads.some(load => load.to === site.id || load.from === site.id)\n            const pressure = complete ? 0 : bounded((seconds - site.lastServed - 10) / 60, 0, 1, 0) * (busy ? .15 : 1)\n            // Slow, low-contrast breathing, bounded below one cycle per second.\n            const pulse = Number(((1 + Math.sin(pressurePhase(seconds - site.lastServed - 10, speed) * Math.PI * 2)) / 2).toFixed(6))\n            const colour = complete ? \"currentColor\" : site.kind === \"import\" ? importColor : exportColor\n            return <g key={site.id} data-market-site={site.id} data-kind={site.kind} data-complete={complete} data-remaining={site.remaining}\n              transform={`translate(${site.point.join(\" \")}) scale(${appear * (complete ? 1 - (1 - fade) * .6 : 1)})`} opacity={fade}>\n              {showPressure && pressure > .1 && <rect data-market-layer=\"pressure\" x={-radius - 3 - pulse * 2} y={-radius - 3 - pulse * 2} width={(radius + 3 + pulse * 2) * 2} height={(radius + 3 + pulse * 2) * 2} rx=\"2\" stroke={colour} strokeOpacity={pressure * (.12 + pulse * .2)} />}\n              {showCompletion && complete && <rect data-market-layer=\"completion\" x={-radius - 3} y={-radius - 3} width={radius * 2 + 6} height={radius * 2 + 6} rx=\"2\" stroke=\"currentColor\" strokeWidth=\"3\" strokeOpacity={.2 * (1 - completedAge / 7)} />}\n              <rect x={-radius} y={-radius} width={radius * 2} height={radius * 2} rx=\"1.5\" stroke={colour} strokeWidth={complete ? 1.4 : .9} strokeOpacity=\".9\" />\n              <rect x={-filled} y={-filled} width={filled * 2} height={filled * 2} rx=\"1\" fill={colour} fillOpacity={complete ? .3 : .38} />\n              {complete ? <path d=\"M-3 0l2 2 4-4\" stroke=\"currentColor\" strokeWidth=\"1\" /> : <path d={site.kind === \"import\" ? \"M-2.5 0h5M0-2.5v5\" : \"M-2.5 0h5\"} stroke={colour} strokeWidth=\"1\" />}\n              {site.material === 1 && <circle cx={radius - 2} cy={-radius + 2} r=\"1\" fill={colour} />}\n              {showLabels && <text data-market-layer=\"labels\" x={radius + 4} y=\"2\" fontSize=\"7\" fontFamily=\"monospace\" fill=\"currentColor\" opacity=\".6\">{site.kind === \"import\" ? \"+\" : \"−\"}{site.remaining}</text>}\n            </g>\n          })}\n        </g>\n      </g>\n    </svg>\n  </div>\n}\n",
      "type": "registry:component",
      "target": "components/blocks/uk-material-network/uk-material-network.tsx"
    },
    {
      "path": "components/blocks/uk-material-network/uk-outline.ts",
      "content": "/** Original, deliberately simplified UK silhouette in illustration coordinates.\n * Not a geographic dataset, boundary authority or routing surface. Northern\n * Ireland and selected islands are included; the Republic of Ireland is not UK. */\nexport type MapPoint = [number, number]\nexport const UK_LAND: MapPoint[][] = [\n  [[233,74],[266,66],[278,83],[308,75],[322,89],[301,115],[317,131],[344,138],[351,160],[330,183],[307,191],[322,213],[300,229],[298,249],[278,257],[305,278],[327,291],[337,317],[356,339],[360,363],[381,389],[387,421],[403,447],[418,478],[413,511],[435,532],[471,537],[488,556],[483,581],[468,602],[489,621],[472,639],[435,645],[414,657],[379,658],[358,649],[335,660],[304,659],[292,674],[260,676],[244,691],[221,690],[209,705],[190,709],[172,731],[142,732],[128,743],[107,740],[120,724],[139,714],[159,692],[180,683],[190,666],[216,658],[235,642],[255,638],[270,615],[255,604],[229,608],[211,596],[209,572],[190,562],[179,544],[195,529],[221,532],[233,513],[224,498],[239,479],[256,487],[275,475],[272,454],[293,436],[302,414],[302,393],[286,374],[281,350],[263,341],[250,327],[230,337],[212,324],[226,302],[217,288],[201,297],[192,280],[207,255],[194,245],[212,221],[207,197],[222,183],[209,162],[216,140],[201,126],[217,109]],\n  [[147,354],[171,348],[191,361],[199,382],[186,400],[177,420],[154,428],[143,414],[124,417],[111,404],[122,391],[117,377],[135,367]],\n  [[179,167],[189,153],[196,167],[189,184],[192,200],[180,215],[173,201]],\n  [[160,210],[169,198],[174,214],[169,239],[155,244],[158,227]],\n  [[305,40],[315,35],[323,43],[312,52]],\n  [[351,10],[358,5],[362,20],[354,29]],\n]\n\nexport const ukPath = UK_LAND.map(points => `M${points.map(p => p.join(\",\")).join(\" L\")} Z`).join(\" \")\nexport function insidePolygon([x, y]: MapPoint, points: MapPoint[]) {\n  let inside = false\n  for (let i = 0, j = points.length - 1; i < points.length; j = i++) {\n    const current = points[i], previous = points[j]\n    if (!current || !previous) continue\n    const [xi, yi] = current, [xj, yj] = previous\n    if ((yi > y) !== (yj > y) && x < (xj - xi) * (y - yi) / (yj - yi) + xi) inside = !inside\n  }\n  return inside\n}\nexport const landmassAt = (point: MapPoint) => UK_LAND.findIndex(polygon => insidePolygon(point, polygon))\n\n// Illustrative regional hubs, not city coordinates. Jitter is rejection-sampled\n// against the same silhouette we draw, so sites cannot appear offshore.\nexport const HUBS: MapPoint[] = [[280,570],[385,600],[326,495],[312,440],[339,380],[282,303],[273,217],[254,129],[167,389],[236,580],[207,680]]\n",
      "type": "registry:component",
      "target": "components/blocks/uk-material-network/uk-outline.ts"
    }
  ],
  "type": "registry:block"
}