{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dot-globe",
  "title": "Dot Globe",
  "description": "A dotted globe spins up while Live in 142 countries counts in beside it.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/dot-globe/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/InstrumentSans\";\nimport { loadFont as loadSerif } from \"@remotion/google-fonts/InstrumentSerif\";\nimport type React from \"react\";\nimport { useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: sans } = loadFont(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\nconst { fontFamily: serif } = loadSerif(\"italic\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\n\n/**\n * gradients.sh \"limb\" palette: [#e9ecff, #8a8bff, #4b2bd6, #160c3a]. It is the\n * atmosphere: a lit limb hugging the globe, and the light inside the counter.\n */\nconst LIMB = [\"#e9ecff\", \"#8a8bff\", \"#4b2bd6\", \"#160c3a\"] as const;\n\n/**\n * Beat sheet (30fps, speed 1):\n * 0–30     the dotted globe materialises (dots shimmer in), the camera eases\n *          from a low angle to a gentle overhead tilt. Rotation never stops.\n * 18–170   accent arcs lift off city to city along great circles, each one a\n *          travelling stroke; a pulse ring blooms where it lands and the\n *          country counter ticks up by one.\n * 170–210  last landing settles, the counter holds on the final number.\n */\n\nexport interface GlobeCity {\n  name: string;\n  lat: number;\n  lon: number;\n}\n\nexport interface GlobeRoute {\n  from: GlobeCity;\n  to: GlobeCity;\n}\n\nexport interface DotGlobeProps {\n  /** Final counter value; it ticks up once per landed route. */\n  count?: number;\n  prefix?: string;\n  suffix?: string;\n  routes?: GlobeRoute[];\n  accent?: string;\n  /** Playback multiplier. */\n  speed?: number;\n  theme?: \"dark\" | \"light\";\n}\n\nconst C = {\n  london: { name: \"London\", lat: 51.5, lon: -0.13 },\n  lagos: { name: \"Lagos\", lat: 6.52, lon: 3.38 },\n  berlin: { name: \"Berlin\", lat: 52.52, lon: 13.4 },\n  nairobi: { name: \"Nairobi\", lat: -1.29, lon: 36.82 },\n  lisbon: { name: \"Lisbon\", lat: 38.72, lon: -9.14 },\n  newYork: { name: \"New York\", lat: 40.71, lon: -74.0 },\n  saoPaulo: { name: \"São Paulo\", lat: -23.55, lon: -46.63 },\n  madrid: { name: \"Madrid\", lat: 40.42, lon: -3.7 },\n  mexico: { name: \"Mexico City\", lat: 19.43, lon: -99.13 },\n  dakar: { name: \"Dakar\", lat: 14.7, lon: -17.44 },\n  bogota: { name: \"Bogotá\", lat: 4.71, lon: -74.07 },\n  toronto: { name: \"Toronto\", lat: 43.65, lon: -79.38 },\n  cairo: { name: \"Cairo\", lat: 30.04, lon: 31.24 },\n} satisfies Record<string, GlobeCity>;\n\nconst DEFAULT_ROUTES: GlobeRoute[] = [\n  { from: C.london, to: C.lagos },\n  { from: C.berlin, to: C.cairo },\n  { from: C.lisbon, to: C.newYork },\n  { from: C.madrid, to: C.saoPaulo },\n  { from: C.london, to: C.toronto },\n  { from: C.dakar, to: C.bogota },\n  { from: C.newYork, to: C.mexico },\n];\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 ROUTE_START = 18;\nconst ROUTE_GAP = 20;\nconst FLIGHT = 32;\n\n/**\n * Land mask, 3° cells, longitude −180→180 left to right, latitude 78→−54\n * top to bottom. \"#\" = land.\n */\nconst LAND: string[] = [\n  \"............................#######..#################...........#..............##.........######.......................\",\n  \"......................#######..###......#############.........................###.....################..................\",\n  \"......####################.##########....############.............####........#...##.#################################..\",\n  \"....###################################...########..............##########.#############################################\",\n  \".....###########################....###...#####.....###........#########################################################\",\n  \".....#########################....###......###...............#####.###################################################..\",\n  \"......####....###############.....#####...................#...####.############################################.##......\",\n  \"......##........################..######.................###..###..########################################.....##......\",\n  \".................########################................#.#################################################....#.......\",\n  \"..................#######################.................##################################################............\",\n  \"..................######################...................###############################################..............\",\n  \"..................####################...................#############...###.############################..#............\",\n  \"..................##################.....................####....###########.##########################.................\",\n  \"...................################......................###.##....#.################################.#...#.............\",\n  \"....................###############......................#######........############################..####..............\",\n  \".....................#############.......................############################################..#................\",\n  \"......................######....#.......................####################.########################...................\",\n  \".......................#####...........................######################.######################....................\",\n  \"........................####.....#....................##################.#######...################.....................\",\n  \".........................###.##...###.................#########################.....#####..#####........................\",\n  \"...........................####.......................###################.####......####....####....#...................\",\n  \"..............................##......................######################.........##......####...#...................\",\n  \"...............................##.#####................######################........##.....#.##........................\",\n  \".................................########...............#####################.........#......#.......#..................\",\n  \"..................................#########..............###..##############................###...##....................\",\n  \".................................###########...................############..................##.####....................\",\n  \".................................############..................###########...................##.###.#...###.............\",\n  \".................................###############................#########.....................##...##....####...........\",\n  \".................................################...............#########.......................##........####..........\",\n  \"..................................##############................##########..............................................\",\n  \"..................................##############................##########..#.........................####.#............\",\n  \"...................................############.................#########..##........................########...........\",\n  \"....................................###########.................########..##.......................###########..........\",\n  \"....................................##########...................#######..##......................#############.........\",\n  \"....................................#########....................######...........................#############.........\",\n  \"....................................########.....................######...........................#############.........\",\n  \"....................................#######.......................####............................#############.........\",\n  \"....................................######........................###.............................##......#####.........\",\n  \"...................................######.................................................................####........#.\",\n  \"...................................####...............................................................................#.\",\n  \"...................................####.............................................................................##..\",\n  \"...................................###..............................................................................#...\",\n  \"...................................###..................................................................................\",\n  \"...................................##...................................................................................\",\n];\nconst CELL = 3;\nconst LAT_TOP = 78;\n\nfunction isLand(latDeg: number, lonDeg: number) {\n  if (latDeg >= LAT_TOP || latDeg < LAT_TOP - LAND.length * CELL) return false;\n  const r = Math.floor((LAT_TOP - latDeg) / CELL);\n  const c = Math.floor((lonDeg + 180) / CELL);\n  return LAND[r]?.[Math.min(c, LAND[0].length - 1)] === \"#\";\n}\n\nfunction hash(n: number) {\n  const s = Math.sin(n * 127.1 + 311.7) * 43758.5453;\n  return s - Math.floor(s);\n}\n\nconst DEG = Math.PI / 180;\n\n/** Evenly spread points on the sphere (Fibonacci lattice), land only. */\nconst DOTS = (() => {\n  const n = 9000;\n  const golden = Math.PI * (3 - Math.sqrt(5));\n  const out: { cl: number; sl: number; lon: number; h: number }[] = [];\n  for (let i = 0; i < n; i++) {\n    const y = 1 - (2 * (i + 0.5)) / n;\n    const lat = Math.asin(y);\n    const lon =\n      ((((i * golden) % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI)) -\n      Math.PI;\n    if (isLand(lat / DEG, lon / DEG)) {\n      out.push({ cl: Math.cos(lat), sl: y, lon, h: hash(i) });\n    }\n  }\n  return out;\n})();\n\ntype V3 = [number, number, number];\n\nfunction toVec(c: GlobeCity): V3 {\n  const la = c.lat * DEG;\n  const lo = c.lon * DEG;\n  return [\n    Math.cos(la) * Math.sin(lo),\n    Math.sin(la),\n    Math.cos(la) * Math.cos(lo),\n  ];\n}\n\nfunction slerp(a: V3, b: V3, s: number): V3 {\n  const d = Math.min(1, Math.max(-1, a[0] * b[0] + a[1] * b[1] + a[2] * b[2]));\n  const om = Math.acos(d);\n  if (om < 1e-4) return a;\n  const k1 = Math.sin((1 - s) * om) / Math.sin(om);\n  const k2 = Math.sin(s * om) / Math.sin(om);\n  return [a[0] * k1 + b[0] * k2, a[1] * k1 + b[1] * k2, a[2] * k1 + b[2] * k2];\n}\n\nconst THEMES = {\n  dark: {\n    bg: \"#0A0A0B\",\n    stage: \"rgba(255,255,255,0.05)\",\n    fg: \"#F3F2EF\",\n    muted: \"#8B8A90\",\n    dot: [236, 234, 230],\n    backDot: 0.07,\n    body0: \"#1A1830\",\n    body1: \"#0B0A12\",\n    rim: LIMB[0],\n    /** Atmosphere strength and the counter's fill, top → bottom. */\n    air: 1,\n    count: [LIMB[0], LIMB[0], LIMB[1]],\n    shadow: \"rgba(0,0,0,0)\",\n    grain: 0.06,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    stage: \"rgba(255,255,255,0.9)\",\n    fg: \"#121214\",\n    muted: \"#6E6C67\",\n    dot: [34, 33, 36],\n    backDot: 0.06,\n    body0: \"#FFFFFF\",\n    body1: \"#E6E5F0\",\n    rim: \"#FFFFFF\",\n    air: 0.55,\n    count: [LIMB[3], LIMB[3], LIMB[2]],\n    shadow: \"rgba(70,60,45,0.22)\",\n    grain: 0.04,\n  },\n} as const;\n\ntype Theme = (typeof THEMES)[keyof typeof THEMES];\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 }}\n      aria-hidden\n    >\n      <filter id={id}>\n        <feTurbulence\n          type=\"fractalNoise\"\n          baseFrequency=\"0.9\"\n          numOctaves={2}\n          seed={3}\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\n/** The counter digits slide up by one on every landing. */\nfunction Counter({\n  value,\n  since,\n  fontSize,\n  fill,\n}: {\n  value: number;\n  since: number;\n  fontSize: number;\n  /** Top → bottom stops painted through the digits. */\n  fill: readonly string[];\n}) {\n  const p = interpolate(since, [0, 12], [0, 1], { ...CLAMP, easing: EASE_OUT });\n  const prev = String(value - 1);\n  const next = String(value);\n  const width = Math.max(prev.length, next.length);\n  const cells = Array.from({ length: width }, (_, i) => {\n    const a = prev.padStart(width, \" \")[i];\n    const b = next.padStart(width, \" \")[i];\n    return { a, b, moving: a !== b };\n  });\n  const paint: React.CSSProperties = {\n    backgroundImage: `linear-gradient(180deg, ${fill[0]} 12%, ${fill[1]} 40%, ${fill[2]} 92%)`,\n    WebkitBackgroundClip: \"text\",\n    backgroundClip: \"text\",\n    color: \"transparent\",\n  };\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        fontFamily: sans,\n        fontWeight: 400,\n        fontSize,\n        lineHeight: 1,\n        letterSpacing: \"-0.055em\",\n        fontVariantNumeric: \"tabular-nums\",\n      }}\n    >\n      {cells.map((c, i) => (\n        <span\n          key={i}\n          style={{\n            position: \"relative\",\n            display: \"inline-block\",\n            height: fontSize * 1.02,\n            overflow: \"hidden\",\n          }}\n        >\n          <span style={{ visibility: \"hidden\" }}>{c.b === \" \" ? \"\" : \"0\"}</span>\n          {c.moving ? (\n            <>\n              <span\n                style={{\n                  ...paint,\n                  position: \"absolute\",\n                  left: 0,\n                  top: 0,\n                  transform: `translateY(${-p * fontSize}px)`,\n                  opacity: 1 - p,\n                }}\n              >\n                {c.a}\n              </span>\n              <span\n                style={{\n                  ...paint,\n                  position: \"absolute\",\n                  left: 0,\n                  top: 0,\n                  transform: `translateY(${(1 - p) * fontSize}px)`,\n                  opacity: p,\n                }}\n              >\n                {c.b}\n              </span>\n            </>\n          ) : (\n            <span style={{ ...paint, position: \"absolute\", left: 0, top: 0 }}>\n              {c.b}\n            </span>\n          )}\n        </span>\n      ))}\n    </div>\n  );\n}\n\nexport function DotGlobe({\n  count = 142,\n  prefix = \"Live in\",\n  suffix = \"countries\",\n  routes = DEFAULT_ROUTES,\n  accent = \"#3B5BFD\",\n  speed = 1,\n  theme = \"dark\",\n}: DotGlobeProps) {\n  const t = useCurrentFrame() * speed;\n  const { width, height } = useVideoConfig();\n  const u = height / 720;\n  const th: Theme = THEMES[theme];\n  const uid = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n\n  // camera\n  const settle = interpolate(t, [0, 60], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const tiltP = interpolate(t, [0, 210], [0, 1], { ...CLAMP, easing: EASE_IO });\n  const tilt = (6 + 14 * tiltP) * DEG;\n  const lon0 = (8 - t * 0.24) * DEG;\n  const R = height * 0.42 * (0.94 + 0.06 * settle);\n  const cx = width * 0.655;\n  const cy = height * 0.52 + (1 - settle) * 18 * u;\n\n  const ct = Math.cos(tilt);\n  const stl = Math.sin(tilt);\n  const cl0 = Math.cos(lon0);\n  const sl0 = Math.sin(lon0);\n  /** World vector → rotated camera space (z toward viewer). */\n  const rot = (v: V3): V3 => {\n    // spin around the polar axis by -lon0\n    const x = v[0] * cl0 - v[2] * sl0;\n    const z0 = v[0] * sl0 + v[2] * cl0;\n    const y = v[1] * ct - z0 * stl;\n    const z = v[1] * stl + z0 * ct;\n    return [x, y, z];\n  };\n  const proj = (p: V3) => [cx + p[0] * R, cy - p[1] * R] as const;\n  const hidden = (p: V3) => p[2] < 0 && p[0] * p[0] + p[1] * p[1] < 1;\n\n  // light from upper-left, slightly in front\n  const L: V3 = [-0.5, 0.55, 0.67];\n\n  const back: React.ReactNode[] = [];\n  const front: React.ReactNode[] = [];\n  const baseR = 2.15 * u * (R / (height * 0.42));\n  for (let i = 0; i < DOTS.length; i++) {\n    const d = DOTS[i];\n    const appear = interpolate(t, [d.h * 18, d.h * 18 + 12], [0, 1], CLAMP);\n    if (appear <= 0) continue;\n    const lon = d.lon;\n    const v: V3 = [d.cl * Math.sin(lon), d.sl, d.cl * Math.cos(lon)];\n    const p = rot(v);\n    const [sx, sy] = proj(p);\n    if (p[2] < 0) {\n      back.push(\n        <circle\n          key={i}\n          cx={sx}\n          cy={sy}\n          r={baseR * 0.62}\n          fillOpacity={th.backDot * appear}\n        />,\n      );\n    } else {\n      const lambert = Math.max(0, p[0] * L[0] + p[1] * L[1] + p[2] * L[2]);\n      const o =\n        (0.28 + 0.72 * lambert) *\n        appear *\n        (0.35 + 0.65 * Math.min(1, p[2] * 3));\n      front.push(\n        <circle\n          key={i}\n          cx={sx}\n          cy={sy}\n          r={baseR * (0.55 + 0.45 * Math.sqrt(p[2]))}\n          fillOpacity={o}\n        />,\n      );\n    }\n  }\n\n  // routes\n  let landed = 0;\n  let lastLanding = -999;\n  const arcs: React.ReactNode[] = [];\n  const pins: React.ReactNode[] = [];\n  const labels: React.ReactNode[] = [];\n  routes.forEach((route, ri) => {\n    const start = ROUTE_START + ri * ROUTE_GAP;\n    const lt = t - start;\n    if (lt < -4) return;\n    const a = toVec(route.from);\n    const b = toVec(route.to);\n    const ang = Math.acos(\n      Math.min(1, Math.max(-1, a[0] * b[0] + a[1] * b[1] + a[2] * b[2])),\n    );\n    const lift = 0.07 + 0.34 * (ang / Math.PI);\n    const head = interpolate(lt, [0, FLIGHT], [0, 1], {\n      ...CLAMP,\n      easing: EASE_IO,\n    });\n    const tail = interpolate(lt, [FLIGHT * 0.55, FLIGHT + 26], [0, 1], {\n      ...CLAMP,\n      easing: EASE_IO,\n    });\n    const landAt = start + FLIGHT;\n    if (t >= landAt) {\n      landed += 1;\n      lastLanding = landAt;\n    }\n\n    // origin pin\n    const pa = rot(a);\n    const [ax, ay] = proj(pa);\n    const originO = interpolate(lt, [-4, 4], [0, 1], CLAMP);\n    if (!hidden(pa)) {\n      pins.push(\n        <circle\n          key={`o${ri}`}\n          cx={ax}\n          cy={ay}\n          r={2.6 * u}\n          fill={accent}\n          opacity={originO * Math.min(1, pa[2] * 4)}\n        />,\n      );\n    }\n\n    // the arc: short segments with an opacity ramp toward the head\n    const STEPS = 56;\n    if (head > tail) {\n      for (let k = 0; k < STEPS; k++) {\n        const s0 = k / STEPS;\n        const s1 = (k + 1) / STEPS;\n        if (s1 < tail || s0 > head) continue;\n        const ss0 = Math.max(s0, tail);\n        const ss1 = Math.min(s1, head);\n        const pt = (s: number) => {\n          const g = slerp(a, b, s);\n          const r = 1 + lift * Math.sin(Math.PI * s);\n          return rot([g[0] * r, g[1] * r, g[2] * r]);\n        };\n        const q0 = pt(ss0);\n        const q1 = pt(ss1);\n        if (hidden(q0) || hidden(q1)) continue;\n        const [x0, y0] = proj(q0);\n        const [x1, y1] = proj(q1);\n        const rel = (ss1 - tail) / Math.max(1e-3, head - tail);\n        arcs.push(\n          <line\n            key={`a${ri}-${k}`}\n            x1={x0}\n            y1={y0}\n            x2={x1}\n            y2={y1}\n            stroke={accent}\n            strokeWidth={2.4 * u}\n            strokeLinecap=\"round\"\n            opacity={0.15 + 0.85 * rel}\n          />,\n        );\n      }\n      if (lt < FLIGHT) {\n        const g = slerp(a, b, head);\n        const r = 1 + lift * Math.sin(Math.PI * head);\n        const hp = rot([g[0] * r, g[1] * r, g[2] * r]);\n        if (!hidden(hp)) {\n          const [hx, hy] = proj(hp);\n          arcs.push(\n            <circle key={`h${ri}`} cx={hx} cy={hy} r={3.2 * u} fill={accent} />,\n          );\n        }\n      }\n    }\n\n    // landing: rings drawn in the tangent plane, then a resting pin\n    const since = t - landAt;\n    const pb = rot(b);\n    if (since >= 0 && !hidden(pb)) {\n      // tangent basis at b\n      const up: V3 = Math.abs(b[1]) > 0.95 ? [1, 0, 0] : [0, 1, 0];\n      const e1: V3 = [\n        up[1] * b[2] - up[2] * b[1],\n        up[2] * b[0] - up[0] * b[2],\n        up[0] * b[1] - up[1] * b[0],\n      ];\n      const n1 = Math.hypot(...e1);\n      e1[0] /= n1;\n      e1[1] /= n1;\n      e1[2] /= n1;\n      const e2: V3 = [\n        b[1] * e1[2] - b[2] * e1[1],\n        b[2] * e1[0] - b[0] * e1[2],\n        b[0] * e1[1] - b[1] * e1[0],\n      ];\n      [0, 7].forEach((delay, k) => {\n        const rp = interpolate(since - delay, [0, 30], [0, 1], {\n          ...CLAMP,\n          easing: EASE_OUT,\n        });\n        if (rp <= 0 || rp >= 1) return;\n        const rho = 0.012 + rp * 0.085;\n        const pts: string[] = [];\n        for (let j = 0; j <= 32; j++) {\n          const th2 = (j / 32) * Math.PI * 2;\n          const cr = Math.cos(rho);\n          const sr = Math.sin(rho);\n          const w: V3 = [\n            b[0] * cr + sr * (Math.cos(th2) * e1[0] + Math.sin(th2) * e2[0]),\n            b[1] * cr + sr * (Math.cos(th2) * e1[1] + Math.sin(th2) * e2[1]),\n            b[2] * cr + sr * (Math.cos(th2) * e1[2] + Math.sin(th2) * e2[2]),\n          ];\n          const [px, py] = proj(rot(w));\n          pts.push(`${px.toFixed(1)},${py.toFixed(1)}`);\n        }\n        arcs.push(\n          <polygon\n            key={`r${ri}-${k}`}\n            points={pts.join(\" \")}\n            fill=\"none\"\n            stroke={accent}\n            strokeWidth={(1.6 - k * 0.4) * u}\n            opacity={(1 - rp) * (k === 0 ? 0.9 : 0.5)}\n          />,\n        );\n      });\n      const [bx, by] = proj(pb);\n      const pinP = interpolate(since, [0, 8], [0, 1], {\n        ...CLAMP,\n        easing: EASE_OUT,\n      });\n      pins.push(\n        <circle\n          key={`p${ri}`}\n          cx={bx}\n          cy={by}\n          r={(2.4 + 1.6 * (1 - pinP) + 0.6) * u}\n          fill={accent}\n          stroke={th.bg}\n          strokeWidth={1.2 * u}\n          opacity={Math.min(1, pb[2] * 4)}\n        />,\n      );\n      const labelO =\n        interpolate(since, [2, 12], [0, 1], CLAMP) *\n        interpolate(since, [44, 58], [1, 0], CLAMP);\n      if (labelO > 0) {\n        labels.push(\n          <div\n            key={`l${ri}`}\n            style={{\n              position: \"absolute\",\n              left: bx + 12 * u,\n              top: by - 9 * u,\n              fontFamily: sans,\n              fontSize: 13 * u,\n              fontWeight: 500,\n              letterSpacing: \"-0.005em\",\n              color: th.fg,\n              whiteSpace: \"nowrap\",\n              opacity: labelO,\n              transform: `translateX(${(1 - labelO) * -6 * u}px)`,\n              textShadow: `0 0 ${6 * u}px ${th.bg}, 0 0 ${2 * u}px ${th.bg}`,\n            }}\n          >\n            {route.to.name}\n          </div>,\n        );\n      }\n    }\n  });\n\n  const value = count - routes.length + landed;\n  const textP = (d: number) =>\n    interpolate(t, [6 + d, 34 + d], [0, 1], { ...CLAMP, easing: EASE_OUT });\n  const marginX = width * 0.094;\n  const bigSize = Math.min(168 * u, width * 0.13);\n  const smallSize = 40 * u;\n  const dotFill = `rgb(${th.dot[0]},${th.dot[1]},${th.dot[2]})`;\n\n  const textItem = (d: number): React.CSSProperties => ({\n    opacity: textP(d),\n    transform: `translateY(${(1 - textP(d)) * 18 * u}px)`,\n  });\n\n  return (\n    <AbsoluteFill style={{ background: th.bg, overflow: \"hidden\" }}>\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 60% 70% at 62% 35%, ${th.stage}, transparent 72%)`,\n        }}\n      />\n      <svg\n        width={width}\n        height={height}\n        style={{ position: \"absolute\", inset: 0 }}\n        aria-hidden\n      >\n        <defs>\n          <radialGradient id={`body${uid}`} cx=\"38%\" cy=\"30%\" r=\"75%\">\n            <stop offset=\"0%\" stopColor={th.body0} />\n            <stop offset=\"100%\" stopColor={th.body1} />\n          </radialGradient>\n          <radialGradient id={`rim${uid}`} cx=\"50%\" cy=\"50%\" r=\"50%\">\n            <stop offset=\"80%\" stopColor={th.rim} stopOpacity={0} />\n            <stop offset=\"96%\" stopColor={th.rim} stopOpacity={0.35} />\n            <stop offset=\"100%\" stopColor={th.rim} stopOpacity={0.9} />\n          </radialGradient>\n          <linearGradient id={`rimMask${uid}`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">\n            <stop offset=\"0%\" stopColor=\"#fff\" stopOpacity={1} />\n            <stop offset=\"55%\" stopColor=\"#fff\" stopOpacity={0.15} />\n            <stop offset=\"100%\" stopColor=\"#fff\" stopOpacity={0} />\n          </linearGradient>\n          <mask id={`rm${uid}`}>\n            <rect\n              x={cx - R}\n              y={cy - R}\n              width={R * 2}\n              height={R * 2}\n              fill={`url(#rimMask${uid})`}\n            />\n          </mask>\n          {/* the lit limb: a thin bright edge falling off into deep violet */}\n          <radialGradient\n            id={`air${uid}`}\n            gradientUnits=\"userSpaceOnUse\"\n            cx={cx}\n            cy={cy}\n            r={R * 1.5}\n          >\n            <stop\n              offset={`${(100 / 1.5) * 0.96}%`}\n              stopColor={LIMB[1]}\n              stopOpacity={0}\n            />\n            <stop\n              offset={`${(100 / 1.5) * 1.0}%`}\n              stopColor={LIMB[0]}\n              stopOpacity={0.75}\n            />\n            <stop\n              offset={`${(100 / 1.5) * 1.03}%`}\n              stopColor={LIMB[1]}\n              stopOpacity={0.45}\n            />\n            <stop\n              offset={`${(100 / 1.5) * 1.12}%`}\n              stopColor={LIMB[2]}\n              stopOpacity={0.22}\n            />\n            <stop\n              offset={`${(100 / 1.5) * 1.3}%`}\n              stopColor={LIMB[3]}\n              stopOpacity={0.1}\n            />\n            <stop offset=\"100%\" stopColor={LIMB[3]} stopOpacity={0} />\n          </radialGradient>\n          <radialGradient id={`sh${uid}`} cx=\"50%\" cy=\"50%\" r=\"50%\">\n            <stop offset=\"0%\" stopColor={th.shadow} />\n            <stop offset=\"100%\" stopColor={th.shadow} stopOpacity={0} />\n          </radialGradient>\n        </defs>\n        {/* contact shadow (light theme) */}\n        <ellipse\n          cx={cx + R * 0.08}\n          cy={cy + R * 1.08}\n          rx={R * 0.85}\n          ry={R * 0.1}\n          fill={`url(#sh${uid})`}\n          opacity={settle}\n        />\n        <circle\n          cx={cx}\n          cy={cy}\n          r={R * 1.5}\n          fill={`url(#air${uid})`}\n          opacity={\n            th.air *\n            interpolate(t, [0, 40, 210], [0, 0.85, 1], {\n              ...CLAMP,\n              easing: EASE_OUT,\n            })\n          }\n        />\n        <g opacity={interpolate(t, [0, 16], [0, 1], CLAMP)}>\n          <circle cx={cx} cy={cy} r={R} fill={`url(#body${uid})`} />\n        </g>\n        <g fill={dotFill}>{back}</g>\n        <g fill={dotFill}>{front}</g>\n        <circle\n          cx={cx}\n          cy={cy}\n          r={R}\n          fill={`url(#rim${uid})`}\n          mask={`url(#rm${uid})`}\n          opacity={interpolate(t, [4, 30], [0, 1], CLAMP)}\n        />\n        {arcs}\n        {pins}\n      </svg>\n      {labels}\n\n      {/* counter */}\n      <div\n        style={{\n          position: \"absolute\",\n          left: marginX,\n          top: height * 0.5 - (bigSize + smallSize * 2.4) / 2,\n          display: \"flex\",\n          flexDirection: \"column\",\n          gap: 6 * u,\n        }}\n      >\n        <div\n          style={{\n            ...textItem(0),\n            fontFamily: sans,\n            fontWeight: 400,\n            fontSize: smallSize,\n            letterSpacing: \"-0.025em\",\n            color: th.muted,\n            lineHeight: 1.1,\n          }}\n        >\n          {prefix}\n        </div>\n        <div style={{ ...textItem(3), marginLeft: -bigSize * 0.04 }}>\n          <Counter\n            value={value}\n            since={t - lastLanding}\n            fontSize={bigSize}\n            fill={th.count}\n          />\n        </div>\n        <div\n          style={{\n            ...textItem(6),\n            fontFamily: serif,\n            fontStyle: \"italic\",\n            fontWeight: 400,\n            fontSize: smallSize * 1.3,\n            letterSpacing: \"-0.01em\",\n            color: th.fg,\n            lineHeight: 1,\n          }}\n        >\n          {suffix}\n        </div>\n      </div>\n\n      <Grain opacity={th.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/dot-globe.tsx"
    }
  ],
  "type": "registry:component"
}
