{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dot-map",
  "title": "Dot Map",
  "description": "A dotted world map lights up city by city, ending on 142 countries.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/dot-map/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport type React from \"react\";\nimport { useId, useMemo } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: manrope } = loadManrope(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\n\nexport interface DotMapCity {\n  lon: number;\n  lat: number;\n}\n\nexport interface DotMapProps {\n  count?: number;\n  label?: string;\n  cities?: DotMapCity[];\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\ntype Poly = number[];\n\nconst LAND: Poly[] = [\n  [\n    -168, 66, -162, 70, -156, 71.3, -140, 69.6, -128, 70, -115, 68.5, -95, 68,\n    -85, 69.5, -80, 69, -68, 66, -62, 60, -56, 52, -60, 47, -66, 44.5, -70, 42,\n    -74, 40.5, -76, 35, -81, 31, -80.5, 25.5, -82.5, 28, -84, 30, -89, 30, -94,\n    29.5, -97.5, 27, -97.5, 22, -95, 18.5, -91, 18.5, -90.5, 21, -87, 21.5, -88,\n    16, -83.5, 15, -83.5, 11, -79.5, 9, -77.5, 8.5, -80, 7.5, -85.5, 10, -87.5,\n    13, -92, 14.5, -96, 15.7, -105, 19.5, -106, 23, -109.5, 23.5, -112, 29,\n    -117, 32.5, -120.5, 34.5, -124, 40, -124.5, 48, -130, 54.5, -137, 59, -146,\n    60.5, -152, 59, -158, 57, -164, 55, -158, 58.5, -162, 60, -166, 62, -165,\n    64.5,\n  ],\n  [\n    -90, 74, -78, 73.5, -67, 70, -62, 66.5, -66, 62.5, -74, 64, -84, 66, -90,\n    69,\n  ],\n  [\n    -120, 76, -95, 81, -75, 83, -62, 82, -75, 78, -80, 76, -98, 75.5, -118,\n    73.5,\n  ],\n  [\n    -73, 78, -60, 82, -35, 83.5, -20, 82, -18, 76, -22, 70, -32, 68, -40, 65,\n    -43, 60, -49, 61.5, -53, 66, -55, 70, -58, 75.5, -66, 77,\n  ],\n  [-85, 21.8, -82, 23.2, -77, 22.3, -74.2, 20.2, -77.5, 19.9, -80, 21.8],\n  [-74.4, 18.5, -72.8, 19.9, -69, 19.7, -68.4, 18.5, -71.5, 17.6],\n  [\n    -77.5, 8.5, -72, 12, -63, 10.7, -58, 6.5, -52, 5, -50, 0, -44, -2.5, -35,\n    -5.5, -35, -9, -39, -14, -39.5, -18, -41, -22.5, -48, -26, -48.5, -28.5,\n    -53, -33.5, -57, -36, -57.5, -38.5, -62, -39, -65, -41, -63.5, -42.5, -67.5,\n    -46, -66, -48, -69, -51, -68.5, -53, -70.5, -54.5, -73.5, -52.5, -75.5, -47,\n    -73.5, -42, -73.5, -37, -71.5, -30, -70.5, -23, -70.3, -18, -75, -15.5, -77,\n    -12, -79.5, -7.5, -81, -4.5, -80, -1, -80, 1, -78.5, 2.5, -77.5, 4, -77.5,\n    7,\n  ],\n  [-24, 65.5, -14, 66.3, -13.5, 65, -18, 63.4, -22.5, 63.8],\n  [\n    -5.7, 50, 1.5, 51.2, 1.7, 52.7, 0, 53.5, -0.5, 54.5, -2, 55.9, -1.8, 57.6,\n    -3.5, 58.6, -5, 58.6, -6.2, 56.6, -5, 55, -3, 54, -4.5, 53.3, -4.6, 52,\n    -5.3, 51.7, -3, 51.4,\n  ],\n  [-10, 51.6, -6, 52.2, -6, 54, -7.5, 55.2, -10, 54.2],\n  [\n    -9.3, 43, -8, 43.7, -1.8, 43.4, -1.2, 46, -2.2, 47.1, -4.6, 47.9, -4.6,\n    48.6, -1.6, 48.7, -1.3, 49.6, 0.2, 49.7, 1.6, 50.9, 3.2, 51.3, 4.4, 52.2,\n    4.8, 53, 7, 53.4, 8.5, 53.8, 8.3, 56.9, 10.5, 57.6, 10.2, 55.2, 12, 54.3,\n    14, 54, 19, 54.5, 21, 56.5, 23.5, 59.3, 29, 60, 22.5, 60.2, 21.5, 62.8, 25,\n    65, 21.5, 65.8, 19, 63.5, 17.2, 61, 18.7, 59.8, 16.5, 57, 14.3, 55.5, 12.8,\n    55.6, 11, 58.7, 10, 59.2, 6, 58.1, 5, 62, 10, 64, 14, 67.5, 18, 69.5, 25,\n    71, 31, 70, 41, 67, 44, 68.5, 53, 68.3, 60, 69, 68, 72.5, 72, 73, 80, 72.5,\n    87, 75, 100, 78, 105, 77.5, 113, 74, 127, 73.5, 140, 72.5, 150, 71.5, 160,\n    70, 170, 70, 180, 69, 180, 65, 178, 62.5, 173, 61.3, 170, 60, 165, 60,\n    163.5, 58, 162, 56, 160, 53.5, 156.6, 51, 156, 55, 156, 57.5, 154, 59.2,\n    150, 59.6, 143, 59.3, 137, 54, 140.5, 52, 141, 48.5, 138, 45.5, 135, 43.3,\n    132, 43, 130, 42.5, 129.5, 40.5, 129.3, 37, 129.4, 35.4, 126.5, 34.4, 126.3,\n    36.8, 125.1, 37.8, 124.5, 39.8, 121.6, 39, 122, 40.5, 118.5, 39.2, 118.8,\n    37.6, 122.5, 37.2, 120.5, 35.5, 119.5, 34.5, 121, 32, 122, 29.8, 121, 28,\n    119.5, 25.5, 116.5, 23, 113, 22.3, 110.4, 21.1, 108, 21.6, 106.5, 20.5,\n    105.7, 18.8, 108.5, 15.5, 109.3, 12.5, 108, 11, 105, 8.6, 104.8, 10.4, 103,\n    11, 101, 12.5, 100.2, 13.3, 99.2, 10.4, 100.4, 7.3, 103.5, 4.5, 104.3, 1.4,\n    103.4, 1.3, 101.3, 2.8, 100.3, 5, 98.3, 8.2, 98.5, 10.5, 97.7, 16.5, 94.5,\n    16.2, 94, 19, 92, 21.5, 90, 21.8, 87.2, 21.5, 86.5, 20, 80.3, 15.5, 80.2,\n    13, 79.8, 10.3, 77.5, 8, 76.3, 10, 74.8, 13, 73.4, 16, 72.8, 19.5, 72.6,\n    21.4, 70.5, 20.8, 69, 22.4, 67.3, 24.6, 66.5, 25.4, 61.5, 25.2, 57.3, 25.8,\n    56.5, 27.2, 54, 26.6, 51.5, 27.9, 50.2, 29.9, 48, 30, 48.5, 28.4, 50, 26.3,\n    51.6, 24.2, 54.5, 24.2, 56, 25.6, 56.4, 24.3, 58.5, 23.6, 59.8, 22.4, 58.5,\n    20.5, 57.7, 19, 55, 17.2, 52.2, 15.7, 48.7, 14, 45, 12.8, 43.5, 12.7, 42.7,\n    15.5, 41.2, 18.5, 39, 21.5, 38.4, 24, 36.6, 26, 35, 28, 34.9, 29.5, 34.5,\n    31.6, 35.5, 33.5, 36, 35, 36, 36.6, 34.5, 36.8, 32.5, 36.1, 30, 36.3, 28,\n    36.7, 27.3, 37.5, 26.6, 38.4, 26.2, 39.5, 26.2, 40.6, 24, 40.8, 22.8, 40.4,\n    23.8, 38, 23.2, 36.5, 21.7, 36.8, 21, 38.3, 20.2, 39.6, 19.4, 41.8, 18.5,\n    42.5, 15.8, 43.7, 13.7, 45.1, 12.4, 45.4, 12.3, 44.3, 13.7, 43.5, 14.2,\n    42.2, 16.1, 41.4, 18.5, 40.2, 16.6, 38.9, 15.7, 38, 15.9, 39.6, 14.9, 40.3,\n    12.8, 41.3, 11.1, 42.4, 10, 44, 8.7, 44.4, 7, 43.6, 4.6, 43.4, 3.2, 41.9,\n    0.9, 41, 0, 39.8, -0.6, 38.3, -2.2, 36.7, -4.4, 36.7, -5.6, 36, -6.4, 36.8,\n    -7.4, 37.2, -8.9, 37, -8.8, 38.7, -9.5, 39.4, -8.8, 41.9,\n  ],\n  [-180, 69, -174, 67, -170, 66, -172, 64.5, -180, 65],\n  [12.4, 38, 15.6, 38.3, 15.1, 36.7],\n  [11, 78.5, 16, 80, 23, 80.3, 27, 79.5, 22, 77.5, 17, 76.5, 13, 77.5],\n  [51.5, 71.5, 55, 73, 58, 75.5, 68, 77, 65, 75.5, 58, 72.5, 56, 70.6],\n  [\n    -5.9, 35.8, -1.5, 35.1, 3, 36.8, 10, 37.3, 11, 35, 10, 34, 11, 33.2, 15.2,\n    32.3, 20, 30.8, 23, 32.6, 29, 30.9, 32.3, 31.2, 34.2, 31.3, 34.5, 28, 33.5,\n    27, 35.5, 23.5, 37.2, 21, 38.5, 18, 39.7, 15.5, 43.3, 12.5, 44, 10.4, 51.2,\n    11.8, 51, 10.5, 49, 6.5, 46, 2, 41.5, -1.8, 39.2, -4.7, 39.5, -8, 40.5,\n    -10.5, 40.5, -15, 35, -22, 32.8, -25.8, 32.4, -28.6, 30, -31.3, 27, -33.5,\n    22, -34.2, 18.5, -34.2, 18.2, -31.5, 15.2, -27, 14.5, -22.5, 11.8, -17,\n    12.2, -13, 13.7, -10.7, 12, -5, 9, -1, 9.6, 3.5, 8.5, 4.5, 5.5, 4.3, 1.5,\n    6.2, -2, 4.8, -7.5, 4.4, -11.5, 6.9, -13.3, 8.8, -15, 10.9, -16.7, 12.5,\n    -17.4, 14.7, -16.4, 19.5, -17, 21, -15, 24.5, -13, 27.6, -9.8, 30, -9.5,\n    32.5, -6.8, 34,\n  ],\n  [44, -25, 47, -25, 50.2, -15.5, 49.3, -12, 44.3, -16.2, 43.3, -22],\n  [\n    113.5, -22, 114, -26.5, 115, -34, 118, -35, 124, -33.8, 129, -31.6, 132,\n    -31.5, 135.5, -34.8, 137.8, -32.5, 138.5, -35.5, 140, -38, 144, -38.3,\n    146.5, -39, 150, -37.5, 153.2, -30, 153.5, -25, 150.5, -22, 146, -18.8,\n    145.3, -15, 142.5, -10.7, 141.6, -13, 141.5, -16.5, 140.5, -17.5, 135.5,\n    -15, 136.8, -12.2, 132, -11.3, 130, -13, 129.3, -15, 126, -14, 122.3, -17.2,\n    121, -19.5, 117, -20.7,\n  ],\n  [144.7, -40.7, 148.3, -40.9, 147.9, -43.2, 146, -43.6],\n  [172.7, -34.5, 178.5, -37.7, 177, -39.3, 174.8, -41.4, 173.8, -39.3],\n  [172.6, -40.5, 174.2, -41.7, 171.2, -44.3, 169, -46.6, 166.5, -46, 172, -41],\n  [\n    129.6, 33.3, 131, 31, 132, 33.5, 135.3, 33.6, 139.8, 35, 140.9, 36.9, 141.9,\n    39.5, 141.5, 41.4, 140, 40.8, 139.8, 38.4, 137, 37, 133, 35.5, 130.9, 34.3,\n  ],\n  [\n    140, 41.5, 141.5, 42.5, 143.3, 42, 145.5, 43.3, 144.3, 44.2, 142, 45.5,\n    141.7, 44.2, 140.4, 43.2,\n  ],\n  [120.1, 23, 121, 25.3, 122, 25, 120.8, 21.9],\n  [79.7, 8, 80.2, 9.8, 81.9, 7.2, 80.5, 5.9],\n  [95.2, 5.6, 98, 4, 104.5, -2.5, 106, -5.8, 102.3, -4, 98.5, 0, 95.3, 3],\n  [105.2, -6.8, 108, -6.5, 112.5, -6.9, 114.5, -7.8, 110, -8.1, 106, -7.4],\n  [\n    109, 1.5, 110, -1.5, 111, -3, 114.5, -4, 116.5, -2, 118, 1, 119, 5, 116.5,\n    7, 114, 4.5, 111.5, 2.5,\n  ],\n  [119.4, -3.5, 120, 1, 124.9, 1.5, 121.5, -1, 123, -4.5, 120.5, -5.6],\n  [\n    131, -1.3, 135, -3.3, 138, -1.6, 141, -2.6, 146, -5, 147.5, -6.5, 147.2,\n    -8.5, 150, -10.4, 146, -8.1, 143.5, -9, 142.3, -9.2, 138.5, -8.3, 137.8,\n    -5.5, 133.5, -4, 132, -2.7,\n  ],\n  [120, 18.5, 122.3, 18.5, 124, 13, 121.5, 13.9, 120.6, 14.5, 119.8, 16.3],\n  [122, 7, 126.3, 6.3, 125.5, 9.8, 123.5, 8.5],\n];\n\nconst WATER: Poly[] = [\n  [-95, 59, -88, 56, -82, 53, -79, 55, -77, 60, -78, 62.5, -86, 64, -94, 62],\n  [\n    27.5, 42, 28, 44, 30, 45.5, 33, 44.5, 36.5, 45.3, 39.5, 47, 38, 44.5, 41.5,\n    41.5, 36, 41.5, 31, 41.2, 28.5, 41,\n  ],\n  [\n    47, 44.5, 49, 46.5, 53, 47, 53.5, 45, 51, 44, 52.5, 41.7, 54, 40.5, 53.8,\n    37.3, 51, 36.7, 49, 38, 49.5, 40.3, 47.5, 43,\n  ],\n];\n\nconst DEFAULT_CITIES: DotMapCity[] = [\n  { lon: -122.4, lat: 37.8 },\n  { lon: -118.2, lat: 34.1 },\n  { lon: -123.1, lat: 49.3 },\n  { lon: -104.9, lat: 39.7 },\n  { lon: -99.1, lat: 19.4 },\n  { lon: -87.6, lat: 41.9 },\n  { lon: -79.4, lat: 43.7 },\n  { lon: -74, lat: 40.7 },\n  { lon: -84.4, lat: 33.7 },\n  { lon: -74.1, lat: 4.7 },\n  { lon: -77, lat: -12 },\n  { lon: -70.7, lat: -33.4 },\n  { lon: -58.4, lat: -34.6 },\n  { lon: -46.6, lat: -23.5 },\n  { lon: -43.2, lat: -22.9 },\n  { lon: -21.9, lat: 64.1 },\n  { lon: -9.1, lat: 38.7 },\n  { lon: -3.7, lat: 40.4 },\n  { lon: -0.1, lat: 51.5 },\n  { lon: -6.3, lat: 53.3 },\n  { lon: 2.4, lat: 48.9 },\n  { lon: 4.9, lat: 52.4 },\n  { lon: -7.6, lat: 33.6 },\n  { lon: 3.4, lat: 6.5 },\n  { lon: 13.4, lat: 52.5 },\n  { lon: 12.5, lat: 41.9 },\n  { lon: 18.1, lat: 59.3 },\n  { lon: 21, lat: 52.2 },\n  { lon: 18.4, lat: -33.9 },\n  { lon: 28, lat: -26.2 },\n  { lon: 31.2, lat: 30 },\n  { lon: 29, lat: 41 },\n  { lon: 36.8, lat: -1.3 },\n  { lon: 30.5, lat: 50.5 },\n  { lon: 46.7, lat: 24.7 },\n  { lon: 55.3, lat: 25.2 },\n  { lon: 72.9, lat: 19.1 },\n  { lon: 77.2, lat: 28.6 },\n  { lon: 77.6, lat: 13 },\n  { lon: 90.4, lat: 23.8 },\n  { lon: 100.5, lat: 13.8 },\n  { lon: 103.8, lat: 1.4 },\n  { lon: 106.8, lat: -6.2 },\n  { lon: 106.7, lat: 10.8 },\n  { lon: 114.2, lat: 22.3 },\n  { lon: 121.5, lat: 31.2 },\n  { lon: 116.4, lat: 39.9 },\n  { lon: 121, lat: 14.6 },\n  { lon: 127, lat: 37.6 },\n  { lon: 139.7, lat: 35.7 },\n  { lon: 135.5, lat: 34.7 },\n  { lon: 115.9, lat: -31.9 },\n  { lon: 151.2, lat: -33.9 },\n  { lon: 145, lat: -37.8 },\n  { lon: 174.8, lat: -36.8 },\n];\n\nconst STEP = 2.5;\nconst LAT_TOP = 80;\nconst LAT_BOTTOM = -57.5;\nconst COLS = Math.round(360 / STEP);\nconst ROWS = Math.round((LAT_TOP - LAT_BOTTOM) / STEP);\n\nconst EASE_IO = Easing.bezier(0.65, 0, 0.35, 1);\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nconst THEMES = {\n  dark: {\n    bg: \"#0A0A0B\",\n    fg: \"#F3F2EF\",\n    muted: \"#8B8A90\",\n    city: \"#FFFFFF\",\n    lift: 0.08,\n    dotAlpha: 1,\n    grain: 0.06,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    fg: \"#121214\",\n    muted: \"#6E6C67\",\n    city: \"#121214\",\n    lift: 0.3,\n    dotAlpha: 0.85,\n    grain: 0.045,\n  },\n} as const;\n\n/**\n * gradients.sh palettes, sampled per dot so the land reads as a halftone\n * screen of the gradient: \"mesh\" on dark, \"pixel-glass\" on light.\n */\nconst PALETTES = {\n  dark: {\n    reach: 0.46,\n    bg: \"#0E1645\",\n    colors: [\"#ECDCF2\", \"#2E45B7\", \"#8796EE\", \"#2E45B7\", \"#8796EE\", \"#ECDCF2\"],\n  },\n  light: {\n    reach: 0.68,\n    bg: \"#F4F3F1\",\n    colors: [\"#7A3CFF\", \"#FF4FB8\", \"#3D6BFF\"],\n  },\n} as const;\nconst BLOBS: [number, number][] = [\n  [16, 20],\n  [84, 26],\n  [72, 86],\n  [22, 80],\n  [52, 48],\n  [92, 62],\n];\n\nfunction rgb(hex: string): [number, number, number] {\n  const v = Number.parseInt(hex.slice(1), 16);\n  return [(v >> 16) & 255, (v >> 8) & 255, v & 255];\n}\n\nfunction toHex(c: [number, number, number]) {\n  return `#${c.map((v) => Math.round(v).toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\n/** CSS-equivalent of the stacked radial layers, evaluated at (x, y) in 0–1. */\nfunction meshAt(\n  x: number,\n  y: number,\n  bg: string,\n  colors: readonly string[],\n  drift: number,\n  reach: number,\n) {\n  let c = rgb(bg);\n  for (let i = BLOBS.length - 1; i >= 0; i--) {\n    const bx = BLOBS[i][0] / 100 + 0.04 * Math.sin(drift + i * 1.7);\n    const by = BLOBS[i][1] / 100 + 0.03 * Math.cos(drift * 0.8 + i * 2.3);\n    const d = Math.hypot((x - bx) / 0.85, (y - by) / 0.95);\n    const a = Math.max(0, 1 - d / reach);\n    const k = rgb(colors[i % colors.length]);\n    c = [\n      c[0] + (k[0] - c[0]) * a,\n      c[1] + (k[1] - c[1]) * a,\n      c[2] + (k[2] - c[2]) * a,\n    ];\n  }\n  return c;\n}\n\nconst MAP_IN = -6;\nconst MAP_DUR = 26;\nconst LIGHT_AT = 20;\nconst LIGHT_SPAN = 66;\nconst RIPPLE = 26;\nconst DONE = LIGHT_AT + LIGHT_SPAN + 6;\n\nfunction hash(n: number) {\n  const x = Math.sin(n * 127.1 + 311.7) * 43758.5453;\n  return x - Math.floor(x);\n}\n\nfunction inside(lon: number, lat: number, poly: Poly) {\n  let hit = false;\n  const n = poly.length / 2;\n  for (let i = 0, j = n - 1; i < n; j = i++) {\n    const xi = poly[i * 2];\n    const yi = poly[i * 2 + 1];\n    const xj = poly[j * 2];\n    const yj = poly[j * 2 + 1];\n    if (\n      yi > lat !== yj > lat &&\n      lon < ((xj - xi) * (lat - yi)) / (yj - yi) + xi\n    ) {\n      hit = !hit;\n    }\n  }\n  return hit;\n}\n\nfunction isLand(lon: number, lat: number) {\n  return (\n    LAND.some((p) => inside(lon, lat, p)) &&\n    !WATER.some((p) => inside(lon, lat, p))\n  );\n}\n\nfunction buildMask() {\n  const land: boolean[] = [];\n  for (let r = 0; r < ROWS; r++) {\n    for (let c = 0; c < COLS; c++) {\n      land.push(isLand(-180 + (c + 0.5) * STEP, LAT_TOP - (r + 0.5) * STEP));\n    }\n  }\n  return land;\n}\n\nconst MASK = buildMask();\n\nfunction Grain({ opacity }: { opacity: number }) {\n  const id = `g${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  return (\n    <svg\n      width=\"100%\"\n      height=\"100%\"\n      style={{ position: \"absolute\", inset: 0, opacity, pointerEvents: \"none\" }}\n      aria-hidden\n    >\n      <filter id={id}>\n        <feTurbulence\n          type=\"fractalNoise\"\n          baseFrequency=\"0.9\"\n          numOctaves={2}\n          seed={31}\n          stitchTiles=\"stitch\"\n        />\n        <feColorMatrix values=\"0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0.9 0\" />\n      </filter>\n      <rect width=\"100%\" height=\"100%\" filter={`url(#${id})`} />\n    </svg>\n  );\n}\n\nfunction snap(lon: number, lat: number) {\n  const c0 = Math.floor((lon + 180) / STEP);\n  const r0 = Math.floor((LAT_TOP - lat) / STEP);\n  let best = -1;\n  let bestD = Number.POSITIVE_INFINITY;\n  for (let dr = -2; dr <= 2; dr++) {\n    for (let dc = -2; dc <= 2; dc++) {\n      const r = r0 + dr;\n      const c = (c0 + dc + COLS) % COLS;\n      if (r < 0 || r >= ROWS || !MASK[r * COLS + c]) continue;\n      const d = dr * dr + dc * dc;\n      if (d < bestD) {\n        bestD = d;\n        best = r * COLS + c;\n      }\n    }\n  }\n  return best;\n}\n\nexport function DotMap({\n  count = 142,\n  label = \"countries\",\n  cities = DEFAULT_CITIES,\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: DotMapProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H } = useVideoConfig();\n  const u = Math.min(W / 1280, H / 720);\n  const p = THEMES[theme];\n\n  const mapW = Math.min(W * 0.84, (H * 0.6 * COLS) / ROWS);\n  const pitch = mapW / COLS;\n  const mapH = pitch * ROWS;\n  const numSize = 124 * u;\n  const gap = 40 * u;\n  const blockH = mapH + gap + numSize * 0.9;\n  const mapX = (W - mapW) / 2;\n  const mapY = (H - blockH) / 2;\n  const r = pitch * 0.3;\n\n  const lit = useMemo(() => {\n    const seen = new Set<number>();\n    const out: { cell: number; at: number }[] = [];\n    cities.forEach((city, i) => {\n      const cell = snap(city.lon, city.lat);\n      if (cell < 0 || seen.has(cell)) return;\n      seen.add(cell);\n      const x = (city.lon + 180) / 360;\n      out.push({\n        cell,\n        at: LIGHT_AT + x * LIGHT_SPAN + (hash(i + 0.7) - 0.5) * 8,\n      });\n    });\n    return out.sort((a, b) => a.at - b.at);\n  }, [cities]);\n  const litAt = useMemo(() => {\n    const m = new Map<number, number>();\n    for (const l of lit) m.set(l.cell, l.at);\n    return m;\n  }, [lit]);\n\n  const pal = PALETTES[theme];\n  const drift = interpolate(t, [0, 135], [0, 1.2]);\n  const liftTo = rgb(theme === \"dark\" ? \"#FFFFFF\" : p.bg);\n\n  const push = interpolate(t, [0, DONE + 40], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const scale = 0.975 + 0.03 * push;\n\n  const dots: React.ReactNode[] = [];\n  const accents: React.ReactNode[] = [];\n  for (let r0 = 0; r0 < ROWS; r0++) {\n    for (let c = 0; c < COLS; c++) {\n      const idx = r0 * COLS + c;\n      if (!MASK[idx]) continue;\n      const x = mapX + (c + 0.5) * pitch;\n      const y = mapY + (r0 + 0.5) * pitch;\n      const delay = MAP_IN + (c / COLS) * 16 + hash(idx * 0.37) * 6;\n      const a = interpolate(t, [delay, delay + MAP_DUR - 16], [0, 1], {\n        ...CLAMP,\n        easing: EASE_OUT,\n      });\n      if (a <= 0) continue;\n      const at = litAt.get(idx);\n      if (at !== undefined && t >= at) {\n        const k = interpolate(t, [at, at + 10], [0, 1], {\n          ...CLAMP,\n          easing: EASE_OUT,\n        });\n        accents.push(\n          <circle\n            key={`c${idx}`}\n            cx={x}\n            cy={y}\n            r={r * (1.2 + 0.7 * k)}\n            fill={p.city}\n          />,\n        );\n        const w = (t - at) / RIPPLE;\n        if (w < 1) {\n          const e = Easing.bezier(0.22, 1, 0.36, 1)(w);\n          accents.push(\n            <circle\n              key={`w${idx}`}\n              cx={x}\n              cy={y}\n              r={r * 1.6 + e * pitch * 2.6}\n              fill=\"none\"\n              stroke={accent}\n              strokeWidth={Math.max(1, 1.4 * u)}\n              opacity={(1 - w) * 0.8}\n            />,\n          );\n        }\n        continue;\n      }\n      const m = meshAt(\n        c / COLS,\n        r0 / ROWS,\n        pal.bg,\n        pal.colors,\n        drift,\n        pal.reach,\n      );\n      const tint: [number, number, number] = [\n        m[0] + (liftTo[0] - m[0]) * p.lift,\n        m[1] + (liftTo[1] - m[1]) * p.lift,\n        m[2] + (liftTo[2] - m[2]) * p.lift,\n      ];\n      dots.push(\n        <circle\n          key={idx}\n          cx={x}\n          cy={y}\n          r={r * (0.7 + 0.3 * a)}\n          fill={toHex(tint)}\n          opacity={a * p.dotAlpha}\n        />,\n      );\n    }\n  }\n\n  let shown = 0;\n  for (const l of lit) if (t >= l.at) shown++;\n  const value = lit.length\n    ? Math.round((count * shown) / lit.length)\n    : Math.round(count);\n  const numIn = interpolate(t, [LIGHT_AT - 8, LIGHT_AT + 8], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const labIn = interpolate(t, [LIGHT_AT, LIGHT_AT + 16], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n\n  return (\n    <AbsoluteFill style={{ background: p.bg, overflow: \"hidden\" }}>\n      <AbsoluteFill\n        style={{ transform: `scale(${scale})`, transformOrigin: \"50% 50%\" }}\n      >\n        <svg\n          width={W}\n          height={H}\n          style={{ position: \"absolute\", inset: 0 }}\n          aria-hidden\n        >\n          {dots}\n          {accents}\n        </svg>\n        <div\n          style={{\n            position: \"absolute\",\n            left: 0,\n            width: W,\n            top: mapY + mapH + gap,\n            display: \"flex\",\n            justifyContent: \"center\",\n            alignItems: \"baseline\",\n            gap: 18 * u,\n            whiteSpace: \"nowrap\",\n          }}\n        >\n          <span\n            style={{\n              fontFamily: manrope,\n              fontWeight: 400,\n              fontSize: numSize,\n              lineHeight: 0.9,\n              letterSpacing: \"-0.04em\",\n              color: p.fg,\n              fontVariantNumeric: \"tabular-nums\",\n              opacity: numIn,\n              transform: `translateY(${(1 - numIn) * 12 * u}px)`,\n              display: \"inline-block\",\n              minWidth: `${String(Math.round(count)).length * 0.6}em`,\n              textAlign: \"right\",\n            }}\n          >\n            {value}\n          </span>\n          <span\n            style={{\n              fontFamily: manrope,\n              fontWeight: 500,\n              fontSize: 30 * u,\n              letterSpacing: \"-0.015em\",\n              color: p.muted,\n              opacity: labIn,\n              transform: `translateY(${(1 - labIn) * 8 * u}px)`,\n              display: \"inline-block\",\n            }}\n          >\n            {label}\n          </span>\n        </div>\n      </AbsoluteFill>\n\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 80% 80% at 50% 50%, transparent 62%, ${p.bg} 100%)`,\n          opacity: 0.6,\n          pointerEvents: \"none\",\n        }}\n      />\n      <Grain opacity={p.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/dot-map.tsx"
    }
  ],
  "type": "registry:component"
}
