{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "planet",
  "type": "registry:component",
  "title": "Planet",
  "description": "The world as dots on a globe you can turn, with markers pinned to it.",
  "dependencies": [
    "@base-ui/react",
    "tailwind-variants"
  ],
  "registryDependencies": [],
  "cssVars": {
    "light": {
      "success": "oklch(0.696 0.17 162.48)",
      "warning": "oklch(0.769 0.188 70.08)",
      "info": "oklch(0.623 0.214 259.815)"
    },
    "dark": {
      "success": "oklch(0.696 0.17 162.48)",
      "warning": "oklch(0.769 0.188 70.08)",
      "info": "oklch(0.623 0.214 259.815)"
    }
  },
  "files": [
    {
      "path": "components/panelui/planet/index.tsx",
      "type": "registry:component",
      "target": "@components/panelui/planet/index.tsx",
      "content": "'use client';\n\n/**\n * Planet — the world as dots on a globe you can turn, with things pinned to it.\n *\n * ```tsx\n * <Planet defaultRotation={[78, 18]}>\n *   <Planet.Globe>\n *     <Planet.Marker lat={24.7} lng={46.7} label=\"Saudi Arabia\" count={2} />\n *     <Planet.Marker lat={-33.9} lng={151.2} label=\"Australia\" />\n *   </Planet.Globe>\n *   <Planet.Controls />\n * </Planet>\n * ```\n *\n * ## Two layers\n *\n * The globe is a canvas and the markers are ordinary elements laid over it.\n * The land is several thousand dots, which is a job for a canvas; a marker is\n * a label, an avatar, maybe a link, which is a job for the DOM. Each frame the\n * canvas is redrawn and every marker is moved to where its coordinate now is,\n * by a transform written straight to the element. Turning the globe never\n * re-renders React.\n *\n * ## Nothing is drawn that is not asked for\n *\n * There is no loop running while the globe is still. A drag, a press on a\n * control, a resize or a change of theme asks for one frame, and a frame asks\n * for another only while something is still moving.\n *\n * ## The land is a question, not a shape\n *\n * `land.ts` says, per square degree, whether it is land. A grid of points is\n * laid over the sphere at an even spacing and the ones on land are kept, so\n * the coastlines come out as the edge of a dot pattern. The spacing is in\n * pixels, so a small globe and a large one are dotted equally finely and a\n * small one is not a smear.\n *\n * ## It takes the text colour\n *\n * The dots and the shading are drawn in `currentColor`, read from the element\n * each frame. On a dark surface the land is light and on a light one it is\n * dark, with no colour passed in and nothing to keep in step with the theme.\n */\nimport {\n  createContext,\n  forwardRef,\n  useCallback,\n  useContext,\n  useEffect,\n  useImperativeHandle,\n  useMemo,\n  useRef,\n  useState,\n  type ComponentProps,\n  type KeyboardEvent,\n  type MouseEvent,\n  type PointerEvent as ReactPointerEvent,\n  type ReactNode,\n} from 'react';\nimport { tv } from 'tailwind-variants';\nimport { LAND, LAND_COLUMNS, LAND_ROWS } from './land';\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport type { ElementType as RenderElementType } from 'react';\n\n/** Draws a part as another element, or composes it with a component: `render={<a href=\"/\" />}`. */\ntype RenderProp<Tag extends RenderElementType> = { render?: useRender.ComponentProps<Tag>['render'] };\n\nconst planetVariants = tv({\n  slots: {\n    // `isolate` keeps the markers' depth ordering inside the planet. Each marker\n    // sets its own z-index, and without a stacking context of its own those\n    // numbers compete with the page's sidebars and menus, and win.\n    root: 'isolate flex w-full flex-col items-center gap-4 text-foreground',\n    // `touch-pan-y` leaves a vertical swipe to the page. A globe that traps\n    // the scroll it sits in is worse than one that only turns sideways.\n    globe:\n      'relative aspect-square w-full max-w-md cursor-grab touch-pan-y select-none rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-4 focus-visible:ring-offset-background active:cursor-grabbing',\n    // The glow's colour is put in a real colour property, so the browser\n    // resolves it and the drawing code can read back a colour it can use.\n    canvas: 'absolute inset-0 size-full [outline-color:var(--pu-glow)]',\n    markers: 'pointer-events-none absolute inset-0',\n    // Opacity 0 until the first frame has placed it, so a marker is never\n    // seen stacked in the corner it starts in.\n    marker:\n      'pointer-events-auto absolute left-0 top-0 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center opacity-0 will-change-transform',\n    // The same label and pin the app library's Map draws: opaque enough to\n    // read over the land, with an edge and a shadow so it sits on the globe\n    // and not in it.\n    // The stem under the label ties it to its pin, so two labels close\n    // together can still be told apart.\n    label:\n      'absolute bottom-full mb-2.5 flex items-center gap-1.5 whitespace-nowrap rounded-md border bg-background/95 px-1.5 py-0.5 text-xs font-medium text-foreground shadow-sm after:absolute after:left-1/2 after:top-full after:h-2 after:w-px after:-translate-x-1/2 after:bg-foreground/40',\n    count: 'tabular-nums text-muted-foreground',\n    // An opaque surface. A tint would let the land show through whatever the\n    // pin holds, and initials over a field of dots cannot be read.\n    pin: 'relative flex size-8 items-center justify-center overflow-hidden rounded-full border-2 border-[var(--pu-color)] bg-card text-card-foreground shadow-md ring-2 ring-background',\n    dot: 'relative size-3.5 rounded-full border-2 border-background bg-[var(--pu-color)] shadow-md',\n    // A marker that can be pressed. The button has no box of its own, so the\n    // label above it is still placed from the marker and pressing either the\n    // label or the pin counts.\n    press:\n      'group/marker flex cursor-pointer flex-col items-center rounded-full outline-none [&>[data-pin]]:transition-[scale,box-shadow] [&>[data-pin]]:duration-200 hover:[&>[data-pin]]:scale-125 focus-visible:[&>[data-pin]]:ring-2 focus-visible:[&>[data-pin]]:ring-ring active:[&>[data-pin]]:scale-110',\n    // A ring that spreads from the marker and fades, for one that wants\n    // looking at. Still for anyone who has asked for reduced motion.\n    pulse:\n      'pointer-events-none absolute left-1/2 top-1/2 size-3.5 -translate-x-1/2 -translate-y-1/2 animate-ping rounded-full bg-[var(--pu-color)] opacity-70 motion-reduce:hidden',\n    // One surface split by a hairline, not two buttons with a gap between.\n    controls:\n      'inline-flex items-stretch divide-x divide-border overflow-hidden rounded-xl border bg-card text-card-foreground shadow-sm',\n    control:\n      'inline-flex size-9 cursor-pointer items-center justify-center text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground active:bg-muted',\n    caption: 'flex items-center px-3 text-sm font-medium',\n    // The places on the globe again, as a list beside it, ruled off from what is above.\n    list: 'grid grid-cols-2 gap-1 border-t pt-3',\n    listItem:\n      'flex w-full cursor-pointer items-center gap-2 rounded-lg px-2 py-1.5 text-start text-sm text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring aria-pressed:bg-muted aria-pressed:text-foreground',\n    listDot: 'size-2 shrink-0 rounded-full bg-[var(--pu-color)]',\n    listName: 'truncate',\n  },\n  variants: {\n    /** The colour of the light at the globe's edge. */\n    glow: {\n      primary: { globe: '[--pu-glow:var(--primary)]' },\n      info: { globe: '[--pu-glow:var(--info)]' },\n      success: { globe: '[--pu-glow:var(--success)]' },\n      warning: { globe: '[--pu-glow:var(--warning)]' },\n      destructive: { globe: '[--pu-glow:var(--destructive)]' },\n    },\n    // The marker that has been picked: larger, with a ring of its own colour.\n    selected: {\n      true: {\n        dot: 'scale-125 shadow-[0_0_0_4px_color-mix(in_oklab,var(--pu-color)_35%,transparent)]',\n        pin: 'scale-110 shadow-[0_0_0_4px_color-mix(in_oklab,var(--pu-color)_35%,transparent)]',\n        marker: 'z-10',\n      },\n    },\n    color: {\n      primary: { marker: '[--pu-color:var(--primary)]', listDot: '[--pu-color:var(--primary)]' },\n      info: { marker: '[--pu-color:var(--info)]', listDot: '[--pu-color:var(--info)]' },\n      success: { marker: '[--pu-color:var(--success)]', listDot: '[--pu-color:var(--success)]' },\n      warning: { marker: '[--pu-color:var(--warning)]', listDot: '[--pu-color:var(--warning)]' },\n      destructive: { marker: '[--pu-color:var(--destructive)]', listDot: '[--pu-color:var(--destructive)]' },\n    },\n  },\n  defaultVariants: { color: 'primary', glow: 'primary' },\n});\n\n/* -------------------------------------------------------------------------- */\n/* The land                                                                   */\n/* -------------------------------------------------------------------------- */\n\nconst RADIANS = Math.PI / 180;\n\n/** The globe's radius as a share of half its box. The rest is the glow. */\nconst SPHERE = 0.9;\n\n/** The points of a dot grid that fall on land, as unit vectors. */\ninterface Dots {\n  x: Float32Array;\n  y: Float32Array;\n  z: Float32Array;\n  count: number;\n}\n\nlet landBits: Uint8Array | null = null;\n\nfunction isLand(latitude: number, longitude: number): boolean {\n  if (!landBits) {\n    const decoded = atob(LAND);\n    landBits = new Uint8Array(decoded.length);\n    for (let i = 0; i < decoded.length; i += 1) landBits[i] = decoded.charCodeAt(i);\n  }\n  const row = Math.min(LAND_ROWS - 1, Math.max(0, Math.floor(((90 - latitude) / 180) * LAND_ROWS)));\n  const column = Math.min(LAND_COLUMNS - 1, Math.max(0, Math.floor(((longitude + 180) / 360) * LAND_COLUMNS)));\n  const cell = row * LAND_COLUMNS + column;\n  return ((landBits[cell >> 3] ?? 0) & (1 << (cell & 7))) !== 0;\n}\n\nconst dotCache = new Map<number, Dots>();\n\n/**\n * A grid with `step` degrees between rows, and between the points along each\n * row at the equator. A row nearer a pole is a shorter circle and gets fewer\n * points, so the spacing on the sphere is the same everywhere and the dots do\n * not crowd together towards the top.\n */\nfunction dotsFor(step: number): Dots {\n  const cached = dotCache.get(step);\n  if (cached) return cached;\n\n  const xs: number[] = [];\n  const ys: number[] = [];\n  const zs: number[] = [];\n  const rows = Math.round(180 / step);\n  for (let row = 0; row < rows; row += 1) {\n    const latitude = 90 - (row + 0.5) * (180 / rows);\n    const cos = Math.cos(latitude * RADIANS);\n    const sin = Math.sin(latitude * RADIANS);\n    const columns = Math.max(1, Math.round((360 / step) * cos));\n    for (let column = 0; column < columns; column += 1) {\n      const longitude = -180 + (column + 0.5) * (360 / columns);\n      if (!isLand(latitude, longitude)) continue;\n      xs.push(cos * Math.sin(longitude * RADIANS));\n      ys.push(sin);\n      zs.push(cos * Math.cos(longitude * RADIANS));\n    }\n  }\n\n  const dots = {\n    x: Float32Array.from(xs),\n    y: Float32Array.from(ys),\n    z: Float32Array.from(zs),\n    count: xs.length,\n  };\n  dotCache.set(step, dots);\n  return dots;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Context                                                                    */\n/* -------------------------------------------------------------------------- */\n\n/** `[longitude, latitude]` of the point facing the viewer, in degrees. */\nexport type PlanetRotation = [longitude: number, latitude: number];\n\n/** Where a coordinate is on the globe's box, and how far towards the viewer. */\ninterface Projected {\n  x: number;\n  y: number;\n  /** 1 at the centre, 0 on the edge, negative on the far side. */\n  depth: number;\n}\n\ntype Project = (latitude: number, longitude: number) => Projected;\n\ninterface PlanetContextValue {\n  /** Called with the current projection on every frame the globe draws. */\n  subscribe(listener: (project: Project) => void): () => void;\n  /** The set `subscribe` adds to, which the globe hands to its engine. */\n  listeners: Set<(project: Project) => void>;\n  rotateBy(longitude: number, latitude?: number): void;\n  rotateTo(rotation: PlanetRotation): void;\n  /** The globe's own loop, started by `Planet.Globe` once it has a canvas. */\n  engine: React.RefObject<Engine | null>;\n  defaultRotation: PlanetRotation;\n  autoRotate: number;\n  interactive: boolean;\n  onRotationChange?: (rotation: PlanetRotation) => void;\n}\n\nconst PlanetContext = createContext<PlanetContextValue | null>(null);\n\nfunction usePlanetContext(part: string): PlanetContextValue {\n  const context = useContext(PlanetContext);\n  if (!context) throw new Error(`${part} must be used inside a <Planet>.`);\n  return context;\n}\n\nexport interface PlanetHandle {\n  /** Turn by this many degrees of longitude, and optionally latitude. */\n  rotateBy(longitude: number, latitude?: number): void;\n  /** Turn until this coordinate faces the viewer. */\n  rotateTo(rotation: PlanetRotation): void;\n  /** The coordinate facing the viewer right now. */\n  getRotation(): PlanetRotation;\n}\n\n/* -------------------------------------------------------------------------- */\n/* The engine                                                                 */\n/* -------------------------------------------------------------------------- */\n\n/** How far the globe can be tipped. Past this the far pole comes into view. */\nconst MAX_TILT = 60;\n/** Pixels between neighbouring dots, whatever size the globe is. */\nconst DOT_SPACING = 4.4;\n/** Where the light comes from: in front, a little above and to the left. */\nconst LIGHT = { x: -0.3, y: 0.42, z: 0.86 };\nconst ALPHA_STEPS = 8;\n\nconst clamp = (value: number, low: number, high: number) => Math.min(high, Math.max(low, value));\n/** A longitude brought back into -180..180, so it never grows without bound. */\nconst wrap = (longitude: number) => ((((longitude + 180) % 360) + 360) % 360) - 180;\n/** The short way round from one longitude to another. */\nconst shortest = (from: number, to: number) => wrap(to - from);\n\ninterface Engine {\n  rotateBy(longitude: number, latitude?: number): void;\n  rotateTo(rotation: PlanetRotation): void;\n  getRotation(): PlanetRotation;\n  setAutoRotate(speed: number): void;\n  dragStart(): void;\n  dragMove(dx: number, dy: number, vertical: boolean): void;\n  dragEnd(): void;\n  setHover(hovering: boolean): void;\n  invalidate(): void;\n  destroy(): void;\n}\n\ninterface EngineOptions {\n  canvas: HTMLCanvasElement;\n  rotation: PlanetRotation;\n  autoRotate: number;\n  listeners: Set<(project: Project) => void>;\n  onSettle(rotation: PlanetRotation): void;\n}\n\nfunction createEngine({ canvas, rotation, autoRotate, listeners, onSettle }: EngineOptions): Engine {\n  const context = canvas.getContext('2d');\n  const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');\n\n  let longitude = wrap(rotation[0]);\n  let latitude = clamp(rotation[1], -MAX_TILT, MAX_TILT);\n  /** Where an eased turn is heading, or `null` when none is under way. */\n  let target: { longitude: number; latitude: number } | null = null;\n  /** Degrees a second the globe is still coasting at after a drag. */\n  let velocity = 0;\n  let spin = autoRotate;\n  let dragging = false;\n  let hovering = false;\n  let visible = true;\n  let frame = 0;\n  let last = 0;\n  let size = 0;\n  let destroyed = false;\n  /** Samples of recent drag movement, to turn a flick into a coast. */\n  let samples: { time: number; dx: number }[] = [];\n\n  const draw = () => {\n    if (!context || size === 0) return;\n    const ratio = window.devicePixelRatio || 1;\n    const centre = size / 2;\n    // Smaller than the canvas, which leaves a margin for the light round it.\n    const radius = (size / 2) * SPHERE;\n    const style = getComputedStyle(canvas);\n    const color = style.color;\n    const glow = style.outlineColor || color;\n\n    context.setTransform(ratio, 0, 0, ratio, 0, 0);\n    context.clearRect(0, 0, size, size);\n\n    /*\n     * The light round the globe: a ring that starts at the edge and fades\n     * outwards. Drawn as a ring and not a disc, because a gradient paints its\n     * first colour all the way in to the middle.\n     */\n    const halo = context.createRadialGradient(centre, centre, radius * 0.98, centre, centre, size / 2);\n    halo.addColorStop(0, glow);\n    halo.addColorStop(1, 'transparent');\n    context.fillStyle = halo;\n    // Faint on purpose. Any stronger and it washes over the land near the\n    // edge, which is where the dots are already at their quietest.\n    context.globalAlpha = 0.14;\n    context.beginPath();\n    context.arc(centre, centre, size / 2, 0, Math.PI * 2);\n    context.arc(centre, centre, radius, 0, Math.PI * 2, true);\n    context.fill('evenodd');\n    context.globalAlpha = 1;\n\n    /* The sphere itself: a faint disc, lit from the upper left. */\n    context.save();\n    context.beginPath();\n    context.arc(centre, centre, radius, 0, Math.PI * 2);\n    context.clip();\n    context.fillStyle = color;\n    context.globalAlpha = 0.035;\n    context.fillRect(0, 0, size, size);\n    const lit = context.createRadialGradient(\n      centre - radius * 0.42,\n      centre - radius * 0.48,\n      radius * 0.05,\n      centre - radius * 0.2,\n      centre - radius * 0.2,\n      radius * 1.15\n    );\n    lit.addColorStop(0, color);\n    lit.addColorStop(1, 'transparent');\n    context.fillStyle = lit;\n    context.globalAlpha = 0.11;\n    context.fillRect(0, 0, size, size);\n    context.restore();\n\n    /* The land. */\n    const sinLongitude = Math.sin(longitude * RADIANS);\n    const cosLongitude = Math.cos(longitude * RADIANS);\n    const sinLatitude = Math.sin(latitude * RADIANS);\n    const cosLatitude = Math.cos(latitude * RADIANS);\n\n    // Finer than a degree buys nothing: that is what the land grid resolves.\n    const step = clamp(Math.round((DOT_SPACING / (radius * RADIANS)) * 10) / 10, 1, 6);\n    const dots = dotsFor(step);\n    const dotRadius = Math.max(0.6, radius * 0.0062);\n\n    // One path per level of brightness. Filling several thousand dots one at a\n    // time is several thousand state changes; this is eight.\n    const paths: Path2D[] = Array.from({ length: ALPHA_STEPS }, () => new Path2D());\n    for (let i = 0; i < dots.count; i += 1) {\n      const dx = dots.x[i] ?? 0;\n      const dy = dots.y[i] ?? 0;\n      const dz = dots.z[i] ?? 0;\n      // Turn about the vertical axis, then tip towards the viewer.\n      const x = dx * cosLongitude - dz * sinLongitude;\n      const forward = dx * sinLongitude + dz * cosLongitude;\n      const depth = dy * sinLatitude + forward * cosLatitude;\n      if (depth <= 0) continue;\n      const y = dy * cosLatitude - forward * sinLatitude;\n\n      const light = Math.max(0, x * LIGHT.x + y * LIGHT.y + depth * LIGHT.z);\n      // Dots fade out towards the edge. They are seen nearly side on there,\n      // packed into lines, and at full strength the rim reads as a bright ring\n      // around a globe whose middle is the part worth looking at.\n      const turned = Math.min(1, depth / 0.55);\n      const edge = turned * turned * (3 - 2 * turned);\n      const alpha = (0.22 + 0.78 * light) * edge;\n      const level = Math.min(ALPHA_STEPS - 1, Math.floor(alpha * ALPHA_STEPS));\n      const path = paths[level];\n      if (!path) continue;\n      const px = centre + x * radius;\n      const py = centre - y * radius;\n      // Dots facing the viewer are drawn a little larger than those turned\n      // away, as they would be seen.\n      const r = dotRadius * (0.72 + 0.42 * depth);\n      path.moveTo(px + r, py);\n      path.arc(px, py, r, 0, Math.PI * 2);\n    }\n    context.fillStyle = color;\n    paths.forEach((path, level) => {\n      context.globalAlpha = (level + 0.5) / ALPHA_STEPS;\n      context.fill(path);\n    });\n    context.globalAlpha = 1;\n\n    /* The markers, which are told where they are rather than drawn here. */\n    const project: Project = (markerLatitude, markerLongitude) => {\n      const cos = Math.cos(markerLatitude * RADIANS);\n      const sin = Math.sin(markerLatitude * RADIANS);\n      const dx = cos * Math.sin(markerLongitude * RADIANS);\n      const dz = cos * Math.cos(markerLongitude * RADIANS);\n      const x = dx * cosLongitude - dz * sinLongitude;\n      const forward = dx * sinLongitude + dz * cosLongitude;\n      return {\n        x: centre + x * radius,\n        y: centre - (sin * cosLatitude - forward * sinLatitude) * radius,\n        depth: sin * sinLatitude + forward * cosLatitude,\n      };\n    };\n    for (const listener of listeners) listener(project);\n  };\n\n  const resting = () => {\n    const turning = spin !== 0 && !hovering && !dragging && visible && !reducedMotion.matches;\n    return !dragging && target === null && Math.abs(velocity) < 1 && !turning;\n  };\n\n  const tick = (now: number) => {\n    frame = 0;\n    if (destroyed) return;\n    const elapsed = last === 0 ? 0 : Math.min(0.05, (now - last) / 1000);\n    last = now;\n\n    if (target) {\n      // Closes most of the remaining distance each frame, so a turn starts\n      // quickly and settles gently however far it has to go.\n      const ease = reducedMotion.matches ? 1 : 1 - Math.exp(-elapsed * 6);\n      const remaining = shortest(longitude, target.longitude);\n      longitude = wrap(longitude + remaining * ease);\n      latitude += (target.latitude - latitude) * ease;\n      if (Math.abs(remaining) < 0.05 && Math.abs(target.latitude - latitude) < 0.05) {\n        longitude = target.longitude;\n        latitude = target.latitude;\n        target = null;\n      }\n    } else if (!dragging && Math.abs(velocity) >= 1) {\n      longitude = wrap(longitude + velocity * elapsed);\n      velocity *= Math.exp(-elapsed * 3.2);\n    } else if (!dragging && !hovering && visible && spin !== 0 && !reducedMotion.matches) {\n      longitude = wrap(longitude + spin * elapsed);\n    }\n\n    draw();\n\n    if (resting()) {\n      velocity = 0;\n      last = 0;\n      onSettle([longitude, latitude]);\n      return;\n    }\n    frame = requestAnimationFrame(tick);\n  };\n\n  const invalidate = () => {\n    if (frame === 0 && !destroyed) frame = requestAnimationFrame(tick);\n  };\n\n  /* Size the canvas to its box, at the density of the screen it is on. */\n  const resize = new ResizeObserver(() => {\n    const next = Math.round(canvas.clientWidth);\n    if (next === 0) return;\n    const ratio = window.devicePixelRatio || 1;\n    size = next;\n    canvas.width = Math.round(next * ratio);\n    canvas.height = Math.round(next * ratio);\n    // Synchronously, not on the next frame: setting the size cleared the\n    // canvas, and waiting a frame to redraw would flash it empty.\n    draw();\n    invalidate();\n  });\n  resize.observe(canvas);\n\n  /* A theme change repaints nothing by itself, since nothing is animating. */\n  const theme = new MutationObserver(invalidate);\n  theme.observe(document.documentElement, {\n    attributes: true,\n    attributeFilter: ['class', 'style', 'data-theme'],\n  });\n\n  /* Turning on its own stops while the globe is off screen. */\n  const viewport = new IntersectionObserver(([entry]) => {\n    visible = entry?.isIntersecting ?? true;\n    if (visible) invalidate();\n  });\n  viewport.observe(canvas);\n\n  const onMotionPreference = () => invalidate();\n  reducedMotion.addEventListener('change', onMotionPreference);\n\n  return {\n    rotateBy(byLongitude, byLatitude = 0) {\n      const from = target ?? { longitude, latitude };\n      target = {\n        longitude: wrap(from.longitude + byLongitude),\n        latitude: clamp(from.latitude + byLatitude, -MAX_TILT, MAX_TILT),\n      };\n      velocity = 0;\n      invalidate();\n    },\n    rotateTo([toLongitude, toLatitude]) {\n      target = { longitude: wrap(toLongitude), latitude: clamp(toLatitude, -MAX_TILT, MAX_TILT) };\n      velocity = 0;\n      invalidate();\n    },\n    getRotation: () => [longitude, latitude],\n    setAutoRotate(speed) {\n      spin = speed;\n      invalidate();\n    },\n    dragStart() {\n      dragging = true;\n      target = null;\n      velocity = 0;\n      samples = [];\n      invalidate();\n    },\n    dragMove(dx, dy, vertical) {\n      if (!dragging || size === 0) return;\n      // A point on the surface stays under the pointer: an arc the length of\n      // the movement, on a sphere of this radius.\n      const degrees = 1 / ((size / 2) * RADIANS);\n      longitude = wrap(longitude - dx * degrees);\n      if (vertical) latitude = clamp(latitude + dy * degrees, -MAX_TILT, MAX_TILT);\n      const now = performance.now();\n      samples.push({ time: now, dx: -dx * degrees });\n      samples = samples.filter((sample) => now - sample.time < 120);\n      invalidate();\n    },\n    dragEnd() {\n      if (!dragging) return;\n      dragging = false;\n      const now = performance.now();\n      const recent = samples.filter((sample) => now - sample.time < 120);\n      const first = recent[0];\n      if (first && recent.length > 1 && !reducedMotion.matches) {\n        const span = Math.max(16, now - first.time) / 1000;\n        velocity = clamp(recent.reduce((sum, sample) => sum + sample.dx, 0) / span, -540, 540);\n      }\n      invalidate();\n    },\n    setHover(next) {\n      hovering = next;\n      invalidate();\n    },\n    invalidate,\n    destroy() {\n      destroyed = true;\n      if (frame !== 0) cancelAnimationFrame(frame);\n      resize.disconnect();\n      theme.disconnect();\n      viewport.disconnect();\n      reducedMotion.removeEventListener('change', onMotionPreference);\n    },\n  };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Root                                                                       */\n/* -------------------------------------------------------------------------- */\n\nexport interface PlanetProps extends Omit<ComponentProps<'div'>, 'children'> {\n  children?: ReactNode;\n  /**\n   * The coordinate facing the viewer to begin with, as `[longitude, latitude]`\n   * in degrees. Read once: turning the globe afterwards does not change it.\n   */\n  defaultRotation?: PlanetRotation;\n  /**\n   * Turn on its own, at this many degrees of longitude a second. `true` is 6.\n   * It stops while the pointer is over the globe, while it is being dragged,\n   * while it is off screen, and for anyone who has asked for reduced motion.\n   */\n  autoRotate?: boolean | number;\n  /** Let the globe be dragged and turned with the arrow keys. */\n  interactive?: boolean;\n  /** Called with the coordinate facing the viewer each time the globe comes to rest. */\n  onRotationChange?: (rotation: PlanetRotation) => void;\n}\n\nconst PlanetRoot = forwardRef<PlanetHandle, PlanetProps>(function PlanetRoot(\n  {\n    children,\n    className,\n    defaultRotation = [0, 15],\n    autoRotate = false,\n    interactive = true,\n    onRotationChange,\n    ...props\n  },\n  ref\n) {\n  const slots = planetVariants();\n  const engine = useRef<Engine | null>(null);\n  const listeners = useRef(new Set<(project: Project) => void>()).current;\n  // The opening view, kept from the first render. The prop says where the\n  // globe starts, not where it is.\n  const initial = useRef(defaultRotation).current;\n  const speed = autoRotate === true ? 6 : autoRotate === false ? 0 : autoRotate;\n\n  const subscribe = useCallback(\n    (listener: (project: Project) => void) => {\n      listeners.add(listener);\n      // A marker added later is placed straight away, not on the next turn.\n      engine.current?.invalidate();\n      return () => {\n        listeners.delete(listener);\n      };\n    },\n    [listeners]\n  );\n\n  const rotateBy = useCallback((longitude: number, latitude?: number) => {\n    engine.current?.rotateBy(longitude, latitude);\n  }, []);\n  const rotateTo = useCallback((rotation: PlanetRotation) => {\n    engine.current?.rotateTo(rotation);\n  }, []);\n\n  useImperativeHandle(\n    ref,\n    () => ({\n      rotateBy,\n      rotateTo,\n      getRotation: () => engine.current?.getRotation() ?? initial,\n    }),\n    [rotateBy, rotateTo, initial]\n  );\n\n  const context = useMemo<PlanetContextValue>(\n    () => ({\n      subscribe,\n      listeners,\n      rotateBy,\n      rotateTo,\n      engine,\n      defaultRotation: initial,\n      autoRotate: speed,\n      interactive,\n      onRotationChange,\n    }),\n    [subscribe, listeners, rotateBy, rotateTo, initial, speed, interactive, onRotationChange]\n  );\n\n  return (\n    <PlanetContext.Provider value={context}>\n      <div className={slots.root({ className })} {...props}>\n        {children}\n      </div>\n    </PlanetContext.Provider>\n  );\n});\nPlanetRoot.displayName = 'Planet';\n\n/* -------------------------------------------------------------------------- */\n/* Globe                                                                      */\n/* -------------------------------------------------------------------------- */\n\nexport interface PlanetGlobeProps extends Omit<ComponentProps<'div'>, 'children' | 'color'> {\n  /** The markers. Anything else placed here is laid over the globe unmoved. */\n  children?: ReactNode;\n  /** Which of the theme's roles the light round the globe is drawn in. */\n  color?: PlanetMarkerColor;\n  /** What a screen reader calls the globe. */\n  label?: string;\n}\n\n/** Degrees one press of an arrow key turns the globe. */\nconst KEY_STEP = 15;\n\nfunction PlanetGlobe({\n  children,\n  className,\n  color = 'primary',\n  label = 'Globe',\n  render,\n  ...props\n}: PlanetGlobeProps & RenderProp<'div'>) {\n  const slots = planetVariants({ glow: color });\n  const planet = usePlanetContext('Planet.Globe');\n  const canvas = useRef<HTMLCanvasElement>(null);\n  const pointer = useRef<{ id: number; x: number; y: number; vertical: boolean } | null>(null);\n  // Where the globe has come to rest, for the slider semantics below. Updated\n  // when it settles, not while it turns, so turning never re-renders.\n  const [settled, setSettled] = useState<PlanetRotation>(planet.defaultRotation);\n\n  // Read through a ref so a new callback from the parent does not rebuild the\n  // engine and throw away where the globe has been turned to.\n  const onRotationChange = useRef(planet.onRotationChange);\n  onRotationChange.current = planet.onRotationChange;\n\n  const { engine, defaultRotation, listeners } = planet;\n  // The glow is read off the canvas when it draws, so a new colour needs a\n  // new drawing to be seen.\n  useEffect(() => {\n    engine.current?.invalidate();\n  }, [engine, color]);\n\n  useEffect(() => {\n    if (!canvas.current) return;\n    const created = createEngine({\n      canvas: canvas.current,\n      rotation: defaultRotation,\n      autoRotate: 0,\n      listeners,\n      onSettle: (rotation) => {\n        setSettled((current) =>\n          Math.round(current[0]) === Math.round(rotation[0]) && Math.round(current[1]) === Math.round(rotation[1])\n            ? current\n            : rotation\n        );\n        onRotationChange.current?.(rotation);\n      },\n    });\n    engine.current = created;\n    return () => {\n      created.destroy();\n      engine.current = null;\n    };\n    // The engine is built once per canvas. What can change later, the\n    // turning speed, is pushed into it by the effect below.\n  }, [engine, defaultRotation, listeners]);\n\n  useEffect(() => {\n    engine.current?.setAutoRotate(planet.autoRotate);\n  }, [engine, planet.autoRotate]);\n\n  const onPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {\n    if (!planet.interactive || event.button !== 0) return;\n    // A press that starts on a marker belongs to the marker.\n    if ((event.target as HTMLElement).closest('[data-planet-marker]')) return;\n    event.currentTarget.setPointerCapture(event.pointerId);\n    pointer.current = {\n      id: event.pointerId,\n      x: event.clientX,\n      y: event.clientY,\n      // A finger moving vertically is scrolling the page, so only a mouse or a\n      // pen tips the globe as well as turning it.\n      vertical: event.pointerType !== 'touch',\n    };\n    engine.current?.dragStart();\n  };\n\n  const onPointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {\n    const active = pointer.current;\n    if (!active || active.id !== event.pointerId) return;\n    engine.current?.dragMove(event.clientX - active.x, event.clientY - active.y, active.vertical);\n    active.x = event.clientX;\n    active.y = event.clientY;\n  };\n\n  const endDrag = (event: ReactPointerEvent<HTMLDivElement>) => {\n    if (pointer.current?.id !== event.pointerId) return;\n    pointer.current = null;\n    engine.current?.dragEnd();\n  };\n\n  const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n    if (!planet.interactive) return;\n    const turn: Record<string, [number, number]> = {\n      ArrowLeft: [-KEY_STEP, 0],\n      ArrowRight: [KEY_STEP, 0],\n      ArrowUp: [0, KEY_STEP],\n      ArrowDown: [0, -KEY_STEP],\n    };\n    const by = turn[event.key];\n    if (!by) return;\n    event.preventDefault();\n    planet.rotateBy(by[0], by[1]);\n  };\n\n  const longitude = Math.round(settled[0]);\n  const side = longitude === 0 ? '' : longitude > 0 ? ' east' : ' west';\n\n  return useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps(\n      {\n        role: planet.interactive ? 'slider' : 'img',\n        'aria-label': label,\n        'aria-orientation': planet.interactive ? 'horizontal' : undefined,\n        'aria-valuemin': planet.interactive ? -180 : undefined,\n        'aria-valuemax': planet.interactive ? 180 : undefined,\n        'aria-valuenow': planet.interactive ? longitude : undefined,\n        'aria-valuetext': planet.interactive ? `Centred on ${Math.abs(longitude)} degrees${side}` : undefined,\n        tabIndex: planet.interactive ? 0 : undefined,\n        className: slots.globe({ className }),\n        onPointerDown: onPointerDown,\n        onPointerMove: onPointerMove,\n        onPointerUp: endDrag,\n        onPointerCancel: endDrag,\n        onPointerEnter: () => engine.current?.setHover(true),\n        onPointerLeave: () => engine.current?.setHover(false),\n        onKeyDown: onKeyDown,\n        children: (\n          <>\n            <canvas ref={canvas} aria-hidden=\"true\" className={slots.canvas()} />\n            <div className={slots.markers()}>{children}</div>\n          </>\n        ),\n      },\n      props\n    ),\n  });\n}\nPlanetGlobe.displayName = 'Planet.Globe';\n\n/* -------------------------------------------------------------------------- */\n/* Marker                                                                     */\n/* -------------------------------------------------------------------------- */\n\n/** Which of the theme's roles a marker's dot is drawn in. */\nexport type PlanetMarkerColor = 'primary' | 'info' | 'success' | 'warning' | 'destructive';\n\nexport interface PlanetMarkerProps extends Omit<ComponentProps<'div'>, 'children' | 'color' | 'onClick'> {\n  /** Degrees north of the equator. South is negative. */\n  lat: number;\n  /** Degrees east of Greenwich. West is negative. */\n  lng: number;\n  /** A caption in a tag above the pin. */\n  label?: ReactNode;\n  /** A figure after the label, in a quieter colour. */\n  count?: ReactNode;\n  /** Which of the theme's roles the dot, or the ring of a pin with children, is drawn in. */\n  color?: PlanetMarkerColor;\n  /** Spreads a fading ring from the marker, for one that wants looking at. */\n  pulse?: boolean;\n  /**\n   * Called when the marker is pressed. Passing this makes the marker a\n   * button: it takes focus, and Enter or Space press it.\n   */\n  onClick?: (event: MouseEvent<HTMLButtonElement>) => void;\n  /** Draws the marker as the one that has been picked. */\n  selected?: boolean;\n  /** What a screen reader calls a marker that can be pressed. Defaults to its label. */\n  name?: string;\n  /** Extra classes for the label. */\n  labelClassName?: string;\n  /** What is drawn at the coordinate: an avatar, an icon. A dot when left out. */\n  children?: ReactNode;\n}\n\n/** How near the edge a marker starts to fade, as depth. */\nconst FADE_FROM = 0.28;\nconst FADE_TO = 0.06;\n\nfunction PlanetMarker({\n  lat,\n  lng,\n  label,\n  count,\n  color = 'primary',\n  pulse = false,\n  onClick,\n  selected = false,\n  name,\n  className,\n  labelClassName,\n  children,\n  render,\n  ...props\n}: PlanetMarkerProps & RenderProp<'div'>) {\n  const slots = planetVariants({ color, selected });\n  const planet = usePlanetContext('Planet.Marker');\n  const element = useRef<HTMLDivElement>(null);\n  const { subscribe } = planet;\n\n  useEffect(\n    () =>\n      subscribe((project) => {\n        const node = element.current;\n        if (!node) return;\n        const { x, y, depth } = project(lat, lng);\n        const shown = clamp((depth - FADE_TO) / (FADE_FROM - FADE_TO), 0, 1);\n        // `translate` is its own property, so it does not replace the classes'\n        // transform that centres the pin on the point.\n        node.style.translate = `${x.toFixed(2)}px ${y.toFixed(2)}px`;\n        node.style.opacity = shown.toFixed(3);\n        node.style.pointerEvents = shown > 0.5 ? '' : 'none';\n        // A marker on the far side cannot be seen, so it cannot be tabbed to.\n        node.toggleAttribute('inert', shown <= 0.5);\n        // Nearer the viewer is on top, as it would be on a real globe.\n        node.style.zIndex = String(Math.round(depth * 100));\n      }),\n    [subscribe, lat, lng]\n  );\n\n  const body = (\n    <>\n      {label !== undefined && label !== null ? (\n        <span className={slots.label({ className: labelClassName })}>\n          {label}\n          {count !== undefined && count !== null ? <span className={slots.count()}>{count}</span> : null}\n        </span>\n      ) : null}\n      {pulse ? <span aria-hidden=\"true\" className={slots.pulse()} /> : null}\n      {children !== undefined && children !== null ? (\n        <span data-pin=\"\" className={slots.pin()}>\n          {children}\n        </span>\n      ) : (\n        <span data-pin=\"\" className={slots.dot()} />\n      )}\n    </>\n  );\n\n  return useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps(\n      {\n        ref: element,\n        'data-planet-marker': '',\n        className: slots.marker({ className }),\n        children: (\n          <>\n            {onClick ? (\n              <button\n                type=\"button\"\n                aria-label={name ?? (typeof label === 'string' ? label : undefined)}\n                aria-pressed={selected}\n                className={slots.press()}\n                onClick={onClick}\n                // Kept from the globe, which would otherwise take the press as the\n                // start of a drag and the click would never arrive.\n                onPointerDown={(event) => event.stopPropagation()}\n                onKeyDown={(event) => event.stopPropagation()}\n              >\n                {body}\n              </button>\n            ) : (\n              body\n            )}\n          </>\n        ),\n      },\n      props\n    ),\n  });\n}\nPlanetMarker.displayName = 'Planet.Marker';\n\n/* -------------------------------------------------------------------------- */\n/* Controls                                                                   */\n/* -------------------------------------------------------------------------- */\n\nexport interface PlanetControlsProps extends Omit<ComponentProps<'div'>, 'children'> {\n  /** Degrees of longitude one press turns the globe. */\n  step?: number;\n  /** Words to show between the two arrows. Without them it is the arrows alone. */\n  children?: ReactNode;\n}\n\nconst Arrow = ({ flipped }: { flipped?: boolean }) => (\n  <svg\n    viewBox=\"0 0 16 16\"\n    aria-hidden=\"true\"\n    className={flipped ? 'size-4 -scale-x-100' : 'size-4'}\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"1.6\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n  >\n    <path d=\"M3 8h10M9 4l4 4-4 4\" />\n  </svg>\n);\n\nfunction PlanetControls({ step = 60, className, children, render, ...props }: PlanetControlsProps & RenderProp<'div'>) {\n  const slots = planetVariants();\n  const planet = usePlanetContext('Planet.Controls');\n\n  return useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps(\n      {\n        role: 'group',\n        'aria-label': 'Turn the globe',\n        className: slots.controls({ className }),\n        children: (\n          <>\n            <button\n              type=\"button\"\n              aria-label=\"Turn west\"\n              className={slots.control()}\n              onClick={() => planet.rotateBy(-step)}\n            >\n              <Arrow flipped />\n            </button>\n            {children !== undefined && children !== null ? <span className={slots.caption()}>{children}</span> : null}\n            <button\n              type=\"button\"\n              aria-label=\"Turn east\"\n              className={slots.control()}\n              onClick={() => planet.rotateBy(step)}\n            >\n              <Arrow />\n            </button>\n          </>\n        ),\n      },\n      props\n    ),\n  });\n}\nPlanetControls.displayName = 'Planet.Controls';\n\n/* -------------------------------------------------------------------------- */\n/* The list                                                                   */\n/* -------------------------------------------------------------------------- */\n\nexport interface PlanetListProps extends ComponentProps<'ul'> {}\n\n/**\n * The places on the globe as a list, in two columns, for picking one by name.\n * Put it beside or under the globe and turn the globe to whatever is picked.\n */\nfunction PlanetList({ className, render, ...props }: PlanetListProps & RenderProp<'ul'>) {\n  return useRender({\n    defaultTagName: 'ul',\n    render,\n    props: mergeProps({ 'data-slot': 'planet-list', className: planetVariants().list({ className }) }, props),\n  });\n}\nPlanetList.displayName = 'Planet.List';\n\nexport interface PlanetListItemProps extends Omit<ComponentProps<'button'>, 'color'> {\n  /** The colour of the dot before the name. Match it to the place's marker. */\n  color?: PlanetMarkerColor;\n  /** Draws the row as the one that has been picked. */\n  selected?: boolean;\n}\n\n/** One place in `Planet.List`: a dot in its marker's colour, and its name. A button. */\nfunction PlanetListItem({\n  color = 'primary',\n  selected = false,\n  className,\n  children,\n  render,\n  ...props\n}: PlanetListItemProps & RenderProp<'button'>) {\n  const slots = planetVariants({ color });\n  // `render` draws the row's button as something else, such as a link to the place.\n  const button = useRender({\n    defaultTagName: 'button',\n    render,\n    props: mergeProps(\n      {\n        type: render ? undefined : ('button' as const),\n        'aria-pressed': selected,\n        className: slots.listItem({ className }),\n        children: (\n          <>\n            <span aria-hidden=\"true\" className={slots.listDot()} />\n            <span className={slots.listName()}>{children}</span>\n          </>\n        ),\n      },\n      props\n    ),\n  });\n  return <li data-slot=\"planet-list-item\">{button}</li>;\n}\nPlanetListItem.displayName = 'Planet.ListItem';\n\nexport const Planet = Object.assign(PlanetRoot, {\n  Globe: PlanetGlobe,\n  Marker: PlanetMarker,\n  Controls: PlanetControls,\n  List: PlanetList,\n  ListItem: PlanetListItem,\n});\n\n/*\n * The parts again, by name. `Planet.Globe` is a property of a component, and a\n * Server Component cannot read a property off something imported from a\n * client module, only pass the import itself along. So a page that is a\n * Server Component writes `<PlanetGlobe>`; anywhere else the dotted form is\n * the same thing.\n */\nexport { PlanetGlobe, PlanetMarker, PlanetControls, PlanetList, PlanetListItem };\n"
    },
    {
      "path": "components/panelui/planet/land.ts",
      "type": "registry:component",
      "target": "@components/panelui/planet/land.ts",
      "content": "// Generated by scripts/generate-land.mjs from Natural Earth's 1:110m land polygons,\n// which are in the public domain. Do not edit.\n\n/** Cells per row and rows in the grid: one cell per degree. */\nexport const LAND_COLUMNS = 360;\nexport const LAND_ROWS = 180;\n\n/**\n * Where the land is. One bit per cell, row 0 northernmost and column 0\n * westernmost, packed eight to a byte from the low bit and base64 encoded.\n */\nexport const LAND =\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADg' +\n  '/wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA/v8/AOD//z8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAD+///D/////5//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAH/j/+//////8PAAAAgB0A' +\n  'AHgAAAAAAD8AAAAAAAAAAAAAAAAAAAAAAAAAAOD//wP8//////8BAACAfx4AAAAAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAA4' +\n  '+I73//n///////8AAAAAfwAAAAAAAAAAAAAfAAAAAAAAAAAAAAAAAAAAAOAQACDwP4D///////8BAAAAPAQAAAAAAAAAAAAY' +\n  'AAAAAAAAAAAAAAAAAAAAADzAgPP5H8D//////38AAAAAAAAAAAAA4AEAAAD+DwAAAAAAAAAAAAAAAAAAAIC/g7PfBwAA/v//' +\n  '//8AAAAAAAAAAADgBwAAwP//PwAA4B8AAAAAAAAAAAAAAAAMAAD8AgAA+P////8AAAAAAAAAAABwAAAA/P//AwAAAAAAAAAA' +\n  'AAAAAAAAAP8Ahvc5OwAA8P///38AAAAAAAAAAAAcAADg////34cHAAcAAAAAAAAAAAAAgN9/xzf8RwAA4P///z8AAAAAAAAA' +\n  'AAAPAB7g//////8fgB8AAAAAAQAAAAAAAO//B3f8/wsA4P///z8AAAAAAAAAAAAPAG////////8f2f8HAAAAAAD4HwAAAAD8' +\n  'H/D4/38A4P///xMAAAAAAPAHAAAAgN//////////////DwAAAAD+///h/4//H+/Bg/8BwP7//w8AAAAAwP8fAAAAj9//////' +\n  '/////////88/AeD///////8DB8TPB/4AAP///wAAAAAA8P//AwDjf77/////////////////DwD////////////vh/kPwP//' +\n  'BwAAAAAA/P//H/P//5//////////////////34H8////////////Afw/gP//AQAAAAAA/v+fH////+//////////////////' +\n  '/vD///////////9fAPwYgP8PAOA/AAAA/+N/8P//////////////////////QID4///////////Pw/0DAP8HAMAfAACA//F/' +\n  '/v////////////////////8/AAP4///////////BBPAHAP4HAAAAAADg//z///////////////////////9/AID/////////' +\n  '//8AQ8ABAPwBAAAAAAD8P/7///////////////////////9fAMD//////////z8AwA8AAPgBAAAAAAD+H/7/////////////' +\n  '////////Of8BAID///z//////z8AwD8AAMABAAAAAAD+P/z///////////////////9/wH8AAAD8MwD//////x8AwH8MAAAA' +\n  'AAAAAAD+P2D///////////////////95cAAAAACAAwD8/////38AwP8eAAAAAAAAgAGcH/D//////////////////wEAOAAA' +\n  'AADADgDA/////38AgP8/AAAAAAAAwANAH/T//////////////////wAAfgAAAAAwAAAA//////8PgP8/AAAAAAAAwAFwD/7/' +\n  '////////////////PwAAfwAAAAAGAAAA//////8/wP//AAAAAAAAwAOwA/7/////////////////DwCAPwAAAIAAAAAA/v//' +\n  '////8///BwAAAAAAOAcgcP//////////////////HwAAHwAAAAAAAACA+P//////4///DwAAAAAAOA74////////////////' +\n  '/////wUADwAAAAAAAAAA8P//////4///DwAAAAAAGD///////////////////////wUAAwAAAAAAAAAA8P//////7///FwAA' +\n  'AAAAAB///////////////////////wUAAgAAAAAAAAAA0P//////////CAAAAAAAgOH//////////////////////w0AAAAA' +\n  'AAAAAAAAYP////////8xLAAAAAAAAPz//////////////////////wwAAAAAAAAAAAAAAP7//////38HfgAAAAAAgP//////' +\n  '/////////////////wQAAAAAAAAAAAAAAP3///////8HUAAAAAAAAP7/////////////////////fwQAAAAAAAAAAAAAAP//' +\n  '//////+XAAAAAAAAAPz///93/P//////////////PwwAAAAAAAAAAAAAAP////////9/AAAAAAAAAPj//f9j/v//////////' +\n  '////HwAAAAAAAAAAAAAAAP////////8MAAAAAAAAAPj/+P8B/P//////////////DwQAAAAAAAAAAAAAAP///////z8AAAAA' +\n  'AAAAcPzH8/8A8P//////////////Bx4AAAAAAAAAAAAAAP///////x8AAAAAAAAA+H+Aw/8AwP////////////9/AA8AAAAA' +\n  'AAAAAAAAAP///////x8AAAAAAAAA+D8Aj//w4f////////////8/AAAAAAAAAAAAAAAAAP///////wMAAAAAAAAA+B8wuE/+' +\n  '/////////////98fAAMAAAAAAAAAAAAAAP///////wMAAAAAAAAA+A8wEMf//////////////0cOAAMAAAAAAAAAAAAAAP7/' +\n  '/////wEAAAAAAAAA+A8AAI7//////////////wMOAAEAAAAAAAAAAAAAAP7//////wAAAAAAAAAA+AcABob/////////////' +\n  '/zccwAEAAAAAAAAAAAAAAPz//////wAAAAAAAAAAQOB/AAQ2/////////////x8c8AEAAAAAAAAAAAAAAPj//////wAAAAAA' +\n  'AAAAQPx/AAAA/////////////w8Y/gEAAAAAAAAAAAAAAPD/////fwAAAAAAAAAA4P8/AAAA/////////////w+EGwAAAAAA' +\n  'AAAAAAAAAMD/////HwAAAAAAAAAA8P9/AACA/////////////x/AAwAAAAAAAAAAAAAAAID/////DwAAAAAAAAAA+P//BwaA' +\n  '/////////////x/AAAAAAAAAAAAAAAAAAID9////BwAAAAAAAAAA/P//D3+E/////////////z9AAAAAAAAAAAAAAAAAAAD5' +\n  '////BwAAAAAAAAAA/P//f////////////////x8AAAAAAAAAAAAAAAAAAADy/x8GBgAAAAAAAAAA/P/////v/8//////////' +\n  '/z8AAAAAAAAAAAAAAAAAAAD0/w8ABgAAAAAAAAAA/v////+//4///////////z8AAAAAAAAAAAAAAAAAAADu/wcADgAAAAAA' +\n  'AACA//////8f/x/+/////////x8AAAAAAAAAAAAAAAAAAACI/wcALAAAAAAAAADA//////8//z/g/////////w8AAAAAAAAA' +\n  'AAAAAAAAAACQ/wcACAAAAAAAAADg//////9//r+A/////////wcAAAAAAAAAAAAAAAAAAAAQ/wMAAAAAAAAAAADg//////9/' +\n  '/n8cgP///////ycAAAAAAAAAAAAAAAAAAAAA/gMAAAAAAAAAAADw////////+P9/AP///////xEAAAAAAAAAAAAAAAAAAAAA' +\n  '/AMAHQAAAAAAAADw////////+P//APz/f///fxAAAAAAAAAAAAAAAAAAAAAA+AcAYAAAAAAAAAD4////////+f9/APz/B///' +\n  'BgAAAAAAAAAAAAAAAAAAAAAA/AccgAEAAAAAAADw////////8f9/AOD/B/5/AAAAAAAAAAAAAAAAAQAAAAAA+A8eADgAAAAA' +\n  'AADw////////4f8/AOD/Afw/BgAAAAAAAAAAAAAAAAAAAAAA8J8PAPwCAAAAAADw////////4/8fAOD/APw/AgAAAAAAAAAA' +\n  'AAAAAAAAAAAAgP8PAAAAAAAAAADw////////x/8HAOB/APx/ADAAAAAAAAAAAAAAAAAAAAAAAP4PAAAAAAAAAAD4////////' +\n  'h/8BAOA/APz/ADAAAAAAAAAAAAAAAAAAAAAAAID/AAAAAAAAAAD4////////j/8AAMAPAMD/ATAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAD/AQAAAAAAAAD4////////nx8AAMAPAMD/ATAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAAAAAD4////////vwcAAIAPAMD/' +\n  'AcAAAAAAAAAAAAAAAAAAAAAAAADgAQAAAAAAAAD4////////fwAAAIAPAMD8AQABAAAAAAAAAAAAAAAAAAAAAADAAAgAAAAA' +\n  'AADw////////f2AAAAAPAID4AUACAAAAAAAAAAAAAAAAAAAAAADAAe9zAAAAAADg/////////34AAAAPAEBwAAgAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAE+9/AAAAAADA/////////38AAAAXAEAgAAQCAAAAAAAAAAAAAAAAAAAAAAAAz///AAAAAACA////////' +\n  '/z8AAAASAMAAAIACAAAAAAAAAAAAAAAAAAAAAAAAyP//AQAAAACA/////////z8AAAAwAIAAAEAHAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAgP//AwAAAAAA/v///////x8AAAAwAAADAAMBAAAAAAAAAAAAAAAAAAAAAAAAwP//fwAAAAAA/A/+/////x8AAAAAAAAH' +\n  'AAcAAAAAAAAAAAAAAAAAAAAAAAAAgP///wAAAAAAEAD8/////w8AAAAAADAGwAcAAAAAAAAAAAAAAAAAAAAAAAAAgP///wEA' +\n  'AAAAAADA/////wcAAAAAAGAG4AEAAAAAAAAAAAAAAAAAAAAAAAAA4P///wEAAAAAAADA/////wMAAAAAAMAM+AMAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAA4P///wMAAAAAAADg/////wEAAAAAAIAL/gMQAAAAAAAAAAAAAAAAAAAAAAAA8P///wMAAAAAAADg////' +\n  'fwAAAAAAAIAH/vMQAAAAAAAAAAAAAAAAAAAAAAAA8P///wcAAAAAAADg////PwAAAAAAAAAP/gMAAQAAAAAAAAAAAAAAAAAA' +\n  'AAAA+P///38AAAAAAADg////PwAAAAAAAAAO/DmAAwAAAAAAAAAAAAAAAAAAAAAA+P///38BAAAAAADA////HwAAAAAAAAA+' +\n  '/DkA8gAAAAAAAAAAAAAAAAAAAAAA8P////8fAAAAAACA////DwAAAAAAAAA8wChE/gcAAAAAAAAAAAAAAAAAAAAA+P////8/' +\n  'AAAAAACA////BwAAAAAAAAA4AEAA8B8AAAAAAAAAAAAAAAAAAAAA+P//////AQAAAAAA////BwAAAAAAAAAwAEAAxD8NAAAA' +\n  'AAAAAAAAAAAAAAAA+P//////AQAAAAAA////BwAAAAAAAADAAQAAxP+AAAAAAAAAAAAAAAAAAAAA8P//////AQAAAAAA/v//' +\n  'BwAAAAAAAACAHwAAwH8ABAAAAAAAAAAAAAAAAAAA4P//////AQAAAAAA/v//BwAAAAAAAAAAQFYEAMcAAAAAAAAAAAAAAAAA' +\n  'AAAA4P//////AAAAAAAA/v//DwAAAAAAAAAAAAgBAIABMAAAAAAAAAAAAAAAAAAAwP//////AAAAAAAA/v//DwAAAAAAAAAA' +\n  'AAAAAAAEAAAAAAAAAAAAAAAAAAAAgP////9/AAAAAAAA/P//DwAAAAAAAAAAAAAAAQQAAAAAAAAAAAAAAAAAAAAAgP////8/' +\n  'AAAAAAAA/v//HyAAAAAAAAAAAACAHwQAAAAAAAAAAAAAAAAAAAAAAP////8fAAAAAAAA/v//HyAAAAAAAAAAAADADwwAAAAA' +\n  'AAAAAAAAAAAAAAAAAP////8fAAAAAAAA////HzAAAAAAAAAAAADsDxwAAAAAAAAAAAAAAAAAAAAAAP7///8fAAAAAAAA////' +\n  'DzgAAAAAAAAAAAD/DxwAAAAAAAAAAAAAAAAAAAAAAPj///8fAAAAAAAA////Dz8AAAAAAAAAAAD/Pz4AAAiAAAAAAAAAAAAA' +\n  'AAAAAOD///8fAAAAAAAA////Ax8AAAAAAAAAAMD//z4AAABAAAAAAAAAAAAAAAAAAMD///8PAAAAAAAA////AB8AAAAAAAAA' +\n  'AMD//z8AAAAAAAAAAAAAAAAAAAAAAMD///8PAAAAAAAA/v9/AB8AAAAAAAAAAOD//38AAAAAAAAAAAAAAAAAAAAAAMD///8P' +\n  'AAAAAAAA/v9/AB8AAAAAAAAAAPz///8BAAEAAAAAAAAAAAAAAAAAAMD///8HAAAAAAAA/P9/gA8AAAAAAAAAgP////8BAAIA' +\n  'AAAAAAAAAAAAAAAAAMD///8DAAAAAAAA/P//gA8AAAAAAAAAwP////8DAAAAAAAAAAAAAAAAAAAAAMD//38AAAAAAAAA/P9/' +\n  'AA8AAAAAAAAAwP////8HAAAAAAAAAAAAAAAAAAAAAOD//x8AAAAAAAAA+P9/AAcAAAAAAAAA4P////8PAAAAAAAAAAAAAAAA' +\n  'AAAAAOD//w8AAAAAAAAA+P8fAAIAAAAAAAAAwP////8fAAAAAAAAAAAAAAAAAAAAAOD//wcAAAAAAAAA+P8fAAAAAAAAAAAA' +\n  '4P////8fAAAAAAAAAAAAAAAAAAAAAOD//wcAAAAAAAAA+P8fAAAAAAAAAAAAwP////8fAAAAAAAAAAAAAAAAAAAAAOD//wcA' +\n  'AAAAAAAA8P8PAAAAAAAAAAAAgP////8/AAAAAAAAAAAAAAAAAAAAAOD//wMAAAAAAAAA4P8HAAAAAAAAAAAAgP////8fAAAA' +\n  'AAAAAAAAAAAAAAAAAPD//wMAAAAAAAAA4P8HAAAAAAAAAAAAgP////8fAAAAAAAAAAAAAAAAAAAAAPD//wEAAAAAAAAAwP8D' +\n  'AAAAAAAAAAAAAP////8fAAAAAAAAAAAAAAAAAAAAAOD//wAAAAAAAAAAwP8BAAAAAAAAAAAAAP8D/P8PAAAAAAAAAAAAAAAA' +\n  'AAAAAPD/fwAAAAAAAAAAwH8AAAAAAAAAAAAAgP8A2P8HAAAAAAAAAAAAAAAAAAAAAPD/OwAAAAAAAAAAgAEAAAAAAAAAAAAA' +\n  'AAcA6P8HAAAAAAAAAAAAAAAAAAAAAPj/BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwP8DAAACAAAAAAAAAAAAAAAAAPj/BwAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8DAAAEAAAAAAAAAAAAAAAAAPz/BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8DAAAI' +\n  'AAAAAAAAAAAAAAAAAPj/AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGgAAAA4AAAAAAAAAAAAAAAAAPg/AAAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAAAPw/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAA' +\n  'AAAAAPwHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAALAAAAAAAAAAAAAAAAAPwfAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAAOAAAIADAAAAAAAAAAAAAAAAAPgHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAMABAAAAAAAAAAAAAAAAAPwHAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAAAP4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgA' +\n  'AAAAAAAAAAAAAAAAAP4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAP4DAAAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAAP8AAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAH6AAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAAPADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAIAfAAAAAAAe' +\n  'eADADwAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAPD/AADA/////////z8AAAAAAAAAAAAAAAAAAAAAAAAHAAAA' +\n  'AAAAAAAAAAAAgP///wH4//////////8DAAAAAAAAAAAAAAAAAAAAADAfAAAAAAAAAAAAAADw8////wP+////////////AwAA' +\n  'AAAAAAAAAAAAAAAAAHA/AAAAAAAAAAA8//P//////+D/////////////HwAAAAAAAAAAAAAAAAAAAP5+AAAAAAAA5v//////' +\n  '//////D//////////////38AAAAAAAAAAAAAAAAAAAB+AAAAAACA/////////////////////////////38AAAAAAAAAYAYA' +\n  '4ON/OPh/AAAAAADw/////////////////////////////x8AAAAAAADwv/MXgP////8fAAAAAADw////////////////////' +\n  '/////////wMAAAAAAPz///////////8HAAAAAAD+/////////////////////////////wAAAAAAAPz//////////z8AAAAA' +\n  'AP///////////////////////////////wAAAADA/////////////wEAAAAA8P///////////////////////////////wAA' +\n  'AACH////////////PwAAAPgA/////////////////////////////////wcAAAAcgP//////////fwAAAP4BwP//////////' +\n  '////////////////////HwAAAAAAAP7//////////wf8wD8AwP//////////////////////////////DwAAAAAA/v//////' +\n  '//////8HAAD4////////////////////////////////HwAAAAAA+P//////////////4f//////////////////////////' +\n  '/////////wAAAAAA/P///////////////////////////////////////////////////x8A3x8AAP7/////////////////' +\n  '//////////////////////////////////8/AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';\n"
    }
  ]
}
